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

Add Register and Sign Up forms #9

Merged
merged 22 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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
3 changes: 2 additions & 1 deletion app/client/components/CustomToast.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Transition } from "@headlessui/react";
import parse from "html-react-parser";
import React from "react";
import { Alert, AlertType } from "./alert/Alert";

Expand All @@ -25,7 +26,7 @@ export const CustomToast = ({
leaveTo="opacity-0 scale-50"
>
<div className="w-fit">
<Alert type={levelTag}>{message}</Alert>
<Alert type={levelTag}>{parse(message)}</Alert>
</div>
</Transition>
);
Expand Down
2 changes: 1 addition & 1 deletion app/client/components/containers/CenteredContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { PropsWithChildren } from "react";

export const CenteredContainer = ({ children }: PropsWithChildren) => {
return <div className="mx-auto w-5/6 2xl:container lg:w-4/6">{children}</div>;
return <div className="mx-auto w-11/12 2xl:container lg:w-4/6">{children}</div>;
};
2 changes: 1 addition & 1 deletion app/client/components/containers/CenteredFormContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { CenteredContainer } from "./CenteredContainer";
export const CenteredFormContainer = ({ children }: PropsWithChildren) => {
return (
<CenteredContainer>
<div className="mx-auto mb-4 w-full lg:w-4/6 xl:w-1/2 2xl:w-5/12">
<div className="mx-auto mb-4 w-full lg:w-9/12 xl:w-7/12 2xl:w-6/12">
{children}
</div>
</CenteredContainer>
Expand Down
25 changes: 25 additions & 0 deletions app/client/components/forms/BasicDescription.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Description } from "@headlessui/react";
import { FieldHandler } from "@reactivated";
import parse from "html-react-parser";
import React from "react";

interface BasicDescriptionProps {
field: FieldHandler;
}

export const BasicDescription = ({ field }: BasicDescriptionProps) => {
if (!field.help_text && !field.error) {
return <></>;
}

return (
<Description as="div" className="prose mx-2 max-w-none">
{field.error && (
<span className="block text-error">{parse(field.error)}</span>
)}
{field.help_text && (
<span className="block">{parse(field.help_text)}</span>
)}
</Description>
);
};
20 changes: 7 additions & 13 deletions app/client/components/forms/BasicForm.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React from "react";

import { CSRFToken, FieldHandler, FormHandler } from "@reactivated";
import { FieldHandler, FormHandler } from "@reactivated";
import { FieldMap } from "reactivated/dist/forms";
import { Alert } from "../alert/Alert";
import { BasicFormField } from "./BasicFormField";
import { BasicFormWithCustomFields } from "./BasicFormWithCustomFields";

interface BasicFormProps<T extends FieldMap> {
form: FormHandler<T>;
Expand All @@ -15,13 +15,10 @@ export const BasicForm = <T extends FieldMap>({
submitButtonLabel,
}: BasicFormProps<T>) => {
return (
<form method="POST">
<CSRFToken />
{form.nonFieldErrors?.map((error) => (
<Alert key={error} type="error">
{error}
</Alert>
))}
<BasicFormWithCustomFields
form={form}
submitButtonLabel={submitButtonLabel}
>
{form.visibleFields.map((field) => (
<BasicFormField
key={field.name}
Expand All @@ -32,9 +29,6 @@ export const BasicForm = <T extends FieldMap>({
field={field as unknown as FieldHandler}
/>
))}
<button type="submit" className="btn btn-primary btn-lg my-4 w-full">
{submitButtonLabel}
</button>
</form>
</BasicFormWithCustomFields>
);
};
44 changes: 39 additions & 5 deletions app/client/components/forms/BasicFormField.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,51 @@
import { Field, Label } from "@headlessui/react";
import { Field } from "@headlessui/react";
import { FieldHandler } from "@reactivated";
import clsx from "clsx";
import React from "react";
import { WidgetHandler } from "reactivated/dist/forms";
import { DjangoFormsWidgetsCheckboxInput } from "reactivated/dist/generated";
import { BasicDescription } from "./BasicDescription";
import { BasicLabel } from "./BasicLabel";
import { BasicWidget } from "./BasicWidget";

interface BasicFormFieldProps {
field: FieldHandler;
disabled?: boolean;
checked?: boolean;
onCheckboxChange?: (
field: WidgetHandler<DjangoFormsWidgetsCheckboxInput>,
value: boolean,
) => void;
}

export const BasicFormField = ({ field }: BasicFormFieldProps) => {
export const BasicFormField = ({
field,
disabled = undefined,
checked = undefined,
onCheckboxChange = undefined,
}: BasicFormFieldProps) => {
const isCheckbox = field.tag === "django.forms.widgets.CheckboxInput";

return (
<Field className="mb-3">
<Label className="label font-semibold">{field.label}</Label>
<BasicWidget field={field} />
<Field
className="mb-4 flex flex-col"
disabled={disabled === undefined ? field.disabled : disabled}
>
<div
className={clsx(
"flex",
isCheckbox && "flex-row items-center gap-x-2",
!isCheckbox && "flex-col",
)}
>
<BasicLabel field={field} />
<BasicWidget
field={field}
checked={checked}
onCheckboxChange={onCheckboxChange}
/>
</div>
<BasicDescription field={field} />
</Field>
);
};
31 changes: 31 additions & 0 deletions app/client/components/forms/BasicFormWithCustomFields.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React, { PropsWithChildren } from "react";

import { CSRFToken, FormHandler } from "@reactivated";
import { FieldMap } from "reactivated/dist/forms";
import { Alert } from "../alert/Alert";

interface BasicFormWithCustomFieldsProps<T extends FieldMap> {
form: FormHandler<T>;
submitButtonLabel: string;
}

export const BasicFormWithCustomFields = <T extends FieldMap>({
form,
submitButtonLabel,
children,
}: PropsWithChildren<BasicFormWithCustomFieldsProps<T>>) => {
return (
<form method="POST">
<CSRFToken />
{form.nonFieldErrors?.map((error) => (
<Alert key={error} type="error">
{error}
</Alert>
))}
{children}
<button type="submit" className="btn btn-primary btn-lg my-4 w-full">
{submitButtonLabel}
</button>
</form>
);
};
16 changes: 16 additions & 0 deletions app/client/components/forms/BasicLabel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Label } from "@headlessui/react";
import { FieldHandler } from "@reactivated";
import React from "react";

interface BasicLabelProps {
field: FieldHandler;
}

export const BasicLabel = ({ field }: BasicLabelProps) => {
return (
<Label className="label inline-block text-wrap text-base font-semibold">
{field.label}
{field.widget.required && <span className="mx-1 text-error">*</span>}
</Label>
);
};
66 changes: 62 additions & 4 deletions app/client/components/forms/BasicWidget.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,84 @@
import { Input } from "@headlessui/react";
import { Checkbox, Input, Select, Textarea } from "@headlessui/react";
import { FieldHandler, Widget } from "@reactivated";
import React from "react";
import { WidgetHandler } from "reactivated/dist/forms";
import { DjangoFormsWidgetsCheckboxInput } from "reactivated/dist/generated";

interface BasicWidgetProps {
field: FieldHandler;
checked?: boolean;
onCheckboxChange?: (
field: WidgetHandler<DjangoFormsWidgetsCheckboxInput>,
value: boolean,
) => void;
}

export const BasicWidget = ({ field }: BasicWidgetProps) => {
// It's a switch, because there will be more widget types implemented in the future.
export const BasicWidget = ({
field,
checked = undefined,
onCheckboxChange = undefined,
}: BasicWidgetProps) => {
switch (field.tag) {
case "django.forms.widgets.TextInput":
case "django.forms.widgets.EmailInput":
case "django.forms.widgets.PasswordInput":
return (
<Input
type={field.widget.type}
name={field.name}
className="input input-bordered w-full"
required={field.widget.required}
disabled={field.disabled}
value={field.value ?? ""}
onChange={(e) => field.handler(e.target.value)}
/>
);

case "django.forms.widgets.CheckboxInput":
return (
<Checkbox
name={field.name}
className={`checkbox order-first my-2 size-8 data-[checked]:checkbox-success data-[disabled]:cursor-not-allowed data-[disabled]:border-transparent data-[disabled]:bg-base-content/40 data-[disabled]:opacity-20`}
checked={checked === undefined ? field.value : checked}
onChange={(checked) => {
onCheckboxChange === undefined
? field.handler(checked)
: onCheckboxChange(field, checked);
}}
/>
);

case "django.forms.widgets.Textarea":
return (
<Textarea
name={field.name}
className="textarea textarea-bordered w-full"
required={field.widget.required}
value={field.value ?? ""}
onChange={(e) => field.handler(e.target.value)}
/>
);

case "django.forms.widgets.Select":
return (
<Select
name={field.name}
className="select select-bordered w-full"
required={field.widget.required}
value={field.value ?? ""}
onChange={(e) => field.handler(e.target.value)}
>
{field.widget.optgroups.map((optgroup) => {
const currentOption = optgroup[1][0];
const optgroupValue = (currentOption.value ?? "").toString();

return (
<option key={optgroupValue} value={optgroupValue}>
{currentOption.label}
</option>
);
})}
</Select>
);
default:
return <Widget field={field} />;
}
Expand Down
48 changes: 48 additions & 0 deletions app/client/components/register/AccomodationFieldGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React, { useState } from "react";
import { WidgetHandler } from "reactivated/dist/forms";
import { DjangoFormsWidgetsCheckboxInput } from "reactivated/dist/generated";
import { BasicFormField } from "../forms/BasicFormField";

interface AccomodationFieldGroupProps {
dinnerField: WidgetHandler<DjangoFormsWidgetsCheckboxInput>;
accomodationField: WidgetHandler<DjangoFormsWidgetsCheckboxInput>;
breakfastField: WidgetHandler<DjangoFormsWidgetsCheckboxInput>;
}

export const AccomodationFieldGroup = ({
dinnerField,
accomodationField,
breakfastField,
}: AccomodationFieldGroupProps) => {
const [breakfastDisabled, setBreakfastDisabled] = useState(
breakfastField.disabled || !accomodationField.value,
);

const [dinnerDisabled, setDinnerDisabled] = useState(
dinnerField.disabled || !accomodationField.value,
);

const onAccomodationChange = (
field: WidgetHandler<DjangoFormsWidgetsCheckboxInput>,
value: boolean,
) => {
dinnerField.handler(value);
setDinnerDisabled(!value);

breakfastField.handler(value);
setBreakfastDisabled(!value);

field.handler(value);
};

return (
<>
<BasicFormField field={dinnerField} disabled={dinnerDisabled} />
<BasicFormField
field={accomodationField}
onCheckboxChange={onAccomodationChange}
/>
<BasicFormField field={breakfastField} disabled={breakfastDisabled} />
</>
);
};
Loading