Skip to content

Commit

Permalink
Merge pull request #36 from podcodar/vitorschelb/form-validation
Browse files Browse the repository at this point in the history
 Validates data from form steps according to zod schemas.
  • Loading branch information
vitorschelb authored Jul 7, 2023
2 parents 6d342f5 + ba25363 commit 6cd9c99
Show file tree
Hide file tree
Showing 10 changed files with 150 additions and 59 deletions.
28 changes: 28 additions & 0 deletions src/app/app/onboarding/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"use client";
import { PropsWithChildren } from "react";
import { OnboardingProvider, Step } from "@/contexts/OnboardingFormProvider";
import OnboardingRegistration from "@/components/forms/OnboardingRegistration";
import OnboardingContact from "@/components/forms/OnboardingContact";
import OnboardingProfessional from "@/components/forms/OnboardingProfessional";
import OnboardingAbout from "@/components/forms/OnboardingAbout";
import { useSearchParams } from "next/navigation";

const steps: Step[] = [
{ content: OnboardingRegistration },
{ content: OnboardingContact },
{ content: OnboardingProfessional },
{ content: OnboardingAbout },
];

export default async function OnboardingLayout({
children,
}: PropsWithChildren) {
const searchParams = useSearchParams();
const initialStep = searchParams.get("step") ?? "0";

return (
<OnboardingProvider initialStep={Number(initialStep)} steps={steps}>
{children}
</OnboardingProvider>
);
}
41 changes: 14 additions & 27 deletions src/app/app/onboarding/page.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,31 @@
"use client";
import OnboardingAbout from "@/components/forms/OnboardingAbout";
import OnboardingProfessional from "@/components/forms/OnboardingProfessional";
import OnboardingContact from "@/components/forms/OnboardingContact";
import OnboardingRegistration from "@/components/forms/OnboardingRegistration";
import { useOnboardingForm } from "@/hooks/useOnboardingForm";
import { useSearchParams } from "next/navigation";

const steps = [
<OnboardingRegistration key={0} />,
<OnboardingContact key={1} />,
<OnboardingProfessional key={2} />,
<OnboardingAbout key={3} />,
];
import { useOnboardingContext } from "@/contexts/OnboardingFormProvider";

export default function Form() {
const searchParams = useSearchParams();
const initialStep = searchParams.get("step") ?? "0";
const stepNumber = parseInt(initialStep, 10);

const {
step,
content,
steps,
canMoveNext,
canMovePrevious,
moveNextStep,
movePrevStep,
} = useOnboardingForm(stepNumber, steps);
content: Component,
} = useOnboardingContext();

return (
<div className="absolute top-0 bottom-0 z-10 right-0 left-0 bg-pod-purple">
<div className="bg-white">
<div className="grid gap-4 max-w-sm mx-auto">
<div className="flex-1">{content}</div>
<div className="flex-1">
<Component />
</div>

<div className="bg-red-100 flex justify-between">
<div className="flex justify-between">
<button
className="disabled:text-gray-400"
className="text-white text-sm rounded-xl p-1 w-24 h-12 font-medium border-2 bg-pod-purple shadow-md"
disabled={!canMovePrevious}
onClick={movePrevStep}
>
prev
Voltar
</button>

<div>
Expand All @@ -52,11 +39,11 @@ export default function Form() {
</div>

<button
className="disabled:text-gray-400"
className="text-white text-sm rounded-xl p-1 w-24 h-12 font-medium border-2 bg-pod-purple shadow-md"
disabled={!canMoveNext}
onClick={moveNextStep}
>
next
Continuar
</button>
</div>
</div>
Expand All @@ -68,5 +55,5 @@ function StepDot({ idx = 0, step = 0 }) {
let color = "text-gray-300";
if (idx < step) color = "text-blue-400";
if (idx === step) color = "text-purple-400";
return <span className={color}>o</span>;
return <span className={`${color} font-medium text-2xl`}>o</span>;
}
8 changes: 8 additions & 0 deletions src/components/forms/OnboardingAbout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { AboutSchema } from "@/shared/onboarding";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Label, Form, Textarea } from "@/shared/components";
import { useOnboardingContext } from "@/contexts/OnboardingFormProvider";

export default function OnboardingAbout() {
const {
Expand All @@ -15,11 +16,17 @@ export default function OnboardingAbout() {
} = useForm<AboutSchema>({
resolver: zodResolver(aboutSchema),
});
const { moveNextStep } = useOnboardingContext();
const values = watch(["qOne", "qTwo"]);

function onSubmit(data: AboutSchema) {
const isValid = aboutSchema.safeParse(data);
isValid.success
? moveNextStep()
: console.log("Dados inválidos:", isValid.error);
console.log(data);
}

return (
<div className="bg-slate-800 grid">
<Form onSubmit={handleSubmit(onSubmit)}>
Expand Down Expand Up @@ -49,6 +56,7 @@ export default function OnboardingAbout() {
{errors.qTwo && <span>{errors.qTwo.message}</span>}
</div>
</div>
<button type="submit">TESTE ENVIAR</button>
</Form>
<pre>
<code>{JSON.stringify(values, null, 2)}</code>
Expand Down
11 changes: 11 additions & 0 deletions src/components/forms/OnboardingContact.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import { ContactSchema } from "@/shared/onboarding";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Input, Label, Form } from "@/shared/components";
import { useOnboardingContext } from "@/contexts/OnboardingFormProvider";

export default function OnboardingContact() {
// TODO: Recebe onSubmit, que vai ser uma função.
const {
register,
handleSubmit,
Expand All @@ -15,6 +17,7 @@ export default function OnboardingContact() {
} = useForm<ContactSchema>({
resolver: zodResolver(contactSchema),
});
const { moveNextStep } = useOnboardingContext();
const values = watch([
"telefone",
"cidadeEstado",
Expand All @@ -23,8 +26,15 @@ export default function OnboardingContact() {
]);

function onSubmit(data: ContactSchema) {
const isValid = contactSchema.safeParse(data);
isValid.success
? moveNextStep()
: console.log("Dados inválidos:", isValid.error);
console.log(data);
// TODO: enviar o dado para o store global.(proxima pr) "props.onSubmit();"
// TODO: Após o envio, seguir para a proxima etapa, chamar o next
}

return (
<div className="bg-slate-800 grid">
<Form onSubmit={handleSubmit(onSubmit)}>
Expand Down Expand Up @@ -64,6 +74,7 @@ export default function OnboardingContact() {
</div>
{errors.email && <span>{errors.email.message}</span>}
</div>
<button type="submit">TESTE ENVIAR</button>
</Form>

<pre>
Expand Down
7 changes: 7 additions & 0 deletions src/components/forms/OnboardingProfessional.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ProfessionalSchema } from "@/shared/onboarding";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Input, Label, Select, Form } from "@/shared/components";
import { useOnboardingContext } from "@/contexts/OnboardingFormProvider";

export default function OnboardingProfessional() {
const {
Expand All @@ -15,6 +16,7 @@ export default function OnboardingProfessional() {
} = useForm<ProfessionalSchema>({
resolver: zodResolver(professionalSchema),
});
const { moveNextStep } = useOnboardingContext();
const values = watch([
"educationLevel",
"profissao",
Expand All @@ -24,6 +26,10 @@ export default function OnboardingProfessional() {
]);

function onSubmit(data: ProfessionalSchema) {
const isValid = professionalSchema.safeParse(data);
isValid.success
? moveNextStep()
: console.log("Dados inválidos:", isValid.error);
console.log(data);
}

Expand Down Expand Up @@ -92,6 +98,7 @@ export default function OnboardingProfessional() {
</div>
</div>
</div>
<button type="submit"> TESTE ENVIAR </button>
</Form>
<pre>
<code>{JSON.stringify(values, null, 2)}</code>
Expand Down
7 changes: 7 additions & 0 deletions src/components/forms/OnboardingRegistration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { RegistrationSchema } from "@/shared/onboarding";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Input, Label, Select, Form, Title } from "@/shared/components";
import { useOnboardingContext } from "@/contexts/OnboardingFormProvider";

export default function OnboardingRegistration() {
const {
Expand All @@ -15,13 +16,18 @@ export default function OnboardingRegistration() {
} = useForm<RegistrationSchema>({
resolver: zodResolver(registrationSchema),
});
const { moveNextStep } = useOnboardingContext();
const values = watch([
"nomeSocial",
"gender",
"idade"
]);

function onSubmit(data: RegistrationSchema) {
const isValid = registrationSchema.safeParse(data);
isValid.success
? moveNextStep()
: console.log("Dados inválidos:", isValid.error);
console.log(data);
}

Expand Down Expand Up @@ -63,6 +69,7 @@ export default function OnboardingRegistration() {
</div>
</div>
</div>
<button type="submit">TESTE ENVIAR</button>
</Form>

<pre>
Expand Down
71 changes: 71 additions & 0 deletions src/contexts/OnboardingFormProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"use client";
import { FC, useState, createContext, ReactNode, useContext } from "react";
import { raise } from "@/shared/exceptions";

type OnboardingContextProps = {
steps: Step[];
children: ReactNode;
initialStep?: number;
};

export type Step = {
content: FC;
};

type OnboardingContextValues = {
step: number;
steps: Step[];
canMovePrevious: boolean;
canMoveNext: boolean;
content: FC;
moveNextStep: () => void;
movePrevStep: () => void;
};

export const OnboardingProvider: FC<OnboardingContextProps> = ({
steps,
children,
initialStep = 0,
}) => {
const [step, setStep] = useState(initialStep);
const canMovePrevious = step !== 0;
const canMoveNext = step !== steps.length - 1;
const { content } = steps[step];

//TODO: Create ActionFunction to abrigate
// const actions = createActions(...states)
function moveNextStep() {
if (!canMoveNext) return;
setStep((step) => step + 1);
}

function movePrevStep() {
if (!canMovePrevious) return;
setStep((step) => step - 1);
}

// const values = createValues(...states)
const values: OnboardingContextValues = {
step,
steps,
canMovePrevious,
canMoveNext,
content,
moveNextStep,
movePrevStep,
};

return (
<OnboardingContext.Provider value={values}>
{children}
</OnboardingContext.Provider>
);
};

export const useOnboardingContext = (): OnboardingContextValues => {
const context = useContext(OnboardingContext);
if (!context) return raise("No OnboardingContext found");
return context;
};

const OnboardingContext = createContext<OnboardingContextValues | null>(null);
5 changes: 1 addition & 4 deletions src/contexts/UserProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { User } from "@prisma/client";
import { ReactNode, createContext, useContext } from "react";
import { raise } from "@/shared/exceptions";

const UserContext = createContext<User | null>(null);

Expand All @@ -16,7 +17,3 @@ export const useUser = (): User => {
const user = useContext(UserContext);
return user ?? raise("No provider found.");
};

const raise = (error: string): never => {
throw new Error(error);
};
28 changes: 0 additions & 28 deletions src/hooks/useOnboardingForm.tsx

This file was deleted.

3 changes: 3 additions & 0 deletions src/shared/exceptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const raise = (error: string): never => {
throw new Error(error);
};

1 comment on commit 6cd9c99

@vercel
Copy link

@vercel vercel bot commented on 6cd9c99 Jul 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.