-
Notifications
You must be signed in to change notification settings - Fork 474
feat(ui): add profile email verification and management flows #9737
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
b7ddbbd
60fa358
b145a9b
5cd8b34
9bbe37f
3c5efe6
033a3a1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| --- | ||
| --- | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| import type { UserProfileAccountSectionViewProps } from '@clerk/ui/mosaic/user-profile/user-profile-account-section/user-profile-account-section.view'; | ||
| import type { UserProfileAddEmailViewProps } from '@clerk/ui/mosaic/user-profile/user-profile-add-email.view'; | ||
|
|
||
| interface FixtureOptions { | ||
| failAt?: UserProfileAddEmailViewProps['step']; | ||
| onVerified?: (emailAddress: string) => void; | ||
| } | ||
|
|
||
| export function createUserProfileAddEmailFixture({ failAt, onVerified }: FixtureOptions = {}): Pick< | ||
| UserProfileAccountSectionViewProps, | ||
| 'onSendEmailCode' | 'onVerifyEmailCode' | ||
| > { | ||
| return { | ||
| onSendEmailCode: async () => { | ||
| await new Promise(resolve => setTimeout(resolve, 700)); | ||
| if (failAt === 'email') { | ||
| throw new Error('We couldn’t send a code. Try again.'); | ||
| } | ||
| }, | ||
| onVerifyEmailCode: async (emailAddress, code) => { | ||
| await new Promise(resolve => setTimeout(resolve, 700)); | ||
| if (failAt === 'verify' || code === '000000') { | ||
| throw new Error('That code is incorrect. Try again.'); | ||
| } | ||
| onVerified?.(emailAddress); | ||
| }, | ||
| }; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| import { useEffect, useState } from 'react'; | ||
|
|
||
| export function useUserProfileVerifyEmailLinkFixture({ failResend = false } = {}) { | ||
| const [open, setOpen] = useState(false); | ||
| const [resendSeconds, setResendSeconds] = useState(12); | ||
| const [isResending, setIsResending] = useState(false); | ||
| const [errorMessage, setErrorMessage] = useState<string>(); | ||
|
|
||
| useEffect(() => { | ||
| if (!open) { | ||
| return; | ||
| } | ||
| if (isResending) { | ||
| const timer = setTimeout(() => { | ||
| setIsResending(false); | ||
| if (failResend) { | ||
| setErrorMessage('Unable to send the verification link. Try again.'); | ||
| } else { | ||
| setResendSeconds(12); | ||
| } | ||
| }, 700); | ||
| return () => clearTimeout(timer); | ||
| } | ||
| if (resendSeconds > 0) { | ||
| const timer = setTimeout(() => setResendSeconds(seconds => seconds - 1), 1000); | ||
| return () => clearTimeout(timer); | ||
| } | ||
| }, [open, isResending, resendSeconds, failResend]); | ||
|
Comment on lines
+9
to
+28
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win Add direct unit tests for the new fixture hook. The 🤖 Prompt for AI Agents |
||
|
|
||
| return { | ||
| open, | ||
| emailAddress: 'example@email.com', | ||
| resendSeconds, | ||
| isResending, | ||
| errorMessage, | ||
| onOpenChange: (value: boolean) => { | ||
| setOpen(value); | ||
| setResendSeconds(12); | ||
| setIsResending(false); | ||
| setErrorMessage(undefined); | ||
| }, | ||
| onResend: () => { | ||
| setErrorMessage(undefined); | ||
| setIsResending(true); | ||
| }, | ||
| }; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| import { useEffect, useState } from 'react'; | ||
|
|
||
| export function useUserProfileVerifyEmailSsoFixture({ failConnect = false } = {}) { | ||
| const [open, setOpen] = useState(false); | ||
| const [isConnecting, setIsConnecting] = useState(false); | ||
| const [errorMessage, setErrorMessage] = useState<string>(); | ||
|
|
||
| useEffect(() => { | ||
| if (!isConnecting) { | ||
| return; | ||
| } | ||
| const timer = setTimeout(() => { | ||
| setIsConnecting(false); | ||
| if (failConnect) { | ||
| setErrorMessage('Unable to connect to Okta. Try again.'); | ||
| } else { | ||
| setOpen(false); | ||
| } | ||
| }, 1200); | ||
| return () => clearTimeout(timer); | ||
| }, [isConnecting, failConnect]); | ||
|
|
||
| return { | ||
| open, | ||
| emailAddress: 'example@email.com', | ||
| connection: { | ||
| provider: 'Okta SSO', | ||
| domain: 'acme.co', | ||
| iconUrl: 'https://img.clerk.com/static/okta.svg', | ||
| }, | ||
| isConnecting, | ||
| errorMessage, | ||
| onOpenChange: (value: boolean) => { | ||
| setOpen(value); | ||
| setIsConnecting(false); | ||
| setErrorMessage(undefined); | ||
| }, | ||
| onConnect: () => { | ||
| setErrorMessage(undefined); | ||
| setIsConnecting(true); | ||
| }, | ||
| }; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,10 @@ Account details, profile image, email addresses, and phone numbers composed with | |
|
|
||
| ## Multiple accounts | ||
|
|
||
| **Add email** opens email entry followed by a six-digit verification code. Resend is available after | ||
| the countdown. In this preview, `000000` shows an incorrect-code error; another six-digit code adds | ||
| the verified address. | ||
|
|
||
| <Story | ||
| name='MultipleAccounts' | ||
| storyModule={Stories} | ||
|
|
@@ -27,5 +31,62 @@ Account details, profile image, email addresses, and phone numbers composed with | |
| { name: 'Button', href: '/components/button', layer: 'Components' }, | ||
| { name: 'Badge', href: '/components/badge', layer: 'Components' }, | ||
| { name: 'Icon', href: '/components/icon', layer: 'Components' }, | ||
| { name: 'Dialog', href: '/components/dialog', layer: 'Components' }, | ||
| { name: 'Card', href: '/components/card', layer: 'Components' }, | ||
| { name: 'Flow', href: '/components/flow', layer: 'Components' }, | ||
| { name: 'Otp', href: '/components/otp', layer: 'Components' }, | ||
| ]} | ||
| /> | ||
|
|
||
| ## Email verification error | ||
|
|
||
| This example rejects every verification attempt so the error remains visible and the user can retry. | ||
|
|
||
| <Story name='AddEmailFails' storyModule={Stories} /> | ||
|
|
||
| ## Email-link verification | ||
|
|
||
| The profile uses OTP. This separate view displays an email-link verification in progress, with a | ||
| resend countdown and Cancel. Its caller supplies the address, pending state, errors, and callbacks. | ||
|
|
||
| <Story | ||
| name='EmailLinkVerification' | ||
| storyModule={Stories} | ||
| composition={[ | ||
| { name: 'Dialog', href: '/components/dialog', layer: 'Components' }, | ||
| { name: 'Card', href: '/components/card', layer: 'Components' }, | ||
| { name: 'Spinner', href: '/components/spinner', layer: 'Components' }, | ||
| { name: 'Button', href: '/components/button', layer: 'Components' }, | ||
| ]} | ||
| /> | ||
|
|
||
| ### Resend error | ||
|
|
||
| After the countdown, resend to see the supplied error message. | ||
|
|
||
| <Story name='EmailLinkResendFails' storyModule={Stories} /> | ||
|
|
||
| ## Enterprise SSO verification | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift Add the required documentation hierarchy. Update this page to include As per coding guidelines: “Playground / Props / Usage are mandatory and always in this order.” 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
|
|
||
| When an email matches an enterprise SSO connection, this view presents the provider and a Connect | ||
| action. The option composes `Item` with the shared provider icon, label, description, and button. | ||
| The caller supplies the connection, pending state, errors, and callback. In this preview, Connect | ||
| simulates completion and closes the dialog. | ||
|
|
||
| <Story | ||
| name='EmailSsoVerification' | ||
| storyModule={Stories} | ||
| composition={[ | ||
| { name: 'Dialog', href: '/components/dialog', layer: 'Components' }, | ||
| { name: 'Card', href: '/components/card', layer: 'Components' }, | ||
| { name: 'Item', href: '/components/item', layer: 'Components' }, | ||
| { name: 'IconFrame', href: '/components/icon-frame', layer: 'Components' }, | ||
| { name: 'Button', href: '/components/button', layer: 'Components' }, | ||
| ]} | ||
| /> | ||
|
|
||
| ### Connection error | ||
|
|
||
| Connect to see the supplied error message. The user can retry or cancel. | ||
|
|
||
| <Story name='EmailSsoConnectFails' storyModule={Stories} /> | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Add the affected package and changelog entry.
This empty Changesets file does not request a package version or describe the new email actions. Add the applicable package with the correct release level and a concise user-facing summary.
As per coding guidelines, “Use Changesets for version management and changelogs.”
🤖 Prompt for AI Agents
Source: Coding guidelines