diff --git a/package.json b/package.json index 410cc164..fb8cc056 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@types/react-router-dom": "^5.3.3", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-hook-form": "^7.51.1", "react-router-dom": "^6.22.3" }, "devDependencies": { diff --git a/src/api/api.ts b/src/api/api.ts index e3c84ac8..5c18427c 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -3,13 +3,28 @@ const rootUrl = "https://intercom-manager.dev.eyevinn.technology/"; type TCreateProductionOptions = { name: string; - lines: string[]; + lines: { name: string }[]; }; type TCreateProductionResponse = { - productionId: string; + productionid: string; }; +type TLine = { + name: string; + id: string; + smbid: string; + connections: unknown; +}; + +type TFetchProductionResponse = { + name: string; + productionid: string; + lines: TLine[]; +}; + +type TListProductionsResponse = TFetchProductionResponse[]; + // TODO create generic response/error converter and response data validator export const API = { createProduction: ({ @@ -23,17 +38,15 @@ export const API = { }, body: JSON.stringify({ name, - lines: lines.map((line) => ({ - name: line, - })), + lines, }), }).then((response) => response.json()), // TODO add response types, headers - listProductions: () => + listProductions: (): Promise => fetch(`${rootUrl}productions/`, { method: "GET" }).then((response) => response.json() ), - fetchProduction: (id: number) => + fetchProduction: (id: number): Promise => fetch(`${rootUrl}productions/${id}`, { method: "GET" }).then((response) => response.json() ), @@ -45,7 +58,7 @@ export const API = { fetch(`${rootUrl}productions/${id}/lines`, { method: "GET" }).then( (response) => response.json() ), - fetchProductionLine: (productionId: number, lineId: number) => + fetchProductionLine: (productionId: number, lineId: number): Promise => fetch(`${rootUrl}productions/${productionId}/lines/${lineId}`, { method: "GET", }).then((response) => response.json()), diff --git a/src/components/landing-page/create-production.tsx b/src/components/landing-page/create-production.tsx index 30a97b25..9ca464c3 100644 --- a/src/components/landing-page/create-production.tsx +++ b/src/components/landing-page/create-production.tsx @@ -1,3 +1,6 @@ +import { SubmitHandler, useFieldArray, useForm } from "react-hook-form"; +import { useState } from "react"; +import styled from "@emotion/styled"; import { DisplayContainerHeader } from "./display-container-header.tsx"; import { DecorativeLabel, @@ -6,24 +9,95 @@ import { FormLabel, SubmitButton, } from "./form-elements.tsx"; +import { API } from "../../api/api.ts"; +type FormValues = { + productionName: string; + defaultLine: string; + lines: { name: string }[]; +}; + +const ProductionConfirmation = styled.div` + background: #91fa8c; + padding: 1rem; + border-radius: 0.5rem; + border: 1px solid #b2ffa1; + color: #1a1a1a; +`; export const CreateProduction = () => { + const [createdProductionId, setCreatedProductionId] = useState( + null + ); + const { + formState: { errors }, + control, + register, + handleSubmit, + } = useForm(); + const { fields, append } = useFieldArray({ + control, + name: "lines", + rules: { + minLength: 1, + }, + }); + + const onSubmit: SubmitHandler = (value) => { + API.createProduction({ + name: value.productionName, + lines: [{ name: value.defaultLine }, ...value.lines], + }) + .then((v) => setCreatedProductionId(v.productionid)) + .catch((e) => { + console.error(e); + // TODO error handling, display error in form or in global header? + }); + }; + return ( Create Production Production Name - + + {errors.productionName &&
BAD INPUT
} Line - + - - Line - - - Create Production + {fields.map((field, index) => ( + + Line + + + ))} + append({ name: "" })}> + Add Line + + + + Create Production + + {createdProductionId !== null && ( + + The production ID is: {createdProductionId.toString()} + + )}
); }; diff --git a/src/components/landing-page/form-elements.tsx b/src/components/landing-page/form-elements.tsx index 9a474ad9..96320ff2 100644 --- a/src/components/landing-page/form-elements.tsx +++ b/src/components/landing-page/form-elements.tsx @@ -28,4 +28,7 @@ export const DecorativeLabel = styled.span` export const SubmitButton = styled.button` font-size: 1.6rem; + padding: 1rem; + display: block; + margin: 0 0 2rem 0; `; diff --git a/src/components/landing-page/productions-list.tsx b/src/components/landing-page/productions-list.tsx index c7ed8d42..9c8f4194 100644 --- a/src/components/landing-page/productions-list.tsx +++ b/src/components/landing-page/productions-list.tsx @@ -1,4 +1,7 @@ import styled from "@emotion/styled"; +import { useEffect, useState } from "react"; +import { API } from "../../api/api.ts"; +import { TProduction } from "../production/types.ts"; const ProductionListContainer = styled.div` display: flex; @@ -28,42 +31,57 @@ const ProductionId = styled.div` `; export const ProductionsList = () => { + const [productions, setProductions] = useState([]); + + // TODO extract to separate hook file + // TODO trigger new fetch whenever a production is created + useEffect(() => { + let aborted = false; + + API.listProductions() + .then((result) => { + if (aborted) return; + + setProductions( + result + // pick laste 10 items + .slice(-10) + // display in reverse order + .toReversed() + // convert to local format + .map((prod) => { + return { + name: prod.name, + id: parseInt(prod.productionid, 10), + lines: prod.lines.map((l) => ({ + name: l.name, + id: parseInt(l.id, 10), + connected: false, + connectionId: "1", + participants: [], + })), + }; + }) + ); + }) + .catch(() => { + // TODO handle error/retry + }); + + return () => { + aborted = true; + }; + }, []); + return ( - - Mello - 123 - - - Bolibompa - 4 - - - Nyheterna - 928 - - - Sikta mot Stjärnorna - 38974 - - - Idol - 5 - - - - IdolIdol Idol Idol Idol Idol Idol Idol Idol Idol Idol Idol Idol Idol - Idol Idol Idol Idol Idol Idol Idol Idol Idol Idol - - 5 - - - - IdolIdolIdolIdolIdolIdolIdolIdolIdolIdolIdolIdolIdolIdol - IdolIdolIdolIdolIdolIdolIdolIdolIdolIdol - - 5 - + {/* TODO add loading indicator */} + {productions.map((p) => ( + + {p.name} + {p.id} + + ))} ); }; diff --git a/src/components/production/types.ts b/src/components/production/types.ts index 0dadb1f0..6f1c332c 100644 --- a/src/components/production/types.ts +++ b/src/components/production/types.ts @@ -7,7 +7,6 @@ export type TLine = { name: string; id: number; connected: boolean; - connectionId: string; participants: TParticipant[]; }; diff --git a/tsconfig.json b/tsconfig.json index 5226fa4c..ca1ff984 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,10 @@ { "compilerOptions": { - "target": "ES2020", + "target": "esnext", "useDefineForClassFields": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], + "lib": ["esnext", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, @@ -13,7 +12,6 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - /* Linting */ "strict": true, "noUnusedLocals": true, @@ -22,5 +20,9 @@ }, "include": ["src"], "exclude": ["./node_modules"], - "references": [{ "path": "./tsconfig.node.json" }] + "references": [ + { + "path": "./tsconfig.node.json" + } + ] } diff --git a/yarn.lock b/yarn.lock index b7191fcf..2285d83d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3018,6 +3018,11 @@ react-dom@^18.2.0: loose-envify "^1.1.0" scheduler "^0.23.0" +react-hook-form@^7.51.1: + version "7.51.1" + resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.51.1.tgz#3ce5f8b5ef41903b4054a641cef8c0dc8bf8ae85" + integrity sha512-ifnBjl+kW0ksINHd+8C/Gp6a4eZOdWyvRv0UBaByShwU8JbVx5hTcTWEcd5VdybvmPTATkVVXk9npXArHmo56w== + react-is@^16.13.1, react-is@^16.7.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"