aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMax Nanis2026-09-13 13:15:03 -0700
committerMax Nanis2026-09-13 13:15:03 -0700
commitd7f2bd71b1546ba4142f6d4631bba5212b25f873 (patch)
tree1cae63d9d7a643bc4cfe0cca8d520302842868e7
parentf674bf9b06c73b823badd84d1455d6c4b923ca82 (diff)
downloadamt-jb-d7f2bd71b1546ba4142f6d4631bba5212b25f873.tar.gz
amt-jb-d7f2bd71b1546ba4142f6d4631bba5212b25f873.zip
"/auth/link-amt/" to POST
-rw-r--r--jb-ui/src/JBApp.tsx2
-rw-r--r--jb-ui/src/pages/MagicAMTLink.tsx46
2 files changed, 48 insertions, 0 deletions
diff --git a/jb-ui/src/JBApp.tsx b/jb-ui/src/JBApp.tsx
index c55cd73..083e52d 100644
--- a/jb-ui/src/JBApp.tsx
+++ b/jb-ui/src/JBApp.tsx
@@ -33,6 +33,7 @@ import { addCashoutMethods } from "@/models/cashoutMethodsSlice";
import { addEvent } from "@/models/grlEventsSlice";
import { addStatsData } from "@/models/grlStatsSlice";
import MagicLink from "@/pages/MagicLink";
+import MagicAMTLink from "@/pages/MagicAMTLink";
import Result from "@/pages/Result";
import clsx from "clsx";
@@ -274,6 +275,7 @@ function JBApp() {
<Route element={<Layout />}>
<Route path="/" element={<Work />} />
<Route path="/auth/magic-link/" element={<MagicLink />} />
+ <Route path="/auth/link-amt/" element={<MagicAMTLink />} />
<Route path="/preview/" element={<Transfer />} />
<Route path="/work/" element={<Transfer />} />
diff --git a/jb-ui/src/pages/MagicAMTLink.tsx b/jb-ui/src/pages/MagicAMTLink.tsx
new file mode 100644
index 0000000..fdd7437
--- /dev/null
+++ b/jb-ui/src/pages/MagicAMTLink.tsx
@@ -0,0 +1,46 @@
+import { useEffect, useRef, useState } from "react";
+
+type ExchangeState = "working" | "failed" | "missing";
+
+const MagicLink = function () {
+ const started = useRef(false);
+ const [state, setState] = useState<ExchangeState>("working");
+
+ useEffect(() => {
+ // React Strict Mode runs effects twice in development. Never redeem twice.
+ if (started.current) return;
+ started.current = true;
+
+ const params = new URLSearchParams(window.location.search);
+ const token = params.get("token");
+ if (!token) {
+ setState("missing");
+ return;
+ }
+
+ // Keep the credential out of browser history and subsequent Referer headers.
+ window.history.replaceState({}, "", window.location.pathname);
+
+ void fetch("/auth/link-amt/exchange/", {
+ method: "POST",
+ credentials: "include",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ token: token }),
+ })
+ .then((response) => {
+ if (!response.ok) throw new Error("Magic-link exchange failed");
+ window.location.replace("/");
+ })
+ .catch(() => setState("failed"));
+ }, []);
+
+ if (state === "missing") {
+ return <p>This login link is missing its token.</p>;
+ }
+ if (state === "failed") {
+ return <p>This login link is invalid or has expired.</p>;
+ }
+ return <p>Signing you in…</p>;
+};
+
+export default MagicLink;