mirror of
https://github.com/TheRealOwenRees/chess-scribe-website.git
synced 2026-07-23 09:56:57 +00:00
Compare commits
9 Commits
55989f09c0
...
8d19564a4e
| Author | SHA1 | Date | |
|---|---|---|---|
|
8d19564a4e
|
|||
|
b94edbedb2
|
|||
|
cba86118a6
|
|||
|
3dcdc52052
|
|||
|
0903ac12c6
|
|||
|
80c2cce4de
|
|||
|
ed820b7fd1
|
|||
|
eb49b6248d
|
|||
|
0f61400272
|
@@ -1,9 +1,12 @@
|
|||||||
- og / json-sd
|
- og / json-sd
|
||||||
- Lichess login
|
|
||||||
- logger
|
- logger
|
||||||
- bring sections headers on all pages other than home page further up (reduce top margin / padding)
|
- bring sections headers on all pages other than home page further up (reduce top margin / padding)
|
||||||
- set game current position on game load to ply 0 and whatever the FEN is (starting position probably, but could be custom position)
|
- set game current position on game load to ply 0 and whatever the FEN is (starting position probably, but could be custom position)
|
||||||
- privacy policy - have no access to anything you generate, only keep a count of success/failed pdf generations with timestamp and error message
|
- privacy policy - have no access to anything you generate, only keep a count of success/failed pdf generations with timestamp and error message
|
||||||
- a11y and lighthouse fixes
|
- a11y and lighthouse fixes
|
||||||
- check wcag compliance
|
- check wcag compliance
|
||||||
- white background?
|
- Callback page - improve loading display in suspense
|
||||||
|
- 429 lichess error handling
|
||||||
|
- add throw errors or return errors in server code
|
||||||
|
- try/catch where needed in client code
|
||||||
|
- generate state and check it against the state returned from lichess https://lichess.org/api#tag/oauth/GET/oauth
|
||||||
+1
-1
@@ -6,7 +6,7 @@
|
|||||||
"#/*": "./src/*"
|
"#/*": "./src/*"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev --port 3000",
|
"dev": "vite dev --port 3000 --host",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ interface IProps {
|
|||||||
|
|
||||||
const CustomHeaders = ({ headers, updateHeaders }: IProps) => {
|
const CustomHeaders = ({ headers, updateHeaders }: IProps) => {
|
||||||
return (
|
return (
|
||||||
<div className="mt-4 w-full max-w-175">
|
<div className="mt-4 w-full max-w-150">
|
||||||
<details className="bg-(--neutral-content)/20 p-4 rounded-lg">
|
<details className="bg-(--neutral-content)/20 p-4 rounded-lg">
|
||||||
<summary>
|
<summary>
|
||||||
Custom Headers (PDF)
|
Custom Headers (PDF)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ interface IProps {
|
|||||||
|
|
||||||
const HeaderFields = ({ headers, updateHeaders }: IProps) => {
|
const HeaderFields = ({ headers, updateHeaders }: IProps) => {
|
||||||
return (
|
return (
|
||||||
<div className="mt-4 w-full max-w-175">
|
<div className="mt-4 w-full max-w-150">
|
||||||
<details className="bg-(--neutral-content)/15 p-4 rounded-lg">
|
<details className="bg-(--neutral-content)/15 p-4 rounded-lg">
|
||||||
<summary>
|
<summary>
|
||||||
Headers
|
Headers
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const LichessButton = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className="text-(--accent) border rounded-xl px-6 py-3 cursor-pointer flex items-center gap-2 p-2 hover:bg-(--accent) hover:text-(--header-bg) transition-colors duration-300 ease-in-out"
|
className="w-full text-(--accent) border rounded-xl px-6 py-3 cursor-pointer flex items-center justify-center gap-2 p-2 hover:bg-(--accent) hover:text-(--header-bg) transition-colors duration-300 ease-in-out"
|
||||||
onClick={onClickHandler}
|
onClick={onClickHandler}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,110 +0,0 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import { useLichess } from "#/hooks/useLichess.ts";
|
|
||||||
|
|
||||||
const parseStudyId = (url: string): string | null => {
|
|
||||||
const match = url.match(
|
|
||||||
/(?:https?:\/\/)?(?:www\.)?lichess\.org\/study\/([a-zA-Z0-9]+)/,
|
|
||||||
);
|
|
||||||
return match?.[1] ?? null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const LichessStudyUrlInput = () => {
|
|
||||||
const [url, setUrl] = useState("");
|
|
||||||
const [feedback, setFeedback] = useState<{
|
|
||||||
type: "success" | "error";
|
|
||||||
message: string;
|
|
||||||
} | null>(null);
|
|
||||||
|
|
||||||
const { setSelectedStudyId, selectedStudyId } = useLichess();
|
|
||||||
|
|
||||||
const handleSubmit = () => {
|
|
||||||
if (!url.trim()) {
|
|
||||||
setFeedback({ type: "error", message: "Please enter a study URL" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const studyId = parseStudyId(url.trim());
|
|
||||||
|
|
||||||
if (!studyId) {
|
|
||||||
setFeedback({
|
|
||||||
type: "error",
|
|
||||||
message: "Invalid Lichess study URL",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (studyId === selectedStudyId) {
|
|
||||||
setFeedback({
|
|
||||||
type: "success",
|
|
||||||
message: "Study already selected",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setSelectedStudyId(studyId);
|
|
||||||
setFeedback({ type: "success", message: "Study loaded — pick a chapter" });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
||||||
if (e.key === "Enter") handleSubmit();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex items-center gap-2 min-w-[500px]">
|
|
||||||
<div className="relative flex-1">
|
|
||||||
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-(--neutral-content) pointer-events-none">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="16"
|
|
||||||
height="16"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth="2"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
|
|
||||||
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
<input
|
|
||||||
type="url"
|
|
||||||
placeholder="Lichess Study URL"
|
|
||||||
value={url}
|
|
||||||
onChange={(e) => {
|
|
||||||
setUrl(e.target.value);
|
|
||||||
if (feedback) setFeedback(null);
|
|
||||||
}}
|
|
||||||
onKeyDown={handleKeyDown}
|
|
||||||
className={`w-full h-12 pl-10 pr-4 rounded-lg border text-(--base-content) placeholder-(--neutral-content)/50 outline-hidden transition-all duration-200 focus:border-(--accent) focus:outline-3 focus:outline-offset-2 focus:outline-(--accent)/50 ${
|
|
||||||
feedback?.type === "error"
|
|
||||||
? "border-red-500"
|
|
||||||
: "border-(--neutral-content)"
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn btn-secondary px-6 whitespace-nowrap"
|
|
||||||
onClick={handleSubmit}
|
|
||||||
>
|
|
||||||
Load Study
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{feedback ? (
|
|
||||||
<span
|
|
||||||
className={`text-sm whitespace-nowrap ${
|
|
||||||
feedback.type === "error" ? "text-red-500" : "text-(--accent)"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{feedback.message}
|
|
||||||
</span>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default LichessStudyUrlInput;
|
|
||||||
@@ -77,6 +77,7 @@ export const useLichess = () => {
|
|||||||
|
|
||||||
const lichessLogout = async () => {
|
const lichessLogout = async () => {
|
||||||
await logoutFn();
|
await logoutFn();
|
||||||
|
setSelectedStudyId(null);
|
||||||
setUser(null);
|
setUser(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+17
-10
@@ -2,10 +2,10 @@ 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 SelectLichessStudy from "#/components/SelectLichessStudy.tsx";
|
||||||
import SelectStudyChapter from "#/components/SelectStudyChapter.tsx";
|
import SelectStudyChapter from "#/components/SelectStudyChapter.tsx";
|
||||||
|
import { useLichessUser } from "#/context/LichessUserContext.tsx";
|
||||||
import { useChessGame } from "#/hooks/useChessGame.ts";
|
import { useChessGame } from "#/hooks/useChessGame.ts";
|
||||||
import { useLichess } from "#/hooks/useLichess.ts";
|
import { useLichess } from "#/hooks/useLichess.ts";
|
||||||
|
|
||||||
@@ -41,6 +41,8 @@ const Chessboard = () => {
|
|||||||
const { selectedStudyId, setSelectedStudyId, setStudyChapters } =
|
const { selectedStudyId, setSelectedStudyId, setStudyChapters } =
|
||||||
useLichess();
|
useLichess();
|
||||||
|
|
||||||
|
const { user } = useLichessUser();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="px-4 pb-8 place-self-center min-h-[calc(100vh-226px)]">
|
<main className="px-4 pb-8 place-self-center min-h-[calc(100vh-226px)]">
|
||||||
<Section title="Convert PGN to PDF">
|
<Section title="Convert PGN to PDF">
|
||||||
@@ -52,15 +54,19 @@ const Chessboard = () => {
|
|||||||
className="file-input max-w-xs"
|
className="file-input max-w-xs"
|
||||||
onChange={handleLoadPgn}
|
onChange={handleLoadPgn}
|
||||||
/>
|
/>
|
||||||
<SelectLichessStudy
|
{user ? (
|
||||||
setSelectedStudyId={setSelectedStudyId}
|
<SelectLichessStudy
|
||||||
setStudyChapters={setStudyChapters}
|
setSelectedStudyId={setSelectedStudyId}
|
||||||
/>{" "}
|
setStudyChapters={setStudyChapters}
|
||||||
or <LichessStudyUrlInput />
|
/>
|
||||||
<SelectStudyChapter
|
) : null}
|
||||||
selectedStudyId={selectedStudyId || ""}
|
<div />
|
||||||
gameDispatch={gameDispatch}
|
{selectedStudyId && user ? (
|
||||||
/>
|
<SelectStudyChapter
|
||||||
|
selectedStudyId={selectedStudyId || ""}
|
||||||
|
gameDispatch={gameDispatch}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<div className="grid lg:grid-cols-2 gap-4">
|
<div className="grid lg:grid-cols-2 gap-4">
|
||||||
@@ -103,6 +109,7 @@ const Chessboard = () => {
|
|||||||
id="render-times"
|
id="render-times"
|
||||||
name="render-times"
|
name="render-times"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
disabled={!gameState.pgn}
|
||||||
checked={gameState.diagramClock}
|
checked={gameState.diagramClock}
|
||||||
onChange={handleToggleClock}
|
onChange={handleToggleClock}
|
||||||
/>
|
/>
|
||||||
|
|||||||
+17
-1
@@ -10,7 +10,7 @@ const Home = () => (
|
|||||||
<main className="px-4 pb-8 pt-14">
|
<main className="px-4 pb-8 pt-14">
|
||||||
<section
|
<section
|
||||||
id="hero"
|
id="hero"
|
||||||
className="grid max-w-screen-2xl items-center gap-8 p-8 text-center md:grid-cols-2 md:text-left mx-auto"
|
className="relative grid max-w-screen-2xl items-center gap-8 p-8 text-center md:grid-cols-2 md:text-left mx-auto md:min-h-[calc(100vh-226px)]"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-base-content md:text-4xl">
|
<h1 className="text-3xl font-bold text-base-content md:text-4xl">
|
||||||
@@ -37,6 +37,22 @@ const Home = () => (
|
|||||||
priority
|
priority
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="absolute bottom-4 left-1/2 hidden -translate-x-1/2 md:block animate-bounce text-base-content/50">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="32"
|
||||||
|
height="32"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
>
|
||||||
|
<title>scroll down</title>
|
||||||
|
<path d="m6 9 6 6 6-6" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<Section
|
<Section
|
||||||
title="A Unique Chess Publication Service"
|
title="A Unique Chess Publication Service"
|
||||||
|
|||||||
@@ -117,8 +117,14 @@ const deleteAccessToken = createServerFn().handler(async () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return await response.json();
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to delete access token: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return { success: false, message: "No token found to delete." };
|
||||||
});
|
});
|
||||||
|
|
||||||
export const logout = createServerFn().handler(async () => {
|
export const logout = createServerFn().handler(async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user