mirror of
https://github.com/TheRealOwenRees/chess-scribe-website.git
synced 2026-07-23 01:46:57 +00:00
initial commit
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
interface IProps {
|
||||
children: ReactNode;
|
||||
title: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
const AccordionItem = ({ title, id, children }: IProps) => (
|
||||
<div className="hero__accordion-item">
|
||||
<details className="hero__accordion-details" name="linked">
|
||||
<summary
|
||||
className="hero__accordion-item__summary"
|
||||
aria-describedby={title}
|
||||
>
|
||||
<span className="hero__accordion-item__title-container">
|
||||
<span className="hero__accordion-item__title">{title}</span>
|
||||
</span>
|
||||
</summary>
|
||||
</details>
|
||||
|
||||
<div className="hero__accordion-item__content">
|
||||
<p
|
||||
className="hero__accordion-item__content__inner"
|
||||
role="definition"
|
||||
id={id}
|
||||
>
|
||||
{children}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default AccordionItem;
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Image } from "@unpic/react";
|
||||
import buyMeACoffeeLogo from "@/assets/images/buymeacoffeelogo.svg?url";
|
||||
|
||||
interface IProps {
|
||||
height: number;
|
||||
width: number;
|
||||
}
|
||||
|
||||
const CoffeeWidget = ({ height, width }: IProps) => {
|
||||
return (
|
||||
<a
|
||||
href="https://buymeacoffee.com/owenreesdev"
|
||||
target="_blank"
|
||||
className="ml-auto flex items-center gap-2 text-sm"
|
||||
rel="noopener"
|
||||
>
|
||||
<Image
|
||||
src={buyMeACoffeeLogo}
|
||||
alt="Buy Me A Coffee Logo"
|
||||
width={width}
|
||||
height={height}
|
||||
/>
|
||||
<p>Support This Project</p>
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
export default CoffeeWidget;
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
interface IProps {
|
||||
title: string;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
const Feature = ({ title, children }: IProps) => (
|
||||
<div className="mb-auto">
|
||||
<h6 className="text-(--base-content) text-2xl font-bold">{title}</h6>
|
||||
<p className="text-(--neutral-content) font-medium" data-testid="text">
|
||||
{children}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Feature;
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Link } from "@tanstack/react-router";
|
||||
import { Image } from "@unpic/react";
|
||||
|
||||
import { LuGithub, LuMail } from "react-icons/lu";
|
||||
|
||||
import CoffeeWidget from "#/components/CoffeeWidget.tsx";
|
||||
import footerLogo from "@/assets/images/footerLogo.svg?url";
|
||||
|
||||
export default function Footer() {
|
||||
return (
|
||||
<footer className="text-white w-full grid-cols-2 items-center bg-(--accent) px-10 py-5">
|
||||
<div className="flex w-full items-center">
|
||||
<Link to="/">
|
||||
<Image
|
||||
src={footerLogo}
|
||||
alt="ChessScribe Logo"
|
||||
width={10}
|
||||
height={10}
|
||||
className="hidden h-10 w-10 sm:block"
|
||||
/>
|
||||
</Link>
|
||||
<div>
|
||||
<p data-testid="copyright">
|
||||
Copyright © 2023 - {new Date().getFullYear()}
|
||||
</p>
|
||||
</div>
|
||||
<div className="ml-auto flex gap-2">
|
||||
<a
|
||||
href={import.meta.env.VITE_GITHUB_URL} // TODO ZOD
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-2xl hover:text-(--neutral-content) transition-colors duration-300 ease-in-out cursor-pointer"
|
||||
>
|
||||
<LuGithub />
|
||||
</a>
|
||||
|
||||
<Link
|
||||
to="/contact"
|
||||
rel="noopener noreferrer"
|
||||
className="text-2xl hover:text-(--neutral-content) transition-colors duration-300 ease-in-out"
|
||||
>
|
||||
<LuMail />
|
||||
</Link>
|
||||
</div>
|
||||
<CoffeeWidget width={20} height={50} />
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { Link } from "@tanstack/react-router";
|
||||
import { Image } from "@unpic/react";
|
||||
|
||||
import logo from "@/assets/images/logo.svg?url";
|
||||
|
||||
export default function Header() {
|
||||
return (
|
||||
<header>
|
||||
<nav className="w-full max-w-screen-2xl place-self-center p-8">
|
||||
<div className="container mx-auto flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<Link to="/" className="flex flex-row items-center justify-center">
|
||||
<Image
|
||||
src={logo}
|
||||
alt="ChessScribe Logo"
|
||||
width={50}
|
||||
height={50}
|
||||
className="mr-4"
|
||||
/>
|
||||
<span className="nav-title hidden text-2xl font-extrabold sm:block">
|
||||
ChessScribe
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<ul className="flex flex-1 items-center gap-14 font-semibold">
|
||||
<li>
|
||||
<Link to="/chessboard" className="nav-item">
|
||||
Chessboard
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<Link to="/contact" className="nav-item">
|
||||
Contact
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
interface IProps {
|
||||
onClickHandler: () => void;
|
||||
}
|
||||
|
||||
const LichessButton = ({ onClickHandler }: IProps) => {
|
||||
return (
|
||||
<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"
|
||||
onClick={onClickHandler}
|
||||
type="button"
|
||||
>
|
||||
<div className="w-6 h-6">
|
||||
<svg
|
||||
viewBox="0 0 50 50"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor"
|
||||
>
|
||||
<title>Lichess Logo</title>
|
||||
<path d="M38.956.5c-3.53.418-6.452.902-9.286 2.984C5.534 1.786-.692 18.533.68 29.364 3.493 50.214 31.918 55.785 41.329 41.7c-7.444 7.696-19.276 8.752-28.323 3.084C3.959 39.116-.506 27.392 4.683 17.567 9.873 7.742 18.996 4.535 29.03 6.405c2.43-1.418 5.225-3.22 7.655-3.187l-1.694 4.86 12.752 21.37c-.439 5.654-5.459 6.112-5.459 6.112-.574-1.47-1.634-2.942-4.842-6.036-3.207-3.094-17.465-10.177-15.788-16.207-2.001 6.967 10.311 14.152 14.04 17.663 3.73 3.51 5.426 6.04 5.795 6.756 0 0 9.392-2.504 7.838-8.927L37.4 7.171z" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<p>Log into Lichess.org</p>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default LichessButton;
|
||||
@@ -0,0 +1,33 @@
|
||||
import { trackAppRouter } from "@socialgouv/matomo-next";
|
||||
import { useLocation, useSearch } from "@tanstack/react-router";
|
||||
import { useEffect } from "react";
|
||||
|
||||
const MATOMO_URL = import.meta.env.NEXT_PUBLIC_MATOMO_URL;
|
||||
const MATOMO_SITE_ID = import.meta.env.NEXT_PUBLIC_MATOMO_SITE_ID;
|
||||
|
||||
export function MatomoAnalytics() {
|
||||
const location = useLocation();
|
||||
const searchParams = useSearch({ strict: false });
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined") return;
|
||||
|
||||
const serializedParams = new URLSearchParams(
|
||||
searchParams as Record<string, string>,
|
||||
);
|
||||
|
||||
trackAppRouter({
|
||||
url: MATOMO_URL || "",
|
||||
siteId: MATOMO_SITE_ID || "",
|
||||
pathname: location.pathname,
|
||||
searchParams: serializedParams,
|
||||
enableHeatmapSessionRecording: false,
|
||||
enableHeartBeatTimer: true,
|
||||
cleanUrl: true,
|
||||
disableCookies: true,
|
||||
debug: import.meta.env.DEV,
|
||||
});
|
||||
}, [location.pathname, searchParams]);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import LichessPgnViewer from "lichess-pgn-viewer";
|
||||
import type PgnViewerType from "lichess-pgn-viewer/pgnViewer";
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
interface IProps {
|
||||
gamePgn: string;
|
||||
}
|
||||
|
||||
const PgnViewer = ({ gamePgn }: IProps) => {
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
const viewerRef = useRef<PgnViewerType | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const element: HTMLElement | null = document.querySelector(".lpv-board");
|
||||
if (!element) return;
|
||||
|
||||
viewerRef.current = LichessPgnViewer(element, {
|
||||
pgn: gamePgn,
|
||||
scrollToMove: false,
|
||||
});
|
||||
|
||||
return () => {
|
||||
if (containerRef.current) {
|
||||
containerRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [gamePgn]);
|
||||
|
||||
return <div ref={containerRef} className="lpv-board" />;
|
||||
};
|
||||
|
||||
export default PgnViewer;
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
interface IProps {
|
||||
title: string;
|
||||
smallTitle?: string;
|
||||
description?: string;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const Section = ({ title, smallTitle, description, children }: IProps) => {
|
||||
const words = title.trim().split(" ");
|
||||
const lastWord = words.pop();
|
||||
const mainTitle = words.join(" ");
|
||||
|
||||
return (
|
||||
<section className="mx-auto mt-8 grid max-w-5xl place-items-center gap-4 p-8 text-center md:grid-cols-3 md:text-left text-(--accent)">
|
||||
<h3 className="md:col-span-3 mb-4 text-center text-lg font-bold tracking-wider text-primary">
|
||||
{smallTitle}
|
||||
</h3>
|
||||
<h4 className="md:col-span-3 mb-4 text-center text-4xl font-extrabold text-(--neutral-content) md:text-5xl">
|
||||
{mainTitle} <span className="text-(--accent)">{lastWord}</span>
|
||||
</h4>
|
||||
<p className="md:col-span-3 mb-8 w-1/2 text-center font-semibold text-(--neutral-content) ">
|
||||
{description}
|
||||
</p>
|
||||
{children}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Section;
|
||||
Reference in New Issue
Block a user