/* 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) => (
INDIEKOS{ e.currentTarget.style.display="none"; }} />

{head}

{subt &&
{subt}
}
{children}
{scope === "superadmin" ? "Console platform INDIEKOS" : isPenghuni ? "Portal penghuni INDIE KOS" : "Manajemen kost"}
); // ============ PENGHUNI (No HP + OTP) ============ if (isPenghuni) { if (otpStep === "phone") { return Shell(
setOtpPhone(e.target.value)} placeholder="08xxxxxxxxxx" />
Kode OTP dikirim ke WhatsApp kamu dari nomor pengelola kos.
{err &&
{err}
}
, "Masuk Penghuni", "Pantau tagihan & info kos kamu" ); } return Shell(
setOtpCode(e.target.value.replace(/\D/g,""))} placeholder="6 digit" style={{ letterSpacing:"0.3em", textAlign:"center", fontSize:"var(--fs-lg)" }} />
Kode dikirim ke WhatsApp {masked || "kamu"}. Berlaku 5 menit.
{ok &&
{ok}
} {err &&
{err}
}
{e.preventDefault(); setErr(""); setOk(""); setOtpCode(""); setOtpStep("phone");}}>← Ganti nomor {e.preventDefault(); if(!loading) sendOtp();}} style={{ fontWeight:600 }}>Kirim ulang kode
, "Masukkan kode", "Verifikasi nomor WhatsApp kamu" ); } // ============ OPERATOR / SUPERADMIN ============ const isSignup = mode === "signup"; return Shell( <> {isSignup ? (
setKosName(e.target.value)} placeholder="Kos Mawar Residence" />
setOwnerName(e.target.value)} placeholder="Nama pemilik" />
setEmail(e.target.value)} placeholder="nama@domain.id" />
setPassword(e.target.value)} placeholder="••••••••" />
setPropName(e.target.value)} placeholder="Properti utama" />
{err &&
{err}
} {ok &&
{ok}
}
) : (
setEmail(e.target.value)} placeholder="nama@domain.id" />
setPassword(e.target.value)} placeholder="••••••••" />
{err &&
{err}
}
)} {!isSignup && ( <>
atau
)} {allowSignup && (
{isSignup ? (<>Sudah punya akun? {e.preventDefault(); setErr(""); setOk(""); setMode("login");}} style={{fontWeight:600}}>Masuk di sini) : (<>Belum punya akun? {e.preventDefault(); setErr(""); setMode("signup");}} style={{fontWeight:600}}>Daftar sebagai pemilik kos)}
)} , isSignup ? "Daftar pemilik kos" : title, isSignup ? "Buat akun INDIEKOS gratis — coba 14 hari, tanpa kartu kredit." : sub ); } window.LoginScreen = LoginScreen;