From a5c0922399fa43f51e77850bd01d004c9040a500 Mon Sep 17 00:00:00 2001 From: nitin <142569587+ehconitin@users.noreply.github.com> Date: Thu, 12 Jun 2025 22:35:36 +0530 Subject: [PATCH] Improve email validation modal design (#12490) closes https://github.com/twentyhq/core-team-issues/issues/1020 --- .../components/EmailVerificationSent.tsx | 208 +++++++++++++----- .../OnboardingModalCircularIcon.tsx | 33 +++ .../ui/layout/modal/components/Modal.tsx | 2 +- .../src/pages/onboarding/PaymentSuccess.tsx | 105 ++++----- .../display/icon/components/TablerIcons.ts | 9 +- packages/twenty-ui/src/display/index.ts | 9 +- .../twenty-ui/src/theme/constants/Modal.ts | 1 + 7 files changed, 255 insertions(+), 112 deletions(-) create mode 100644 packages/twenty-front/src/modules/onboarding/components/OnboardingModalCircularIcon.tsx diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/components/EmailVerificationSent.tsx b/packages/twenty-front/src/modules/auth/sign-in-up/components/EmailVerificationSent.tsx index c82076fa7..e877882d7 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/components/EmailVerificationSent.tsx +++ b/packages/twenty-front/src/modules/auth/sign-in-up/components/EmailVerificationSent.tsx @@ -3,32 +3,83 @@ import styled from '@emotion/styled'; import { SubTitle } from '@/auth/components/SubTitle'; import { Title } from '@/auth/components/Title'; import { useHandleResendEmailVerificationToken } from '@/auth/sign-in-up/hooks/useHandleResendEmailVerificationToken'; -import { useTheme } from '@emotion/react'; -import { IconMail } from 'twenty-ui/display'; -import { Loader } from 'twenty-ui/feedback'; +import { + SignInUpStep, + signInUpStepState, +} from '@/auth/states/signInUpStepState'; +import { OnboardingModalCircularIcon } from '@/onboarding/components/OnboardingModalCircularIcon'; +import { t } from '@lingui/core/macro'; +import { useSetRecoilState } from 'recoil'; +import { + IconGmail, + IconMail, + IconMailX, + IconMicrosoft, +} from 'twenty-ui/display'; import { MainButton } from 'twenty-ui/input'; -import { RGBA } from 'twenty-ui/theme'; import { AnimatedEaseIn } from 'twenty-ui/utilities'; -const StyledMailContainer = styled.div` - align-items: center; +const StyledContainer = styled.div` display: flex; - justify-content: center; - border: 2px solid ${(props) => props.color}; - border-radius: ${({ theme }) => theme.border.radius.rounded}; - box-shadow: ${(props) => - props.color && `-4px 4px 0 -2px ${RGBA(props.color, 1)}`}; - height: 36px; - width: 36px; - margin-bottom: ${({ theme }) => theme.spacing(4)}; + flex-direction: column; + align-items: center; + gap: ${({ theme }) => theme.spacing(8)}; + width: 100%; +`; + +const StyledTextContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + text-align: center; `; const StyledEmail = styled.span` font-weight: ${({ theme }) => theme.font.weight.medium}; `; -const StyledButtonContainer = styled.div` - margin-top: ${({ theme }) => theme.spacing(8)}; +const StyledButtonsContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + gap: ${({ theme }) => theme.spacing(3)}; + width: 100%; + max-width: 240px; +`; + +const StyledBottomLinks = styled.div` + align-items: center; + display: flex; + flex-direction: row; + gap: ${({ theme }) => theme.spacing(2)}; + justify-content: center; +`; + +const StyledLinkButton = styled.button` + background: none; + border: none; + font-family: ${({ theme }) => theme.font.family}; + font-size: ${({ theme }) => theme.font.size.xs}; + font-weight: ${({ theme }) => theme.font.weight.regular}; + line-height: 140%; + color: ${({ theme }) => theme.font.color.tertiary}; + cursor: pointer; + + &:hover { + color: ${({ theme }) => theme.font.color.secondary}; + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } +`; + +const StyledDot = styled.div` + background: ${({ theme }) => theme.font.color.light}; + border-radius: 50%; + height: 2px; + width: 2px; `; export const EmailVerificationSent = ({ @@ -38,46 +89,101 @@ export const EmailVerificationSent = ({ email: string | null; isError?: boolean; }) => { - const theme = useTheme(); - const color = - theme.name === 'light' ? theme.grayScale.gray90 : theme.grayScale.gray10; + const setSignInUpStep = useSetRecoilState(signInUpStepState); const { handleResendEmailVerificationToken, loading: isLoading } = useHandleResendEmailVerificationToken(); - return ( + const handleOpenGmail = () => { + const gmailUrl = email + ? `https://mail.google.com/mail/u/${email}/` + : 'https://mail.google.com/'; + window.open(gmailUrl, '_blank'); + }; + + const handleOpenOutlook = () => { + const outlookUrl = email + ? `https://outlook.live.com/mail/${email}/` + : 'https://outlook.live.com/'; + window.open(outlookUrl, '_blank'); + }; + + const handleChangeEmail = () => { + setSignInUpStep(SignInUpStep.Email); + }; + + const title = isError ? t`Email Verification Failed` : t`Check your Emails`; + const subtitle = isError + ? t`We encountered an issue verifying` + : t`A verification email has been sent to`; + + const Icon = isError ? IconMailX : IconMail; + + const mainButtons = isError ? ( <> - - - - - - - {isError ? 'Email Verification Failed' : 'Confirm Your Email Address'} - - - {isError ? ( - <> - Oops! We encountered an issue verifying{' '} - {email}. Please request a new - verification email and try again. - - ) : ( - <> - A verification email has been sent to{' '} - {email}. Please check your inbox and - click the link in the email to activate your account. - - )} - - - (isLoading ? : undefined)} - width={200} - /> - + + + + ) : ( + <> + + ); + + return ( + + + + + + + + {title} + + + {subtitle} {email} + + + + {mainButtons} + + {!isError && ( + + + {isLoading ? t`Sending...` : t`Resend email`} + + + + {t`Change email`} + + + )} + + ); }; diff --git a/packages/twenty-front/src/modules/onboarding/components/OnboardingModalCircularIcon.tsx b/packages/twenty-front/src/modules/onboarding/components/OnboardingModalCircularIcon.tsx new file mode 100644 index 000000000..c3673f676 --- /dev/null +++ b/packages/twenty-front/src/modules/onboarding/components/OnboardingModalCircularIcon.tsx @@ -0,0 +1,33 @@ +import { useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; +import { IconComponent } from 'twenty-ui/display'; +import { RGBA } from 'twenty-ui/theme'; + +const StyledCheckContainer = styled.div<{ color: string }>` + align-items: center; + display: flex; + justify-content: center; + border: 2px solid ${({ color }) => color}; + border-radius: ${({ theme }) => theme.border.radius.rounded}; + box-shadow: ${({ color }) => color && `-4px 4px 0 -2px ${RGBA(color, 1)}`}; + height: 36px; + width: 36px; +`; + +type OnboardingModalCircularIconProps = { + Icon: IconComponent; +}; + +export const OnboardingModalCircularIcon = ({ + Icon, +}: OnboardingModalCircularIconProps) => { + const theme = useTheme(); + const color = + theme.name === 'light' ? theme.grayScale.gray90 : theme.grayScale.gray10; + + return ( + + + + ); +}; diff --git a/packages/twenty-front/src/modules/ui/layout/modal/components/Modal.tsx b/packages/twenty-front/src/modules/ui/layout/modal/components/Modal.tsx index fed4cc434..4b4e604aa 100644 --- a/packages/twenty-front/src/modules/ui/layout/modal/components/Modal.tsx +++ b/packages/twenty-front/src/modules/ui/layout/modal/components/Modal.tsx @@ -37,7 +37,7 @@ const StyledModalDiv = styled(motion.div)<{ z-index: ${RootStackingContextZIndices.RootModal}; // should be higher than Backdrop's z-index width: ${({ isMobile, size, theme }) => { - if (isMobile) return theme.modal.size.fullscreen; + if (isMobile) return theme.modal.size.fullscreen.width; switch (size) { case 'small': return theme.modal.size.sm.width; diff --git a/packages/twenty-front/src/pages/onboarding/PaymentSuccess.tsx b/packages/twenty-front/src/pages/onboarding/PaymentSuccess.tsx index 099ec1d8b..4847e2563 100644 --- a/packages/twenty-front/src/pages/onboarding/PaymentSuccess.tsx +++ b/packages/twenty-front/src/pages/onboarding/PaymentSuccess.tsx @@ -1,85 +1,86 @@ import { SubTitle } from '@/auth/components/SubTitle'; import { Title } from '@/auth/components/Title'; import { currentUserState } from '@/auth/states/currentUserState'; +import { OnboardingModalCircularIcon } from '@/onboarding/components/OnboardingModalCircularIcon'; import { AppPath } from '@/types/AppPath'; import { Modal } from '@/ui/layout/modal/components/Modal'; import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus'; -import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; +import { useState } from 'react'; import { useSetRecoilState } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { IconCheck } from 'twenty-ui/display'; +import { Loader } from 'twenty-ui/feedback'; import { MainButton } from 'twenty-ui/input'; -import { RGBA } from 'twenty-ui/theme'; import { AnimatedEaseIn } from 'twenty-ui/utilities'; import { useGetCurrentUserLazyQuery } from '~/generated/graphql'; import { useNavigateApp } from '~/hooks/useNavigateApp'; -const StyledCheckContainer = styled.div` - align-items: center; - display: flex; - justify-content: center; - border: 2px solid ${(props) => props.color}; - border-radius: ${({ theme }) => theme.border.radius.rounded}; - box-shadow: ${(props) => - props.color && `-4px 4px 0 -2px ${RGBA(props.color, 1)}`}; - height: 36px; - width: 36px; - margin-bottom: ${({ theme }) => theme.spacing(4)}; +const StyledModalContent = styled(Modal.Content)` + gap: ${({ theme }) => theme.spacing(8)}; `; -const StyledButtonContainer = styled.div` - margin-top: ${({ theme }) => theme.spacing(8)}; +const StyledTitleContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + text-align: center; `; export const PaymentSuccess = () => { - const theme = useTheme(); const navigate = useNavigateApp(); const subscriptionStatus = useSubscriptionStatus(); const [getCurrentUser] = useGetCurrentUserLazyQuery(); const setCurrentUser = useSetRecoilState(currentUserState); - const color = - theme.name === 'light' ? theme.grayScale.gray90 : theme.grayScale.gray10; - + const [isLoading, setIsLoading] = useState(false); const navigateWithSubscriptionCheck = async () => { - if (isDefined(subscriptionStatus)) { - navigate(AppPath.CreateWorkspace); - return; + if (isLoading) return; + + setIsLoading(true); + + try { + if (isDefined(subscriptionStatus)) { + navigate(AppPath.CreateWorkspace); + return; + } + + const result = await getCurrentUser({ fetchPolicy: 'network-only' }); + const currentUser = result.data?.currentUser; + const refreshedSubscriptionStatus = + currentUser?.currentWorkspace?.currentBillingSubscription?.status; + + if (isDefined(currentUser) && isDefined(refreshedSubscriptionStatus)) { + setCurrentUser(currentUser); + navigate(AppPath.CreateWorkspace); + return; + } + + throw new Error( + "We're waiting for a confirmation from our payment provider (Stripe).\n" + + 'Please try again in a few seconds, sorry.', + ); + } catch (error) { + setIsLoading(false); + throw error; } - - const result = await getCurrentUser({ fetchPolicy: 'network-only' }); - const currentUser = result.data?.currentUser; - const refreshedSubscriptionStatus = - currentUser?.currentWorkspace?.currentBillingSubscription?.status; - - if (isDefined(currentUser) && isDefined(refreshedSubscriptionStatus)) { - setCurrentUser(currentUser); - navigate(AppPath.CreateWorkspace); - return; - } - - throw new Error( - "We're waiting for a confirmation from our payment provider (Stripe).\n" + - 'Please try again in a few seconds, sorry.', - ); }; return ( - + - - - + - All set! - Your account has been activated. - - - - + + All set! + Your account has been activated. + + (isLoading ? : null)} + disabled={isLoading} + /> + ); }; diff --git a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts index 600ae4887..00c4e65f5 100644 --- a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts +++ b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts @@ -4,18 +4,18 @@ export { IconAlertCircle, IconAlertTriangle, IconApi, - IconAppWindow, IconApps, + IconAppWindow, IconArchive, IconArchiveOff, IconArrowBackUp, IconArrowDown, IconArrowLeft, IconArrowRight, - IconArrowUp, - IconArrowUpRight, IconArrowsDiagonal, IconArrowsVertical, + IconArrowUp, + IconArrowUpRight, IconAt, IconBaselineDensitySmall, IconBell, @@ -47,8 +47,8 @@ export { IconChevronDown, IconChevronLeft, IconChevronRight, - IconChevronUp, IconChevronsRight, + IconChevronUp, IconCircleDot, IconCircleOff, IconCirclePlus, @@ -202,6 +202,7 @@ export { IconLogout, IconMail, IconMailCog, + IconMailX, IconMap, IconMaximize, IconMessage, diff --git a/packages/twenty-ui/src/display/index.ts b/packages/twenty-ui/src/display/index.ts index 907f1eac1..6318cf854 100644 --- a/packages/twenty-ui/src/display/index.ts +++ b/packages/twenty-ui/src/display/index.ts @@ -65,18 +65,18 @@ export { IconAlertCircle, IconAlertTriangle, IconApi, - IconAppWindow, IconApps, + IconAppWindow, IconArchive, IconArchiveOff, IconArrowBackUp, IconArrowDown, IconArrowLeft, IconArrowRight, - IconArrowUp, - IconArrowUpRight, IconArrowsDiagonal, IconArrowsVertical, + IconArrowUp, + IconArrowUpRight, IconAt, IconBaselineDensitySmall, IconBell, @@ -108,8 +108,8 @@ export { IconChevronDown, IconChevronLeft, IconChevronRight, - IconChevronUp, IconChevronsRight, + IconChevronUp, IconCircleDot, IconCircleOff, IconCirclePlus, @@ -263,6 +263,7 @@ export { IconLogout, IconMail, IconMailCog, + IconMailX, IconMap, IconMaximize, IconMessage, diff --git a/packages/twenty-ui/src/theme/constants/Modal.ts b/packages/twenty-ui/src/theme/constants/Modal.ts index 7b50624ca..823e5568c 100644 --- a/packages/twenty-ui/src/theme/constants/Modal.ts +++ b/packages/twenty-ui/src/theme/constants/Modal.ts @@ -16,6 +16,7 @@ export const MODAL: { height: '800px', }, fullscreen: { + width: '100dvw', height: '100dvh', }, },