109 lines
3.2 KiB
TypeScript
109 lines
3.2 KiB
TypeScript
import React, { useEffect, useMemo, useState } from "react";
|
|
|
|
import { Box, Button, Paper, Typography } from "@mui/material";
|
|
|
|
import { clearAuthToken, decodeJwtPayload, getAuthToken, setAuthToken } from "../auth";
|
|
import { useToast } from "../toast";
|
|
|
|
declare global {
|
|
interface Window {
|
|
google?: any;
|
|
}
|
|
}
|
|
|
|
function loadGoogleScript(): Promise<void> {
|
|
return new Promise((resolve, reject) => {
|
|
if (window.google?.accounts?.id) return resolve();
|
|
const existing = document.getElementById("google-gsi");
|
|
if (existing) {
|
|
existing.addEventListener("load", () => resolve());
|
|
return;
|
|
}
|
|
const s = document.createElement("script");
|
|
s.id = "google-gsi";
|
|
s.src = "https://accounts.google.com/gsi/client";
|
|
s.async = true;
|
|
s.defer = true;
|
|
s.onload = () => resolve();
|
|
s.onerror = () => reject(new Error("Failed to load Google script"));
|
|
document.head.appendChild(s);
|
|
});
|
|
}
|
|
|
|
export default function GoogleAuthCard({ onSignedIn }: { onSignedIn?: () => void }) {
|
|
const { toast } = useToast();
|
|
const [token, setToken] = useState<string | null>(() => getAuthToken());
|
|
|
|
const clientId = (process.env.REACT_APP_GOOGLE_CLIENT_ID || "").trim();
|
|
|
|
const payload = useMemo(() => (token ? decodeJwtPayload(token) : null), [token]);
|
|
const email = payload?.email as string | undefined;
|
|
|
|
useEffect(() => {
|
|
if (!clientId) return;
|
|
void loadGoogleScript()
|
|
.then(() => {
|
|
if (!window.google?.accounts?.id) return;
|
|
window.google.accounts.id.initialize({
|
|
client_id: clientId,
|
|
callback: (resp: any) => {
|
|
if (resp?.credential) {
|
|
setAuthToken(resp.credential);
|
|
setToken(resp.credential);
|
|
toast("Signed in.", "success");
|
|
onSignedIn?.();
|
|
}
|
|
},
|
|
});
|
|
window.google.accounts.id.renderButton(document.getElementById("gsi-btn"), {
|
|
theme: "outline",
|
|
size: "large",
|
|
type: "standard",
|
|
shape: "pill",
|
|
});
|
|
})
|
|
.catch(() => toast("Google auth script failed to load.", "error"));
|
|
}, [clientId, onSignedIn, toast]);
|
|
|
|
return (
|
|
<Paper sx={{ mt: 2, p: 2 }}>
|
|
<Typography variant="h6" sx={{ mb: 1 }}>
|
|
Authentication (Google)
|
|
</Typography>
|
|
|
|
{!clientId && (
|
|
<Typography sx={{ color: "text.secondary" }}>
|
|
Set `REACT_APP_GOOGLE_CLIENT_ID` in your UI environment to enable sign-in.
|
|
</Typography>
|
|
)}
|
|
|
|
{clientId && (
|
|
<Box sx={{ display: "flex", alignItems: "center", gap: 2, flexWrap: "wrap" }}>
|
|
<div id="gsi-btn" />
|
|
{token ? (
|
|
<>
|
|
<Typography sx={{ color: "text.secondary" }}>
|
|
Signed in{email ? ` as ${email}` : ""}.
|
|
</Typography>
|
|
<Button
|
|
variant="outlined"
|
|
onClick={() => {
|
|
clearAuthToken();
|
|
setToken(null);
|
|
toast("Signed out.", "info");
|
|
}}
|
|
>
|
|
Sign out
|
|
</Button>
|
|
</>
|
|
) : (
|
|
<Typography sx={{ color: "text.secondary" }}>
|
|
Sign in to unlock API access.
|
|
</Typography>
|
|
)}
|
|
</Box>
|
|
)}
|
|
</Paper>
|
|
);
|
|
}
|