/* INDIEKOS — Layar login: Operator + Superadmin (email/Google + daftar), dan Penghuni (No HP + OTP WhatsApp). */ function LoginScreen({ scope, title, sub, denied, onSignedIn }) { const [mode, setMode] = useState("login"); // 'login' | 'signup' const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [kosName, setKosName] = useState(""); const [ownerName, setOwnerName] = useState(""); const [propName, setPropName] = useState(""); const [loading, setLoading] = useState(false); const [err, setErr] = useState(denied || ""); const [ok, setOk] = useState(""); // Penghuni OTP state const [otpStep, setOtpStep] = useState("phone"); // 'phone' | 'code' const [otpPhone, setOtpPhone] = useState(""); const [otpCode, setOtpCode] = useState(""); const [masked, setMasked] = useState(""); useEffect(() => { setErr(denied || ""); }, [denied]); const allowSignup = scope !== "superadmin"; const isPenghuni = scope === "penghuni"; const googleSignIn = async () => { setErr(""); setLoading(true); try { const { error } = await window._supabase.auth.signInWithOAuth({ provider: "google", options: { redirectTo: window.location.origin + window.location.pathname }, }); if (error) { setErr(error.message || "Google sign-in gagal"); setLoading(false); } } catch (ex) { setErr(ex.message || "Google sign-in gagal"); setLoading(false); } }; const submit = async (e) => { e.preventDefault(); setErr(""); setLoading(true); try { const { data, error } = await window._supabase.auth.signInWithPassword({ email: email.trim(), password }); if (error) { setErr(error.message || "Email/password tidak cocok"); return; } onSignedIn && onSignedIn(data.session); } catch (ex) { setErr(ex.message || "Gagal masuk"); } finally { setLoading(false); } }; const submitSignup = async (e) => { e.preventDefault(); setErr(""); setOk(""); setLoading(true); try { const r = await fetch(window.INDIEKOS_API + "/public-signup", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ account_name: kosName.trim(), owner_name: ownerName.trim(), owner_email: email.trim(), password, property_name: propName.trim() }), }); const j = await r.json(); if (!r.ok) { setErr(j.error || "Pendaftaran gagal"); return; } setOk("Akun berhasil dibuat! Sedang masuk…"); const { data, error } = await window._supabase.auth.signInWithPassword({ email: email.trim(), password }); if (error) { setErr("Akun dibuat, tapi gagal masuk otomatis. Coba login manual."); setMode("login"); return; } onSignedIn && onSignedIn(data.session); } catch (ex) { setErr(ex.message || "Pendaftaran gagal"); } finally { setLoading(false); } }; // ---- Penghuni: kirim OTP ---- const sendOtp = async (e) => { if (e) e.preventDefault(); setErr(""); setOk(""); setLoading(true); try { const r = await fetch(window.INDIEKOS_API + "/tenant/request-otp", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ phone: otpPhone.trim() }), }); const j = await r.json(); if (!r.ok) { setErr(j.error || "Gagal mengirim kode"); return; } setMasked(j.phone_masked || ""); setOtpStep("code"); setOk("Kode OTP dikirim ke WhatsApp kamu. Cek pesan dari pengelola kos ya."); } catch (ex) { setErr(ex.message || "Gagal mengirim kode"); } finally { setLoading(false); } }; // ---- Penghuni: verifikasi OTP + mint sesi ---- const verifyOtp = async (e) => { if (e) e.preventDefault(); setErr(""); setLoading(true); try { const r = await fetch(window.INDIEKOS_API + "/tenant/verify-otp", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ phone: otpPhone.trim(), code: otpCode.trim() }), }); const j = await r.json(); if (!r.ok) { setErr(j.error || "Verifikasi gagal"); return; } const { data, error } = await window._supabase.auth.verifyOtp({ token_hash: j.token_hash, type: "magiclink" }); if (error) { setErr(error.message || "Gagal membuat sesi login"); return; } onSignedIn && onSignedIn(data.session); } catch (ex) { setErr(ex.message || "Verifikasi gagal"); } finally { setLoading(false); } }; const Shell = (children, head, subt) => (
{ e.currentTarget.style.display="none"; }} />