99 lines
3.7 KiB
TypeScript
99 lines
3.7 KiB
TypeScript
"use client";
|
|
|
|
/**
|
|
* OAuthButtons — "Continue with …" row used above both LoginForm and
|
|
* SignUpForm. Clicking a button performs a top-level navigation to the
|
|
* BFF's /oauth/<provider>/start endpoint, which kicks off the
|
|
* Authorization Code flow. The BFF handles the round-trip and 302s back
|
|
* to ${frontend}${next} with the session cookie already set.
|
|
*
|
|
* Top-level navigation (not fetch) is intentional: cross-origin redirects
|
|
* to Google / Facebook can't happen from XHR, and we need the BFF to set
|
|
* the cookie before the browser lands back on the frontend.
|
|
*
|
|
* To re-enable a provider that's currently commented out, uncomment its
|
|
* <a> block in the grid below.
|
|
*/
|
|
|
|
const BFF_BASE =
|
|
process.env.NEXT_PUBLIC_BFF_URL || "http://localhost:8080";
|
|
|
|
type Props = {
|
|
/** Frontend path to land on after the round-trip. Defaults to /u/me. */
|
|
next?: string;
|
|
/** Whether to render a divider with "or continue with email" below. */
|
|
showDivider?: boolean;
|
|
};
|
|
|
|
export function OAuthButtons({ next, showDivider = true }: Props) {
|
|
function startUrl(provider: "google" | "facebook"): string {
|
|
const u = new URL(`${BFF_BASE}/api/v1/auth/oauth/${provider}/start`);
|
|
if (next) u.searchParams.set("next", next);
|
|
return u.toString();
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-3">
|
|
<div className="grid grid-cols-1 gap-2">
|
|
<a
|
|
href={startUrl("google")}
|
|
className="h-11 inline-flex items-center justify-center gap-2 rounded-md border border-border bg-card text-sm font-medium text-foreground hover:bg-muted transition-colors"
|
|
>
|
|
<GoogleIcon className="size-4" />
|
|
Login with Google
|
|
</a>
|
|
<a
|
|
href={startUrl("facebook")}
|
|
className="h-11 inline-flex items-center justify-center gap-2 rounded-md border border-border bg-card text-sm font-medium text-foreground hover:bg-muted transition-colors"
|
|
>
|
|
<FacebookIcon className="size-4" />
|
|
Login with Facebook
|
|
</a>
|
|
</div>
|
|
{showDivider ? (
|
|
<div className="relative flex items-center py-1">
|
|
<span className="flex-grow border-t border-border" />
|
|
<span className="mx-3 text-xs uppercase tracking-wider text-muted-foreground">
|
|
or
|
|
</span>
|
|
<span className="flex-grow border-t border-border" />
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function GoogleIcon({ className }: { className?: string }) {
|
|
return (
|
|
<svg className={className} viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden>
|
|
<path
|
|
fill="#4285F4"
|
|
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 0 1-2.2 3.32v2.76h3.56c2.08-1.92 3.28-4.74 3.28-8.09Z"
|
|
/>
|
|
<path
|
|
fill="#34A853"
|
|
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.56-2.76c-.99.66-2.25 1.06-3.72 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84A11 11 0 0 0 12 23Z"
|
|
/>
|
|
<path
|
|
fill="#FBBC05"
|
|
d="M5.84 14.11A6.59 6.59 0 0 1 5.5 12c0-.73.13-1.45.34-2.11V7.05H2.18A11 11 0 0 0 1 12c0 1.78.43 3.46 1.18 4.95l3.66-2.84Z"
|
|
/>
|
|
<path
|
|
fill="#EA4335"
|
|
d="M12 5.38c1.62 0 3.06.56 4.21 1.65l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.05l3.66 2.84C6.71 7.31 9.14 5.38 12 5.38Z"
|
|
/>
|
|
</svg>
|
|
);
|
|
}
|
|
|
|
function FacebookIcon({ className }: { className?: string }) {
|
|
return (
|
|
<svg className={className} viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden>
|
|
<path
|
|
fill="#1877F2"
|
|
d="M24 12a12 12 0 1 0-13.88 11.85V15.47H7.08V12h3.04V9.36c0-3 1.79-4.67 4.53-4.67 1.31 0 2.69.24 2.69.24v2.96H15.83c-1.49 0-1.95.92-1.95 1.87V12h3.32l-.53 3.47h-2.79v8.38A12 12 0 0 0 24 12Z"
|
|
/>
|
|
</svg>
|
|
);
|
|
}
|