204 lines
5.5 KiB
TypeScript
204 lines
5.5 KiB
TypeScript
import React, { useState } from 'react';
|
|
import {
|
|
IonButton,
|
|
IonContent,
|
|
IonHeader,
|
|
IonInput,
|
|
IonItem,
|
|
IonLabel,
|
|
IonPage,
|
|
IonText,
|
|
IonTitle,
|
|
IonToolbar,
|
|
useIonViewWillEnter,
|
|
} from '@ionic/react';
|
|
import { useHistory } from 'react-router-dom';
|
|
import { supabase } from '../supabase';
|
|
import { setStatusBarStyle, Style } from '../utils/statusBar';
|
|
|
|
const Auth: React.FC = () => {
|
|
const history = useHistory();
|
|
const [mode, setMode] = useState<'login' | 'signup'>('login');
|
|
const [email, setEmail] = useState('');
|
|
const [password, setPassword] = useState('');
|
|
const [message, setMessage] = useState('');
|
|
const [errorMessage, setErrorMessage] = useState('');
|
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
|
|
useIonViewWillEnter(() => {
|
|
setStatusBarStyle(Style.Light);
|
|
});
|
|
|
|
const showError = (msg: string) => {
|
|
setErrorMessage(msg);
|
|
window.setTimeout(() => setErrorMessage(''), 4000);
|
|
};
|
|
|
|
const showMessage = (msg: string) => {
|
|
setMessage(msg);
|
|
window.setTimeout(() => setMessage(''), 4000);
|
|
};
|
|
|
|
const normaliseAuthError = (msg: string) => {
|
|
const lowered = msg.toLowerCase();
|
|
|
|
if (lowered.includes('email not confirmed')) {
|
|
return 'Please confirm your email before signing in. Check your inbox for the verification email.';
|
|
}
|
|
|
|
if (lowered.includes('invalid login credentials')) {
|
|
return 'Incorrect email or password. Please try again.';
|
|
}
|
|
|
|
if (lowered.includes('user already registered')) {
|
|
return 'An account with this email already exists. Please sign in instead.';
|
|
}
|
|
|
|
return msg;
|
|
};
|
|
|
|
const handleResendConfirmation = async () => {
|
|
if (!email.trim()) {
|
|
showError(
|
|
'Enter your email address first so we can resend the confirmation email.'
|
|
);
|
|
return;
|
|
}
|
|
|
|
setIsSubmitting(true);
|
|
const { error } = await supabase.auth.resend({
|
|
type: 'signup',
|
|
email: email.trim(),
|
|
});
|
|
setIsSubmitting(false);
|
|
|
|
if (error) {
|
|
showError(normaliseAuthError(error.message));
|
|
return;
|
|
}
|
|
|
|
showMessage('Confirmation email sent. Please check your inbox.');
|
|
};
|
|
|
|
const handleSubmit = async () => {
|
|
if (!email.trim() || password.trim().length < 6) {
|
|
showError('Enter a valid email and password with at least 6 characters.');
|
|
return;
|
|
}
|
|
|
|
setIsSubmitting(true);
|
|
if (mode === 'login') {
|
|
const { error } = await supabase.auth.signInWithPassword({
|
|
email: email.trim(),
|
|
password,
|
|
});
|
|
setIsSubmitting(false);
|
|
if (error) {
|
|
showError(normaliseAuthError(error.message));
|
|
return;
|
|
}
|
|
history.replace('/home');
|
|
return;
|
|
}
|
|
|
|
const { data, error } = await supabase.auth.signUp({
|
|
email: email.trim(),
|
|
password,
|
|
});
|
|
setIsSubmitting(false);
|
|
if (error) {
|
|
showError(normaliseAuthError(error.message));
|
|
return;
|
|
}
|
|
|
|
if (data.session) {
|
|
history.replace('/home');
|
|
return;
|
|
}
|
|
|
|
showMessage('Account created! Check your email for a verification link.');
|
|
};
|
|
|
|
return (
|
|
<IonPage>
|
|
<IonHeader className="ion-no-border">
|
|
<IonToolbar>
|
|
<IonTitle>
|
|
{mode === 'login' ? 'Welcome back' : 'Create account'}
|
|
</IonTitle>
|
|
</IonToolbar>
|
|
</IonHeader>
|
|
<IonContent className="ion-padding auth-page">
|
|
<div className="auth-card">
|
|
<h1>
|
|
{mode === 'login'
|
|
? 'Sign in to continue'
|
|
: 'Create your shopping account'}
|
|
</h1>
|
|
<p className="muted-text">
|
|
Save your orders, addresses, and track deliveries securely.
|
|
</p>
|
|
|
|
{errorMessage && (
|
|
<IonText color="danger">
|
|
<p>{errorMessage}</p>
|
|
</IonText>
|
|
)}
|
|
{message && (
|
|
<IonText color="success">
|
|
<p>{message}</p>
|
|
</IonText>
|
|
)}
|
|
|
|
<div className="auth-form">
|
|
<IonItem lines="none" className="auth-input-item">
|
|
<IonLabel position="stacked">Email</IonLabel>
|
|
<IonInput
|
|
value={email}
|
|
type="email"
|
|
onIonInput={(e) => setEmail(e.detail.value ?? '')}
|
|
/>
|
|
</IonItem>
|
|
<IonItem lines="none" className="auth-input-item">
|
|
<IonLabel position="stacked">Password</IonLabel>
|
|
<IonInput
|
|
value={password}
|
|
type="password"
|
|
onIonInput={(e) => setPassword(e.detail.value ?? '')}
|
|
/>
|
|
</IonItem>
|
|
<IonButton
|
|
expand="block"
|
|
onClick={handleSubmit}
|
|
disabled={isSubmitting}
|
|
>
|
|
{mode === 'login' ? 'Sign in' : 'Create account'}
|
|
</IonButton>
|
|
{mode === 'login' && (
|
|
<IonButton
|
|
fill="clear"
|
|
expand="block"
|
|
onClick={handleResendConfirmation}
|
|
disabled={isSubmitting}
|
|
>
|
|
Resend confirmation email
|
|
</IonButton>
|
|
)}
|
|
<IonButton
|
|
fill="clear"
|
|
expand="block"
|
|
onClick={() => setMode(mode === 'login' ? 'signup' : 'login')}
|
|
>
|
|
{mode === 'login'
|
|
? 'Need an account? Sign up'
|
|
: 'Already have an account? Sign in'}
|
|
</IonButton>
|
|
</div>
|
|
</div>
|
|
</IonContent>
|
|
</IonPage>
|
|
);
|
|
};
|
|
|
|
export default Auth;
|