Full-stack marketplace for buying/selling second-hand items. React 19 + TypeScript + Tailwind CSS v4 frontend with 17 screens, Express + Prisma + Socket.io backend, Stripe payments, JWT auth. Deployed at https://marketplace.173.212.212.157.sslip.io/ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
98 lines
5.0 KiB
TypeScript
98 lines
5.0 KiB
TypeScript
import { useState } from 'react';
|
|
import { Link, useNavigate } from 'react-router-dom';
|
|
import { Mail, Lock, Eye, EyeOff, User } from 'lucide-react';
|
|
import { Input } from '../components/ui/Input';
|
|
import { GradientButton } from '../components/ui/GradientButton';
|
|
import { Button } from '../components/ui/Button';
|
|
import { useAuth } from '../context/AuthContext';
|
|
|
|
export function SignUpPage() {
|
|
const navigate = useNavigate();
|
|
const { signup } = useAuth();
|
|
const [fullName, setFullName] = useState('');
|
|
const [email, setEmail] = useState('');
|
|
const [password, setPassword] = useState('');
|
|
const [confirmPassword, setConfirmPassword] = useState('');
|
|
const [showPassword, setShowPassword] = useState(false);
|
|
const [error, setError] = useState('');
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
if (password !== confirmPassword) {
|
|
setError('Passwords do not match');
|
|
return;
|
|
}
|
|
setError('');
|
|
setIsLoading(true);
|
|
try {
|
|
await signup({ fullName, email, password });
|
|
navigate('/profile/create');
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Sign up failed');
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-[80vh] flex items-center justify-center px-4 py-12">
|
|
<div className="w-full max-w-md">
|
|
<div className="bg-white rounded-3xl shadow-xl p-8">
|
|
<div className="text-center mb-8">
|
|
<h1 className="text-2xl font-bold text-gray-900">Sign Up</h1>
|
|
<p className="text-sm text-gray-500 mt-2">Create your account to start buying and selling</p>
|
|
</div>
|
|
|
|
<div className="space-y-3 mb-6">
|
|
<Button variant="outline" className="w-full justify-center gap-2" onClick={() => {}}>
|
|
<img src="https://www.gstatic.com/firebasejs/ui/2.0.0/images/auth/google.svg" alt="Google" className="w-5 h-5" />
|
|
Sign up with Google
|
|
</Button>
|
|
<Button variant="outline" className="w-full justify-center gap-2 !border-blue-200 !text-blue-600 hover:!bg-blue-50" onClick={() => {}}>
|
|
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>
|
|
Sign up with Facebook
|
|
</Button>
|
|
</div>
|
|
|
|
<div className="relative mb-6">
|
|
<div className="absolute inset-0 flex items-center"><div className="w-full border-t border-gray-200" /></div>
|
|
<div className="relative flex justify-center text-sm"><span className="px-3 bg-white text-gray-400">or</span></div>
|
|
</div>
|
|
|
|
{error && <div className="mb-4 p-3 rounded-xl bg-red-50 text-red-600 text-sm">{error}</div>}
|
|
|
|
<form onSubmit={handleSubmit} className="space-y-4">
|
|
<Input label="Full Name" placeholder="Enter your full name" value={fullName} onChange={(e) => setFullName(e.target.value)}
|
|
icon={<User className="w-4 h-4" />} required />
|
|
<Input label="Email" type="email" placeholder="Enter your email" value={email} onChange={(e) => setEmail(e.target.value)}
|
|
icon={<Mail className="w-4 h-4" />} required />
|
|
<div className="relative">
|
|
<Input label="Password" type={showPassword ? 'text' : 'password'} placeholder="Create a password" value={password} onChange={(e) => setPassword(e.target.value)}
|
|
icon={<Lock className="w-4 h-4" />} required />
|
|
<button type="button" onClick={() => setShowPassword(!showPassword)}
|
|
className="absolute right-3 top-[38px] text-gray-400 hover:text-gray-600 cursor-pointer">
|
|
{showPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
|
|
</button>
|
|
</div>
|
|
<Input label="Confirm Password" type={showPassword ? 'text' : 'password'} placeholder="Confirm your password" value={confirmPassword} onChange={(e) => setConfirmPassword(e.target.value)}
|
|
icon={<Lock className="w-4 h-4" />} required />
|
|
<label className="flex items-start gap-2 text-xs text-gray-500">
|
|
<input type="checkbox" required className="mt-0.5 accent-primary-600" />
|
|
By signing up, you agree to our Terms of Service and Privacy Policy
|
|
</label>
|
|
<GradientButton type="submit" className="w-full" size="lg" isLoading={isLoading}>
|
|
Sign Up
|
|
</GradientButton>
|
|
</form>
|
|
|
|
<p className="text-center text-sm text-gray-500 mt-6">
|
|
Already have an account?{' '}
|
|
<Link to="/login" className="text-primary-600 font-semibold hover:text-primary-700">Login ></Link>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|