From 11d63b171501b9c29398133b795c3bcc1ae85a1b Mon Sep 17 00:00:00 2001 From: Max Nanis Date: Fri, 11 Sep 2026 18:16:06 -0700 Subject: using RTK with session login. start of magic token request --- jb-ui/.vscode/settings.json | 2 +- jb-ui/src/AuthContext.tsx | 28 ++++++++++++ jb-ui/src/JBApp.tsx | 4 +- jb-ui/src/models/authAPI.ts | 23 ++++++++++ jb-ui/src/pages/Work.tsx | 103 ++++++++++++++++++++++++++++++++++++++++---- jb-ui/src/store.ts | 5 +++ jb/views/auth.py | 20 +++++---- 7 files changed, 164 insertions(+), 21 deletions(-) create mode 100644 jb-ui/src/AuthContext.tsx create mode 100644 jb-ui/src/models/authAPI.ts diff --git a/jb-ui/.vscode/settings.json b/jb-ui/.vscode/settings.json index 4257030..9b8250c 100644 --- a/jb-ui/.vscode/settings.json +++ b/jb-ui/.vscode/settings.json @@ -10,6 +10,6 @@ "editor.formatOnSave": true }, "editor.codeActionsOnSave": { - "source.fixAll.eslint": true + "source.fixAll.eslint": "explicit" } } \ No newline at end of file diff --git a/jb-ui/src/AuthContext.tsx b/jb-ui/src/AuthContext.tsx new file mode 100644 index 0000000..85bcdb0 --- /dev/null +++ b/jb-ui/src/AuthContext.tsx @@ -0,0 +1,28 @@ +// AuthContext.jsx +import { createContext, useContext, useEffect, useState } from "react"; + +const AuthContext = createContext(null); + +export function AuthProvider({ children }) { + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + fetch("/api/me", { credentials: "include" }) + .then((res) => (res.ok ? res.json() : null)) + .then(setUser) + .finally(() => setLoading(false)); + }, []); + + return ( + + {children} + + ); +} + +export function useAuth() { + return useContext(AuthContext); +} diff --git a/jb-ui/src/JBApp.tsx b/jb-ui/src/JBApp.tsx index 7c99315..fd0eeba 100644 --- a/jb-ui/src/JBApp.tsx +++ b/jb-ui/src/JBApp.tsx @@ -225,10 +225,10 @@ function JBApp() { return ( - } /> }> }> - } /> + } /> + } /> {/* } /> */} } /> diff --git a/jb-ui/src/models/authAPI.ts b/jb-ui/src/models/authAPI.ts new file mode 100644 index 0000000..1ac7886 --- /dev/null +++ b/jb-ui/src/models/authAPI.ts @@ -0,0 +1,23 @@ +import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; + +export const authApi = createApi({ + reducerPath: "authApi", + baseQuery: fetchBaseQuery({ + baseUrl: "/auth", + credentials: "include", // sends the httpOnly cookie + }), + tagTypes: ["User"], + endpoints: (builder) => ({ + getMe: builder.query({ + query: () => "/session", + providesTags: ["User"], + }), + // logout: builder.mutation({ + // query: () => ({ url: "/de-session", method: "POST" }), + // invalidatesTags: ["User"], + // }), + }), +}); + +// export const { useGetMeQuery, useLogoutMutation } = authApi; +export const { useGetMeQuery } = authApi; diff --git a/jb-ui/src/pages/Work.tsx b/jb-ui/src/pages/Work.tsx index 32dcc96..e33328e 100644 --- a/jb-ui/src/pages/Work.tsx +++ b/jb-ui/src/pages/Work.tsx @@ -1,3 +1,4 @@ +import { useGetMeQuery } from "@/models/authAPI"; import { BucketTask, OfferwallApi, @@ -828,17 +829,101 @@ const SurveyEnter = () => { ); }; +type RequestState = "idle" | "working" | "sent" | "failed"; + +const Login = () => { + const [email, setEmail] = useState(""); + const [state, setState] = useState("idle"); + const bpuid = useAppSelector((state) => state.app.bpuid); + + // const dispatch = useAppDispatch(); + const submit = async (event: FormEvent) => { + event.preventDefault(); + setState("working"); + + try { + const response = await fetch("/auth/magic-link/request", { + method: "POST", + credentials: "include", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + email: email, + }), + }); + + if (!response.ok) throw new Error("Account-link request failed"); + + const body = (await response.json()) as { magic_link?: string }; + console.log(body.magic_link ?? null); + } catch { + setState("failed"); + } + }; + + return ( +
+

+ Please Login +

+ +
+ setEmail(event.target.value)} + placeholder="hello@jamesbillings67.com" + /> + +
+ + {state === "sent" && ( +

+ Check your email to login. +

+ )} + + {state === "failed" &&

Unable to send the link. Please try again.

} +
+ ); +}; + const Work = () => { const entered = useAppSelector((state) => state.app.currentBucketEntered); - const assignment_id = useAppSelector((state) => state.app.assignment_id); - - if (!assignment_id) { - return ( -

- No Assignment ID found. Please access this page through the Amazon - Mechanical Turk platform. -

- ); + // const assignment_id = useAppSelector((state) => state.app.assignment_id); + // if (!assignment_id) { + // return ( + //

+ // No Assignment ID found. Please access this page through the Amazon + // Mechanical Turk platform. + //

+ // ); + // } + + const { data: user, isLoading, isError } = useGetMeQuery(); + // const location = useLocation(); + + if (isLoading) return
Loading...
; + + if (isError || !user) { + return ; } return <>{entered ? : }; diff --git a/jb-ui/src/store.ts b/jb-ui/src/store.ts index 07cac9e..baf13b7 100644 --- a/jb-ui/src/store.ts +++ b/jb-ui/src/store.ts @@ -1,6 +1,8 @@ import appReducers from "@/models/appSlice"; import grlStatsReducers from "@/models/grlStatsSlice"; import grlEventsReducers from "@/models/grlEventsSlice"; +import { authApi } from "@/models/authAPI"; + import profilingQuestionsReducers from "@/models/profilingQuestionsSlice"; import type { Action, ThunkAction } from "@reduxjs/toolkit"; import { configureStore } from "@reduxjs/toolkit"; @@ -8,10 +10,13 @@ import { configureStore } from "@reduxjs/toolkit"; export const store = configureStore({ reducer: { app: appReducers, + // auth: authReducers, + [authApi.reducerPath]: authApi.reducer, stats: grlStatsReducers, events: grlEventsReducers, profilingQuestions: profilingQuestionsReducers, }, + middleware: (getDefault) => getDefault().concat(authApi.middleware), }); // Infer the type of `store` diff --git a/jb/views/auth.py b/jb/views/auth.py index bf39b51..ce9c1e0 100644 --- a/jb/views/auth.py +++ b/jb/views/auth.py @@ -2,7 +2,7 @@ from typing import Annotated from urllib.parse import urlencode from fastapi import APIRouter, Depends, HTTPException, Response, status -from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse +from fastapi.responses import HTMLResponse, RedirectResponse from jb.api.auth import ( SESSION_COOKIE_NAME, @@ -119,7 +119,7 @@ def link_amt_account(body: AmtAccountLink) -> dict[str, str]: return {} -@auth_router.get("/link-amt/", response_class=HTMLResponse, include_in_schema=False) +@auth_router.get("/debug/", response_class=HTMLResponse, include_in_schema=False) def link_amt_account_landing_page( gr_api: Annotated[GRApiManager, Depends(get_gr_api_manager)], token: str | None = None, @@ -129,13 +129,15 @@ def link_amt_account_landing_page( # TODO! Try catch any of this, and if it fails, show the user a # TODO! failed HTML page. As of now, it shows them a failed JSON response. - if token is None: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="token is required", - ) - _response = RedirectResponse(url="/", status_code=status.HTTP_303_SEE_OTHER) - _exchange_amt_account_link(token=token, response=_response, gr_api=gr_api) + if settings.debug: + if token is None: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="token is required", + ) + + _response = RedirectResponse(url="/", status_code=status.HTTP_303_SEE_OTHER) + _exchange_amt_account_link(token=token, response=_response, gr_api=gr_api) return HTMLResponse( BASE_HTML, -- cgit v1.2.3