Skip to content

Commit

Permalink
Merge pull request #9 from ksiuwr/bartacc/basic_forms_implementation
Browse files Browse the repository at this point in the history
Add Register and Sign Up forms
  • Loading branch information
bartacc authored Jul 31, 2024
2 parents 91e5596 + 8f34ff1 commit 364ae8b
Show file tree
Hide file tree
Showing 27 changed files with 830 additions and 120 deletions.
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

0 comments on commit 364ae8b

Please sign in to comment.