Spaces:
Running
Running
| import React, { useState } from 'react'; | |
| import { motion, AnimatePresence } from 'framer-motion'; | |
| import { useAuth } from '../context/AuthContext'; | |
| import { GoogleLogin } from '@react-oauth/google'; | |
| import { useNavigate } from 'react-router-dom'; | |
| import { useTranslation } from 'react-i18next'; | |
| const AuthPage = () => { | |
| const { t } = useTranslation(); | |
| const [isLogin, setIsLogin] = useState(true); | |
| const [email, setEmail] = useState(''); | |
| const [password, setPassword] = useState(''); | |
| const [confirmPassword, setConfirmPassword] = useState(''); | |
| const [displayName, setDisplayName] = useState(''); | |
| const [error, setError] = useState(''); | |
| const [isSubmitting, setIsSubmitting] = useState(false); | |
| const { user, login, register, googleLogin } = useAuth(); | |
| const navigate = useNavigate(); | |
| React.useEffect(() => { | |
| if (user) { | |
| navigate('/dashboard'); | |
| } | |
| }, [user, navigate]); | |
| const handleSubmit = async (e) => { | |
| e.preventDefault(); | |
| setError(''); | |
| if (!isLogin && password !== confirmPassword) { | |
| setError(t('auth.passwords_do_not_match', 'Passwords do not match')); | |
| return; | |
| } | |
| setIsSubmitting(true); | |
| let result; | |
| if (isLogin) { | |
| result = await login(email, password); | |
| } else { | |
| result = await register(email, password, displayName); | |
| } | |
| if (result.success) { | |
| navigate('/dashboard'); | |
| } else { | |
| setError(result.message); | |
| } | |
| setIsSubmitting(false); | |
| }; | |
| const handleGoogleSuccess = async (credentialResponse) => { | |
| setIsSubmitting(true); | |
| const result = await googleLogin(credentialResponse.credential); | |
| if (result.success) { | |
| navigate('/dashboard'); | |
| } else { | |
| setError(result.message); | |
| } | |
| setIsSubmitting(false); | |
| }; | |
| return ( | |
| <div className="min-h-[80vh] flex items-center justify-center py-12 px-4"> | |
| <div className="w-full max-w-md relative"> | |
| {/* Decorative Scribbles */} | |
| <motion.div | |
| initial={{ opacity: 0 }} | |
| animate={{ opacity: 0.1 }} | |
| className="absolute -top-12 -left-12 pointer-events-none" | |
| > | |
| <svg width="150" height="150" viewBox="0 0 100 100"> | |
| <path d="M10,10 Q50,0 90,10 T80,90 T10,80 Z" fill="none" stroke="currentColor" strokeWidth="1" /> | |
| </svg> | |
| </motion.div> | |
| <motion.div | |
| initial={{ opacity: 0, y: 20 }} | |
| animate={{ opacity: 1, y: 0 }} | |
| className="bg-surface-container-lowest p-8 md:p-10 rough-border rough-shadow relative z-10" | |
| > | |
| <div className="text-center mb-8"> | |
| <h1 className="text-5xl font-display-lg text-primary mb-2"> | |
| {isLogin ? t('auth.welcome_back', 'Welcome Back!') : t('auth.start_flow', 'Start Your Flow')} | |
| </h1> | |
| <p className="font-accent-note text-2xl text-secondary -rotate-1"> | |
| {isLogin ? t('auth.plans_waiting', 'Your plans are waiting for you.') : t('auth.join_revolution', 'Join the visual revolution.')} | |
| </p> | |
| </div> | |
| {error && ( | |
| <div className="mb-6 p-4 bg-error-container text-error text-sm font-bold border-2 border-error rounded-lg"> | |
| {error} | |
| </div> | |
| )} | |
| <form onSubmit={handleSubmit} className="flex flex-col gap-6"> | |
| {!isLogin && ( | |
| <div className="flex flex-col gap-2"> | |
| <label className="text-sm font-bold uppercase tracking-widest text-on-surface-variant">{t('auth.full_name', 'Full Name')}</label> | |
| <input | |
| type="text" | |
| value={displayName} | |
| onChange={(e) => setDisplayName(e.target.value)} | |
| className="w-full px-4 py-3 bg-surface-container-lowest border-2 border-primary rounded-lg focus:outline-none focus:ring-2 focus:ring-secondary/50 font-body-md" | |
| placeholder={t('auth.enter_name', 'Enter your name')} | |
| required={!isLogin} | |
| /> | |
| </div> | |
| )} | |
| <div className="flex flex-col gap-2"> | |
| <label className="text-sm font-bold uppercase tracking-widest text-on-surface-variant">{t('auth.email', 'Email')}</label> | |
| <input | |
| type="email" | |
| value={email} | |
| onChange={(e) => setEmail(e.target.value)} | |
| className="w-full px-4 py-3 bg-surface-container-lowest border-2 border-primary rounded-lg focus:outline-none focus:ring-2 focus:ring-secondary/50 font-body-md" | |
| placeholder="your@email.com" | |
| required | |
| /> | |
| </div> | |
| <div className="flex flex-col gap-2"> | |
| <div className="flex justify-between items-center"> | |
| <label className="text-sm font-bold uppercase tracking-widest text-on-surface-variant">{t('auth.password', 'Password')}</label> | |
| {isLogin && ( | |
| <button type="button" className="text-xs font-bold text-secondary hover:underline underline-offset-4">{t('auth.forgot', 'Forgot?')}</button> | |
| )} | |
| </div> | |
| <input | |
| type="password" | |
| value={password} | |
| onChange={(e) => setPassword(e.target.value)} | |
| className="w-full px-4 py-3 bg-surface-container-lowest border-2 border-primary rounded-lg focus:outline-none focus:ring-2 focus:ring-secondary/50 font-body-md" | |
| placeholder="••••••••" | |
| required | |
| /> | |
| </div> | |
| {!isLogin && ( | |
| <div className="flex flex-col gap-2"> | |
| <label className="text-sm font-bold uppercase tracking-widest text-on-surface-variant">{t('auth.confirm_password', 'Confirm Password')}</label> | |
| <input | |
| type="password" | |
| value={confirmPassword} | |
| onChange={(e) => setConfirmPassword(e.target.value)} | |
| className="w-full px-4 py-3 bg-surface-container-lowest border-2 border-primary rounded-lg focus:outline-none focus:ring-2 focus:ring-secondary/50 font-body-md" | |
| placeholder="••••••••" | |
| required={!isLogin} | |
| /> | |
| </div> | |
| )} | |
| <motion.button | |
| whileHover={{ scale: 1.02 }} | |
| whileTap={{ scale: 0.98 }} | |
| disabled={isSubmitting} | |
| className="mt-4 bg-primary text-on-primary py-4 px-6 text-xl font-bold rough-border rough-shadow-hover transition-all disabled:opacity-50" | |
| > | |
| {isSubmitting ? t('auth.processing', 'Processing...') : (isLogin ? t('auth.enter_workspace', 'Enter Workspace') : t('auth.create_account', 'Create Account'))} | |
| </motion.button> | |
| </form> | |
| <div className="mt-8 flex flex-col gap-4"> | |
| <div className="relative flex items-center justify-center"> | |
| <div className="absolute inset-0 flex items-center"> | |
| <div className="w-full border-t border-dashed border-primary/20"></div> | |
| </div> | |
| <span className="relative px-4 bg-surface-container-lowest text-xs font-bold text-on-surface-variant/40 uppercase tracking-widest"> | |
| {t('auth.or_continue', 'Or continue with')} | |
| </span> | |
| </div> | |
| <div className="flex justify-center"> | |
| <GoogleLogin | |
| onSuccess={handleGoogleSuccess} | |
| onError={() => setError(t('auth.google_failed', 'Google Login Failed'))} | |
| theme="outline" | |
| size="large" | |
| width="100%" | |
| /> | |
| </div> | |
| </div> | |
| <div className="mt-10 text-center"> | |
| <button | |
| onClick={() => { | |
| setIsLogin(!isLogin); | |
| setError(''); | |
| setPassword(''); | |
| setConfirmPassword(''); | |
| }} | |
| className="text-lg font-bold text-primary underline underline-offset-8 decoration-secondary decoration-4 hover:text-secondary transition-all font-accent-note" | |
| > | |
| {isLogin ? t('auth.dont_have_account', "Don't have an account? Sign Up") : t('auth.already_member', 'Already a member? Log In')} | |
| </button> | |
| </div> | |
| </motion.div> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| export default AuthPage; | |