2025-06-20 05:23:50 +08:00
|
|
|
import { fontWeights } from '@/constants/Fonts';
|
|
|
|
import { supabase } from '@/constants/SupabaseConfig';
|
2025-08-29 22:42:05 +08:00
|
|
|
import { useTheme } from '@/hooks/useThemeColor';
|
2025-06-20 05:23:50 +08:00
|
|
|
import React, { useState } from 'react';
|
|
|
|
import {
|
|
|
|
Alert,
|
|
|
|
KeyboardAvoidingView,
|
2025-07-02 04:00:56 +08:00
|
|
|
Linking,
|
2025-06-20 05:23:50 +08:00
|
|
|
Modal,
|
|
|
|
Platform,
|
2025-08-29 22:42:05 +08:00
|
|
|
StyleSheet,
|
2025-06-20 05:23:50 +08:00
|
|
|
Text,
|
|
|
|
TextInput,
|
|
|
|
TouchableOpacity,
|
|
|
|
View
|
|
|
|
} from 'react-native';
|
2025-08-29 22:42:05 +08:00
|
|
|
import { Card } from './ui/Card';
|
2025-06-20 05:23:50 +08:00
|
|
|
|
|
|
|
interface AuthOverlayProps {
|
|
|
|
visible: boolean;
|
|
|
|
onClose: () => void;
|
|
|
|
}
|
|
|
|
|
|
|
|
export const AuthOverlay: React.FC<AuthOverlayProps> = ({ visible, onClose }) => {
|
|
|
|
const [email, setEmail] = useState('');
|
|
|
|
const [password, setPassword] = useState('');
|
2025-07-02 04:00:56 +08:00
|
|
|
const [confirmPassword, setConfirmPassword] = useState('');
|
2025-06-20 05:23:50 +08:00
|
|
|
const [loading, setLoading] = useState(false);
|
2025-07-02 04:00:56 +08:00
|
|
|
const [mode, setMode] = useState<'signin' | 'signup' | 'forgot'>('signin');
|
|
|
|
const [showSuccess, setShowSuccess] = useState(false);
|
|
|
|
const [successEmail, setSuccessEmail] = useState('');
|
2025-06-20 05:23:50 +08:00
|
|
|
|
2025-08-29 22:42:05 +08:00
|
|
|
const theme = useTheme();
|
|
|
|
|
|
|
|
const styles = StyleSheet.create({
|
2025-06-20 05:23:50 +08:00
|
|
|
overlay: {
|
|
|
|
flex: 1,
|
|
|
|
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
2025-08-29 22:42:05 +08:00
|
|
|
justifyContent: 'center',
|
|
|
|
alignItems: 'center',
|
2025-06-20 05:23:50 +08:00
|
|
|
padding: 20,
|
|
|
|
},
|
|
|
|
title: {
|
2025-08-29 22:42:05 +08:00
|
|
|
fontSize: 22,
|
2025-06-20 05:23:50 +08:00
|
|
|
fontFamily: fontWeights[600],
|
|
|
|
color: theme.foreground,
|
2025-08-29 22:42:05 +08:00
|
|
|
textAlign: 'center',
|
|
|
|
marginBottom: 16,
|
2025-06-20 05:23:50 +08:00
|
|
|
},
|
|
|
|
input: {
|
|
|
|
borderWidth: 1,
|
|
|
|
borderColor: theme.border,
|
2025-08-29 22:42:05 +08:00
|
|
|
borderRadius: 12,
|
2025-06-20 05:23:50 +08:00
|
|
|
padding: 12,
|
2025-08-29 22:42:05 +08:00
|
|
|
height: 48,
|
|
|
|
marginBottom: 0,
|
2025-06-20 05:23:50 +08:00
|
|
|
fontSize: 16,
|
|
|
|
color: theme.foreground,
|
|
|
|
backgroundColor: theme.background,
|
|
|
|
},
|
|
|
|
button: {
|
|
|
|
backgroundColor: theme.primary,
|
2025-08-29 22:42:05 +08:00
|
|
|
borderRadius: 12,
|
|
|
|
height: 48,
|
|
|
|
alignItems: 'center',
|
|
|
|
justifyContent: 'center',
|
|
|
|
marginTop: 8,
|
|
|
|
marginBottom: 16,
|
2025-06-20 05:23:50 +08:00
|
|
|
},
|
|
|
|
buttonDisabled: {
|
|
|
|
opacity: 0.6,
|
|
|
|
},
|
|
|
|
buttonText: {
|
|
|
|
color: theme.background,
|
|
|
|
fontSize: 16,
|
|
|
|
fontFamily: fontWeights[600],
|
|
|
|
},
|
|
|
|
switchButton: {
|
2025-08-29 22:42:05 +08:00
|
|
|
alignItems: 'center',
|
|
|
|
padding: 6,
|
2025-06-20 05:23:50 +08:00
|
|
|
},
|
|
|
|
switchText: {
|
|
|
|
color: theme.primary,
|
|
|
|
fontSize: 14,
|
|
|
|
fontFamily: fontWeights[500],
|
|
|
|
},
|
|
|
|
closeButton: {
|
2025-08-29 22:42:05 +08:00
|
|
|
position: 'absolute',
|
|
|
|
top: 12,
|
|
|
|
right: 12,
|
|
|
|
padding: 6,
|
|
|
|
zIndex: 1,
|
2025-06-20 05:23:50 +08:00
|
|
|
},
|
|
|
|
closeText: {
|
|
|
|
color: theme.mutedForeground,
|
|
|
|
fontSize: 18,
|
|
|
|
fontFamily: fontWeights[500],
|
|
|
|
},
|
2025-07-02 04:00:56 +08:00
|
|
|
successContainer: {
|
2025-08-29 22:42:05 +08:00
|
|
|
alignItems: 'center',
|
2025-07-02 04:00:56 +08:00
|
|
|
},
|
|
|
|
successIcon: {
|
2025-08-29 22:42:05 +08:00
|
|
|
width: 64,
|
|
|
|
height: 64,
|
|
|
|
borderRadius: 32,
|
2025-07-02 04:00:56 +08:00
|
|
|
backgroundColor: '#10B981',
|
2025-08-29 22:42:05 +08:00
|
|
|
alignItems: 'center',
|
|
|
|
justifyContent: 'center',
|
|
|
|
marginBottom: 16,
|
2025-07-02 04:00:56 +08:00
|
|
|
},
|
|
|
|
successIconText: {
|
2025-08-29 22:42:05 +08:00
|
|
|
fontSize: 28,
|
2025-07-02 04:00:56 +08:00
|
|
|
color: '#ffffff',
|
|
|
|
},
|
|
|
|
successTitle: {
|
2025-08-29 22:42:05 +08:00
|
|
|
fontSize: 24,
|
2025-07-02 04:00:56 +08:00
|
|
|
fontFamily: fontWeights[600],
|
|
|
|
color: theme.foreground,
|
2025-08-29 22:42:05 +08:00
|
|
|
textAlign: 'center',
|
|
|
|
marginBottom: 8,
|
2025-07-02 04:00:56 +08:00
|
|
|
},
|
|
|
|
successDescription: {
|
2025-08-29 22:42:05 +08:00
|
|
|
fontSize: 15,
|
2025-07-02 04:00:56 +08:00
|
|
|
color: theme.mutedForeground,
|
2025-08-29 22:42:05 +08:00
|
|
|
textAlign: 'center',
|
|
|
|
marginBottom: 6,
|
|
|
|
lineHeight: 20,
|
2025-07-02 04:00:56 +08:00
|
|
|
},
|
|
|
|
successEmail: {
|
2025-08-29 22:42:05 +08:00
|
|
|
fontSize: 15,
|
2025-07-02 04:00:56 +08:00
|
|
|
fontFamily: fontWeights[600],
|
|
|
|
color: theme.foreground,
|
2025-08-29 22:42:05 +08:00
|
|
|
textAlign: 'center',
|
|
|
|
marginBottom: 16,
|
2025-07-02 04:00:56 +08:00
|
|
|
},
|
|
|
|
successNote: {
|
|
|
|
backgroundColor: '#10B98120',
|
|
|
|
borderColor: '#10B98140',
|
|
|
|
borderWidth: 1,
|
|
|
|
borderRadius: 8,
|
2025-08-29 22:42:05 +08:00
|
|
|
padding: 12,
|
|
|
|
marginBottom: 20,
|
2025-07-02 04:00:56 +08:00
|
|
|
},
|
|
|
|
successNoteText: {
|
2025-08-29 22:42:05 +08:00
|
|
|
fontSize: 13,
|
2025-07-02 04:00:56 +08:00
|
|
|
color: '#059669',
|
2025-08-29 22:42:05 +08:00
|
|
|
textAlign: 'center',
|
|
|
|
lineHeight: 18,
|
2025-07-02 04:00:56 +08:00
|
|
|
},
|
|
|
|
successButtonContainer: {
|
2025-08-29 22:42:05 +08:00
|
|
|
width: '100%',
|
|
|
|
gap: 8,
|
2025-07-02 04:00:56 +08:00
|
|
|
},
|
|
|
|
legalText: {
|
2025-08-29 22:42:05 +08:00
|
|
|
fontSize: 11,
|
2025-07-02 04:00:56 +08:00
|
|
|
color: theme.mutedForeground,
|
2025-08-29 22:42:05 +08:00
|
|
|
textAlign: 'center',
|
|
|
|
marginTop: 12,
|
|
|
|
lineHeight: 14,
|
|
|
|
paddingHorizontal: 4,
|
2025-07-02 04:00:56 +08:00
|
|
|
},
|
|
|
|
legalLink: {
|
|
|
|
color: theme.primary,
|
|
|
|
},
|
2025-08-29 22:42:05 +08:00
|
|
|
});
|
2025-06-20 05:23:50 +08:00
|
|
|
|
|
|
|
const handleAuth = async () => {
|
2025-07-02 04:00:56 +08:00
|
|
|
if (!email.trim()) {
|
|
|
|
Alert.alert('Error', 'Please enter your email');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mode !== 'forgot' && !password.trim()) {
|
|
|
|
Alert.alert('Error', 'Please enter your password');
|
2025-06-20 05:23:50 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
setLoading(true);
|
|
|
|
|
|
|
|
try {
|
2025-07-02 04:00:56 +08:00
|
|
|
if (mode === 'signup') {
|
|
|
|
if (password !== confirmPassword) {
|
|
|
|
Alert.alert('Error', 'Passwords do not match');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (password.length < 6) {
|
|
|
|
Alert.alert('Error', 'Password must be at least 6 characters');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-06-20 05:23:50 +08:00
|
|
|
const { error } = await supabase.auth.signUp({
|
|
|
|
email: email.trim(),
|
|
|
|
password: password.trim(),
|
|
|
|
});
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
Alert.alert('Sign Up Error', error.message);
|
|
|
|
} else {
|
2025-07-02 04:00:56 +08:00
|
|
|
setSuccessEmail(email.trim());
|
|
|
|
setShowSuccess(true);
|
|
|
|
}
|
|
|
|
} else if (mode === 'forgot') {
|
|
|
|
const { error } = await supabase.auth.resetPasswordForEmail(email.trim());
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
Alert.alert('Error', error.message);
|
|
|
|
} else {
|
|
|
|
Alert.alert('Success', 'Check your email for a password reset link');
|
|
|
|
setMode('signin');
|
2025-06-20 05:23:50 +08:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
const { error } = await supabase.auth.signInWithPassword({
|
|
|
|
email: email.trim(),
|
|
|
|
password: password.trim(),
|
|
|
|
});
|
|
|
|
|
|
|
|
if (error) {
|
2025-07-02 04:00:56 +08:00
|
|
|
if (error.message === 'Email not confirmed') {
|
|
|
|
Alert.alert(
|
|
|
|
'Email Not Confirmed',
|
|
|
|
'Please check your email and click the confirmation link before signing in.',
|
|
|
|
[
|
|
|
|
{ text: 'Cancel', style: 'cancel' },
|
|
|
|
{
|
|
|
|
text: 'Resend Email',
|
|
|
|
onPress: async () => {
|
|
|
|
const { error: resendError } = await supabase.auth.resend({
|
|
|
|
type: 'signup',
|
|
|
|
email: email.trim()
|
|
|
|
});
|
|
|
|
|
|
|
|
if (resendError) {
|
|
|
|
Alert.alert('Error', resendError.message);
|
|
|
|
} else {
|
|
|
|
Alert.alert('Success', 'Confirmation email sent! Please check your inbox.');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
Alert.alert('Sign In Error', error.message);
|
|
|
|
}
|
2025-06-20 05:23:50 +08:00
|
|
|
} else {
|
|
|
|
onClose();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
Alert.alert('Error', 'An unexpected error occurred');
|
|
|
|
} finally {
|
|
|
|
setLoading(false);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const resetForm = () => {
|
|
|
|
setEmail('');
|
|
|
|
setPassword('');
|
2025-07-02 04:00:56 +08:00
|
|
|
setConfirmPassword('');
|
|
|
|
setMode('signin');
|
|
|
|
setShowSuccess(false);
|
|
|
|
setSuccessEmail('');
|
2025-06-20 05:23:50 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
const handleClose = () => {
|
|
|
|
resetForm();
|
|
|
|
onClose();
|
|
|
|
};
|
|
|
|
|
2025-07-02 04:00:56 +08:00
|
|
|
const handleBackToSignIn = () => {
|
|
|
|
setShowSuccess(false);
|
|
|
|
setSuccessEmail('');
|
|
|
|
setMode('signin');
|
|
|
|
setEmail('');
|
|
|
|
setPassword('');
|
|
|
|
setConfirmPassword('');
|
|
|
|
};
|
|
|
|
|
2025-06-20 05:23:50 +08:00
|
|
|
return (
|
|
|
|
<Modal visible={visible} transparent animationType="fade">
|
|
|
|
<KeyboardAvoidingView
|
|
|
|
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
|
|
|
style={styles.overlay}
|
|
|
|
>
|
2025-08-29 22:42:05 +08:00
|
|
|
<Card
|
|
|
|
style={{
|
|
|
|
width: '100%',
|
|
|
|
maxWidth: 400,
|
|
|
|
position: 'relative',
|
|
|
|
padding: 16,
|
|
|
|
gap: 16,
|
|
|
|
}}
|
|
|
|
bordered
|
|
|
|
elevated
|
|
|
|
>
|
2025-06-20 05:23:50 +08:00
|
|
|
<TouchableOpacity style={styles.closeButton} onPress={handleClose}>
|
|
|
|
<Text style={styles.closeText}>✕</Text>
|
|
|
|
</TouchableOpacity>
|
|
|
|
|
2025-07-02 04:00:56 +08:00
|
|
|
{showSuccess ? (
|
|
|
|
<View style={styles.successContainer}>
|
|
|
|
<View style={styles.successIcon}>
|
|
|
|
<Text style={styles.successIconText}>✉</Text>
|
|
|
|
</View>
|
2025-06-20 05:23:50 +08:00
|
|
|
|
2025-07-02 04:00:56 +08:00
|
|
|
<Text style={styles.successTitle}>Check your email</Text>
|
|
|
|
|
|
|
|
<Text style={styles.successDescription}>
|
|
|
|
We've sent a confirmation link to:
|
|
|
|
</Text>
|
|
|
|
|
|
|
|
<Text style={styles.successEmail}>{successEmail}</Text>
|
|
|
|
|
|
|
|
<View style={styles.successNote}>
|
|
|
|
<Text style={styles.successNoteText}>
|
|
|
|
Click the link in the email to activate your account. If you don't see the email, check your spam folder.
|
|
|
|
</Text>
|
|
|
|
</View>
|
|
|
|
|
|
|
|
<View style={styles.successButtonContainer}>
|
|
|
|
<TouchableOpacity style={styles.button} onPress={handleBackToSignIn}>
|
|
|
|
<Text style={styles.buttonText}>Back to Sign In</Text>
|
|
|
|
</TouchableOpacity>
|
|
|
|
|
|
|
|
<TouchableOpacity
|
2025-08-29 22:42:05 +08:00
|
|
|
style={[styles.button, { backgroundColor: 'transparent', borderWidth: 1, borderColor: theme.primary }]}
|
2025-07-02 04:00:56 +08:00
|
|
|
onPress={handleClose}
|
|
|
|
>
|
2025-08-29 22:42:05 +08:00
|
|
|
<Text style={[styles.buttonText, { color: theme.primary }]}>Close</Text>
|
2025-07-02 04:00:56 +08:00
|
|
|
</TouchableOpacity>
|
|
|
|
</View>
|
|
|
|
</View>
|
|
|
|
) : (
|
|
|
|
<>
|
|
|
|
<Text style={styles.title}>
|
|
|
|
{mode === 'signup' ? 'Create Account' : mode === 'forgot' ? 'Reset Password' : 'Sign In'}
|
|
|
|
</Text>
|
|
|
|
|
|
|
|
<TextInput
|
|
|
|
style={styles.input}
|
|
|
|
placeholder="Email"
|
2025-08-29 22:42:05 +08:00
|
|
|
placeholderTextColor={theme.mutedForeground}
|
2025-07-02 04:00:56 +08:00
|
|
|
value={email}
|
|
|
|
onChangeText={setEmail}
|
|
|
|
keyboardType="email-address"
|
|
|
|
autoCapitalize="none"
|
|
|
|
autoCorrect={false}
|
|
|
|
/>
|
|
|
|
|
|
|
|
{mode !== 'forgot' && (
|
|
|
|
<TextInput
|
|
|
|
style={styles.input}
|
|
|
|
placeholder="Password"
|
2025-08-29 22:42:05 +08:00
|
|
|
placeholderTextColor={theme.mutedForeground}
|
2025-07-02 04:00:56 +08:00
|
|
|
value={password}
|
|
|
|
onChangeText={setPassword}
|
|
|
|
secureTextEntry
|
|
|
|
autoCapitalize="none"
|
|
|
|
autoCorrect={false}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
|
|
|
|
{mode === 'signup' && (
|
|
|
|
<TextInput
|
|
|
|
style={styles.input}
|
|
|
|
placeholder="Confirm Password"
|
2025-08-29 22:42:05 +08:00
|
|
|
placeholderTextColor={theme.mutedForeground}
|
2025-07-02 04:00:56 +08:00
|
|
|
value={confirmPassword}
|
|
|
|
onChangeText={setConfirmPassword}
|
|
|
|
secureTextEntry
|
|
|
|
autoCapitalize="none"
|
|
|
|
autoCorrect={false}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
|
|
|
|
<TouchableOpacity
|
|
|
|
style={[styles.button, loading && styles.buttonDisabled]}
|
|
|
|
onPress={handleAuth}
|
|
|
|
disabled={loading}
|
|
|
|
>
|
|
|
|
<Text style={styles.buttonText}>
|
|
|
|
{loading ? 'Loading...' : mode === 'signup' ? 'Sign Up' : mode === 'forgot' ? 'Send Reset Link' : 'Sign In'}
|
|
|
|
</Text>
|
|
|
|
</TouchableOpacity>
|
|
|
|
|
|
|
|
{mode === 'signin' && (
|
|
|
|
<TouchableOpacity
|
|
|
|
style={styles.switchButton}
|
|
|
|
onPress={() => setMode('forgot')}
|
|
|
|
>
|
|
|
|
<Text style={styles.switchText}>Forgot Password?</Text>
|
|
|
|
</TouchableOpacity>
|
|
|
|
)}
|
|
|
|
|
|
|
|
<TouchableOpacity
|
|
|
|
style={styles.switchButton}
|
|
|
|
onPress={() => setMode(mode === 'signup' ? 'signin' : 'signup')}
|
|
|
|
>
|
|
|
|
<Text style={styles.switchText}>
|
|
|
|
{mode === 'signup'
|
|
|
|
? 'Already have an account? Sign In'
|
|
|
|
: mode === 'forgot'
|
|
|
|
? 'Back to Sign In'
|
|
|
|
: 'Need an account? Sign Up'}
|
|
|
|
</Text>
|
|
|
|
</TouchableOpacity>
|
|
|
|
|
|
|
|
<View>
|
|
|
|
<Text style={styles.legalText}>
|
|
|
|
By continuing, you agree to our{' '}
|
|
|
|
<Text
|
|
|
|
style={styles.legalLink}
|
|
|
|
onPress={() => Linking.openURL('https://www.suna.so/legal?tab=terms')}
|
|
|
|
>
|
|
|
|
Terms of Service
|
|
|
|
</Text>
|
|
|
|
{' '}and{' '}
|
|
|
|
<Text
|
|
|
|
style={styles.legalLink}
|
|
|
|
onPress={() => Linking.openURL('https://www.suna.so/legal?tab=privacy')}
|
|
|
|
>
|
|
|
|
Privacy Policy
|
|
|
|
</Text>
|
|
|
|
</Text>
|
|
|
|
</View>
|
|
|
|
</>
|
|
|
|
)}
|
2025-08-29 22:42:05 +08:00
|
|
|
</Card>
|
2025-06-20 05:23:50 +08:00
|
|
|
</KeyboardAvoidingView>
|
|
|
|
</Modal>
|
|
|
|
);
|
|
|
|
};
|