Skip to content

Commit fe16d12

Browse files
committed
fix(mobile): improve authentication UX and iPad layout
Authentication improvements: - Clear error messages when switching between sign-in/sign-up modes - Display sign-up errors in UI with field-specific highlighting - Add password validation (8+ chars, strength requirements) - Show generic error messages for security (email taken, account not found) - Highlight both email and password fields for sign-in errors - Add error display to email verification screen with red PIN borders iPad layout fixes: - Constrain PIN verification boxes to 56x56 (prevent stretching) - Limit master password form width to 480px on large screens - Center authentication content on tablets
1 parent efa2cb3 commit fe16d12

File tree

3 files changed

+140
-25
lines changed

3 files changed

+140
-25
lines changed

apps/mobile/v1/src/components/MasterPasswordDialog/MasterPasswordForm.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ export function MasterPasswordForm({
101101
: 'Enter your master password to decrypt your notes';
102102

103103
return (
104-
<>
104+
<View style={styles.contentWrapper}>
105105
<View style={styles.header}>
106106
<Text style={[styles.title, { color: theme.colors.foreground }]}>{title}</Text>
107107
<Text style={[styles.subtitle, { color: theme.colors.mutedForeground }]}>
@@ -244,6 +244,6 @@ export function MasterPasswordForm({
244244
</Text>
245245
</View>
246246
)}
247-
</>
247+
</View>
248248
);
249249
}

apps/mobile/v1/src/components/MasterPasswordDialog/styles.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,13 @@ export const styles = StyleSheet.create({
1111
flexGrow: 1,
1212
padding: 24,
1313
justifyContent: 'center',
14+
alignItems: 'center',
1415
minHeight: Dimensions.get('window').height - 100,
1516
},
17+
contentWrapper: {
18+
width: '100%',
19+
maxWidth: 480,
20+
},
1621
header: {
1722
alignItems: 'center',
1823
marginBottom: 48,

apps/mobile/v1/src/screens/AuthScreen.tsx

Lines changed: 133 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,10 @@ export default function AuthScreen() {
149149
let message = 'Invalid email or password';
150150
if (originalMessage.toLowerCase().includes('identifier')) {
151151
message = 'Email or password is incorrect';
152+
} else if (originalMessage.toLowerCase().includes('account')) {
153+
message = 'Email or password is incorrect';
152154
} else if (originalMessage) {
153-
message = originalMessage;
155+
message = 'Email or password is incorrect';
154156
}
155157

156158
logger.error('Sign in failed', err, {
@@ -173,6 +175,7 @@ export default function AuthScreen() {
173175
const handleSignUp = async () => {
174176
if (!signUpLoaded) return;
175177
setLoading(true);
178+
setErrorMessage('');
176179

177180
try {
178181
// Create sign up with all required fields including legal acceptance
@@ -195,15 +198,27 @@ export default function AuthScreen() {
195198
});
196199
} catch (err: unknown) {
197200
const error = err as ClerkError;
201+
const originalMessage = error.errors?.[0]?.message || '';
202+
// Replace specific Clerk messages with generic ones
203+
let message = 'Unable to create account';
204+
if (originalMessage.toLowerCase().includes('email') && originalMessage.toLowerCase().includes('taken')) {
205+
message = 'Unable to create account with this email';
206+
} else if (originalMessage.toLowerCase().includes('password') && originalMessage.toLowerCase().includes('strong')) {
207+
message = 'Password must contain uppercase, lowercase, number, and special character';
208+
} else if (originalMessage) {
209+
message = 'Unable to create account';
210+
}
211+
198212
logger.error('Sign up failed', err, {
199213
attributes: {
200214
email,
201215
firstName,
202216
lastName,
203-
errorMessage: error.errors?.[0]?.message,
217+
errorMessage: originalMessage,
204218
},
205219
});
206-
showToast(error.errors?.[0]?.message || 'Unable to create account');
220+
setErrorMessage(message);
221+
showToast(message);
207222
} finally {
208223
setLoading(false);
209224
}
@@ -216,6 +231,7 @@ export default function AuthScreen() {
216231
const handleVerifyEmail = async () => {
217232
if (!signUpLoaded) return;
218233
setLoading(true);
234+
setErrorMessage('');
219235

220236
try {
221237
// Try multiple approaches to set legal acceptance (fallback for different Clerk configurations)
@@ -249,42 +265,73 @@ export default function AuthScreen() {
249265
email,
250266
});
251267
} else {
268+
const message = 'Unable to complete sign up. Please try again.';
252269
logger.warn('Sign up incomplete after verification', {
253270
attributes: {
254271
status: completeSignUp.status,
255272
missingFields: completeSignUp.missingFields,
256273
email,
257274
},
258275
});
259-
showToast('Unable to complete sign up. Check Clerk Dashboard settings.');
276+
setErrorMessage(message);
277+
showToast(message);
260278
}
261279
} catch (err: unknown) {
262280
const error = err as ClerkError;
281+
const message = error.errors?.[0]?.message || 'Invalid verification code';
263282
logger.error('Email verification failed', err, {
264283
attributes: {
265284
email,
266-
errorMessage: error.errors?.[0]?.message,
285+
errorMessage: message,
267286
},
268287
});
269-
showToast(error.errors?.[0]?.message || 'Invalid code');
288+
setErrorMessage(message);
289+
showToast(message);
270290
} finally {
271291
setLoading(false);
272292
}
273293
};
274294

275295
const handleSubmit = () => {
276-
if (!email.trim() || !password.trim()) {
277-
showToast('Please fill in all fields');
296+
if (isSignUp && !firstName.trim()) {
297+
const message = 'Please enter your first name';
298+
setErrorMessage(message);
299+
showToast(message);
278300
return;
279301
}
280302

281-
if (isSignUp && (!firstName.trim() || !lastName.trim())) {
282-
showToast('Please enter your first and last name');
303+
if (isSignUp && !lastName.trim()) {
304+
const message = 'Please enter your last name';
305+
setErrorMessage(message);
306+
showToast(message);
307+
return;
308+
}
309+
310+
if (!email.trim()) {
311+
const message = 'Please enter your email';
312+
setErrorMessage(message);
313+
showToast(message);
314+
return;
315+
}
316+
317+
if (!password.trim()) {
318+
const message = 'Please enter your password';
319+
setErrorMessage(message);
320+
showToast(message);
321+
return;
322+
}
323+
324+
if (isSignUp && password.length < 8) {
325+
const message = 'Password must be at least 8 characters';
326+
setErrorMessage(message);
327+
showToast(message);
283328
return;
284329
}
285330

286331
if (isSignUp && !acceptedTerms) {
287-
showToast('Please accept the Terms of Service and Privacy Policy');
332+
const message = 'Please accept the Terms of Service and Privacy Policy';
333+
setErrorMessage(message);
334+
showToast(message);
288335
return;
289336
}
290337

@@ -368,7 +415,7 @@ export default function AuthScreen() {
368415
style={[
369416
styles.pinBox,
370417
{
371-
borderColor: isFocused ? theme.colors.primary : theme.colors.border,
418+
borderColor: errorMessage ? '#ef4444' : (isFocused ? theme.colors.primary : theme.colors.border),
372419
backgroundColor: theme.colors.background,
373420
}
374421
]}
@@ -385,14 +432,23 @@ export default function AuthScreen() {
385432
<TextInput
386433
ref={passwordRef}
387434
value={verificationCode}
388-
onChangeText={(text) => setVerificationCode(text.replace(/[^0-9]/g, '').slice(0, 6))}
435+
onChangeText={(text) => {
436+
setVerificationCode(text.replace(/[^0-9]/g, '').slice(0, 6));
437+
if (errorMessage) setErrorMessage('');
438+
}}
389439
keyboardType="number-pad"
390440
maxLength={6}
391441
autoFocus
392442
style={styles.hiddenInput}
393443
/>
394444
</TouchableOpacity>
395445

446+
{errorMessage ? (
447+
<Text style={[styles.errorText, { color: '#ef4444', textAlign: 'center', marginTop: -24, marginBottom: 16 }]}>
448+
{errorMessage}
449+
</Text>
450+
) : null}
451+
396452
<Button
397453
onPress={handleVerifyEmail}
398454
disabled={loading || verificationCode.length !== 6}
@@ -406,6 +462,7 @@ export default function AuthScreen() {
406462
onPress={() => {
407463
setPendingVerification(false);
408464
setVerificationCode('');
465+
setErrorMessage('');
409466
}}
410467
style={styles.switchButton}
411468
>
@@ -422,21 +479,39 @@ export default function AuthScreen() {
422479
<Input
423480
placeholder="Enter your first name"
424481
value={firstName}
425-
onChangeText={setFirstName}
482+
onChangeText={(text) => {
483+
setFirstName(text);
484+
if (errorMessage) setErrorMessage('');
485+
}}
426486
autoCapitalize="words"
427487
autoComplete="name-given"
488+
style={(errorMessage && errorMessage.toLowerCase().includes('first name')) ? { borderColor: '#ef4444' } : undefined}
428489
/>
490+
{errorMessage && errorMessage.toLowerCase().includes('first name') ? (
491+
<Text style={[styles.errorText, { color: '#ef4444' }]}>
492+
{errorMessage}
493+
</Text>
494+
) : null}
429495
</View>
430496

431497
<View style={styles.inputContainer}>
432498
<Text style={[styles.label, { color: theme.colors.foreground }]}>Last Name</Text>
433499
<Input
434500
placeholder="Enter your last name"
435501
value={lastName}
436-
onChangeText={setLastName}
502+
onChangeText={(text) => {
503+
setLastName(text);
504+
if (errorMessage) setErrorMessage('');
505+
}}
437506
autoCapitalize="words"
438507
autoComplete="name-family"
508+
style={(errorMessage && errorMessage.toLowerCase().includes('last name')) ? { borderColor: '#ef4444' } : undefined}
439509
/>
510+
{errorMessage && errorMessage.toLowerCase().includes('last name') ? (
511+
<Text style={[styles.errorText, { color: '#ef4444' }]}>
512+
{errorMessage}
513+
</Text>
514+
) : null}
440515
</View>
441516
</>
442517
)}
@@ -446,11 +521,28 @@ export default function AuthScreen() {
446521
<Input
447522
placeholder="Enter your email"
448523
value={email}
449-
onChangeText={setEmail}
524+
onChangeText={(text) => {
525+
setEmail(text);
526+
if (errorMessage) setErrorMessage('');
527+
}}
450528
keyboardType="email-address"
451529
autoCapitalize="none"
452530
autoComplete="email"
531+
style={(errorMessage && (
532+
(isSignUp && (errorMessage.toLowerCase().includes('email') || errorMessage.toLowerCase().includes('account'))) ||
533+
errorMessage.toLowerCase() === 'please enter your email' ||
534+
(!isSignUp && (errorMessage.toLowerCase().includes('invalid') || errorMessage.toLowerCase().includes('incorrect')))
535+
)) ? { borderColor: '#ef4444' } : undefined}
453536
/>
537+
{errorMessage && (
538+
(isSignUp && (errorMessage.toLowerCase().includes('email') || errorMessage.toLowerCase().includes('account'))) ||
539+
errorMessage.toLowerCase() === 'please enter your email' ||
540+
(!isSignUp && (errorMessage.toLowerCase().includes('invalid') || errorMessage.toLowerCase().includes('incorrect')))
541+
) ? (
542+
<Text style={[styles.errorText, { color: '#ef4444' }]}>
543+
{errorMessage}
544+
</Text>
545+
) : null}
454546
</View>
455547

456548
{!isForgotPassword && (
@@ -462,7 +554,12 @@ export default function AuthScreen() {
462554
style={[
463555
styles.passwordInput,
464556
{
465-
borderColor: errorMessage ? '#ef4444' : theme.colors.input,
557+
borderColor: (errorMessage && (
558+
errorMessage.toLowerCase().includes('password') ||
559+
errorMessage.toLowerCase().includes('characters') ||
560+
errorMessage.toLowerCase().includes('contain') ||
561+
(!isSignUp && (errorMessage.toLowerCase().includes('invalid') || errorMessage.toLowerCase().includes('incorrect')))
562+
)) ? '#ef4444' : theme.colors.input,
466563
color: theme.colors.foreground,
467564
backgroundColor: theme.colors.background,
468565
}
@@ -493,7 +590,11 @@ export default function AuthScreen() {
493590
/>
494591
</TouchableOpacity>
495592
</View>
496-
{errorMessage ? (
593+
{errorMessage && (
594+
errorMessage.toLowerCase().includes('password') ||
595+
errorMessage.toLowerCase().includes('characters') ||
596+
errorMessage.toLowerCase().includes('contain')
597+
) ? (
497598
<Text style={[styles.errorText, { color: '#ef4444' }]}>
498599
{errorMessage}
499600
</Text>
@@ -503,7 +604,10 @@ export default function AuthScreen() {
503604

504605
{!isSignUp && !isForgotPassword && (
505606
<TouchableOpacity
506-
onPress={() => setIsForgotPassword(true)}
607+
onPress={() => {
608+
setIsForgotPassword(true);
609+
setErrorMessage('');
610+
}}
507611
style={styles.forgotPasswordButton}
508612
>
509613
<Text style={[styles.forgotPasswordText, { color: theme.colors.primary }]}>
@@ -559,15 +663,21 @@ export default function AuthScreen() {
559663
{isForgotPassword ? (
560664
<Button
561665
variant="ghost"
562-
onPress={() => setIsForgotPassword(false)}
666+
onPress={() => {
667+
setIsForgotPassword(false);
668+
setErrorMessage('');
669+
}}
563670
style={styles.switchButton}
564671
>
565672
Back to Sign In
566673
</Button>
567674
) : (
568675
<Button
569676
variant="ghost"
570-
onPress={() => setIsSignUp(!isSignUp)}
677+
onPress={() => {
678+
setIsSignUp(!isSignUp);
679+
setErrorMessage('');
680+
}}
571681
style={styles.switchButton}
572682
>
573683
{isSignUp ? 'Already have an account? Sign In' : "Don't have an account? Sign Up"}
@@ -705,13 +815,13 @@ const styles = StyleSheet.create({
705815
},
706816
pinContainer: {
707817
flexDirection: 'row',
708-
justifyContent: 'space-between',
818+
justifyContent: 'center',
709819
marginBottom: 32,
710820
marginTop: 24,
711821
gap: 8,
712822
},
713823
pinBox: {
714-
flex: 1,
824+
width: 56,
715825
height: 56,
716826
borderWidth: 2,
717827
borderRadius: 12,

0 commit comments

Comments
 (0)