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

quick fixes we found while trying on prod #75

Merged
merged 16 commits into from
Mar 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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