mirror of
https://github.com/TheRealOwenRees/echecsfrance.git
synced 2026-07-23 04:26:57 +00:00
Fix layout issues & co-locate components (#46)
* Fix layout issues & co-locate components * Fix front page * Fix centering
This commit is contained in:
Vendored
+1
@@ -11,6 +11,7 @@
|
|||||||
"localisation",
|
"localisation",
|
||||||
"randomisation",
|
"randomisation",
|
||||||
"Rapide",
|
"Rapide",
|
||||||
|
"Rees",
|
||||||
"sommes",
|
"sommes",
|
||||||
"tournois"
|
"tournois"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -7,12 +7,13 @@ export default function Footer() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<footer
|
<footer
|
||||||
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"
|
className="fixed bottom-0 z-30 flex h-20 w-full flex-col items-center justify-center 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>
|
||||||
<p>© {new Date().getFullYear()} - Owen Rees</p>
|
<p>© {new Date().getFullYear()} - Owen Rees</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex space-x-4 p-2">
|
<div className="flex space-x-4 p-2">
|
||||||
<Link
|
<Link
|
||||||
href="https://github.com/TheRealOwenRees/echecsfrance"
|
href="https://github.com/TheRealOwenRees/echecsfrance"
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import HamburgerMenu from "@/components/HamburgerMenu";
|
import HamburgerMenu from "./HamburgerMenu";
|
||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
|
|
||||||
const Hamburger = () => {
|
const Hamburger = () => {
|
||||||
@@ -30,13 +30,12 @@ const Hamburger = () => {
|
|||||||
}`}
|
}`}
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
{
|
|
||||||
<HamburgerMenu
|
<HamburgerMenu
|
||||||
menuVisible={menuVisible}
|
menuVisible={menuVisible}
|
||||||
setMenuVisible={setMenuVisible}
|
setMenuVisible={setMenuVisible}
|
||||||
hamburgerButtonRef={hamburgerButtonRef}
|
hamburgerButtonRef={hamburgerButtonRef}
|
||||||
/>
|
/>
|
||||||
}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -8,8 +8,8 @@ import { Dispatch, RefObject, SetStateAction, useRef, useState } from "react";
|
|||||||
import Link from "next-intl/link";
|
import Link from "next-intl/link";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
import ThemeSwitcher from "@/components/ThemeSwitcher";
|
|
||||||
import useHamburgerClose from "@/hooks/useHamburgerClose";
|
import useHamburgerClose from "@/hooks/useHamburgerClose";
|
||||||
|
import ThemeSwitcher from "./ThemeSwitcher";
|
||||||
|
|
||||||
const HamburgerMenu = ({
|
const HamburgerMenu = ({
|
||||||
menuVisible,
|
menuVisible,
|
||||||
@@ -53,9 +53,9 @@ const HamburgerMenu = ({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={menuRef}
|
ref={menuRef}
|
||||||
className={`absolute -right-[173px] top-0 ${
|
className={`fixed right-0 top-0 ${
|
||||||
menuVisible ? "-translate-x-full" : ""
|
menuVisible ? "" : "translate-x-full"
|
||||||
} z-[9999] flex bg-teal-600 transition-transform duration-500 ease-linear dark:bg-gray-600 md:hidden`}
|
} z-[9999] flex bg-teal-600 transition-transform duration-200 ease-linear dark:bg-gray-600 md:hidden`}
|
||||||
onMouseEnter={handleMouseEnterMenu}
|
onMouseEnter={handleMouseEnterMenu}
|
||||||
onMouseLeave={handleMouseLeaveMenu}
|
onMouseLeave={handleMouseLeaveMenu}
|
||||||
>
|
>
|
||||||
@@ -65,7 +65,7 @@ const HamburgerMenu = ({
|
|||||||
href="/tournois"
|
href="/tournois"
|
||||||
className="border-b-2 border-transparent transition-all duration-300 ease-in-out hover:border-white"
|
className="border-b-2 border-transparent transition-all duration-300 ease-in-out hover:border-white"
|
||||||
>
|
>
|
||||||
{t("competitions")}
|
{t("tournaments")}
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li className="py-5">
|
<li className="py-5">
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import Link from "next-intl/link";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
|
import ThemeSwitcher from "./ThemeSwitcher";
|
||||||
|
import Hamburger from "./Hamburger";
|
||||||
|
|
||||||
|
export default function Navbar() {
|
||||||
|
const t = useTranslations("Nav");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav
|
||||||
|
className="relative mt-0 h-16 w-full overflow-x-clip border-b-[1px] bg-white px-5 dark:border-gray-700 dark:bg-gray-800"
|
||||||
|
data-test="navbar"
|
||||||
|
>
|
||||||
|
<div className="container mx-auto flex h-full items-center">
|
||||||
|
<div className="flex w-full justify-center font-extrabold md:w-1/2 md:justify-start">
|
||||||
|
<Link
|
||||||
|
className="text-gray-900 no-underline hover:no-underline dark:text-white"
|
||||||
|
href="/"
|
||||||
|
>
|
||||||
|
<span className="text-2xl">{t("title")}</span>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="pb-2 md:hidden" data-test="mobile-menu">
|
||||||
|
<Hamburger />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="hidden h-full justify-center md:flex md:w-1/2 md:justify-end"
|
||||||
|
data-test="desktop-menu"
|
||||||
|
>
|
||||||
|
<ul className="list-reset flex h-full flex-1 items-center justify-around text-gray-900 no-underline dark:text-white md:flex-none">
|
||||||
|
<li className="mr-10 h-full">
|
||||||
|
<Link
|
||||||
|
className="inline-flex h-full items-center border-b-4 border-t-4 border-transparent transition-all duration-300 ease-in-out hover:border-b-teal-600"
|
||||||
|
href="/tournois"
|
||||||
|
>
|
||||||
|
{t("tournaments")}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li className="mr-10 h-full">
|
||||||
|
<Link
|
||||||
|
className="inline-flex h-full items-center border-b-4 border-t-4 border-transparent transition-all duration-300 ease-in-out hover:border-b-teal-600"
|
||||||
|
href="/qui-sommes-nous"
|
||||||
|
>
|
||||||
|
{t("about")}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li className="mr-10 h-full">
|
||||||
|
<Link
|
||||||
|
className="inline-flex h-full items-center border-b-4 border-t-4 border-transparent transition-all duration-300 ease-in-out hover:border-b-teal-600"
|
||||||
|
href="/contactez-nous"
|
||||||
|
>
|
||||||
|
{t("contact")}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<ThemeSwitcher />
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { NextIntlClientProvider, AbstractIntlMessages } from "next-intl";
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
|
import "@/app/globals.css";
|
||||||
|
import Navbar from "./Navbar";
|
||||||
|
import Footer from "./Footer";
|
||||||
|
|
||||||
|
const TestableLayout = ({
|
||||||
|
children,
|
||||||
|
locale,
|
||||||
|
messages,
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
locale: string;
|
||||||
|
messages: AbstractIntlMessages;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<NextIntlClientProvider locale={locale} messages={messages}>
|
||||||
|
<div className="flex min-h-screen flex-col bg-white font-sans leading-normal tracking-normal dark:bg-gray-800">
|
||||||
|
<Navbar />
|
||||||
|
<div className="relative mb-20 flex flex-1 justify-center">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
</NextIntlClientProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TestableLayout;
|
||||||
@@ -1,28 +1,25 @@
|
|||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
import Layout from "@/components/Layout";
|
import ContactForm from "./ContactForm";
|
||||||
import ContactForm from "@/components/ContactForm";
|
|
||||||
|
|
||||||
// TODO fix page sizing
|
// TODO fix page sizing
|
||||||
export default function Contact() {
|
export default function Contact() {
|
||||||
const t = useTranslations("Contact");
|
const t = useTranslations("Contact");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<section className="grid place-items-center bg-white dark:bg-gray-800">
|
||||||
<section className="grid place-items-center bg-white dark:bg-gray-800">
|
<div className="mx-auto max-w-screen-md px-4 pb-16 pt-8 lg:pt-16">
|
||||||
<div className="mx-auto max-w-screen-md px-4 pb-32 pt-8 lg:pt-16">
|
<h2
|
||||||
<h2
|
className="mb-4 text-center text-4xl font-extrabold tracking-tight text-gray-900 dark:text-white"
|
||||||
className="mb-4 text-center text-4xl font-extrabold tracking-tight text-gray-900 dark:text-white"
|
data-test="header2"
|
||||||
data-test="header2"
|
>
|
||||||
>
|
{t("title")}
|
||||||
{t("title")}
|
</h2>
|
||||||
</h2>
|
<p className="mb-8 text-center font-light text-gray-500 dark:text-gray-400 sm:text-xl lg:mb-16">
|
||||||
<p className="mb-8 text-center font-light text-gray-500 dark:text-gray-400 sm:text-xl lg:mb-16">
|
{t("info")}
|
||||||
{t("info")}
|
</p>
|
||||||
</p>
|
<ContactForm />
|
||||||
<ContactForm />
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</section>
|
|
||||||
</Layout>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { Analytics } from "@vercel/analytics/react";
|
import { Analytics } from "@vercel/analytics/react";
|
||||||
import { Inter } from "next/font/google";
|
import { Inter } from "next/font/google";
|
||||||
import { useLocale, NextIntlClientProvider } from "next-intl";
|
import { useLocale } from "next-intl";
|
||||||
import { getTranslator } from "next-intl/server";
|
import { getTranslator } from "next-intl/server";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
import "@/app/globals.css";
|
import "@/app/globals.css";
|
||||||
|
import TestableLayout from "./components/TestableLayout";
|
||||||
|
|
||||||
const inter = Inter({ subsets: ["latin"] });
|
const inter = Inter({ subsets: ["latin"] });
|
||||||
|
|
||||||
@@ -49,9 +50,9 @@ export default async function RootLayout({
|
|||||||
return (
|
return (
|
||||||
<html lang={locale}>
|
<html lang={locale}>
|
||||||
<body className={inter.className}>
|
<body className={inter.className}>
|
||||||
<NextIntlClientProvider locale={locale} messages={messages}>
|
<TestableLayout locale={locale} messages={messages}>
|
||||||
{children}
|
{children}
|
||||||
</NextIntlClientProvider>
|
</TestableLayout>
|
||||||
<Analytics />
|
<Analytics />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+31
-34
@@ -2,45 +2,42 @@ import Link from "next/link";
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
import Layout from "@/components/Layout";
|
|
||||||
import bgImage from "@/public/images/map-bg.jpg";
|
import bgImage from "@/public/images/map-bg.jpg";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const t = useTranslations("Home");
|
const t = useTranslations("Home");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<header className="relative flex flex-1 items-center justify-center">
|
||||||
<header className="grid h-[calc(100%-153px)] md:h-[calc(100%-173px)] items-center">
|
<div className="absolute h-full w-full brightness-[0.2]">
|
||||||
<div className="relative h-full w-full brightness-[0.2]">
|
<Image
|
||||||
<Image
|
src={bgImage}
|
||||||
src={bgImage}
|
alt="Background image of France"
|
||||||
alt="Background image of France"
|
fill={true}
|
||||||
fill={true}
|
style={{ objectFit: "cover" }}
|
||||||
style={{ objectFit: "cover" }}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
<div className="z-10 text-center text-white">
|
||||||
<div className="absolute flex flex-col items-center w-full text-center text-white">
|
<h1 className="p-5 text-5xl" data-test="header1">
|
||||||
<h1 className="p-5 text-5xl" data-test="header1">
|
{t("title")}
|
||||||
{t("title")}
|
</h1>
|
||||||
</h1>
|
<h2 className="p-5 text-3xl">{t("purpose")}</h2>
|
||||||
<h2 className="p-5 text-3xl">{t("purpose")}</h2>
|
<h3 className="mb-5 p-5 text-xl">
|
||||||
<h3 className="mb-5 p-5 text-xl">
|
{t.rich("how", {
|
||||||
{t.rich("how", {
|
link: (chunks) => (
|
||||||
link: (chunks) => (
|
<Link href="http://www.echecs.asso.fr/" target="_blank">
|
||||||
<Link href="http://www.echecs.asso.fr/" target="_blank">
|
<abbr title="Fédération Française des Échecs">{chunks}</abbr>
|
||||||
<abbr title="Fédération Française des Échecs">{chunks}</abbr>
|
</Link>
|
||||||
</Link>
|
),
|
||||||
),
|
})}
|
||||||
})}
|
</h3>
|
||||||
</h3>
|
<Link
|
||||||
<Link
|
href="/tournois"
|
||||||
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"
|
||||||
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")}
|
||||||
{t("mapLink")}
|
</Link>
|
||||||
</Link>
|
</div>
|
||||||
</div>
|
</header>
|
||||||
</header>
|
|
||||||
</Layout>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+105
-109
@@ -1,121 +1,117 @@
|
|||||||
import Link from "next-intl/link";
|
import Link from "next-intl/link";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
import Layout from "@/components/Layout";
|
|
||||||
|
|
||||||
export default function About() {
|
export default function About() {
|
||||||
const t = useTranslations("About");
|
const t = useTranslations("About");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<header className="mb-16 grid place-items-center bg-white dark:bg-gray-800">
|
||||||
<header className="grid place-items-center bg-white dark:bg-gray-800">
|
<div className="h-full max-w-5xl bg-white px-4 pt-8 dark:bg-gray-800">
|
||||||
<div className="h-full max-w-5xl bg-white px-4 pb-12 pt-8 dark:bg-gray-800">
|
<h2
|
||||||
<h2
|
className="mb-8 text-center text-4xl font-extrabold tracking-tight text-gray-900 dark:text-white"
|
||||||
className="mb-8 text-center text-4xl font-extrabold tracking-tight text-gray-900 dark:text-white"
|
data-test="header2"
|
||||||
data-test="header2"
|
>
|
||||||
>
|
{t("title")}
|
||||||
{t("title")}
|
</h2>
|
||||||
</h2>
|
<p className="mb-4 text-center font-light text-gray-500 dark:text-gray-400 md:text-xl">
|
||||||
<p className="mb-4 text-center font-light text-gray-500 dark:text-gray-400 md:text-xl">
|
{t("p1")}
|
||||||
{t("p1")}
|
</p>
|
||||||
</p>
|
<p className="mb-4 text-center font-light text-gray-500 dark:text-gray-400 md:text-xl">
|
||||||
<p className="mb-4 text-center font-light text-gray-500 dark:text-gray-400 md:text-xl">
|
{t.rich("p2", {
|
||||||
{t.rich("p2", {
|
homeLink: (chunks) => (
|
||||||
homeLink: (chunks) => (
|
<Link href="/" className="text-teal-600">
|
||||||
<Link href="/" className="text-teal-600">
|
{chunks}
|
||||||
{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>
|
|
||||||
<ul className="mb-4 text-center text-teal-600">
|
|
||||||
<li>
|
|
||||||
<Link href="https://github.com/AlvaroNW" target="_blank">
|
|
||||||
AlvaroNW
|
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
),
|
||||||
<li>
|
})}
|
||||||
<Link href="https://github.com/Florifourchette" target="_blank">
|
</p>
|
||||||
Florifourchette
|
<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>
|
||||||
|
<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>
|
</Link>
|
||||||
</li>
|
),
|
||||||
</ul>
|
})}
|
||||||
<h3
|
</p>
|
||||||
className="mb-4 mt-8 text-center text-2xl font-extrabold tracking-tight text-gray-900 dark:text-white"
|
<ul className="mb-4 flex justify-around text-teal-600">
|
||||||
data-test="header2"
|
<li>
|
||||||
>
|
<Link href="https://nextjs.org/" target="_blank">
|
||||||
{t("techTitle")}
|
NextJS
|
||||||
</h3>
|
</Link>
|
||||||
<p className="mb-4 text-center font-light text-gray-500 dark:text-gray-400 md:text-xl">
|
</li>
|
||||||
{t.rich("techInfo", {
|
<li>
|
||||||
homeLink: (chunks) => (
|
<Link href="#" target="_blank">
|
||||||
<Link href="/" className="text-teal-600">
|
Typescript
|
||||||
{chunks}
|
</Link>
|
||||||
</Link>
|
</li>
|
||||||
),
|
<li>
|
||||||
})}
|
<Link href="https://tailwindcss.com/" target="_blank">
|
||||||
</p>
|
Tailwind
|
||||||
<ul className="mb-4 flex justify-around text-teal-600">
|
</Link>
|
||||||
<li>
|
</li>
|
||||||
<Link href="https://nextjs.org/" target="_blank">
|
<li>
|
||||||
NextJS
|
<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="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>
|
</Link>
|
||||||
</li>
|
),
|
||||||
<li>
|
})}
|
||||||
<Link href="#" target="_blank">
|
</p>
|
||||||
Typescript
|
</div>
|
||||||
</Link>
|
</header>
|
||||||
</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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const SearchBar = ({
|
|||||||
tournamentFilter,
|
tournamentFilter,
|
||||||
setTournamentFilter,
|
setTournamentFilter,
|
||||||
}: SearchBarProps) => {
|
}: SearchBarProps) => {
|
||||||
const t = useTranslations("Competitions");
|
const t = useTranslations("Tournaments");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white p-3 dark:bg-gray-800">
|
<div className="bg-white p-3 dark:bg-gray-800">
|
||||||
@@ -10,7 +10,7 @@ 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";
|
||||||
import Legend from "@/components/Legend";
|
import Legend from "./Legend";
|
||||||
|
|
||||||
export default function TournamentMap({ tournamentData }: TournamentDataProps) {
|
export default function TournamentMap({ tournamentData }: TournamentDataProps) {
|
||||||
const center: LatLngLiteral = { lat: 47.0844, lng: 2.3964 };
|
const center: LatLngLiteral = { lat: 47.0844, lng: 2.3964 };
|
||||||
@@ -28,10 +28,7 @@ export default function TournamentMap({ tournamentData }: TournamentDataProps) {
|
|||||||
const otherMarkers = createLayerGroups("1h KO", "red", { tournamentData });
|
const otherMarkers = createLayerGroups("1h KO", "red", { tournamentData });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<section id="tournament-map" className="grid h-[calc(100vh-144px)]">
|
||||||
id="tournament-map"
|
|
||||||
className="grid h-[calc(100vh-153px)] md:h-[calc(100vh-83px)] lg:h-[calc(100vh-173px)]"
|
|
||||||
>
|
|
||||||
<MapContainer
|
<MapContainer
|
||||||
center={center}
|
center={center}
|
||||||
zoom={5}
|
zoom={5}
|
||||||
@@ -4,14 +4,14 @@ import { TournamentDataProps } from "@/types";
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
import SearchBar from "@/components/SearchBar";
|
import SearchBar from "./SearchBar";
|
||||||
import ScrollToTopButton from "@/components/ScrollToTopButton";
|
import ScrollToTopButton from "./ScrollToTopButton";
|
||||||
|
|
||||||
export default function TournamentTable({
|
export default function TournamentTable({
|
||||||
tournamentData,
|
tournamentData,
|
||||||
}: TournamentDataProps) {
|
}: TournamentDataProps) {
|
||||||
let tableData;
|
let tableData;
|
||||||
const t = useTranslations("Competitions");
|
const t = useTranslations("Tournaments");
|
||||||
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);
|
||||||
@@ -63,7 +63,7 @@ export default function TournamentTable({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
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"
|
className="tournament-table grid w-full auto-rows-max pb-20 lg:col-start-2 lg:col-end-3 lg:h-[calc(100vh-144px)] lg:overflow-y-scroll"
|
||||||
id="tournament-table"
|
id="tournament-table"
|
||||||
data-test="tournament-table-div"
|
data-test="tournament-table-div"
|
||||||
>
|
>
|
||||||
@@ -89,7 +89,7 @@ export default function TournamentTable({
|
|||||||
{t("town")}
|
{t("town")}
|
||||||
</th>
|
</th>
|
||||||
<th className="sticky top-0 bg-teal-600 p-3 text-white dark:bg-gray-600">
|
<th className="sticky top-0 bg-teal-600 p-3 text-white dark:bg-gray-600">
|
||||||
{t("competition")}
|
{t("tournament")}
|
||||||
</th>
|
</th>
|
||||||
<th className="sticky top-0 bg-teal-600 p-3 text-white dark:bg-gray-600">
|
<th className="sticky top-0 bg-teal-600 p-3 text-white dark:bg-gray-600">
|
||||||
{t("timeControl")}
|
{t("timeControl")}
|
||||||
@@ -2,15 +2,15 @@ import clientPromise from "@/lib/mongodb";
|
|||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
import Layout from "@/components/Layout";
|
|
||||||
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";
|
||||||
|
|
||||||
|
import TournamentTable from "./TournamentTable";
|
||||||
|
|
||||||
export const revalidate = 3600; // revalidate cache every 6 hours;
|
export const revalidate = 3600; // revalidate cache every 6 hours;
|
||||||
|
|
||||||
const LoadingMap = () => {
|
const LoadingMap = () => {
|
||||||
const t = useTranslations("Competitions");
|
const t = useTranslations("Tournaments");
|
||||||
return (
|
return (
|
||||||
<div className="grid h-screen place-items-center bg-white text-gray-900 dark:bg-gray-800 dark:text-white">
|
<div className="grid h-screen place-items-center bg-white text-gray-900 dark:bg-gray-800 dark:text-white">
|
||||||
<p>{t("loading")}</p>
|
<p>{t("loading")}</p>
|
||||||
@@ -22,7 +22,7 @@ const LoadingMap = () => {
|
|||||||
* Imports the tournament map component, ensuring CSR only.
|
* Imports the tournament map component, ensuring CSR only.
|
||||||
* @remarks SSR is not supported by react-leaflet
|
* @remarks SSR is not supported by react-leaflet
|
||||||
*/
|
*/
|
||||||
const TournamentMap = dynamic(() => import("@/components/TournamentMap"), {
|
const TournamentMap = dynamic(() => import("./TournamentMap"), {
|
||||||
ssr: false,
|
ssr: false,
|
||||||
loading: LoadingMap,
|
loading: LoadingMap,
|
||||||
});
|
});
|
||||||
@@ -43,15 +43,13 @@ export default async function Tournaments() {
|
|||||||
const tournamentData = await getTournaments();
|
const tournamentData = await getTournaments();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<main className="relative grid h-full lg:grid-cols-2">
|
||||||
<main className="relative grid lg:grid-cols-2">
|
<div className="">
|
||||||
<div className="">
|
<TournamentMap tournamentData={JSON.parse(tournamentData)} />
|
||||||
<TournamentMap tournamentData={JSON.parse(tournamentData)} />
|
</div>
|
||||||
</div>
|
<div className="relative bg-white dark:bg-gray-800 lg:overflow-y-auto">
|
||||||
<div className="relative bg-white dark:bg-gray-800 lg:overflow-y-auto">
|
<TournamentTable tournamentData={JSON.parse(tournamentData)} />
|
||||||
<TournamentTable tournamentData={JSON.parse(tournamentData)} />
|
</div>
|
||||||
</div>
|
</main>
|
||||||
</main>
|
|
||||||
</Layout>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
import Navbar from "@/components/Navbar";
|
|
||||||
import Footer from "@/components/Footer";
|
|
||||||
import { ReactNode } from "react";
|
|
||||||
|
|
||||||
export default function Layout({
|
|
||||||
children, // will be a page or nested layout
|
|
||||||
}: {
|
|
||||||
children: ReactNode;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<div className="h-screen min-h-[600px] bg-white dark:bg-gray-800 font-sans leading-normal tracking-normal">
|
|
||||||
<Navbar />
|
|
||||||
{children}
|
|
||||||
<Footer />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
import Link from "next-intl/link";
|
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
|
|
||||||
import ThemeSwitcher from "@/components/ThemeSwitcher";
|
|
||||||
import Hamburger from "@/components/Hamburger";
|
|
||||||
|
|
||||||
export default function Navbar() {
|
|
||||||
const t = useTranslations("Nav");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<nav
|
|
||||||
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"
|
|
||||||
>
|
|
||||||
<div className="container mx-auto flex items-center">
|
|
||||||
<div className="flex w-full justify-center pb-3 font-extrabold md:w-1/2 md:justify-start md:pb-0">
|
|
||||||
<Link
|
|
||||||
className="text-gray-900 no-underline hover:no-underline dark:text-white"
|
|
||||||
href="/"
|
|
||||||
>
|
|
||||||
<span className="text-2xl">{t("title")}</span>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
<div className="pb-2 md:hidden" data-test="mobile-menu">
|
|
||||||
<Hamburger />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className="hidden justify-center pt-2 md:flex md:w-1/2 md:justify-end"
|
|
||||||
data-test="desktop-menu"
|
|
||||||
>
|
|
||||||
<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">
|
|
||||||
<Link
|
|
||||||
className="inline-block border-b-4 border-transparent py-5 transition-all duration-300 ease-in-out hover:border-teal-600"
|
|
||||||
href="/tournois"
|
|
||||||
>
|
|
||||||
{t("competitions")}
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li className="mr-10">
|
|
||||||
<Link
|
|
||||||
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"
|
|
||||||
>
|
|
||||||
{t("about")}
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li className="mr-10">
|
|
||||||
<Link
|
|
||||||
className="inline-block border-b-4 border-transparent py-5 transition-all duration-300 ease-in-out hover:border-teal-600"
|
|
||||||
href="/contactez-nous"
|
|
||||||
>
|
|
||||||
{t("contact")}
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<ThemeSwitcher />
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import ScrollToTopButton from "@/components/ScrollToTopButton";
|
import ScrollToTopButton from "@/app/[lang]/tournois/ScrollToTopButton";
|
||||||
|
|
||||||
describe("Scroll to top button", () => {
|
describe("Scroll to top button", () => {
|
||||||
it("desktop scroll button clickable", () => {
|
it("desktop scroll button clickable", () => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import ThemeSwitcher from "../../components/ThemeSwitcher";
|
import ThemeSwitcher from "@/app/[lang]/components/ThemeSwitcher";
|
||||||
import "@/css/theme-toggle.css";
|
import "@/css/theme-toggle.css";
|
||||||
|
|
||||||
describe("ThemeSwitcher component", () => {
|
describe("ThemeSwitcher component", () => {
|
||||||
@@ -14,8 +14,9 @@ describe("ThemeSwitcher component", () => {
|
|||||||
"linear-gradient(rgb(0, 255, 255), rgb(135, 206, 235)"
|
"linear-gradient(rgb(0, 255, 255), rgb(135, 206, 235)"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// checking that the toggle is clickable and dark mode is active
|
// checking that the toggle is clickable and dark mode is active
|
||||||
cy.getByData("toggle").click();
|
cy.getByData("toggle").last().click();
|
||||||
cy.getByData("toggle-dark").should("exist");
|
cy.getByData("toggle-dark").should("exist");
|
||||||
cy.getByData("toggle-dark").should(($toggle) => {
|
cy.getByData("toggle-dark").should(($toggle) => {
|
||||||
const backgroundImage = $toggle.css("background-image");
|
const backgroundImage = $toggle.css("background-image");
|
||||||
|
|||||||
@@ -14,8 +14,8 @@
|
|||||||
// ***********************************************************
|
// ***********************************************************
|
||||||
|
|
||||||
import { mount } from "cypress/react18";
|
import { mount } from "cypress/react18";
|
||||||
import { NextIntlClientProvider } from "next-intl";
|
|
||||||
|
|
||||||
|
import TestableLayout from "@/app/[lang]/components/TestableLayout";
|
||||||
import messages from "@/messages/fr.json";
|
import messages from "@/messages/fr.json";
|
||||||
import "./commands";
|
import "./commands";
|
||||||
|
|
||||||
@@ -37,9 +37,9 @@ declare global {
|
|||||||
|
|
||||||
Cypress.Commands.add("mount", (component, options) => {
|
Cypress.Commands.add("mount", (component, options) => {
|
||||||
const wrapped = (
|
const wrapped = (
|
||||||
<NextIntlClientProvider locale="fr" messages={messages}>
|
<TestableLayout locale="fr" messages={messages}>
|
||||||
{component}
|
{component}
|
||||||
</NextIntlClientProvider>
|
</TestableLayout>
|
||||||
);
|
);
|
||||||
|
|
||||||
return mount(wrapped, options);
|
return mount(wrapped, options);
|
||||||
|
|||||||
+7
-7
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"Metadata": {
|
"Metadata": {
|
||||||
"title": "Echecs France - Tournois d'Echecs",
|
"title": "Echecs France - Tournois d'Echecs",
|
||||||
"description": "Find chess competitions, in France, on a map",
|
"description": "Find chess tournaments, in France, on a map",
|
||||||
"keywords": "chess, France, competition, competitions, FFE, map"
|
"keywords": "chess, France, tournament, tournaments, FFE, map"
|
||||||
},
|
},
|
||||||
|
|
||||||
"Nav": {
|
"Nav": {
|
||||||
"title": "Echecs France",
|
"title": "Echecs France",
|
||||||
"competitions": "Competitions",
|
"tournaments": "Tournaments",
|
||||||
"about": "About Us",
|
"about": "About Us",
|
||||||
"contact": "Contact"
|
"contact": "Contact"
|
||||||
},
|
},
|
||||||
@@ -26,19 +26,19 @@
|
|||||||
|
|
||||||
"Home": {
|
"Home": {
|
||||||
"title": "France Echecs",
|
"title": "France Echecs",
|
||||||
"purpose": "Find chess competitions, in France, on a map",
|
"purpose": "Find chess tournaments, in France, on a map",
|
||||||
"how": "A graphical representation of all the chess competitions published by the <link>FFE</link>",
|
"how": "A graphical representation of all the chess tournaments published by the <link>FFE</link>",
|
||||||
"mapLink": "See the Map"
|
"mapLink": "See the Map"
|
||||||
},
|
},
|
||||||
|
|
||||||
"Competitions": {
|
"Tournaments": {
|
||||||
"loading": "Loading...",
|
"loading": "Loading...",
|
||||||
"searchLabel": "Search",
|
"searchLabel": "Search",
|
||||||
"searchPlaceholder": "Search",
|
"searchPlaceholder": "Search",
|
||||||
"noneFound": "No tournaments found",
|
"noneFound": "No tournaments found",
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
"town": "Town",
|
"town": "Town",
|
||||||
"competition": "Competition",
|
"tournament": "Tournament",
|
||||||
"timeControl": "Time Control",
|
"timeControl": "Time Control",
|
||||||
"approx": "Géo-localisation is approximative"
|
"approx": "Géo-localisation is approximative"
|
||||||
},
|
},
|
||||||
|
|||||||
+3
-3
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
"Nav": {
|
"Nav": {
|
||||||
"title": "Échecs France",
|
"title": "Échecs France",
|
||||||
"competitions": "Tournois",
|
"tournaments": "Tournois",
|
||||||
"about": "Qui Sommes-Nous",
|
"about": "Qui Sommes-Nous",
|
||||||
"contact": "Contactez-Nous"
|
"contact": "Contactez-Nous"
|
||||||
},
|
},
|
||||||
@@ -31,14 +31,14 @@
|
|||||||
"mapLink": "Voir La Carte"
|
"mapLink": "Voir La Carte"
|
||||||
},
|
},
|
||||||
|
|
||||||
"Competitions": {
|
"Tournaments": {
|
||||||
"loading": "Téléchargement...",
|
"loading": "Téléchargement...",
|
||||||
"searchLabel": "Rechercher",
|
"searchLabel": "Rechercher",
|
||||||
"searchPlaceholder": "Rechercher",
|
"searchPlaceholder": "Rechercher",
|
||||||
"noneFound": "Pas de tournois trouvé",
|
"noneFound": "Pas de tournois trouvé",
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
"town": "Ville",
|
"town": "Ville",
|
||||||
"competition": "Tournois",
|
"tournament": "Tournois",
|
||||||
"timeControl": "Cadence",
|
"timeControl": "Cadence",
|
||||||
"approx": "Géolocalisation approximative"
|
"approx": "Géolocalisation approximative"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -22,12 +22,12 @@ type TournamentMarkerProps = {
|
|||||||
colour: string;
|
colour: string;
|
||||||
} & Omit<MarkerProps, "position">;
|
} & Omit<MarkerProps, "position">;
|
||||||
|
|
||||||
export const CompetitionMarker = ({
|
export const TournamentMarker = ({
|
||||||
tournament,
|
tournament,
|
||||||
colour,
|
colour,
|
||||||
...markerProps
|
...markerProps
|
||||||
}: TournamentMarkerProps) => {
|
}: TournamentMarkerProps) => {
|
||||||
const t = useTranslations("Competitions");
|
const t = useTranslations("Tournaments");
|
||||||
|
|
||||||
const iconOptions = new L.Icon({
|
const iconOptions = new L.Icon({
|
||||||
iconUrl: `/images/leaflet/marker-icon-2x-${colour}.png`,
|
iconUrl: `/images/leaflet/marker-icon-2x-${colour}.png`,
|
||||||
@@ -72,7 +72,7 @@ export const createLayerGroups = (
|
|||||||
|
|
||||||
const layerGroup = filteredTournaments.map((tournament) => {
|
const layerGroup = filteredTournaments.map((tournament) => {
|
||||||
return (
|
return (
|
||||||
<CompetitionMarker
|
<TournamentMarker
|
||||||
key={tournament._id}
|
key={tournament._id}
|
||||||
tournament={tournament}
|
tournament={tournament}
|
||||||
colour={colour}
|
colour={colour}
|
||||||
|
|||||||
Reference in New Issue
Block a user