From 02cf41f28538d63175d25725f5a5c7eee7cbf593 Mon Sep 17 00:00:00 2001 From: sajald77 Date: Thu, 27 Jul 2023 17:04:31 -0400 Subject: [PATCH 01/15] fix: add update email feature --- src/forms/components/TextField.tsx | 10 +- src/pages/otp/VerifyYourEmail.tsx | 20 +- src/pages/otp/VerifyYourEmailContent.tsx | 12 +- .../otp/components/ReceiveOneTimePassword.tsx | 14 +- .../otp/components/VerifyOneTimePassword.tsx | 2 +- .../components/ProfileSettingsModal.tsx | 225 ++++++++++++++++++ src/pages/profile/components/index.ts | 2 + src/pages/profile/views/AccountInfo.tsx | 25 +- src/translations/English.json | 8 +- src/utils/validations/index.ts | 1 + src/utils/validations/yup.ts | 8 + 11 files changed, 293 insertions(+), 34 deletions(-) create mode 100644 src/pages/profile/components/ProfileSettingsModal.tsx create mode 100644 src/utils/validations/yup.ts diff --git a/src/forms/components/TextField.tsx b/src/forms/components/TextField.tsx index 68154cdeb..5042759fd 100644 --- a/src/forms/components/TextField.tsx +++ b/src/forms/components/TextField.tsx @@ -29,10 +29,12 @@ export const TextField = ({ - {label || name} - {required ? '*' : ''} - + label && ( + <> + {label} + {required ? '*' : ''} + + ) } subtitle={caption} error={ diff --git a/src/pages/otp/VerifyYourEmail.tsx b/src/pages/otp/VerifyYourEmail.tsx index 10a9adbaa..ffce4cb5e 100644 --- a/src/pages/otp/VerifyYourEmail.tsx +++ b/src/pages/otp/VerifyYourEmail.tsx @@ -3,20 +3,20 @@ import { useTranslation } from 'react-i18next' import { CustomModalProps, Modal } from '../../components/layouts' import { MfaAction, OtpResponseFragment } from '../../types' -import { VerifyYourEmailContent } from './VerifyYourEmailContent' +import { + VerifyYourEmailContent, + VerifyYourEmailContentProps, +} from './VerifyYourEmailContent' -interface VerifyYourEmailProps extends Omit { - action?: MfaAction - handleVerify?: ( - otpCode: number, - optData: OtpResponseFragment, - email?: string, - ) => void -} +interface VerifyYourEmailProps + extends VerifyYourEmailContentProps, + Omit {} export const VerifyYourEmail = ({ action, handleVerify, + otpSent, + otpData, ...rest }: VerifyYourEmailProps) => { const { t } = useTranslation() @@ -40,6 +40,8 @@ export const VerifyYourEmail = ({ diff --git a/src/pages/otp/VerifyYourEmailContent.tsx b/src/pages/otp/VerifyYourEmailContent.tsx index ba198a413..dd4707f1f 100644 --- a/src/pages/otp/VerifyYourEmailContent.tsx +++ b/src/pages/otp/VerifyYourEmailContent.tsx @@ -13,11 +13,13 @@ import { import { useNotification } from '../../utils' import { ReceiveOneTimePassword, VerifyOneTimePassword } from './components' -interface VerifyYourEmailContentProps { +export interface VerifyYourEmailContentProps { action: MfaAction + otpSent?: boolean + otpData?: OtpResponseFragment handleVerify?: ( otpCode: number, - optData: OtpResponseFragment, + otpData: OtpResponseFragment, email?: string, ) => void } @@ -25,12 +27,14 @@ interface VerifyYourEmailContentProps { export const VerifyYourEmailContent = ({ action, handleVerify, + otpSent, + otpData: otp, }: VerifyYourEmailContentProps) => { const { t } = useTranslation() const { toast } = useNotification() - const [sentOtp, setSentOtp] = useState(false) - const [otpData, setOtpData] = useState() + const [sentOtp, setSentOtp] = useState(otpSent || false) + const [otpData, setOtpData] = useState(otp) const [inputEmail, setInputEmail] = useState('') const [sendOtpByEmail] = useSendOtpByEmailMutation({ diff --git a/src/pages/otp/components/ReceiveOneTimePassword.tsx b/src/pages/otp/components/ReceiveOneTimePassword.tsx index a9ce38538..8e8323b69 100644 --- a/src/pages/otp/components/ReceiveOneTimePassword.tsx +++ b/src/pages/otp/components/ReceiveOneTimePassword.tsx @@ -3,19 +3,11 @@ import { yupResolver } from '@hookform/resolvers/yup' import { Dispatch, SetStateAction } from 'react' import { useForm } from 'react-hook-form' import { useTranslation } from 'react-i18next' -import * as yup from 'yup' import { useAuthContext } from '../../../context' import { TextField } from '../../../forms/components/TextField' import { MfaAction, useUserEmailUpdateMutation } from '../../../types' -import { useNotification } from '../../../utils' - -const schema = yup.object({ - email: yup - .string() - .required('Email is a required field') - .email('Please enter a valid Email address'), -}) +import { emailValidationSchema, useNotification } from '../../../utils' interface ReceiveOneTimePasswordProps { handleSendOtpByEmail(email: string): void @@ -32,10 +24,10 @@ export const ReceiveOneTimePassword = ({ const { toast } = useNotification() const { user, setUser, isUserAProjectCreator } = useAuthContext() const canEditEmail = - !user.email || !isUserAProjectCreator || !user.isEmailVerified + (!user.email || !isUserAProjectCreator) && !user.isEmailVerified const form = useForm<{ email: string }>({ - resolver: canEditEmail ? yupResolver(schema) : undefined, + resolver: canEditEmail ? yupResolver(emailValidationSchema) : undefined, values: user.email ? { email: user.email, diff --git a/src/pages/otp/components/VerifyOneTimePassword.tsx b/src/pages/otp/components/VerifyOneTimePassword.tsx index 2a08780de..9aa8578b1 100644 --- a/src/pages/otp/components/VerifyOneTimePassword.tsx +++ b/src/pages/otp/components/VerifyOneTimePassword.tsx @@ -20,7 +20,7 @@ interface VerifyOneTimePasswordProps { handleSendOtpByEmail(email: string): void handleVerify?: ( otpCode: number, - optData: OtpResponseFragment, + otpData: OtpResponseFragment, email?: string, ) => void inputEmail: string diff --git a/src/pages/profile/components/ProfileSettingsModal.tsx b/src/pages/profile/components/ProfileSettingsModal.tsx new file mode 100644 index 000000000..a9714b98b --- /dev/null +++ b/src/pages/profile/components/ProfileSettingsModal.tsx @@ -0,0 +1,225 @@ +import { CheckCircleIcon, WarningIcon } from '@chakra-ui/icons' +import { + Box, + Button, + InputGroup, + InputRightAddon, + InputRightElement, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalHeader, + ModalOverlay, + Text, + useDisclosure, + VStack, +} from '@chakra-ui/react' +import { yupResolver } from '@hookform/resolvers/yup' +import { useState } from 'react' +import { useForm } from 'react-hook-form' +import { useTranslation } from 'react-i18next' + +import { Body1, Body2 } from '../../../components/typography' +import { useAuthContext } from '../../../context' +import { TextField } from '../../../forms/components/TextField' +import { lightModeColors } from '../../../styles' +import { + MfaAction, + OtpResponseFragment, + useSendOtpByEmailMutation, + useUserEmailUpdateMutation, +} from '../../../types' +import { emailValidationSchema, useNotification } from '../../../utils' +import { VerifyYourEmail } from '../../otp' +import { EditProfileModalProps } from '../hooks/useEditProfileModal' + +export const ProfileSettingsModal = ({ + isOpen, + onClose, + props, +}: EditProfileModalProps) => { + const { t } = useTranslation() + const { toast } = useNotification() + const { user, setUser } = useAuthContext() + + const [otpSent, setSentOtp] = useState(false) + const [otpData, setOtpData] = useState() + + const { + isOpen: isverifyEmailModalOpen, + onOpen: onVerifyEmailModalOpen, + onClose: onVerifyEmailModalClose, + } = useDisclosure() + const [currentMfaAction, setCurrentMfaAction] = useState( + MfaAction.UserEmailUpdate, + ) + + const { formState, control, handleSubmit } = useForm<{ email: string }>({ + resolver: yupResolver(emailValidationSchema), + values: user.email + ? { + email: user.email, + } + : undefined, + }) + + const [sendOtpByEmail] = useSendOtpByEmailMutation({ + onError() { + toast({ + status: 'error', + title: 'Failed to generate otp.', + description: 'Please try again', + }) + }, + onCompleted(data) { + const otp = data.sendOTPByEmail + if (otp) { + setSentOtp(true) + setOtpData(otp) + setCurrentMfaAction(MfaAction.UserEmailVerification) + onVerifyEmailModalOpen() + } + }, + }) + + const [updateUserEmail] = useUserEmailUpdateMutation({ + onError() { + toast({ + status: 'error', + title: 'Failed to update email.', + description: 'Please try again', + }) + }, + onCompleted(data) { + const emailUpdateUser = data.userEmailUpdate + if (emailUpdateUser && emailUpdateUser.email) { + setUser((current) => ({ ...current, ...emailUpdateUser })) + sendOtpByEmail({ + variables: { + input: { + email: user.email, + action: MfaAction.UserEmailVerification, + }, + }, + }) + } + }, + }) + + const onSubmit = async ({ email }: { email: string }) => { + if (!user.email || !user.isEmailVerified) { + updateUserEmail({ + variables: { + input: { + email, + }, + }, + }) + } else { + setCurrentMfaAction(MfaAction.UserEmailUpdate) + onVerifyEmailModalOpen() + } + } + + const handleModalClosed = () => { + setOtpData(undefined) + setSentOtp(false) + onVerifyEmailModalClose() + } + + const handleVerifyEmailClick = () => { + if (user.email) { + sendOtpByEmail({ + variables: { + input: { + email: user.email, + action: MfaAction.UserEmailVerification, + }, + }, + }) + } + } + + const isSavedEmailUnverified = + !formState.isDirty && user.email && !user.isEmailVerified + + return ( + <> + + + + + {t('Settings')} + + +
{ + e.stopPropagation() + e.preventDefault() + handleSubmit(onSubmit)(e) + }} + > + + + {t('Email')} + + {t( + 'This is your account recovery email. Verify this email to edit your project wallet information. You will also receive important project notifications to this email.', + )} + + + + + + {!formState.isDirty && user.isEmailVerified && ( + + )} + {isSavedEmailUnverified && ( + + )} + + + {isSavedEmailUnverified && ( + + {t('This email has not been verified')} + + )} + + + {!user.isEmailVerified && ( + + )} + + + + +
+
+
+
+
+ + + ) +} diff --git a/src/pages/profile/components/index.ts b/src/pages/profile/components/index.ts index 8aeae86de..8a7afe591 100644 --- a/src/pages/profile/components/index.ts +++ b/src/pages/profile/components/index.ts @@ -1,4 +1,6 @@ export * from './CreateAProjectButton' +export * from './EditProfileModal' export * from './ExternalAccountDisplay' export * from './ExternalAccountLinkItem' +export * from './ProfileSettingsModal' export * from './ProfileTabLayout' diff --git a/src/pages/profile/views/AccountInfo.tsx b/src/pages/profile/views/AccountInfo.tsx index 5dbe156ea..a19fc403c 100644 --- a/src/pages/profile/views/AccountInfo.tsx +++ b/src/pages/profile/views/AccountInfo.tsx @@ -1,11 +1,14 @@ -import { Avatar, Box, Button, SkeletonCircle, VStack } from '@chakra-ui/react' +import { Avatar, Button, SkeletonCircle, Stack, VStack } from '@chakra-ui/react' import { useTranslation } from 'react-i18next' +import { BsGearFill } from 'react-icons/bs' +import { MdEdit } from 'react-icons/md' import { CardLayout, SkeletonLayout } from '../../../components/layouts' import { Body1, H1 } from '../../../components/typography' +import { useModal } from '../../../hooks/useModal' import { ConnectAccounts, ExternalAccountType } from '../../auth' import { LightningAddress } from '../../projectView/projectMainBody/components' -import { ExternalAccountDisplay } from '../components' +import { ExternalAccountDisplay, ProfileSettingsModal } from '../components' import { EditProfileModal } from '../components/EditProfileModal' import { useEditProfileModal } from '../hooks/useEditProfileModal' import { UserProfileState } from '../type' @@ -23,6 +26,7 @@ export const AccountInfo = ({ }: AccountInfoProps) => { const { t } = useTranslation() const modalProps = useEditProfileModal() + const settingModalProps = useModal() if (isLoading) { return @@ -105,18 +109,31 @@ export const AccountInfo = ({ {isEdit && userProfile && ( <> - + - + + {modalProps.isOpen && } + {settingModalProps.isOpen && ( + + )} )} diff --git a/src/translations/English.json b/src/translations/English.json index b9ddccf52..937ce758b 100644 --- a/src/translations/English.json +++ b/src/translations/English.json @@ -627,6 +627,12 @@ "Wallet updated successfully!":"Wallet updated successfully!", "Failed to update wallet.":"Failed to update wallet.", "Failed to login with email":"Failed to login with email", - "You can update your wallet securely by using the One Time Password sent to your verified email.":"You can update your wallet securely by using the One Time Password sent to your verified email." + "You can update your wallet securely by using the One Time Password sent to your verified email.":"You can update your wallet securely by using the One Time Password sent to your verified email.", + "This email is where you will receive important project notifications":"This email is where you will receive important project notifications", + "Verify email":"Verify email", + "This email has not been verified":"This email has not been verified", + + + } diff --git a/src/utils/validations/index.ts b/src/utils/validations/index.ts index b54510c00..5307381e6 100644 --- a/src/utils/validations/index.ts +++ b/src/utils/validations/index.ts @@ -12,3 +12,4 @@ export * from './project' export * from './regex' export * from './testImage' export * from './wallet' +export * from './yup' diff --git a/src/utils/validations/yup.ts b/src/utils/validations/yup.ts new file mode 100644 index 000000000..51dbf942e --- /dev/null +++ b/src/utils/validations/yup.ts @@ -0,0 +1,8 @@ +import * as yup from 'yup' + +export const emailValidationSchema = yup.object({ + email: yup + .string() + .required('Email is a required field') + .email('Please enter a valid Email address'), +}) From a0c423e487b965a085d60e228699604a35f293fd Mon Sep 17 00:00:00 2001 From: sajald77 Date: Thu, 27 Jul 2023 19:31:03 -0400 Subject: [PATCH 02/15] fix: remove verify email prompt --- src/App.tsx | 2 - src/pages/otp/DefaultEmailVerify.tsx | 41 ------------------- src/pages/otp/VerifyYourEmailContent.tsx | 6 +++ .../otp/components/ReceiveOneTimePassword.tsx | 5 +-- src/pages/otp/index.ts | 1 - .../components/ProfileSettingsModal.tsx | 2 - .../projectCreate/ProjectCreateStart.tsx | 34 ++------------- src/translations/English.json | 5 +-- 8 files changed, 12 insertions(+), 84 deletions(-) delete mode 100644 src/pages/otp/DefaultEmailVerify.tsx diff --git a/src/App.tsx b/src/App.tsx index f64594174..593d99644 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,7 +7,6 @@ import { client } from './config' import { Head } from './config/Head' import { AuthProvider, ChakraThemeProvider, NavProvider } from './context' import { BtcProvider } from './context/btc' -import { DefaultEmailVerify } from './pages/otp' export const App = () => ( @@ -19,7 +18,6 @@ export const App = () => ( - diff --git a/src/pages/otp/DefaultEmailVerify.tsx b/src/pages/otp/DefaultEmailVerify.tsx deleted file mode 100644 index b9c5eeb8d..000000000 --- a/src/pages/otp/DefaultEmailVerify.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { useDisclosure } from '@chakra-ui/react' -import { useEffect } from 'react' - -import { useAuthContext } from '../../context' -import { MfaAction } from '../../types' -import { VerifyYourEmail } from './VerifyYourEmail' - -export const EmailVerificationTriggered = 'EmailVerificationTriggered' - -export const DefaultEmailVerify = () => { - const { isOpen, onOpen, onClose } = useDisclosure() - - const { user, isUserAProjectCreator } = useAuthContext() - - useEffect(() => { - if ( - user.id && - (!user.email || !user.isEmailVerified) && - (isUserAProjectCreator || - !(localStorage.getItem(EmailVerificationTriggered) === 'true')) - ) { - onOpen() - } else { - onClose() - } - }, [user, isUserAProjectCreator, onClose, onOpen]) - - const handleCloseForNonCreators = () => { - localStorage.setItem(EmailVerificationTriggered, 'true') - onClose() - } - - return ( - {} : handleCloseForNonCreators} - noClose={isUserAProjectCreator} - action={MfaAction.UserEmailVerification} - /> - ) -} diff --git a/src/pages/otp/VerifyYourEmailContent.tsx b/src/pages/otp/VerifyYourEmailContent.tsx index dd4707f1f..6d2725eeb 100644 --- a/src/pages/otp/VerifyYourEmailContent.tsx +++ b/src/pages/otp/VerifyYourEmailContent.tsx @@ -72,6 +72,12 @@ export const VerifyYourEmailContent = ({ ) } + if (action === MfaAction.UserEmailUpdate) { + return t( + 'You can update your email securely by using One Time Password sent to your last verfied email.', + ) + } + return t( 'Backup your Geyser account and project with your email. This will ensure that you can always access Geyser (in case of social media censorship) and can securely update your project information.', ) diff --git a/src/pages/otp/components/ReceiveOneTimePassword.tsx b/src/pages/otp/components/ReceiveOneTimePassword.tsx index 8e8323b69..0e5aadda0 100644 --- a/src/pages/otp/components/ReceiveOneTimePassword.tsx +++ b/src/pages/otp/components/ReceiveOneTimePassword.tsx @@ -22,9 +22,8 @@ export const ReceiveOneTimePassword = ({ }: ReceiveOneTimePasswordProps) => { const { t } = useTranslation() const { toast } = useNotification() - const { user, setUser, isUserAProjectCreator } = useAuthContext() - const canEditEmail = - (!user.email || !isUserAProjectCreator) && !user.isEmailVerified + const { user, setUser } = useAuthContext() + const canEditEmail = !user.email || !user.isEmailVerified const form = useForm<{ email: string }>({ resolver: canEditEmail ? yupResolver(emailValidationSchema) : undefined, diff --git a/src/pages/otp/index.ts b/src/pages/otp/index.ts index c8e4a9894..63c0f74f7 100644 --- a/src/pages/otp/index.ts +++ b/src/pages/otp/index.ts @@ -1,2 +1 @@ -export * from './DefaultEmailVerify' export * from './VerifyYourEmail' diff --git a/src/pages/profile/components/ProfileSettingsModal.tsx b/src/pages/profile/components/ProfileSettingsModal.tsx index a9714b98b..681814369 100644 --- a/src/pages/profile/components/ProfileSettingsModal.tsx +++ b/src/pages/profile/components/ProfileSettingsModal.tsx @@ -3,7 +3,6 @@ import { Box, Button, InputGroup, - InputRightAddon, InputRightElement, Modal, ModalBody, @@ -23,7 +22,6 @@ import { useTranslation } from 'react-i18next' import { Body1, Body2 } from '../../../components/typography' import { useAuthContext } from '../../../context' import { TextField } from '../../../forms/components/TextField' -import { lightModeColors } from '../../../styles' import { MfaAction, OtpResponseFragment, diff --git a/src/pages/projectCreate/ProjectCreateStart.tsx b/src/pages/projectCreate/ProjectCreateStart.tsx index ff2202676..c3425475d 100644 --- a/src/pages/projectCreate/ProjectCreateStart.tsx +++ b/src/pages/projectCreate/ProjectCreateStart.tsx @@ -1,13 +1,5 @@ -import { - Box, - Button, - Image, - ImageProps, - Text, - useDisclosure, - VStack, -} from '@chakra-ui/react' -import { PropsWithChildren, useEffect, useState } from 'react' +import { Box, Button, Image, ImageProps, Text, VStack } from '@chakra-ui/react' +import { PropsWithChildren, useState } from 'react' import { useTranslation } from 'react-i18next' import { useNavigate, useParams } from 'react-router-dom' @@ -22,22 +14,18 @@ import { LaunchProjectWorldUrl, } from '../../constants' import { useAuthContext } from '../../context' -import { MfaAction } from '../../types' import { hasNostrAccount, hasTwitterAccount, useMobileMode } from '../../utils' import { ConnectWithNostr } from '../auth/ConnectWithNostr' import { ConnectWithTwitter } from '../auth/ConnectWithTwitter' -import { VerifyYourEmail } from '../otp' import { FormContinueButton } from './components/FormContinueButton' import { ProjectCreateLayout } from './components/ProjectCreateLayout' export const ProjectCreateStart = () => { const { t } = useTranslation() const isMobile = useMobileMode() - const { loading, user, isLoggedIn } = useAuthContext() + const { loading, user } = useAuthContext() const navigate = useNavigate() - const { isOpen, onClose, onOpen } = useDisclosure() - const params = useParams<{ projectId: string }>() const handleBack = () => navigate('/') @@ -49,16 +37,6 @@ export const ProjectCreateStart = () => { : getPath('privateProjectLaunch'), ) - useEffect(() => { - if (isLoggedIn) { - if (!user.email || !user.isEmailVerified) { - onOpen() - } else { - onClose() - } - } - }, [user, isLoggedIn, onOpen, onClose]) - return ( {t('Create a new project')}} @@ -148,12 +126,6 @@ export const ProjectCreateStart = () => { )} - {}} - noClose={true} - action={MfaAction.UserEmailVerification} - /> ) } diff --git a/src/translations/English.json b/src/translations/English.json index 937ce758b..4bc7f370b 100644 --- a/src/translations/English.json +++ b/src/translations/English.json @@ -631,8 +631,5 @@ "This email is where you will receive important project notifications":"This email is where you will receive important project notifications", "Verify email":"Verify email", "This email has not been verified":"This email has not been verified", - - - - + "You can update your email securely by using One Time Password sent to your last verfied email.":"You can update your email securely by using One Time Password sent to your last verfied email." } From bca25193c83edc53c6ad927c4744c85f2837df1e Mon Sep 17 00:00:00 2001 From: sajald77 Date: Thu, 27 Jul 2023 19:38:30 -0400 Subject: [PATCH 03/15] chore: cleanup --- src/pages/otp/VerifyYourEmail.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/otp/VerifyYourEmail.tsx b/src/pages/otp/VerifyYourEmail.tsx index ffce4cb5e..7af5b32aa 100644 --- a/src/pages/otp/VerifyYourEmail.tsx +++ b/src/pages/otp/VerifyYourEmail.tsx @@ -2,7 +2,7 @@ import { Stack, Text, VStack } from '@chakra-ui/react' import { useTranslation } from 'react-i18next' import { CustomModalProps, Modal } from '../../components/layouts' -import { MfaAction, OtpResponseFragment } from '../../types' +import { MfaAction } from '../../types' import { VerifyYourEmailContent, VerifyYourEmailContentProps, From 0f2de29156d38006ef74250f12b9b1adef2328fb Mon Sep 17 00:00:00 2001 From: sajald77 Date: Thu, 27 Jul 2023 21:25:48 -0400 Subject: [PATCH 04/15] feat: add delete user profile --- src/components/ui/TextInputBox.tsx | 2 +- src/graphql/mutations/user.ts | 9 + .../profile/components/DeleteTextConfirm.tsx | 107 +++++++++ .../components/ProfileSettingsModal.tsx | 223 ------------------ src/pages/profile/components/index.ts | 6 +- .../views/{ => account}/AccountInfo.tsx | 19 +- .../account}/EditProfileModal.tsx | 18 +- .../views/account/ProfileSettingsModal.tsx | 43 ++++ .../account/components/DeleteUserProfile.tsx | 87 +++++++ .../account/components/UpdateVerifyEmail.tsx | 195 +++++++++++++++ .../profile/views/account/components/index.ts | 1 + src/pages/profile/views/account/index.ts | 1 + src/pages/profile/views/index.ts | 4 +- .../views/{ => profileTabs}/CreateProject.tsx | 6 +- .../views/profileTabs/ProfileProjects.tsx | 2 +- src/pages/profile/views/profileTabs/index.tsx | 4 + src/translations/English.json | 12 +- src/types/generated/graphql.ts | 71 +++++- 18 files changed, 556 insertions(+), 254 deletions(-) create mode 100644 src/pages/profile/components/DeleteTextConfirm.tsx delete mode 100644 src/pages/profile/components/ProfileSettingsModal.tsx rename src/pages/profile/views/{ => account}/AccountInfo.tsx (87%) rename src/pages/profile/{components => views/account}/EditProfileModal.tsx (90%) create mode 100644 src/pages/profile/views/account/ProfileSettingsModal.tsx create mode 100644 src/pages/profile/views/account/components/DeleteUserProfile.tsx create mode 100644 src/pages/profile/views/account/components/UpdateVerifyEmail.tsx create mode 100644 src/pages/profile/views/account/components/index.ts create mode 100644 src/pages/profile/views/account/index.ts rename src/pages/profile/views/{ => profileTabs}/CreateProject.tsx (71%) diff --git a/src/components/ui/TextInputBox.tsx b/src/components/ui/TextInputBox.tsx index e5fc7540e..505e666f8 100644 --- a/src/components/ui/TextInputBox.tsx +++ b/src/components/ui/TextInputBox.tsx @@ -54,7 +54,7 @@ export const TextInputBox = forwardRef( {error ? ( typeof error === 'object' ? ( error - ) : ( + ) : typeof error === 'boolean' ? null : ( {t(`${error}`)} diff --git a/src/graphql/mutations/user.ts b/src/graphql/mutations/user.ts index ecd8ed6d5..9c4d0cab1 100644 --- a/src/graphql/mutations/user.ts +++ b/src/graphql/mutations/user.ts @@ -36,3 +36,12 @@ export const MUTATION_UPDATE_USER = gql` } } ` + +export const MUTATION_DELETE_USER = gql` + mutation UserDelete { + userDelete { + message + success + } + } +` diff --git a/src/pages/profile/components/DeleteTextConfirm.tsx b/src/pages/profile/components/DeleteTextConfirm.tsx new file mode 100644 index 000000000..1c5753284 --- /dev/null +++ b/src/pages/profile/components/DeleteTextConfirm.tsx @@ -0,0 +1,107 @@ +import { + Box, + Button, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalHeader, + ModalOverlay, + Text, + VStack, +} from '@chakra-ui/react' +import { ChangeEvent, useState } from 'react' +import { Trans, useTranslation } from 'react-i18next' + +import { TextInputBox } from '../../../components/ui' + +interface DeleteTextConfirmProps { + isOpen: boolean + onClose: () => void + title: string + description?: string + textToConfirm: string + confirm?: () => any + close?: () => any + isLoading?: boolean +} + +export const DeleteTextConfirm = ({ + isOpen, + onClose, + title, + description, + confirm, + textToConfirm, + close, + isLoading, +}: DeleteTextConfirmProps) => { + const { t } = useTranslation() + + const [confirmText, setConfirmText] = useState('') + + const textValid = confirmText === textToConfirm + + return ( + + + + + + {title} + + + + + + + {description} + {textToConfirm && ( + + + {"Type '{{textToConfirm}}' and confirm"} + + {description} + + )} + + + {textToConfirm && ( + ) => + setConfirmText(event.target.value) + } + error={!textValid} + /> + )} + {confirm && ( + + )} + {close && ( + + )} + + + + + ) +} diff --git a/src/pages/profile/components/ProfileSettingsModal.tsx b/src/pages/profile/components/ProfileSettingsModal.tsx deleted file mode 100644 index 681814369..000000000 --- a/src/pages/profile/components/ProfileSettingsModal.tsx +++ /dev/null @@ -1,223 +0,0 @@ -import { CheckCircleIcon, WarningIcon } from '@chakra-ui/icons' -import { - Box, - Button, - InputGroup, - InputRightElement, - Modal, - ModalBody, - ModalCloseButton, - ModalContent, - ModalHeader, - ModalOverlay, - Text, - useDisclosure, - VStack, -} from '@chakra-ui/react' -import { yupResolver } from '@hookform/resolvers/yup' -import { useState } from 'react' -import { useForm } from 'react-hook-form' -import { useTranslation } from 'react-i18next' - -import { Body1, Body2 } from '../../../components/typography' -import { useAuthContext } from '../../../context' -import { TextField } from '../../../forms/components/TextField' -import { - MfaAction, - OtpResponseFragment, - useSendOtpByEmailMutation, - useUserEmailUpdateMutation, -} from '../../../types' -import { emailValidationSchema, useNotification } from '../../../utils' -import { VerifyYourEmail } from '../../otp' -import { EditProfileModalProps } from '../hooks/useEditProfileModal' - -export const ProfileSettingsModal = ({ - isOpen, - onClose, - props, -}: EditProfileModalProps) => { - const { t } = useTranslation() - const { toast } = useNotification() - const { user, setUser } = useAuthContext() - - const [otpSent, setSentOtp] = useState(false) - const [otpData, setOtpData] = useState() - - const { - isOpen: isverifyEmailModalOpen, - onOpen: onVerifyEmailModalOpen, - onClose: onVerifyEmailModalClose, - } = useDisclosure() - const [currentMfaAction, setCurrentMfaAction] = useState( - MfaAction.UserEmailUpdate, - ) - - const { formState, control, handleSubmit } = useForm<{ email: string }>({ - resolver: yupResolver(emailValidationSchema), - values: user.email - ? { - email: user.email, - } - : undefined, - }) - - const [sendOtpByEmail] = useSendOtpByEmailMutation({ - onError() { - toast({ - status: 'error', - title: 'Failed to generate otp.', - description: 'Please try again', - }) - }, - onCompleted(data) { - const otp = data.sendOTPByEmail - if (otp) { - setSentOtp(true) - setOtpData(otp) - setCurrentMfaAction(MfaAction.UserEmailVerification) - onVerifyEmailModalOpen() - } - }, - }) - - const [updateUserEmail] = useUserEmailUpdateMutation({ - onError() { - toast({ - status: 'error', - title: 'Failed to update email.', - description: 'Please try again', - }) - }, - onCompleted(data) { - const emailUpdateUser = data.userEmailUpdate - if (emailUpdateUser && emailUpdateUser.email) { - setUser((current) => ({ ...current, ...emailUpdateUser })) - sendOtpByEmail({ - variables: { - input: { - email: user.email, - action: MfaAction.UserEmailVerification, - }, - }, - }) - } - }, - }) - - const onSubmit = async ({ email }: { email: string }) => { - if (!user.email || !user.isEmailVerified) { - updateUserEmail({ - variables: { - input: { - email, - }, - }, - }) - } else { - setCurrentMfaAction(MfaAction.UserEmailUpdate) - onVerifyEmailModalOpen() - } - } - - const handleModalClosed = () => { - setOtpData(undefined) - setSentOtp(false) - onVerifyEmailModalClose() - } - - const handleVerifyEmailClick = () => { - if (user.email) { - sendOtpByEmail({ - variables: { - input: { - email: user.email, - action: MfaAction.UserEmailVerification, - }, - }, - }) - } - } - - const isSavedEmailUnverified = - !formState.isDirty && user.email && !user.isEmailVerified - - return ( - <> - - - - - {t('Settings')} - - -
{ - e.stopPropagation() - e.preventDefault() - handleSubmit(onSubmit)(e) - }} - > - - - {t('Email')} - - {t( - 'This is your account recovery email. Verify this email to edit your project wallet information. You will also receive important project notifications to this email.', - )} - - - - - - {!formState.isDirty && user.isEmailVerified && ( - - )} - {isSavedEmailUnverified && ( - - )} - - - {isSavedEmailUnverified && ( - - {t('This email has not been verified')} - - )} - - - {!user.isEmailVerified && ( - - )} - - - - -
-
-
-
-
- - - ) -} diff --git a/src/pages/profile/components/index.ts b/src/pages/profile/components/index.ts index 8a7afe591..59e7a0002 100644 --- a/src/pages/profile/components/index.ts +++ b/src/pages/profile/components/index.ts @@ -1,6 +1,8 @@ export * from './CreateAProjectButton' -export * from './EditProfileModal' +export * from './DeleteTextConfirm' +export * from './EditableAvatar' +export * from './ExternalAccountBody' export * from './ExternalAccountDisplay' export * from './ExternalAccountLinkItem' -export * from './ProfileSettingsModal' +export * from './ProfileTabLayout' export * from './ProfileTabLayout' diff --git a/src/pages/profile/views/AccountInfo.tsx b/src/pages/profile/views/account/AccountInfo.tsx similarity index 87% rename from src/pages/profile/views/AccountInfo.tsx rename to src/pages/profile/views/account/AccountInfo.tsx index a19fc403c..01d897c4f 100644 --- a/src/pages/profile/views/AccountInfo.tsx +++ b/src/pages/profile/views/account/AccountInfo.tsx @@ -3,15 +3,16 @@ import { useTranslation } from 'react-i18next' import { BsGearFill } from 'react-icons/bs' import { MdEdit } from 'react-icons/md' -import { CardLayout, SkeletonLayout } from '../../../components/layouts' -import { Body1, H1 } from '../../../components/typography' -import { useModal } from '../../../hooks/useModal' -import { ConnectAccounts, ExternalAccountType } from '../../auth' -import { LightningAddress } from '../../projectView/projectMainBody/components' -import { ExternalAccountDisplay, ProfileSettingsModal } from '../components' -import { EditProfileModal } from '../components/EditProfileModal' -import { useEditProfileModal } from '../hooks/useEditProfileModal' -import { UserProfileState } from '../type' +import { CardLayout, SkeletonLayout } from '../../../../components/layouts' +import { Body1, H1 } from '../../../../components/typography' +import { useModal } from '../../../../hooks/useModal' +import { ConnectAccounts, ExternalAccountType } from '../../../auth' +import { LightningAddress } from '../../../projectView/projectMainBody/components' +import { ExternalAccountDisplay } from '../../components' +import { useEditProfileModal } from '../../hooks/useEditProfileModal' +import { UserProfileState } from '../../type' +import { EditProfileModal } from './EditProfileModal' +import { ProfileSettingsModal } from './ProfileSettingsModal' interface AccountInfoProps extends UserProfileState { isEdit: boolean diff --git a/src/pages/profile/components/EditProfileModal.tsx b/src/pages/profile/views/account/EditProfileModal.tsx similarity index 90% rename from src/pages/profile/components/EditProfileModal.tsx rename to src/pages/profile/views/account/EditProfileModal.tsx index dbc28dcb6..33fdbeaf5 100644 --- a/src/pages/profile/components/EditProfileModal.tsx +++ b/src/pages/profile/views/account/EditProfileModal.tsx @@ -16,18 +16,18 @@ import { FormEventHandler, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { BsFillCheckCircleFill, BsFillXCircleFill } from 'react-icons/bs' -import { TextArea, TextInputBox } from '../../../components/ui' -import Loader from '../../../components/ui/Loader' -import { MUTATION_UPDATE_USER } from '../../../graphql' -import { useDebounce } from '../../../hooks' +import { TextArea, TextInputBox } from '../../../../components/ui' +import Loader from '../../../../components/ui/Loader' +import { MUTATION_UPDATE_USER } from '../../../../graphql' +import { useDebounce } from '../../../../hooks' import { LNAddressEvaluationState, useUserLightningAddress, -} from '../../../hooks/useUserLightningAddress' -import { useNotification } from '../../../utils' -import { getUserLightningAddress } from '../../../utils' -import { EditProfileModalProps } from '../hooks/useEditProfileModal' -import { EditableAvatar } from './EditableAvatar' +} from '../../../../hooks/useUserLightningAddress' +import { useNotification } from '../../../../utils' +import { getUserLightningAddress } from '../../../../utils' +import { EditableAvatar } from '../../components' +import { EditProfileModalProps } from '../../hooks/useEditProfileModal' export const EditProfileModal = ({ isOpen, diff --git a/src/pages/profile/views/account/ProfileSettingsModal.tsx b/src/pages/profile/views/account/ProfileSettingsModal.tsx new file mode 100644 index 000000000..5071493fd --- /dev/null +++ b/src/pages/profile/views/account/ProfileSettingsModal.tsx @@ -0,0 +1,43 @@ +import { + Box, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalHeader, + ModalOverlay, + VStack, +} from '@chakra-ui/react' +import { useTranslation } from 'react-i18next' + +import { EditProfileModalProps } from '../../hooks/useEditProfileModal' +import { UpdateVerifyEmail } from './components' +import { DeleteUserProfile } from './components/DeleteUserProfile' + +export const ProfileSettingsModal = ({ + isOpen, + onClose, + props, +}: EditProfileModalProps) => { + const { t } = useTranslation() + + return ( + <> + + + + + {t('Settings')} + + + + + + + + + + + + ) +} diff --git a/src/pages/profile/views/account/components/DeleteUserProfile.tsx b/src/pages/profile/views/account/components/DeleteUserProfile.tsx new file mode 100644 index 000000000..66c7f2302 --- /dev/null +++ b/src/pages/profile/views/account/components/DeleteUserProfile.tsx @@ -0,0 +1,87 @@ +import { ApolloError } from '@apollo/client' +import { Button, VStack } from '@chakra-ui/react' +import { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useNavigate } from 'react-router-dom' + +import { Body1, Body2 } from '../../../../../components/typography' +import { getPath } from '../../../../../constants' +import { useAuthContext } from '../../../../../context' +import { useModal } from '../../../../../hooks/useModal' +import { useUserDeleteMutation } from '../../../../../types' +import { useNotification } from '../../../../../utils' +import { DeleteTextConfirm } from '../../../components' + +export const DeleteUserProfile = () => { + const { t } = useTranslation() + const { toast } = useNotification() + const navigate = useNavigate() + const { isUserAProjectCreator } = useAuthContext() + + const [userDeleted, setUserDeleted] = useState(false) + const deleteProfile = useModal() + + const [deleteUserProfile] = useUserDeleteMutation({ + onError(error: ApolloError) { + toast({ + status: 'error', + title: 'Failed to delete profile', + description: `${error?.message}`, + }) + }, + onCompleted() { + setUserDeleted(true) + }, + }) + + const handleDeleteProject = () => { + deleteUserProfile() + } + + const handleDeleteClose = () => { + navigate(getPath('landingPage')) + } + + const deleteTextProps = userDeleted + ? { + title: t('Profile has been deleted'), + description: t('Your profile has been deleted.'), + textToConfirm: '', + close: handleDeleteClose, + buttonText: t('Close'), + } + : { + title: t('Delete profile'), + description: t('To delete your profile permanently from Geyser.'), + textToConfirm: 'delete this profile', + confirm: handleDeleteProject, + } + + return ( + <> + + {t('Delete profile')} + + {t( + 'You can delete your profile permanently from Geyser. This will allow you to retain your anonymity and connect this account with another profile', + )} + + + + + + + ) +} diff --git a/src/pages/profile/views/account/components/UpdateVerifyEmail.tsx b/src/pages/profile/views/account/components/UpdateVerifyEmail.tsx new file mode 100644 index 000000000..df946bd0d --- /dev/null +++ b/src/pages/profile/views/account/components/UpdateVerifyEmail.tsx @@ -0,0 +1,195 @@ +import { CheckCircleIcon, WarningIcon } from '@chakra-ui/icons' +import { + Button, + InputGroup, + InputRightElement, + Text, + VStack, +} from '@chakra-ui/react' +import { yupResolver } from '@hookform/resolvers/yup' +import { useState } from 'react' +import { useForm } from 'react-hook-form' +import { useTranslation } from 'react-i18next' + +import { Body1, Body2 } from '../../../../../components/typography' +import { useAuthContext } from '../../../../../context' +import { TextField } from '../../../../../forms/components/TextField' +import { useModal } from '../../../../../hooks/useModal' +import { + MfaAction, + OtpResponseFragment, + useSendOtpByEmailMutation, + useUserEmailUpdateMutation, +} from '../../../../../types' +import { emailValidationSchema, useNotification } from '../../../../../utils' +import { VerifyYourEmail } from '../../../../otp' + +export const UpdateVerifyEmail = () => { + const { t } = useTranslation() + const { toast } = useNotification() + const { user, setUser } = useAuthContext() + + const [otpSent, setSentOtp] = useState(false) + const [otpData, setOtpData] = useState() + + const verifyEmailModal = useModal() + + const [currentMfaAction, setCurrentMfaAction] = useState( + MfaAction.UserEmailUpdate, + ) + + const { formState, control, handleSubmit } = useForm<{ email: string }>({ + resolver: yupResolver(emailValidationSchema), + values: user.email + ? { + email: user.email, + } + : undefined, + }) + + const [sendOtpByEmail] = useSendOtpByEmailMutation({ + onError() { + toast({ + status: 'error', + title: 'Failed to generate otp.', + description: 'Please try again', + }) + }, + onCompleted(data) { + const otp = data.sendOTPByEmail + if (otp) { + setSentOtp(true) + setOtpData(otp) + setCurrentMfaAction(MfaAction.UserEmailVerification) + verifyEmailModal.onOpen() + } + }, + }) + + const [updateUserEmail] = useUserEmailUpdateMutation({ + onError() { + toast({ + status: 'error', + title: 'Failed to update email.', + description: 'Please try again', + }) + }, + onCompleted(data) { + const emailUpdateUser = data.userEmailUpdate + if (emailUpdateUser && emailUpdateUser.email) { + setUser((current) => ({ ...current, ...emailUpdateUser })) + sendOtpByEmail({ + variables: { + input: { + email: user.email, + action: MfaAction.UserEmailVerification, + }, + }, + }) + } + }, + }) + + const onSubmit = async ({ email }: { email: string }) => { + if (!user.email || !user.isEmailVerified) { + updateUserEmail({ + variables: { + input: { + email, + }, + }, + }) + } else { + setCurrentMfaAction(MfaAction.UserEmailUpdate) + verifyEmailModal.onOpen() + } + } + + const handleModalClosed = () => { + setOtpData(undefined) + setSentOtp(false) + verifyEmailModal.onClose() + } + + const handleVerifyEmailClick = () => { + if (user.email) { + sendOtpByEmail({ + variables: { + input: { + email: user.email, + action: MfaAction.UserEmailVerification, + }, + }, + }) + } + } + + const isSavedEmailUnverified = + !formState.isDirty && user.email && !user.isEmailVerified + + return ( + <> +
{ + e.stopPropagation() + e.preventDefault() + handleSubmit(onSubmit)(e) + }} + > + + {t('Email')} + + {t( + 'This is your account recovery email. Verify this email to edit your project wallet information. You will also receive important project notifications to this email.', + )} + + + + + + {!formState.isDirty && user.isEmailVerified && ( + + )} + {isSavedEmailUnverified && ( + + )} + + + {isSavedEmailUnverified && ( + + {t('This email has not been verified')} + + )} + + + {!user.isEmailVerified && ( + + )} + + +
+ + + + ) +} diff --git a/src/pages/profile/views/account/components/index.ts b/src/pages/profile/views/account/components/index.ts new file mode 100644 index 000000000..021f8d297 --- /dev/null +++ b/src/pages/profile/views/account/components/index.ts @@ -0,0 +1 @@ +export * from './UpdateVerifyEmail' diff --git a/src/pages/profile/views/account/index.ts b/src/pages/profile/views/account/index.ts new file mode 100644 index 000000000..c4300e980 --- /dev/null +++ b/src/pages/profile/views/account/index.ts @@ -0,0 +1 @@ +export * from './AccountInfo' diff --git a/src/pages/profile/views/index.ts b/src/pages/profile/views/index.ts index 7a843a904..07fc27a0e 100644 --- a/src/pages/profile/views/index.ts +++ b/src/pages/profile/views/index.ts @@ -1,3 +1,3 @@ -export * from './AccountInfo' +export * from './account' export * from './badges' -export * from './CreateProject' +export * from './profileTabs' diff --git a/src/pages/profile/views/CreateProject.tsx b/src/pages/profile/views/profileTabs/CreateProject.tsx similarity index 71% rename from src/pages/profile/views/CreateProject.tsx rename to src/pages/profile/views/profileTabs/CreateProject.tsx index 8070b79f9..965916347 100644 --- a/src/pages/profile/views/CreateProject.tsx +++ b/src/pages/profile/views/profileTabs/CreateProject.tsx @@ -1,8 +1,8 @@ import { useTranslation } from 'react-i18next' -import { CardLayout, CardLayoutProps } from '../../../components/layouts' -import { Body2, H2 } from '../../../components/typography' -import { CreateAProjectButton } from '../components' +import { CardLayout, CardLayoutProps } from '../../../../components/layouts' +import { Body2, H2 } from '../../../../components/typography' +import { CreateAProjectButton } from '../../components' export const CreateProject = (props: CardLayoutProps) => { const { t } = useTranslation() diff --git a/src/pages/profile/views/profileTabs/ProfileProjects.tsx b/src/pages/profile/views/profileTabs/ProfileProjects.tsx index cd76b34de..53adf8026 100644 --- a/src/pages/profile/views/profileTabs/ProfileProjects.tsx +++ b/src/pages/profile/views/profileTabs/ProfileProjects.tsx @@ -6,7 +6,7 @@ import { QUERY_USER_PROFILE_PROJECTS } from '../../../../graphql' import { Project, User, UserGetInput } from '../../../../types' import { LandingProjectCard } from '../../../landing/components' import { CreateAProjectButton, ProfileTabLayout } from '../../components' -import { CreateProject } from '../CreateProject' +import { CreateProject } from './CreateProject' export const ProfileProjects = ({ userProfile, diff --git a/src/pages/profile/views/profileTabs/index.tsx b/src/pages/profile/views/profileTabs/index.tsx index 7843b27de..44739b63f 100644 --- a/src/pages/profile/views/profileTabs/index.tsx +++ b/src/pages/profile/views/profileTabs/index.tsx @@ -1 +1,5 @@ +export * from './CreateProject' +export * from './ProfileActivity' +export * from './ProfileFollowed' +export * from './ProfileProjects' export * from './ProfileTabs' diff --git a/src/translations/English.json b/src/translations/English.json index 4bc7f370b..8b7845623 100644 --- a/src/translations/English.json +++ b/src/translations/English.json @@ -631,5 +631,15 @@ "This email is where you will receive important project notifications":"This email is where you will receive important project notifications", "Verify email":"Verify email", "This email has not been verified":"This email has not been verified", - "You can update your email securely by using One Time Password sent to your last verfied email.":"You can update your email securely by using One Time Password sent to your last verfied email." + "You can update your email securely by using One Time Password sent to your last verfied email.":"You can update your email securely by using One Time Password sent to your last verfied email.", + "Delete profile":"Delete profile", + "Failed to delete profile":"Failed to delete profile", + "Settings":"Settings", + "You can delete your profile permanently from Geyser. This will allow you to retain your anonymity and connect this account with another profile":"You can delete your profile permanently from Geyser. This will allow you to retain your anonymity and connect this account with another profile", + "This is your account recovery email. Verify this email to edit your project wallet information. You will also receive important project notifications to this email.":"This is your account recovery email. Verify this email to edit your project wallet information. You will also receive important project notifications to this email.", + "To delete your profile permanently from Geyser.":"To delete your profile permanently from Geyser.", + "Your profile has been deleted.":"Your profile has been deleted.", + "Profile has been deleted":"Profile has been deleted", + "Type '{{textToConfirm}}' and confirm":"Type '{{textToConfirm}}' and confirm", + "Close":"Close" } diff --git a/src/types/generated/graphql.ts b/src/types/generated/graphql.ts index 5e7976659..b3237d934 100644 --- a/src/types/generated/graphql.ts +++ b/src/types/generated/graphql.ts @@ -400,6 +400,7 @@ export enum FundingMethod { GeyserQr = 'geyser_qr', LnAddress = 'ln_address', LnurlPay = 'lnurl_pay', + Nip57Zap = 'nip57_zap', PodcastKeysend = 'podcast_keysend', } @@ -1259,7 +1260,7 @@ export type ProjectsResponse = { export type ProjectsSummary = { __typename?: 'ProjectsSummary' /** Total of satoshis raised by projects on the platform. */ - fundedTotal?: Maybe + fundedTotal?: Maybe /** Total number of funders on the platform. */ fundersCount?: Maybe /** Total number of projects ever created on the platform. */ @@ -3416,7 +3417,11 @@ export type ProjectsSummaryResolvers< ContextType = any, ParentType extends ResolversParentTypes['ProjectsSummary'] = ResolversParentTypes['ProjectsSummary'], > = { - fundedTotal?: Resolver, ParentType, ContextType> + fundedTotal?: Resolver< + Maybe, + ParentType, + ContextType + > fundersCount?: Resolver, ParentType, ContextType> projectsCount?: Resolver< Maybe, @@ -4947,6 +4952,17 @@ export type UpdateUserMutation = { } } +export type UserDeleteMutationVariables = Exact<{ [key: string]: never }> + +export type UserDeleteMutation = { + __typename?: 'Mutation' + userDelete: { + __typename?: 'DeleteUserResponse' + message?: string | null + success: boolean + } +} + export type CreateWalletMutationVariables = Exact<{ input: CreateWalletInput }> @@ -5473,7 +5489,7 @@ export type ProjectsSummaryQuery = { __typename?: 'Query' projectsSummary: { __typename?: 'ProjectsSummary' - fundedTotal?: number | null + fundedTotal?: any | null fundersCount?: number | null projectsCount?: number | null } @@ -7735,6 +7751,55 @@ export type UpdateUserMutationOptions = Apollo.BaseMutationOptions< UpdateUserMutation, UpdateUserMutationVariables > +export const UserDeleteDocument = gql` + mutation UserDelete { + userDelete { + message + success + } + } +` +export type UserDeleteMutationFn = Apollo.MutationFunction< + UserDeleteMutation, + UserDeleteMutationVariables +> + +/** + * __useUserDeleteMutation__ + * + * To run a mutation, you first call `useUserDeleteMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useUserDeleteMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [userDeleteMutation, { data, loading, error }] = useUserDeleteMutation({ + * variables: { + * }, + * }); + */ +export function useUserDeleteMutation( + baseOptions?: Apollo.MutationHookOptions< + UserDeleteMutation, + UserDeleteMutationVariables + >, +) { + const options = { ...defaultOptions, ...baseOptions } + return Apollo.useMutation( + UserDeleteDocument, + options, + ) +} +export type UserDeleteMutationHookResult = ReturnType< + typeof useUserDeleteMutation +> +export type UserDeleteMutationResult = Apollo.MutationResult +export type UserDeleteMutationOptions = Apollo.BaseMutationOptions< + UserDeleteMutation, + UserDeleteMutationVariables +> export const CreateWalletDocument = gql` mutation CreateWallet($input: CreateWalletInput!) { createWallet(input: $input) { From 6fb1c68c804cce1102cbaafcca1ce58d84ecc668 Mon Sep 17 00:00:00 2001 From: sajald77 Date: Fri, 28 Jul 2023 10:34:30 -0400 Subject: [PATCH 05/15] fix: update copy to mention look in spam folder --- src/pages/otp/VerifyYourEmailContent.tsx | 7 ++++++- src/translations/English.json | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/pages/otp/VerifyYourEmailContent.tsx b/src/pages/otp/VerifyYourEmailContent.tsx index 6d2725eeb..736057caf 100644 --- a/src/pages/otp/VerifyYourEmailContent.tsx +++ b/src/pages/otp/VerifyYourEmailContent.tsx @@ -104,7 +104,12 @@ export const VerifyYourEmailContent = ({ alignSelf="center" /> - {getDescription()} + + {getDescription()}
+ {t('Check your SPAM folder for the email.')} +
+ + {sentOtp && otpData ? ( Date: Fri, 28 Jul 2023 13:55:17 -0400 Subject: [PATCH 06/15] chore: cleanup --- src/pages/otp/VerifyYourEmailContent.tsx | 2 +- .../profile/views/account/components/UpdateVerifyEmail.tsx | 2 +- src/translations/English.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/otp/VerifyYourEmailContent.tsx b/src/pages/otp/VerifyYourEmailContent.tsx index 736057caf..bfa1ac9da 100644 --- a/src/pages/otp/VerifyYourEmailContent.tsx +++ b/src/pages/otp/VerifyYourEmailContent.tsx @@ -41,7 +41,7 @@ export const VerifyYourEmailContent = ({ onError() { toast({ status: 'error', - title: 'Failed to generate otp.', + title: 'Failed to generate OTP.', description: 'Please try again', }) }, diff --git a/src/pages/profile/views/account/components/UpdateVerifyEmail.tsx b/src/pages/profile/views/account/components/UpdateVerifyEmail.tsx index df946bd0d..2d6c0ebea 100644 --- a/src/pages/profile/views/account/components/UpdateVerifyEmail.tsx +++ b/src/pages/profile/views/account/components/UpdateVerifyEmail.tsx @@ -51,7 +51,7 @@ export const UpdateVerifyEmail = () => { onError() { toast({ status: 'error', - title: 'Failed to generate otp.', + title: 'Failed to generate OTP.', description: 'Please try again', }) }, diff --git a/src/translations/English.json b/src/translations/English.json index 93bd1fd24..88461e75e 100644 --- a/src/translations/English.json +++ b/src/translations/English.json @@ -617,7 +617,7 @@ "Email input": "Email input", "OTP input": "OTP input", "Send code again": "Send code again", - "Failed to generate otp.": "Failed to generate otp.", + "Failed to generate OTP.": "Failed to generate OTP.", "Email is a required field": "Email is a required field", "Please enter a valid Email address": "Please enter a valid Email address", "Failed to update email.": "Failed to update email.", From 40dbfae900f7e2cefad204dd89fbf75ddbbc8d05 Mon Sep 17 00:00:00 2001 From: sajald77 Date: Fri, 28 Jul 2023 21:01:59 -0400 Subject: [PATCH 07/15] fix: add tooltip when delete profile button is disabled --- .../account/components/DeleteUserProfile.tsx | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/pages/profile/views/account/components/DeleteUserProfile.tsx b/src/pages/profile/views/account/components/DeleteUserProfile.tsx index 66c7f2302..7706ed8ec 100644 --- a/src/pages/profile/views/account/components/DeleteUserProfile.tsx +++ b/src/pages/profile/views/account/components/DeleteUserProfile.tsx @@ -1,5 +1,5 @@ import { ApolloError } from '@apollo/client' -import { Button, VStack } from '@chakra-ui/react' +import { Button, Tooltip, VStack } from '@chakra-ui/react' import { useState } from 'react' import { useTranslation } from 'react-i18next' import { useNavigate } from 'react-router-dom' @@ -66,15 +66,23 @@ export const DeleteUserProfile = () => { 'You can delete your profile permanently from Geyser. This will allow you to retain your anonymity and connect this account with another profile', )} - + + Date: Fri, 28 Jul 2023 21:32:06 -0400 Subject: [PATCH 08/15] fix: dont show tag that has less than 3 projects --- .../projects/components/ProjectsDisplayMostFundedThisWeek.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/landing/projects/components/ProjectsDisplayMostFundedThisWeek.tsx b/src/pages/landing/projects/components/ProjectsDisplayMostFundedThisWeek.tsx index 25030b9f2..eb0ae3dfb 100644 --- a/src/pages/landing/projects/components/ProjectsDisplayMostFundedThisWeek.tsx +++ b/src/pages/landing/projects/components/ProjectsDisplayMostFundedThisWeek.tsx @@ -35,7 +35,7 @@ export const ProjectsDisplayMostFundedThisWeek = ({ return } - if (projects.length === 0) { + if (projects.length <= 3) { return null } From f1dde5199906bb06ef87a8eee02c0964f4007731 Mon Sep 17 00:00:00 2001 From: sajald77 Date: Fri, 28 Jul 2023 21:33:12 -0400 Subject: [PATCH 09/15] fix: only show projects with length 3 --- .../projects/components/ProjectsDisplayMostFundedThisWeek.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/landing/projects/components/ProjectsDisplayMostFundedThisWeek.tsx b/src/pages/landing/projects/components/ProjectsDisplayMostFundedThisWeek.tsx index eb0ae3dfb..2fd53aed2 100644 --- a/src/pages/landing/projects/components/ProjectsDisplayMostFundedThisWeek.tsx +++ b/src/pages/landing/projects/components/ProjectsDisplayMostFundedThisWeek.tsx @@ -35,7 +35,7 @@ export const ProjectsDisplayMostFundedThisWeek = ({ return } - if (projects.length <= 3) { + if (projects.length <= 2) { return null } From b4d3c10a6b6bf12637a2d445331895bfb9c5739a Mon Sep 17 00:00:00 2001 From: sajald77 Date: Fri, 28 Jul 2023 23:26:59 -0400 Subject: [PATCH 10/15] fix: update tooltip copy --- .../profile/views/account/components/DeleteUserProfile.tsx | 2 +- src/translations/English.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/profile/views/account/components/DeleteUserProfile.tsx b/src/pages/profile/views/account/components/DeleteUserProfile.tsx index 7706ed8ec..810224602 100644 --- a/src/pages/profile/views/account/components/DeleteUserProfile.tsx +++ b/src/pages/profile/views/account/components/DeleteUserProfile.tsx @@ -69,7 +69,7 @@ export const DeleteUserProfile = () => { diff --git a/src/translations/English.json b/src/translations/English.json index 88461e75e..30e028f3a 100644 --- a/src/translations/English.json +++ b/src/translations/English.json @@ -642,5 +642,6 @@ "Profile has been deleted":"Profile has been deleted", "Type '{{textToConfirm}}' and confirm":"Type '{{textToConfirm}}' and confirm", "Close":"Close", - "Check your SPAM folder for the email.":"Check your SPAM folder for the email." + "Check your SPAM folder for the email.":"Check your SPAM folder for the email.", + "project creators cannot delete their profile":"project creators cannot delete their profile" } From 84e970481a41c7b957e05988b4291774ae430e2c Mon Sep 17 00:00:00 2001 From: sajald77 Date: Tue, 1 Aug 2023 11:16:58 -0400 Subject: [PATCH 11/15] fix: fixes based on linear issues --- src/pages/otp/VerifyYourEmailContent.tsx | 3 +- .../otp/components/VerifyOneTimePassword.tsx | 3 + .../account/components/DeleteUserProfile.tsx | 8 +-- .../account/components/UpdateVerifyEmail.tsx | 70 ++++++++++++++----- src/translations/English.json | 8 ++- 5 files changed, 65 insertions(+), 27 deletions(-) diff --git a/src/pages/otp/VerifyYourEmailContent.tsx b/src/pages/otp/VerifyYourEmailContent.tsx index bfa1ac9da..d2eaeb471 100644 --- a/src/pages/otp/VerifyYourEmailContent.tsx +++ b/src/pages/otp/VerifyYourEmailContent.tsx @@ -105,8 +105,7 @@ export const VerifyYourEmailContent = ({ /> - {getDescription()}
- {t('Check your SPAM folder for the email.')} + {getDescription()} {t('Check your SPAM folder for the email.')}
diff --git a/src/pages/otp/components/VerifyOneTimePassword.tsx b/src/pages/otp/components/VerifyOneTimePassword.tsx index 9aa8578b1..2ddfe5072 100644 --- a/src/pages/otp/components/VerifyOneTimePassword.tsx +++ b/src/pages/otp/components/VerifyOneTimePassword.tsx @@ -68,6 +68,7 @@ export const VerifyOneTimePassword = ({ }, onCompleted() { queryCurrentUser() + toast({ status: 'success', title: 'Email verification successfull!' }) }, }) @@ -90,6 +91,8 @@ export const VerifyOneTimePassword = ({ }, }) } + + setOptCode('') } const handleSendCodeAgain = () => { diff --git a/src/pages/profile/views/account/components/DeleteUserProfile.tsx b/src/pages/profile/views/account/components/DeleteUserProfile.tsx index 810224602..aaa219636 100644 --- a/src/pages/profile/views/account/components/DeleteUserProfile.tsx +++ b/src/pages/profile/views/account/components/DeleteUserProfile.tsx @@ -61,15 +61,11 @@ export const DeleteUserProfile = () => { <> {t('Delete profile')} - - {t( - 'You can delete your profile permanently from Geyser. This will allow you to retain your anonymity and connect this account with another profile', - )} - + {t('Delete your profile permanently from Geyser.')} diff --git a/src/pages/profile/views/account/components/UpdateVerifyEmail.tsx b/src/pages/profile/views/account/components/UpdateVerifyEmail.tsx index 2d6c0ebea..5de29909b 100644 --- a/src/pages/profile/views/account/components/UpdateVerifyEmail.tsx +++ b/src/pages/profile/views/account/components/UpdateVerifyEmail.tsx @@ -38,7 +38,9 @@ export const UpdateVerifyEmail = () => { MfaAction.UserEmailUpdate, ) - const { formState, control, handleSubmit } = useForm<{ email: string }>({ + const { formState, control, handleSubmit, getValues } = useForm<{ + email: string + }>({ resolver: yupResolver(emailValidationSchema), values: user.email ? { @@ -86,6 +88,14 @@ export const UpdateVerifyEmail = () => { }, }, }) + toast({ + status: 'success', + title: 'Successfully updated user email', + }) + toast({ + status: 'info', + title: 'OTP Sent to the updated email', + }) } }, }) @@ -124,9 +134,30 @@ export const UpdateVerifyEmail = () => { } } + const handleEmailUpdate = (otpCode: number, otpData: OtpResponseFragment) => { + const formValues = getValues() + if (formValues.email) { + updateUserEmail({ + variables: { + input: { + email: formValues.email, + twoFAInput: { + OTP: { + otp: otpCode, + otpVerificationToken: otpData.otpVerificationToken, + }, + }, + }, + }, + }) + } + } + const isSavedEmailUnverified = !formState.isDirty && user.email && !user.isEmailVerified + const isSavedEmailVerfied = !formState.isDirty && user.isEmailVerified + return ( <>
{ {t('Email')} {t( - 'This is your account recovery email. Verify this email to edit your project wallet information. You will also receive important project notifications to this email.', + 'Verify your email to secure your account and be able to edit project wallet information. This email will be used to notify you on important project and wallet updates.', )} - {!formState.isDirty && user.isEmailVerified && ( + {isSavedEmailVerfied && ( )} {isSavedEmailUnverified && ( @@ -155,6 +186,12 @@ export const UpdateVerifyEmail = () => { )} + {isSavedEmailVerfied && ( + + {t('This email has been verified')} + + )} + {isSavedEmailUnverified && ( {t('This email has not been verified')} @@ -162,30 +199,31 @@ export const UpdateVerifyEmail = () => { )} - {!user.isEmailVerified && ( + {!user.isEmailVerified && !formState.isDirty && user.email ? ( + + ) : ( )} -
Date: Tue, 1 Aug 2023 11:27:47 -0400 Subject: [PATCH 12/15] fix: final chnage to delete user profile tooltip --- .../profile/views/account/components/DeleteUserProfile.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/profile/views/account/components/DeleteUserProfile.tsx b/src/pages/profile/views/account/components/DeleteUserProfile.tsx index aaa219636..19cd6dffc 100644 --- a/src/pages/profile/views/account/components/DeleteUserProfile.tsx +++ b/src/pages/profile/views/account/components/DeleteUserProfile.tsx @@ -59,7 +59,7 @@ export const DeleteUserProfile = () => { return ( <> - + {t('Delete profile')} {t('Delete your profile permanently from Geyser.')} Date: Tue, 1 Aug 2023 15:42:27 -0400 Subject: [PATCH 13/15] fix: final OTP fixes --- .../account/components/UpdateVerifyEmail.tsx | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/pages/profile/views/account/components/UpdateVerifyEmail.tsx b/src/pages/profile/views/account/components/UpdateVerifyEmail.tsx index 5de29909b..4e0095e63 100644 --- a/src/pages/profile/views/account/components/UpdateVerifyEmail.tsx +++ b/src/pages/profile/views/account/components/UpdateVerifyEmail.tsx @@ -80,10 +80,11 @@ export const UpdateVerifyEmail = () => { const emailUpdateUser = data.userEmailUpdate if (emailUpdateUser && emailUpdateUser.email) { setUser((current) => ({ ...current, ...emailUpdateUser })) + verifyEmailModal.onClose() sendOtpByEmail({ variables: { input: { - email: user.email, + email: emailUpdateUser.email, action: MfaAction.UserEmailVerification, }, }, @@ -215,19 +216,20 @@ export const UpdateVerifyEmail = () => { )} - - + {verifyEmailModal.isOpen && ( + + )} ) } From 7ded5fcdee4c7aabac6107d04dbd168be59ecc3b Mon Sep 17 00:00:00 2001 From: sajald77 Date: Tue, 1 Aug 2023 15:44:39 -0400 Subject: [PATCH 14/15] fix: typo --- src/components/nav/top-nav-bar/TopNavBarMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/nav/top-nav-bar/TopNavBarMenu.tsx b/src/components/nav/top-nav-bar/TopNavBarMenu.tsx index f77cdbd09..98d26a6bb 100644 --- a/src/components/nav/top-nav-bar/TopNavBarMenu.tsx +++ b/src/components/nav/top-nav-bar/TopNavBarMenu.tsx @@ -93,7 +93,7 @@ export const TopNavBarMenu = ({ width="100%" onClick={onDashboardSelected} > - (t{'Edit project'}) + {t('Edit project')} From 6afbe1475f1244e01bd9c67130f395b9c8984d17 Mon Sep 17 00:00:00 2001 From: sajald77 Date: Tue, 1 Aug 2023 16:54:23 -0400 Subject: [PATCH 15/15] chore: update package version and add changelogs --- CHANGELOG.md | 27 +++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7126375d0..128e961ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,30 @@ +## [0.4.1](https://github.com/geyserfund/geyser-app/compare/v0.4.0...v0.4.1) (2023-08-01) + + +### Features + +* add delete user profile ([0f2de29](https://github.com/geyserfund/geyser-app/commit/0f2de29156d38006ef74250f12b9b1adef2328fb)) + + +### Bug Fixes + +* add tooltip when delete profile button is disabled ([40dbfae](https://github.com/geyserfund/geyser-app/commit/40dbfae900f7e2cefad204dd89fbf75ddbbc8d05)) +* add update email feature ([02cf41f](https://github.com/geyserfund/geyser-app/commit/02cf41f28538d63175d25725f5a5c7eee7cbf593)) +* allow save only on form dirty ([3e0da2c](https://github.com/geyserfund/geyser-app/commit/3e0da2c730b71fa7a28338abeecd1acbd5b839ce)) +* check mark color, resolves GEY-3041 ([9aafbad](https://github.com/geyserfund/geyser-app/commit/9aafbad4b24bc9b3870bb12239cc11ee57c17ae2)) +* dont show tag that has less than 3 projects ([2a0a99e](https://github.com/geyserfund/geyser-app/commit/2a0a99e2bee1e635f39ef5e81de7ca7efc9bad54)) +* e2e test ([8d80a41](https://github.com/geyserfund/geyser-app/commit/8d80a4167e5e11c5a418084f8f316f35f94c04a0)) +* final chnage to delete user profile tooltip ([631ac9c](https://github.com/geyserfund/geyser-app/commit/631ac9c88814797c52518a9ec2654e92b6a30c8c)) +* final OTP fixes ([cc75bcf](https://github.com/geyserfund/geyser-app/commit/cc75bcf98cfd26fbf3bb89e3efb03bc462677aec)) +* fix otp copy when wallet update and add back button ([c33f6fc](https://github.com/geyserfund/geyser-app/commit/c33f6fc94f2d0d677bcae646502aaa12e13370ba)) +* fixes based on linear issues ([84e9704](https://github.com/geyserfund/geyser-app/commit/84e970481a41c7b957e05988b4291774ae430e2c)) +* only show projects with length 3 ([f1dde51](https://github.com/geyserfund/geyser-app/commit/f1dde5199906bb06ef87a8eee02c0964f4007731)) +* remove verify email prompt ([a0c423e](https://github.com/geyserfund/geyser-app/commit/a0c423e487b965a085d60e228699604a35f293fd)) +* typo ([7ded5fc](https://github.com/geyserfund/geyser-app/commit/7ded5fcdee4c7aabac6107d04dbd168be59ecc3b)) +* update changelog and package version ([9478d01](https://github.com/geyserfund/geyser-app/commit/9478d01ce748f7fbfc0e4b8fbfbe6c031494bf39)) +* update copy to mention look in spam folder ([6fb1c68](https://github.com/geyserfund/geyser-app/commit/6fb1c68c804cce1102cbaafcca1ce58d84ecc668)) +* update tooltip copy ([b4d3c10](https://github.com/geyserfund/geyser-app/commit/b4d3c10a6b6bf12637a2d445331895bfb9c5739a)) + ## [0.4.0](https://github.com/geyserfund/geyser-app/compare/v0.3.1...v0.4.0) (2023-07-25) diff --git a/package.json b/package.json index 1cf6031e7..208c6eeb7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "geyser-app", "private": true, - "version": "0.4.0", + "version": "0.4.1", "scripts": { "dev": "vite", "build": "tsc && vite build",