From 5a5846d853e6b482aab02aaa7ee3e08c1855ebaf Mon Sep 17 00:00:00 2001
From: cdricms <36056008+cdricms@users.noreply.github.com>
Date: Wed, 22 Jan 2025 17:39:03 +0100
Subject: [PATCH] Added CSRF & YouTube and dark mode
---
.env.template | 7 +
backend/api/contact.go | 61 ++++
backend/api/core/csrf.go | 3 +
backend/api/get_csrf.go | 19 ++
backend/go.mod | 7 +
backend/go.sum | 8 +
backend/main.go | 30 +-
docker-compose.yaml | 2 +-
frontend/app/(main)/blogs/[slug]/page.tsx | 20 +-
frontend/app/(main)/page.tsx | 55 +++-
frontend/app/(main)/planning/page.tsx | 199 ++++++++++++
frontend/app/globals.css | 96 +++---
frontend/app/layout.tsx | 10 +-
frontend/components/ThemeProvider.tsx | 11 +
frontend/components/contact.tsx | 93 +++++-
frontend/components/gallery.tsx | 96 +++---
frontend/components/hero.tsx | 6 +-
frontend/components/login-form.tsx | 4 +-
frontend/components/nav-bar.tsx | 4 +-
frontend/components/ui/calendar.tsx | 82 +++++
frontend/components/ui/dialog.tsx | 122 ++++++++
frontend/components/ui/popover.tsx | 33 ++
frontend/components/ui/switch.tsx | 46 +--
frontend/hooks/use-api.tsx | 23 +-
frontend/hooks/use-login.tsx | 12 +-
frontend/interfaces/youtube.ts | 48 +++
frontend/next.config.ts | 4 -
frontend/package-lock.json | 356 +++++++++++++++-------
frontend/package.json | 9 +
29 files changed, 1186 insertions(+), 280 deletions(-)
create mode 100644 backend/api/contact.go
create mode 100644 backend/api/core/csrf.go
create mode 100644 backend/api/get_csrf.go
create mode 100644 frontend/app/(main)/planning/page.tsx
create mode 100644 frontend/components/ThemeProvider.tsx
create mode 100644 frontend/components/ui/calendar.tsx
create mode 100644 frontend/components/ui/dialog.tsx
create mode 100644 frontend/components/ui/popover.tsx
create mode 100644 frontend/interfaces/youtube.ts
diff --git a/.env.template b/.env.template
index ae23f5c..c44c796 100644
--- a/.env.template
+++ b/.env.template
@@ -15,3 +15,10 @@ FRONTEND_HOSTNAME=${FRONTEND_HOSTNAME:-latosa-frontend}
BACKEND_HOSTNAME=${BACKEND_HOSTNAME:-latosa-backend}
DATABASE_HOSTNAME=${DATABASE_HOSTNAME:-latosa-database}
SERVER_NAME=${SERVER_NAME:-localhost}
+CORS_AllowOrigin=${CORS_AllowOrigin:-*}
+
+SMTP_DOMAIN=${SMTP_DOMAIN:-smtp.gmail.com}
+SMTP_PORT=${SMTP_PORT:-587}
+SMTP_EMAIL=${SMTP_EMAIL}
+SMTP_APP_PASSWORD=${SMTP_APP_PASSWORD}
+YOUTUBE_API_KEY=${YOUTUBE_API_KEY}
diff --git a/backend/api/contact.go b/backend/api/contact.go
new file mode 100644
index 0000000..8d4333a
--- /dev/null
+++ b/backend/api/contact.go
@@ -0,0 +1,61 @@
+package api
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "os"
+ "strconv"
+
+ "fr.latosa-escrima/api/core"
+ "gopkg.in/gomail.v2"
+)
+
+type ContactForm struct {
+ Firstname string `json:"firstname"`
+ Lastname string `json:"lastname"`
+ EMail string `json:"email"`
+ Subject string `json:"subject"`
+ Message string `json:"message"`
+}
+
+func HandleContact(w http.ResponseWriter, r *http.Request) {
+ // TODO: Warning email not being sent ?
+ var form ContactForm
+ err := json.NewDecoder(r.Body).Decode(&form)
+ if err != nil {
+ core.JSONError{
+ Status: core.Error,
+ Message: err.Error(),
+ }.Respond(w, http.StatusBadRequest)
+ return
+ }
+
+ fmt.Println("Received form", form)
+ fmt.Println("ENV:", os.Environ())
+
+ m := gomail.NewMessage()
+ m.SetHeader("From", os.Getenv("SMTP_EMAIL"))
+ // m.SetHeader("Reply-To", form.EMail)
+ m.SetHeader("To", os.Getenv("SMTP_EMAIL"))
+ m.SetHeader("Subject", form.Subject)
+ m.SetBody("text/plain", fmt.Sprintf("%s %s vous a envoyé un email:\n\n%s", form.Firstname, form.Lastname, form.Message))
+ port, err := strconv.Atoi(os.Getenv("SMTP_PORT"))
+ if err != nil {
+ port = 587
+ }
+ d := gomail.NewDialer(os.Getenv("SMTP_DOMAIN"), port, os.Getenv("SMTP_EMAIL"), os.Getenv("SMTP_APP_PASSWORD"))
+
+ if err = d.DialAndSend(); err != nil {
+ core.JSONError{
+ Status: core.Error,
+ Message: err.Error(),
+ }.Respond(w, http.StatusInternalServerError)
+ return
+ }
+
+ core.JSONSuccess{
+ Status: core.Success,
+ Message: "Email sent.",
+ }.Respond(w, http.StatusAccepted)
+}
diff --git a/backend/api/core/csrf.go b/backend/api/core/csrf.go
new file mode 100644
index 0000000..602bd76
--- /dev/null
+++ b/backend/api/core/csrf.go
@@ -0,0 +1,3 @@
+package core
+
+var CSRF_KEY = []byte("32-byte-long-auth-key")
diff --git a/backend/api/get_csrf.go b/backend/api/get_csrf.go
new file mode 100644
index 0000000..6f27b6d
--- /dev/null
+++ b/backend/api/get_csrf.go
@@ -0,0 +1,19 @@
+package api
+
+import (
+ "fmt"
+ "net/http"
+
+ "fr.latosa-escrima/api/core"
+ "github.com/gorilla/csrf"
+)
+
+func HandleCSRF(w http.ResponseWriter, r *http.Request) {
+ token := csrf.Token(r)
+ fmt.Println(token)
+ core.JSONSuccess{
+ Status: core.Success,
+ Message: "CSRF generated.",
+ Data: map[string]string{"csrf": token},
+ }.Respond(w, http.StatusOK)
+}
diff --git a/backend/go.mod b/backend/go.mod
index 0720b0d..51ec100 100644
--- a/backend/go.mod
+++ b/backend/go.mod
@@ -12,6 +12,12 @@ require (
github.com/uptrace/bun/driver/pgdriver v1.2.8
)
+require (
+ github.com/gorilla/csrf v1.7.2 // direct
+ github.com/gorilla/securecookie v1.1.2 // indirect
+ gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
+)
+
require (
github.com/fatih/color v1.18.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
@@ -24,5 +30,6 @@ require (
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/sys v0.29.0 // indirect
+ gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
mellium.im/sasl v0.3.2 // indirect
)
diff --git a/backend/go.sum b/backend/go.sum
index b23099f..50e9fe5 100644
--- a/backend/go.sum
+++ b/backend/go.sum
@@ -8,6 +8,10 @@ github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17w
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI=
+github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk=
+github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
+github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
@@ -45,6 +49,10 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
+gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
+gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
+gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
mellium.im/sasl v0.3.2 h1:PT6Xp7ccn9XaXAnJ03FcEjmAn7kK1x7aoXV6F+Vmrl0=
diff --git a/backend/main.go b/backend/main.go
index 615135c..01a42c5 100644
--- a/backend/main.go
+++ b/backend/main.go
@@ -2,29 +2,34 @@ package main
import (
"fmt"
- "github.com/joho/godotenv"
- "github.com/uptrace/bun/extra/bundebug"
"log"
"net/http"
"os"
+ "github.com/joho/godotenv"
+ "github.com/uptrace/bun/extra/bundebug"
+
_ "github.com/lib/pq"
- api "fr.latosa-escrima/api"
+ "fr.latosa-escrima/api"
"fr.latosa-escrima/api/core"
+ "github.com/gorilla/csrf"
)
+var CORS_AllowOrigin string
+
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "
Hello, World! ")
}
func Cors(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Allow all origins (can restrict to specific origins)
- w.Header().Set("Access-Control-Allow-Origin", "*")
+ w.Header().Set("Access-Control-Allow-Origin", CORS_AllowOrigin)
// Allow certain HTTP methods (you can customize these as needed)
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, PATCH")
// Allow certain headers (you can add more as needed)
- w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
+ w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-CSRF-Token")
+ w.Header().Set("Access-Control-Allow-Credentials", "true")
// Handle OPTIONS pre-flight request
if r.Method == http.MethodOptions {
return
@@ -43,6 +48,7 @@ func main() {
port := os.Getenv("BACKEND_DOCKER_PORT")
hostname := os.Getenv("DATABASE_HOSTNAME")
postgres_port := os.Getenv("POSTGRES_DOCKER_PORT")
+ CORS_AllowOrigin = os.Getenv("CORS_AllowOrigin")
if environ == "DEV" {
port = os.Getenv("BACKEND_PORT")
hostname = "localhost"
@@ -66,6 +72,12 @@ func main() {
defer core.DB.Close()
+ CSRFMiddleware := csrf.Protect(
+ core.CSRF_KEY,
+ csrf.Secure(environ != "DEV"),
+ csrf.HttpOnly(true),
+ )
+
mux := http.NewServeMux()
core.HandleRoutes(mux, map[string]core.Handler{
@@ -108,6 +120,14 @@ func main() {
Handler: api.HandleVerifyMedia,
Middlewares: []core.Middleware{api.Methods("POST"), api.AuthJWT},
},
+ "/contact": {
+ Handler: api.HandleContact,
+ Middlewares: []core.Middleware{api.Methods("POST"), CSRFMiddleware},
+ },
+ "/csrf-token": {
+ Handler: api.HandleCSRF,
+ Middlewares: []core.Middleware{api.Methods("GET"), CSRFMiddleware},
+ },
})
fmt.Printf("Serving on port %s\n", port)
diff --git a/docker-compose.yaml b/docker-compose.yaml
index cbfda19..f8839af 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -19,7 +19,7 @@ services:
depends_on:
- psql
volumes:
- - ./backend/media:/media
+ - ./backend/media:/app/media
build:
context: ./backend/
dockerfile: Dockerfile
diff --git a/frontend/app/(main)/blogs/[slug]/page.tsx b/frontend/app/(main)/blogs/[slug]/page.tsx
index 2cac801..85c3a0a 100644
--- a/frontend/app/(main)/blogs/[slug]/page.tsx
+++ b/frontend/app/(main)/blogs/[slug]/page.tsx
@@ -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 = {
diff --git a/frontend/app/(main)/page.tsx b/frontend/app/(main)/page.tsx
index 5070134..18f0266 100644
--- a/frontend/app/(main)/page.tsx
+++ b/frontend/app/(main)/page.tsx
@@ -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 (
@@ -20,10 +28,10 @@ export default async function Home() {
position="left"
image="https://shadcnblocks.com/images/block/placeholder-2.svg"
>
-
+
Un Système Centré sur les Concepts{" "}
-
+
Étude et application des meilleurs
concepts et stratégies issus de
@@ -37,7 +45,7 @@ export default async function Home() {
Éducation au Mouvement et à l’Efficacité
-
+
Plus qu’un 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"
>
-
+
Les Premières Étapes
-
+
Initialement centré sur les techniques
et mouvements, le système s’est montré
@@ -78,10 +86,10 @@ export default async function Home() {
La Découverte des Concepts Clés
{" "}
-
+
Rôle central des concepts de combat :
-
+
Puissance dans les frappes.
Blocage ferme.
Équilibre et attitude.
@@ -103,7 +111,7 @@ export default async function Home() {
>
Latosa Escrima Concepts repose sur cinq concepts
fondamentaux :
-
+
Équilibre
Vitesse (Timing et Distance)
Puissance
@@ -112,8 +120,35 @@ export default async function Home() {
-
-
+
+ {videos && (
+
+ {videos.items.map((video) => {
+ return (
+
+ VIDEO
+
+ );
+ })}
+
+ )}
diff --git a/frontend/app/(main)/planning/page.tsx b/frontend/app/(main)/planning/page.tsx
new file mode 100644
index 0000000..4079025
--- /dev/null
+++ b/frontend/app/(main)/planning/page.tsx
@@ -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(null);
+ const [events, setEvents] = useState([
+ {
+ 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 (
+
+
+
+
+
{
+ setEventSelected((e) => (open ? e : null));
+ }}
+ >
+
+
+ {eventSelected?.title}
+
+ {eventSelected?.description}
+
+
+
+
+
+
+ Début
+
+ {/*
+
+
+ {eventSelected?.start ? (
+ format(eventSelected?.start, "PPP")
+ ) : (
+ Choisissez une date.
+ )}
+
+
+
+
+
+ date > new Date() ||
+ date < new Date("1900-01-01")
+ }
+ initialFocus
+ />
+
+ */}
+
{
+ const val = e.currentTarget.value;
+ console.log(val);
+ setEventSelected((ev) => {
+ if (ev)
+ return {
+ ...ev,
+ start: val,
+ };
+ return ev;
+ });
+ }}
+ className="col-span-3"
+ />
+
+
+
+ Fin
+
+
+ setEventSelected((ev) => {
+ if (ev)
+ return {
+ ...ev,
+ end: e.currentTarget.value,
+ };
+ return ev;
+ })
+ }
+ className="col-span-3"
+ />
+
+
+
+ {
+ setEvents((evs) => {
+ evs = evs.filter(
+ (e) => e.id !== eventSelected?.id,
+ );
+ evs.push(eventSelected!);
+ return evs;
+ });
+ }}
+ type="submit"
+ >
+ Mettre à jour
+
+
+
+
+
+ );
+};
+
+export default Planning;
diff --git a/frontend/app/globals.css b/frontend/app/globals.css
index 296a425..f4f4112 100644
--- a/frontend/app/globals.css
+++ b/frontend/app/globals.css
@@ -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%;
}
}
diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx
index d6ef1f6..a89e441 100644
--- a/frontend/app/layout.tsx
+++ b/frontend/app/layout.tsx
@@ -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({
- {children}
+
+ {children}
+