First Commit
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user