feat(auth): unify sign-in options
This commit is contained in:
@@ -47,7 +47,7 @@ function loadGoogleScript(): Promise<void> {
|
||||
});
|
||||
}
|
||||
|
||||
export default function GoogleAuthCard({ onSignedIn }: { onSignedIn?: () => void }) {
|
||||
export default function GoogleAuthCard({ onSignedIn, presentation = "account" }: { onSignedIn?: () => void; presentation?: "account" | "sign-in" }) {
|
||||
const { toast } = useToast();
|
||||
const { t } = useI18n();
|
||||
const [me, setMe] = useState<MeResponse | null>(null);
|
||||
@@ -57,8 +57,9 @@ export default function GoogleAuthCard({ onSignedIn }: { onSignedIn?: () => void
|
||||
const hostRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const clientId = (process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID || "").trim();
|
||||
const signInOnly = presentation === "sign-in";
|
||||
const signedIn = Boolean(me?.provider);
|
||||
const actionLabel = !signedIn
|
||||
const actionLabel = signInOnly || !signedIn
|
||||
? t("continueWithGoogle")
|
||||
: me?.provider === "local" && !me?.googleLink?.linked
|
||||
? t("linkWithGoogle")
|
||||
@@ -74,23 +75,25 @@ export default function GoogleAuthCard({ onSignedIn }: { onSignedIn?: () => void
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (signInOnly) return;
|
||||
void refreshMe();
|
||||
api.get<{ allowRegistration: boolean }>("/auth/config").then((res) => {
|
||||
setAllowRegistration(Boolean(res.data?.allowRegistration));
|
||||
}).catch(() => setAllowRegistration(false));
|
||||
}, []);
|
||||
}, [signInOnly]);
|
||||
|
||||
useEffect(() => {
|
||||
if (signInOnly) return;
|
||||
const onAuthChanged = () => { void refreshMe(); };
|
||||
window.addEventListener("auth-changed", onAuthChanged);
|
||||
return () => window.removeEventListener("auth-changed", onAuthChanged);
|
||||
}, []);
|
||||
}, [signInOnly]);
|
||||
|
||||
useEffect(() => {
|
||||
const host = hostRef.current;
|
||||
if (!clientId || !host) return;
|
||||
|
||||
const shouldRenderButton = !signedIn || (me?.provider === "local" && !me?.googleLink?.linked);
|
||||
const shouldRenderButton = signInOnly || !signedIn || (me?.provider === "local" && !me?.googleLink?.linked);
|
||||
host.replaceChildren();
|
||||
if (!shouldRenderButton) return;
|
||||
|
||||
@@ -106,7 +109,7 @@ export default function GoogleAuthCard({ onSignedIn }: { onSignedIn?: () => void
|
||||
if (!credential) return;
|
||||
setWorking(true);
|
||||
try {
|
||||
if (me?.provider === "local") {
|
||||
if (!signInOnly && me?.provider === "local") {
|
||||
const res = await api.post<{ linked: boolean; email?: string | null }>("/auth/google/link", { token: credential, rememberMe: getAuthPersistencePreference() === "local" });
|
||||
toast(res.data?.email ? t("googleLinkedSuccessWithEmail", { email: res.data.email }) : t("googleLinkedSuccess"), "success");
|
||||
await refreshMe();
|
||||
@@ -132,7 +135,7 @@ export default function GoogleAuthCard({ onSignedIn }: { onSignedIn?: () => void
|
||||
size: "large",
|
||||
type: "standard",
|
||||
shape: "pill",
|
||||
text: me?.provider === "local" ? "continue_with" : "signin_with",
|
||||
text: !signInOnly && me?.provider === "local" ? "continue_with" : "signin_with",
|
||||
});
|
||||
})
|
||||
.catch(() => toast(t("googleScriptLoadFailed"), "error"));
|
||||
@@ -141,10 +144,29 @@ export default function GoogleAuthCard({ onSignedIn }: { onSignedIn?: () => void
|
||||
active = false;
|
||||
host.replaceChildren();
|
||||
};
|
||||
}, [clientId, me?.provider, me?.googleLink?.linked, onSignedIn, signedIn, toast, t]);
|
||||
}, [clientId, me?.provider, me?.googleLink?.linked, onSignedIn, signInOnly, signedIn, toast, t]);
|
||||
|
||||
const signedInName = me?.userName || me?.displayName || [me?.firstName, me?.lastName].filter(Boolean).join(" ") || me?.email || "";
|
||||
|
||||
if (signInOnly) {
|
||||
if (pendingToken) {
|
||||
return <TwoFactorChallenge
|
||||
pendingToken={pendingToken}
|
||||
onCancel={() => setPendingToken(null)}
|
||||
onSuccess={() => {
|
||||
setPendingToken(null);
|
||||
window.dispatchEvent(new Event("auth-changed"));
|
||||
toast(t("googleSignedIn"), "success");
|
||||
onSignedIn?.();
|
||||
}}
|
||||
/>;
|
||||
}
|
||||
|
||||
return clientId
|
||||
? <Box aria-label={actionLabel} aria-busy={working} sx={{ display: "flex", justifyContent: "center", minHeight: 40 }}><div ref={hostRef} /></Box>
|
||||
: <Button fullWidth variant="outlined" disabled>{actionLabel}</Button>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Paper sx={{ mt: 2, p: 2 }}>
|
||||
<Typography variant="h6" sx={{ mb: 1 }}>
|
||||
|
||||
@@ -32,7 +32,7 @@ export function getMicrosoftMsalInstance(clientId: string): PublicClientApplicat
|
||||
return msalInstance;
|
||||
}
|
||||
|
||||
export default function MicrosoftAuthCard({ onSignedIn }: { onSignedIn?: () => void }) {
|
||||
export default function MicrosoftAuthCard({ onSignedIn, presentation = "account" }: { onSignedIn?: () => void; presentation?: "account" | "sign-in" }) {
|
||||
const { toast } = useToast();
|
||||
const { t } = useI18n();
|
||||
const [me, setMe] = useState<MeResponse | null>(null);
|
||||
@@ -41,8 +41,9 @@ export default function MicrosoftAuthCard({ onSignedIn }: { onSignedIn?: () => v
|
||||
const [currentPassword, setCurrentPassword] = useState("");
|
||||
|
||||
const clientId = (process.env.NEXT_PUBLIC_MICROSOFT_CLIENT_ID || "").trim();
|
||||
const signInOnly = presentation === "sign-in";
|
||||
const signedIn = Boolean(me?.provider);
|
||||
const actionLabel = !signedIn
|
||||
const actionLabel = signInOnly || !signedIn
|
||||
? t("continueWithMicrosoft")
|
||||
: me?.provider === "local" && !me?.microsoftLink?.linked
|
||||
? t("linkWithMicrosoft")
|
||||
@@ -58,14 +59,16 @@ export default function MicrosoftAuthCard({ onSignedIn }: { onSignedIn?: () => v
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (signInOnly) return;
|
||||
void refreshMe();
|
||||
}, []);
|
||||
}, [signInOnly]);
|
||||
|
||||
useEffect(() => {
|
||||
if (signInOnly) return;
|
||||
const onAuthChanged = () => { void refreshMe(); };
|
||||
window.addEventListener("auth-changed", onAuthChanged);
|
||||
return () => window.removeEventListener("auth-changed", onAuthChanged);
|
||||
}, []);
|
||||
}, [signInOnly]);
|
||||
|
||||
async function handleSignIn() {
|
||||
if (!clientId) return;
|
||||
@@ -77,7 +80,7 @@ export default function MicrosoftAuthCard({ onSignedIn }: { onSignedIn?: () => v
|
||||
const idToken = result.idToken;
|
||||
if (!idToken) throw new Error(t("microsoftAuthFailed"));
|
||||
|
||||
if (me?.provider === "local") {
|
||||
if (!signInOnly && me?.provider === "local") {
|
||||
const res = await api.post<{ linked: boolean; email?: string | null }>("/auth/microsoft/link", { token: idToken, currentPassword });
|
||||
toast(res.data?.email ? t("microsoftLinkedSuccessWithEmail", { email: res.data.email }) : t("microsoftLinkedSuccess"), "success");
|
||||
clearAuthClientState();
|
||||
@@ -107,6 +110,23 @@ export default function MicrosoftAuthCard({ onSignedIn }: { onSignedIn?: () => v
|
||||
|
||||
const signedInName = me?.userName || me?.displayName || [me?.firstName, me?.lastName].filter(Boolean).join(" ") || me?.email || "";
|
||||
|
||||
if (signInOnly) {
|
||||
if (pendingToken) {
|
||||
return <TwoFactorChallenge
|
||||
pendingToken={pendingToken}
|
||||
onCancel={() => setPendingToken(null)}
|
||||
onSuccess={() => {
|
||||
setPendingToken(null);
|
||||
window.dispatchEvent(new Event("auth-changed"));
|
||||
toast(t("microsoftSignedIn"), "success");
|
||||
onSignedIn?.();
|
||||
}}
|
||||
/>;
|
||||
}
|
||||
|
||||
return <Button fullWidth variant="outlined" disabled={working || !clientId} onClick={() => void handleSignIn()}>{actionLabel}</Button>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Paper sx={{ mt: 2, p: 2 }}>
|
||||
<Typography variant="h6" sx={{ mb: 1 }}>
|
||||
|
||||
Reference in New Issue
Block a user