initial
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Alert, Pressable, StyleSheet, Text } from 'react-native';
|
||||
import { useLocalSearchParams, useRouter } from 'expo-router';
|
||||
import { api } from '@/api';
|
||||
import { useT } from '@/i18n';
|
||||
import { AuthShell } from '@/components/AuthShell';
|
||||
import { Button, Field, TextField } from '@/components/ui';
|
||||
import { CODE_LENGTH, CodeField } from '@/components/CodeField';
|
||||
import { colors, fontSize, spacing } from '@/theme';
|
||||
|
||||
const MIN_PASSWORD = 8;
|
||||
|
||||
export default function ResetPasswordScreen() {
|
||||
const { email } = useLocalSearchParams<{ email: string }>();
|
||||
const router = useRouter();
|
||||
const t = useT();
|
||||
|
||||
const [code, setCode] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [showPass, setShowPass] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
async function onSubmit() {
|
||||
if (code.length < CODE_LENGTH) return setError(t('verify.invalid'));
|
||||
if (password.length < MIN_PASSWORD) {
|
||||
return setError(t('register.shortPassword', { count: MIN_PASSWORD }));
|
||||
}
|
||||
|
||||
setError(null);
|
||||
setLoading(true);
|
||||
try {
|
||||
await api.auth.resetPassword(String(email), code, password);
|
||||
// Every session was revoked server-side, so signing in again is the only
|
||||
// way forward.
|
||||
Alert.alert(t('reset.doneTitle'), t('reset.doneBody'), [
|
||||
{ text: t('login.signIn'), onPress: () => router.replace('/login') },
|
||||
]);
|
||||
} catch (e: any) {
|
||||
setError(e?.code === 'code_expired' ? t('verify.expired') : t('verify.invalid'));
|
||||
setCode('');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthShell
|
||||
title={t('reset.title')}
|
||||
subtitle={t('verify.subtitle', { email: String(email ?? '') })}
|
||||
back
|
||||
error={error}
|
||||
>
|
||||
<Field label={t('verify.code')}>
|
||||
<CodeField value={code} onChange={setCode} autoFocus />
|
||||
</Field>
|
||||
|
||||
<Field label={t('reset.newPassword')}>
|
||||
<TextField
|
||||
icon="lock-closed-outline"
|
||||
value={password}
|
||||
onChangeText={setPassword}
|
||||
placeholder={t('register.passwordHint', { count: MIN_PASSWORD })}
|
||||
secureTextEntry={!showPass}
|
||||
autoCapitalize="none"
|
||||
textContentType="newPassword"
|
||||
/>
|
||||
<Pressable onPress={() => setShowPass((v) => !v)} style={styles.showPass} hitSlop={8}>
|
||||
<Text style={styles.link}>{showPass ? t('login.hide') : t('login.show')}</Text>
|
||||
</Pressable>
|
||||
</Field>
|
||||
|
||||
<Button
|
||||
title={t('reset.submit')}
|
||||
size="lg"
|
||||
block
|
||||
loading={loading}
|
||||
onPress={onSubmit}
|
||||
style={{ marginTop: spacing[2] }}
|
||||
/>
|
||||
</AuthShell>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
showPass: { position: 'absolute', right: 14, top: 40 },
|
||||
link: { color: colors.teal700, fontWeight: '600', fontSize: fontSize.sm },
|
||||
});
|
||||
Reference in New Issue
Block a user