From b17a5065fb51d888d68f414db6bb07ee0be3c143 Mon Sep 17 00:00:00 2001 From: alexandre-kakal <84040142+alexandre-kakal@users.noreply.github.com> Date: Tue, 25 Jun 2024 10:53:50 +0200 Subject: [PATCH 1/3] Ak event journey forms (#18) middleware form front journey --------- Co-authored-by: alexandre-kakal-akarah --- src/app/api/journeys/route.ts | 25 ++-- src/app/api/steps/[id]/route.ts | 1 + src/app/api/steps/route.ts | 1 + src/app/utils/errorHandlerUtils.ts | 46 ++----- src/components/EventsFeed.tsx | 1 - src/components/Icons.tsx | 11 ++ src/components/JourneyCard.tsx | 39 +++++- src/components/JourneysFeed.tsx | 1 - .../form/journey/BottomSheetModal.tsx | 1 - src/components/form/journey/JourneyForm.tsx | 116 +++++++++++++++++- .../form/journey/steps/FirstStep.tsx | 7 +- .../form/journey/steps/SecondStep.tsx | 5 +- .../form/journey/steps/ThirdStep.tsx | 3 +- src/middleware.ts | 35 ++++++ src/repositories/userRepository.ts | 1 - src/services/stepService.ts | 2 + src/validators/api/journeySchema.ts | 2 +- src/validators/api/stepSchema.ts | 8 +- src/validators/journeyFormSchema.ts | 39 +++--- 19 files changed, 256 insertions(+), 88 deletions(-) create mode 100644 src/middleware.ts diff --git a/src/app/api/journeys/route.ts b/src/app/api/journeys/route.ts index 0ded0d9..a878f40 100644 --- a/src/app/api/journeys/route.ts +++ b/src/app/api/journeys/route.ts @@ -1,15 +1,12 @@ -import { - handleException, - handlePrismaException, -} from "@/app/utils/errorHandlerUtils"; +import { handleException } from "@/app/utils/errorHandlerUtils"; import { getAllJourneys, registerOrModifyJourney, } from "@/services/journeyService"; +import { BadRequestException } from "@/types/exceptions"; import { JourneyWithoutDates } from "@/types/journey"; import { StepWithoutDates } from "@/types/step"; import { journeyBodySchema } from "@/validators/api/journeySchema"; -import { Prisma } from "@prisma/client"; import { NextRequest, NextResponse } from "next/server"; /** @@ -34,22 +31,16 @@ export async function POST(request: NextRequest) { try { const body = await request.json(); // Parse the body with zod to get the journey and steps - const parsedBody = journeyBodySchema.parse(body); - const journey: JourneyWithoutDates = parsedBody.journey; - const steps: StepWithoutDates[] = parsedBody.steps; + const parsedBody = journeyBodySchema.safeParse(body); + if (parsedBody.error) { + throw new BadRequestException("Invalid request body"); + } + const journey: JourneyWithoutDates = body.journey; + const steps: StepWithoutDates[] = body.steps; const result = await registerOrModifyJourney(null, journey, steps); return NextResponse.json({ data: result }, { status: 201 }); } catch (error: any) { - if ( - error instanceof Prisma.PrismaClientKnownRequestError || - error instanceof Prisma.PrismaClientUnknownRequestError || - error instanceof Prisma.PrismaClientRustPanicError || - error instanceof Prisma.PrismaClientInitializationError || - error instanceof Prisma.PrismaClientValidationError - ) { - return handlePrismaException(error); - } return handleException(error); } } diff --git a/src/app/api/steps/[id]/route.ts b/src/app/api/steps/[id]/route.ts index 3b6fe90..5f6c0fb 100644 --- a/src/app/api/steps/[id]/route.ts +++ b/src/app/api/steps/[id]/route.ts @@ -41,6 +41,7 @@ export async function PUT( const id: number = Number(params.id); const body = await request.json(); // Parse the body with zod to get the step + // @ts-ignore const step: StepWithoutDates = stepBodySchema.parse(body).step; const result = await registerOrModifyStep(id, step); diff --git a/src/app/api/steps/route.ts b/src/app/api/steps/route.ts index 3bfc6d6..37d11f9 100644 --- a/src/app/api/steps/route.ts +++ b/src/app/api/steps/route.ts @@ -13,6 +13,7 @@ export async function POST(request: NextRequest) { try { const body = await request.json(); // Parse the body with zod to get the step + // @ts-ignore const step: StepWithoutDates = stepBodySchema.parse(body).step; const result = await registerOrModifyStep(null, step); diff --git a/src/app/utils/errorHandlerUtils.ts b/src/app/utils/errorHandlerUtils.ts index 5207bd9..26ccb64 100644 --- a/src/app/utils/errorHandlerUtils.ts +++ b/src/app/utils/errorHandlerUtils.ts @@ -32,6 +32,18 @@ export function handleException(error: any) { return NextResponse.json({ message: error.message }, { status: 404 }); case error instanceof InternalServerErrorException: return NextResponse.json({ message: error.message }, { status: 500 }); + + case error instanceof Prisma.PrismaClientKnownRequestError: + return NextResponse.json({ message: error.message }, { status: 400 }); + case error instanceof Prisma.PrismaClientUnknownRequestError: + return NextResponse.json({ message: error.message }, { status: 400 }); + case error instanceof Prisma.PrismaClientRustPanicError: + return NextResponse.json({ message: error.message }, { status: 500 }); + case error instanceof Prisma.PrismaClientInitializationError: + return NextResponse.json({ message: error.message }, { status: 500 }); + case error instanceof Prisma.PrismaClientValidationError: + return NextResponse.json({ message: error.message }, { status: 400 }); + default: return NextResponse.json( { message: "An unexpected error occurred" }, @@ -40,40 +52,6 @@ export function handleException(error: any) { } } -export function handlePrismaException( - error: - | Prisma.PrismaClientKnownRequestError - | Prisma.PrismaClientUnknownRequestError - | Prisma.PrismaClientRustPanicError - | Prisma.PrismaClientInitializationError - | Prisma.PrismaClientValidationError, -) { - const message = error.message; - const status = 500; - - if (error instanceof Prisma.PrismaClientKnownRequestError) { - return NextResponse.json({ message }, { status: 400 }); - } - - if (error instanceof Prisma.PrismaClientUnknownRequestError) { - return NextResponse.json({ message }, { status: 400 }); - } - - if (error instanceof Prisma.PrismaClientRustPanicError) { - return NextResponse.json({ message }, { status: 500 }); - } - - if (error instanceof Prisma.PrismaClientInitializationError) { - return NextResponse.json({ message }, { status: 500 }); - } - - if (error instanceof Prisma.PrismaClientValidationError) { - return NextResponse.json({ message }, { status: 400 }); - } - - return NextResponse.json({ message }, { status }); -} - function formatZodErrors(error: ZodError): string { return error.errors .map((e) => { diff --git a/src/components/EventsFeed.tsx b/src/components/EventsFeed.tsx index 6523b08..800ffd4 100644 --- a/src/components/EventsFeed.tsx +++ b/src/components/EventsFeed.tsx @@ -7,7 +7,6 @@ async function getData() { const result = await getAllEvents(); return result; } catch (error: any) { - console.log(error, "error"); handleException(error); } } diff --git a/src/components/Icons.tsx b/src/components/Icons.tsx index a016dea..e3cf8ac 100644 --- a/src/components/Icons.tsx +++ b/src/components/Icons.tsx @@ -301,4 +301,15 @@ export const Icons = { ), + check: (props: LucideProps) => ( + + + + ), }; diff --git a/src/components/JourneyCard.tsx b/src/components/JourneyCard.tsx index 1ca968c..f219bfb 100644 --- a/src/components/JourneyCard.tsx +++ b/src/components/JourneyCard.tsx @@ -12,17 +12,52 @@ type JourneyCardProps = { const JourneyCard = ({ journey }: JourneyCardProps) => { const averageRate = calculateAverageRating(journey.comments); + let phisicalDificulty = "facile"; + let cluesDificulty = "facile"; + + switch (journey.physicalDifficulty) { + case 1: + phisicalDificulty = "facile"; + break; + case 2: + phisicalDificulty = "intermédiaire"; + break; + case 3: + phisicalDificulty = "difficile"; + break; + default: + phisicalDificulty = "facile"; + break; + } + + switch (journey.cluesDifficulty) { + case 1: + cluesDificulty = "facile"; + break; + case 2: + cluesDificulty = "intermédiaire"; + break; + case 3: + cluesDificulty = "difficile"; + break; + default: + cluesDificulty = "facile"; + break; + } + return (
-

Facile

+

+ {phisicalDificulty} +

-

Intermédiaire

+

{cluesDificulty}

{ const [dragDisabled, setDragDisabled] = useState(false); useEffect(() => { - console.log(editedStep, "edited step"); if (editedStep) { form.setValue("puzzle", editedStep.puzzle); form.setValue("answer", editedStep.answer); diff --git a/src/components/form/journey/JourneyForm.tsx b/src/components/form/journey/JourneyForm.tsx index db533f1..72d58c3 100644 --- a/src/components/form/journey/JourneyForm.tsx +++ b/src/components/form/journey/JourneyForm.tsx @@ -16,10 +16,24 @@ import { zodResolver } from "@hookform/resolvers/zod"; import StepCounter from "../StepCounter"; import Steps from "./steps/Steps"; import { Icons } from "@/components/Icons"; +import { StepWithoutDates } from "@/types/step"; +import { JourneyWithoutDates } from "@/types/journey"; export type JourneyFormValues = z.infer; type FieldName = keyof JourneyFormValues; +type JourneyStep = { + steps: { + puzzle: string; + hint: string; + answer: string; + coordinates: { + latitude: number; + longitude: number; + }; + }[]; +}; + const firstStepFields = Object.keys(firstStepSchema.shape); const secondStepFields = Object.keys(secondStepSchema.shape); const thirdStepFields = Object.keys(thirdStepSchema.shape); @@ -43,7 +57,7 @@ const steps = [ const JourneyForm = () => { const { isVisible, hideModal, clearSteps } = useJourneyFormStore(); const [formStatus, setFormStatus] = useState<"idle" | "errored">("idle"); - const [currentStep, setCurrentStep] = useState(2); + const [currentStep, setCurrentStep] = useState(0); const [dir, setDir] = useState<"ltr" | "rtl">("ltr"); const form = useForm({ @@ -62,18 +76,76 @@ const JourneyForm = () => { }); const processForm: SubmitHandler = async (data) => { - console.log(data); + console.log("processing form"); + const { steps, ...journey } = data; + + const parsedSteps: JourneyStep = JSON.parse(steps); + // @ts-ignore + const stepObject: StepWithoutDates[] = parsedSteps.steps.map( + (step, index) => { + // to do : get optional additional location fields (city, country, address, postal code) + return { + puzzle: step.puzzle, + hint: step.hint, + answer: step.answer, + latitude: step.coordinates.latitude, + longitude: step.coordinates.longitude, + stepNumber: index + 1, + pictureHint: undefined, + picturePuzzle: undefined, + }; + }, + ); + + // to do : estimate distance and duration from steps + + // get user id from session + const journeyObject: JourneyWithoutDates = { + authorId: 1, + title: journey.title, + description: journey.description, + requirement: journey.requirement, + mobilityImpaired: journey.mobilityImpaired, + partiallySighted: journey.partiallySighted, + partiallyDeaf: journey.partiallyDeaf, + cognitivelyImpaired: journey.cognitivelyImpaired, + treasure: journey.treasure, + cluesDifficulty: Number(journey.cluesDifficulty), + physicalDifficulty: Number(journey.physicalDifficulty), + estimatedDistance: 1000, + estimatedDuration: 60, + lastCompletion: new Date(), + }; + + const body = { + journey: journeyObject, + steps: stepObject, + }; + + const res = await fetch( + `${process.env.BASE_URL || "http://localhost:3000"}/api/journeys`, + { + method: "POST", + body: JSON.stringify(body), + headers: { + "Content-Type": "application/json", + }, + }, + ); + if (!res.ok) { + setFormStatus("errored"); + } clearSteps(); }; const next = async () => { const fields = steps[currentStep].fields; const output = await form.trigger(fields as FieldName[]); - console.log(output, "output", fields, "fields"); if (!output) return; if (currentStep < steps.length) { if (currentStep === steps.length - 1) { - await form.handleSubmit(processForm)(); + console.log("submitting"); + // await form.handleSubmit(processForm)(); } if (currentStep < steps.length - 1) { @@ -121,6 +193,42 @@ const JourneyForm = () => { /> )} + + {form.formState.isSubmitSuccessful && formStatus !== "errored" && ( +
+

+ Votre parcours a bien été enregistré ! +

+

+ Lorem, ipsum dolor sit amet consectetur adipisicing elit. Quasi + nam illo iste veniam ea! Doloremque distinctio mollitia omnis. +

+ +
+ )} + + {formStatus === "errored" && ( +
+

+ Oups, une erreur est survenue... +

+

+ Votre parcours n'a pas pu être créé, veuillez réessayer plus tard. +

+ +
+ )}
); diff --git a/src/components/form/journey/steps/FirstStep.tsx b/src/components/form/journey/steps/FirstStep.tsx index 2fed688..f2d9404 100644 --- a/src/components/form/journey/steps/FirstStep.tsx +++ b/src/components/form/journey/steps/FirstStep.tsx @@ -58,7 +58,12 @@ const FirstStep = ({ form, next }: FirstStepProps) => { /> {/* TODO: add image file input */} - diff --git a/src/components/form/journey/steps/SecondStep.tsx b/src/components/form/journey/steps/SecondStep.tsx index 986e510..dde9d0d 100644 --- a/src/components/form/journey/steps/SecondStep.tsx +++ b/src/components/form/journey/steps/SecondStep.tsx @@ -82,7 +82,7 @@ const SecondStep = ({ form, next, prev }: SecondStepProps) => { - + )} /> @@ -118,7 +118,7 @@ const SecondStep = ({ form, next, prev }: SecondStepProps) => { - + )} /> @@ -250,6 +250,7 @@ const SecondStep = ({ form, next, prev }: SecondStepProps) => { Retour
-
}> diff --git a/src/app/(app)/layout.tsx b/src/app/(app)/layout.tsx index 125c5d6..dc31b80 100644 --- a/src/app/(app)/layout.tsx +++ b/src/app/(app)/layout.tsx @@ -1,5 +1,5 @@ import React from "react"; -import MobileNav from "@/components/MobileNav"; +import Nav from "@/components/Nav"; import { ReactParallaxProvider } from "@/providers/Providers"; export default function AppLayout({ @@ -10,7 +10,7 @@ export default function AppLayout({ return ( {children} - +