Skip to content

Commit

Permalink
feat: functional password reset
Browse files Browse the repository at this point in the history
  • Loading branch information
corp-0 committed Mar 1, 2024
1 parent b5ee007 commit 082574e
Show file tree
Hide file tree
Showing 10 changed files with 334 additions and 112 deletions.
74 changes: 36 additions & 38 deletions app/(account)/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,50 +1,48 @@
import Container from "../../common/uiLibrary/container";
import {TextInput, Label} from "flowbite-react";
import React from "react";
import Button from "../../common/uiLibrary/Button";
import Panel from "../../common/uiLibrary/panel";
import AlternativeActions from "../../common/uiLibrary/forms/alternativeActions";
import FormContainer from "../../common/uiLibrary/Layouters/formContainer";
import TextField from "../../common/uiLibrary/forms/textField";
import ContactInformation from "../../(home)/contactInformation";

const LoginPage = () => {
return (
<Container>
<div className="flex items-start justify-center min-h-screen pt-20 md:pt-32">
<Panel className='flex h-full max-w-md'>
<div className="flex flex-col gap-4 p-4 w-full">
<h2 className="text-center text-2xl font-bold">Login</h2>
<div className='flex flex-col 'style={{minHeight: 'calc(100vh - 60px)'}}>
<div className='flex-grow'>
<FormContainer title='Login'>
<TextField
label='Your email'
id='email'
type='email'
placeholder='cuban@pete.com'
required
shadow
/>

<form className="flex flex-col gap-4 w-full">
<div>
<div className="mb-2 block">
<Label htmlFor="email" value="Your email"/>
</div>
<TextInput className='w-full' id="email" type="email" placeholder="name@domain.com" required shadow/>
</div>
<TextField
label='Your password'
id='password'
type='password'
placeholder='********'
required
shadow
/>

<div>
<div className="mb-2 block">
<Label htmlFor="password" value="Your password"/>
</div>
<TextInput className='w-full' id="password" type="password" required shadow/>
</div>
<Button type="submit" className="mt-4 w-full" filled>Log in</Button>

<Button type="submit" className="mt-4 w-full" filled>Log in</Button>

</form>

<div className="text-center">
<a href="/register" className="text-sm text-gray-500 hover:text-blue-600 hover:underline">
Don&apos;t have an account?
</a>
<br/>
<a href="/reset-password"
className="text-sm text-gray-500 hover:text-blue-600 hover:underline">
Forgot your password?
</a>
</div>
</div>
</Panel>
<AlternativeActions
links={
[
{link: '/register', linkText: 'Don\'t have an account?'},
{link: '/reset-password', linkText: 'Forgot your password?'}
]
}
/>
</FormContainer>
</div>
</Container>
<ContactInformation/>
</div>

);
}

Expand Down
139 changes: 67 additions & 72 deletions app/(account)/register/page.tsx
Original file line number Diff line number Diff line change
@@ -1,87 +1,82 @@
import Container from "../../common/uiLibrary/container";
import {TextInput, Label} from "flowbite-react";
import React from "react";
import Button from "../../common/uiLibrary/Button";
import Panel from "../../common/uiLibrary/panel";
import FormContainer from "../../common/uiLibrary/Layouters/formContainer";
import TextField from "../../common/uiLibrary/forms/textField";
import AlternativeActions from "../../common/uiLibrary/forms/alternativeActions";
import ContactInformation from "../../(home)/contactInformation";

const LoginPage = () => {
const RegisterPage = () => {
return (
<Container>
<div className="flex items-start justify-center min-h-screen pt-20 md:pt-32">
<Panel className='flex h-full max-w-md'>
<div className="flex flex-col gap-4 p-4 w-full">
<h2 className="text-center text-2xl font-bold">Create a new account</h2>

<form className="flex flex-col gap-4 w-full">
<div>
<div className="mb-2 block">
<Label htmlFor="email" value="Your email"/>
</div>
<TextInput
className='w-full' id="email" type="email" placeholder="name@domain.com"
required shadow
/>
</div>
<div className='flex flex-col ' style={{minHeight: 'calc(100vh - 60px)'}}>
<div className='flex-grow'>
<FormContainer title='Create a new account'>
<TextField
label='Your email'
id='email'
type='email'
placeholder='cuban@pete.com'
shadow
/>

<div>
<div className="mb-2 block">
<Label htmlFor="unique_identifier" value="Your unique identifier"/>
</div>
<TextInput
className='w-full' id="unique_identifier" type="text"
placeholder="YourUniqueID"
required shadow
helperText={
<>
Choose a unique identifier for your account. <b>This identifier is permanent
and cannot be changed later</b>. Please choose carefully!
</>}/>
</div>
<TextField
label='Your unique identifier'
id='unique_identifier'
type='text'
placeholder='YourUniqueID'
required
shadow
helperText={
<>
Choose a unique identifier for your account. <b>This identifier is permanent
and cannot be changed later</b>. Please choose carefully!
</>}
/>

<div>
<div className="mb-2 block">
<Label htmlFor="username" value="Username"/>
</div>
<TextInput
className='w-full' id="username" type="text"
placeholder="YourUsername"
required shadow
helperText={
<>
Choose a username that will be displayed to other users. This can be changed
later.
</>}/>
</div>
<TextField
label='Username'
id='username'
type='text'
placeholder='YourUsername'
required
shadow
helperText={
<>
Choose a username that will be displayed to other users. This can be changed
later.
</>}
/>

<div>
<div className="mb-2 block">
<Label htmlFor="password" value="Your password"/>
</div>
<TextInput className='w-full' id="password" type="password" required shadow/>
</div>
<TextField
label='Your password'
id='password'
type='password'
placeholder='********'
shadow
required
/>

<div>
<div className="mb-2 block">
<Label htmlFor="password2" value="Confirm your password"/>
</div>
<TextInput className='w-full' id="password2" type="password" required shadow/>
</div>
<TextField
label='Confirm your password'
id='password2'
type='password'
placeholder='********'
shadow
required
/>

<Button type="submit" className="mt-4 w-full" filled>Register</Button>
<Button type="submit" className="mt-4 w-full" filled>Register</Button>

</form>

<div className="text-center">
<a href="/login"
className="text-sm text-gray-500 hover:text-blue-600 hover:underline">
Already have an account?
</a>
</div>
</div>
</Panel>
<AlternativeActions
links={[
{link: '/login', linkText: 'Already have an account?'}
]}
/>
</FormContainer>
</div>
</Container>
<ContactInformation/>
</div>
);
}

export default LoginPage;
export default RegisterPage;
48 changes: 48 additions & 0 deletions app/(account)/reset-password/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
'use server'

import {revalidatePath} from "next/cache";
import {z} from "zod";

export interface ResendMailResponse {
success: boolean;
email?: string;
message?: string;
fieldErrors?: { [key: string]: string };
}

export const resendMailConfirmationToken = async (prevState: ResendMailResponse, formData : FormData): Promise<ResendMailResponse> => {
const schema = z.object({
email: z.string().email(),
});

const parsed = schema.safeParse(
{ email: formData.get('email')}
);

if (!parsed.success) {
return { success: false, email: '', message: '', fieldErrors: { email: 'Invalid email address'}};
}

const email = parsed.data.email;

try {
const response = await fetch(`${process.env.CC_API_URL}/accounts/resend-account-confirmation/asd`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ email }),
});

console.log(prevState)
if (!response.ok) {
return { success: false, email: email, message: 'An unexpected error occurred'};
}

revalidatePath('reset-password');

return { success: true};
} catch (error) {
return { success: false, email: email, message: 'An unexpected error occurred'};
}
};
68 changes: 68 additions & 0 deletions app/(account)/reset-password/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
'use client'

import Button from "../../common/uiLibrary/Button";
import React from "react";
import FormContainer from "../../common/uiLibrary/Layouters/formContainer";
import TextField from "../../common/uiLibrary/forms/textField";
import {resendMailConfirmationToken, ResendMailResponse} from "./actions";
import {useFormState} from "react-dom";
import ContactInformation from "../../(home)/contactInformation";

const initialState: ResendMailResponse = {
success: false,
message: undefined,
email: ''
};

const ResetPasswordPage = () => {
const [state, formAction] = useFormState(resendMailConfirmationToken, initialState);

const successMessage = () => (
<div className='flex flex-col gap-4'>
<h3 className="text-lg text-center font-medium text-green-800">Success!</h3>
<p>An email has been sent with instructions to reset your password.</p>
<p>Please check your inbox and follow the instructions to complete the process.</p>
<p>Didn't receive the email? Check your Spam folder or try resending the email. Ensure your email address is
entered correctly.</p>
</div>
);

const errorMessages = () => {
return (
<div className='flex flex-col gap-4'>
<h3 className="text-lg text-center font-medium text-red-700">Oops!</h3>
<p>There was an unexpected error while trying to reset your password.</p>
<p>Please try again later or contact us.</p>
</div>
)
}

return (
<div className='flex flex-col ' style={{minHeight: 'calc(100vh - 60px)'}}>
<div className='flex-grow'>
<FormContainer action={formAction} title='Reset Password'>
{state.success ? successMessage() : state.message && errorMessages()}

<TextField
id='email'
name='email'
label='Email'
type='email'
placeholder='cuban@pete.com'
required
shadow
helperText={ state.fieldErrors?.email &&
<div className='text-red-700'>
{state.fieldErrors?.email}
</div>
}
/>
<Button type="submit" className="mt-4 w-full" filled>Submit</Button>
</FormContainer>
</div>
<ContactInformation/>
</div>
)
}

export default ResetPasswordPage;
34 changes: 34 additions & 0 deletions app/common/uiLibrary/Layouters/formContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import layoutChildren from "../../../../types/layoutChildren";
import Container from "../container";
import TopMiddlePlacer from "./topMiddlePlacer";
import Panel from "../panel";
import React from "react";
import classNames from "classnames";

interface FormContainerProps extends layoutChildren, React.FormHTMLAttributes<HTMLFormElement>{
title: string;
}

const FormContainer = (props: FormContainerProps) => {

const classes = classNames(
props.className ? props.className : 'flex flex-col gap-4 w-full',
);

return (
<Container>
<TopMiddlePlacer>
<Panel className='flex h-full max-w-md'>
<div className='flex flex-col gap-4 p-4 w-full'>
<h2 className="text-center text-2xl font-bold">{props.title}</h2>
<form className={classes} action={props.action}>
{props.children}
</form>
</div>
</Panel>
</TopMiddlePlacer>
</Container>
);
}

export default FormContainer;
Loading

0 comments on commit 082574e

Please sign in to comment.