// auth.jsx — session hook + Login/Register modals for saraslot.
// Loaded after supabase-client.jsx, before data.jsx and pages.jsx.
const { useState: useStateAu, useEffect: useEffectAu, useCallback: useCallbackAu, createContext: createContextAu, useContext: useContextAu } = React;
const SessionContext = createContextAu({ session: null, ready: false, refresh: () => {} });
function SessionProvider({ children }) {
const [session, setSession] = useStateAu(null);
const [ready, setReady] = useStateAu(false);
useEffectAu(() => {
let mounted = true;
window.sb.auth.getSession().then(({ data }) => {
if (!mounted) return;
setSession(data.session || null);
setReady(true);
});
const { data: sub } = window.sb.auth.onAuthStateChange((_event, sess) => {
setSession(sess || null);
});
return () => { mounted = false; sub?.subscription?.unsubscribe?.(); };
}, []);
const refresh = useCallbackAu(async () => {
const { data } = await window.sb.auth.getSession();
setSession(data.session || null);
}, []);
return {children};
}
window.SessionProvider = SessionProvider;
function useSession() {
return useContextAu(SessionContext);
}
window.useSession = useSession;
async function signOut() {
await window.sb.auth.signOut();
}
window.signOut = signOut;
/* ─────────────────────────────────────────────────────────────
Auth modal shell
──────────────────────────────────────────────────────────── */
function AuthModal({ open, onClose, children, title }) {
if (!open) return null;
return (
e.stopPropagation()}>
{title}
{children}
);
}
function PhoneField({ value, onChange }) {
return (
);
}
function PasswordField({ value, onChange, label = "Kata Laluan", autoComplete = "current-password" }) {
return (
);
}
/* ─────────────────────────────────────────────────────────────
LoginModal — phone + password
──────────────────────────────────────────────────────────── */
function LoginModal({ open, onClose, onSwitchToRegister }) {
const [phone, setPhone] = useStateAu("");
const [password, setPassword] = useStateAu("");
const [error, setError] = useStateAu("");
const [busy, setBusy] = useStateAu(false);
const submit = async (e) => {
e.preventDefault();
setError("");
setBusy(true);
const email = window.phoneToEmail(phone);
const { error: err } = await window.sb.auth.signInWithPassword({ email, password });
setBusy(false);
if (err) {
setError("Phone or password incorrect.");
return;
}
setPhone(""); setPassword("");
onClose();
};
return (
);
}
window.LoginModal = LoginModal;
/* ─────────────────────────────────────────────────────────────
RegisterModal — phone + username + password
──────────────────────────────────────────────────────────── */
function RegisterModal({ open, onClose, onSwitchToLogin }) {
const [phone, setPhone] = useStateAu("");
const [username, setUsername] = useStateAu("");
const [password, setPassword] = useStateAu("");
const [error, setError] = useStateAu("");
const [busy, setBusy] = useStateAu(false);
const submit = async (e) => {
e.preventDefault();
setError("");
const digits = String(phone).replace(/\D+/g, "");
if (digits.length < 7) { setError("Enter a valid phone number."); return; }
if (!username.trim()) { setError("Pick a username."); return; }
setBusy(true);
const email = window.phoneToEmail(phone);
const { data, error: err } = await window.sb.auth.signUp({
email,
password,
options: { data: { phone: digits, username: username.trim() } },
});
if (err) {
setBusy(false);
setError(err.message || "Could not register. Phone may already be in use.");
return;
}
// The handle_new_user trigger inserted a profiles row using new.phone (null for
// email signup) or new.id::text fallback. Update the row with the real phone +
// username now that the session is live.
try {
await window.sb.from("profiles").update({
phone: digits,
username: username.trim(),
}).eq("id", data.user.id);
} catch (_) { /* ignore — trigger should have created the row */ }
setBusy(false);
setPhone(""); setUsername(""); setPassword("");
onClose();
};
return (
);
}
window.RegisterModal = RegisterModal;
/* ─────────────────────────────────────────────────────────────
AuthGate — used by pages that require a session
──────────────────────────────────────────────────────────── */
function AuthGate({ children, fallback }) {
const { session, ready } = useSession();
if (!ready) return Loading…
;
if (!session) return fallback || Please log in to view this page.
;
return children;
}
window.AuthGate = AuthGate;