diff --git a/client/src/app/dashboard/(application)/education/page.tsx b/client/src/app/dashboard/(application)/education/page.tsx index 3a83738b..ff0aea1a 100644 --- a/client/src/app/dashboard/(application)/education/page.tsx +++ b/client/src/app/dashboard/(application)/education/page.tsx @@ -127,7 +127,7 @@ function EducationForm({ schoolOptions, countryOptions, application }: Education async function onSubmit(values: z.infer): Promise { await updateApplication("education", values) await mutateApplication({ ...application, ...values }) - if (application.university == null) router.push("/dashboard/cv") + if (application.university == null) router.push("/dashboard/travel") } return ( diff --git a/client/src/app/dashboard/(application)/travel-reimbursement/error.tsx b/client/src/app/dashboard/(application)/travel-reimbursement/error.tsx new file mode 100644 index 00000000..5611be6d --- /dev/null +++ b/client/src/app/dashboard/(application)/travel-reimbursement/error.tsx @@ -0,0 +1,3 @@ +"use client" + +export { FormLoadingError as default } from "@/components/dashboard/form-loading-error" diff --git a/client/src/app/dashboard/(application)/travel-reimbursement/layout.tsx b/client/src/app/dashboard/(application)/travel-reimbursement/layout.tsx new file mode 100644 index 00000000..2729f07a --- /dev/null +++ b/client/src/app/dashboard/(application)/travel-reimbursement/layout.tsx @@ -0,0 +1,11 @@ +import type * as React from "react" + +export default function TravelReimbursementFormLayout({ children }: { children: React.ReactNode }) { + return ( + <> +

Travel Reimbursement Form +

+ {children} + + ) +} diff --git a/client/src/app/dashboard/(application)/travel-reimbursement/page.tsx b/client/src/app/dashboard/(application)/travel-reimbursement/page.tsx new file mode 100644 index 00000000..8f28c73e --- /dev/null +++ b/client/src/app/dashboard/(application)/travel-reimbursement/page.tsx @@ -0,0 +1,186 @@ +"use client" + +import { zodResolver } from "@hookform/resolvers/zod" +import { useRouter } from "next/navigation" +import * as React from "react" +import { useForm } from "react-hook-form" +import { z } from "zod" +import { MultiSelect } from "@durhack/web-components/ui/multi-select" +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, FormDescription } from "@durhack/web-components/ui/form" +import { Input } from "@durhack/web-components/ui/input" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, + SelectValueClipper, +} from "@durhack/web-components/ui/select" +import { + FileUpload, + FileUploadDropzoneBasket, + FileUploadDropzoneInput, + FileUploadDropzoneRoot, + FileUploadErrorMessage, + FileUploadFileList, +} from "@durhack/web-components/ui/file-upload" + + +import { FormSkeleton } from "@/components/dashboard/form-skeleton" +import { FormSubmitButton } from "@/components/dashboard/form-submit-button" +import type { Application } from "@/hooks/use-application" +import { useApplicationContext } from "@/hooks/use-application-context" +import { isLoaded } from "@/lib/is-loaded" +import { updateApplication } from "@/lib/update-application" + +type TravelReimbursementFormFields = { + + methodOfTravel: string + receiptFiles: File[] +} + +const TravelReimbursementFormSchema = z.object({ + methodoftravel: z + .array( + z.enum(["train", "bus", "private road vehicle", "international transport", "other"]) + ), + receiptFiles: z + .array( + z + .custom((value) => value instanceof File) + .refine((value) => value.size <= 10485760, "Maximum file size is 10MB!") + .refine((value) => { + if ( + ![ + "application/pdf", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "application/msword", + "application/png", + "application/jpg" + ].includes(value.type) + ) + return false + + const split = value.name.split(".") + const extension = split[split.length - 1] + return ["doc", "docx", "pdf","png", "jpg"].includes(extension) + }, "Please upload a PDF or Word doc or a PNG or JPG image!"), + ) + + }) + + +/** + * This component accepts application via props, rather than via + * useApplicationContext, because it requires the application to already be loaded before being rendered. + */ +function TravelReimbursementForm({ application }: { application: Application }) { + const router = useRouter() + const { mutateApplication } = useApplicationContext() + + const form = useForm>({ + resolver: zodResolver(TravelReimbursementFormSchema), + }) + + async function onSubmit(values: z.infer): Promise { + await updateApplication("travelReimbursement", values) + await mutateApplication({ ...application, ...values }) + } + + return ( +
+ +
+
+ ( + + Method of travel +
+ + + + + {value === "other" && } +
+ +
+ )} + /> +
+
+ + +
+ ( + + Travel receipts + +

+ Only pdf, doc, docs, png and jpg files are accepted. +

+
+ + + + + + + + + +
+ )} + /> +
+
+ Submit travel reimbursement request +
+
+ + ) +} + +function TravelReimbursementFormSkeleton() { + return +} + +export default function TravelReimbursementFormPage() { + const { application, applicationIsLoading } = useApplicationContext() + + if (!isLoaded(application, applicationIsLoading)) { + return + } + + return +} diff --git a/client/src/app/dashboard/(application)/travel/error.tsx b/client/src/app/dashboard/(application)/travel/error.tsx new file mode 100644 index 00000000..5611be6d --- /dev/null +++ b/client/src/app/dashboard/(application)/travel/error.tsx @@ -0,0 +1,3 @@ +"use client" + +export { FormLoadingError as default } from "@/components/dashboard/form-loading-error" diff --git a/client/src/app/dashboard/(application)/travel/layout.tsx b/client/src/app/dashboard/(application)/travel/layout.tsx new file mode 100644 index 00000000..63b85456 --- /dev/null +++ b/client/src/app/dashboard/(application)/travel/layout.tsx @@ -0,0 +1,10 @@ +import type * as React from "react" + +export default function TravelPageLayout({ children }: { children: React.ReactNode }) { + return ( + <> +

Travel Details

+ {children} + + ) +} diff --git a/client/src/app/dashboard/(application)/travel/page.tsx b/client/src/app/dashboard/(application)/travel/page.tsx new file mode 100644 index 00000000..e156f942 --- /dev/null +++ b/client/src/app/dashboard/(application)/travel/page.tsx @@ -0,0 +1,103 @@ +"use client" + +import { zodResolver } from "@hookform/resolvers/zod" +import { useRouter } from "next/navigation" +import * as React from "react" +import { useForm } from "react-hook-form" +import { z } from "zod" +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, FormDescription } from "@durhack/web-components/ui/form" +import { Input } from "@durhack/web-components/ui/input" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, + SelectValueClipper, +} from "@durhack/web-components/ui/select" +import { FormSkeleton } from "@/components/dashboard/form-skeleton" +import { FormSubmitButton } from "@/components/dashboard/form-submit-button" +import type { Application } from "@/hooks/use-application" +import { useApplicationContext } from "@/hooks/use-application-context" +import { isLoaded } from "@/lib/is-loaded" +import { updateApplication } from "@/lib/update-application" + +type TravelDetailsFormFields = { + travelOrigin: string +} +const TravelDetailsFormSchema = z.object({ + travelOrigin: z.enum(["prefer-not-to-answer", "Durham", "elsewhere in the UK", "abroad"]) + }) +/** + * This component accepts application via props, rather than via + * useApplicationContext, because it requires the application to already be loaded before being rendered. + */ +function TravelDetailsForm({ application }: { application: Application }) { + const router = useRouter() + const { mutateApplication } = useApplicationContext() + + const form = useForm>({ + resolver: zodResolver(TravelDetailsFormSchema), + }) +async function onSubmit(values: z.infer): Promise { + await updateApplication("travel", values) + await mutateApplication({ ...application, ...values }) + //if (application.travelOrigin == null) router.push("/dashboard/cv") + } + return ( +
+ +
+
+ ( + + Where will you be travelling from +
+ + {value === "elsewhere-in-the-uk" && } + {value === "prefer-not-to-answer" &&

+ You will not be able to apply for travel reimbursement.

} +
+ +
+ )} + /> +
+
+
+ Save Progress +
+
+ + ) + } +function TravelDetailsFormSkeleton() { + return +} + +export default function TravelDetailsFormPage() { + const { application, applicationIsLoading } = useApplicationContext() + + if (!isLoaded(application, applicationIsLoading)) { + return + } + + return +} \ No newline at end of file diff --git a/client/src/components/dashboard/sidebar.tsx b/client/src/components/dashboard/sidebar.tsx index 9f21aa03..a7440d1a 100644 --- a/client/src/components/dashboard/sidebar.tsx +++ b/client/src/components/dashboard/sidebar.tsx @@ -19,6 +19,7 @@ const menuItems = [ { id: 4, name: "Contact", link: "/contact" }, { id: 5, name: "Extra", link: "/extra" }, { id: 6, name: "Education", link: "/education" }, + { id: 9, name: "Travel", link: "/travel" }, { id: 7, name: "CV", link: "/cv" }, { id: 8, name: "Submit", link: "/submit" }, ] as const satisfies readonly MenuItem[]