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 { useLichessOAuth } from "#/hooks/useLichessOAuth.ts";
import { useLichess } from "#/hooks/useLichess.ts";
const LichessButton = () => {
const { lichessLogin, lichessLogout } = useLichessOAuth();
const { lichessLogin, lichessLogout } = useLichess();
const { user } = useLichessUser();
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,
getToken,
getUser,
getUserStudies,
login,
logout,
setSession,
@@ -17,8 +18,8 @@ interface ITokenData {
token_type: string;
}
export const useLichessOAuth = () => {
const { setUser } = useLichessUser();
export const useLichess = () => {
const { user, setUser } = useLichessUser();
const loginFn = useServerFn(login);
const logoutFn = useServerFn(logout);
@@ -26,6 +27,7 @@ export const useLichessOAuth = () => {
const getUserFn = useServerFn(getUser);
const setSessionFn = useServerFn(setSession);
const getSessionFn = useServerFn(getSession);
const getUserStudiesFn = useServerFn(getUserStudies);
const lichessAccessToken = async ({ code }: { code: string }) => {
return accessTokenFn({ data: { code } });
@@ -34,11 +36,13 @@ export const useLichessOAuth = () => {
const setLichessUser = async ({
username,
id,
token,
}: {
username: string;
id: string;
token: string;
}) => {
await setSessionFn({ data: { username, id } });
await setSessionFn({ data: { username, id, token } });
setUser({ username, id, isLoggedIn: true });
};
@@ -48,9 +52,7 @@ export const useLichessOAuth = () => {
});
};
const lichessLogin = async () => {
await loginFn();
};
const lichessLogin = async () => await loginFn();
const lichessLogout = async () => {
await logoutFn();
@@ -70,12 +72,22 @@ export const useLichessOAuth = () => {
(async () => {
const tokenData = await lichessAccessToken({ code });
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" });
})();
}, [navigate, code]);
};
const getLichessUserStudies = async () => {
if (!user) throw new Error("User not found");
await getUserStudiesFn();
};
return {
lichessLogin,
lichessLogout,
@@ -84,5 +96,6 @@ export const useLichessOAuth = () => {
setLichessUser,
getLichessSession,
useLichessCallback,
getLichessUserStudies,
};
};
+3 -3
View File
@@ -1,14 +1,14 @@
import { getRouteApi } from "@tanstack/react-router";
import { useLichessOAuth } from "#/hooks/useLichessOAuth.ts";
import { useLichess } from "#/hooks/useLichess.ts";
const routeApi = getRouteApi("/callback");
const Callback = () => {
const { useLichessCallback } = useLichessOAuth();
const { useLichessCallback } = useLichess();
const { code } = routeApi.useSearch();
useLichessCallback({ code }).then();
return <div>{code}</div>;
return <div>Loading...</div>;
};
export default Callback;
+3
View File
@@ -2,7 +2,9 @@ import { lazy, Suspense } from "react";
import CustomHeaders from "#/components/CustomHeaders.tsx";
import HeaderFields from "#/components/HeaderFields.tsx";
import LichessButton from "#/components/LichessButton.tsx";
import LichessStudyUrlInput from "#/components/LichessStudyUrlInput.tsx";
import Section from "#/components/Section.tsx";
import SelectLichessStudy from "#/components/SelectLichessStudy.tsx";
import { useChessGame } from "#/hooks/useChessGame.ts";
const PgnViewer = lazy(() => import("#/components/PgnViewer.tsx"));
@@ -44,6 +46,7 @@ const Chessboard = () => {
className="file-input max-w-xs"
onChange={handleLoadPgn}
/>
<SelectLichessStudy /> or <LichessStudyUrlInput />
</Section>
<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");
if (!session) return null;
try {
return JSON.parse(session) as { username: string; id: string };
return JSON.parse(session) as {
username: string;
id: string;
token: string;
};
} catch {
return null;
}
@@ -30,11 +34,17 @@ export const getSession = createServerFn({ method: "GET" }).handler(
);
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 }) => {
setCookie(
"lichess-session",
JSON.stringify({ username: data.username, id: data.id }),
JSON.stringify({
username: data.username,
id: data.id,
token: data.token,
}),
{
path: "/",
httpOnly: true,
@@ -86,6 +96,8 @@ export const getToken = createServerFn({ method: "POST" })
export const getUser = createServerFn({ method: "GET" })
.inputValidator((data: { access_token: string }) => data)
.handler(async ({ data }) => {
console.log(data.access_token);
const response = await fetch("https://lichess.org/api/account", {
headers: {
Authorization: `Bearer ${data.access_token}`,
@@ -95,9 +107,26 @@ export const getUser = createServerFn({ method: "GET" })
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 () => {
await deleteAccessToken();
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 () => {
@@ -125,3 +154,27 @@ export const login = createServerFn({ method: "GET" }).handler(async () => {
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));
},
);