Skip to content

Commit faa5af0

Browse files
authored
fix(react-kit): highlight terms and check email (#174)
1 parent c4856f3 commit faa5af0

3 files changed

Lines changed: 69 additions & 37 deletions

File tree

packages/react-kit/src/auth/pages/SignUp.tsx

Lines changed: 57 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { OrView } from '../../shared/components/OrView'
1414
import { ScreenWrapper } from '../../shared/components/ScreenWrapper'
1515
import { SignUpFooter } from '../../shared/components/SignUpFooter'
1616
import { Text } from '../../shared/components/Text'
17+
import { isValidEmailAddress } from '../../shared/utils/common'
1718
import { useAuth } from '../hooks/useAuth'
1819

1920
// Helper to check if error is due to user closing OAuth window
@@ -41,6 +42,7 @@ export function SignUp() {
4142
const { goToStep, setEmail, setOtpSession, config, enabledMethods } =
4243
useAuth()
4344
const [agreedToTerms, setAgreedToTerms] = useState(false)
45+
const [highlightAgreement, setHighlightAgreement] = useState(false)
4446
const [emailInput, setEmailInput] = useState('')
4547
const shouldUseOtp = config?.emailAuthMethod === 'otp'
4648
const { mutateAsync: sendOtp, isPending: isSendOtpPending } = useSendOTP()
@@ -102,19 +104,30 @@ export function SignUp() {
102104
const canContinue = !requiresAgreement || agreedToTerms
103105

104106
const handleRegisterPasskey = () => {
105-
if (anyPending || !canContinue) return
107+
if (anyPending) return
108+
if (!canContinue) {
109+
setHighlightAgreement(true)
110+
return
111+
}
106112
setError(null)
107113
registerPasskey()
108114
}
109115

110116
const handleLoginPasskey = () => {
111-
if (anyPending || !canContinue) return
117+
if (anyPending) return
118+
if (!canContinue) {
119+
setHighlightAgreement(true)
120+
return
121+
}
112122
setError(null)
113123
loginPasskey()
114124
}
115125

116126
const handleGoogleAuth = async () => {
117-
if (!canContinue) return
127+
if (!canContinue) {
128+
setHighlightAgreement(true)
129+
return
130+
}
118131
setError(null)
119132
try {
120133
await authenticateOAuth({ provider: 'google' })
@@ -130,43 +143,35 @@ export function SignUp() {
130143
goToStep('wallet-selection')
131144
}
132145

133-
const handleSendOtp = async () => {
134-
if (!emailInput || anyPending || !canContinue) return
135-
setError(null)
136-
try {
137-
const { otpId, otpEncryptionTargetBundle } = await sendOtp({
138-
email: emailInput,
139-
})
140-
setEmail(emailInput)
141-
setOtpSession({ otpId, otpEncryptionTargetBundle })
142-
goToStep('otp-input')
143-
} catch (err) {
144-
setError(
145-
err instanceof Error ? err.message : 'Failed to send verification code',
146-
)
146+
const handleEmailSubmit = async (forceMethod?: 'otp' | 'magicLink') => {
147+
if (!emailInput || anyPending) return
148+
if (!isValidEmailAddress(emailInput)) return
149+
if (!canContinue) {
150+
setHighlightAgreement(true)
151+
return
147152
}
148-
}
149153

150-
const handleSendMagicLink = async () => {
151-
if (!emailInput || anyPending || !canContinue) return
154+
const useOtp =
155+
forceMethod !== undefined ? forceMethod === 'otp' : shouldUseOtp
156+
152157
setError(null)
153158
try {
154-
const { otpId, otpEncryptionTargetBundle } = await sendMagicLink({
155-
email: emailInput,
156-
redirectURL: config?.magicLinkBaseUrl ?? '',
157-
})
159+
const { otpId, otpEncryptionTargetBundle } = useOtp
160+
? await sendOtp({ email: emailInput })
161+
: await sendMagicLink({
162+
email: emailInput,
163+
redirectURL: config?.magicLinkBaseUrl ?? '',
164+
})
158165
setEmail(emailInput)
159166
setOtpSession({ otpId, otpEncryptionTargetBundle })
160-
goToStep('email-verification')
167+
goToStep(useOtp ? 'otp-input' : 'email-verification')
161168
} catch (err) {
162169
setError(
163170
err instanceof Error ? err.message : 'Failed to send verification code',
164171
)
165172
}
166173
}
167174

168-
const handleEmailSubmit = shouldUseOtp ? handleSendOtp : handleSendMagicLink
169-
170175
if (error) {
171176
return (
172177
<div className="flex items-center justify-center min-h-screen">
@@ -239,24 +244,28 @@ export function SignUp() {
239244
autoComplete="email"
240245
disabled={anyPending}
241246
variant="listItemStyle"
247+
className="rounded-3xl"
242248
onKeyDown={(e) => {
243249
if (
244250
e.key === 'Enter' &&
245251
emailInput &&
246252
!anyPending &&
247-
canContinue &&
248253
!showBothEmailButtons
249254
) {
250255
handleEmailSubmit()
251256
}
252257
}}
253258
>
254259
{!showBothEmailButtons &&
255-
(emailInput && !anyPending && canContinue ? (
260+
(emailInput && !anyPending ? (
256261
<button
257262
type="button"
258-
className="w-13 h-13 rounded-2xl bg-greyScale/[3%] flex items-center justify-center hover:bg-greyScale/[5%] transition-colors cursor-pointer"
259-
onClick={handleEmailSubmit}
263+
className={`w-13 h-13 rounded-2xl bg-greyScale/[3%] flex items-center justify-center transition-colors ${
264+
isValidEmailAddress(emailInput) && canContinue
265+
? 'cursor-pointer hover:bg-greyScale/[5%]'
266+
: 'cursor-not-allowed opacity-50'
267+
}`}
268+
onClick={() => handleEmailSubmit()}
260269
>
261270
<Icon
262271
name="chevronRight"
@@ -276,16 +285,24 @@ export function SignUp() {
276285
text="Continue with email magic link"
277286
iconName="email"
278287
trailIcon
279-
disabled={!emailInput || anyPending || !canContinue}
280-
onClick={handleSendMagicLink}
288+
disabled={
289+
!emailInput ||
290+
anyPending ||
291+
!isValidEmailAddress(emailInput)
292+
}
293+
onClick={() => handleEmailSubmit('magicLink')}
281294
/>
282295
<Button
283296
action="secondaryNeutral"
284297
text="Continue with email OTP code"
285298
iconName="email"
286299
trailIcon
287-
disabled={!emailInput || anyPending || !canContinue}
288-
onClick={handleSendOtp}
300+
disabled={
301+
!emailInput ||
302+
anyPending ||
303+
!isValidEmailAddress(emailInput)
304+
}
305+
onClick={() => handleEmailSubmit('otp')}
289306
/>
290307
</>
291308
)}
@@ -310,7 +327,11 @@ export function SignUp() {
310327
termsAndConditionsUrl={config?.termsAndConditionsUrl}
311328
privacyPolicyUrl={config?.privacyPolicyUrl}
312329
agreedToTerms={agreedToTerms}
313-
setAgreedToTerms={setAgreedToTerms}
330+
setAgreedToTerms={(agreed) => {
331+
setAgreedToTerms(agreed)
332+
if (agreed) setHighlightAgreement(false)
333+
}}
334+
highlight={highlightAgreement}
314335
/>
315336
</div>
316337
)}

packages/react-kit/src/shared/components/SignUpFooter/index.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,24 @@ export function SignUpFooter({
66
privacyPolicyUrl,
77
agreedToTerms,
88
setAgreedToTerms,
9+
highlight = false,
910
}: {
1011
termsAndConditionsUrl?: string | undefined
1112
privacyPolicyUrl?: string | undefined
1213
agreedToTerms: boolean
1314
setAgreedToTerms: (agreed: boolean) => void
15+
highlight?: boolean
1416
}) {
1517
const showAgreement = !!(termsAndConditionsUrl || privacyPolicyUrl)
1618

1719
return (
1820
<div className="flex flex-col items-center gap-5">
1921
{showAgreement && (
20-
<div className="flex flex-row items-center gap-2">
22+
<div
23+
className={`flex flex-row items-center gap-2 rounded-md p-2 transition-colors ${
24+
highlight ? 'border border-negative' : 'border border-transparent'
25+
}`}
26+
>
2127
<input
2228
type="checkbox"
2329
checked={agreedToTerms}

packages/react-kit/src/shared/utils/common.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,8 @@ export function camelCaseToTitle(str: string): string {
3636
.map((word) => capitalizeFirst(word))
3737
.join(' ')
3838
}
39+
40+
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
41+
export function isValidEmailAddress(email: string): boolean {
42+
return EMAIL_REGEX.test(email.trim())
43+
}

0 commit comments

Comments
 (0)