mirror of
https://github.com/TheRealOwenRees/echecsfrance.git
synced 2026-07-23 04:26:57 +00:00
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"trailingComma": "all",
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"plugins": ["prettier-plugin-tailwindcss"]
|
||||||
|
}
|
||||||
Vendored
+1
-1
@@ -27,5 +27,5 @@
|
|||||||
"unspiderfy"
|
"unspiderfy"
|
||||||
],
|
],
|
||||||
|
|
||||||
"css.customData": [".vscode/tailwindcss.json"],
|
"css.customData": [".vscode/tailwindcss.json"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,18 +10,22 @@
|
|||||||
## A website displaying upcoming chess tournaments for the [FFE](http://www.echecs.asso.fr/) on a map
|
## A website displaying upcoming chess tournaments for the [FFE](http://www.echecs.asso.fr/) on a map
|
||||||
|
|
||||||
## Tech Stack
|
## Tech Stack
|
||||||
|
|
||||||
This website is built in Typescript using:
|
This website is built in Typescript using:
|
||||||
|
|
||||||
- [Next.js](https://nextjs.org/)
|
- [Next.js](https://nextjs.org/)
|
||||||
- [Tailwind](https://tailwindcss.com/)
|
- [Tailwind](https://tailwindcss.com/)
|
||||||
- [MongoDB](https://www.mongodb.com/)
|
- [MongoDB](https://www.mongodb.com/)
|
||||||
- [Cypress](https://www.cypress.io/)
|
- [Cypress](https://www.cypress.io/)
|
||||||
|
|
||||||
and is deployed on [Vercel](https://vercel.com/)
|
and is deployed on [Vercel](https://vercel.com/)
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
- [Leaflet](https://leafletjs.com/) - a Javascript library for interactive maps
|
- [Leaflet](https://leafletjs.com/) - a Javascript library for interactive maps
|
||||||
- [Nodemailer](https://nodemailer.com/) - a module for Node.js applications to allow easy as cake email sending
|
- [Nodemailer](https://nodemailer.com/) - a module for Node.js applications to allow easy as cake email sending
|
||||||
|
|
||||||
## Contributions
|
## Contributions
|
||||||
|
|
||||||
Contributions are encouraged. Please open an issue to discuss your ideas.
|
Contributions are encouraged. Please open an issue to discuss your ideas.
|
||||||
Add your code into a feature branch such as `feature/feature-name`. We utilise a [GitHub Flow](https://www.gitkraken.com/learn/git/best-practices/git-branch-strategy#github-flow-considerations) branching strategy.
|
Add your code into a feature branch such as `feature/feature-name`. We utilise a [GitHub Flow](https://www.gitkraken.com/learn/git/best-practices/git-branch-strategy#github-flow-considerations) branching strategy.
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export default function Footer() {
|
|||||||
>
|
>
|
||||||
<FaRegEnvelope />
|
<FaRegEnvelope />
|
||||||
</Link>
|
</Link>
|
||||||
<div className="space-x-2 mr-4 text-xs">
|
<div className="mr-4 space-x-2 text-xs">
|
||||||
<Link href="/" locale="fr">
|
<Link href="/" locale="fr">
|
||||||
FR
|
FR
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const Legend = () => {
|
|||||||
{ tc: TimeControl.Blitz, colour: "#cec348" },
|
{ tc: TimeControl.Blitz, colour: "#cec348" },
|
||||||
{ tc: TimeControl.Other, colour: "#d10c3e" },
|
{ tc: TimeControl.Other, colour: "#d10c3e" },
|
||||||
].filter(({ tc }) => tournaments.some((t) => t.timeControl === tc)),
|
].filter(({ tc }) => tournaments.some((t) => t.timeControl === tc)),
|
||||||
[tournaments]
|
[tournaments],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -32,7 +32,7 @@ const Legend = () => {
|
|||||||
const div = L.DomUtil.create("div", "map-legend");
|
const div = L.DomUtil.create("div", "map-legend");
|
||||||
div.setAttribute(
|
div.setAttribute(
|
||||||
"style",
|
"style",
|
||||||
"background: white; color: black; border: 2px solid grey; border-radius: 6px; padding: 10px;"
|
"background: white; color: black; border: 2px solid grey; border-radius: 6px; padding: 10px;",
|
||||||
);
|
);
|
||||||
|
|
||||||
div.innerHTML = `
|
div.innerHTML = `
|
||||||
@@ -42,11 +42,11 @@ const Legend = () => {
|
|||||||
({ tc, colour }) => `
|
({ tc, colour }) => `
|
||||||
<li>
|
<li>
|
||||||
<span class="block h-4 w-7 border border-[#999] float-left mr-1" style="background: ${colour}"></span>${t(
|
<span class="block h-4 w-7 border border-[#999] float-left mr-1" style="background: ${colour}"></span>${t(
|
||||||
"timeControlEnum",
|
"timeControlEnum",
|
||||||
{ tc }
|
{ tc },
|
||||||
)}
|
)}
|
||||||
</li>
|
</li>
|
||||||
`
|
`,
|
||||||
)
|
)
|
||||||
.join("")}
|
.join("")}
|
||||||
</ul>`;
|
</ul>`;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { ScrollableElement } from "@/types";
|
|||||||
import { FaArrowUp } from "react-icons/fa";
|
import { FaArrowUp } from "react-icons/fa";
|
||||||
import { handleScrollToTop } from "@/handlers/scrollHandlers";
|
import { handleScrollToTop } from "@/handlers/scrollHandlers";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import {useBreakpoint} from "@/hooks/tailwind";
|
import { useBreakpoint } from "@/hooks/tailwind";
|
||||||
|
|
||||||
const ScrollToTopButton = () => {
|
const ScrollToTopButton = () => {
|
||||||
const scrollToTopElementRef = useRef<ScrollableElement | null>(null);
|
const scrollToTopElementRef = useRef<ScrollableElement | null>(null);
|
||||||
|
|||||||
@@ -99,11 +99,11 @@ const TimeControlGroup = ({ timeControl, colour }: TimeControlGroupProps) => {
|
|||||||
|
|
||||||
const filteredTournaments = useMemo(
|
const filteredTournaments = useMemo(
|
||||||
() => tournaments.filter((t) => t.timeControl === timeControl),
|
() => tournaments.filter((t) => t.timeControl === timeControl),
|
||||||
[timeControl, tournaments]
|
[timeControl, tournaments],
|
||||||
);
|
);
|
||||||
|
|
||||||
const hoveredListTournamentId = useAtomValue(
|
const hoveredListTournamentId = useAtomValue(
|
||||||
debouncedHoveredListTournamentIdAtom
|
debouncedHoveredListTournamentIdAtom,
|
||||||
);
|
);
|
||||||
|
|
||||||
const expandAndBounceIfNeeded = useCallback(() => {
|
const expandAndBounceIfNeeded = useCallback(() => {
|
||||||
@@ -167,7 +167,7 @@ const TimeControlGroup = ({ timeControl, colour }: TimeControlGroupProps) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[hoveredListTournamentId]
|
[hoveredListTournamentId],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onUnSpiderified = useCallback(
|
const onUnSpiderified = useCallback(
|
||||||
@@ -178,7 +178,7 @@ const TimeControlGroup = ({ timeControl, colour }: TimeControlGroupProps) => {
|
|||||||
// Once closed, we can expand the next group if needed
|
// Once closed, we can expand the next group if needed
|
||||||
expandAndBounceIfNeeded();
|
expandAndBounceIfNeeded();
|
||||||
},
|
},
|
||||||
[expandAndBounceIfNeeded]
|
[expandAndBounceIfNeeded],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -215,7 +215,7 @@ const TimeControlGroup = ({ timeControl, colour }: TimeControlGroupProps) => {
|
|||||||
iconSize: new L.Point(40, 40),
|
iconSize: new L.Point(40, 40),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[timeControl]
|
[timeControl],
|
||||||
);
|
);
|
||||||
|
|
||||||
const markers = filteredTournaments.map((tournament) => {
|
const markers = filteredTournaments.map((tournament) => {
|
||||||
@@ -242,7 +242,7 @@ const TimeControlGroup = ({ timeControl, colour }: TimeControlGroupProps) => {
|
|||||||
</MarkerClusterGroup>
|
</MarkerClusterGroup>
|
||||||
</LayerGroup>
|
</LayerGroup>
|
||||||
),
|
),
|
||||||
[createClusterCustomIcon, markers]
|
[createClusterCustomIcon, markers],
|
||||||
);
|
);
|
||||||
|
|
||||||
return group;
|
return group;
|
||||||
@@ -251,7 +251,7 @@ const TimeControlGroup = ({ timeControl, colour }: TimeControlGroupProps) => {
|
|||||||
export default function TournamentMap() {
|
export default function TournamentMap() {
|
||||||
const setMapBounds = useSetAtom(mapBoundsAtom);
|
const setMapBounds = useSetAtom(mapBoundsAtom);
|
||||||
const hoveredListTournamentId = useAtomValue(
|
const hoveredListTournamentId = useAtomValue(
|
||||||
debouncedHoveredListTournamentIdAtom
|
debouncedHoveredListTournamentIdAtom,
|
||||||
);
|
);
|
||||||
|
|
||||||
const classic = useAtomValue(classicAtom);
|
const classic = useAtomValue(classicAtom);
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export const TournamentMarker = forwardRef<
|
|||||||
});
|
});
|
||||||
|
|
||||||
const setHoveredMapTournamentId = useSetAtom(
|
const setHoveredMapTournamentId = useSetAtom(
|
||||||
debouncedHoveredMapTournamentIdAtom
|
debouncedHoveredMapTournamentIdAtom,
|
||||||
);
|
);
|
||||||
|
|
||||||
const iconOptions = useMemo(
|
const iconOptions = useMemo(
|
||||||
@@ -48,7 +48,7 @@ export const TournamentMarker = forwardRef<
|
|||||||
popupAnchor: [1, -34],
|
popupAnchor: [1, -34],
|
||||||
shadowSize: [41, 41],
|
shadowSize: [41, 41],
|
||||||
}),
|
}),
|
||||||
[colour]
|
[colour],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -26,10 +26,10 @@ export default function TournamentTable() {
|
|||||||
const [syncVisible, setSyncVisible] = useAtom(syncVisibleAtom);
|
const [syncVisible, setSyncVisible] = useAtom(syncVisibleAtom);
|
||||||
const hoveredMapTournamentId = useAtomValue(hoveredMapTournamentIdAtom);
|
const hoveredMapTournamentId = useAtomValue(hoveredMapTournamentIdAtom);
|
||||||
const debouncedHoveredMapTournamentId = useAtomValue(
|
const debouncedHoveredMapTournamentId = useAtomValue(
|
||||||
debouncedHoveredMapTournamentIdAtom
|
debouncedHoveredMapTournamentIdAtom,
|
||||||
);
|
);
|
||||||
const setHoveredListTournamentId = useSetAtom(
|
const setHoveredListTournamentId = useSetAtom(
|
||||||
debouncedHoveredListTournamentIdAtom
|
debouncedHoveredListTournamentIdAtom,
|
||||||
);
|
);
|
||||||
|
|
||||||
const isLg = useBreakpoint("lg");
|
const isLg = useBreakpoint("lg");
|
||||||
@@ -38,7 +38,7 @@ export default function TournamentTable() {
|
|||||||
if (!isLg || debouncedHoveredMapTournamentId === null) return;
|
if (!isLg || debouncedHoveredMapTournamentId === null) return;
|
||||||
|
|
||||||
const tournamentRow = document.getElementById(
|
const tournamentRow = document.getElementById(
|
||||||
debouncedHoveredMapTournamentId
|
debouncedHoveredMapTournamentId,
|
||||||
);
|
);
|
||||||
|
|
||||||
tournamentRow?.scrollIntoView({ behavior: "smooth" });
|
tournamentRow?.scrollIntoView({ behavior: "smooth" });
|
||||||
@@ -110,7 +110,7 @@ export default function TournamentTable() {
|
|||||||
className={twMerge(
|
className={twMerge(
|
||||||
"scroll-m-20 bg-white text-gray-900 hover:bg-gray-200 dark:bg-gray-800 dark:text-white dark:hover:bg-gray-900",
|
"scroll-m-20 bg-white text-gray-900 hover:bg-gray-200 dark:bg-gray-800 dark:text-white dark:hover:bg-gray-900",
|
||||||
hoveredMapTournamentId === tournament._id &&
|
hoveredMapTournamentId === tournament._id &&
|
||||||
"bg-gray-200 dark:bg-gray-900"
|
"bg-gray-200 dark:bg-gray-900",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<td className="p-3">{tournament.date}</td>
|
<td className="p-3">{tournament.date}</td>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export async function POST(req: Request) {
|
|||||||
|
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ success: `Message delivered to ${mailInfo.accepted}` },
|
{ success: `Message delivered to ${mailInfo.accepted}` },
|
||||||
{ status: 250 }
|
{ status: 250 },
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
errorLog(error);
|
errorLog(error);
|
||||||
|
|||||||
+21
-17
@@ -1,6 +1,6 @@
|
|||||||
import { TimeControl, Tournament } from '@/types';
|
import { TimeControl, Tournament } from "@/types";
|
||||||
import { atom } from 'jotai';
|
import { atom } from "jotai";
|
||||||
import { LatLngBounds } from 'leaflet';
|
import { LatLngBounds } from "leaflet";
|
||||||
|
|
||||||
import atomWithDebounce from "@/utils/atomWithDebounce";
|
import atomWithDebounce from "@/utils/atomWithDebounce";
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ export const tournamentsAtom = atom<Tournament[]>([]);
|
|||||||
export const mapBoundsAtom = atom<LatLngBounds | null>(null);
|
export const mapBoundsAtom = atom<LatLngBounds | null>(null);
|
||||||
export const syncVisibleAtom = atom(true);
|
export const syncVisibleAtom = atom(true);
|
||||||
|
|
||||||
export const searchStringAtom = atom('');
|
export const searchStringAtom = atom("");
|
||||||
export const classicAtom = atom(true);
|
export const classicAtom = atom(true);
|
||||||
export const rapidAtom = atom(true);
|
export const rapidAtom = atom(true);
|
||||||
export const blitzAtom = atom(true);
|
export const blitzAtom = atom(true);
|
||||||
@@ -19,9 +19,8 @@ export const {
|
|||||||
debouncedValueAtom: debouncedHoveredMapTournamentIdAtom,
|
debouncedValueAtom: debouncedHoveredMapTournamentIdAtom,
|
||||||
} = atomWithDebounce<string | null>(null);
|
} = atomWithDebounce<string | null>(null);
|
||||||
|
|
||||||
export const {
|
export const { debouncedValueAtom: debouncedHoveredListTournamentIdAtom } =
|
||||||
debouncedValueAtom: debouncedHoveredListTournamentIdAtom,
|
atomWithDebounce<string | null>(null, 300, true);
|
||||||
} = atomWithDebounce<string | null>(null, 300, true);
|
|
||||||
|
|
||||||
export const filteredTournamentsByTimeControlAtom = atom((get) => {
|
export const filteredTournamentsByTimeControlAtom = atom((get) => {
|
||||||
const tournaments = get(tournamentsAtom);
|
const tournaments = get(tournamentsAtom);
|
||||||
@@ -31,11 +30,13 @@ export const filteredTournamentsByTimeControlAtom = atom((get) => {
|
|||||||
const blitz = get(blitzAtom);
|
const blitz = get(blitzAtom);
|
||||||
const other = get(otherAtom);
|
const other = get(otherAtom);
|
||||||
|
|
||||||
return tournaments.filter(tournament =>
|
return tournaments.filter(
|
||||||
(tournament.timeControl === TimeControl.Classic && classic) ||
|
(tournament) =>
|
||||||
(tournament.timeControl === TimeControl.Rapid && rapid) ||
|
(tournament.timeControl === TimeControl.Classic && classic) ||
|
||||||
(tournament.timeControl === TimeControl.Blitz && blitz) ||
|
(tournament.timeControl === TimeControl.Rapid && rapid) ||
|
||||||
(tournament.timeControl === TimeControl.Other && other));
|
(tournament.timeControl === TimeControl.Blitz && blitz) ||
|
||||||
|
(tournament.timeControl === TimeControl.Other && other),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export const filteredTournamentsListAtom = atom((get) => {
|
export const filteredTournamentsListAtom = atom((get) => {
|
||||||
@@ -45,14 +46,17 @@ export const filteredTournamentsListAtom = atom((get) => {
|
|||||||
|
|
||||||
const searchString = get(searchStringAtom).trim();
|
const searchString = get(searchStringAtom).trim();
|
||||||
|
|
||||||
|
|
||||||
// When searching, we search all the tournament, regardless of the map display
|
// When searching, we search all the tournament, regardless of the map display
|
||||||
if (searchString !== '')
|
if (searchString !== "")
|
||||||
return tournaments.filter((t) => t.town.includes(searchString.toUpperCase()))
|
return tournaments.filter((t) =>
|
||||||
|
t.town.includes(searchString.toUpperCase()),
|
||||||
|
);
|
||||||
|
|
||||||
// If we not syncing to the map, return all tournaments
|
// If we not syncing to the map, return all tournaments
|
||||||
if (mapBounds === null || !syncVisible) return tournaments;
|
if (mapBounds === null || !syncVisible) return tournaments;
|
||||||
|
|
||||||
// Filter by those in the current map bounds
|
// Filter by those in the current map bounds
|
||||||
return tournaments.filter(tournament => mapBounds.contains(tournament.latLng))
|
return tournaments.filter((tournament) =>
|
||||||
})
|
mapBounds.contains(tournament.latLng),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|||||||
+22
-19
@@ -1,24 +1,27 @@
|
|||||||
import { MetadataRoute } from "next";
|
import { MetadataRoute } from "next";
|
||||||
|
|
||||||
export default function sitemap(): MetadataRoute.Sitemap {
|
export default function sitemap(): MetadataRoute.Sitemap {
|
||||||
return ['fr', 'en'].flatMap(locale => {
|
return ["fr", "en"].flatMap((locale) => {
|
||||||
const prefix = `https://echecsfrance.com/${locale === 'fr' ? '' : `${locale}/`}`
|
const prefix = `https://echecsfrance.com/${
|
||||||
|
locale === "fr" ? "" : `${locale}/`
|
||||||
|
}`;
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
url: prefix,
|
url: prefix,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: `${prefix}tournois`,
|
url: `${prefix}tournois`,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: `${prefix}qui-sommes-nous`,
|
url: `${prefix}qui-sommes-nous`,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: `${prefix}contactez-nous`,
|
url: `${prefix}contactez-nous`,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
},
|
},
|
||||||
]});
|
];
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,31 @@
|
|||||||
.marker-cluster-Classic {
|
.marker-cluster-Classic {
|
||||||
background-color: rgba(145, 231, 135, 0.6);
|
background-color: rgba(145, 231, 135, 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
.marker-cluster-Classic div {
|
.marker-cluster-Classic div {
|
||||||
background-color: rgba(36, 171, 33, 0.6)
|
background-color: rgba(36, 171, 33, 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
.marker-cluster-Rapid {
|
.marker-cluster-Rapid {
|
||||||
background-color: rgba(85, 150, 201, 0.6)
|
background-color: rgba(85, 150, 201, 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
.marker-cluster-Rapid div {
|
.marker-cluster-Rapid div {
|
||||||
background-color: rgba(39, 128, 201, 0.6)
|
background-color: rgba(39, 128, 201, 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
.marker-cluster-Blitz {
|
.marker-cluster-Blitz {
|
||||||
background-color: rgba(217, 211, 84, 0.6);
|
background-color: rgba(217, 211, 84, 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
.marker-cluster-Blitz div {
|
.marker-cluster-Blitz div {
|
||||||
background-color: rgba(202, 196, 39, 0.6)
|
background-color: rgba(202, 196, 39, 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
.marker-cluster-Other {
|
.marker-cluster-Other {
|
||||||
background-color: rgba(211, 67, 84, 0.6);
|
background-color: rgba(211, 67, 84, 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
.marker-cluster-Other div {
|
.marker-cluster-Other div {
|
||||||
background-color: rgba(203, 41, 60, 0.6);
|
background-color: rgba(203, 41, 60, 0.6);
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-23
@@ -1,30 +1,30 @@
|
|||||||
.toggle {
|
.toggle {
|
||||||
height: 2em;
|
height: 2em;
|
||||||
width: 3em;
|
width: 3em;
|
||||||
border-radius: 50px;
|
border-radius: 50px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
background-image: linear-gradient(aqua, skyblue);
|
background-image: linear-gradient(aqua, skyblue);
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.toggle-dark {
|
.toggle-dark {
|
||||||
height: 2em;
|
height: 2em;
|
||||||
width: 3em;
|
width: 3em;
|
||||||
border-radius: 50px;
|
border-radius: 50px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
background-image: linear-gradient(midnightblue, rebeccapurple);
|
background-image: linear-gradient(midnightblue, rebeccapurple);
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.theme-icon{
|
.theme-icon {
|
||||||
margin-left: 0.2em;
|
margin-left: 0.2em;
|
||||||
height: 2em;
|
height: 2em;
|
||||||
width: 1.2em;
|
width: 1.2em;
|
||||||
color: yellow;
|
color: yellow;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-icon-dark{
|
.theme-icon-dark {
|
||||||
height: 2em;
|
height: 2em;
|
||||||
width: 1.2em;
|
width: 1.2em;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ describe("ThemeSwitcher component", () => {
|
|||||||
cy.getByData("toggle").should(($toggle) => {
|
cy.getByData("toggle").should(($toggle) => {
|
||||||
const backgroundImage = $toggle.css("background-image");
|
const backgroundImage = $toggle.css("background-image");
|
||||||
expect(backgroundImage).to.include(
|
expect(backgroundImage).to.include(
|
||||||
"linear-gradient(rgb(0, 255, 255), rgb(135, 206, 235)"
|
"linear-gradient(rgb(0, 255, 255), rgb(135, 206, 235)",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ describe("ThemeSwitcher component", () => {
|
|||||||
cy.getByData("toggle-dark").should(($toggle) => {
|
cy.getByData("toggle-dark").should(($toggle) => {
|
||||||
const backgroundImage = $toggle.css("background-image");
|
const backgroundImage = $toggle.css("background-image");
|
||||||
expect(backgroundImage).to.include(
|
expect(backgroundImage).to.include(
|
||||||
"linear-gradient(rgb(25, 25, 112), rgb(102, 51, 153)"
|
"linear-gradient(rgb(25, 25, 112), rgb(102, 51, 153)",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ describe("Data fetching from API endpoints", () => {
|
|||||||
const responseData = response.body;
|
const responseData = response.body;
|
||||||
const itemCount = responseData.length;
|
const itemCount = responseData.length;
|
||||||
expect(itemCount).to.eq(tableRows);
|
expect(itemCount).to.eq(tableRows);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ describe("Test links", () => {
|
|||||||
});
|
});
|
||||||
cy.log(link.prop("href"));
|
cy.log(link.prop("href"));
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<!--suppress HtmlRequiredLangAttribute, HtmlUnknownTag -->
|
<!--suppress HtmlRequiredLangAttribute, HtmlUnknownTag -->
|
||||||
lUnknownTag -->
|
lUnknownTag -->
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||||
<title>Components App</title>
|
<title>Components App</title>
|
||||||
<!-- Used by Next.js to inject CSS. -->
|
<!-- Used by Next.js to inject CSS. -->
|
||||||
<div id="__next_css__DO_NOT_USE__"></div>
|
<div id="__next_css__DO_NOT_USE__"></div>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
// ***********************************************************
|
// ***********************************************************
|
||||||
|
|
||||||
// Import commands.js using ES2015 syntax:
|
// Import commands.js using ES2015 syntax:
|
||||||
import './commands'
|
import "./commands";
|
||||||
|
|
||||||
// Alternatively you can use CommonJS syntax:
|
// Alternatively you can use CommonJS syntax:
|
||||||
// require('./commands')
|
// require('./commands')
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
// Use type safe message keys with `next-intl`
|
// Use type safe message keys with `next-intl`
|
||||||
type Messages = typeof import('./messages/fr.json');
|
type Messages = typeof import("./messages/fr.json");
|
||||||
|
|
||||||
declare interface IntlMessages extends Messages {}
|
declare interface IntlMessages extends Messages {}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
google-site-verification: google48fe072216892488.html
|
google-site-verification: google48fe072216892488.html
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const useContactForm = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleChange = (
|
const handleChange = (
|
||||||
e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||||
) => {
|
) => {
|
||||||
setValues((prevState) => {
|
setValues((prevState) => {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ function useDarkMode(): [string, Dispatch<SetStateAction<string>>] {
|
|||||||
const [theme, setTheme] = useState(
|
const [theme, setTheme] = useState(
|
||||||
typeof window !== "undefined"
|
typeof window !== "undefined"
|
||||||
? localStorage.theme || (prefersDarkMode ? "dark" : "light")
|
? localStorage.theme || (prefersDarkMode ? "dark" : "light")
|
||||||
: "dark"
|
: "dark",
|
||||||
);
|
);
|
||||||
|
|
||||||
const colorTheme = theme === "dark" ? "light" : "dark";
|
const colorTheme = theme === "dark" ? "light" : "dark";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {getRequestConfig} from 'next-intl/server';
|
import { getRequestConfig } from "next-intl/server";
|
||||||
|
|
||||||
export default getRequestConfig(async ({locale}) => ({
|
export default getRequestConfig(async ({ locale }) => ({
|
||||||
messages: (await import(`./messages/${locale}.json`)).default
|
messages: (await import(`./messages/${locale}.json`)).default,
|
||||||
}));
|
}));
|
||||||
|
|||||||
+2
-2
@@ -42,7 +42,7 @@
|
|||||||
"About": {
|
"About": {
|
||||||
"title": "Who are we?",
|
"title": "Who are we?",
|
||||||
"p1": "This project came to life in early 2022 with the aim of providing a visualization of chess tournaments on a map in France. Having moved to France in 2019, I was not familiar with the geography of the country and wanted to know which tournaments were taking place near my location.",
|
"p1": "This project came to life in early 2022 with the aim of providing a visualization of chess tournaments on a map in France. Having moved to France in 2019, I was not familiar with the geography of the country and wanted to know which tournaments were taking place near my location.",
|
||||||
"p2": "The project was put on hold until spring 2023, when I revived it. Rebuilt from scratch, Échecs France is now open source and open to contributions. You can find more information and access the project at <homeLink>Échecs France</homeLink>",
|
"p2": "The project was put on hold until spring 2023, when I revived it. Rebuilt from scratch, Échecs France is now open source and open to contributions. You can find more information and access the project at <homeLink>Échecs France</homeLink>",
|
||||||
"p3": " I plan to implement an online donation button for those who wish to contribute to the expenses associated with the website. Once the operating costs are deducted, any remaining funds will be redirected to the chess community, either by sponsoring events or through the creation of donations.",
|
"p3": " I plan to implement an online donation button for those who wish to contribute to the expenses associated with the website. Once the operating costs are deducted, any remaining funds will be redirected to the chess community, either by sponsoring events or through the creation of donations.",
|
||||||
"thanksTitle": "Thanks",
|
"thanksTitle": "Thanks",
|
||||||
"thanksInfo": "This project is what it is thanks to the contributions of all of you. I would like to extend special thanks to the following individuals for their contributions:",
|
"thanksInfo": "This project is what it is thanks to the contributions of all of you. I would like to extend special thanks to the following individuals for their contributions:",
|
||||||
@@ -65,5 +65,5 @@
|
|||||||
"sending": "Sending...",
|
"sending": "Sending...",
|
||||||
"success": "Thank you for your message.",
|
"success": "Thank you for your message.",
|
||||||
"failure": "Sorry, something went wrong. Please try again."
|
"failure": "Sorry, something went wrong. Please try again."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -42,7 +42,7 @@
|
|||||||
"About": {
|
"About": {
|
||||||
"title": "Qui Sommes-Nous?",
|
"title": "Qui Sommes-Nous?",
|
||||||
"p1": "Ce projet a vu le jour début 2022 afin de permettre une visualisation sur une carte des tournois d'échecs en France. Ayant déménagé en France en 2019, je ne connaissais alors pas la géographie française et je souhaitais savoir quels tournois avaient lieu près de chez moi.",
|
"p1": "Ce projet a vu le jour début 2022 afin de permettre une visualisation sur une carte des tournois d'échecs en France. Ayant déménagé en France en 2019, je ne connaissais alors pas la géographie française et je souhaitais savoir quels tournois avaient lieu près de chez moi.",
|
||||||
"p2": "Le projet a été mis de côté jusqu'au printemps 2023; date à laquelle je lui ai redonné vie. Reconstruit à partir de zéro, <homeLink>Échecs France</homeLink> est désormais open source et ouvert aux contributions.",
|
"p2": "Le projet a été mis de côté jusqu'au printemps 2023; date à laquelle je lui ai redonné vie. Reconstruit à partir de zéro, <homeLink>Échecs France</homeLink> est désormais open source et ouvert aux contributions.",
|
||||||
"p3": "Je compte mettre en place un bouton de don en ligne pour ceux qui souhaitent participer aux frais associés au site. Une fois les coûts de fonctionnement déduits, tous les fonds restant seront redirigés vers le monde des échecs soit en sponsorant des événements ou par la création de dons.",
|
"p3": "Je compte mettre en place un bouton de don en ligne pour ceux qui souhaitent participer aux frais associés au site. Une fois les coûts de fonctionnement déduits, tous les fonds restant seront redirigés vers le monde des échecs soit en sponsorant des événements ou par la création de dons.",
|
||||||
"thanksTitle": "Remerciements",
|
"thanksTitle": "Remerciements",
|
||||||
"thanksInfo": "Ce projet est ce qu'il est grâce aux contributions de vous tous. Je souhaite en particulier remercier les personnes suivantes pour leurs contributions\u00a0:",
|
"thanksInfo": "Ce projet est ce qu'il est grâce aux contributions de vous tous. Je souhaite en particulier remercier les personnes suivantes pour leurs contributions\u00a0:",
|
||||||
@@ -65,5 +65,5 @@
|
|||||||
"sending": "Envoi en cours...",
|
"sending": "Envoi en cours...",
|
||||||
"success": "Merci pour votre message.",
|
"success": "Merci pour votre message.",
|
||||||
"failure": "Oops, quelque chose ne va pas. Veuillez réessayer SVP."
|
"failure": "Oops, quelque chose ne va pas. Veuillez réessayer SVP."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-5
@@ -1,15 +1,15 @@
|
|||||||
import createMiddleware from 'next-intl/middleware';
|
import createMiddleware from "next-intl/middleware";
|
||||||
|
|
||||||
export default createMiddleware({
|
export default createMiddleware({
|
||||||
// A list of all locales that are supported
|
// A list of all locales that are supported
|
||||||
locales: ['fr', 'en'],
|
locales: ["fr", "en"],
|
||||||
|
|
||||||
// If this locale is matched, pathnames work without a prefix (e.g. `/tournois`)
|
// If this locale is matched, pathnames work without a prefix (e.g. `/tournois`)
|
||||||
defaultLocale: 'fr'
|
defaultLocale: "fr",
|
||||||
});
|
});
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
// Skip all paths that should not be internationalized. This example skips certain folders
|
// Skip all paths that should not be internationalized. This example skips certain folders
|
||||||
// such as "api", and all files with an extension (e.g. favicon.ico)
|
// such as "api", and all files with an extension (e.g. favicon.ico)
|
||||||
matcher: ['/((?!api|_next|robots|.*\\..*).*)']
|
matcher: ["/((?!api|_next|robots|.*\\..*).*)"],
|
||||||
}
|
};
|
||||||
|
|||||||
+1
-3
@@ -5,9 +5,7 @@ const withBundleAnalyzer = require("@next/bundle-analyzer")({
|
|||||||
enabled: process.env.ANALYZE === "true",
|
enabled: process.env.ANALYZE === "true",
|
||||||
});
|
});
|
||||||
|
|
||||||
const withNextIntl = require('next-intl/plugin')(
|
const withNextIntl = require("next-intl/plugin")("./i18n.ts");
|
||||||
'./i18n.ts'
|
|
||||||
);
|
|
||||||
|
|
||||||
module.exports = nextConfig;
|
module.exports = nextConfig;
|
||||||
module.exports = withBundleAnalyzer(withNextIntl({}));
|
module.exports = withBundleAnalyzer(withNextIntl({}));
|
||||||
|
|||||||
+1
-1
@@ -3,4 +3,4 @@ module.exports = {
|
|||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
autoprefixer: {},
|
autoprefixer: {},
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
plugins: [require('prettier-plugin-tailwindcss')],
|
|
||||||
}
|
|
||||||
+4
-4
@@ -11,12 +11,12 @@ module.exports = {
|
|||||||
minHeight: {
|
minHeight: {
|
||||||
// We use 100svh, falling back to vh for old browsers
|
// We use 100svh, falling back to vh for old browsers
|
||||||
// The 144px is for the header and footer
|
// The 144px is for the header and footer
|
||||||
content: ['calc(100vh - 144px)', 'calc(100svh - 144px)'],
|
content: ["calc(100vh - 144px)", "calc(100svh - 144px)"],
|
||||||
},
|
},
|
||||||
height: {
|
height: {
|
||||||
content: ['calc(100vh - 144px)', 'calc(100svh - 144px)'],
|
content: ["calc(100vh - 144px)", "calc(100svh - 144px)"],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [],
|
||||||
};
|
};
|
||||||
|
|||||||
+7
-1
@@ -24,6 +24,12 @@
|
|||||||
"@/*": ["./*"]
|
"@/*": ["./*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "global.d.ts"],
|
"include": [
|
||||||
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts",
|
||||||
|
"global.d.ts"
|
||||||
|
],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export enum TimeControl {
|
|||||||
Rapid = "Rapid",
|
Rapid = "Rapid",
|
||||||
Blitz = "Blitz",
|
Blitz = "Blitz",
|
||||||
Other = "Other",
|
Other = "Other",
|
||||||
};
|
}
|
||||||
|
|
||||||
export interface Tournament {
|
export interface Tournament {
|
||||||
_id: string;
|
_id: string;
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import { atom, SetStateAction } from "jotai";
|
|||||||
export default function atomWithDebounce<T>(
|
export default function atomWithDebounce<T>(
|
||||||
initialValue: T,
|
initialValue: T,
|
||||||
delayMilliseconds = 500,
|
delayMilliseconds = 500,
|
||||||
shouldDebounceOnReset = false
|
shouldDebounceOnReset = false,
|
||||||
) {
|
) {
|
||||||
const prevTimeoutAtom = atom<ReturnType<typeof setTimeout> | undefined>(
|
const prevTimeoutAtom = atom<ReturnType<typeof setTimeout> | undefined>(
|
||||||
undefined
|
undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
// DO NOT EXPORT currentValueAtom as using this atom to set state can cause
|
// DO NOT EXPORT currentValueAtom as using this atom to set state can cause
|
||||||
@@ -48,7 +48,7 @@ export default function atomWithDebounce<T>(
|
|||||||
|
|
||||||
// set previous timeout atom in case it needs to get cleared
|
// set previous timeout atom in case it needs to get cleared
|
||||||
set(prevTimeoutAtom, nextTimeoutId);
|
set(prevTimeoutAtom, nextTimeoutId);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// exported atom setter to clear timeout if needed
|
// exported atom setter to clear timeout if needed
|
||||||
@@ -61,6 +61,6 @@ export default function atomWithDebounce<T>(
|
|||||||
currentValueAtom: atom((get) => get(_currentValueAtom)),
|
currentValueAtom: atom((get) => get(_currentValueAtom)),
|
||||||
isDebouncingAtom,
|
isDebouncingAtom,
|
||||||
clearTimeoutAtom,
|
clearTimeoutAtom,
|
||||||
debouncedValueAtom
|
debouncedValueAtom,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user