Spaces:
Sleeping
Sleeping
| import { useState, useEffect, useCallback, CSSProperties } from "react"; | |
| import { useNavigate } from "react-router-dom"; | |
| import { useAuth } from "../../lib/authContext"; | |
| type Role = "STUDENT" | "SHEIKH"; | |
| type StatusType = "waiting" | "success" | "error"; | |
| interface Status { | |
| text: string; | |
| type: StatusType; | |
| } | |
| declare global { | |
| interface Window { | |
| google: { | |
| accounts: { | |
| id: { | |
| initialize: (config: { | |
| client_id: string; | |
| callback: (response: { credential: string }) => void; | |
| }) => void; | |
| renderButton: ( | |
| element: HTMLElement | null, | |
| options: object | |
| ) => void; | |
| }; | |
| }; | |
| }; | |
| } | |
| } | |
| const CLIENT_ID = import.meta.env.VITE_GOOGLE_CLIENT_ID as string; | |
| const STATUS_COLORS: Record<StatusType, { bg: string; color: string }> = { | |
| waiting: { bg: "#fff3cd", color: "#856404" }, | |
| success: { bg: "#d4edda", color: "#155724" }, | |
| error: { bg: "#f8d7da", color: "#721c24" }, | |
| }; | |
| export default function GoogleAuth() { | |
| const navigate = useNavigate(); | |
| const { signInWithGoogle } = useAuth(); | |
| const [selectedRole, setSelectedRole] = useState<Role>("STUDENT"); | |
| const [status, setStatus] = useState<Status>({ text: "β³ Waiting for sign-in", type: "waiting" }); | |
| const handleCredentialResponse = useCallback( | |
| async (googleResponse: { credential: string }): Promise<void> => { | |
| const idToken = googleResponse.credential; | |
| setStatus({ text: "β³ Signing in...", type: "waiting" }); | |
| try { | |
| const user = await signInWithGoogle(idToken, selectedRole); | |
| setStatus({ text: "β Success! Redirecting...", type: "success" }); | |
| // Navigate based on role and profile completion status | |
| const role = user.role.toLowerCase(); | |
| if (role === "admin") { | |
| navigate("/admin/dashboard", { replace: true }); | |
| } else if (role === "sheikh") { | |
| const approvalStatus = user.sheikhApprovalStatus?.toUpperCase(); | |
| if (approvalStatus === "REJECTED") { | |
| navigate("/sheikh/dashboard", { replace: true }); | |
| } | |
| else if (!user.profileCompleted) { | |
| navigate("/complete-profile/sheikh", { replace: true }); | |
| } else { | |
| const approvalStatus = user.sheikhApprovalStatus?.toUpperCase(); | |
| if (approvalStatus === "APPROVED") { | |
| navigate("/sheikh/dashboard", { replace: true }); | |
| } else { | |
| navigate("/sheikh/interview", { replace: true }); | |
| } | |
| } | |
| } else { | |
| // student | |
| if (!user.profileCompleted) { | |
| navigate("/complete-profile/student", { replace: true }); | |
| } else { | |
| navigate("/student/dashboard", { replace: true }); | |
| } | |
| } | |
| } catch (err: unknown) { | |
| const message = err instanceof Error ? err.message : "Sign-in failed"; | |
| setStatus({ text: `β ${message}`, type: "error" }); | |
| } | |
| }, | |
| [selectedRole, signInWithGoogle, navigate] | |
| ); | |
| useEffect(() => { | |
| const script = document.createElement("script"); | |
| script.src = "https://accounts.google.com/gsi/client"; | |
| script.async = true; | |
| script.defer = true; | |
| script.onload = () => { | |
| window.google.accounts.id.initialize({ | |
| client_id: CLIENT_ID, | |
| callback: handleCredentialResponse, | |
| }); | |
| window.google.accounts.id.renderButton( | |
| document.getElementById("googleBtn"), | |
| { theme: "outline", size: "large", text: "sign_in_with", shape: "rectangular", width: 300 } | |
| ); | |
| }; | |
| document.body.appendChild(script); | |
| return () => { document.body.removeChild(script); }; | |
| }, [handleCredentialResponse]); | |
| return ( | |
| <div style={styles.page}> | |
| <div style={styles.container}> | |
| <div style={styles.header}> | |
| <div style={styles.icon}>π</div> | |
| <h1 style={styles.h1}>Quran App β Sign In</h1> | |
| <p style={styles.subtitle}>Sign in with Google to continue</p> | |
| </div> | |
| <div style={styles.roleSelect}> | |
| {(["STUDENT", "SHEIKH"] as Role[]).map((role) => ( | |
| <button | |
| key={role} | |
| onClick={() => setSelectedRole(role)} | |
| style={{ ...styles.roleBtn, ...(selectedRole === role ? styles.roleBtnActive : {}) }} | |
| > | |
| {role === "STUDENT" ? "π Student" : "π Sheikh"} | |
| </button> | |
| ))} | |
| </div> | |
| <div style={styles.signinSection}> | |
| <p style={styles.signinHint}>Click the button below to sign in with Google</p> | |
| <div | |
| style={{ | |
| ...styles.statusBadge, | |
| background: STATUS_COLORS[status.type].bg, | |
| color: STATUS_COLORS[status.type].color, | |
| }} | |
| > | |
| {status.text} | |
| </div> | |
| <div id="googleBtn" style={{ display: "flex", justifyContent: "center" }} /> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| const styles: Record<string, CSSProperties> = { | |
| page: { fontFamily: "'Segoe UI', sans-serif", background: "#f0f4f8", minHeight: "100vh", display: "flex", alignItems: "center", justifyContent: "center" }, | |
| container: { background: "white", borderRadius: 16, padding: 40, width: 600, boxShadow: "0 4px 24px rgba(0,0,0,0.10)" }, | |
| header: { textAlign: "center", marginBottom: 32 }, | |
| icon: { fontSize: 48, marginBottom: 12 }, | |
| h1: { color: "#1a6b3c", fontSize: 24, marginBottom: 6 }, | |
| subtitle: { color: "#777", fontSize: 14 }, | |
| roleSelect: { display: "flex", gap: 12, marginBottom: 24 }, | |
| roleBtn: { flex: 1, padding: 10, border: "2px solid #ddd", borderRadius: 8, background: "white", cursor: "pointer", fontSize: 14, fontWeight: 600, color: "#555" }, | |
| roleBtnActive: { borderColor: "#1a6b3c", background: "#e8f5e9", color: "#1a6b3c" }, | |
| signinSection: { textAlign: "center", marginBottom: 24 }, | |
| signinHint: { color: "#888", fontSize: 13, marginBottom: 16 }, | |
| statusBadge: { display: "inline-block", padding: "6px 16px", borderRadius: 20, fontSize: 12, fontWeight: 600, marginBottom: 16 }, | |
| infoRow: { display: "flex", justifyContent: "flex-end", fontSize: 12, color: "#888", marginTop: 16 }, | |
| }; | |