From 2c9da9b45fbb0a9f2637befe7c0b4d6ab6e9b1ac Mon Sep 17 00:00:00 2001 From: Hugo Marques Date: Mon, 10 Jun 2024 18:49:25 +0100 Subject: [PATCH 1/4] Verify Email Url --- .env.dist | 1 + config.ts | 7 +- src/api/user.ts | 10 + src/views/LearnAndEarn/Metrics.tsx | 81 ++++---- src/views/LearnAndEarn/ValidateEmail.tsx | 250 +++++++++++++++++++++++ src/views/LearnAndEarn/index.tsx | 8 +- 6 files changed, 314 insertions(+), 43 deletions(-) create mode 100644 src/views/LearnAndEarn/ValidateEmail.tsx diff --git a/.env.dist b/.env.dist index 3e909c36..a4463820 100644 --- a/.env.dist +++ b/.env.dist @@ -29,3 +29,4 @@ NEXT_PUBLIC_FB_VAPID_KEY= NEXT_PUBLIC_FB_MEASUREMENT_ID= NEXT_PUBLIC_MICROCREDIT_MANAGER_ADMIN= NEXT_PUBLIC_CLIENT_ID= +NEXT_PUBLIC_VERIFY_EMAIL_URL= \ No newline at end of file diff --git a/config.ts b/config.ts index 3ded3c54..b2da2071 100644 --- a/config.ts +++ b/config.ts @@ -136,7 +136,12 @@ const config = { /* * ClientId for L&E */ - clientId: process.env.NEXT_PUBLIC_CLIENT_ID + clientId: process.env.NEXT_PUBLIC_CLIENT_ID, + + /* + * Email verification URL + */ + verifyEmailUrl: process.env.NEXT_PUBLIC_VERIFY_EMAIL_URL }; export default config; diff --git a/src/api/user.ts b/src/api/user.ts index 51c76f87..03467e21 100644 --- a/src/api/user.ts +++ b/src/api/user.ts @@ -168,6 +168,15 @@ export const userApi = emptySplitApi.injectEndpoints({ }), transformResponse: (response: { data: User }) => response.data }), + // Send Email + sendVerifyEmail: builder.mutation({ + query: (body) => ({ + body, + method: 'POST', + url: '/users/request-verify' + }), + transformResponse: (response: { data: any }) => response.data + }), // Verify Email verifyEmail: builder.mutation({ query: (body) => ({ @@ -193,5 +202,6 @@ export const { useUpdateNotificationsMutation, useUpdateUserMutation, useGetPreSignedMutation, + useSendVerifyEmailMutation, useVerifyEmailMutation } = userApi; diff --git a/src/views/LearnAndEarn/Metrics.tsx b/src/views/LearnAndEarn/Metrics.tsx index 0d804738..e9cc70a7 100644 --- a/src/views/LearnAndEarn/Metrics.tsx +++ b/src/views/LearnAndEarn/Metrics.tsx @@ -20,6 +20,7 @@ import config from '../../../config'; import processTransactionError from '../../utils/processTransactionError'; import styled from 'styled-components'; import useTranslations from '../../libs/Prismic/hooks/useTranslations'; +import ValidateEmail from './ValidateEmail'; const CardsGrid = styled(Grid)` flex-wrap: wrap; @@ -134,47 +135,53 @@ const Metrics = (props: any) => { ))} - - - - - - - - + - {view?.data['needHelp']} - - - - + + + + + {view?.data['needHelp']} + + + + + ) : ( + + )} ); }; diff --git a/src/views/LearnAndEarn/ValidateEmail.tsx b/src/views/LearnAndEarn/ValidateEmail.tsx new file mode 100644 index 00000000..1670c10f --- /dev/null +++ b/src/views/LearnAndEarn/ValidateEmail.tsx @@ -0,0 +1,250 @@ +import { + Box, + Button, + Card, + CircledIcon, + Icon, + Input, + Text, + TextLink, + colors, + toast +} from '@impact-market/ui'; +import { useEffect, useState } from 'react'; +import styled from 'styled-components'; +import RichText from '../../libs/Prismic/components/RichText'; +import processTransactionError from '../../utils/processTransactionError'; +import { selectCurrentUser } from 'src/state/slices/auth'; +import { useSelector } from 'react-redux'; +import router from 'next/router'; +import { useSendVerifyEmailMutation } from 'src/api/user'; +import config from 'config'; + +const ConsentWrapper = styled(Box)` + display: flex; + flex-direction: row; + margin-top: 1rem; + gap: 0.5rem; + align-items: center; +`; + +const CheckBox = styled(Box)` + background-color: ${colors.p100}; + border-radius: 5px; + height: 20px; + width: 20px; +`; + +const IconStyled = styled(Icon)` + color: ${colors.p500}; + height: 100%; + width: 30px; + margin: 0 auto; +`; + +const ButtonStyled = styled(Button)` + margin-top: 1rem; + border: none; +`; + +const ValidateEmail = () => { + const auth = useSelector(selectCurrentUser); + const [sendVerifyEmail] = useSendVerifyEmailMutation(); + const [openForm, setOpenForm] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [consent, setConsent] = useState(false); + const [email, setEmail] = useState(''); + const [isEmailValid, setIsEmailValid] = useState(true); + const [success, setSuccess] = useState(false); + + useEffect(() => { + if (auth?.user?.email) { + setEmail(auth?.user?.email); + } + }, [auth]); + + const verifyEmail = async () => { + if (!validateEmail(email)) { + setIsEmailValid(false); + return; + } + + try { + await sendVerifyEmail({ + email: email, + url: config.verifyEmailUrl + }).unwrap(); + + setIsLoading(false); + setOpenForm(false); + setSuccess(true); + } catch (error) { + toast.error(`An error has occurred. Please try again later.`); + processTransactionError(error, 'verify_email'); + console.log(error); + } + }; + + const validateEmail = (email: string) => { + const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return re.test(email); + }; + + const handleEmailChange = (e: React.ChangeEvent) => { + setEmail(e.target.value); + setIsEmailValid(true); // Reset email validation state when user types + }; + + return ( + + + {success ? ( + + ) : ( + + )} + + + {openForm ? ( + <> + + + {!isEmailValid && ( + + Please enter a valid address + + )} + + + + setConsent(!consent)} + padding={0.3} + flex + > + {consent && ( + + )} + + + + + + Confirm email + + + ) : ( + <> + {!success && ( + setOpenForm(true)} + isLoading={isLoading} + success + > + Continue + + )} + + {success && ( + <> + + We've sent you an email to {email}. + + { + setSuccess(false); + setOpenForm(true); + }} + style={{ + marginTop: '1rem', + display: 'flex', + justifyContent: 'center' + }} + > + + Change email + + + + )} + + )} + { + router.reload(); + }} + style={{ + marginTop: '1rem', + display: 'flex', + justifyContent: 'center' + }} + > + Refresh page + + + + ); +}; + +export default ValidateEmail; diff --git a/src/views/LearnAndEarn/index.tsx b/src/views/LearnAndEarn/index.tsx index 4750383e..a0f9a4e9 100644 --- a/src/views/LearnAndEarn/index.tsx +++ b/src/views/LearnAndEarn/index.tsx @@ -1,5 +1,4 @@ import { - Alert, Box, Display, DropdownMenu, @@ -40,8 +39,7 @@ const LearnAndEarn = (props: any) => { headingTitle: heading, headingContent: content, claimAvailable = null, - claimDisabled = null, - onlyBeneficiariesTooltip + claimDisabled = null } = view.data; const { levels, categories } = prismic; @@ -240,11 +238,11 @@ const LearnAndEarn = (props: any) => { - + /> */} Date: Mon, 10 Jun 2024 18:50:35 +0100 Subject: [PATCH 2/4] Verify Email Url --- src/views/LearnAndEarn/Metrics.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/LearnAndEarn/Metrics.tsx b/src/views/LearnAndEarn/Metrics.tsx index e9cc70a7..959ccc3f 100644 --- a/src/views/LearnAndEarn/Metrics.tsx +++ b/src/views/LearnAndEarn/Metrics.tsx @@ -168,7 +168,7 @@ const Metrics = (props: any) => { Date: Mon, 10 Jun 2024 18:52:58 +0100 Subject: [PATCH 3/4] Verify Email Url --- src/views/LearnAndEarn/ValidateEmail.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/views/LearnAndEarn/ValidateEmail.tsx b/src/views/LearnAndEarn/ValidateEmail.tsx index 1670c10f..1f486f52 100644 --- a/src/views/LearnAndEarn/ValidateEmail.tsx +++ b/src/views/LearnAndEarn/ValidateEmail.tsx @@ -1,3 +1,4 @@ +// @ts-nocheck import { Box, Button, From a3d363a27afa8d042f9ffb11e8a0ed09b4ebca2a Mon Sep 17 00:00:00 2001 From: Hugo Marques Date: Mon, 10 Jun 2024 19:00:41 +0100 Subject: [PATCH 4/4] Verify Email Url --- config.ts | 2 +- src/api/user.ts | 18 +++++++-------- src/views/LearnAndEarn/Metrics.tsx | 2 +- src/views/LearnAndEarn/ValidateEmail.tsx | 28 +++++++++++++----------- 4 files changed, 26 insertions(+), 24 deletions(-) diff --git a/config.ts b/config.ts index b2da2071..011ff3df 100644 --- a/config.ts +++ b/config.ts @@ -139,7 +139,7 @@ const config = { clientId: process.env.NEXT_PUBLIC_CLIENT_ID, /* - * Email verification URL + * Email Verification URL */ verifyEmailUrl: process.env.NEXT_PUBLIC_VERIFY_EMAIL_URL }; diff --git a/src/api/user.ts b/src/api/user.ts index 03467e21..9bcdeea2 100644 --- a/src/api/user.ts +++ b/src/api/user.ts @@ -151,6 +151,15 @@ export const userApi = emptySplitApi.injectEndpoints({ transformResponse: (response: { data: RecoverUser }) => response.data }), + // Send Email + sendVerifyEmail: builder.mutation({ + query: (body) => ({ + body, + method: 'POST', + url: '/users/request-verify' + }), + transformResponse: (response: { data: any }) => response.data + }), // Mark notifications as read updateNotifications: builder.mutation({ query: ({ body }) => ({ @@ -168,15 +177,6 @@ export const userApi = emptySplitApi.injectEndpoints({ }), transformResponse: (response: { data: User }) => response.data }), - // Send Email - sendVerifyEmail: builder.mutation({ - query: (body) => ({ - body, - method: 'POST', - url: '/users/request-verify' - }), - transformResponse: (response: { data: any }) => response.data - }), // Verify Email verifyEmail: builder.mutation({ query: (body) => ({ diff --git a/src/views/LearnAndEarn/Metrics.tsx b/src/views/LearnAndEarn/Metrics.tsx index 959ccc3f..2e0e00a9 100644 --- a/src/views/LearnAndEarn/Metrics.tsx +++ b/src/views/LearnAndEarn/Metrics.tsx @@ -16,11 +16,11 @@ import { useState } from 'react'; import Message from '../../libs/Prismic/components/Message'; import RichText from '../../libs/Prismic/components/RichText'; import String from '../../libs/Prismic/components/String'; +import ValidateEmail from './ValidateEmail'; import config from '../../../config'; import processTransactionError from '../../utils/processTransactionError'; import styled from 'styled-components'; import useTranslations from '../../libs/Prismic/hooks/useTranslations'; -import ValidateEmail from './ValidateEmail'; const CardsGrid = styled(Grid)` flex-wrap: wrap; diff --git a/src/views/LearnAndEarn/ValidateEmail.tsx b/src/views/LearnAndEarn/ValidateEmail.tsx index 1f486f52..33fd0e92 100644 --- a/src/views/LearnAndEarn/ValidateEmail.tsx +++ b/src/views/LearnAndEarn/ValidateEmail.tsx @@ -11,15 +11,15 @@ import { colors, toast } from '@impact-market/ui'; -import { useEffect, useState } from 'react'; -import styled from 'styled-components'; -import RichText from '../../libs/Prismic/components/RichText'; -import processTransactionError from '../../utils/processTransactionError'; import { selectCurrentUser } from 'src/state/slices/auth'; +import { useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; -import router from 'next/router'; import { useSendVerifyEmailMutation } from 'src/api/user'; +import RichText from '../../libs/Prismic/components/RichText'; import config from 'config'; +import processTransactionError from '../../utils/processTransactionError'; +import router from 'next/router'; +import styled from 'styled-components'; const ConsentWrapper = styled(Box)` display: flex; @@ -67,12 +67,13 @@ const ValidateEmail = () => { const verifyEmail = async () => { if (!validateEmail(email)) { setIsEmailValid(false); + return; } try { await sendVerifyEmail({ - email: email, + email, url: config.verifyEmailUrl }).unwrap(); @@ -88,12 +89,13 @@ const ValidateEmail = () => { const validateEmail = (email: string) => { const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return re.test(email); }; const handleEmailChange = (e: React.ChangeEvent) => { setEmail(e.target.value); - setIsEmailValid(true); // Reset email validation state when user types + setIsEmailValid(true); }; return ( @@ -119,8 +121,8 @@ const ValidateEmail = () => { { setOpenForm(true); }} style={{ - marginTop: '1rem', display: 'flex', - justifyContent: 'center' + justifyContent: 'center', + marginTop: '1rem' }} > @@ -236,9 +238,9 @@ const ValidateEmail = () => { router.reload(); }} style={{ - marginTop: '1rem', display: 'flex', - justifyContent: 'center' + justifyContent: 'center', + marginTop: '1rem' }} > Refresh page