From f7b5012129b0c8a9eab4241de0cafcdb0f20e267 Mon Sep 17 00:00:00 2001 From: Gilles <43683714+corp-0@users.noreply.github.com> Date: Sat, 9 Mar 2024 13:42:51 -0300 Subject: [PATCH 01/16] fix: fix bug where password confirmation field would get the username helper text. --- app/(account)/register/page.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/(account)/register/page.tsx b/app/(account)/register/page.tsx index 2060643..f318007 100644 --- a/app/(account)/register/page.tsx +++ b/app/(account)/register/page.tsx @@ -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) &&
{state.error?.error.password2}
- : - usernameHelperText() } /> From 9e6af2d8dc9bb41c57011e9bee9db2e62e71dd72 Mon Sep 17 00:00:00 2001 From: Gilles <43683714+corp-0@users.noreply.github.com> Date: Sat, 9 Mar 2024 13:45:30 -0300 Subject: [PATCH 02/16] fix: fix wrong URL on reset-password action which caused the process to always fail --- app/(account)/reset-password/actions.ts | 4 ++-- app/(account)/reset-password/page.tsx | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/(account)/reset-password/actions.ts b/app/(account)/reset-password/actions.ts index 3ed3af8..6064da0 100644 --- a/app/(account)/reset-password/actions.ts +++ b/app/(account)/reset-password/actions.ts @@ -10,7 +10,7 @@ export interface ResendMailResponse { fieldErrors?: { [key: string]: string }; } -export const resendMailConfirmationToken = async (prevState: ResendMailResponse, formData : FormData): Promise => { +export const requestAPasswordReset = async (prevState: ResendMailResponse, formData : FormData): Promise => { const schema = z.object({ email: z.string().email(), }); @@ -26,7 +26,7 @@ export const resendMailConfirmationToken = async (prevState: ResendMailResponse, const email = parsed.data.email; try { - const response = await fetch(`${process.env.CC_API_URL}/accounts/resend-account-confirmation/asd`, { + const response = await fetch(`${process.env.CC_API_URL}/accounts/reset-password/`, { method: "POST", headers: { "Content-Type": "application/json", diff --git a/app/(account)/reset-password/page.tsx b/app/(account)/reset-password/page.tsx index 6dba675..388584c 100644 --- a/app/(account)/reset-password/page.tsx +++ b/app/(account)/reset-password/page.tsx @@ -4,7 +4,7 @@ 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 {requestAPasswordReset, ResendMailResponse} from "./actions"; import {useFormState} from "react-dom"; import ContactInformation from "../../(home)/contactInformation"; @@ -15,14 +15,14 @@ const initialState: ResendMailResponse = { }; const ResetPasswordPage = () => { - const [state, formAction] = useFormState(resendMailConfirmationToken, initialState); + const [state, formAction] = useFormState(requestAPasswordReset, initialState); const successMessage = () => (

Success!

An email has been sent with instructions to reset your password.

Please check your inbox and follow the instructions to complete the process.

-

Didn't receive the email? Check your Spam folder or try resending the email. Ensure your email address is +

Didn't receive the email? Check your Spam folder or try requesting a password reset again. Ensure your email address is entered correctly.

); From 00d5b3fe2b020cf301486fdf6d87c8444abc3746 Mon Sep 17 00:00:00 2001 From: Gilles <43683714+corp-0@users.noreply.github.com> Date: Sat, 9 Mar 2024 13:54:16 -0300 Subject: [PATCH 03/16] fix: fix navbar links being relative instead of absolute path --- app/common/defaultNavbar.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/app/common/defaultNavbar.tsx b/app/common/defaultNavbar.tsx index d1fb833..14da603 100644 --- a/app/common/defaultNavbar.tsx +++ b/app/common/defaultNavbar.tsx @@ -16,9 +16,10 @@ export default function DefaultNavbar() { const loggedOptions = () => ( <> - My Account + {/* hidden while we work on it */} + {/*My Account*/} - Logout + Logout ) @@ -26,11 +27,11 @@ export default function DefaultNavbar() { const notLoggedOptions = () => ( <> - Login/Register + Login/Register - Reset password + Reset password ) @@ -50,10 +51,10 @@ export default function DefaultNavbar() {

Home

- +

Blog

- +

Changelog

From 7c6a3e34b63c23f2a2b026a289899315a96c36c7 Mon Sep 17 00:00:00 2001 From: Gilles <43683714+corp-0@users.noreply.github.com> Date: Sat, 9 Mar 2024 14:02:21 -0300 Subject: [PATCH 04/16] refactor: move confirm-email page to (account) internal category --- app/{ => (account)}/confirm-email/[token]/actions.ts | 0 app/{ => (account)}/confirm-email/[token]/page.tsx | 5 +++-- 2 files changed, 3 insertions(+), 2 deletions(-) rename app/{ => (account)}/confirm-email/[token]/actions.ts (100%) rename app/{ => (account)}/confirm-email/[token]/page.tsx (91%) diff --git a/app/confirm-email/[token]/actions.ts b/app/(account)/confirm-email/[token]/actions.ts similarity index 100% rename from app/confirm-email/[token]/actions.ts rename to app/(account)/confirm-email/[token]/actions.ts diff --git a/app/confirm-email/[token]/page.tsx b/app/(account)/confirm-email/[token]/page.tsx similarity index 91% rename from app/confirm-email/[token]/page.tsx rename to app/(account)/confirm-email/[token]/page.tsx index 771a6de..92defc2 100644 --- a/app/confirm-email/[token]/page.tsx +++ b/app/(account)/confirm-email/[token]/page.tsx @@ -2,8 +2,9 @@ import {usePathname} 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 }>({}); From 4e6b181c709233080dc7c6154c298a137f74f0f3 Mon Sep 17 00:00:00 2001 From: Gilles <43683714+corp-0@users.noreply.github.com> Date: Sat, 9 Mar 2024 14:05:49 -0300 Subject: [PATCH 05/16] refactor: use new guards to handle errors in confirm-email action --- app/(account)/confirm-email/[token]/actions.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/(account)/confirm-email/[token]/actions.ts b/app/(account)/confirm-email/[token]/actions.ts index 0ebe69b..bb54cfd 100644 --- a/app/(account)/confirm-email/[token]/actions.ts +++ b/app/(account)/confirm-email/[token]/actions.ts @@ -1,5 +1,7 @@ "use server" +import {isFieldError, isGeneralError} from "../../../../lib/auth/guards"; + export const postMailConfirmationToken = async (token: string): Promise => { try { const response = await fetch(`${process.env.CC_API_URL}/accounts/confirm-account`, { @@ -13,12 +15,12 @@ export const postMailConfirmationToken = async (token: string): Promise => if (!response.ok) { const errorResponse = await response.json(); if (errorResponse.error) { - if (typeof errorResponse.error === 'string') { - return { error: errorResponse.error }; - } else if (errorResponse.error.token) { + if (isGeneralError(errorResponse.error)) { + return { error: "An unexpected error occurred." }; + } + + if (isFieldError(errorResponse.error) && errorResponse.error.token) { return { error: errorResponse.error.token.join(' ') }; - } else if (errorResponse.error.non_field_errors) { - return { error: errorResponse.error.non_field_errors.join(' ') }; } } return { error: "An unexpected error occurred." }; From dba5cebe5754c241757c8a23a491e139239e6a9e Mon Sep 17 00:00:00 2001 From: Gilles <43683714+corp-0@users.noreply.github.com> Date: Sat, 9 Mar 2024 14:33:00 -0300 Subject: [PATCH 06/16] refactor: simplify error handling in confirm-email actions.ts --- .../confirm-email/[token]/actions.ts | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/app/(account)/confirm-email/[token]/actions.ts b/app/(account)/confirm-email/[token]/actions.ts index bb54cfd..deedb65 100644 --- a/app/(account)/confirm-email/[token]/actions.ts +++ b/app/(account)/confirm-email/[token]/actions.ts @@ -9,25 +9,20 @@ export const postMailConfirmationToken = async (token: string): Promise => headers: { "Content-Type": "application/json", }, - body: JSON.stringify({ token }), + body: JSON.stringify({token}), }); if (!response.ok) { const errorResponse = await response.json(); - if (errorResponse.error) { - if (isGeneralError(errorResponse.error)) { - return { error: "An unexpected error occurred." }; - } - - if (isFieldError(errorResponse.error) && errorResponse.error.token) { - return { error: errorResponse.error.token.join(' ') }; - } + if (isFieldError(errorResponse) && errorResponse.error.token) { + return {error: errorResponse.error.token.join(' ')}; } - return { error: "An unexpected error occurred." }; + + return {error: "An unexpected error occurred."}; } - return { success: true }; + return {success: true}; } catch (error) { - return { error: "An unexpected error occurred." }; + return {error: "An unexpected error occurred."}; } }; From 044a77272b92898aaf4ebfec6ee9e11c700cea07 Mon Sep 17 00:00:00 2001 From: Gilles <43683714+corp-0@users.noreply.github.com> Date: Sat, 9 Mar 2024 14:33:59 -0300 Subject: [PATCH 07/16] refactor: use useParams to get the dynamic url instead of rebuilding it with usePathname in confirm-email --- app/(account)/confirm-email/[token]/page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/(account)/confirm-email/[token]/page.tsx b/app/(account)/confirm-email/[token]/page.tsx index 92defc2..acb087a 100644 --- a/app/(account)/confirm-email/[token]/page.tsx +++ b/app/(account)/confirm-email/[token]/page.tsx @@ -1,5 +1,5 @@ "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"; @@ -8,7 +8,7 @@ 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 () => { From 043152416ce394bacfa3883b040d563f773d3210 Mon Sep 17 00:00:00 2001 From: Gilles <43683714+corp-0@users.noreply.github.com> Date: Sat, 9 Mar 2024 14:34:22 -0300 Subject: [PATCH 08/16] chore: remove unneeded type hint --- app/(home)/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/(home)/page.tsx b/app/(home)/page.tsx index 2207cc2..ba71221 100644 --- a/app/(home)/page.tsx +++ b/app/(home)/page.tsx @@ -49,7 +49,7 @@ const fetchLatestBlogPost = async (): Promise => { return resPage1.results.concat(resPage2.results); } -const HomePage: () => Promise = async () => { +const HomePage = async () => { const latestBlogPosts: BlogPost[] = await fetchLatestBlogPost(); const NoSsrHeroImageClient = dynamic(() => import('./HeroRandomImageClient'), {ssr: false}); From d1d401be71ab5bbe82f81b31a431cfe1269c0d0b Mon Sep 17 00:00:00 2001 From: Gilles <43683714+corp-0@users.noreply.github.com> Date: Sat, 9 Mar 2024 15:54:03 -0300 Subject: [PATCH 09/16] feat: add page to type your new password as a second step in password-reset process --- .../reset-password/[token]/actions.ts | 65 ++++++++++++++ app/(account)/reset-password/[token]/page.tsx | 86 +++++++++++++++++++ 2 files changed, 151 insertions(+) create mode 100644 app/(account)/reset-password/[token]/actions.ts create mode 100644 app/(account)/reset-password/[token]/page.tsx diff --git a/app/(account)/reset-password/[token]/actions.ts b/app/(account)/reset-password/[token]/actions.ts new file mode 100644 index 0000000..115104f --- /dev/null +++ b/app/(account)/reset-password/[token]/actions.ts @@ -0,0 +1,65 @@ +import {FieldError, GeneralError} from "../../../../lib/auth/guards"; +import {z} from "zod"; + +export interface ResetPasswordStep2Response { + success: boolean; + error?: GeneralError | FieldError; +} + +export const postPasswordReset = async (_: ResetPasswordStep2Response, formData: FormData): Promise => { + 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) { + return { success: false, error: {status: 400, error: {password: ['An unexpected error occurred']}}}; + } + + return { success: true}; + } catch (error) { + return { success: false, error: {status: 400, error: {password: ['An unexpected error occurred']}}}; + } +} \ No newline at end of file diff --git a/app/(account)/reset-password/[token]/page.tsx b/app/(account)/reset-password/[token]/page.tsx new file mode 100644 index 0000000..d296f1b --- /dev/null +++ b/app/(account)/reset-password/[token]/page.tsx @@ -0,0 +1,86 @@ +"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"; + +const ResetPasswordPageStep2 = () => { + const {token} = useParams<{token: string}>(); + const initialState: ResetPasswordStep2Response = { + success: false, + error: undefined + } + + const [state, formAction] = useFormState(postPasswordReset, initialState); + + const successMessage = () => ( +
+

Success!

+

Your password has been reset successfully.

+

You can now log in using your new password.

+
+ ); + + const errorMessage = () => { + return ( +
+

Oops!

+

There was an unexpected error while trying to reset your password.

+

Please try again later or contact us.

+
+ ) + } + + return ( +
+
+ + {state.success ? successMessage() : errorMessage()} + + + {state.error?.error.password} +
+ } + /> + + + {state.error?.error.password2} +
+ } + /> + + + + + + + + + ) +} + +export default ResetPasswordPageStep2; \ No newline at end of file From a9233a9a7b1e0d1613f3a46a24e7b30a830891b8 Mon Sep 17 00:00:00 2001 From: Gilles <43683714+corp-0@users.noreply.github.com> Date: Sat, 9 Mar 2024 15:55:43 -0300 Subject: [PATCH 10/16] chore: rename the step1 password reset request type, so it is actually called what it represents --- app/(account)/reset-password/actions.ts | 4 ++-- app/(account)/reset-password/page.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/(account)/reset-password/actions.ts b/app/(account)/reset-password/actions.ts index 6064da0..675c059 100644 --- a/app/(account)/reset-password/actions.ts +++ b/app/(account)/reset-password/actions.ts @@ -3,14 +3,14 @@ import {revalidatePath} from "next/cache"; import {z} from "zod"; -export interface ResendMailResponse { +export interface ResetPassowrdStep1 { success: boolean; email?: string; message?: string; fieldErrors?: { [key: string]: string }; } -export const requestAPasswordReset = async (prevState: ResendMailResponse, formData : FormData): Promise => { +export const requestAPasswordReset = async (_: ResetPassowrdStep1, formData : FormData): Promise => { const schema = z.object({ email: z.string().email(), }); diff --git a/app/(account)/reset-password/page.tsx b/app/(account)/reset-password/page.tsx index 388584c..ff32b60 100644 --- a/app/(account)/reset-password/page.tsx +++ b/app/(account)/reset-password/page.tsx @@ -4,11 +4,11 @@ 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 {requestAPasswordReset, ResendMailResponse} from "./actions"; +import {requestAPasswordReset, ResetPassowrdStep1} from "./actions"; import {useFormState} from "react-dom"; import ContactInformation from "../../(home)/contactInformation"; -const initialState: ResendMailResponse = { +const initialState: ResetPassowrdStep1 = { success: false, message: undefined, email: '' From 54cbe060e0ee4fd45d907450350ec23691c7b1ad Mon Sep 17 00:00:00 2001 From: Gilles <43683714+corp-0@users.noreply.github.com> Date: Sat, 9 Mar 2024 16:29:23 -0300 Subject: [PATCH 11/16] feat: finish reset-password page and functionality --- app/(account)/login/page.tsx | 3 +- .../reset-password/[token]/actions.ts | 16 ++++- app/(account)/reset-password/[token]/page.tsx | 68 ++++++++++--------- 3 files changed, 52 insertions(+), 35 deletions(-) diff --git a/app/(account)/login/page.tsx b/app/(account)/login/page.tsx index bde0fa6..d6eb1c1 100644 --- a/app/(account)/login/page.tsx +++ b/app/(account)/login/page.tsx @@ -89,7 +89,8 @@ 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-confirmation-email', linkText: 'Haven\'t received confirmation email yet?'}, ] } /> diff --git a/app/(account)/reset-password/[token]/actions.ts b/app/(account)/reset-password/[token]/actions.ts index 115104f..f233447 100644 --- a/app/(account)/reset-password/[token]/actions.ts +++ b/app/(account)/reset-password/[token]/actions.ts @@ -1,4 +1,6 @@ -import {FieldError, GeneralError} from "../../../../lib/auth/guards"; +"use server" + +import {FieldError, GeneralError, isFieldError, isGeneralError} from "../../../../lib/auth/guards"; import {z} from "zod"; export interface ResetPasswordStep2Response { @@ -55,11 +57,19 @@ export const postPasswordReset = async (_: ResetPasswordStep2Response, formData: }); if (!response.ok) { - return { success: false, error: {status: 400, error: {password: ['An unexpected error occurred']}}}; + 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: {password: ['An unexpected error occurred']}}}; + return { success: false, error: {status: 400, error: "An unexpected error occurred"}}; } } \ No newline at end of file diff --git a/app/(account)/reset-password/[token]/page.tsx b/app/(account)/reset-password/[token]/page.tsx index d296f1b..b3e4e99 100644 --- a/app/(account)/reset-password/[token]/page.tsx +++ b/app/(account)/reset-password/[token]/page.tsx @@ -11,7 +11,7 @@ import {postPasswordReset, ResetPasswordStep2Response} from "./actions"; import {isFieldError} from "../../../../lib/auth/guards"; const ResetPasswordPageStep2 = () => { - const {token} = useParams<{token: string}>(); + const {token} = useParams<{ token: string }>(); const initialState: ResetPasswordStep2Response = { success: false, error: undefined @@ -31,52 +31,58 @@ const ResetPasswordPageStep2 = () => { return (

Oops!

-

There was an unexpected error while trying to reset your password.

-

Please try again later or contact us.

+

There was an error while trying to reset your password. Your password-reset token might be invalid or expired.

+

Please try requesting a new password reset or contact us.

) } - return ( -
-
- - {state.success ? successMessage() : errorMessage()} - - { + return ( + <> + {state.error && errorMessage()} + {state.error?.error.password}
} - /> + /> - {state.error?.error.password2}
} - /> + /> - + - + + + ) + } + return ( +
+
+ + {state.success ? successMessage() : resetPasswordForm()}
From 7b1651ef7dbc36201258c93c6d21e7e97814cf8e Mon Sep 17 00:00:00 2001 From: Gilles <43683714+corp-0@users.noreply.github.com> Date: Sat, 9 Mar 2024 16:42:05 -0300 Subject: [PATCH 12/16] refactor: move layout for occupying the whole page but header into a component --- app/(account)/login/page.tsx | 7 ++++--- app/(account)/reset-password/[token]/page.tsx | 5 +++-- app/common/uiLibrary/Layouters/fullPage.tsx | 12 ++++++++++++ 3 files changed, 19 insertions(+), 5 deletions(-) create mode 100644 app/common/uiLibrary/Layouters/fullPage.tsx diff --git a/app/(account)/login/page.tsx b/app/(account)/login/page.tsx index d6eb1c1..9204921 100644 --- a/app/(account)/login/page.tsx +++ b/app/(account)/login/page.tsx @@ -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); @@ -41,7 +42,7 @@ const LoginPage = () => { const loginForm = () => { return ( -
+
{isLoading &&
@@ -90,14 +91,14 @@ const LoginPage = () => { [ {link: '/register', linkText: 'Don\'t have an account?'}, {link: '/reset-password', linkText: 'Forgot your password?'}, - {link: '/resend-confirmation-email', linkText: 'Haven\'t received confirmation email yet?'}, + {link: '/resend-confirm-email', linkText: 'Haven\'t received confirmation email yet?'}, ] } />
-
+
) } diff --git a/app/(account)/reset-password/[token]/page.tsx b/app/(account)/reset-password/[token]/page.tsx index b3e4e99..b52e520 100644 --- a/app/(account)/reset-password/[token]/page.tsx +++ b/app/(account)/reset-password/[token]/page.tsx @@ -9,6 +9,7 @@ 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 }>(); @@ -79,13 +80,13 @@ const ResetPasswordPageStep2 = () => { } return ( -
+
{state.success ? successMessage() : resetPasswordForm()}
-
+ ) } diff --git a/app/common/uiLibrary/Layouters/fullPage.tsx b/app/common/uiLibrary/Layouters/fullPage.tsx new file mode 100644 index 0000000..ef34bfa --- /dev/null +++ b/app/common/uiLibrary/Layouters/fullPage.tsx @@ -0,0 +1,12 @@ +import layoutChildren from "../../../../types/layoutChildren"; + +// makes the view occupy the full page, excluding the header. +const FullPage = (props: layoutChildren) => { + return ( +
+ {props.children} +
+ ) +} + +export default FullPage; \ No newline at end of file From be84832f1bf28afbda3514f2b946b55750683395 Mon Sep 17 00:00:00 2001 From: Gilles <43683714+corp-0@users.noreply.github.com> Date: Sat, 9 Mar 2024 17:12:28 -0300 Subject: [PATCH 13/16] feat: add page to resend confirmation email --- app/(account)/resend-confirm-email/actions.ts | 53 ++++++++++++++ app/(account)/resend-confirm-email/page.tsx | 70 +++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 app/(account)/resend-confirm-email/actions.ts create mode 100644 app/(account)/resend-confirm-email/page.tsx diff --git a/app/(account)/resend-confirm-email/actions.ts b/app/(account)/resend-confirm-email/actions.ts new file mode 100644 index 0000000..5ff606f --- /dev/null +++ b/app/(account)/resend-confirm-email/actions.ts @@ -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 => { + 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}; + } + + throw new Error('Unknown error'); + } + + } catch (e) { + return {success: false, error: {error: 'An unexpected error happened', status: 500}}; + } + + return {success: true} +} \ No newline at end of file diff --git a/app/(account)/resend-confirm-email/page.tsx b/app/(account)/resend-confirm-email/page.tsx new file mode 100644 index 0000000..156ca55 --- /dev/null +++ b/app/(account)/resend-confirm-email/page.tsx @@ -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()} + + + + + ) + } + const errorMessage = () => { + return ( +
+

Oops!

+

There was an unexpected error while trying to resend the confirmation email.

+

Please try again later or contact us.

+
+ ) + } + + const successMessage = () => ( +
+

Success!

+

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.

+

Please check your inbox for the confirmation email. If you don't receive it within a few minutes, check + your spam or junk folder. For further assistance, don't hesitate to contact us.

+
+ ); + + return ( + +
+ + {state.success ? successMessage() : resendForm()} + +
+ +
+ ) +}; + +export default ResendConfirmationMail; \ No newline at end of file From 4ae8d4250c3e23a8baedff4477804306075ce2a9 Mon Sep 17 00:00:00 2001 From: Gilles <43683714+corp-0@users.noreply.github.com> Date: Sat, 9 Mar 2024 17:16:43 -0300 Subject: [PATCH 14/16] chore: fuck you linter --- app/(account)/resend-confirm-email/page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/(account)/resend-confirm-email/page.tsx b/app/(account)/resend-confirm-email/page.tsx index 156ca55..86f6777 100644 --- a/app/(account)/resend-confirm-email/page.tsx +++ b/app/(account)/resend-confirm-email/page.tsx @@ -50,8 +50,8 @@ const ResendConfirmationMail = () => {

Success!

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.

-

Please check your inbox for the confirmation email. If you don't receive it within a few minutes, check - your spam or junk folder. For further assistance, don't hesitate to contact us.

+

Please check your inbox for the confirmation email. If you don't receive it within a few minutes, check + your spam or junk folder. For further assistance, don't hesitate to contact us.

); From db12e840d06ffa65c6d416b45f1a850221e9f32f Mon Sep 17 00:00:00 2001 From: Gilles <43683714+corp-0@users.noreply.github.com> Date: Sat, 9 Mar 2024 17:19:02 -0300 Subject: [PATCH 15/16] chore: remove unused import --- app/(account)/confirm-email/[token]/actions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/(account)/confirm-email/[token]/actions.ts b/app/(account)/confirm-email/[token]/actions.ts index deedb65..d555853 100644 --- a/app/(account)/confirm-email/[token]/actions.ts +++ b/app/(account)/confirm-email/[token]/actions.ts @@ -1,6 +1,6 @@ "use server" -import {isFieldError, isGeneralError} from "../../../../lib/auth/guards"; +import {isFieldError} from "../../../../lib/auth/guards"; export const postMailConfirmationToken = async (token: string): Promise => { try { From c86f15398ce1a6e2d9fc958d2ffcf7636b8c2561 Mon Sep 17 00:00:00 2001 From: Gilles <43683714+corp-0@users.noreply.github.com> Date: Sat, 9 Mar 2024 17:19:21 -0300 Subject: [PATCH 16/16] chore: remove unused import --- app/(account)/resend-confirm-email/actions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/(account)/resend-confirm-email/actions.ts b/app/(account)/resend-confirm-email/actions.ts index 5ff606f..b27851b 100644 --- a/app/(account)/resend-confirm-email/actions.ts +++ b/app/(account)/resend-confirm-email/actions.ts @@ -42,7 +42,7 @@ export const postResendConfirmationMail = async (_: ResendConfirmationMailReques return {success: false, error: error}; } - throw new Error('Unknown error'); + return {success: false, error: {error: 'An unexpected error happened', status: 500}}; } } catch (e) {