Files
digicraft-fe/pages/SignupPage.tsx
Fahri Can Seçer 6e3bee17ef
Some checks failed
Deploy Frontend / deploy (push) Has been cancelled
main
2026-02-05 01:34:13 +03:00

244 lines
13 KiB
TypeScript

import React, { useState } from 'react';
import axios from 'axios';
import { useAuth } from '../AuthContext';
import { useNavigate, Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Tooltip } from '../components/Tooltip';
import { LegalModal } from '../components/LegalModal';
import { ShieldCheck, FileText, AlertTriangle, Key } from 'lucide-react';
import { GoogleLogin } from '@react-oauth/google';
export default function Signup() {
const { t } = useTranslation();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [etsyShopName, setEtsyShopName] = useState('');
const [etsyShopLink, setEtsyShopLink] = useState('');
// New Fields for Phase 6
const [apiKey, setApiKey] = useState('');
const [termsAccepted, setTermsAccepted] = useState(false);
const [kvkkAccepted, setKvkkAccepted] = useState(false);
// UI State
const [error, setError] = useState<{ message: string, code?: string } | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const [modalType, setModalType] = useState<'terms' | 'kvkk' | null>(null);
const { login } = useAuth();
const navigate = useNavigate();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
// Frontend Verification
if (!termsAccepted || !kvkkAccepted) {
setError({ message: "You must accept both the User Agreement and KVKK to register." });
return;
}
if (apiKey.length < 20) {
setError({ message: "Please enter a valid Gemini API Key." });
return;
}
setIsSubmitting(true);
try {
const res = await axios.post('/api/auth/register', {
email,
password,
apiKey,
etsyShopName,
etsyShopLink,
termsAccepted: true // Backend checks this
});
login(res.data.token, res.data.user);
navigate('/');
} catch (err: any) {
const data = err.response?.data;
const code = data?.code || 'UNKNOWN_ERROR';
const msg = data?.error || 'Registration failed. Please try again.';
setError({ message: msg, code });
} finally {
setIsSubmitting(false);
}
};
return (
<div className="flex min-h-screen items-center justify-center bg-stone-50 p-4">
<LegalModal
isOpen={!!modalType}
onClose={() => setModalType(null)}
type={modalType}
/>
<div className="w-full max-w-lg rounded-2xl bg-white p-8 shadow-xl border border-stone-100">
<div className="text-center mb-8">
<h2 className="text-3xl font-black text-stone-900 tracking-tight">Create Account</h2>
<p className="text-stone-500 mt-2 text-sm">Join the Beta access program</p>
</div>
{error && (
<div className={`mb-6 rounded-xl p-4 flex gap-3 items-start ${error.code === 'QUOTA_EXCEEDED' ? 'bg-amber-50 text-amber-800 border-amber-200 border' : 'bg-red-50 text-red-800 border-red-200 border'}`}>
<AlertTriangle className="w-5 h-5 flex-shrink-0 mt-0.5" />
<div>
<h4 className="font-bold text-sm uppercase tracking-wide mb-1">
{error.code === 'QUOTA_EXCEEDED' ? 'Quota Exceeded' : 'Registration Failed'}
</h4>
<p className="text-sm opacity-90">{error.message}</p>
{error.code === 'INVALID_KEY' && (
<p className="text-xs mt-2 font-mono bg-white/50 p-1 rounded inline-block">
Tip: Keys usually start with AIza...
</p>
)}
</div>
</div>
)}
<form onSubmit={handleSubmit} className="space-y-5">
{/* Security Note */}
<div className="bg-blue-50 border border-blue-100 rounded-lg p-3 flex gap-2 items-center text-xs text-blue-700">
<ShieldCheck className="w-4 h-4" />
<span>Your data is encrypted. We strictly follow KVKK/GDPR.</span>
</div>
<div>
<label className="mb-1 block text-sm font-bold text-stone-700">Email Address</label>
<input
type="email"
className="w-full rounded-lg border border-stone-300 p-3 text-sm focus:border-stone-900 focus:ring-1 focus:ring-stone-900 focus:outline-none transition-all"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
placeholder="you@example.com"
/>
</div>
<div>
<label className="mb-1 block text-sm font-bold text-stone-700">Password</label>
<input
type="password"
className="w-full rounded-lg border border-stone-300 p-3 text-sm focus:border-stone-900 focus:ring-1 focus:ring-stone-900 focus:outline-none transition-all"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
placeholder="••••••••"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="mb-1 block text-sm font-bold text-stone-700">Etsy Shop Name</label>
<input
type="text"
className="w-full rounded-lg border border-stone-300 p-3 text-sm focus:border-stone-900 focus:ring-1 focus:ring-stone-900 focus:outline-none transition-all"
value={etsyShopName}
onChange={(e) => setEtsyShopName(e.target.value)}
placeholder="e.g. PixelParadise"
/>
</div>
<div>
<label className="mb-1 block text-sm font-bold text-stone-700">Etsy Shop Link</label>
<input
type="url"
className="w-full rounded-lg border border-stone-300 p-3 text-sm focus:border-stone-900 focus:ring-1 focus:ring-stone-900 focus:outline-none transition-all"
value={etsyShopLink}
onChange={(e) => setEtsyShopLink(e.target.value)}
placeholder="https://etsy.com/shop/..."
/>
</div>
</div>
<div>
<div className="flex justify-between items-center mb-1">
<label className="block text-sm font-bold text-stone-700">Gemini API Key (Required)</label>
<a href="https://aistudio.google.com/app/apikey" target="_blank" rel="noreferrer" className="text-[10px] font-bold text-blue-600 hover:underline uppercase">
Get Key
</a>
</div>
<div className="relative">
<Key className="absolute left-3 top-3 w-4 h-4 text-stone-400" />
<input
type="password"
className="w-full rounded-lg border border-stone-300 p-3 pl-10 text-sm font-mono focus:border-stone-900 focus:ring-1 focus:ring-stone-900 focus:outline-none transition-all"
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
required
placeholder="AIzaSy..."
/>
</div>
<p className="text-[10px] text-stone-400 mt-1">
The system will send a test request to validate this key immediately.
</p>
</div>
{/* Legal Checkboxes */}
<div className="space-y-3 pt-2">
<label className="flex items-start gap-3 cursor-pointer group">
<input
type="checkbox"
className="mt-1 w-4 h-4 rounded border-stone-300 text-stone-900 focus:ring-stone-900"
checked={termsAccepted}
onChange={(e) => setTermsAccepted(e.target.checked)}
/>
<span className="text-sm text-stone-600 group-hover:text-stone-800 transition-colors">
I accept the <button type="button" onClick={(e) => { e.preventDefault(); setModalType('terms'); }} className="font-bold underline">User Agreement & IP Rights</button>.
</span>
</label>
<label className="flex items-start gap-3 cursor-pointer group">
<input
type="checkbox"
className="mt-1 w-4 h-4 rounded border-stone-300 text-stone-900 focus:ring-stone-900"
checked={kvkkAccepted}
onChange={(e) => setKvkkAccepted(e.target.checked)}
/>
<span className="text-sm text-stone-600 group-hover:text-stone-800 transition-colors">
I have read and accept the <button type="button" onClick={(e) => { e.preventDefault(); setModalType('kvkk'); }} className="font-bold underline">KVKK (PDPL) & Privacy Policy</button>.
</span>
</label>
</div>
<button
type="submit"
disabled={isSubmitting || !termsAccepted || !kvkkAccepted || !apiKey}
className="w-full rounded-xl bg-stone-900 py-3.5 font-bold text-white uppercase tracking-widest hover:bg-stone-800 disabled:opacity-50 disabled:cursor-not-allowed transition-all shadow-lg hover:shadow-xl transform active:scale-[0.98]"
>
{isSubmitting ? 'Verifying Key...' : 'Create Account'}
</button>
</form>
<div className="mt-8">
<div className="relative">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-stone-200"></div>
</div>
<div className="relative flex justify-center text-sm">
<span className="bg-white px-2 text-stone-500 font-bold uppercase text-[10px] tracking-widest">Or continue with</span>
</div>
</div>
<div className="mt-6 flex justify-center">
<GoogleLogin
onSuccess={async (credentialResponse) => {
try {
const res = await axios.post('/api/auth/google', { credential: credentialResponse.credential });
login(res.data.token, res.data.user);
navigate('/');
} catch (err) {
setError({ message: 'Google Signup Failed' });
}
}}
onError={() => setError({ message: 'Google Signup Failed' })}
/>
</div>
</div>
<div className="mt-6 text-center pt-6 border-t border-stone-100">
<p className="text-stone-500 text-sm">Already have an account? <Link to="/login" className="font-bold text-stone-900 hover:underline">Log in</Link></p>
</div>
</div>
</div>
);
}