Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop' into alex/implement-adm…
Browse files Browse the repository at this point in the history
…in-league-operation
  • Loading branch information
alexappleget committed Oct 24, 2024
2 parents 2e8b5ae + a267371 commit 8f62f60
Show file tree
Hide file tree
Showing 15 changed files with 436 additions and 265 deletions.
10 changes: 10 additions & 0 deletions api/apiFunctions.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ export interface IAccountData {
}
export interface IUser {
documentId: string;
// for the appwrite auth collection
id: string;
email: string;
leagues: string[];
labels: string[];
}

export interface ICollectionUser {
documentId: string;
// for the custom user collection
id: string;
email: string;
leagues: string[];
Expand Down
5 changes: 4 additions & 1 deletion api/apiFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ILeague,
IGameWeek,
IUser,
ICollectionUser,
IWeeklyPicks,
INFLTeam,
IRecoveryToken,
Expand Down Expand Up @@ -149,7 +150,9 @@ export async function updateUserEmail({
* @param userId - The user ID
* @returns {Models.DocumentList<Models.Document> | Error} - The user object or an error
*/
export async function getCurrentUser(userId: IUser['id']): Promise<IUser> {
export async function getCurrentUser(
userId: IUser['id'],
): Promise<ICollectionUser> {
try {
const user = await databases.listDocuments(
appwriteConfig.databaseId,
Expand Down
7 changes: 7 additions & 0 deletions app/(main)/league/all/page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ jest.mock('@/store/dataStore', () => ({
id: '1234',
email: '[email protected]',
leagues: ['league1'],
labels: [],
},
allLeagues: [
{
Expand Down Expand Up @@ -82,6 +83,7 @@ describe('Leagues Component', () => {
leagues: [],
},
allLeagues: [],
labels: [],
});

render(<Leagues />);
Expand All @@ -105,6 +107,7 @@ describe('Leagues Component', () => {
leagues: [],
},
allLeagues: [],
labels: [],
});

render(<Leagues />);
Expand All @@ -121,6 +124,7 @@ describe('Leagues Component', () => {
email: '[email protected]',
id: '123',
leagues: [],
labels: [],
},
allLeagues: [
{
Expand Down Expand Up @@ -150,6 +154,7 @@ describe('Leagues Component', () => {
email: '[email protected]',
id: '123',
leagues: [],
labels: [],
};

const league = {
Expand Down Expand Up @@ -202,6 +207,7 @@ describe('Leagues Component', () => {
user.id,
user.email,
[...user.leagues, league.leagueId],
user.labels,
);
expect(toast.custom).toHaveBeenCalledWith(
<Alert
Expand All @@ -220,6 +226,7 @@ describe('Leagues Component', () => {
email: '[email protected]',
id: '123',
leagues: [],
labels: [],
};

const league = {
Expand Down
11 changes: 7 additions & 4 deletions app/(main)/league/all/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,13 @@ const Leagues = (): JSX.Element => {
});

setLeagues([...leagues, league]);
updateUser(user.documentId, user.id, user.email, [
...user.leagues,
league.leagueId,
]);
updateUser(
user.documentId,
user.id,
user.email,
[...user.leagues, league.leagueId],
user.labels,
);
toast.custom(
<Alert
variant={AlertVariants.Success}
Expand Down
261 changes: 261 additions & 0 deletions app/(main)/register/Register.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
// Copyright (c) Gridiron Survivor.
// Licensed under the MIT License.

'use client';
import { AlertVariants } from '@/components/AlertNotification/Alerts.enum';
import { Button } from '@/components/Button/Button';
import { Control, useForm, useWatch, SubmitHandler } from 'react-hook-form';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '../../../components/Form/Form';
import { Input } from '@/components/Input/Input';
import { registerAccount } from '@/api/apiFunctions';
import { toast } from 'react-hot-toast';
import { useAuthContext } from '@/context/AuthContextProvider';
import { useRouter } from 'next/navigation';
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
import Alert from '@/components/AlertNotification/AlertNotification';
import LinkCustom from '@/components/LinkCustom/LinkCustom';
import Logo from '@/components/Logo/Logo';
import logo from '/public/assets/logo-colored-outline.svg';
import React, { JSX, useEffect, useState } from 'react';
import LoadingSpinner from '@/components/LoadingSpinner/LoadingSpinner';

const RegisterUserSchema = z
.object({
email: z
.string()
.min(1, { message: 'Please enter an email address' })
.email({ message: 'Please enter a valid email address' }),
password: z
.string()
.min(1, { message: 'Please enter a password' })
.min(8, { message: 'Password must be at least 8 characters' }),
confirmPassword: z
.string()
.min(1, { message: 'Please confirm your password' })
.min(8, { message: 'Password must be at least 8 characters' }),
})
.refine((data) => data.password === data.confirmPassword, {
message: "Passwords don't match",
path: ['confirmPassword'],
});

type RegisterUserSchemaType = z.infer<typeof RegisterUserSchema>;

/**
* Renders the registration page.
* @returns {JSX.Element} The rendered registration page.
*/
const Register = (): JSX.Element => {
const router = useRouter();
const { login, isSignedIn } = useAuthContext();
const [isLoading, setIsLoading] = useState(false);

useEffect(() => {
if (isSignedIn) {
router.push('/league/all');
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isSignedIn]);

const form = useForm<RegisterUserSchemaType>({
resolver: zodResolver(RegisterUserSchema),
});

/**
* The current value of the 'email' field in the form.
* @type {string}
*/
const email: string = useWatch({
control: form.control,
name: 'email',
defaultValue: '',
});

/**
* The current value of the 'password' field in the form.
* @type {string}
*/
const password: string = useWatch({
control: form.control,
name: 'password',
defaultValue: '',
});

/**
* The current value of the 'confirmPassword' field in the form.
* @type {string}
*/
const confirmPassword: string = useWatch({
control: form.control,
name: 'confirmPassword',
defaultValue: '',
});

/**
* A function that handles form submission.
* @param {RegisterUserSchemaType} data - The data submitted in the form.
* @returns {Promise<void>} Promise that resolves after form submission is processed.
*/
const onSubmit: SubmitHandler<RegisterUserSchemaType> = async (
data: RegisterUserSchemaType,
): Promise<void> => {
try {
setIsLoading(true);
await registerAccount(data);
await login(data);
toast.custom(
<Alert
variant={AlertVariants.Success}
message="You have successfully registered your account."
/>,
);
} catch (error) {
console.error('Registration Failed', error);
toast.custom(
<Alert variant={AlertVariants.Error} message="Something went wrong!" />,
);
} finally {
setIsLoading(false);
}
};

const isDisabled = !email || !password || password !== confirmPassword;

return (
<section className="grid xl:grid-cols-2 xl:grid-rows-none">
<div
data-testid="dark-mode-section"
className="row-span-1 grid w-full place-items-center from-[#4E160E] to-zinc-950 bg-gradient-to-b xl:h-screen xl:bg-gradient-to-b"
>
<Logo className="mx-auto w-52 xl:w-64 xl:place-self-end" src={logo} />
<div className="mx-auto grid gap-4 place-self-end px-8 pb-8 text-foreground">
<p className="hidden leading-7 xl:block">
Thank you... fantasy football draft, for letting me know that even
in my fantasies, I am bad at sports.
</p>
<p className="hidden leading-7 xl:block">Jimmy Fallon</p>
</div>
</div>
<div className="row-span-1 mx-auto grid max-w-sm justify-center space-y-4 px-4 xl:flex xl:flex-col">
<div>
<h1 className="text-5xl font-extrabold tracking-tight text-foreground">
Register A New Account
</h1>
<p className="pb-4 font-normal leading-7 text-muted-foreground">
If you have an existing account{' '}
<LinkCustom href="/login">Login!</LinkCustom>
</p>
</div>

<Form {...form}>
<form
id="input-container"
className="grid gap-3"
data-testid="register-form"
onSubmit={form.handleSubmit(onSubmit)}
>
<FormField
control={form.control as Control<object>}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel className="border-none px-0 py-0 pb-2 pl-1">
Email
</FormLabel>
<FormControl>
<Input
data-testid="email"
type="email"
placeholder="Your email"
{...field}
/>
</FormControl>
{form.formState.errors?.email && (
<FormMessage>
{form.formState.errors?.email.message}
</FormMessage>
)}
</FormItem>
)}
/>
<FormField
control={form.control as Control<object>}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel className="border-none px-0 py-0 pb-2 pl-1">
Password
</FormLabel>
<FormControl>
<Input
data-testid="password"
type="password"
placeholder="Your password"
{...field}
/>
</FormControl>
{form.formState.errors?.password && (
<FormMessage>
{form.formState.errors?.password.message}
</FormMessage>
)}
</FormItem>
)}
/>
<FormField
control={form.control as Control<object>}
name="confirmPassword"
render={({ field }) => (
<FormItem>
<FormLabel className="border-none px-0 py-0 pb-2 pl-1">
Confirm Password
</FormLabel>
<FormControl>
<Input
data-testid="confirm-password"
type="password"
placeholder="Confirm your password"
{...field}
/>
</FormControl>
{form.formState.errors?.confirmPassword && (
<FormMessage>
{form.formState.errors?.confirmPassword.message}
</FormMessage>
)}
</FormItem>
)}
/>

<Button
data-testid="continue-button"
label={
isLoading ? (
<LoadingSpinner data-testid="loading-spinner" />
) : (
'Continue'
)
}
type="submit"
disabled={isDisabled}
/>
<p className="pb-4 font-normal leading-7 text-muted-foreground">
<LinkCustom href="/login">Login</LinkCustom>
{' '} to get started playing.
</p>
</form>
</Form>
</div>
</section>
);
};

export default Register;
Loading

0 comments on commit 8f62f60

Please sign in to comment.