here (and for SignUp too)
+ is to avoid an unnecessary "flicker".
+ component here is loaded SSR and is immediately present.
+ Only the "guts" below is lazy loaded. By having the header already
+ present the page feels less flickery at a very affordable cost of
+ allowing this to be part of the main JS bundle.
+ */}
+
}>
+
+
+
+ );
+}
+export function SignUp() {
+ return (
+
+ Loading...}>
+
+
+
+ );
+}
diff --git a/client/src/auth/sign-in.tsx b/client/src/auth/sign-in.tsx
new file mode 100644
index 000000000000..148789756472
--- /dev/null
+++ b/client/src/auth/sign-in.tsx
@@ -0,0 +1,94 @@
+import { useSearchParams } from "react-router-dom";
+
+import { useUserData } from "../user-context";
+import { useLocale } from "../hooks";
+
+export default function SignInApp() {
+ const [searchParams] = useSearchParams();
+ const locale = useLocale();
+ const userData = useUserData();
+ const sp = new URLSearchParams();
+
+ // This is the `?next=` parameter we send into the redirect loop IF you did
+ // not click into this page from an existing one.
+ const defaultNext = `/${locale}/`;
+
+ let next = searchParams.get("next") || defaultNext;
+ if (next.toLowerCase() === window.location.pathname.toLowerCase()) {
+ // It's never OK for the ?next= variable to be to come back here to this page.
+ // Explicitly check that.
+ next = defaultNext;
+ }
+
+ let prefix = "";
+ // When doing local development with Yari, the link to authenticate in Kuma
+ // needs to be absolute. And we also need to send the absolute URL as the
+ // `next` query string parameter so Kuma sends us back when the user has
+ // authenticated there.
+ if (
+ process.env.NODE_ENV === "development" &&
+ process.env.REACT_APP_KUMA_HOST
+ ) {
+ const combined = new URL(next, window.location.href);
+ next = combined.toString();
+ prefix = `http://${process.env.REACT_APP_KUMA_HOST}`;
+ }
+ sp.set("next", next);
+
+ // Temporary just long as Kuma still needs to support sign-up both as
+ // Kuma front-end (HTML) Yari (redirects).
+ // Delete this line once Kuma ONLY deals with Yari in the signup view.
+ sp.set("yarisignup", "1");
+
+ return (
+
+ {/* We need to wait for the userData (/api/v1/whoami) because it will
+ determine what we display.
+ We *could* render on the optimism that people most likely will be
+ on this page because they're NOT signed in. But it feels a bit
+ "ugly" if a page has to change its mind and rerender something
+ completely different without it being due to a user action.
+ */}
+ {userData ? (
+ <>
+ {userData.isAuthenticated ? (
+
+ ) : (
+
You arrived here on this page without the necessary details.
+
+
+ Try starting over the sign-in process
+
+ .
+
+
+ );
+ }
+
+ const signupURL = `/${locale}/users/account/signup`;
+
+ async function submitSignUp() {
+ const formData = new URLSearchParams();
+ formData.set("terms", "1");
+
+ // This is just a temporary thing needed to tell Kuma's signup view
+ // that the request came from (the jamstack) Yari and not the existing
+ // Kuma front-end. Then Kuma knows to certainly only respond with redirects.
+ formData.set("yarisignup", "1");
+
+ // In local development, after you've signed in the `next` query string
+ // might be a full absolute URL that points to `http://localhost.org:3000/...`.
+ // We can safely remove this and just keep the pathname. In production
+ // this will never have to happen.
+ let nextURL = searchParams.get("next");
+ if (!nextURL) {
+ nextURL = `/${locale}/`;
+ } else if (nextURL && nextURL.includes("://")) {
+ nextURL = new URL(nextURL).pathname;
+ }
+ formData.set("next", nextURL);
+
+ formData.set("locale", locale);
+ if (!csrfMiddlewareToken) {
+ throw new Error("CSRF token not set");
+ }
+ let response: Response;
+ try {
+ response = await fetch(signupURL, {
+ method: "POST",
+ headers: {
+ "X-CSRFToken": csrfMiddlewareToken,
+ "Content-Type": "application/x-www-form-urlencoded",
+ },
+ body: formData,
+ });
+ } catch (error) {
+ setSignupError(error);
+ return;
+ }
+
+ if (response.ok) {
+ // This will "force" a new XHR request in the useUserData hook.
+ mutate("/api/v1/whoami");
+
+ navigate(nextURL);
+ } else {
+ setSignupError(new Error(`${response.status} on ${signupURL}`));
+ }
+ }
+
+ return (
+
+ );
+}
+
+function DisplaySignupProvider({ provider }: { provider: string }) {
+ if (!provider) {
+ // Exit early because there's nothing useful we can say
+ return null;
+ }
+ let providerVerbose = provider.charAt(0).toUpperCase() + provider.slice(1);
+ if (provider === "github") {
+ providerVerbose = "GitHub";
+ }
+ return (
+
+ You are signing in to MDN Web Docs with {providerVerbose}.
+
+ );
+}
+
+interface UserDetails {
+ name?: string;
+ avatar_url?: string;
+}
+
+function DisplayUserDetails({ details }: { details: string }) {
+ if (!details) {
+ // Exit early because there's nothing useful we can say
+ return null;
+ }
+
+ const userDetails: UserDetails = JSON.parse(details);
+
+ return (
+