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

Server Rules Page #81

Open
wants to merge 21 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
ba7ad79
fix: fix bug where password confirmation field would get the username…
corp-0 Mar 9, 2024
f69a84b
fix: fix wrong URL on reset-password action which caused the process …
corp-0 Mar 9, 2024
4052a1e
fix: fix navbar links being relative instead of absolute path
corp-0 Mar 9, 2024
c5d19c7
refactor: move confirm-email page to (account) internal category
corp-0 Mar 9, 2024
c59ea10
refactor: use new guards to handle errors in confirm-email action
corp-0 Mar 9, 2024
b20f80a
refactor: simplify error handling in confirm-email actions.ts
corp-0 Mar 9, 2024
3fd1cf5
refactor: use useParams to get the dynamic url instead of rebuilding …
corp-0 Mar 9, 2024
d39ffa4
chore: remove unneeded type hint
corp-0 Mar 9, 2024
c115f47
feat: add page to type your new password as a second step in password…
corp-0 Mar 9, 2024
e336e85
chore: rename the step1 password reset request type, so it is actuall…
corp-0 Mar 9, 2024
dc04ea2
feat: finish reset-password page and functionality
corp-0 Mar 9, 2024
625b843
refactor: move layout for occupying the whole page but header into a …
corp-0 Mar 9, 2024
4fbf02d
feat: add page to resend confirmation email
corp-0 Mar 9, 2024
e460948
chore: fuck you linter
corp-0 Mar 9, 2024
596b964
chore: remove unused import
corp-0 Mar 9, 2024
8c8361e
chore: remove unused import
corp-0 Mar 9, 2024
ef25810
First Draft of rules
MaxIsJoe Jul 4, 2024
ba9bd7f
Update page.tsx
MaxIsJoe Jul 4, 2024
7194ba7
Update page.tsx
MaxIsJoe Jul 4, 2024
4437ea3
Update page.tsx
MaxIsJoe Jul 4, 2024
e116a52
COMPONENTS RGGAAHHH
MaxIsJoe Jul 4, 2024
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
28 changes: 28 additions & 0 deletions app/(account)/confirm-email/[token]/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"use server"

import {isFieldError} from "../../../../lib/auth/guards";

export const postMailConfirmationToken = async (token: string): Promise<any> => {
try {
const response = await fetch(`${process.env.CC_API_URL}/accounts/confirm-account`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({token}),
});

if (!response.ok) {
const errorResponse = await response.json();
if (isFieldError(errorResponse) && errorResponse.error.token) {
return {error: errorResponse.error.token.join(' ')};
}

return {error: "An unexpected error occurred."};
}

return {success: true};
} catch (error) {
return {error: "An unexpected error occurred."};
}
};
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
"use client"
import {usePathname} from "next/navigation";
import {useParams} from "next/navigation";
import {postMailConfirmationToken} from "./actions";
import React, {useEffect, useState} from "react";
import Panel from "../../common/uiLibrary/panel";
import ContactInformation from "../../(home)/contactInformation";
import Panel from "../../../common/uiLibrary/panel";
import ContactInformation from "../../../(home)/contactInformation";


const MailConfirmationPage = () => {
const [response, setResponse] = useState<{ success?: boolean; error?: string }>({});
const token = usePathname().split('/').filter(Boolean).pop();
const {token} = useParams<{token: string}>();

useEffect(() => {
const fetchData = async () => {
Expand Down
8 changes: 5 additions & 3 deletions app/(account)/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {AuthorizerContext} from "../../../context/AuthorizerContextProvider";
import {isFieldError, isGeneralError} from "../../../lib/auth/guards";
import GenericLoading from "../../common/uiLibrary/genericLoading";
import {redirect} from "next/navigation";
import FullPage from "../../common/uiLibrary/Layouters/fullPage";

const LoginPage = () => {
const {state, credentialsLogin} = useContext(AuthorizerContext);
Expand Down Expand Up @@ -41,7 +42,7 @@ const LoginPage = () => {

const loginForm = () => {
return (
<div className='flex flex-col' style={{minHeight: 'calc(100vh - 60px)'}}>
<FullPage>
<div className='flex-grow relative'>
{isLoading &&
<div className='absolute top-0 left-0 w-full h-full flex flex-col justify-center items-center bg-black bg-opacity-70 z-10'>
Expand Down Expand Up @@ -89,14 +90,15 @@ const LoginPage = () => {
links={
[
{link: '/register', linkText: 'Don\'t have an account?'},
{link: '/reset-password', linkText: 'Forgot your password?'}
{link: '/reset-password', linkText: 'Forgot your password?'},
{link: '/resend-confirm-email', linkText: 'Haven\'t received confirmation email yet?'},
]
}
/>
</FormContainer>
</div>
<ContactInformation/>
</div>
</FullPage>
)
}

Expand Down
4 changes: 1 addition & 3 deletions app/(account)/register/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,10 @@ const RegisterPage = () => {
placeholder='********'
shadow
required
helperText={(state.error && isFieldError(state.error) && state.error?.error.password2) ?
helperText={(state.error && isFieldError(state.error) && state.error?.error.password2) &&
<div className='text-red-700'>
{state.error?.error.password2}
</div>
:
usernameHelperText()
}
/>

Expand Down
53 changes: 53 additions & 0 deletions app/(account)/resend-confirm-email/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"use server"

import {z} from 'zod';
import {FieldError, GeneralError, isFieldError} from "../../../lib/auth/guards";

export interface ResendConfirmationMailRequest {
success: boolean;
error?: GeneralError | FieldError;
}

export const postResendConfirmationMail = async (_: ResendConfirmationMailRequest, formData: FormData): Promise<ResendConfirmationMailRequest> => {
const schema = z.object({
email: z.string().email()
});

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

if (!parsed.success) {
const fieldErrors = parsed.error.errors.reduce((acc, error) => {
return {...acc, [error.path[0]]: error.message}
}, {});

return {success: false, error: {status: 400, error: fieldErrors}};
}

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

if (!response.ok) {
const error = await response.json();
if (isFieldError(error)) {
return {success: false, error: error};
}

return {success: false, error: {error: 'An unexpected error happened', status: 500}};
}

} catch (e) {
return {success: false, error: {error: 'An unexpected error happened', status: 500}};
}

return {success: true}
}
70 changes: 70 additions & 0 deletions app/(account)/resend-confirm-email/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"use client"


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

const ResendConfirmationMail = () => {
const initialState: ResendConfirmationMailRequest = {
success: false,
}

const [state, formAction] = useFormState(postResendConfirmationMail, initialState);

const resendForm = () => {
return (
<>
{!state.success && state.error && errorMessage()}

<TextField
id='email'
name='email'
label='Email'
type='email'
placeholder='cuban@pete.com'
required
shadow
/>
<Button type="submit" className="mt-4 w-full" filled>Submit</Button>
</>
)
}
const errorMessage = () => {
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 resend the confirmation email.</p>
<p>Please try again later or contact us.</p>
</div>
)
}

const successMessage = () => (
<div className='flex flex-col gap-4'>
<h3 className="text-lg text-center font-medium text-green-800">Success!</h3>
<p>Your request has been processed successfully. If your account is found in our system and the email
address you provided matches our records, we have sent a confirmation email to that address.</p>
<p>Please check your inbox for the confirmation email. If you don&apos;t receive it within a few minutes, check
your spam or junk folder. For further assistance, don&apos;t hesitate to contact us.</p>
</div>
);

return (
<FullPage>
<div className='flex-grow'>
<FormContainer action={formAction} title='Resend password confirmation'>
{state.success ? successMessage() : resendForm()}
</FormContainer>
</div>
<ContactInformation/>
</FullPage>
)
};

export default ResendConfirmationMail;
75 changes: 75 additions & 0 deletions app/(account)/reset-password/[token]/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"use server"

import {FieldError, GeneralError, isFieldError, isGeneralError} from "../../../../lib/auth/guards";
import {z} from "zod";

export interface ResetPasswordStep2Response {
success: boolean;
error?: GeneralError | FieldError;
}

export const postPasswordReset = async (_: ResetPasswordStep2Response, formData: FormData): Promise<ResetPasswordStep2Response> => {
const schema = z.object({
password: z.string().min(6),
token: z.string().min(1)
});

const parsed = schema.safeParse({
password: formData.get('password'),
password2: formData.get('password2'),
token: formData.get('token')
});

if (!parsed.success) {
const fieldErrors = parsed.error.errors.reduce((acc, error) => {
return {...acc, [error.path[0]]: error.message}
}, {});

return {success: false, error: {status: 400, error: fieldErrors}};
}

const password2 = formData.get('password2');
if (password2 !== parsed.data.password) {
return {
success: false,
error:
{
status: 400,
error: {
password2: ['Passwords do not match']
}
}
}
}

const body = {
password: parsed.data.password,
token: parsed.data.token
}

try {
const response = await fetch(`${process.env.CC_API_URL}/accounts/reset-password/${parsed.data.token}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});

if (!response.ok) {
if (isFieldError(response) && response.error.token) {
return {success: false, error: {status: 400, error: "Your link has expired. Please request a new one."}};
}

if (isFieldError(response) || isGeneralError(response)) {
return {success: false, error: response};
}

return { success: false, error: {status: 400, error: "An unexpected error occurred"}};
}

return { success: true};
} catch (error) {
return { success: false, error: {status: 400, error: "An unexpected error occurred"}};
}
}
93 changes: 93 additions & 0 deletions app/(account)/reset-password/[token]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"use client"


import {useParams} from "next/navigation";
import React from "react";
import FormContainer from "../../../common/uiLibrary/Layouters/formContainer";
import TextField from "../../../common/uiLibrary/forms/textField";
import Button from "../../../common/uiLibrary/Button";
import {useFormState} from "react-dom";
import {postPasswordReset, ResetPasswordStep2Response} from "./actions";
import {isFieldError} from "../../../../lib/auth/guards";
import FullPage from "../../../common/uiLibrary/Layouters/fullPage";

const ResetPasswordPageStep2 = () => {
const {token} = useParams<{ token: string }>();
const initialState: ResetPasswordStep2Response = {
success: false,
error: undefined
}

const [state, formAction] = useFormState(postPasswordReset, initialState);

const successMessage = () => (
<div className='flex flex-col gap-4'>
<h3 className="text-lg text-center font-medium text-green-800">Success!</h3>
<p>Your password has been reset successfully.</p>
<p>You can now log in using your new password.</p>
</div>
);

const errorMessage = () => {
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 error while trying to reset your password. Your password-reset token might be invalid or expired.</p>
<p>Please try requesting a new password reset or contact us.</p>
</div>
)
}

const resetPasswordForm = () => {
return (
<>
{state.error && errorMessage()}
<TextField
label='New password'
type='password'
id='password'
name='password'
placeholder='********'
required
shadow
helperText={state.error && isFieldError(state.error) && state.error?.error.password &&
<div className='text-red-700'>
{state.error?.error.password}
</div>
}
/>

<TextField
label='Confirm your password'
type='password'
id='password2'
name='password2'
placeholder='********'
required
shadow
helperText={state.error && isFieldError(state.error) && state.error?.error.password2 &&
<div className='text-red-700'>
{state.error?.error.password2}
</div>
}
/>

<input type='hidden' id='token' name='token' value={token}/>

<Button type="submit" className="mt-4 w-full" filled>Reset Password</Button>
</>
)
}

return (
<FullPage>
<div className='flex-grow relative'>
<FormContainer action={formAction} title='Reset your password'>
{state.success ? successMessage() : resetPasswordForm()}
</FormContainer>
</div>
</FullPage>
)
}

export default ResetPasswordPageStep2;
Loading
Loading