Prettify all files

This commit is contained in:
Timothy Armes
2023-07-17 11:49:07 +02:00
parent 95118b32e2
commit d3afc1d1f4
34 changed files with 152 additions and 135 deletions
+5
View File
@@ -0,0 +1,5 @@
{
"trailingComma": "all",
"bracketSpacing": true,
"plugins": ["prettier-plugin-tailwindcss"]
}
+1 -1
View File
@@ -27,5 +27,5 @@
"unspiderfy"
],
"css.customData": [".vscode/tailwindcss.json"],
"css.customData": [".vscode/tailwindcss.json"]
}
+6 -2
View File
@@ -10,18 +10,22 @@
## A website displaying upcoming chess tournaments for the [FFE](http://www.echecs.asso.fr/) on a map
## Tech Stack
This website is built in Typescript using:
- [Next.js](https://nextjs.org/)
- [Tailwind](https://tailwindcss.com/)
- [MongoDB](https://www.mongodb.com/)
- [Cypress](https://www.cypress.io/)
- [Cypress](https://www.cypress.io/)
and is deployed on [Vercel](https://vercel.com/)
## Dependencies
- [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
## Contributions
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.
+1 -1
View File
@@ -31,7 +31,7 @@ export default function Footer() {
>
<FaRegEnvelope />
</Link>
<div className="space-x-2 mr-4 text-xs">
<div className="mr-4 space-x-2 text-xs">
<Link href="/" locale="fr">
FR
</Link>
+6 -6
View File
@@ -20,7 +20,7 @@ const Legend = () => {
{ tc: TimeControl.Blitz, colour: "#cec348" },
{ tc: TimeControl.Other, colour: "#d10c3e" },
].filter(({ tc }) => tournaments.some((t) => t.timeControl === tc)),
[tournaments]
[tournaments],
);
useEffect(() => {
@@ -32,7 +32,7 @@ const Legend = () => {
const div = L.DomUtil.create("div", "map-legend");
div.setAttribute(
"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 = `
@@ -42,11 +42,11 @@ const Legend = () => {
({ tc, colour }) => `
<li>
<span class="block h-4 w-7 border border-[#999] float-left mr-1" style="background: ${colour}"></span>${t(
"timeControlEnum",
{ tc }
)}
"timeControlEnum",
{ tc },
)}
</li>
`
`,
)
.join("")}
</ul>`;
+1 -1
View File
@@ -5,7 +5,7 @@ import { ScrollableElement } from "@/types";
import { FaArrowUp } from "react-icons/fa";
import { handleScrollToTop } from "@/handlers/scrollHandlers";
import { useEffect, useRef, useState } from "react";
import {useBreakpoint} from "@/hooks/tailwind";
import { useBreakpoint } from "@/hooks/tailwind";
const ScrollToTopButton = () => {
const scrollToTopElementRef = useRef<ScrollableElement | null>(null);
+7 -7
View File
@@ -99,11 +99,11 @@ const TimeControlGroup = ({ timeControl, colour }: TimeControlGroupProps) => {
const filteredTournaments = useMemo(
() => tournaments.filter((t) => t.timeControl === timeControl),
[timeControl, tournaments]
[timeControl, tournaments],
);
const hoveredListTournamentId = useAtomValue(
debouncedHoveredListTournamentIdAtom
debouncedHoveredListTournamentIdAtom,
);
const expandAndBounceIfNeeded = useCallback(() => {
@@ -167,7 +167,7 @@ const TimeControlGroup = ({ timeControl, colour }: TimeControlGroupProps) => {
}
}
},
[hoveredListTournamentId]
[hoveredListTournamentId],
);
const onUnSpiderified = useCallback(
@@ -178,7 +178,7 @@ const TimeControlGroup = ({ timeControl, colour }: TimeControlGroupProps) => {
// Once closed, we can expand the next group if needed
expandAndBounceIfNeeded();
},
[expandAndBounceIfNeeded]
[expandAndBounceIfNeeded],
);
useEffect(() => {
@@ -215,7 +215,7 @@ const TimeControlGroup = ({ timeControl, colour }: TimeControlGroupProps) => {
iconSize: new L.Point(40, 40),
});
},
[timeControl]
[timeControl],
);
const markers = filteredTournaments.map((tournament) => {
@@ -242,7 +242,7 @@ const TimeControlGroup = ({ timeControl, colour }: TimeControlGroupProps) => {
</MarkerClusterGroup>
</LayerGroup>
),
[createClusterCustomIcon, markers]
[createClusterCustomIcon, markers],
);
return group;
@@ -251,7 +251,7 @@ const TimeControlGroup = ({ timeControl, colour }: TimeControlGroupProps) => {
export default function TournamentMap() {
const setMapBounds = useSetAtom(mapBoundsAtom);
const hoveredListTournamentId = useAtomValue(
debouncedHoveredListTournamentIdAtom
debouncedHoveredListTournamentIdAtom,
);
const classic = useAtomValue(classicAtom);
+2 -2
View File
@@ -35,7 +35,7 @@ export const TournamentMarker = forwardRef<
});
const setHoveredMapTournamentId = useSetAtom(
debouncedHoveredMapTournamentIdAtom
debouncedHoveredMapTournamentIdAtom,
);
const iconOptions = useMemo(
@@ -48,7 +48,7 @@ export const TournamentMarker = forwardRef<
popupAnchor: [1, -34],
shadowSize: [41, 41],
}),
[colour]
[colour],
);
return (
+4 -4
View File
@@ -26,10 +26,10 @@ export default function TournamentTable() {
const [syncVisible, setSyncVisible] = useAtom(syncVisibleAtom);
const hoveredMapTournamentId = useAtomValue(hoveredMapTournamentIdAtom);
const debouncedHoveredMapTournamentId = useAtomValue(
debouncedHoveredMapTournamentIdAtom
debouncedHoveredMapTournamentIdAtom,
);
const setHoveredListTournamentId = useSetAtom(
debouncedHoveredListTournamentIdAtom
debouncedHoveredListTournamentIdAtom,
);
const isLg = useBreakpoint("lg");
@@ -38,7 +38,7 @@ export default function TournamentTable() {
if (!isLg || debouncedHoveredMapTournamentId === null) return;
const tournamentRow = document.getElementById(
debouncedHoveredMapTournamentId
debouncedHoveredMapTournamentId,
);
tournamentRow?.scrollIntoView({ behavior: "smooth" });
@@ -110,7 +110,7 @@ export default function TournamentTable() {
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",
hoveredMapTournamentId === tournament._id &&
"bg-gray-200 dark:bg-gray-900"
"bg-gray-200 dark:bg-gray-900",
)}
>
<td className="p-3">{tournament.date}</td>
+1 -1
View File
@@ -27,7 +27,7 @@ export async function POST(req: Request) {
return NextResponse.json(
{ success: `Message delivered to ${mailInfo.accepted}` },
{ status: 250 }
{ status: 250 },
);
} catch (error) {
errorLog(error);
+21 -17
View File
@@ -1,6 +1,6 @@
import { TimeControl, Tournament } from '@/types';
import { atom } from 'jotai';
import { LatLngBounds } from 'leaflet';
import { TimeControl, Tournament } from "@/types";
import { atom } from "jotai";
import { LatLngBounds } from "leaflet";
import atomWithDebounce from "@/utils/atomWithDebounce";
@@ -8,7 +8,7 @@ export const tournamentsAtom = atom<Tournament[]>([]);
export const mapBoundsAtom = atom<LatLngBounds | null>(null);
export const syncVisibleAtom = atom(true);
export const searchStringAtom = atom('');
export const searchStringAtom = atom("");
export const classicAtom = atom(true);
export const rapidAtom = atom(true);
export const blitzAtom = atom(true);
@@ -19,9 +19,8 @@ export const {
debouncedValueAtom: debouncedHoveredMapTournamentIdAtom,
} = atomWithDebounce<string | null>(null);
export const {
debouncedValueAtom: debouncedHoveredListTournamentIdAtom,
} = atomWithDebounce<string | null>(null, 300, true);
export const { debouncedValueAtom: debouncedHoveredListTournamentIdAtom } =
atomWithDebounce<string | null>(null, 300, true);
export const filteredTournamentsByTimeControlAtom = atom((get) => {
const tournaments = get(tournamentsAtom);
@@ -31,11 +30,13 @@ export const filteredTournamentsByTimeControlAtom = atom((get) => {
const blitz = get(blitzAtom);
const other = get(otherAtom);
return tournaments.filter(tournament =>
(tournament.timeControl === TimeControl.Classic && classic) ||
(tournament.timeControl === TimeControl.Rapid && rapid) ||
(tournament.timeControl === TimeControl.Blitz && blitz) ||
(tournament.timeControl === TimeControl.Other && other));
return tournaments.filter(
(tournament) =>
(tournament.timeControl === TimeControl.Classic && classic) ||
(tournament.timeControl === TimeControl.Rapid && rapid) ||
(tournament.timeControl === TimeControl.Blitz && blitz) ||
(tournament.timeControl === TimeControl.Other && other),
);
});
export const filteredTournamentsListAtom = atom((get) => {
@@ -45,14 +46,17 @@ export const filteredTournamentsListAtom = atom((get) => {
const searchString = get(searchStringAtom).trim();
// When searching, we search all the tournament, regardless of the map display
if (searchString !== '')
return tournaments.filter((t) => t.town.includes(searchString.toUpperCase()))
if (searchString !== "")
return tournaments.filter((t) =>
t.town.includes(searchString.toUpperCase()),
);
// If we not syncing to the map, return all tournaments
if (mapBounds === null || !syncVisible) return tournaments;
// 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
View File
@@ -1,24 +1,27 @@
import { MetadataRoute } from "next";
export default function sitemap(): MetadataRoute.Sitemap {
return ['fr', 'en'].flatMap(locale => {
const prefix = `https://echecsfrance.com/${locale === 'fr' ? '' : `${locale}/`}`
return ["fr", "en"].flatMap((locale) => {
const prefix = `https://echecsfrance.com/${
locale === "fr" ? "" : `${locale}/`
}`;
return [
{
url: prefix,
lastModified: new Date(),
},
{
url: `${prefix}tournois`,
lastModified: new Date(),
},
{
url: `${prefix}qui-sommes-nous`,
lastModified: new Date(),
},
{
url: `${prefix}contactez-nous`,
lastModified: new Date(),
},
]});
{
url: prefix,
lastModified: new Date(),
},
{
url: `${prefix}tournois`,
lastModified: new Date(),
},
{
url: `${prefix}qui-sommes-nous`,
lastModified: new Date(),
},
{
url: `${prefix}contactez-nous`,
lastModified: new Date(),
},
];
});
}
+8 -8
View File
@@ -1,31 +1,31 @@
.marker-cluster-Classic {
background-color: rgba(145, 231, 135, 0.6);
background-color: rgba(145, 231, 135, 0.6);
}
.marker-cluster-Classic div {
background-color: rgba(36, 171, 33, 0.6)
background-color: rgba(36, 171, 33, 0.6);
}
.marker-cluster-Rapid {
background-color: rgba(85, 150, 201, 0.6)
background-color: rgba(85, 150, 201, 0.6);
}
.marker-cluster-Rapid div {
background-color: rgba(39, 128, 201, 0.6)
background-color: rgba(39, 128, 201, 0.6);
}
.marker-cluster-Blitz {
background-color: rgba(217, 211, 84, 0.6);
background-color: rgba(217, 211, 84, 0.6);
}
.marker-cluster-Blitz div {
background-color: rgba(202, 196, 39, 0.6)
background-color: rgba(202, 196, 39, 0.6);
}
.marker-cluster-Other {
background-color: rgba(211, 67, 84, 0.6);
background-color: rgba(211, 67, 84, 0.6);
}
.marker-cluster-Other div {
background-color: rgba(203, 41, 60, 0.6);
background-color: rgba(203, 41, 60, 0.6);
}
+23 -23
View File
@@ -1,30 +1,30 @@
.toggle {
height: 2em;
width: 3em;
border-radius: 50px;
margin: auto;
background-image: linear-gradient(aqua, skyblue);
position: relative;
cursor: pointer;
height: 2em;
width: 3em;
border-radius: 50px;
margin: auto;
background-image: linear-gradient(aqua, skyblue);
position: relative;
cursor: pointer;
}
.toggle-dark {
height: 2em;
width: 3em;
border-radius: 50px;
margin: auto;
background-image: linear-gradient(midnightblue, rebeccapurple);
position: relative;
cursor: pointer;
height: 2em;
width: 3em;
border-radius: 50px;
margin: auto;
background-image: linear-gradient(midnightblue, rebeccapurple);
position: relative;
cursor: pointer;
}
.theme-icon{
margin-left: 0.2em;
height: 2em;
width: 1.2em;
color: yellow;
.theme-icon {
margin-left: 0.2em;
height: 2em;
width: 1.2em;
color: yellow;
}
.theme-icon-dark{
height: 2em;
width: 1.2em;
margin-left: auto;
.theme-icon-dark {
height: 2em;
width: 1.2em;
margin-left: auto;
}
+2 -2
View File
@@ -11,7 +11,7 @@ describe("ThemeSwitcher component", () => {
cy.getByData("toggle").should(($toggle) => {
const backgroundImage = $toggle.css("background-image");
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) => {
const backgroundImage = $toggle.css("background-image");
expect(backgroundImage).to.include(
"linear-gradient(rgb(25, 25, 112), rgb(102, 51, 153)"
"linear-gradient(rgb(25, 25, 112), rgb(102, 51, 153)",
);
});
});
+1 -1
View File
@@ -21,7 +21,7 @@ describe("Data fetching from API endpoints", () => {
const responseData = response.body;
const itemCount = responseData.length;
expect(itemCount).to.eq(tableRows);
}
},
);
});
});
+1 -1
View File
@@ -66,7 +66,7 @@ describe("Test links", () => {
});
cy.log(link.prop("href"));
}
}
},
);
});
});
+4 -4
View File
@@ -1,11 +1,11 @@
<!DOCTYPE html>
<!doctype html>
<!--suppress HtmlRequiredLangAttribute, HtmlUnknownTag -->
lUnknownTag -->
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<title>Components App</title>
<!-- Used by Next.js to inject CSS. -->
<div id="__next_css__DO_NOT_USE__"></div>
+2 -2
View File
@@ -14,7 +14,7 @@
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
import "./commands";
// Alternatively you can use CommonJS syntax:
// require('./commands')
// require('./commands')
Vendored
+1 -1
View File
@@ -1,4 +1,4 @@
// 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 {}
+1 -1
View File
@@ -1 +1 @@
google-site-verification: google48fe072216892488.html
google-site-verification: google48fe072216892488.html
+1 -1
View File
@@ -8,7 +8,7 @@ const useContactForm = () => {
});
const handleChange = (
e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => {
setValues((prevState) => {
return {
+1 -1
View File
@@ -8,7 +8,7 @@ function useDarkMode(): [string, Dispatch<SetStateAction<string>>] {
const [theme, setTheme] = useState(
typeof window !== "undefined"
? localStorage.theme || (prefersDarkMode ? "dark" : "light")
: "dark"
: "dark",
);
const colorTheme = theme === "dark" ? "light" : "dark";
+3 -3
View File
@@ -1,5 +1,5 @@
import {getRequestConfig} from 'next-intl/server';
import { getRequestConfig } from "next-intl/server";
export default getRequestConfig(async ({locale}) => ({
messages: (await import(`./messages/${locale}.json`)).default
export default getRequestConfig(async ({ locale }) => ({
messages: (await import(`./messages/${locale}.json`)).default,
}));
+2 -2
View File
@@ -42,7 +42,7 @@
"About": {
"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.",
"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.",
"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:",
@@ -65,5 +65,5 @@
"sending": "Sending...",
"success": "Thank you for your message.",
"failure": "Sorry, something went wrong. Please try again."
}
}
}
+2 -2
View File
@@ -42,7 +42,7 @@
"About": {
"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.",
"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.",
"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:",
@@ -65,5 +65,5 @@
"sending": "Envoi en cours...",
"success": "Merci pour votre message.",
"failure": "Oops, quelque chose ne va pas. Veuillez réessayer SVP."
}
}
}
+5 -5
View File
@@ -1,15 +1,15 @@
import createMiddleware from 'next-intl/middleware';
import createMiddleware from "next-intl/middleware";
export default createMiddleware({
// 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`)
defaultLocale: 'fr'
defaultLocale: "fr",
});
export const config = {
// 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)
matcher: ['/((?!api|_next|robots|.*\\..*).*)']
}
matcher: ["/((?!api|_next|robots|.*\\..*).*)"],
};
+1 -3
View File
@@ -5,9 +5,7 @@ const withBundleAnalyzer = require("@next/bundle-analyzer")({
enabled: process.env.ANALYZE === "true",
});
const withNextIntl = require('next-intl/plugin')(
'./i18n.ts'
);
const withNextIntl = require("next-intl/plugin")("./i18n.ts");
module.exports = nextConfig;
module.exports = withBundleAnalyzer(withNextIntl({}));
+1 -1
View File
@@ -3,4 +3,4 @@ module.exports = {
tailwindcss: {},
autoprefixer: {},
},
}
};
-3
View File
@@ -1,3 +0,0 @@
module.exports = {
plugins: [require('prettier-plugin-tailwindcss')],
}
+4 -4
View File
@@ -11,12 +11,12 @@ module.exports = {
minHeight: {
// We use 100svh, falling back to vh for old browsers
// The 144px is for the header and footer
content: ['calc(100vh - 144px)', 'calc(100svh - 144px)'],
content: ["calc(100vh - 144px)", "calc(100svh - 144px)"],
},
height: {
content: ['calc(100vh - 144px)', 'calc(100svh - 144px)'],
}
}
content: ["calc(100vh - 144px)", "calc(100svh - 144px)"],
},
},
},
plugins: [],
};
+7 -1
View File
@@ -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"]
}
+1 -1
View File
@@ -5,7 +5,7 @@ export enum TimeControl {
Rapid = "Rapid",
Blitz = "Blitz",
Other = "Other",
};
}
export interface Tournament {
_id: string;
+4 -4
View File
@@ -3,10 +3,10 @@ import { atom, SetStateAction } from "jotai";
export default function atomWithDebounce<T>(
initialValue: T,
delayMilliseconds = 500,
shouldDebounceOnReset = false
shouldDebounceOnReset = false,
) {
const prevTimeoutAtom = atom<ReturnType<typeof setTimeout> | undefined>(
undefined
undefined,
);
// 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(prevTimeoutAtom, nextTimeoutId);
}
},
);
// exported atom setter to clear timeout if needed
@@ -61,6 +61,6 @@ export default function atomWithDebounce<T>(
currentValueAtom: atom((get) => get(_currentValueAtom)),
isDebouncingAtom,
clearTimeoutAtom,
debouncedValueAtom
debouncedValueAtom,
};
}