import React, { useState } from "react"; // Cloud-Erst-Einrichtung — der Wizard, der erscheint, wenn der Browser auf // eine leere Cloud-Instanz trifft (0 Studios). Designs / Schritt-Struktur ist // bewusst parallel zum lokalen Setup.jsx, nur die Endpunkte sind anders: // Schritt 1: Studio-Stammdaten // Schritt 2: Admin-Account (Email + Passwort + Anzeigename) // Schritt 3: Buchhaltung (optional) + Übersicht + Abschluss // // Bei Submit wird der `cloudInit`-Prop aus App.jsx aufgerufen — der orchestriert // signUp + ensureProfile + createStudio + load + handleLogin. const C = { bg: "#ebe7e1", surface: "#fdfcfa", surface2: "#f7f4f0", border: "#ddd8d0", border2: "#e6e1da", text: "#1a1a18", text3: "#6a6660", text4: "#8c8880", danger: "#8a1a1a", dangerBg: "#fdf2f2", dangerBorder: "#e0b0b0", }; const S = { wrap: { background: C.bg, minHeight: "100vh", display: "flex", alignItems: "center", justifyContent: "center", fontFamily: "'DM Mono','Courier New',monospace", color: C.text, padding: "32px 16px" }, card: { width: "100%", maxWidth: 460, background: C.surface, borderRadius: 14, padding: "44px 40px", boxShadow: "0 2px 24px rgba(60,50,40,0.10)", border: `1px solid ${C.border}`, maxHeight: "92vh", overflowY: "auto" }, logo: { fontFamily: "Krungthep,'Archivo Black',sans-serif", fontSize: 34, color: C.text, letterSpacing: "-0.02em", textAlign: "center", marginBottom: 4 }, sub: { textAlign: "center", fontSize: 10, color: C.text4, letterSpacing: "0.14em", marginBottom: 32 }, progress: { display: "flex", gap: 6, justifyContent: "center", marginBottom: 32 }, dot: (active, done) => ({ width: active ? 22 : 8, height: 8, borderRadius: 4, background: done ? C.text : active ? C.text : C.border, opacity: done ? 1 : active ? 1 : 0.4, transition: "all 0.3s" }), label: { fontSize: 9, letterSpacing: "0.14em", color: C.text4, display: "block", marginBottom: 5, marginTop: 16 }, input: { width: "100%", boxSizing: "border-box", background: C.surface, border: `1px solid ${C.border}`, borderRadius: 6, padding: "9px 12px", color: C.text, fontFamily: "'DM Mono',monospace", fontSize: 13, outline: "none", transition: "border-color 0.15s" }, inputErr: { border: `1px solid ${C.dangerBorder}` }, textarea: { width: "100%", boxSizing: "border-box", background: C.surface, border: `1px solid ${C.border}`, borderRadius: 6, padding: "9px 12px", color: C.text, fontFamily: "'DM Mono',monospace", fontSize: 13, outline: "none", resize: "vertical", minHeight: 64 }, err: { fontSize: 11, color: C.danger, marginTop: 4 }, hint: { fontSize: 11, color: C.text4, marginTop: 5, lineHeight: 1.5 }, btnPrimary: { width: "100%", background: C.text, border: "none", borderRadius: 8, padding: "12px 0", color: "#fff", fontFamily: "'DM Mono',monospace", fontSize: 13, cursor: "pointer", marginTop: 28, letterSpacing: "0.04em", transition: "opacity 0.15s" }, btnGhost: { width: "100%", background: "transparent", border: `1px solid ${C.border}`, borderRadius: 8, padding: "10px 0", color: C.text4, fontFamily: "'DM Mono',monospace", fontSize: 12, cursor: "pointer", marginTop: 10 }, }; export default function CloudSetup({ cloudInit, cloudUrl }) { const TOTAL = 3; const [step, setStep] = useState(1); const [studio, setStudio] = useState({ name: "", street: "", zip: "", city: "", email: "", phone: "", iban: "", mwst: "", hourlyRate: "" }); const [account, setAccount] = useState({ displayName: "", email: "", password: "", confirm: "" }); const [errors, setErrors] = useState({}); const [showPw, setShowPw] = useState(false); const [busy, setBusy] = useState(false); const [submitErr, setSubmitErr] = useState(""); const setS = (k, v) => setStudio(s => ({ ...s, [k]: v })); const setA = (k, v) => setAccount(a => ({ ...a, [k]: v })); const clearErr = k => setErrors(e => { const n = { ...e }; delete n[k]; return n; }); const validate = () => { const errs = {}; if (step === 1) { if (!studio.name.trim()) errs.name = "Pflichtfeld"; } if (step === 2) { if (!account.displayName.trim()) errs.displayName = "Pflichtfeld"; if (!account.email.trim() || !/.+@.+\..+/.test(account.email)) errs.email = "Gültige Email"; if (account.password.length < 6) errs.password = "Mindestens 6 Zeichen"; if (account.password !== account.confirm) errs.confirm = "Passwörter stimmen nicht überein"; } setErrors(errs); return Object.keys(errs).length === 0; }; const next = () => { if (validate()) setStep(s => s + 1); }; const back = () => setStep(s => s - 1); const finish = async () => { if (busy) return; setBusy(true); setSubmitErr(""); const extras = {}; if (studio.street.trim()) extras.street = studio.street.trim(); if (studio.zip.trim()) extras.zip = studio.zip.trim(); if (studio.city.trim()) extras.city = studio.city.trim(); if (studio.email.trim()) extras.email = studio.email.trim(); if (studio.phone.trim()) extras.phone = studio.phone.trim(); if (studio.iban.trim()) extras.iban = studio.iban.trim().replace(/\s+/g, ""); if (studio.mwst.trim()) extras.mwst = studio.mwst.trim(); if (studio.hourlyRate.trim()) { const n = Number(studio.hourlyRate); if (!Number.isNaN(n) && n > 0) extras.defaultHourlyRate = n; } const res = await cloudInit(account.email.trim(), account.password, account.displayName.trim(), studio.name.trim(), extras); if (!res?.ok) { setSubmitErr(res?.error || "Einrichtung fehlgeschlagen."); setBusy(false); } // bei Erfolg übernimmt App.jsx (currentUser setzen) — keine weitere Aktion nötig }; const progressEl = (
{Array.from({ length: TOTAL }, (_, i) => (
))}
); const renderHeader = (n, title, lead) => ( <>
RAPPORT
SCHRITT {n} VON {TOTAL} · CLOUD
{progressEl}
{title}
{lead &&
{lead}
} ); // ── Step 1: Studio ───────────────────────────────────────────────────────── if (step === 1) return (
{renderHeader(1, "Willkommen.", "Lass uns dein Studio einrichten. Adresse und Buchhaltung sind optional und können später ergänzt werden.")} {cloudUrl && (
Cloud-Server: {(() => { try { return new URL(cloudUrl).host; } catch { return cloudUrl; } })()}
)} { setS("name", e.target.value); clearErr("name"); }} onKeyDown={e => e.key === "Enter" && next()} /> {errors.name &&
{errors.name}
} setS("street", e.target.value)} />
setS("zip", e.target.value)} />
setS("city", e.target.value)} />
setS("email", e.target.value)} />
setS("phone", e.target.value)} />
); // ── Step 2: Account ──────────────────────────────────────────────────────── if (step === 2) return (
{renderHeader(2, "Dein Account.", "Mit dieser Email loggst du dich künftig ein. Du wirst Admin des Studios — weitere Mitarbeitende können später eingeladen werden.")} { setA("displayName", e.target.value); clearErr("displayName"); }} /> {errors.displayName &&
{errors.displayName}
} { setA("email", e.target.value); clearErr("email"); }} /> {errors.email &&
{errors.email}
}
{ setA("password", e.target.value); clearErr("password"); }} />
{errors.password &&
{errors.password}
} { setA("confirm", e.target.value); clearErr("confirm"); }} onKeyDown={e => e.key === "Enter" && next()} /> {errors.confirm &&
{errors.confirm}
}
); // ── Step 3: Buchhaltung + Übersicht + Abschluss ─────────────────────────── return (
{renderHeader(3, "Buchhaltung & Abschluss.", "Alle Felder sind optional. Du kannst sie auch später in den Einstellungen ergänzen.")} setS("iban", e.target.value)} />
setS("mwst", e.target.value)} />
setS("hourlyRate", e.target.value)} />
ÜBERSICHT
{[ { label: "Studio", value: studio.name }, { label: "Account", value: `${account.displayName} · ${account.email}` }, { label: "Adresse", value: [studio.street, [studio.zip, studio.city].filter(Boolean).join(" ")].filter(Boolean).join(", ") || "—" }, ].map(({ label, value }) => (
{label.toUpperCase()}
{value}
))}
{submitErr &&
{submitErr}
}
); }