181 lines
4.9 KiB
TypeScript
181 lines
4.9 KiB
TypeScript
import React, { useState } from 'react';
|
|
import {
|
|
IonContent,
|
|
IonPage,
|
|
IonInput,
|
|
IonButton,
|
|
IonSpinner,
|
|
IonIcon,
|
|
} from '@ionic/react';
|
|
import { chevronBackOutline } from 'ionicons/icons';
|
|
import { useHistory } from 'react-router-dom';
|
|
import { supabase } from '../supabase';
|
|
|
|
const NewPasswordPage: React.FC = () => {
|
|
const [password, setPassword] = useState('');
|
|
const [confirmPassword, setConfirmPassword] = useState('');
|
|
const [submitting, setSubmitting] = useState(false);
|
|
const [message, setMessage] = useState<string | null>(null);
|
|
|
|
const history = useHistory();
|
|
|
|
const showMessage = (msg: string) => {
|
|
setMessage(msg);
|
|
setTimeout(() => setMessage(null), 4000);
|
|
};
|
|
|
|
const handleUpdate = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
if (!password || !confirmPassword) {
|
|
showMessage('Please fill in all fields');
|
|
return;
|
|
}
|
|
|
|
if (password.length < 6) {
|
|
showMessage('Password must be at least 6 characters');
|
|
return;
|
|
}
|
|
|
|
if (password !== confirmPassword) {
|
|
showMessage('Passwords do not match');
|
|
return;
|
|
}
|
|
|
|
setSubmitting(true);
|
|
setMessage(null);
|
|
|
|
try {
|
|
const { error } = await supabase.auth.updateUser({ password });
|
|
|
|
if (error) throw error;
|
|
|
|
history.push('/home');
|
|
} catch (err: any) {
|
|
showMessage(err.message || 'An error occurred');
|
|
} finally {
|
|
setSubmitting(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<IonPage>
|
|
<IonContent
|
|
fullscreen
|
|
style={{
|
|
'--background':
|
|
'linear-gradient(160deg, rgba(199,210,254,0.52) 0%, #f0f4ff 58%, #f8fafc 100%)',
|
|
}}
|
|
>
|
|
<div className="auth-shell">
|
|
<IonButton
|
|
fill="clear"
|
|
color="dark"
|
|
onClick={() => history.push('/auth')}
|
|
style={{
|
|
position: 'absolute',
|
|
top: 'calc(var(--ion-safe-area-top, 0px) + 8px)',
|
|
left: '8px',
|
|
'--padding-start': '8px',
|
|
'--padding-end': '8px',
|
|
zIndex: 10,
|
|
}}
|
|
>
|
|
<div
|
|
style={{
|
|
background: '#ffffff',
|
|
width: '44px',
|
|
height: '44px',
|
|
borderRadius: '12px',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
}}
|
|
>
|
|
<IonIcon icon={chevronBackOutline} />
|
|
</div>
|
|
</IonButton>
|
|
|
|
<div style={{ marginTop: '80px' }}>
|
|
<h1
|
|
className="bold"
|
|
style={{ fontSize: '24px', margin: '0 0 8px 0' }}
|
|
>
|
|
Create new password
|
|
</h1>
|
|
<p
|
|
className="secondary-text"
|
|
style={{ margin: '0', fontSize: '14px' }}
|
|
>
|
|
Set a strong password to protect your account.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="auth-card">
|
|
<form
|
|
onSubmit={handleUpdate}
|
|
style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}
|
|
>
|
|
<div className="input-wrapper">
|
|
<IonInput
|
|
label="New Password"
|
|
labelPlacement="floating"
|
|
type="password"
|
|
placeholder="Min. 6 characters"
|
|
value={password}
|
|
onIonChange={(e) => setPassword(e.detail.value!)}
|
|
/>
|
|
</div>
|
|
|
|
<div className="input-wrapper">
|
|
<IonInput
|
|
label="Confirm Password"
|
|
labelPlacement="floating"
|
|
type="password"
|
|
placeholder="Repeat your password"
|
|
value={confirmPassword}
|
|
onIonChange={(e) => setConfirmPassword(e.detail.value!)}
|
|
/>
|
|
</div>
|
|
|
|
{message && (
|
|
<div
|
|
style={{
|
|
background: 'var(--color-danger-soft)',
|
|
color: 'var(--ion-color-danger-shade)',
|
|
padding: '12px',
|
|
borderRadius: '12px',
|
|
fontSize: '13px',
|
|
textAlign: 'center',
|
|
}}
|
|
>
|
|
{message}
|
|
</div>
|
|
)}
|
|
|
|
<IonButton
|
|
type="submit"
|
|
expand="block"
|
|
className="bold"
|
|
style={{
|
|
height: '52px',
|
|
'--border-radius': 'var(--radius-pill)',
|
|
marginTop: '8px',
|
|
}}
|
|
disabled={submitting}
|
|
>
|
|
{submitting ? (
|
|
<IonSpinner name="crescent" />
|
|
) : (
|
|
'Update password'
|
|
)}
|
|
</IonButton>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</IonContent>
|
|
</IonPage>
|
|
);
|
|
};
|
|
|
|
export default NewPasswordPage;
|