mirror of
https://github.com/TheRealOwenRees/echecsfrance.git
synced 2026-07-23 04:26:57 +00:00
Localisation (#39)
* Localisation * Update tests * Fix map markers in English view * Fix typo
This commit is contained in:
@@ -9,6 +9,8 @@ TODO
|
|||||||
|
|
||||||
# testing
|
# testing
|
||||||
/coverage
|
/coverage
|
||||||
|
/cypress/videos
|
||||||
|
/cypress/screenshots
|
||||||
|
|
||||||
# next.js
|
# next.js
|
||||||
/.next/
|
/.next/
|
||||||
|
|||||||
Vendored
+17
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"cSpell.words": [
|
||||||
|
"approximative",
|
||||||
|
"colour",
|
||||||
|
"contactez",
|
||||||
|
"defaulticon",
|
||||||
|
"Échecs",
|
||||||
|
"Fédération",
|
||||||
|
"Française",
|
||||||
|
"Lente",
|
||||||
|
"localisation",
|
||||||
|
"randomisation",
|
||||||
|
"Rapide",
|
||||||
|
"sommes",
|
||||||
|
"tournois"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
|
import Layout from "@/components/Layout";
|
||||||
|
import ContactForm from "@/components/ContactForm";
|
||||||
|
|
||||||
|
// TODO fix page sizing
|
||||||
|
export default function Contact() {
|
||||||
|
const t = useTranslations("Contact");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<section className="grid place-items-center bg-white dark:bg-gray-800">
|
||||||
|
<div className="mx-auto max-w-screen-md px-4 pb-32 pt-8 lg:pt-16">
|
||||||
|
<h2
|
||||||
|
className="mb-4 text-center text-4xl font-extrabold tracking-tight text-gray-900 dark:text-white"
|
||||||
|
data-test="header2"
|
||||||
|
>
|
||||||
|
{t("title")}
|
||||||
|
</h2>
|
||||||
|
<p className="mb-8 text-center font-light text-gray-500 dark:text-gray-400 sm:text-xl lg:mb-16">
|
||||||
|
{t("info")}
|
||||||
|
</p>
|
||||||
|
<ContactForm />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { Analytics } from "@vercel/analytics/react";
|
||||||
|
import { Inter } from "next/font/google";
|
||||||
|
import { useLocale, NextIntlClientProvider } from "next-intl";
|
||||||
|
import { getTranslator } from "next-intl/server";
|
||||||
|
import { notFound } from "next/navigation";
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
|
import "@/app/globals.css";
|
||||||
|
|
||||||
|
const inter = Inter({ subsets: ["latin"] });
|
||||||
|
|
||||||
|
export async function generateMetadata({
|
||||||
|
params: { locale },
|
||||||
|
}: {
|
||||||
|
params: { locale?: string };
|
||||||
|
}) {
|
||||||
|
// While the `locale` is required, the namespace is optional and
|
||||||
|
// identical to the parameter that `useTranslations` accepts.
|
||||||
|
const t = await getTranslator(locale ?? "fr", "Metadata");
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: t("title"),
|
||||||
|
description: t("description"),
|
||||||
|
keywords: t("keywords"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function RootLayout({
|
||||||
|
children,
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
params: { locale?: string };
|
||||||
|
}) {
|
||||||
|
const locale = useLocale();
|
||||||
|
|
||||||
|
// Show a 404 error if the user requests an unknown locale
|
||||||
|
if (params.locale !== undefined && params.locale !== locale) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
let messages;
|
||||||
|
try {
|
||||||
|
messages = (await import(`@/messages/${locale}.json`)).default;
|
||||||
|
} catch (error) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<html lang={locale}>
|
||||||
|
<body className={inter.className}>
|
||||||
|
<NextIntlClientProvider locale={locale} messages={messages}>
|
||||||
|
{children}
|
||||||
|
</NextIntlClientProvider>
|
||||||
|
<Analytics />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
|
import Layout from "@/components/Layout";
|
||||||
|
import bgImage from "@/public/images/map-bg.jpg";
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
const t = useTranslations("Home");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<header className="grid h-[calc(100%-153px)] place-items-center md:h-[calc(100%-173px)]">
|
||||||
|
<div className="relative h-full w-full brightness-[0.2]">
|
||||||
|
<Image
|
||||||
|
src={bgImage}
|
||||||
|
alt="Background image of France"
|
||||||
|
fill={true}
|
||||||
|
style={{ objectFit: "cover" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="absolute text-center text-white">
|
||||||
|
<h1 className="p-5 text-5xl" data-test="header1">
|
||||||
|
{t("title")}
|
||||||
|
</h1>
|
||||||
|
<h2 className="p-5 text-3xl">{t("purpose")}</h2>
|
||||||
|
<h3 className="mb-5 p-5 text-xl">
|
||||||
|
{t.rich("how", {
|
||||||
|
link: (chunks) => (
|
||||||
|
<Link href="http://www.echecs.asso.fr/" target="_blank">
|
||||||
|
<abbr title="Fédération Française des Échecs">{chunks}</abbr>
|
||||||
|
</Link>
|
||||||
|
),
|
||||||
|
})}
|
||||||
|
</h3>
|
||||||
|
<Link
|
||||||
|
href="/tournois"
|
||||||
|
className="mb-2 mr-2 rounded-lg bg-gradient-to-r from-teal-400 via-teal-500 to-teal-600 px-5 py-2.5 text-center text-sm font-medium text-white hover:bg-gradient-to-br focus:outline-none focus:ring-4 focus:ring-teal-300 dark:focus:ring-teal-800"
|
||||||
|
>
|
||||||
|
{t("mapLink")}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
import Link from "next-intl/link";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
|
import Layout from "@/components/Layout";
|
||||||
|
|
||||||
|
export default function About() {
|
||||||
|
const t = useTranslations("About");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<header className="grid place-items-center bg-white dark:bg-gray-800">
|
||||||
|
<div className="h-full max-w-5xl bg-white px-4 pb-12 pt-8 dark:bg-gray-800">
|
||||||
|
<h2
|
||||||
|
className="mb-8 text-center text-4xl font-extrabold tracking-tight text-gray-900 dark:text-white"
|
||||||
|
data-test="header2"
|
||||||
|
>
|
||||||
|
{t("title")}
|
||||||
|
</h2>
|
||||||
|
<p className="mb-4 text-center font-light text-gray-500 dark:text-gray-400 md:text-xl">
|
||||||
|
{t("p1")}
|
||||||
|
</p>
|
||||||
|
<p className="mb-4 text-center font-light text-gray-500 dark:text-gray-400 md:text-xl">
|
||||||
|
{t.rich("p2", {
|
||||||
|
homeLink: (chunks) => (
|
||||||
|
<Link href="/" className="text-teal-600">
|
||||||
|
{chunks}
|
||||||
|
</Link>
|
||||||
|
),
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
<p className="mb-4 text-center font-light text-gray-500 dark:text-gray-400 md:text-xl">
|
||||||
|
{t("p3")}
|
||||||
|
</p>
|
||||||
|
<h3
|
||||||
|
className="mb-4 mt-8 text-center text-2xl font-extrabold tracking-tight text-gray-900 dark:text-white"
|
||||||
|
data-test="header2"
|
||||||
|
>
|
||||||
|
{t("thanksTitle")}
|
||||||
|
</h3>
|
||||||
|
<p className="mb-4 text-center font-light text-gray-500 dark:text-gray-400 md:text-xl">
|
||||||
|
{t("thanksTitle")}
|
||||||
|
</p>
|
||||||
|
<ul className="mb-4 text-center text-teal-600">
|
||||||
|
<li>
|
||||||
|
<Link href="https://github.com/AlvaroNW" target="_blank">
|
||||||
|
AlvaroNW
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link href="https://github.com/Florifourchette" target="_blank">
|
||||||
|
Florifourchette
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<h3
|
||||||
|
className="mb-4 mt-8 text-center text-2xl font-extrabold tracking-tight text-gray-900 dark:text-white"
|
||||||
|
data-test="header2"
|
||||||
|
>
|
||||||
|
{t("techTitle")}
|
||||||
|
</h3>
|
||||||
|
<p className="mb-4 text-center font-light text-gray-500 dark:text-gray-400 md:text-xl">
|
||||||
|
{t.rich("techInfo", {
|
||||||
|
homeLink: (chunks) => (
|
||||||
|
<Link href="/" className="text-teal-600">
|
||||||
|
{chunks}
|
||||||
|
</Link>
|
||||||
|
),
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
<ul className="mb-4 flex justify-around text-teal-600">
|
||||||
|
<li>
|
||||||
|
<Link href="https://nextjs.org/" target="_blank">
|
||||||
|
NextJS
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link href="#" target="_blank">
|
||||||
|
Typescript
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link href="https://tailwindcss.com/" target="_blank">
|
||||||
|
Tailwind
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link href="https://mongodb.com/" target="_blank">
|
||||||
|
MongoDB
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p className="mb-4 text-center font-light text-gray-500 dark:text-gray-400 md:text-xl">
|
||||||
|
{t("techWith")}
|
||||||
|
</p>
|
||||||
|
<ul className="mb-4 flex justify-around text-teal-600">
|
||||||
|
<li>
|
||||||
|
<Link href="https://leafletjs.com/" target="_blank">
|
||||||
|
Leaflet
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link href="https://nodemailer.com/" target="_blank">
|
||||||
|
Nodemailer
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p className="mb-16 text-center font-light text-gray-500 dark:text-gray-400 md:text-xl">
|
||||||
|
{t.rich("contribInfo", {
|
||||||
|
link: (chunks) => (
|
||||||
|
<Link
|
||||||
|
href="https://github.com/TheRealOwenRees/echecsfrance"
|
||||||
|
target="_blank"
|
||||||
|
className="text-teal-600"
|
||||||
|
>
|
||||||
|
{chunks}
|
||||||
|
</Link>
|
||||||
|
),
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,11 +1,22 @@
|
|||||||
import clientPromise from "@/lib/mongodb";
|
import clientPromise from "@/lib/mongodb";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
import Layout from "@/components/Layout";
|
import Layout from "@/components/Layout";
|
||||||
import TournamentTable from "@/components/TournamentTable";
|
import TournamentTable from "@/components/TournamentTable";
|
||||||
import { dateOrderingFrance } from "@/utils/dbDateOrdering";
|
import { dateOrderingFrance } from "@/utils/dbDateOrdering";
|
||||||
import { errorLog } from "@/utils/logger";
|
import { errorLog } from "@/utils/logger";
|
||||||
|
|
||||||
export const revalidate = 3600; // revalidate cache every 6 hours
|
export const revalidate = 3600; // revalidate cache every 6 hours;
|
||||||
|
|
||||||
|
const LoadingMap = () => {
|
||||||
|
const t = useTranslations("Competitions");
|
||||||
|
return (
|
||||||
|
<div className="grid h-screen place-items-center bg-white text-gray-900 dark:bg-gray-800 dark:text-white">
|
||||||
|
<p>{t("loading")}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Imports the tournament map component, ensuring CSR only.
|
* Imports the tournament map component, ensuring CSR only.
|
||||||
@@ -13,11 +24,7 @@ export const revalidate = 3600; // revalidate cache every 6 hours
|
|||||||
*/
|
*/
|
||||||
const TournamentMap = dynamic(() => import("@/components/TournamentMap"), {
|
const TournamentMap = dynamic(() => import("@/components/TournamentMap"), {
|
||||||
ssr: false,
|
ssr: false,
|
||||||
loading: () => (
|
loading: LoadingMap,
|
||||||
<div className="h-screen grid place-items-center text-gray-900 bg-white dark:bg-gray-800 dark:text-white">
|
|
||||||
<p>Loading map...</p>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const getTournaments = async () => {
|
const getTournaments = async () => {
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import Layout from "@/components/Layout";
|
|
||||||
import ContactForm from "@/components/ContactForm";
|
|
||||||
|
|
||||||
// TODO fix page sizing
|
|
||||||
export default function Contact() {
|
|
||||||
return (
|
|
||||||
<Layout>
|
|
||||||
<section className="grid place-items-center bg-white dark:bg-gray-800">
|
|
||||||
<div className="pt-8 pb-32 lg:pt-16 px-4 mx-auto max-w-screen-md">
|
|
||||||
<h2
|
|
||||||
className="mb-4 text-4xl tracking-tight font-extrabold text-center text-gray-900 dark:text-white"
|
|
||||||
data-test="header2"
|
|
||||||
>
|
|
||||||
Contactez-Nous
|
|
||||||
</h2>
|
|
||||||
<p className="mb-8 lg:mb-16 font-light text-center text-gray-500 dark:text-gray-400 sm:text-xl">
|
|
||||||
Vous souhaitez ajouter les tournois de votre fédération sur ce site?
|
|
||||||
Vous avez un problème technique? Vous aimeriez participer à ce
|
|
||||||
projet? Contactez-nous.
|
|
||||||
</p>
|
|
||||||
<ContactForm />
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</Layout>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import { Analytics } from "@vercel/analytics/react";
|
|
||||||
|
|
||||||
import "./globals.css";
|
|
||||||
import { Inter } from "next/font/google";
|
|
||||||
import { ReactNode } from "react";
|
|
||||||
|
|
||||||
const inter = Inter({ subsets: ["latin"] });
|
|
||||||
|
|
||||||
export const metadata = {
|
|
||||||
title: "Echecs France- Tournois d'Echecs",
|
|
||||||
description: "Trouvez Vos Tournois d'Echecs en France Sur Une Carte",
|
|
||||||
keywords: "echecs, France, tournoi, tournois, FFE, carte",
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function RootLayout({ children }: { children: ReactNode }) {
|
|
||||||
return (
|
|
||||||
<html lang="en">
|
|
||||||
<body className={inter.className}>
|
|
||||||
{children}
|
|
||||||
<Analytics />
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import Link from "next/link";
|
|
||||||
import Image from "next/image";
|
|
||||||
import Layout from "@/components/Layout";
|
|
||||||
import bgImage from "@/public/images/map-bg.jpg";
|
|
||||||
|
|
||||||
export default function Home() {
|
|
||||||
return (
|
|
||||||
<Layout>
|
|
||||||
<header className="grid h-[calc(100%-153px)] md:h-[calc(100%-173px)] place-items-center">
|
|
||||||
<div className="relative h-full w-full brightness-[0.2]">
|
|
||||||
<Image
|
|
||||||
src={bgImage}
|
|
||||||
alt="Background image of France"
|
|
||||||
fill={true}
|
|
||||||
style={{ objectFit: "cover" }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="absolute text-center text-white">
|
|
||||||
<h1 className="text-5xl p-5" data-test="header1">
|
|
||||||
Echecs France
|
|
||||||
</h1>
|
|
||||||
<h2 className="text-3xl p-5">
|
|
||||||
Trouvez Vos Tournois d'Echecs en France Sur Une Carte
|
|
||||||
</h2>
|
|
||||||
<h3 className="text-xl p-5 mb-5">
|
|
||||||
Une représentation visuelle de tous les tournois d'échecs
|
|
||||||
publiés par la{" "}
|
|
||||||
<Link href="http://www.echecs.asso.fr/" target="_blank">
|
|
||||||
<abbr title="Fédération Française des Échecs">FFE</abbr>
|
|
||||||
</Link>
|
|
||||||
</h3>
|
|
||||||
<Link
|
|
||||||
href="/tournois"
|
|
||||||
className="text-white bg-gradient-to-r from-teal-400 via-teal-500 to-teal-600 hover:bg-gradient-to-br focus:ring-4 focus:outline-none focus:ring-teal-300 dark:focus:ring-teal-800 font-medium rounded-lg text-sm px-5 py-2.5 text-center mr-2 mb-2"
|
|
||||||
>
|
|
||||||
Voir La Carte
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
</Layout>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
import Link from "next/link";
|
|
||||||
import Layout from "@/components/Layout";
|
|
||||||
|
|
||||||
export default function About() {
|
|
||||||
return (
|
|
||||||
<Layout>
|
|
||||||
<header className="grid place-items-center bg-white dark:bg-gray-800">
|
|
||||||
<div className="h-full max-w-5xl px-4 pt-8 pb-12 bg-white dark:bg-gray-800">
|
|
||||||
<h2
|
|
||||||
className="mb-8 text-4xl tracking-tight font-extrabold text-center text-gray-900 dark:text-white"
|
|
||||||
data-test="header2"
|
|
||||||
>
|
|
||||||
Qui Sommes-Nous?
|
|
||||||
</h2>
|
|
||||||
<p className="mb-4 font-light text-center text-gray-500 dark:text-gray-400 md:text-xl">
|
|
||||||
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.
|
|
||||||
</p>
|
|
||||||
<p className="mb-4 font-light text-center text-gray-500 dark:text-gray-400 md:text-xl">
|
|
||||||
Le projet a été mis de côté jusqu'au printemps 2023; date à
|
|
||||||
laquelle je lui ai redonné vie. Reconstruit à partir de zéro,{" "}
|
|
||||||
<Link href="/" className="text-teal-600">
|
|
||||||
Echecs France
|
|
||||||
</Link>{" "}
|
|
||||||
est désormais open source et ouvert aux contributions.
|
|
||||||
</p>
|
|
||||||
<p className="mb-4 font-light text-center text-gray-500 dark:text-gray-400 md:text-xl">
|
|
||||||
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.
|
|
||||||
</p>
|
|
||||||
<h3
|
|
||||||
className="mt-8 mb-4 text-2xl tracking-tight font-extrabold text-center text-gray-900 dark:text-white"
|
|
||||||
data-test="header2"
|
|
||||||
>
|
|
||||||
Remerciements
|
|
||||||
</h3>
|
|
||||||
<p className="mb-4 font-light text-center text-gray-500 dark:text-gray-400 md:text-xl">
|
|
||||||
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:
|
|
||||||
</p>
|
|
||||||
<ul className="mb-4 text-center text-teal-600">
|
|
||||||
<li>
|
|
||||||
<Link href="https://github.com/AlvaroNW" target="_blank">
|
|
||||||
AlvaroNW
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Link href="https://github.com/Florifourchette" target="_blank">
|
|
||||||
Florifourchette
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<h3
|
|
||||||
className="mt-8 mb-4 text-2xl tracking-tight font-extrabold text-center text-gray-900 dark:text-white"
|
|
||||||
data-test="header2"
|
|
||||||
>
|
|
||||||
Tech Blurb
|
|
||||||
</h3>
|
|
||||||
<p className="mb-4 font-light text-center text-gray-500 dark:text-gray-400 md:text-xl">
|
|
||||||
<Link href="/" className="text-teal-600">
|
|
||||||
Echecs France
|
|
||||||
</Link>{" "}
|
|
||||||
utilise:
|
|
||||||
</p>
|
|
||||||
<ul className="mb-4 flex justify-around text-teal-600">
|
|
||||||
<li>
|
|
||||||
<Link href="https://nextjs.org/" target="_blank">
|
|
||||||
NextJS
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Link href="#" target="_blank">
|
|
||||||
Typescript
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Link href="https://tailwindcss.com/" target="_blank">
|
|
||||||
Tailwind
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Link href="https://mongodb.com/" target="_blank">
|
|
||||||
MongoDB
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<p className="mb-4 font-light text-center text-gray-500 dark:text-gray-400 md:text-xl">
|
|
||||||
avec
|
|
||||||
</p>
|
|
||||||
<ul className="mb-4 flex justify-around text-teal-600">
|
|
||||||
<li>
|
|
||||||
<Link href="https://leafletjs.com/" target="_blank">
|
|
||||||
Leaflet
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Link href="https://nodemailer.com/" target="_blank">
|
|
||||||
Nodemailer
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<p className="mb-16 font-light text-center text-gray-500 dark:text-gray-400 md:text-xl">
|
|
||||||
Pour plus d'informations sur les moyens de contribution,
|
|
||||||
veuillez visiter notre{" "}
|
|
||||||
<Link
|
|
||||||
href="https://github.com/TheRealOwenRees/echecsfrance"
|
|
||||||
target="_blank"
|
|
||||||
className="text-teal-600"
|
|
||||||
>
|
|
||||||
GitHub.
|
|
||||||
</Link>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
</Layout>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
+7
-5
@@ -1,22 +1,24 @@
|
|||||||
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 => {
|
||||||
|
const prefix = `https://echecsfrance.com/${locale === 'fr' ? '' : `${locale}/`}`
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
url: "https://echecsfrance.com",
|
url: prefix,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: "https://echecsfrance.com/tournois",
|
url: `${prefix}tournois`,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: "https://echecsfrance.com/qui-sommes-nous",
|
url: `${prefix}qui-sommes-nous`,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: "https://echecsfrance.com/contactez-nous",
|
url: `${prefix}contactez-nous`,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
},
|
},
|
||||||
];
|
]});
|
||||||
}
|
}
|
||||||
|
|||||||
+55
-27
@@ -1,10 +1,14 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState, FormEvent } from "react";
|
||||||
import { handleEmailSubmit } from "@/handlers/formSubmitHandlers";
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
|
import sendMail from "@/lib/sendMail";
|
||||||
|
import { errorLog } from "@/utils/logger";
|
||||||
import useContactForm from "@/hooks/useContactForm";
|
import useContactForm from "@/hooks/useContactForm";
|
||||||
|
|
||||||
const ContactForm = () => {
|
const ContactForm = () => {
|
||||||
|
const t = useTranslations("Contact");
|
||||||
const { values, handleChange, resetForm } = useContactForm();
|
const { values, handleChange, resetForm } = useContactForm();
|
||||||
const [responseMessage, setResponseMessage] = useState({
|
const [responseMessage, setResponseMessage] = useState({
|
||||||
isSuccessful: false,
|
isSuccessful: false,
|
||||||
@@ -12,6 +16,41 @@ const ContactForm = () => {
|
|||||||
});
|
});
|
||||||
const [isSending, setIsSending] = useState(false);
|
const [isSending, setIsSending] = useState(false);
|
||||||
|
|
||||||
|
const clearMessage = () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
setResponseMessage({
|
||||||
|
isSuccessful: false,
|
||||||
|
message: "",
|
||||||
|
});
|
||||||
|
}, 10000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEmailSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setIsSending(true);
|
||||||
|
try {
|
||||||
|
const response = await sendMail(values);
|
||||||
|
if (response.status === 250) {
|
||||||
|
setResponseMessage({
|
||||||
|
isSuccessful: true,
|
||||||
|
message: t("success"),
|
||||||
|
});
|
||||||
|
|
||||||
|
resetForm();
|
||||||
|
clearMessage();
|
||||||
|
setIsSending(false);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
errorLog(error);
|
||||||
|
setResponseMessage({
|
||||||
|
isSuccessful: false,
|
||||||
|
message: t("failure"),
|
||||||
|
});
|
||||||
|
clearMessage();
|
||||||
|
setIsSending(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const infoMessage = (
|
const infoMessage = (
|
||||||
<p
|
<p
|
||||||
className={`${
|
className={`${
|
||||||
@@ -25,31 +64,20 @@ const ContactForm = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<form
|
<form onSubmit={handleEmailSubmit} className="space-y-8">
|
||||||
onSubmit={(e) =>
|
|
||||||
handleEmailSubmit(
|
|
||||||
e,
|
|
||||||
values,
|
|
||||||
setResponseMessage,
|
|
||||||
resetForm,
|
|
||||||
setIsSending
|
|
||||||
)
|
|
||||||
}
|
|
||||||
className="space-y-8"
|
|
||||||
>
|
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
htmlFor="email"
|
htmlFor="email"
|
||||||
className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
|
className="mb-2 block text-sm font-medium text-gray-900 dark:text-gray-300"
|
||||||
>
|
>
|
||||||
Adresse e-mail
|
{t("emailLabel")}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
value={values.email}
|
value={values.email}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
type="email"
|
type="email"
|
||||||
id="email"
|
id="email"
|
||||||
className="shadow-sm bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500 dark:shadow-sm-light"
|
className="focus:ring-primary-500 focus:border-primary-500 dark:focus:ring-primary-500 dark:focus:border-primary-500 dark:shadow-sm-light block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 shadow-sm dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400"
|
||||||
placeholder="nom@exemple.com"
|
placeholder="nom@exemple.com"
|
||||||
required
|
required
|
||||||
data-test="email-input"
|
data-test="email-input"
|
||||||
@@ -58,17 +86,17 @@ const ContactForm = () => {
|
|||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
htmlFor="subject"
|
htmlFor="subject"
|
||||||
className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
|
className="mb-2 block text-sm font-medium text-gray-900 dark:text-gray-300"
|
||||||
>
|
>
|
||||||
Sujet
|
{t("subjectLabel")}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
value={values.subject}
|
value={values.subject}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
type="text"
|
type="text"
|
||||||
id="subject"
|
id="subject"
|
||||||
className="block p-3 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 shadow-sm focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500 dark:shadow-sm-light"
|
className="focus:ring-primary-500 focus:border-primary-500 dark:focus:ring-primary-500 dark:focus:border-primary-500 dark:shadow-sm-light block w-full rounded-lg border border-gray-300 bg-gray-50 p-3 text-sm text-gray-900 shadow-sm dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400"
|
||||||
placeholder="Le motif de ma demande"
|
placeholder={t("subjectPlaceholder")}
|
||||||
required
|
required
|
||||||
data-test="subject-input"
|
data-test="subject-input"
|
||||||
/>
|
/>
|
||||||
@@ -76,17 +104,17 @@ const ContactForm = () => {
|
|||||||
<div className="sm:col-span-2">
|
<div className="sm:col-span-2">
|
||||||
<label
|
<label
|
||||||
htmlFor="message"
|
htmlFor="message"
|
||||||
className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
|
className="mb-2 block text-sm font-medium text-gray-900 dark:text-gray-300"
|
||||||
>
|
>
|
||||||
Votre message
|
{t("messageLabel")}
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
value={values.message}
|
value={values.message}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
id="message"
|
id="message"
|
||||||
rows={6}
|
rows={6}
|
||||||
className="block p-2.5 w-full text-sm text-gray-900 bg-gray-50 rounded-lg shadow-sm border border-gray-300 focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
|
className="focus:ring-primary-500 focus:border-primary-500 dark:focus:ring-primary-500 dark:focus:border-primary-500 block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 shadow-sm dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400"
|
||||||
placeholder="Détaillez ici votre demande..."
|
placeholder={t("messagePlaceholder")}
|
||||||
data-test="message-input"
|
data-test="message-input"
|
||||||
required
|
required
|
||||||
></textarea>
|
></textarea>
|
||||||
@@ -94,10 +122,10 @@ const ContactForm = () => {
|
|||||||
<button
|
<button
|
||||||
disabled={isSending}
|
disabled={isSending}
|
||||||
type="submit"
|
type="submit"
|
||||||
className="py-3 px-5 text-sm font-medium text-center text-white rounded-lg bg-teal-600 sm:w-fit hover:bg-primary-800 focus:ring-4 focus:outline-none focus:ring-primary-300 dark:hover:bg-primary-700 dark:focus:ring-primary-800 dark:text-white disabled:opacity-25"
|
className="hover:bg-primary-800 focus:ring-primary-300 dark:hover:bg-primary-700 dark:focus:ring-primary-800 rounded-lg bg-teal-600 px-5 py-3 text-center text-sm font-medium text-white focus:outline-none focus:ring-4 disabled:opacity-25 dark:text-white sm:w-fit"
|
||||||
data-test="submit-button"
|
data-test="submit-button"
|
||||||
>
|
>
|
||||||
{isSending ? "Envoi en cours..." : "Envoi Message"}
|
{isSending ? t("sending") : t("sendButton")}
|
||||||
</button>
|
</button>
|
||||||
{infoMessage}
|
{infoMessage}
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
+18
-5
@@ -1,25 +1,38 @@
|
|||||||
import Link from "next/link";
|
import Link from "next-intl/link";
|
||||||
import { FaGithub, FaRegEnvelope } from "react-icons/fa";
|
import { FaGithub, FaRegEnvelope } from "react-icons/fa";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
export default function Footer() {
|
export default function Footer() {
|
||||||
|
const t = useTranslations("Footer");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer
|
<footer
|
||||||
className="grid w-full fixed bottom-0 justify-items-center py-2 px-5 text-white bg-teal-600 dark:bg-gray-700 z-30"
|
className="fixed bottom-0 z-30 grid w-full justify-items-center bg-teal-600 px-5 py-2 text-white dark:bg-gray-700"
|
||||||
data-test="footer"
|
data-test="footer"
|
||||||
>
|
>
|
||||||
<div className="p-2">
|
<div className="p-2">
|
||||||
<p>© {new Date().getFullYear()} - Owen Rees</p>
|
<p>© {new Date().getFullYear()} - Owen Rees</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex p-2 space-x-4">
|
<div className="flex space-x-4 p-2">
|
||||||
<Link
|
<Link
|
||||||
href="https://github.com/TheRealOwenRees/echecsfrance"
|
href="https://github.com/TheRealOwenRees/echecsfrance"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
aria-label="Lien vers le dépôt GitHub"
|
aria-label={t("githubAria")}
|
||||||
>
|
>
|
||||||
<FaGithub />
|
<FaGithub />
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="/contactez-nous" aria-label="Contactez-nous">
|
<Link href="/contactez-nous" aria-label={t("contactAria")}>
|
||||||
<FaRegEnvelope />
|
<FaRegEnvelope />
|
||||||
</Link>
|
</Link>
|
||||||
|
<div className="space-x-2 text-xs">
|
||||||
|
<Link href="/" locale="fr">
|
||||||
|
FR
|
||||||
|
</Link>
|
||||||
|
<span>|</span>
|
||||||
|
<Link href="/" locale="en">
|
||||||
|
EN
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -10,23 +10,23 @@ const Hamburger = () => {
|
|||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
ref={hamburgerButtonRef}
|
ref={hamburgerButtonRef}
|
||||||
className="hamburger-button space-y-2 relative z-[99999]"
|
className="hamburger-button relative z-[99999] space-y-2"
|
||||||
data-test="hamburger-button"
|
data-test="hamburger-button"
|
||||||
onClick={() => setMenuVisible(!menuVisible)}
|
onClick={() => setMenuVisible(!menuVisible)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`w-8 h-0.5 bg-gray-600 dark:bg-white transition-transform duration-300 ease-in-out ${
|
className={`h-0.5 w-8 bg-gray-600 transition-transform duration-300 ease-in-out dark:bg-white ${
|
||||||
menuVisible ? "rotate-45 translate-y-2.5 translate-x-[1px]" : ""
|
menuVisible ? "translate-x-[1px] translate-y-2.5 rotate-45" : ""
|
||||||
}`}
|
}`}
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
className={`w-8 h-0.5 bg-gray-600 dark:bg-white transition-transform duration-300 ease-linear ${
|
className={`h-0.5 w-8 bg-gray-600 transition-transform duration-300 ease-linear dark:bg-white ${
|
||||||
menuVisible ? "opacity-0 rotate-0 scale-0" : ""
|
menuVisible ? "rotate-0 scale-0 opacity-0" : ""
|
||||||
}`}
|
}`}
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
className={`w-8 h-0.5 bg-gray-600 dark:bg-white transition-transform duration-300 ease-in-out ${
|
className={`h-0.5 w-8 bg-gray-600 transition-transform duration-300 ease-in-out dark:bg-white ${
|
||||||
menuVisible ? "-rotate-45 -translate-y-2.5" : ""
|
menuVisible ? "-translate-y-2.5 -rotate-45" : ""
|
||||||
}`}
|
}`}
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ interface HamburgerMenuState {
|
|||||||
hamburgerButtonRef: RefObject<HTMLDivElement>;
|
hamburgerButtonRef: RefObject<HTMLDivElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
import Link from "next/link";
|
|
||||||
import ThemeSwitcher from "@/components/ThemeSwitcher";
|
|
||||||
import { Dispatch, RefObject, SetStateAction, useRef, useState } from "react";
|
import { Dispatch, RefObject, SetStateAction, useRef, useState } from "react";
|
||||||
|
import Link from "next-intl/link";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
|
import ThemeSwitcher from "@/components/ThemeSwitcher";
|
||||||
import useHamburgerClose from "@/hooks/useHamburgerClose";
|
import useHamburgerClose from "@/hooks/useHamburgerClose";
|
||||||
|
|
||||||
const HamburgerMenu = ({
|
const HamburgerMenu = ({
|
||||||
@@ -14,6 +16,7 @@ const HamburgerMenu = ({
|
|||||||
setMenuVisible,
|
setMenuVisible,
|
||||||
hamburgerButtonRef,
|
hamburgerButtonRef,
|
||||||
}: HamburgerMenuState) => {
|
}: HamburgerMenuState) => {
|
||||||
|
const t = useTranslations("Nav");
|
||||||
const [mouseOverMenu, setMouseOverMenu] = useState(false);
|
const [mouseOverMenu, setMouseOverMenu] = useState(false);
|
||||||
const timeoutRef = useRef<NodeJS.Timeout | undefined>();
|
const timeoutRef = useRef<NodeJS.Timeout | undefined>();
|
||||||
const menuRef = useRef<HTMLDivElement>(null);
|
const menuRef = useRef<HTMLDivElement>(null);
|
||||||
@@ -50,35 +53,35 @@ const HamburgerMenu = ({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={menuRef}
|
ref={menuRef}
|
||||||
className={`absolute top-0 -right-[173px] ${
|
className={`absolute -right-[173px] top-0 ${
|
||||||
menuVisible ? "-translate-x-full" : ""
|
menuVisible ? "-translate-x-full" : ""
|
||||||
} z-[9999] bg-teal-600 flex md:hidden dark:bg-gray-600 transition-transform duration-500 ease-linear`}
|
} z-[9999] flex bg-teal-600 transition-transform duration-500 ease-linear dark:bg-gray-600 md:hidden`}
|
||||||
onMouseEnter={handleMouseEnterMenu}
|
onMouseEnter={handleMouseEnterMenu}
|
||||||
onMouseLeave={handleMouseLeaveMenu}
|
onMouseLeave={handleMouseLeaveMenu}
|
||||||
>
|
>
|
||||||
<ul className="list-reset text-white mt-16 p-5">
|
<ul className="list-reset mt-16 p-5 text-white">
|
||||||
<li className="py-5">
|
<li className="py-5">
|
||||||
<Link
|
<Link
|
||||||
href="/tournois"
|
href="/tournois"
|
||||||
className="border-b-2 border-transparent hover:border-white transition-all ease-in-out duration-300"
|
className="border-b-2 border-transparent transition-all duration-300 ease-in-out hover:border-white"
|
||||||
>
|
>
|
||||||
Tournois
|
{t("competitions")}
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li className="py-5">
|
<li className="py-5">
|
||||||
<Link
|
<Link
|
||||||
href="/qui-sommes-nous"
|
href="/qui-sommes-nous"
|
||||||
className="border-b-2 border-transparent hover:border-white transition-all ease-in-out duration-300"
|
className="border-b-2 border-transparent transition-all duration-300 ease-in-out hover:border-white"
|
||||||
>
|
>
|
||||||
Qui Sommes-Nous
|
{t("about")}
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li className="py-5">
|
<li className="py-5">
|
||||||
<Link
|
<Link
|
||||||
href="/contactez-nous"
|
href="/contactez-nous"
|
||||||
className="border-b-2 border-transparent hover:border-white transition-all ease-in-out duration-300"
|
className="border-b-2 border-transparent transition-all duration-300 ease-in-out hover:border-white"
|
||||||
>
|
>
|
||||||
Contactez-Nous
|
{t("contact")}
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li className="py-5">
|
<li className="py-5">
|
||||||
|
|||||||
+15
-5
@@ -1,8 +1,10 @@
|
|||||||
import { useMap } from "react-leaflet";
|
import { useMap } from "react-leaflet";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import L from "leaflet";
|
import L from "leaflet";
|
||||||
|
|
||||||
const Legend = () => {
|
const Legend = () => {
|
||||||
|
const t = useTranslations("App");
|
||||||
const map = useMap();
|
const map = useMap();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -17,17 +19,25 @@ const Legend = () => {
|
|||||||
"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 = `<ul>
|
div.innerHTML = `<ul>
|
||||||
<li><span style='background:#00ac39; display: block; height: 16px; width: 30px; border: 1px solid #999; float: left; margin-right: 5px'></span>Cadence Lente</li>
|
<li><span style='background:#00ac39; display: block; height: 16px; width: 30px; border: 1px solid #999; float: left; margin-right: 5px'></span>${t(
|
||||||
<li><span style='background:#0086c7; display: block; height: 16px; width: 30px; border: 1px solid #999; float: left; margin-right: 5px'></span>Rapide</li>
|
"tcClassic"
|
||||||
<li><span style='background:#cec348; display: block; height: 16px; width: 30px; border: 1px solid #999; float: left; margin-right: 5px'></span>Blitz</li>
|
)}</li>
|
||||||
<li><span style='background:#d10c3e; display: block; height: 16px; width: 30px; border: 1px solid #999; float: left; margin-right: 5px'></span>1h KO</li>
|
<li><span style='background:#0086c7; display: block; height: 16px; width: 30px; border: 1px solid #999; float: left; margin-right: 5px'></span>${t(
|
||||||
|
"tcRapid"
|
||||||
|
)}</li>
|
||||||
|
<li><span style='background:#cec348; display: block; height: 16px; width: 30px; border: 1px solid #999; float: left; margin-right: 5px'></span>${t(
|
||||||
|
"tcBlitz"
|
||||||
|
)}</li>
|
||||||
|
<li><span style='background:#d10c3e; display: block; height: 16px; width: 30px; border: 1px solid #999; float: left; margin-right: 5px'></span>${t(
|
||||||
|
"tcKO"
|
||||||
|
)}</li>
|
||||||
</ul>`;
|
</ul>`;
|
||||||
return div;
|
return div;
|
||||||
};
|
};
|
||||||
|
|
||||||
legend.addTo(map);
|
legend.addTo(map);
|
||||||
}
|
}
|
||||||
}, [map]);
|
}, [map, t]);
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+17
-13
@@ -1,20 +1,24 @@
|
|||||||
import Link from "next/link";
|
import Link from "next-intl/link";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
import ThemeSwitcher from "@/components/ThemeSwitcher";
|
import ThemeSwitcher from "@/components/ThemeSwitcher";
|
||||||
import Hamburger from "@/components/Hamburger";
|
import Hamburger from "@/components/Hamburger";
|
||||||
|
|
||||||
export default function Navbar() {
|
export default function Navbar() {
|
||||||
|
const t = useTranslations("Nav");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav
|
<nav
|
||||||
className="w-full relative border-b-[1px] pt-5 mt-0 px-5 md:pt-2 bg-white dark:bg-gray-800 dark:border-gray-700 overflow-x-clip"
|
className="relative mt-0 w-full overflow-x-clip border-b-[1px] bg-white px-5 pt-5 dark:border-gray-700 dark:bg-gray-800 md:pt-2"
|
||||||
data-test="navbar"
|
data-test="navbar"
|
||||||
>
|
>
|
||||||
<div className="container mx-auto flex items-center">
|
<div className="container mx-auto flex items-center">
|
||||||
<div className="pb-3 justify-center flex w-full md:w-1/2 md:pb-0 md:justify-start font-extrabold">
|
<div className="flex w-full justify-center pb-3 font-extrabold md:w-1/2 md:justify-start md:pb-0">
|
||||||
<Link
|
<Link
|
||||||
className="text-gray-900 dark:text-white no-underline hover:no-underline"
|
className="text-gray-900 no-underline hover:no-underline dark:text-white"
|
||||||
href="/"
|
href="/"
|
||||||
>
|
>
|
||||||
<span className="text-2xl">Echecs France</span>
|
<span className="text-2xl">{t("title")}</span>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="pb-2 md:hidden" data-test="mobile-menu">
|
<div className="pb-2 md:hidden" data-test="mobile-menu">
|
||||||
@@ -22,32 +26,32 @@ export default function Navbar() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="hidden pt-2 justify-center md:flex md:w-1/2 md:justify-end"
|
className="hidden justify-center pt-2 md:flex md:w-1/2 md:justify-end"
|
||||||
data-test="desktop-menu"
|
data-test="desktop-menu"
|
||||||
>
|
>
|
||||||
<ul className="list-reset text-gray-900 dark:text-white no-underline flex flex-1 justify-around md:flex-none items-center">
|
<ul className="list-reset flex flex-1 items-center justify-around text-gray-900 no-underline dark:text-white md:flex-none">
|
||||||
<li className="mr-10">
|
<li className="mr-10">
|
||||||
<Link
|
<Link
|
||||||
className="inline-block border-b-4 py-5 border-transparent hover:border-teal-600 transition-all ease-in-out duration-300"
|
className="inline-block border-b-4 border-transparent py-5 transition-all duration-300 ease-in-out hover:border-teal-600"
|
||||||
href="/tournois"
|
href="/tournois"
|
||||||
>
|
>
|
||||||
Tournois
|
{t("competitions")}
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li className="mr-10">
|
<li className="mr-10">
|
||||||
<Link
|
<Link
|
||||||
className="inline-block border-b-4 py-5 border-transparent hover:border-teal-600 transition-all ease-in-out duration-300"
|
className="inline-block border-b-4 border-transparent py-5 transition-all duration-300 ease-in-out hover:border-teal-600"
|
||||||
href="/qui-sommes-nous"
|
href="/qui-sommes-nous"
|
||||||
>
|
>
|
||||||
Qui Sommes-Nous
|
{t("about")}
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li className="mr-10">
|
<li className="mr-10">
|
||||||
<Link
|
<Link
|
||||||
className="inline-block border-b-4 py-5 border-transparent hover:border-teal-600 transition-all ease-in-out duration-300"
|
className="inline-block border-b-4 border-transparent py-5 transition-all duration-300 ease-in-out hover:border-teal-600"
|
||||||
href="/contactez-nous"
|
href="/contactez-nous"
|
||||||
>
|
>
|
||||||
Contactez-Nous
|
{t("contact")}
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Dispatch, SetStateAction } from "react";
|
import { Dispatch, SetStateAction } from "react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
type SearchBarProps = {
|
type SearchBarProps = {
|
||||||
tournamentFilter: string;
|
tournamentFilter: string;
|
||||||
@@ -9,15 +10,17 @@ const SearchBar = ({
|
|||||||
tournamentFilter,
|
tournamentFilter,
|
||||||
setTournamentFilter,
|
setTournamentFilter,
|
||||||
}: SearchBarProps) => {
|
}: SearchBarProps) => {
|
||||||
|
const t = useTranslations("Competitions");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-3 bg-white dark:bg-gray-800">
|
<div className="bg-white p-3 dark:bg-gray-800">
|
||||||
<label htmlFor="table-search" className="sr-only">
|
<label htmlFor="table-search" className="sr-only">
|
||||||
Search
|
{t("searchLabel")}
|
||||||
</label>
|
</label>
|
||||||
<div className="relative mt-1">
|
<div className="relative mt-1">
|
||||||
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
||||||
<svg
|
<svg
|
||||||
className="w-5 h-5 text-gray-500 dark:text-gray-400"
|
className="h-5 w-5 text-gray-500 dark:text-gray-400"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
viewBox="0 0 20 20"
|
viewBox="0 0 20 20"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@@ -32,8 +35,8 @@ const SearchBar = ({
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="table-search"
|
id="table-search"
|
||||||
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-80 pl-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
className="block w-80 rounded-lg border border-gray-300 bg-gray-50 p-2.5 pl-10 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-blue-500 dark:focus:ring-blue-500"
|
||||||
placeholder="Recherche"
|
placeholder={t("searchPlaceholder")}
|
||||||
value={tournamentFilter}
|
value={tournamentFilter}
|
||||||
onChange={(e) => setTournamentFilter(e.target.value)}
|
onChange={(e) => setTournamentFilter(e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { LatLngLiteral } from "leaflet";
|
|||||||
import "leaflet/dist/leaflet.css";
|
import "leaflet/dist/leaflet.css";
|
||||||
import "leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css";
|
import "leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css";
|
||||||
import "leaflet-defaulticon-compatibility";
|
import "leaflet-defaulticon-compatibility";
|
||||||
|
|
||||||
import { MapContainer, TileLayer, LayersControl } from "react-leaflet";
|
import { MapContainer, TileLayer, LayersControl } from "react-leaflet";
|
||||||
|
|
||||||
import { createLayerGroups } from "@/utils/layerGroups";
|
import { createLayerGroups } from "@/utils/layerGroups";
|
||||||
@@ -17,8 +18,13 @@ export default function TournamentMap({ tournamentData }: TournamentDataProps) {
|
|||||||
const classicalMarkers = createLayerGroups("Cadence Lente", "green", {
|
const classicalMarkers = createLayerGroups("Cadence Lente", "green", {
|
||||||
tournamentData,
|
tournamentData,
|
||||||
});
|
});
|
||||||
const rapidMarkers = createLayerGroups("Rapide", "blue", { tournamentData });
|
const rapidMarkers = createLayerGroups("Rapide", "blue", {
|
||||||
const blitzMarkers = createLayerGroups("Blitz", "yellow", { tournamentData });
|
tournamentData,
|
||||||
|
});
|
||||||
|
const blitzMarkers = createLayerGroups("Blitz", "yellow", {
|
||||||
|
tournamentData,
|
||||||
|
});
|
||||||
|
|
||||||
const otherMarkers = createLayerGroups("1h KO", "red", { tournamentData });
|
const otherMarkers = createLayerGroups("1h KO", "red", { tournamentData });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
import { TournamentDataProps } from "@/types";
|
import { TournamentDataProps } from "@/types";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
import SearchBar from "@/components/SearchBar";
|
import SearchBar from "@/components/SearchBar";
|
||||||
import ScrollToTopButton from "@/components/ScrollToTopButton";
|
import ScrollToTopButton from "@/components/ScrollToTopButton";
|
||||||
|
|
||||||
@@ -9,6 +11,7 @@ export default function TournamentTable({
|
|||||||
tournamentData,
|
tournamentData,
|
||||||
}: TournamentDataProps) {
|
}: TournamentDataProps) {
|
||||||
let tableData;
|
let tableData;
|
||||||
|
const t = useTranslations("Competitions");
|
||||||
const [searchQuery, setSearchQuery] = useState(""); // text from search bar
|
const [searchQuery, setSearchQuery] = useState(""); // text from search bar
|
||||||
const [filteredTournamentData, setFilteredTournamentData] =
|
const [filteredTournamentData, setFilteredTournamentData] =
|
||||||
useState(tournamentData);
|
useState(tournamentData);
|
||||||
@@ -17,41 +20,41 @@ export default function TournamentTable({
|
|||||||
setFilteredTournamentData(
|
setFilteredTournamentData(
|
||||||
tournamentData.filter((t) => t.town.includes(searchQuery.toUpperCase()))
|
tournamentData.filter((t) => t.town.includes(searchQuery.toUpperCase()))
|
||||||
);
|
);
|
||||||
}, [searchQuery]);
|
}, [searchQuery, tournamentData]);
|
||||||
|
|
||||||
// TODO move this section into its own function
|
// TODO move this section into its own function
|
||||||
if (filteredTournamentData.length === 0) {
|
if (filteredTournamentData.length === 0) {
|
||||||
tableData = (
|
tableData = (
|
||||||
<tr className="bg-white text-gray-900 dark:bg-gray-800 dark:text-white">
|
<tr className="bg-white text-gray-900 dark:bg-gray-800 dark:text-white">
|
||||||
<td colSpan={4} className="p-3">
|
<td colSpan={4} className="p-3">
|
||||||
No tournaments found
|
{t("noneFound")}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
tableData = filteredTournamentData.map((t) => (
|
tableData = filteredTournamentData.map((data) => (
|
||||||
<tr
|
<tr
|
||||||
className="text-gray-900 bg-white dark:bg-gray-800 dark:text-white hover:bg-gray-200 dark:hover:bg-gray-900"
|
className="bg-white text-gray-900 hover:bg-gray-200 dark:bg-gray-800 dark:text-white dark:hover:bg-gray-900"
|
||||||
key={t._id}
|
key={data._id}
|
||||||
>
|
>
|
||||||
<td className="p-3">
|
<td className="p-3">
|
||||||
<a href={t.url} target="_blank">
|
<a href={data.url} target="_blank">
|
||||||
{t.date}
|
{data.date}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td className="p-3">
|
<td className="p-3">
|
||||||
<a href={t.url} target="_blank">
|
<a href={data.url} target="_blank">
|
||||||
{t.town}
|
{data.town}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td className="p-3">
|
<td className="p-3">
|
||||||
<a href={t.url} target="_blank">
|
<a href={data.url} target="_blank">
|
||||||
{t.tournament}
|
{data.tournament}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td className="p-3">
|
<td className="p-3">
|
||||||
<a href={t.url} target="_blank">
|
<a href={data.url} target="_blank">
|
||||||
{t.time_control}
|
{data.time_control}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -60,11 +63,11 @@ export default function TournamentTable({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
className="tournament-table w-full grid auto-rows-max pb-20 lg:h-[calc(100vh-173px)] lg:col-start-2 lg:col-end-3 lg:overflow-y-scroll"
|
className="tournament-table grid w-full auto-rows-max pb-20 lg:col-start-2 lg:col-end-3 lg:h-[calc(100vh-173px)] lg:overflow-y-scroll"
|
||||||
id="tournament-table"
|
id="tournament-table"
|
||||||
data-test="tournament-table-div"
|
data-test="tournament-table-div"
|
||||||
>
|
>
|
||||||
<div className="flex z-10">
|
<div className="z-10 flex">
|
||||||
<SearchBar
|
<SearchBar
|
||||||
tournamentFilter={searchQuery}
|
tournamentFilter={searchQuery}
|
||||||
setTournamentFilter={setSearchQuery}
|
setTournamentFilter={setSearchQuery}
|
||||||
@@ -74,22 +77,22 @@ export default function TournamentTable({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table
|
<table
|
||||||
className="relative table-fixed w-full text-center text-xs"
|
className="relative w-full table-fixed text-center text-xs"
|
||||||
data-test="tournament-table"
|
data-test="tournament-table"
|
||||||
>
|
>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th className="sticky top-0 p-3 bg-teal-600 text-white dark:bg-gray-600">
|
<th className="sticky top-0 bg-teal-600 p-3 text-white dark:bg-gray-600">
|
||||||
Date
|
{t("date")}
|
||||||
</th>
|
</th>
|
||||||
<th className="sticky top-0 p-3 bg-teal-600 text-white dark:bg-gray-600">
|
<th className="sticky top-0 bg-teal-600 p-3 text-white dark:bg-gray-600">
|
||||||
Ville
|
{t("town")}
|
||||||
</th>
|
</th>
|
||||||
<th className="sticky top-0 p-3 bg-teal-600 text-white dark:bg-gray-600">
|
<th className="sticky top-0 bg-teal-600 p-3 text-white dark:bg-gray-600">
|
||||||
Tournois
|
{t("competition")}
|
||||||
</th>
|
</th>
|
||||||
<th className="sticky top-0 p-3 bg-teal-600 text-white dark:bg-gray-600">
|
<th className="sticky top-0 bg-teal-600 p-3 text-white dark:bg-gray-600">
|
||||||
Cadence
|
{t("timeControl")}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import Home from "@/app/page";
|
import Home from "@/app/[lang]/page";
|
||||||
import About from "@/app/qui-sommes-nous/page";
|
import About from "@/app/[lang]/qui-sommes-nous/page";
|
||||||
import Contact from "@/app/contactez-nous/page";
|
import Contact from "@/app/[lang]/contactez-nous/page";
|
||||||
|
|
||||||
const navbarFooterCheck = () => {
|
const navbarFooterCheck = () => {
|
||||||
it("includes navbar", () => {
|
it("includes navbar", () => {
|
||||||
@@ -19,7 +19,7 @@ describe("Verify component mount", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("correct h1 tags with website name included", () => {
|
it("correct h1 tags with website name included", () => {
|
||||||
cy.getByData("header1").contains("echecs france", {
|
cy.getByData("header1").contains("échecs france", {
|
||||||
matchCase: false,
|
matchCase: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,14 +13,15 @@
|
|||||||
// https://on.cypress.io/configuration
|
// https://on.cypress.io/configuration
|
||||||
// ***********************************************************
|
// ***********************************************************
|
||||||
|
|
||||||
// Import commands.js using ES2015 syntax:
|
import { mount } from "cypress/react18";
|
||||||
|
import { NextIntlClientProvider } from "next-intl";
|
||||||
|
|
||||||
|
import messages from "@/messages/fr.json";
|
||||||
import "./commands";
|
import "./commands";
|
||||||
|
|
||||||
// Alternatively you can use CommonJS syntax:
|
// Alternatively you can use CommonJS syntax:
|
||||||
// require('./commands')
|
// require('./commands')
|
||||||
|
|
||||||
import { mount } from "cypress/react18";
|
|
||||||
|
|
||||||
// Augment the Cypress namespace to include type definitions for
|
// Augment the Cypress namespace to include type definitions for
|
||||||
// your custom command.
|
// your custom command.
|
||||||
// Alternatively, can be defined in cypress/support/component.d.ts
|
// Alternatively, can be defined in cypress/support/component.d.ts
|
||||||
@@ -34,7 +35,15 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Cypress.Commands.add("mount", mount);
|
Cypress.Commands.add("mount", (component, options) => {
|
||||||
|
const wrapped = (
|
||||||
|
<NextIntlClientProvider locale="fr" messages={messages}>
|
||||||
|
{component}
|
||||||
|
</NextIntlClientProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
return mount(wrapped, options);
|
||||||
|
});
|
||||||
|
|
||||||
// Example use:
|
// Example use:
|
||||||
// cy.mount(<MyComponent />)
|
// cy.mount(<MyComponent />)
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,11 +0,0 @@
|
|||||||
;FFMETADATA1
|
|
||||||
[CHAPTER]
|
|
||||||
TIMEBASE=1/1000
|
|
||||||
START=-5248
|
|
||||||
END=1236
|
|
||||||
title=Data fetching for map map markers is equal to table rows
|
|
||||||
[CHAPTER]
|
|
||||||
TIMEBASE=1/1000
|
|
||||||
START=6621
|
|
||||||
END=7725
|
|
||||||
title=Data fetching from API endpoints api call should equal website data count
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Vendored
+4
@@ -0,0 +1,4 @@
|
|||||||
|
// Use type safe message keys with `next-intl`
|
||||||
|
type Messages = typeof import('./messages/fr.json');
|
||||||
|
|
||||||
|
declare interface IntlMessages extends Messages {}
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import { Dispatch, FormEvent, SetStateAction } from "react";
|
|
||||||
import sendMail from "@/lib/sendMail";
|
|
||||||
import { errorLog } from "@/utils/logger";
|
|
||||||
|
|
||||||
export const handleEmailSubmit = async (
|
|
||||||
e: FormEvent<HTMLFormElement>,
|
|
||||||
values: Record<string, string>,
|
|
||||||
setResponseMessage: Dispatch<
|
|
||||||
SetStateAction<{ isSuccessful: boolean; message: string }>
|
|
||||||
>,
|
|
||||||
resetForm: () => void,
|
|
||||||
setIsSending: Dispatch<SetStateAction<boolean>>
|
|
||||||
) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setIsSending(true);
|
|
||||||
|
|
||||||
const clearMessage = () => {
|
|
||||||
setTimeout(() => {
|
|
||||||
setResponseMessage({
|
|
||||||
isSuccessful: false,
|
|
||||||
message: "",
|
|
||||||
});
|
|
||||||
}, 10000);
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await sendMail(values);
|
|
||||||
if (response.status === 250) {
|
|
||||||
setResponseMessage({
|
|
||||||
isSuccessful: true,
|
|
||||||
message: "Merci pour votre message.",
|
|
||||||
});
|
|
||||||
resetForm();
|
|
||||||
clearMessage();
|
|
||||||
setIsSending(false);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
errorLog(error);
|
|
||||||
setResponseMessage({
|
|
||||||
isSuccessful: false,
|
|
||||||
message: "Oops, quelque chose ne va pas. Veuillez réessayer SVP.",
|
|
||||||
});
|
|
||||||
clearMessage();
|
|
||||||
setIsSending(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import {getRequestConfig} from 'next-intl/server';
|
||||||
|
|
||||||
|
export default getRequestConfig(async ({locale}) => ({
|
||||||
|
messages: (await import(`./messages/${locale}.json`)).default
|
||||||
|
}));
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
{
|
||||||
|
"Metadata": {
|
||||||
|
"title": "Echecs France - Tournois d'Echecs",
|
||||||
|
"description": "Find chess competitions, in France, on a map",
|
||||||
|
"keywords": "chess, France, competition, competitions, FFE, map"
|
||||||
|
},
|
||||||
|
|
||||||
|
"Nav": {
|
||||||
|
"title": "Echecs France",
|
||||||
|
"competitions": "Competitions",
|
||||||
|
"about": "About Us",
|
||||||
|
"contact": "Contact"
|
||||||
|
},
|
||||||
|
|
||||||
|
"Footer": {
|
||||||
|
"githubAria": "Link to GitHub repository",
|
||||||
|
"contactAria": "Contact Us"
|
||||||
|
},
|
||||||
|
|
||||||
|
"App": {
|
||||||
|
"tcClassic": "Classic",
|
||||||
|
"tcRapid": "Rapid",
|
||||||
|
"tcBlitz": "Blitz",
|
||||||
|
"tcKO": "1h KO"
|
||||||
|
},
|
||||||
|
|
||||||
|
"Home": {
|
||||||
|
"title": "France Echecs",
|
||||||
|
"purpose": "Find chess competitions, in France, on a map",
|
||||||
|
"how": "A graphical representation of all the chess competitions published by the <link>FFE</link>",
|
||||||
|
"mapLink": "See the Map"
|
||||||
|
},
|
||||||
|
|
||||||
|
"Competitions": {
|
||||||
|
"loading": "Loading...",
|
||||||
|
"searchLabel": "Search",
|
||||||
|
"searchPlaceholder": "Search",
|
||||||
|
"noneFound": "No tournaments found",
|
||||||
|
"date": "Date",
|
||||||
|
"town": "Town",
|
||||||
|
"competition": "Competition",
|
||||||
|
"timeControl": "Time Control",
|
||||||
|
"approx": "Géo-localisation is approximative"
|
||||||
|
},
|
||||||
|
|
||||||
|
"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, Echecs France is now open source and open to contributions. You can find more information and access the project at <homeLink>Echecs 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:",
|
||||||
|
"techTitle": "Tech Blurb",
|
||||||
|
"techInfo": "<homeLink>Echecs France</homeLink> uses\u00a0:",
|
||||||
|
"techWith": "with",
|
||||||
|
"contribInfo": "For more information on how to contribute, please visit our <link>GitHub</link>."
|
||||||
|
},
|
||||||
|
|
||||||
|
"Contact": {
|
||||||
|
"title": "Contactez-Nous",
|
||||||
|
"info": "Would you like to add tournaments from your federation to this website? Are you experiencing any technical issues? Would you like to participate in this project? Please feel free to contact us.",
|
||||||
|
"emailLabel": "Email",
|
||||||
|
"subjectLabel": "Subject",
|
||||||
|
"subjectPlaceholder": "Your reason for contacting us",
|
||||||
|
"messageLabel": "Your Message",
|
||||||
|
"messagePlaceholder": "Please provide the details of your request here...",
|
||||||
|
"sendButton": "Envoi Message",
|
||||||
|
"sending": "Sending...",
|
||||||
|
"success": "Thank you for your message.",
|
||||||
|
"failure": "Sorry, something went wrong. Please try again."
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
{
|
||||||
|
"Metadata": {
|
||||||
|
"title": "Echecs France - Tournois d'Échecs",
|
||||||
|
"description": "Trouvez Vos Tournois d'Échecs en France Sur Une Carte",
|
||||||
|
"keywords": "echecs, France, tournoi, tournois, FFE, carte"
|
||||||
|
},
|
||||||
|
|
||||||
|
"Nav": {
|
||||||
|
"title": "Échecs France",
|
||||||
|
"competitions": "Tournois",
|
||||||
|
"about": "Qui Sommes-Nous",
|
||||||
|
"contact": "Contactez-Nous"
|
||||||
|
},
|
||||||
|
|
||||||
|
"Footer": {
|
||||||
|
"githubAria": "Lien vers le dépôt GitHub",
|
||||||
|
"contactAria": "Contactez-nous"
|
||||||
|
},
|
||||||
|
|
||||||
|
"App": {
|
||||||
|
"tcClassic": "Cadence Lente",
|
||||||
|
"tcRapid": "Rapide",
|
||||||
|
"tcBlitz": "Blitz",
|
||||||
|
"tcKO": "1h KO"
|
||||||
|
},
|
||||||
|
|
||||||
|
"Home": {
|
||||||
|
"title": "Échecs France",
|
||||||
|
"purpose": "Trouvez Vos Tournois d'Échecs en France Sur Une Carte",
|
||||||
|
"how": "Une représentation visuelle de tous les tournois d'échecs publiés par la <link>FFE</link>",
|
||||||
|
"mapLink": "Voir La Carte"
|
||||||
|
},
|
||||||
|
|
||||||
|
"Competitions": {
|
||||||
|
"loading": "Téléchargement...",
|
||||||
|
"searchLabel": "Rechercher",
|
||||||
|
"searchPlaceholder": "Rechercher",
|
||||||
|
"noneFound": "Pas de tournois trouvé",
|
||||||
|
"date": "Date",
|
||||||
|
"town": "Ville",
|
||||||
|
"competition": "Tournois",
|
||||||
|
"timeControl": "Cadence",
|
||||||
|
"approx": "Géolocalisation approximative"
|
||||||
|
},
|
||||||
|
|
||||||
|
"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>Echecs 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:",
|
||||||
|
"techTitle": "Tech Blurb",
|
||||||
|
"techInfo": "<homeLink>Échecs France</homeLink> utilise\u00a0:",
|
||||||
|
"techWith": "avec",
|
||||||
|
"contribInfo": "Pour plus d'informations sur les moyens de contribution, veuillez visiter notre <link>GitHub</link>."
|
||||||
|
},
|
||||||
|
|
||||||
|
"Contact": {
|
||||||
|
"title": "Contactez-Nous",
|
||||||
|
"info": "Vous souhaitez ajouter les tournois de votre fédération sur ce site\u00a0? Vous avez un problème technique? Vous aimeriez participer à ce projet? Contactez-nous.",
|
||||||
|
"emailLabel": "Adresse e-mail",
|
||||||
|
"subjectLabel": "Sujet",
|
||||||
|
"subjectPlaceholder": "Le motif de ma demande",
|
||||||
|
"messageLabel": "Votre message",
|
||||||
|
"messagePlaceholder": "Détaillez ici votre demande...",
|
||||||
|
"sendButton": "Envoi Message",
|
||||||
|
"sending": "Envoi en cours...",
|
||||||
|
"success": "Merci pour votre message.",
|
||||||
|
"failure": "Oops, quelque chose ne va pas. Veuillez réessayer SVP."
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import createMiddleware from 'next-intl/middleware';
|
||||||
|
|
||||||
|
export default createMiddleware({
|
||||||
|
// A list of all locales that are supported
|
||||||
|
locales: ['fr', 'en'],
|
||||||
|
|
||||||
|
// If this locale is matched, pathnames work without a prefix (e.g. `/tournois`)
|
||||||
|
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|.*\\..*).*)']
|
||||||
|
}
|
||||||
+5
-1
@@ -5,5 +5,9 @@ const withBundleAnalyzer = require("@next/bundle-analyzer")({
|
|||||||
enabled: process.env.ANALYZE === "true",
|
enabled: process.env.ANALYZE === "true",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const withNextIntl = require('next-intl/plugin')(
|
||||||
|
'./i18n.ts'
|
||||||
|
);
|
||||||
|
|
||||||
module.exports = nextConfig;
|
module.exports = nextConfig;
|
||||||
module.exports = withBundleAnalyzer({});
|
module.exports = withBundleAnalyzer(withNextIntl({}));
|
||||||
|
|||||||
Generated
+161
@@ -20,6 +20,7 @@
|
|||||||
"leaflet-defaulticon-compatibility": "^0.1.1",
|
"leaflet-defaulticon-compatibility": "^0.1.1",
|
||||||
"mongodb": "^5.6.0",
|
"mongodb": "^5.6.0",
|
||||||
"next": "^13.4.7",
|
"next": "^13.4.7",
|
||||||
|
"next-intl": "^3.0.0-beta.7",
|
||||||
"nodemailer": "^6.9.3",
|
"nodemailer": "^6.9.3",
|
||||||
"postcss": "8.4.24",
|
"postcss": "8.4.24",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
@@ -310,6 +311,84 @@
|
|||||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@formatjs/ecma402-abstract": {
|
||||||
|
"version": "1.17.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.17.0.tgz",
|
||||||
|
"integrity": "sha512-6ueQTeJZtwKjmh23bdkq/DMqH4l4bmfvtQH98blOSbiXv/OUiyijSW6jU22IT8BNM1ujCaEvJfTtyCYVH38EMQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/intl-localematcher": "0.4.0",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/fast-memoize": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-Rg0e76nomkz3vF9IPlKeV+Qynok0r7YZjL6syLz4/urSg0IbjPZCB/iYUMNsYA643gh4mgrX3T7KEIFIxJBQeg==",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/icu-messageformat-parser": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-Qxv/lmCN6hKpBSss2uQ8IROVnta2r9jd3ymUEIjm2UyIkUCHVcbUVRGL/KS/wv7876edvsPe+hjHVJ4z8YuVaw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/ecma402-abstract": "1.11.4",
|
||||||
|
"@formatjs/icu-skeleton-parser": "1.3.6",
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/icu-messageformat-parser/node_modules/@formatjs/ecma402-abstract": {
|
||||||
|
"version": "1.11.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.11.4.tgz",
|
||||||
|
"integrity": "sha512-EBikYFp2JCdIfGEb5G9dyCkTGDmC57KSHhRQOC3aYxoPWVZvfWCDjZwkGYHN7Lis/fmuWl906bnNTJifDQ3sXw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/intl-localematcher": "0.2.25",
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/icu-messageformat-parser/node_modules/@formatjs/intl-localematcher": {
|
||||||
|
"version": "0.2.25",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.25.tgz",
|
||||||
|
"integrity": "sha512-YmLcX70BxoSopLFdLr1Ds99NdlTI2oWoLbaUW2M406lxOIPzE1KQhRz2fPUkq34xVZQaihCoU29h0KK7An3bhA==",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/icu-skeleton-parser": {
|
||||||
|
"version": "1.3.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.6.tgz",
|
||||||
|
"integrity": "sha512-I96mOxvml/YLrwU2Txnd4klA7V8fRhb6JG/4hm3VMNmeJo1F03IpV2L3wWt7EweqNLES59SZ4d6hVOPCSf80Bg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/ecma402-abstract": "1.11.4",
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/icu-skeleton-parser/node_modules/@formatjs/ecma402-abstract": {
|
||||||
|
"version": "1.11.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.11.4.tgz",
|
||||||
|
"integrity": "sha512-EBikYFp2JCdIfGEb5G9dyCkTGDmC57KSHhRQOC3aYxoPWVZvfWCDjZwkGYHN7Lis/fmuWl906bnNTJifDQ3sXw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/intl-localematcher": "0.2.25",
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/icu-skeleton-parser/node_modules/@formatjs/intl-localematcher": {
|
||||||
|
"version": "0.2.25",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.25.tgz",
|
||||||
|
"integrity": "sha512-YmLcX70BxoSopLFdLr1Ds99NdlTI2oWoLbaUW2M406lxOIPzE1KQhRz2fPUkq34xVZQaihCoU29h0KK7An3bhA==",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/intl-localematcher": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-bRTd+rKomvfdS4QDlVJ6TA/Jx1F2h/TBVO5LjvhQ7QPPHp19oPNMIum7W2CMEReq/zPxpmCeB31F9+5gl/qtvw==",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@humanwhocodes/config-array": {
|
"node_modules/@humanwhocodes/config-array": {
|
||||||
"version": "0.11.10",
|
"version": "0.11.10",
|
||||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz",
|
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz",
|
||||||
@@ -3713,6 +3792,34 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/intl-messageformat": {
|
||||||
|
"version": "9.13.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-9.13.0.tgz",
|
||||||
|
"integrity": "sha512-7sGC7QnSQGa5LZP7bXLDhVDtQOeKGeBFGHF2Y8LVBwYZoQZCgWeKoPGTa5GMG8g/TzDgeXuYJQis7Ggiw2xTOw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/ecma402-abstract": "1.11.4",
|
||||||
|
"@formatjs/fast-memoize": "1.2.1",
|
||||||
|
"@formatjs/icu-messageformat-parser": "2.1.0",
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/intl-messageformat/node_modules/@formatjs/ecma402-abstract": {
|
||||||
|
"version": "1.11.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.11.4.tgz",
|
||||||
|
"integrity": "sha512-EBikYFp2JCdIfGEb5G9dyCkTGDmC57KSHhRQOC3aYxoPWVZvfWCDjZwkGYHN7Lis/fmuWl906bnNTJifDQ3sXw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/intl-localematcher": "0.2.25",
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/intl-messageformat/node_modules/@formatjs/intl-localematcher": {
|
||||||
|
"version": "0.2.25",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.25.tgz",
|
||||||
|
"integrity": "sha512-YmLcX70BxoSopLFdLr1Ds99NdlTI2oWoLbaUW2M406lxOIPzE1KQhRz2fPUkq34xVZQaihCoU29h0KK7An3bhA==",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ip": {
|
"node_modules/ip": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
|
||||||
@@ -4827,6 +4934,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
|
||||||
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="
|
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/negotiator": {
|
||||||
|
"version": "0.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||||
|
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/next": {
|
"node_modules/next": {
|
||||||
"version": "13.4.7",
|
"version": "13.4.7",
|
||||||
"resolved": "https://registry.npmjs.org/next/-/next-13.4.7.tgz",
|
"resolved": "https://registry.npmjs.org/next/-/next-13.4.7.tgz",
|
||||||
@@ -4877,6 +4992,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/next-intl": {
|
||||||
|
"version": "3.0.0-beta.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/next-intl/-/next-intl-3.0.0-beta.7.tgz",
|
||||||
|
"integrity": "sha512-EMNBHP/xTni6Y/yT9ACytMU2RNsZkoa8ccEomRrTSWyOmSyYFj1vk7A8oSfecGWm6c0n//qKhfzdXzuik7+taQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/intl-localematcher": "^0.2.32",
|
||||||
|
"negotiator": "^0.6.3",
|
||||||
|
"server-only": "0.0.1",
|
||||||
|
"use-intl": "^2.17.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"next": "^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0",
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/next-intl/node_modules/@formatjs/intl-localematcher": {
|
||||||
|
"version": "0.2.32",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.32.tgz",
|
||||||
|
"integrity": "sha512-k/MEBstff4sttohyEpXxCmC3MqbUn9VvHGlZ8fauLzkbwXmVrEeyzS+4uhrvAk9DWU9/7otYWxyDox4nT/KVLQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/next/node_modules/postcss": {
|
"node_modules/next/node_modules/postcss": {
|
||||||
"version": "8.4.14",
|
"version": "8.4.14",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
|
||||||
@@ -6009,6 +6150,11 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/server-only": {
|
||||||
|
"version": "0.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/server-only/-/server-only-0.0.1.tgz",
|
||||||
|
"integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA=="
|
||||||
|
},
|
||||||
"node_modules/sharp": {
|
"node_modules/sharp": {
|
||||||
"version": "0.32.1",
|
"version": "0.32.1",
|
||||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.1.tgz",
|
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.1.tgz",
|
||||||
@@ -6826,6 +6972,21 @@
|
|||||||
"punycode": "^2.1.0"
|
"punycode": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/use-intl": {
|
||||||
|
"version": "2.17.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/use-intl/-/use-intl-2.17.0.tgz",
|
||||||
|
"integrity": "sha512-vnLZeWyAZU2BoTQ6DeXfOBy5gxU/Fz6SSid0wJneDjlmCeT8BaVeHzzN+4Qj6mM9p8AH5I1zwjzU9rPYkmznIw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/ecma402-abstract": "^1.11.4",
|
||||||
|
"intl-messageformat": "^9.3.18"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/util-deprecate": {
|
"node_modules/util-deprecate": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
"leaflet-defaulticon-compatibility": "^0.1.1",
|
"leaflet-defaulticon-compatibility": "^0.1.1",
|
||||||
"mongodb": "^5.6.0",
|
"mongodb": "^5.6.0",
|
||||||
"next": "^13.4.7",
|
"next": "^13.4.7",
|
||||||
|
"next-intl": "^3.0.0-beta.7",
|
||||||
"nodemailer": "^6.9.3",
|
"nodemailer": "^6.9.3",
|
||||||
"postcss": "8.4.24",
|
"postcss": "8.4.24",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
|||||||
+1
-1
@@ -24,6 +24,6 @@
|
|||||||
"@/*": ["./*"]
|
"@/*": ["./*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "global.d.ts"],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
|||||||
+61
-31
@@ -1,8 +1,13 @@
|
|||||||
import { TournamentDataProps } from "@/types";
|
import { TournamentDataProps, Tournament } from "@/types";
|
||||||
import { LatLngLiteral } from "leaflet";
|
import L, { LatLngLiteral } from "leaflet";
|
||||||
|
import {
|
||||||
import L from "leaflet";
|
LayerGroup,
|
||||||
import { LayerGroup, LayersControl, Marker, Popup } from "react-leaflet";
|
LayersControl,
|
||||||
|
Marker,
|
||||||
|
Popup,
|
||||||
|
MarkerProps,
|
||||||
|
} from "react-leaflet";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
const coordinateRandomisation = (lat: number, lng: number): LatLngLiteral => {
|
const coordinateRandomisation = (lat: number, lng: number): LatLngLiteral => {
|
||||||
const randomisation = () => Math.random() * (-0.01 - 0.01) + 0.01;
|
const randomisation = () => Math.random() * (-0.01 - 0.01) + 0.01;
|
||||||
@@ -12,6 +17,50 @@ const coordinateRandomisation = (lat: number, lng: number): LatLngLiteral => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type TournamentMarkerProps = {
|
||||||
|
tournament: Tournament;
|
||||||
|
colour: string;
|
||||||
|
} & Omit<MarkerProps, "position">;
|
||||||
|
|
||||||
|
export const CompetitionMarker = ({
|
||||||
|
tournament,
|
||||||
|
colour,
|
||||||
|
...markerProps
|
||||||
|
}: TournamentMarkerProps) => {
|
||||||
|
const t = useTranslations("Competitions");
|
||||||
|
|
||||||
|
const iconOptions = new L.Icon({
|
||||||
|
iconUrl: `/images/leaflet/marker-icon-2x-${colour}.png`,
|
||||||
|
shadowUrl: "/images/leaflet/marker-shadow.png",
|
||||||
|
iconSize: [25, 41],
|
||||||
|
iconAnchor: [12, 41],
|
||||||
|
popupAnchor: [1, -34],
|
||||||
|
shadowSize: [41, 41],
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Marker
|
||||||
|
position={coordinateRandomisation(
|
||||||
|
tournament.coordinates[0],
|
||||||
|
tournament.coordinates[1]
|
||||||
|
)}
|
||||||
|
icon={iconOptions}
|
||||||
|
{...markerProps}
|
||||||
|
>
|
||||||
|
<Popup>
|
||||||
|
<p>
|
||||||
|
{tournament.date}
|
||||||
|
<br />
|
||||||
|
<a href={tournament.url} target="_blank" rel="noopener noreferrer">
|
||||||
|
{tournament.tournament}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
{t("approx")}
|
||||||
|
</Popup>
|
||||||
|
</Marker>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const createLayerGroups = (
|
export const createLayerGroups = (
|
||||||
timeControl: string,
|
timeControl: string,
|
||||||
colour: string,
|
colour: string,
|
||||||
@@ -21,33 +70,14 @@ export const createLayerGroups = (
|
|||||||
(t) => t.time_control === timeControl
|
(t) => t.time_control === timeControl
|
||||||
);
|
);
|
||||||
|
|
||||||
const iconOptions = new L.Icon({
|
const layerGroup = filteredTournaments.map((tournament) => {
|
||||||
iconUrl: `images/leaflet/marker-icon-2x-${colour}.png`,
|
|
||||||
shadowUrl: "images/leaflet/marker-shadow.png",
|
|
||||||
iconSize: [25, 41],
|
|
||||||
iconAnchor: [12, 41],
|
|
||||||
popupAnchor: [1, -34],
|
|
||||||
shadowSize: [41, 41],
|
|
||||||
});
|
|
||||||
|
|
||||||
const layerGroup = filteredTournaments.map((t) => {
|
|
||||||
return (
|
return (
|
||||||
<Marker
|
<CompetitionMarker
|
||||||
position={coordinateRandomisation(t.coordinates[0], t.coordinates[1])}
|
key={tournament._id}
|
||||||
key={t._id}
|
tournament={tournament}
|
||||||
icon={iconOptions}
|
colour={colour}
|
||||||
>
|
riseOnHover
|
||||||
<Popup>
|
/>
|
||||||
<p>
|
|
||||||
{t.date}
|
|
||||||
<br />
|
|
||||||
<a href={t.url} target="_blank" rel="noopener noreferrer">
|
|
||||||
{t.tournament}
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
géolocalisation approximative
|
|
||||||
</Popup>
|
|
||||||
</Marker>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user