basic loading of user's studies

This commit is contained in:
2026-06-09 10:31:59 +02:00
parent 0b1a0e6457
commit 7b2bf3161b
7 changed files with 111 additions and 16 deletions
+2 -2
View File
@@ -1,8 +1,8 @@
import { useLichessUser } from "#/context/LichessUserContext.tsx"; import { useLichessUser } from "#/context/LichessUserContext.tsx";
import { useLichessOAuth } from "#/hooks/useLichessOAuth.ts"; import { useLichess } from "#/hooks/useLichess.ts";
const LichessButton = () => { const LichessButton = () => {
const { lichessLogin, lichessLogout } = useLichessOAuth(); const { lichessLogin, lichessLogout } = useLichess();
const { user } = useLichessUser(); const { user } = useLichessUser();
const onClickHandler = () => { const onClickHandler = () => {
+5
View File
@@ -0,0 +1,5 @@
const LichessStudyUrlInput = () => {
return <input placeholder="Lichess Study URL" className="" />;
};
export default LichessStudyUrlInput;
+21
View File
@@ -0,0 +1,21 @@
import { useLichess } from "#/hooks/useLichess.ts";
const SelectLichessStudy = () => {
const { getLichessUserStudies } = useLichess();
const onClickHandler = async () => {
await getLichessUserStudies();
};
return (
<button
type="button"
className="btn btn-secondary"
onClick={onClickHandler}
>
Select Lichess Study
</button>
);
};
export default SelectLichessStudy;
@@ -6,6 +6,7 @@ import {
getSession, getSession,
getToken, getToken,
getUser, getUser,
getUserStudies,
login, login,
logout, logout,
setSession, setSession,
@@ -17,8 +18,8 @@ interface ITokenData {
token_type: string; token_type: string;
} }
export const useLichessOAuth = () => { export const useLichess = () => {
const { setUser } = useLichessUser(); const { user, setUser } = useLichessUser();
const loginFn = useServerFn(login); const loginFn = useServerFn(login);
const logoutFn = useServerFn(logout); const logoutFn = useServerFn(logout);
@@ -26,6 +27,7 @@ export const useLichessOAuth = () => {
const getUserFn = useServerFn(getUser); const getUserFn = useServerFn(getUser);
const setSessionFn = useServerFn(setSession); const setSessionFn = useServerFn(setSession);
const getSessionFn = useServerFn(getSession); const getSessionFn = useServerFn(getSession);
const getUserStudiesFn = useServerFn(getUserStudies);
const lichessAccessToken = async ({ code }: { code: string }) => { const lichessAccessToken = async ({ code }: { code: string }) => {
return accessTokenFn({ data: { code } }); return accessTokenFn({ data: { code } });
@@ -34,11 +36,13 @@ export const useLichessOAuth = () => {
const setLichessUser = async ({ const setLichessUser = async ({
username, username,
id, id,
token,
}: { }: {
username: string; username: string;
id: string; id: string;
token: string;
}) => { }) => {
await setSessionFn({ data: { username, id } }); await setSessionFn({ data: { username, id, token } });
setUser({ username, id, isLoggedIn: true }); setUser({ username, id, isLoggedIn: true });
}; };
@@ -48,9 +52,7 @@ export const useLichessOAuth = () => {
}); });
}; };
const lichessLogin = async () => { const lichessLogin = async () => await loginFn();
await loginFn();
};
const lichessLogout = async () => { const lichessLogout = async () => {
await logoutFn(); await logoutFn();
@@ -70,12 +72,22 @@ export const useLichessOAuth = () => {
(async () => { (async () => {
const tokenData = await lichessAccessToken({ code }); const tokenData = await lichessAccessToken({ code });
const userData = await getLichessUser({ tokenData }); const userData = await getLichessUser({ tokenData });
await setLichessUser({ username: userData.username, id: userData.id });
await setLichessUser({
username: userData.username,
id: userData.id,
token: tokenData.access_token,
});
await navigate({ to: "/chessboard" }); await navigate({ to: "/chessboard" });
})(); })();
}, [navigate, code]); }, [navigate, code]);
}; };
const getLichessUserStudies = async () => {
if (!user) throw new Error("User not found");
await getUserStudiesFn();
};
return { return {
lichessLogin, lichessLogin,
lichessLogout, lichessLogout,
@@ -84,5 +96,6 @@ export const useLichessOAuth = () => {
setLichessUser, setLichessUser,
getLichessSession, getLichessSession,
useLichessCallback, useLichessCallback,
getLichessUserStudies,
}; };
}; };
+3 -3
View File
@@ -1,14 +1,14 @@
import { getRouteApi } from "@tanstack/react-router"; import { getRouteApi } from "@tanstack/react-router";
import { useLichessOAuth } from "#/hooks/useLichessOAuth.ts"; import { useLichess } from "#/hooks/useLichess.ts";
const routeApi = getRouteApi("/callback"); const routeApi = getRouteApi("/callback");
const Callback = () => { const Callback = () => {
const { useLichessCallback } = useLichessOAuth(); const { useLichessCallback } = useLichess();
const { code } = routeApi.useSearch(); const { code } = routeApi.useSearch();
useLichessCallback({ code }).then(); useLichessCallback({ code }).then();
return <div>{code}</div>; return <div>Loading...</div>;
}; };
export default Callback; export default Callback;
+3
View File
@@ -2,7 +2,9 @@ import { lazy, Suspense } from "react";
import CustomHeaders from "#/components/CustomHeaders.tsx"; import CustomHeaders from "#/components/CustomHeaders.tsx";
import HeaderFields from "#/components/HeaderFields.tsx"; import HeaderFields from "#/components/HeaderFields.tsx";
import LichessButton from "#/components/LichessButton.tsx"; import LichessButton from "#/components/LichessButton.tsx";
import LichessStudyUrlInput from "#/components/LichessStudyUrlInput.tsx";
import Section from "#/components/Section.tsx"; import Section from "#/components/Section.tsx";
import SelectLichessStudy from "#/components/SelectLichessStudy.tsx";
import { useChessGame } from "#/hooks/useChessGame.ts"; import { useChessGame } from "#/hooks/useChessGame.ts";
const PgnViewer = lazy(() => import("#/components/PgnViewer.tsx")); const PgnViewer = lazy(() => import("#/components/PgnViewer.tsx"));
@@ -44,6 +46,7 @@ const Chessboard = () => {
className="file-input max-w-xs" className="file-input max-w-xs"
onChange={handleLoadPgn} onChange={handleLoadPgn}
/> />
<SelectLichessStudy /> or <LichessStudyUrlInput />
</Section> </Section>
<div className="grid lg:grid-cols-2 gap-4"> <div className="grid lg:grid-cols-2 gap-4">
+57 -4
View File
@@ -22,7 +22,11 @@ export const getSession = createServerFn({ method: "GET" }).handler(
const session = getCookie("lichess-session"); const session = getCookie("lichess-session");
if (!session) return null; if (!session) return null;
try { try {
return JSON.parse(session) as { username: string; id: string }; return JSON.parse(session) as {
username: string;
id: string;
token: string;
};
} catch { } catch {
return null; return null;
} }
@@ -30,11 +34,17 @@ export const getSession = createServerFn({ method: "GET" }).handler(
); );
export const setSession = createServerFn({ method: "POST" }) export const setSession = createServerFn({ method: "POST" })
.inputValidator((data: { username: string; id: string }) => data) .inputValidator(
(data: { username: string; id: string; token: string }) => data,
)
.handler(async ({ data }) => { .handler(async ({ data }) => {
setCookie( setCookie(
"lichess-session", "lichess-session",
JSON.stringify({ username: data.username, id: data.id }), JSON.stringify({
username: data.username,
id: data.id,
token: data.token,
}),
{ {
path: "/", path: "/",
httpOnly: true, httpOnly: true,
@@ -86,6 +96,8 @@ export const getToken = createServerFn({ method: "POST" })
export const getUser = createServerFn({ method: "GET" }) export const getUser = createServerFn({ method: "GET" })
.inputValidator((data: { access_token: string }) => data) .inputValidator((data: { access_token: string }) => data)
.handler(async ({ data }) => { .handler(async ({ data }) => {
console.log(data.access_token);
const response = await fetch("https://lichess.org/api/account", { const response = await fetch("https://lichess.org/api/account", {
headers: { headers: {
Authorization: `Bearer ${data.access_token}`, Authorization: `Bearer ${data.access_token}`,
@@ -95,9 +107,26 @@ export const getUser = createServerFn({ method: "GET" })
return response.json(); return response.json();
}); });
const deleteAccessToken = createServerFn().handler(async () => {
const session = await getSession();
const token = session?.token;
if (token) {
const response = await fetch("https://lichess.org/api/token", {
method: "DELETE",
headers: {
Authorization: `Bearer ${token}`,
},
});
return await response.json();
}
});
export const logout = createServerFn().handler(async () => { export const logout = createServerFn().handler(async () => {
await deleteAccessToken();
deleteCookie("lichess-session", { path: "/" }); deleteCookie("lichess-session", { path: "/" });
deleteCookie("lichess-oauth-verifier", { path: "/" }); deleteCookie("lichess-oauth-verifier", { path: "/" }); // cookie shouldn't exist
}); });
export const login = createServerFn({ method: "GET" }).handler(async () => { export const login = createServerFn({ method: "GET" }).handler(async () => {
@@ -125,3 +154,27 @@ export const login = createServerFn({ method: "GET" }).handler(async () => {
href: `https://lichess.org/oauth?${params.toString()}`, href: `https://lichess.org/oauth?${params.toString()}`,
}); });
}); });
export const getUserStudies = createServerFn({ method: "POST" }).handler(
async () => {
const session = await getSession();
if (!session) return;
const response = await fetch(
`https://lichess.org/api/study/by/${session.username}`,
{
headers: {
Authorization: `Bearer ${session.token}`,
},
},
);
const studies = await response.text();
return studies
.trim()
.split("\n")
.map((study) => JSON.parse(study));
},
);