diff --git a/src/levelcode/pages/ProviderSignIn.tsx b/src/levelcode/pages/ProviderSignIn.tsx
index cabd1c7..3f3c241 100644
--- a/src/levelcode/pages/ProviderSignIn.tsx
+++ b/src/levelcode/pages/ProviderSignIn.tsx
@@ -1,29 +1,55 @@
+import type { ReactNode } from "react";
import { API_BASE } from "../api";
-// "Continue with Google" for the LevelCode Cloud login.
+// Social sign-in for the LevelCode Cloud login: Google + GitHub.
//
-// A real (top-level GET), NOT an api() fetch and NOT the GIS id_token button: Rails' oauth_start
-// 302s to Google and relies on the session cookie it sets to carry the CSRF `state`, neither of which
-// survives an XHR. As a link it keeps native semantics too — keyboard activation, Cmd/Ctrl-click to
-// open in a new tab, and it works without JS.
+// Each is a real (top-level GET), NOT an api() fetch and NOT a GIS-style token button: Rails'
+// oauth_start 302s to the provider and relies on the session cookie it sets to carry the CSRF `state`,
+// neither of which survives an XHR. As links they keep native semantics too — keyboard, Cmd/Ctrl-click
+// to open in a new tab, works without JS.
//
-// The editor's redirect_uri + code_challenge are a PKCE PAIR: forwarded only when BOTH are present, so a
-// partial (unbound) handoff never reaches the backend. Absent — a plain web login — oauth_start just
-// opens a web session. The backend already accepts `google` here; GitHub would come "free" the same way.
+// The editor's redirect_uri + code_challenge are a PKCE PAIR, forwarded only when BOTH are present (see
+// oauthStartUrl) so a partial handoff never reaches the backend. Both providers share the exact same
+// oauth_start/oauth_callback path — the callback dispatches on the stashed provider.
export default function ProviderSignIn({
redirectUri,
codeChallenge,
}: {
redirectUri?: string;
codeChallenge?: string;
+}) {
+ return (
+
+ );
+}
+
+function ProviderLink({
+ provider,
+ label,
+ redirectUri,
+ codeChallenge,
+ children,
+}: {
+ provider: string;
+ label: string;
+ redirectUri?: string;
+ codeChallenge?: string;
+ children: ReactNode;
}) {
return (
-
- Continue with Google
+ {children}
+ {label}
);
}
@@ -62,3 +88,11 @@ function GoogleG() {
);
}
+
+function GitHubMark() {
+ return (
+
+ );
+}