mirror of
https://github.com/TheRealOwenRees/chess-scribe-website.git
synced 2026-07-23 01:46:57 +00:00
Import study link (#1)
* add bug to todo * disabled import study button if no study ID is selected, but always show if logged in as not to break the layout * basic import of lichess study link * change interface import * Minmax M3 solution to import study chapter/game functionality * change button text * fix toast to display on study and chapter load * add lint and format fix scripts * apply auto formatting * update ignored directories * fix specificity * add rudimentary PDF count metrics * allow docker build * create maintenance mode banner, triggered by env variables
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { useServerFn } from "@tanstack/react-start";
|
||||
import {
|
||||
type ChangeEvent,
|
||||
useCallback,
|
||||
@@ -9,6 +10,7 @@ import { toast } from "react-toastify";
|
||||
import { env } from "#/env.ts";
|
||||
import type { IHeader, IPosition } from "#/interfaces.ts";
|
||||
import { gameReducer, initialGameState } from "#/reducers/gameReducer.ts";
|
||||
import { recordPdfSuccess } from "#/server/pdfMetrics.ts";
|
||||
import { downloadPDF } from "#/utils/pdfUtils.ts";
|
||||
import { buildPgnString, getHeaders } from "#/utils/pgnUtils.ts";
|
||||
import { downloadString } from "#/utils/stringUtils.ts";
|
||||
@@ -16,6 +18,8 @@ import { downloadString } from "#/utils/stringUtils.ts";
|
||||
export const useChessGame = () => {
|
||||
const [gameState, gameDispatch] = useReducer(gameReducer, initialGameState);
|
||||
|
||||
const recordPdfSuccessFn = useServerFn(recordPdfSuccess);
|
||||
|
||||
const [currentPosition, setCurrentPosition] = useState<IPosition>({
|
||||
ply: 0,
|
||||
fen: "",
|
||||
@@ -66,7 +70,7 @@ export const useChessGame = () => {
|
||||
if (response.ok) {
|
||||
downloadPDF(await response.blob());
|
||||
|
||||
// TODO add metrics logger - add success to analytics
|
||||
await recordPdfSuccessFn();
|
||||
|
||||
toast.success("PDF successfully generated!", {
|
||||
toastId: "pdf-success",
|
||||
|
||||
+58
-17
@@ -20,13 +20,35 @@ import { getHeaders } from "#/utils/pgnUtils.ts";
|
||||
|
||||
type Status = "loading" | "success" | "error";
|
||||
|
||||
export type LichessResult<T> =
|
||||
| { ok: true; data: T }
|
||||
| { ok: false; error: string };
|
||||
|
||||
const LICHESS_STUDY_LINK_RE =
|
||||
/^https?:\/\/lichess\.org\/study\/([A-Za-z0-9]{8})(?:\.pgn)?(?:$|\/|\?|#)/i;
|
||||
|
||||
const lichessFetch = async <T>(
|
||||
fn: () => Promise<T | undefined>,
|
||||
fallbackError: string,
|
||||
): Promise<LichessResult<T>> => {
|
||||
try {
|
||||
const data = await fn();
|
||||
if (data === undefined || data === null) {
|
||||
return { ok: false, error: fallbackError };
|
||||
}
|
||||
return { ok: true, data };
|
||||
} catch (err) {
|
||||
return {
|
||||
ok: false,
|
||||
error: err instanceof Error ? err.message : fallbackError,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const useLichess = () => {
|
||||
const { user, setUser } = useLichessUser();
|
||||
const [userStudies, setUserStudies] = useState<IUserStudy[]>([]);
|
||||
const [selectedStudyId, setSelectedStudyId] = useState<string | null>(null);
|
||||
const [studyChapters, setStudyChapters] = useState<
|
||||
IUserStudyChapter[] | null
|
||||
>(null);
|
||||
|
||||
const loginFn = useServerFn(login);
|
||||
const logoutFn = useServerFn(logout);
|
||||
@@ -108,20 +130,23 @@ export const useLichess = () => {
|
||||
return { status, error };
|
||||
};
|
||||
|
||||
const getLichessUserStudies = async () => {
|
||||
if (!user) throw new Error("User not found");
|
||||
const studies: IUserStudy[] | undefined = await getUserStudiesFn();
|
||||
if (!studies) return;
|
||||
setUserStudies(studies);
|
||||
const getLichessUserStudies = async (): Promise<
|
||||
LichessResult<IUserStudy[]>
|
||||
> => {
|
||||
if (!user) return { ok: false, error: "Not signed in to Lichess." };
|
||||
return lichessFetch(() => getUserStudiesFn(), "Failed to load studies.");
|
||||
};
|
||||
|
||||
const getLichessStudyChapters = async ({ studyId }: { studyId: string }) => {
|
||||
if (!user) throw new Error("User not found");
|
||||
const chapters: IUserStudyChapter[] | undefined = await getStudyChaptersFn({
|
||||
data: { studyId },
|
||||
});
|
||||
if (!chapters) return;
|
||||
return chapters;
|
||||
const getLichessStudyChapters = async ({
|
||||
studyId,
|
||||
}: {
|
||||
studyId: string;
|
||||
}): Promise<LichessResult<IUserStudyChapter[]>> => {
|
||||
if (!user) return { ok: false, error: "Not signed in to Lichess." };
|
||||
return lichessFetch(
|
||||
() => getStudyChaptersFn({ data: { studyId } }),
|
||||
"Failed to load chapters.",
|
||||
);
|
||||
};
|
||||
|
||||
// return headers and pgn from the Lichess chapter
|
||||
@@ -144,6 +169,23 @@ export const useLichess = () => {
|
||||
return { headers, pgn };
|
||||
};
|
||||
|
||||
// parse and process lichess study link
|
||||
const parseLichessStudyLink = (studyLink: string): LichessResult<string> => {
|
||||
const trimmed = studyLink.trim();
|
||||
|
||||
if (!trimmed) {
|
||||
return { ok: false, error: "Please paste a Lichess study link." };
|
||||
}
|
||||
|
||||
const match = trimmed.match(LICHESS_STUDY_LINK_RE);
|
||||
|
||||
if (!match) {
|
||||
return { ok: false, error: "Invalid Lichess study link." };
|
||||
}
|
||||
|
||||
return { ok: true, data: match[1] };
|
||||
};
|
||||
|
||||
return {
|
||||
lichessLogin,
|
||||
lichessLogout,
|
||||
@@ -158,8 +200,7 @@ export const useLichess = () => {
|
||||
setSelectedStudyId,
|
||||
selectedStudyId,
|
||||
getLichessStudyChapters,
|
||||
studyChapters,
|
||||
setStudyChapters,
|
||||
loadLichessStudyChapter,
|
||||
parseLichessStudyLink,
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user