Added CSRF & YouTube and dark mode

This commit is contained in:
cdricms
2025-01-22 17:39:03 +01:00
parent 48e761667f
commit 5a5846d853
29 changed files with 1186 additions and 280 deletions

View File

@@ -9,21 +9,19 @@ export default async function HistoryDetails({
params: Promise<{ blog_id: string }>;
}) {
const { blog_id } = await params;
let blog = {}
let blog = {};
try {
const res = await fetch('http://localhost:3001/blogs/' + blog_id, {method: "GET"})
blog = await res.json()
console.log(blog as Blog)
} catch(e) {
const res = await fetch("http://localhost:3001/blogs/" + blog_id, {
method: "GET",
});
blog = await res.json();
console.log(blog as Blog);
} catch (e) {
console.log(e);
}
if(blog == null) {
return(
<>
Error
</>
)
if (blog == null) {
return <>Error</>;
}
const blog_item_params: BlogItemParams = {

View File

@@ -4,8 +4,16 @@ import Features, { FeatureItem } from "@/components/features";
import Gallery from "@/components/gallery";
import Hero from "@/components/hero";
import Testimonial from "@/components/testimonial";
import { CarouselItem } from "@/components/ui/carousel";
import { IYoutube } from "@/interfaces/youtube";
export default async function Home() {
let videos: IYoutube | null = null;
if (process.env.YOUTUBE_API_KEY) {
const query = `https://www.googleapis.com/youtube/v3/search?key=${process.env.YOUTUBE_API_KEY}&channelId=UCzuFLl5I0WxSMqbeMaiq_FQ&part=snippet,id&order=date&maxResults=50`;
const res = await fetch(query);
videos = await res.json();
}
return (
<main>
<Hero />
@@ -20,10 +28,10 @@ export default async function Home() {
position="left"
image="https://shadcnblocks.com/images/block/placeholder-2.svg"
>
<ol className="list-decimal text-justify flex flex-col gap-4">
<ol className="flex list-decimal flex-col gap-4 text-justify">
<li>
Un Système Centré sur les Concepts{" "}
<ul className="list-disc list-inside">
<ul className="list-inside list-disc">
<li>
Étude et application des meilleurs
concepts et stratégies issus de
@@ -37,7 +45,7 @@ export default async function Home() {
</li>
<li>
Éducation au Mouvement et à lEfficacité
<ul className="list-disc list-inside">
<ul className="list-inside list-disc">
<li>
Plus quun enchaînement de techniques :
une véritable éducation aux mouvements
@@ -56,12 +64,12 @@ export default async function Home() {
position="right"
image="https://shadcnblocks.com/images/block/placeholder-2.svg"
>
<ol className="list-none text-justify flex flex-col gap-4">
<ol className="flex list-none flex-col gap-4 text-justify">
<li>
<span className="font-bold">
Les Premières Étapes
</span>
<ul className="list-disc list-inside">
<ul className="list-inside list-disc">
<li>
Initialement centré sur les techniques
et mouvements, le système sest montré
@@ -78,10 +86,10 @@ export default async function Home() {
<span className="font-bold">
La Découverte des Concepts Clés
</span>{" "}
<ul className="list-disc list-inside">
<ul className="list-inside list-disc">
<li>
Rôle central des concepts de combat :
<ul className="list-disc list-inside pl-4">
<ul className="list-inside list-disc pl-4">
<li>Puissance dans les frappes.</li>
<li>Blocage ferme.</li>
<li>Équilibre et attitude.</li>
@@ -103,7 +111,7 @@ export default async function Home() {
>
Latosa Escrima Concepts repose sur cinq concepts
fondamentaux :
<ul className="list-disc list-inside">
<ul className="list-inside list-disc">
<li>Équilibre</li>
<li>Vitesse (Timing et Distance)</li>
<li>Puissance</li>
@@ -112,8 +120,35 @@ export default async function Home() {
</ul>
</FeatureItem>
</Features>
<Gallery />
<Gallery />
<Gallery
tagLine="Tag Line"
cta="Book a demo"
ctaHref="#"
title="Gallery"
/>
{videos && (
<Gallery
tagLine=""
cta="Accéder à la chaîne"
ctaHref="https://youtube.com/@WingTsunPicardie"
title="Vidéos YouTube"
>
{videos.items.map((video) => {
return (
<CarouselItem
key={video.id.videoId}
className="pl-[20px] md:max-w-[452px]"
>
<iframe
width="424"
height="238"
src={`https://www.youtube.com/embed/${video.id.videoId}`}
></iframe>
</CarouselItem>
);
})}
</Gallery>
)}
<Testimonial />
</div>
</main>

View File

@@ -0,0 +1,199 @@
"use client";
import "@schedule-x/theme-shadcn/dist/index.css";
import { useNextCalendarApp, ScheduleXCalendar } from "@schedule-x/react";
import { createEventsServicePlugin } from "@schedule-x/events-service";
import {
CalendarEventExternal,
createViewDay,
createViewWeek,
} from "@schedule-x/calendar";
import { useEffect, useState } from "react";
import { format } from "date-fns";
import { Dialog } from "@radix-ui/react-dialog";
import {
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { CalendarIcon } from "lucide-react";
import { Calendar } from "@/components/ui/calendar";
import { cn } from "@/lib/utils";
const Planning = () => {
const plugins = [createEventsServicePlugin()];
const [eventSelected, setEventSelected] =
useState<CalendarEventExternal | null>(null);
const [events, setEvents] = useState<CalendarEventExternal[]>([
{
id: "1",
title: "Event 1",
start: format(new Date(Date.now()), "yyyy-MM-dd HH:mm"),
end: format(
new Date(Date.now() + 1 * 3600 * 1000),
"yyyy-MM-dd HH:mm",
),
},
]);
const calendar = useNextCalendarApp(
{
theme: "shadcn",
views: [createViewDay(), createViewWeek()],
defaultView: "week",
isDark: true,
isResponsive: true,
locale: "fr-FR",
dayBoundaries: {
start: "06:00",
end: "00:00",
},
events,
callbacks: {
onEventClick(event, e) {
setEventSelected(event);
},
},
},
plugins,
);
useEffect(() => {
// get all events
calendar?.events.getAll();
}, []);
return (
<div>
<div className="m-8">
<ScheduleXCalendar calendarApp={calendar} />
</div>
<Dialog
open={eventSelected !== null}
onOpenChange={(open) => {
setEventSelected((e) => (open ? e : null));
}}
>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>{eventSelected?.title}</DialogTitle>
<DialogDescription>
{eventSelected?.description}
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="start" className="text-right">
Début
</Label>
{/*<Popover>
<PopoverTrigger asChild>
<Button
variant={"outline"}
className={cn(
"w-[240px] pl-3 text-left font-normal",
!eventSelected?.start &&
"text-muted-foreground",
)}
>
{eventSelected?.start ? (
format(eventSelected?.start, "PPP")
) : (
<span>Choisissez une date.</span>
)}
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent
className="w-auto p-0"
align="start"
>
<Calendar
mode="single"
selected={
new Date(
eventSelected?.start ??
Date.now(),
)
}
// onSelect={field.onChange}
disabled={(date) =>
date > new Date() ||
date < new Date("1900-01-01")
}
initialFocus
/>
</PopoverContent>
</Popover> */}
<Input
id="start"
value={eventSelected?.start}
onChange={(e) => {
const val = e.currentTarget.value;
console.log(val);
setEventSelected((ev) => {
if (ev)
return {
...ev,
start: val,
};
return ev;
});
}}
className="col-span-3"
/>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="end" className="text-right">
Fin
</Label>
<Input
id="end"
value={eventSelected?.end}
onChange={(e) =>
setEventSelected((ev) => {
if (ev)
return {
...ev,
end: e.currentTarget.value,
};
return ev;
})
}
className="col-span-3"
/>
</div>
</div>
<DialogFooter>
<Button
onClick={() => {
setEvents((evs) => {
evs = evs.filter(
(e) => e.id !== eventSelected?.id,
);
evs.push(eventSelected!);
return evs;
});
}}
type="submit"
>
Mettre à jour
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
};
export default Planning;

View File

@@ -8,75 +8,59 @@ body {
@layer base {
:root {
/* Define your custom padding value */
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--background: 0 0% 98%;
--foreground: 226 32% 15%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--card-foreground: 226 32% 15%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--primary: 0 0% 9%;
--popover-foreground: 226 32% 15%;
--primary: 354 70% 44%;
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%;
--secondary: 0 0% 94%;
--secondary-foreground: 226 32% 15%;
--muted: 0 0% 94%;
--muted-foreground: 226 32% 70%;
--accent: 354 70% 44%;
--accent-foreground: 0 0% 98%;
--destructive: 0 84% 60%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--ring: 0 0% 3.9%;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--radius: 0.5rem;
--sidebar-background: 0 0% 98%;
--sidebar-foreground: 240 5.3% 26.1%;
--sidebar-primary: 240 5.9% 10%;
--sidebar-primary-foreground: 0 0% 98%;
--sidebar-accent: 240 4.8% 95.9%;
--sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 220 13% 91%;
--sidebar-ring: 217.2 91.2% 59.8%;
--border: 0 0% 90%;
--input: 0 0% 94%;
--ring: 354 70% 44%;
--radius: 0.75rem;
--chart-1: 354 70% 44%;
--chart-2: 20 90% 50%;
--chart-3: 200 90% 50%;
--chart-4: 300 90% 50%;
--chart-5: 60 90% 50%;
}
.dark {
--background: 0 0% 3.9%;
--background: 226 32% 15%;
--foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card: 226 32% 20%;
--card-foreground: 0 0% 98%;
--popover: 0 0% 3.9%;
--popover: 226 32% 20%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%;
--primary: 354 70% 44%;
--primary-foreground: 0 0% 98%;
--secondary: 226 32% 25%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--accent: 0 0% 14.9%;
--muted: 226 32% 25%;
--muted-foreground: 0 0% 70%;
--accent: 354 70% 44%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive: 0 84% 60%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 14.9%;
--input: 0 0% 14.9%;
--ring: 0 0% 83.1%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
--sidebar-background: 240 5.9% 10%;
--sidebar-foreground: 240 4.8% 95.9%;
--sidebar-primary: 224.3 76.3% 48%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 240 3.7% 15.9%;
--sidebar-accent-foreground: 240 4.8% 95.9%;
--sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 217.2 91.2% 59.8%;
--border: 226 32% 35%;
--input: 226 32% 25%;
--ring: 354 70% 44%;
--radius: 0.75rem;
--chart-1: 354 70% 44%;
--chart-2: 20 90% 50%;
--chart-3: 200 90% 50%;
--chart-4: 300 90% 50%;
--chart-5: 60 90% 50%;
}
}

View File

@@ -2,6 +2,7 @@ import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "@/app/globals.css";
import SWRLayout from "@/components/layouts/swr-layout";
import { ThemeProvider } from "@/components/ThemeProvider";
const geistSans = Geist({
variable: "--font-geist-sans",
@@ -28,7 +29,14 @@ export default function RootLayout({
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<SWRLayout>{children}</SWRLayout>
<ThemeProvider
attribute="class"
defaultTheme="dark"
enableSystem
disableTransitionOnChange
>
<SWRLayout>{children}</SWRLayout>
</ThemeProvider>
</body>
</html>
);