Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement registration form #80

Merged
merged 4 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
PUBLIC_URL="http://localhost:8080"
API_KEY="alongsecurestring"
API_KEY=""

POSTGRES_PASSWORD="password123"
POSTGRES_USER="postgres"
Expand Down
17 changes: 17 additions & 0 deletions frontend/app/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"use client";

import { buttonVariants } from "@/components/ui/button";
import Image from "next/image";
import Link from "next/link";

export default function Error() {
return (
<div className="h-[100dvh] w-full flex flex-col items-center justify-center space-y-4">
<Image src="/error.webp" height="300" width="300" alt="Error" />
<h2 className="text-xl font-medium">Something went wrong!</h2>
<Link className={buttonVariants()} href={"/documents"}>
Go Back
</Link>
</div>
);
}
301 changes: 301 additions & 0 deletions frontend/app/registration/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,301 @@
"use client";

import { useRouter } from "next/navigation";
import {
QuestionType,
RegistrationFormDocument,
RegistrationFormQuery,
RegistrationFormQueryVariables,
} from "@/lib/gql/generated/graphql";
import { client } from "@/lib/graphClient";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { useEffect, useState } from "react";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Slider } from "@/components/ui/slider";
import { Label } from "@/components/ui/label";
import { Checkbox } from "@/components/ui/checkbox";
import { Progress } from "@/components/ui/progress";
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import {
Form,
FormControl,
FormField,
FormItem,
FormMessage,
} from "@/components/ui/form";

type Props = {
searchParams: {
e: string;
};
};

const SingleChoiceFormSchema = (required: boolean) =>
z.object({
singleChoice: required
? z.number({
required_error: "Bitte wähle eine Option",
})
: z.number().optional(),
});

const MultipleChoiceFormSchema = (required: boolean) =>
z.object({
multipleChoice: required
? z.array(z.number()).refine((value) => value.some((item) => item), {
message: "Bitte triff eine Auswahl",
})
: z.array(z.number()).optional(),
});

const Home = ({ searchParams }: Props) => {
const [regForm, setForm] = useState<RegistrationFormQuery["forms"][0] | null>(
null
);
const [progressValue, setProgressValue] = useState(0);
const [sliderValue, setSliderValue] = useState<number>(0);
const [index, setIndex] = useState(0);
const [loading, setLoading] = useState(true);
const router = useRouter();

useEffect(() => {
const fetchData = async () => {
const eventID = searchParams.e;

const vars: RegistrationFormQueryVariables = {
eventID: parseInt(eventID),
};

await new Promise((resolve) => setTimeout(resolve, 250));

const data = await client.request<RegistrationFormQuery>(
RegistrationFormDocument,
vars
);

if (!data.forms.length) {
router.push("/");
return;
}

setForm(data.forms[0]);
setLoading(false);
};

fetchData();
}, [searchParams.e, router]);

useEffect(() => {
if (regForm) {
setProgressValue((100 / (regForm.questions.length - 1)) * index);
}
}, [index, regForm]);

const mcForm = useForm<z.infer<ReturnType<typeof MultipleChoiceFormSchema>>>({
resolver: zodResolver(MultipleChoiceFormSchema(regForm?.questions[index].required!)),
defaultValues: {
multipleChoice: [],
},
});

const scForm = useForm<z.infer<ReturnType<typeof SingleChoiceFormSchema>>>({
resolver: zodResolver(SingleChoiceFormSchema(regForm?.questions[index].required!)),
defaultValues: {
singleChoice: undefined,
},
});

function handleQuit() {
router.push("/");
}

const FooterButtons = () => (
<div className="flex justify-between w-full">
<Button onClick={handleQuit} variant="outline" className="w-auto">
Abbrechen
</Button>
<Button type="submit" className="w-auto">
{regForm?.questions.length !== index + 1 ? "Nächste Frage" : "Anmelden"}
</Button>
</div>
);

function onSubmit() {
if (regForm?.questions.length !== index + 1) {
setIndex((prevIndex) => prevIndex + 1);
}
}

function onScaleSubmit() {
onSubmit();
setSliderValue(0);
}

function onMCSubmit(data: z.infer<ReturnType<typeof MultipleChoiceFormSchema>>) {
onSubmit();
}

function onSCSubmit(data: z.infer<ReturnType<typeof SingleChoiceFormSchema>>) {
onSubmit();
}

if (loading) {
return <div>Loading...</div>;
}

return (
<div className="grid h-screen place-items-center">
<div className="space-y-5">
<div>
<h1>{regForm?.title}</h1>
<p className="text-sm text-muted-foreground">
{regForm?.description}
</p>
</div>
<Progress value={progressValue} />
<Card className="w-[500px]">
<CardHeader>
<CardTitle>{regForm?.questions[index].title}</CardTitle>
</CardHeader>
{regForm?.questions[index].type === QuestionType.MultipleChoice && (
<Form {...mcForm}>
<form onSubmit={mcForm.handleSubmit(onMCSubmit)}>
<CardContent>
<div className="mt-8 mb-8">
<FormField
control={mcForm.control}
name="multipleChoice"
render={() => (
<FormItem>
{regForm?.questions[index].answers.map((answer) => (
<FormField
key={answer.ID}
control={mcForm.control}
name="multipleChoice"
render={({ field }) => (
<FormItem>
<div
key={answer.ID}
className="flex items-center space-x-2"
>
<FormControl>
<Checkbox
checked={field.value?.includes(
answer.ID
)}
onCheckedChange={(checked) => {
return checked
? field.onChange([
...field.value || [],
answer.ID,
])
: field.onChange(
field.value?.filter(
(value) => value !== answer.ID
)
);
}}
/>
</FormControl>
<Label>{answer.title}</Label>
</div>
</FormItem>
)}
/>
))}
<FormMessage />
</FormItem>
)}
/>
</div>
</CardContent>
<CardFooter>
<FooterButtons />
</CardFooter>
</form>
</Form>
)}

{regForm?.questions[index].type === QuestionType.SingleChoice && (
<Form {...scForm}>
<form onSubmit={scForm.handleSubmit(onSCSubmit)}>
<CardContent>
<div className="mt-8 mb-8">
<FormField
control={scForm.control}
name="singleChoice"
render={({ field }) => (
<FormItem>
<RadioGroup
value={field.value?.toString()}
onValueChange={(value) =>
field.onChange(parseInt(value, 10))
}
>
{regForm.questions[index].answers.map((answer) => (
<div
key={answer.ID}
className="flex items-center space-x-2"
>
<RadioGroupItem value={answer.ID.toString()} />
<label className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
{answer.title}
</label>
</div>
))}
</RadioGroup>
<FormMessage />
</FormItem>
)}
/>
</div>
</CardContent>
<CardFooter>
<FooterButtons />
</CardFooter>
</form>
</Form>
)}

{regForm?.questions[index].type === QuestionType.Scale && (
<form onSubmit={onScaleSubmit}>
<CardContent>
<div className="mt-8 mb-8 space-y-4">
<div className="flex justify-between">
<span className="text-sm font-medium">
{regForm.questions[index].answers[1].title}
</span>
<span className="text-sm font-medium">
{regForm.questions[index].answers[0].title}
</span>
</div>
<Slider
value={[sliderValue]}
onValueChange={(value) => setSliderValue(value[0])}
min={regForm.questions[index].answers[1].points}
max={regForm.questions[index].answers[0].points}
step={1}
/>
</div>
</CardContent>
<CardFooter>
<FooterButtons />
</CardFooter>
</form>
)}
</Card>
</div>
</div>
);
};

export default Home;
Loading
Loading