import React, { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { getCodeList } from 'country-list'; import { Globe, Mail, CheckCircle2, ChevronRight, AlertCircle, CreditCard, Code2, ShieldCheck, MapPin, Gift, } from 'lucide-react'; import api from '../api'; import { useAuth } from '../App'; import { SecuritySetupCards } from './SecuritySetupCards'; import './DeveloperOnboardingModal.css'; interface DeveloperOnboardingModalProps { isOpen?: boolean; onClose?: () => void; onSuccess?: () => void; embedded?: boolean; } export const DeveloperOnboardingModal: React.FC = ({ isOpen = true, onClose, onSuccess, embedded = false, }) => { const { t } = useTranslation(); const { user, login } = useAuth(); const [step, setStep] = useState(1); const [entityType, setEntityType] = useState<'individual' | 'organization'>('individual'); const [orgType, setOrgType] = useState(''); const [displayName, setDisplayName] = useState(user?.username || ''); const [nameChecking, setNameChecking] = useState(false); const [nameError, setNameError] = useState(null); const [isSellingPaid, setIsSellingPaid] = useState(null); // Legal / Address (for paid sellers) const [legalName, setLegalName] = useState(''); const [streetAddress, setStreetAddress] = useState(''); const [city, setCity] = useState(''); const [postalCode, setPostalCode] = useState(''); const [vatId, setVatId] = useState(''); const [country, setCountry] = useState(user?.country || ''); // Contact / Social const [supportEmail, setSupportEmail] = useState(user?.email || ''); const [websiteUrl, setWebsiteUrl] = useState(''); const [githubUrl, setGithubUrl] = useState(''); const [acceptedTerms, setAcceptedTerms] = useState(false); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); // Debounced check for developer display name availability useEffect(() => { const trimmed = displayName.trim(); if (!trimmed) { setNameError(null); return; } const timer = setTimeout(async () => { setNameChecking(true); try { const res = await api.get(`/user/developer/check-name?name=${encodeURIComponent(trimmed)}`); if (res.data?.available === false) { setNameError(res.data.message || t('developer.onboarding.name_taken_error')); } else { setNameError(null); } } catch (err: any) { if (err.response?.data?.error) { setNameError(err.response.data.error); } } finally { setNameChecking(false); } }, 400); return () => clearTimeout(timer); }, [displayName, t]); const hasSecureAuth = Boolean(user?.totp_enabled || (user?.passkey_count && user.passkey_count > 0) || user?.has_passkey); if (!embedded && !isOpen) return null; const handleCompleteOnboarding = async (connectStripe: boolean) => { if (!acceptedTerms) { setError('You must accept the Developer Distribution Agreement to continue.'); return; } setLoading(true); setError(null); try { const res = await api.post('/user/developer/onboard', { entityType, displayName, legalName: isSellingPaid ? (legalName.trim() || null) : null, streetAddress: isSellingPaid ? (streetAddress.trim() || null) : null, city: isSellingPaid ? (city.trim() || null) : null, postalCode: isSellingPaid ? (postalCode.trim() || null) : null, vatId: isSellingPaid ? (vatId.trim() || null) : null, publishingIntent: isSellingPaid ? 'paid' : 'free', supportEmail: supportEmail.trim() || null, websiteUrl: websiteUrl.trim() || null, githubUrl: githubUrl.trim() || null, country: country || user?.country || null, acceptedTerms, }); if (res.data.token) { login(res.data.token); } if (connectStripe) { const stripeRes = await api.post('/stripe/onboard'); if (stripeRes.data.url) { window.location.href = stripeRes.data.url; return; } } onSuccess?.(); onClose?.(); } catch (err: any) { setError(err.response?.data?.error || 'Failed to complete developer onboarding'); } finally { setLoading(false); } }; const nextStep = () => { setStep(s => s + 1); }; const prevStep = () => { setStep(s => Math.max(1, s - 1)); }; const handleStep2Next = async () => { const trimmed = displayName.trim(); if (!trimmed) return; setNameChecking(true); try { const res = await api.get(`/user/developer/check-name?name=${encodeURIComponent(trimmed)}`); if (res.data?.available === false) { setNameError(res.data.message || t('developer.onboarding.name_taken_error')); return; } setNameError(null); nextStep(); } catch (err: any) { setNameError(err.response?.data?.error || 'Failed to verify developer name'); } finally { setNameChecking(false); } }; const isStep2Valid = Boolean(displayName.trim() && !nameError && !nameChecking); const isStep3Valid = Boolean( isSellingPaid !== null && (isSellingPaid === false || (isSellingPaid === true && legalName.trim() && streetAddress.trim() && city.trim() && postalCode.trim() && country)) ); const content = (
{!embedded && onClose && ( )}
{t('developer.onboarding.badge')}

{t('developer.onboarding.title')}

{isSellingPaid ? t('developer.onboarding.subtitle_paid') : t('developer.onboarding.subtitle_free')}

{/* Step Indicator */}
= 1 ? 'active' : ''}`}>1. {t('developer.onboarding.step_account_type')}
= 2 ? 'active' : ''}`}>2. {t('developer.onboarding.step_name')}
= 3 ? 'active' : ''}`}>3. {t('developer.onboarding.step_monetization')}
= 4 ? 'active' : ''}`}>4. {t('developer.onboarding.step_links')}
= 5 ? 'active' : ''}`}>5. {t('developer.onboarding.step_finish')}
{error && (
{error}
)} {/* ── STEP 1: Account Type (Google Play Style) ── */} {step === 1 && (
{/* An organization */}

{t('developer.onboarding.org_title')}

{t('developer.onboarding.org_desc')}

{/* Yourself */}

{t('developer.onboarding.individual_title')}

{t('developer.onboarding.individual_desc')}

)} {/* ── STEP 2: Developer Public Name / Brand ── */} {step === 2 && (

{t('developer.onboarding.dev_name_title')}

{t('developer.onboarding.dev_name_desc')}

{ setDisplayName(e.target.value); setNameError(null); }} autoComplete="nickname" maxLength={60} autoFocus /> {nameError ? ( {nameError} ) : ( {t('developer.onboarding.display_name_hint')} )}
)} {/* ── STEP 3: Monetization & Seller Details ── */} {step === 3 && (
{/* Required Radio Question: Do you plan to publish Paid plugins? */}
{/* If clicked yes, display all the new required seller fields below */} {isSellingPaid === true && (
setLegalName(e.target.value)} placeholder={entityType === 'individual' ? 'First and Last Name' : 'Legal Company Name Inc.'} autoComplete="name" required />
setStreetAddress(e.target.value)} placeholder="123 Main St, Suite 400" autoComplete="street-address" required />
setCity(e.target.value)} placeholder="City" autoComplete="address-level2" required />
setPostalCode(e.target.value)} placeholder="10001" autoComplete="postal-code" required />
setVatId(e.target.value)} placeholder="EU123456789 or Tax ID" autoComplete="off" />
)}
)} {/* ── STEP 4: Support Contact & Portfolio Links ── */} {step === 4 && (
setSupportEmail(e.target.value)} placeholder="support@yourdomain.com" autoComplete="email" required />
setWebsiteUrl(e.target.value)} placeholder="https://yourwebsite.com" autoComplete="url" />
setGithubUrl(e.target.value)} placeholder="https://github.com/yourname" autoComplete="url" />
)} {/* ── STEP 5: Finish & Confirmation ── */} {step === 5 && (
{!isSellingPaid ? ( <>

Ready to Publish Free Plugins!

Your developer profile is configured for free & open-source uploads.

  • Publish unlimited free plugins ($0 fee)
  • Full analytics and review dashboard
  • Option to add address & sell paid plugins anytime later
{/* ── Security Requirement for Developers ── */} {!hasSecureAuth && (
{}} />
)} {/* ── Mandatory Legal Consent ── */}
) : ( <>

{t('developer.onboarding.intent_paid')}

{t('developer.onboarding.intent_paid_desc')}

  • Automated payouts directly to your bank account
  • Set custom pricing, discounts, and coupons
  • Automated license key generation for buyers
{/* ── Security Requirement for Developers ── */} {!hasSecureAuth && (
{}} />
)} {/* ── Mandatory Legal Consent ── */}
)}
)}
); if (embedded) { return (
{content}
); } return (
{content}
); }; export default DeveloperOnboardingModal;