Skip to content

Commit

Permalink
refactor: use FormData browser API and define simple auth component
Browse files Browse the repository at this point in the history
  • Loading branch information
wisnie committed Oct 14, 2022
1 parent 399bf8a commit b45ee2a
Show file tree
Hide file tree
Showing 25 changed files with 284 additions and 528 deletions.
70 changes: 46 additions & 24 deletions components/composited/SignInForm/SignInForm.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,54 @@
import { ChangeEventHandler } from 'react';
import { SignInFields } from '../../../validators/SignInFields';
import { FormEventHandler, Fragment, useRef, useState } from 'react';
import { SignInFields, SignInFieldsSch } from '../../../validators/SignInFields';
import { Button } from '../../generic/Button/Button';
import { ErrorMessage } from '../../generic/ErrorMessage/ErrorMessage';
import { Input } from '../../generic/Input/Input';

type SignInFormProps = {
fields: SignInFields;
onInputChange: ChangeEventHandler<HTMLInputElement>;
onSubmit: (fields: SignInFields) => void;
};

// The submit button could be displayed here, but to make it
// consistant with sign up page, we will use button higher in the hierarchy
export const SignInForm = ({ fields, onInputChange }: SignInFormProps) => {
export const SignInForm = ({ onSubmit }: SignInFormProps) => {
const formRef = useRef<HTMLFormElement>(null);
const [inputErrorMessage, setInputErrorMessage] = useState(false);

const handleSubmit: FormEventHandler<HTMLFormElement> = (e) => {
e.preventDefault();

// in a way this is a redundant check, but it is more
// strict than regular browser email regex
const formData = new FormData(e.currentTarget);
const result = SignInFieldsSch.safeParse({
email: formData.get('email'),
password: formData.get('password'),
});

if (result.success) {
onSubmit(result.data);
setInputErrorMessage(false);
} else {
setInputErrorMessage(true);
}
};

return (
<form className="flex flex-col gap-y-6 sm:gap-y-8 sm:w-1/2 sm:pr-4">
<Input
type="email"
name="email"
label="Adres email"
placeholder="[email protected]"
value={fields.email}
onChange={onInputChange}
/>
<Input
type="password"
name="password"
label="Hasło"
value={fields.password}
onChange={onInputChange}
/>
</form>
<Fragment>
{inputErrorMessage && (
<ErrorMessage
className="mb-6"
title="Błąd danych wejściowych!"
description="Wystąpił błąd danych wejściowych, sprawdź poprawność wpisanych danych. Jeśli błąd nie zniknie, skontaktuj się z administracją serwisu."
/>
)}
<form
ref={formRef}
onSubmit={handleSubmit}
className="flex flex-col items-start gap-y-6 sm:gap-y-8 sm:w-1/2 sm:pr-4"
>
<Input type="email" name="email" label="Adres email" placeholder="[email protected]" />
<Input type="password" name="password" label="Hasło" />
<Button type="submit" content="Zaloguj się" variant="primary" />
</form>
</Fragment>
);
};
51 changes: 18 additions & 33 deletions components/composited/SignInPage/SignInPage.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,30 @@
import { ChangeEventHandler, Fragment, MouseEventHandler, useCallback, useState } from 'react';
import { Button } from '../../generic/Button/Button';
import { useRouter } from 'next/router';

import { Heading } from '../../generic/Heading/Heading';
import { Text } from '../../generic/Text/Text';
import { ErrorAlert } from '../../generic/ErrorAlert/ErrorAlert';
import { SignInFields } from '../../../validators/SignInFields';
import { SignInForm } from '../SignInForm/SignInForm';
import { useMutation } from 'react-query';
import { signIn } from '../../../lib/post/signIn';
import { ErrorMessage } from '../../generic/ErrorMessage/ErrorMessage';

export const SignInPage = () => {
// const {mutate} = useMutation((f) => {
// return signIn(f);
// });
const [fields, setFields] = useState<SignInFields>({
email: '',
password: '',
});

const handleInputChange: ChangeEventHandler<HTMLInputElement | HTMLSelectElement> = useCallback(
({ currentTarget }) => {
setFields((previousFields) => ({
...previousFields,
[currentTarget.name]: currentTarget.value,
}));
},
[],
);

const handleSubmit: MouseEventHandler<HTMLButtonElement> = useCallback(() => 1, []);
const router = useRouter();
const login = useMutation(signIn, { onSuccess: () => router.push('/') });

return (
<Fragment>
<section className="mb-8 sm:mb-12 lg:mb-20">
<Heading className="mb-3" as="h1" variant="base" content="Logowanie" />
<Text
className="mb-12"
content="Konto jest potrzebne, aby utworzyć lokalną społeczność lub do niej dołączyć."
<section>
{login.isError && (
<ErrorMessage
title="Nie udało się zalogować!"
description="Sprawdź czy wprowadzone dane są poprawne i spróbuj ponownie. Jeśli nie możesz rozwiązać problemu, skontakuj się z administracją serwisu."
/>
<SignInForm fields={fields} onInputChange={handleInputChange} />
</section>
<Button variant="primary" content="Zaloguj się" onClick={handleSubmit} />
</Fragment>
)}
<Heading className="mb-3 lg:mb-4" as="h1" variant="base" content="Logowanie" />
<Text
className="mb-8 sm:mb-12 sm:w-1/2 sm:pr-4"
content="Konto jest potrzebne, aby utworzyć lokalną społeczność lub do niej dołączyć."
/>
<SignInForm onSubmit={login.mutate} />
</section>
);
};
110 changes: 110 additions & 0 deletions components/composited/SignUpForm/SignUpForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { FormEventHandler, Fragment, useRef, useState } from 'react';
import { SignUpFields, SignUpFieldsSch } from '../../../validators/SignUpFields';
import { Badge } from '../../generic/Badge/Badge';
import { Button } from '../../generic/Button/Button';
import { Text } from '../../generic/Text/Text';
import { ErrorMessage } from '../../generic/ErrorMessage/ErrorMessage';
import { Heading } from '../../generic/Heading/Heading';
import { Input } from '../../generic/Input/Input';

type SignUpFormProps = {
onSubmit: (fields: SignUpFields) => void;
};

export const SignUpForm = ({ onSubmit }: SignUpFormProps) => {
const formRef = useRef<HTMLFormElement>(null);
const [inputErrorMessage, setInputErrorMessage] = useState(false);

// @todo passwords match
//
// const passwordForm = z
// .object({
// password: z.string(),
// confirm: z.string(),
// })
// .refine((data) => data.password === data.confirm, {
// message: "Passwords don't match",
// path: ["confirm"], // path of error
// })
// .parse({ password: "asdf", confirm: "qwer" });

const handleSubmit: FormEventHandler<HTMLFormElement> = (e) => {
e.preventDefault();

// in a way this is a redundant check, but it is more
// strict than regular browser email regex
const formData = new FormData(e.currentTarget);

const result = SignUpFieldsSch.safeParse({
name: formData.get('fullName'),
email: formData.get('email'),
password: formData.get('password'),
phoneNo: formData.get('phone'),
address: formData.get('address'),
});

if (result.success) {
onSubmit(result.data);
setInputErrorMessage(false);
} else {
setInputErrorMessage(true);
}
};

return (
<Fragment>
{inputErrorMessage && (
<ErrorMessage
className="mb-6"
title="Błąd danych wejściowych!"
description="Wystąpił błąd danych wejściowych, sprawdź poprawność wpisanych danych. Jeśli błąd nie zniknie, skontaktuj się z administracją serwisu."
/>
)}
<form
ref={formRef}
onSubmit={handleSubmit}
className="flex flex-col items-start gap-y-12 sm:gap-y-16"
>
<fieldset className="w-full flex flex-col gap-y-6 sm:grid sm:grid-cols-2 sm:gap-y-8 sm:gap-x-8">
<Input type="text" name="fullName" label="Imię i nazwisko" placeholder="Jan Kowalski" />
<Input type="email" name="email" label="Adres email" placeholder="[email protected]" />
<Input
type="password"
name="password"
label="Hasło (minimalnie 9 znaków)"
minLength={9}
/>
<Input type="password" name="passwordConfirmation" label="Powtórz hasło" minLength={9} />
</fieldset>

<fieldset className="w-full flex flex-col">
<legend className="mb-8">
<div className="flex gap-3 items-center mb-3">
<Heading as="h2" variant="smBold" content="Dodatkowe informacje" />
<Badge color="blue" textContent="Polecamy!" />
</div>
<Text content="Wypełnienie poniższych pól może okazać się przydatne dla Twoich społeczności." />
</legend>
<div className="flex flex-col gap-6 sm:grid sm:grid-cols-2 sm:gap-8">
<Input
type="tel"
name="phone"
label="Numer telefonu"
placeholder="123 456 789"
required={false}
/>
<Input
type="text"
name="address"
label="Adres zamieszkania"
placeholder="Ostrowiec Świętokrzyski, ul. Sandomierska 2"
required={false}
/>
</div>
</fieldset>

<Button type="submit" content="Zarejestruj się" variant="primary" />
</form>
</Fragment>
);
};
85 changes: 15 additions & 70 deletions components/composited/SignUpPage/SignUpPage.tsx
Original file line number Diff line number Diff line change
@@ -1,78 +1,23 @@
import { ChangeEventHandler, Fragment, MouseEventHandler, useCallback, useState } from 'react';
import { Badge } from '../../generic/Badge/Badge';
import { Button } from '../../generic/Button/Button';
import { useRouter } from 'next/router';
import { useMutation } from 'react-query';

import { signUp } from '../../../lib/post/signUp';
import { Heading } from '../../generic/Heading/Heading';
import { DetailsForm } from '../../generic/DetailsForm/DetailsForm';
import { SignUpForm } from '../../generic/SignUpForm/SignUpForm';
import { Text } from '../../generic/Text/Text';
import { formatPhoneInput } from './formatPhoneInput';
import { useUser } from '../../../hooks/useUser';
import { ErrorAlert } from '../../generic/ErrorAlert/ErrorAlert';

export type SignUpFormFields = {
fullName: string;
email: string;
password: string;
passwordConfirmation: string;
phone: string;
address: string;
};
import { SignUpForm } from '../SignUpForm/SignUpForm';

export const SignUpPage = () => {
const { user } = useUser();

console.log(user);

const [fields, setFields] = useState<SignUpFormFields>({
fullName: '',
email: '',
password: '',
passwordConfirmation: '',
phone: '',
address: '',
});

const handleInputChange: ChangeEventHandler<HTMLInputElement | HTMLSelectElement> = useCallback(
({ currentTarget }) => {
const value =
currentTarget.type === 'tel' ? formatPhoneInput(currentTarget.value) : currentTarget.value;

setFields((previousFields) => ({
...previousFields,
[currentTarget.name]: value,
}));
},
[],
);

const handleSubmit: MouseEventHandler<HTMLButtonElement> = useCallback(() => 1, []);
const handleDataSave: MouseEventHandler<HTMLButtonElement> = useCallback(() => 1, []);
const router = useRouter();
const register = useMutation(signUp, { onSuccess: () => router.push('/') });

return (
<Fragment>
<section className="mb-12">
<Heading className="mb-3" as="h1" variant="base" content="Rejestracja" />
<Text
className="mb-12"
content="Konto jest potrzebne, aby utworzyć lokalną społeczność lub do niej dołączyć."
/>
<SignUpForm fields={fields} onInputChange={handleInputChange} />
</section>
<section className="mb-16 sm:mb-20 lg:mb-24">
<div className="flex gap-3 items-center mb-3">
<Heading as="h2" variant="smBold" content="Dodatkowe informacje" />
<Badge color="blue" textContent="Polecamy!" />
</div>
<Text
className="mb-12"
content="Wypełnienie poniższych pól może okazać się przydatne dla Twoich społeczności."
/>
<DetailsForm fields={fields} onInputChange={handleInputChange} />
</section>
<div className="flex gap-4 sm:gap-6 lg:gap-8">
<Button variant="primary" content="Zarejestruj się" onClick={handleSubmit} />
<Button variant="plain" content="Zachowaj dane" onClick={handleDataSave} />
</div>
</Fragment>
<section>
<Heading className="mb-3 lg:mb-4" as="h1" variant="base" content="Rejestracja" />
<Text
className="mb-8 sm:mb-12 sm:w-1/2 sm:pr-4"
content="Konto jest potrzebne, aby utworzyć lokalną społeczność lub do niej dołączyć."
/>
<SignUpForm onSubmit={register.mutate} />
</section>
);
};
7 changes: 0 additions & 7 deletions components/composited/SignUpPage/formatPhoneInput.ts

This file was deleted.

24 changes: 24 additions & 0 deletions components/generic/Auth/Auth.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useRouter } from 'next/router';
import { Fragment, useEffect } from 'react';
import { useUser } from '../../../hooks/useUser';

type AuthProps = {
children: React.ReactNode;
};

export const Auth = ({ children }: AuthProps) => {
const router = useRouter();
const { isLoading, logged } = useUser();

useEffect(() => {
if (!isLoading && !logged) {
void router.push('/signin');
}
}, [isLoading, logged, router]);

if (!logged) {
return null;
}

return <Fragment>{children}</Fragment>;
};
Loading

0 comments on commit b45ee2a

Please sign in to comment.