import React, { useState, useEffect } from 'react'; import { useNavigate, useLocation, Link } from 'react-router-dom'; import { QRCodeSVG } from 'qrcode.react'; import api from '../api'; import { useAuth } from '../App'; import { getCodeList } from 'country-list'; import DeveloperOnboardingModal from '../components/DeveloperOnboardingModal'; import { User, Mail, Globe, Lock, Shield, CreditCard, BookOpen, AlertTriangle, LogOut, CheckCircle, AlertCircle, Eye, EyeOff, ChevronRight, Bell, Smartphone, Key, Trash2, Code, Sparkles, Building2 } from 'lucide-react'; interface LibraryEntry { plugin_id: number; name: string; category: string | null; preview_path: string | null; dev_name: string; amount_cents: number; purchased_at: string; } type Tab = 'account' | 'security' | 'developer' | 'notifications' | 'library' | 'danger'; const NAV: { key: Tab; label: string; icon: React.FC<{ size?: number }> ; danger?: boolean }[] = [ { key: 'account', label: 'Account', icon: User }, { key: 'security', label: 'Security', icon: Shield }, { key: 'developer', label: 'Developer Profile', icon: Code }, { key: 'notifications', label: 'Notifications', icon: Bell }, { key: 'library', label: 'Library', icon: BookOpen }, { key: 'danger', label: 'Danger Zone', icon: AlertTriangle, danger: true }, ]; const ProfilePage = () => { const { user, login, logout, refreshUser } = useAuth(); const navigate = useNavigate(); const location = useLocation(); const [activeTab, setActiveTab] = useState('account'); useEffect(() => { const params = new URLSearchParams(location.search); const tab = params.get('tab') as Tab | null; if (tab && NAV.some(n => n.key === tab)) setActiveTab(tab); else setActiveTab('account'); }, [location]); const [formData, setFormData] = useState({ username: '', email: '', country: '', password: '', currentPassword: '', disable2faPassword: '', newPassword: '', confirmPassword: '', }); const [showCurrentPw, setShowCurrentPw] = useState(false); const [showNewPw, setShowNewPw] = useState(false); const [showConfirmPw, setShowConfirmPw] = useState(false); const [show2faPw, setShow2faPw] = useState(false); const [toast, setToast] = useState<{ text: string; type: 'success' | 'error' } | null>(null); const [isStripeLoading, setIsStripeLoading] = useState(false); const [saving, setSaving] = useState>({}); // Library const [library, setLibrary] = useState([]); const [libraryLoading, setLibraryLoading] = useState(false); const [libraryError, setLibraryError] = useState(null); // 2FA const [is2faEnabled, setIs2faEnabled] = useState(false); const [setup2faData, setSetup2faData] = useState<{ uri: string; secret: string } | null>(null); const [verify2faCode, setVerify2faCode] = useState(''); // Notifications (UI-only for now, persisted locally) const [notifPrefs, setNotifPrefs] = useState({ purchaseEmail: true, reviewEmail: true, updateEmail: false, marketingEmail: false, }); useEffect(() => { refreshUser?.(); }, []); useEffect(() => { if (user) { setFormData(prev => ({ ...prev, username: user.username || '', email: user.email || '', country: user.country || '', })); setIs2faEnabled(user.totp_enabled || false); } }, [user]); // Developer Profile State const [isDevModalOpen, setIsDevModalOpen] = useState(false); const [devProfile, setDevProfile] = useState(null); const [devProfileForm, setDevProfileForm] = useState({ displayName: '', legalName: '', streetAddress: '', city: '', postalCode: '', vatId: '', publishingIntent: '', supportEmail: '', websiteUrl: '', githubUrl: '', }); useEffect(() => { if (activeTab === 'developer' && user?.is_developer) { api.get('/user/developer/profile') .then(res => { if (res.data.profile) { setDevProfile(res.data.profile); setDevProfileForm({ displayName: res.data.profile.display_name || '', legalName: res.data.profile.legal_name || '', streetAddress: res.data.profile.street_address || '', city: res.data.profile.city || '', postalCode: res.data.profile.postal_code || '', vatId: res.data.profile.vat_id || '', publishingIntent: res.data.profile.publishing_intent || '', supportEmail: res.data.profile.support_email || '', websiteUrl: res.data.profile.website_url || '', githubUrl: res.data.profile.github_url || '', }); } }) .catch(err => console.error('Failed to load dev profile', err)); } }, [activeTab, user?.is_developer]); const handleUpdateDevProfile = async (e: React.FormEvent) => { e.preventDefault(); await withSaving('devProfile', async () => { try { const res = await api.put('/user/developer/profile', devProfileForm); setDevProfile(res.data.profile); showToast('Developer profile updated!'); } catch (err: any) { showToast(err.response?.data?.error || 'Failed to update developer profile.', 'error'); } }); }; useEffect(() => { if (activeTab !== 'library') return; setLibraryLoading(true); setLibraryError(null); api.get('/user/library') .then(res => setLibrary(Array.isArray(res.data) ? res.data : [])) .catch(() => setLibraryError('Failed to load your library. Please try again.')) .finally(() => setLibraryLoading(false)); }, [activeTab]); const showToast = (text: string, type: 'success' | 'error' = 'success') => { setToast({ text, type }); setTimeout(() => setToast(null), 4000); }; const withSaving = async (key: string, fn: () => Promise) => { setSaving(s => ({ ...s, [key]: true })); try { await fn(); } finally { setSaving(s => ({ ...s, [key]: false })); } }; const handleUpdate = async (e: React.FormEvent, endpoint: string, payload: object, key: string) => { e.preventDefault(); await withSaving(key, async () => { try { const res = await api.post(endpoint, payload); if (res.data.token) login(res.data.token); showToast(res.data.message || 'Updated successfully!'); } catch (err: any) { showToast(err.response?.data?.error || 'Update failed.', 'error'); } }); }; const handleLogout = () => { logout(); navigate('/login'); }; const handleConnectStripe = async () => { setIsStripeLoading(true); try { const res = await api.post('/stripe/onboard'); if (res.data.url) window.location.href = res.data.url; } catch (err: any) { showToast(err.response?.data?.error || 'Failed to start Stripe onboarding.', 'error'); setIsStripeLoading(false); } }; const handleGoToStripe = async () => { setIsStripeLoading(true); try { const res = await api.get('/stripe/dashboard'); if (res.data.url) window.location.href = res.data.url; } catch (err: any) { showToast(err.response?.data?.error || 'Failed to open Stripe dashboard.', 'error'); setIsStripeLoading(false); } }; const handleDeleteAccount = async () => { if (!window.confirm('Are you sure you want to delete your account? This action is irreversible.')) return; try { await api.delete('/user'); logout(); navigate('/login'); } catch (err: any) { showToast(err.response?.data?.error || 'Failed to delete account.', 'error'); } }; const start2faSetup = async () => { try { const res = await api.post('/user/2fa/setup'); setSetup2faData(res.data); } catch (err: any) { showToast(err.response?.data?.error || 'Failed to start 2FA setup.', 'error'); if (err.response?.status === 400 && err.response?.data?.error === '2FA is already enabled') { setIs2faEnabled(true); refreshUser?.(); } } }; const confirm2faSetup = async (e: React.FormEvent) => { e.preventDefault(); try { await api.post('/user/2fa/verify', { code: verify2faCode }); setIs2faEnabled(true); setSetup2faData(null); setVerify2faCode(''); showToast('2FA enabled successfully!'); refreshUser?.(); } catch (err: any) { showToast(err.response?.data?.error || 'Invalid 2FA code.', 'error'); } }; const disable2fa = async (e: React.FormEvent) => { e.preventDefault(); try { await api.post('/user/2fa/disable', { password: formData.disable2faPassword }); setIs2faEnabled(false); setFormData({ ...formData, disable2faPassword: '' }); showToast('2FA has been disabled.'); refreshUser?.(); } catch (err: any) { showToast(err.response?.data?.error || 'Failed to disable 2FA.', 'error'); } }; // ── helpers ────────────────────────────────────────────────────────────────── const SettingsCard = ({ title, description, icon: Icon, children }: { title: string; description?: string; icon?: React.FC<{ size?: number }>; children: React.ReactNode; }) => (
{(title || Icon) && (
{Icon &&
}

{title}

{description &&

{description}

}
)}
{children}
); const Field = ({ label, hint, children }: { label: string; hint?: string; children: React.ReactNode }) => (
{hint &&

{hint}

} {children}
); const SaveBtn = ({ id, label = 'Save Changes' }: { id: string; label?: string }) => ( ); const Toggle = ({ checked, onChange, label, desc }: { checked: boolean; onChange: (v: boolean) => void; label: string; desc?: string }) => (

{label}

{desc &&

{desc}

}
); const PwInput = ({ placeholder, value, onChange, show, onToggle }: { placeholder: string; value: string; onChange: (v: string) => void; show: boolean; onToggle: () => void; }) => (
onChange(e.target.value)} required />
); // ── render ──────────────────────────────────────────────────────────────────── return (

Account Settings

{/* Toast */} {toast && (
{toast.type === 'success' ? : } {toast.text}
)}
{/* ── Sidebar nav ── */} {/* ── Main content ── */}
{/* ══ ACCOUNT ══════════════════════════════════════════════════ */} {activeTab === 'account' && (

Account Details

Manage your public identity and contact information.

handleUpdate(e, '/user/change-username', { newUsername: formData.username }, 'username')}> setFormData({ ...formData, username: e.target.value })} placeholder="Your username" />
handleUpdate(e, '/user/change-email', { newEmail: formData.email }, 'email')}> setFormData({ ...formData, email: e.target.value })} placeholder="your@email.com" />
{ if (!formData.country) { e.preventDefault(); showToast('Please select a country.', 'error'); return; } handleUpdate(e, '/user/change-country', { newCountry: formData.country }, 'country'); }}>
)} {/* ══ SECURITY ══════════════════════════════════════════════════ */} {activeTab === 'security' && (

Security Settings

Protect your account with a strong password and two-factor authentication.

{/* Password */}
{ if (formData.newPassword !== formData.confirmPassword) { e.preventDefault(); showToast('Passwords do not match.', 'error'); return; } handleUpdate(e, '/user/settings', { currentPassword: formData.currentPassword, newPassword: formData.newPassword, }, 'password'); }}> setFormData({ ...formData, currentPassword: v })} show={showCurrentPw} onToggle={() => setShowCurrentPw(s => !s)} /> setFormData({ ...formData, newPassword: v })} show={showNewPw} onToggle={() => setShowNewPw(s => !s)} /> setFormData({ ...formData, confirmPassword: v })} show={showConfirmPw} onToggle={() => setShowConfirmPw(s => !s)} /> {formData.newPassword && formData.confirmPassword && formData.newPassword !== formData.confirmPassword && (

Passwords do not match

)}
{/* 2FA */} {is2faEnabled ? (
2FA is currently enabled on your account.
setFormData({ ...formData, disable2faPassword: v })} show={show2faPw} onToggle={() => setShow2faPw(s => !s)} />
) : !setup2faData ? (
2FA is not enabled. We recommend enabling it for account protection.
) : (

1 Scan this QR code with your authenticator app:

Or enter this code manually:
{setup2faData.secret}

2 Enter the 6-digit code from your app:

setVerify2faCode(e.target.value.replace(/\D/g, ''))} required />
)}
)} {/* ══ DEVELOPER PROFILE ════════════════════════════════════════════ */} {activeTab === 'developer' && (

Developer Account & Payouts

Manage your creator identity, support information, and payout settings.

{!user?.is_developer ? (

You haven't set up a Developer Profile yet.

Individual creators and organizations can publish plugins for $0. Start your onboarding now!

) : ( <> {/* Developer Details */} {devProfile && (
setDevProfileForm({ ...devProfileForm, displayName: e.target.value })} required /> setDevProfileForm({ ...devProfileForm, legalName: e.target.value })} /> setDevProfileForm({ ...devProfileForm, streetAddress: e.target.value })} placeholder="123 Main St" />
setDevProfileForm({ ...devProfileForm, city: e.target.value })} /> setDevProfileForm({ ...devProfileForm, postalCode: e.target.value })} />
setDevProfileForm({ ...devProfileForm, vatId: e.target.value })} placeholder="EU123456789" /> setDevProfileForm({ ...devProfileForm, supportEmail: e.target.value })} placeholder="support@domain.com" /> setDevProfileForm({ ...devProfileForm, websiteUrl: e.target.value })} placeholder="https://website.com" /> setDevProfileForm({ ...devProfileForm, githubUrl: e.target.value })} placeholder="https://github.com/username" />
)} {/* Payouts / Stripe */}
{user?.stripe_ready ? ( <>

Stripe account connected

Your payouts are active and ready to receive funds from paid plugin sales.

) : user?.stripe_account_id ? ( <>

Stripe onboarding incomplete

Your account was created but hasn't been fully activated yet.

) : ( <>

No Stripe account linked yet. Free plugin uploads are fully enabled.

)}
{[ { q: 'Can I upload free plugins without Stripe?', a: 'Yes! Stripe Connect is only required if you want to sell paid plugins.' }, { q: 'When do I get paid for sales?', a: 'Stripe transfers earnings automatically within 2–7 business days.' }, { q: 'What platform fee applies?', a: 'PumpkinMarket takes a low platform commission per sale. Stripe processing fees apply.' }, ].map(({ q, a }) => (

{q}

{a}

))}
)}
)} {/* ══ NOTIFICATIONS ═════════════════════════════════════════════ */} {activeTab === 'notifications' && (

Notification Preferences

Choose which emails and alerts you want to receive.

setNotifPrefs(p => ({ ...p, purchaseEmail: v }))} label="New purchase" desc="Receive an email whenever someone buys your plugin." /> setNotifPrefs(p => ({ ...p, reviewEmail: v }))} label="New review" desc="Get notified when a user leaves a review on your plugin." /> setNotifPrefs(p => ({ ...p, updateEmail: v }))} label="Marketplace updates" desc="Important changes to marketplace policies or features." /> setNotifPrefs(p => ({ ...p, marketingEmail: v }))} label="Tips & promotions" desc="Occasional tips to grow your plugin sales." />
)} {/* ══ LIBRARY ═══════════════════════════════════════════════════ */} {activeTab === 'library' && (

My Library {library.length > 0 && ( {library.length} plugin{library.length !== 1 ? 's' : ''} )}

Your purchased plugins, available to download at any time.

{libraryLoading && (
{[...Array(3)].map((_, i) => (
))}
)} {libraryError && (
⚠️ {libraryError}
)} {!libraryLoading && !libraryError && library.length === 0 && (
📦

Your library is empty

Purchased plugins will appear here, ready to download anytime.

Browse Plugins
)} {!libraryLoading && !libraryError && library.length > 0 && (
{library.map((entry, idx) => (
{entry.preview_path ? ( {entry.name} ) : (
{entry.name.charAt(0).toUpperCase()}
)}
Owned
{entry.category && (
{entry.category}
)}
{entry.name}
by {entry.dev_name}
€{(entry.amount_cents / 100).toFixed(2)} {new Date(entry.purchased_at).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric', })}
→
))}
)}
)} {/* ══ DANGER ZONE ═══════════════════════════════════════════════ */} {activeTab === 'danger' && (

Danger Zone

These actions are permanent and cannot be undone.

Deleting your account will immediately remove all your plugins from the marketplace and forfeit any pending payouts.
)}
setIsDevModalOpen(false)} onSuccess={() => { refreshUser?.(); setActiveTab('developer'); }} />
); }; export default ProfilePage;