From a69ca86330df75e2b0b1d9eb16d32cc67a2e598d Mon Sep 17 00:00:00 2001 From: Wycz Date: Tue, 8 Sep 2026 08:25:33 +0200 Subject: [PATCH] Initial commit: JuanitoSport - Muay Thai & Yog4Fight PWA --- .env.example | 4 + .gitignore | 13 + app/admin/admin-layout.tsx | 61 +++ app/admin/certificates/certificates-admin.tsx | 112 +++++ app/admin/certificates/page.tsx | 13 + app/admin/homework/homework-admin.tsx | 137 ++++++ app/admin/homework/page.tsx | 18 + app/admin/layout.tsx | 16 + app/admin/media/media-admin.tsx | 175 ++++++++ app/admin/media/page.tsx | 13 + app/admin/messages/messages-admin.tsx | 98 +++++ app/admin/messages/page.tsx | 20 + app/admin/overview.tsx | 83 ++++ app/admin/page.tsx | 40 ++ app/admin/users/page.tsx | 17 + app/admin/users/users-admin.tsx | 260 ++++++++++++ app/admin/yoga/page.tsx | 34 ++ app/admin/yoga/yoga-admin.tsx | 241 +++++++++++ app/api/absences/route.ts | 39 ++ app/api/admin/messages/route.ts | 41 ++ app/api/admin/users/[id]/route.ts | 64 +++ app/api/admin/users/route.ts | 62 +++ app/api/admin/yoga-members/route.ts | 33 ++ app/api/admin/yoga-tiers/[id]/route.ts | 41 ++ app/api/admin/yoga-tiers/route.ts | 34 ++ app/api/auth/[...nextauth]/route.ts | 6 + app/api/certificates/route.ts | 69 +++ app/api/homework/route.ts | 62 +++ app/api/me/route.ts | 29 ++ app/api/media/route.ts | 72 ++++ app/api/messages/route.ts | 55 +++ app/api/training/route.ts | 61 +++ app/api/upload/route.ts | 47 +++ app/api/yoga-sessions/route.ts | 65 +++ app/error.tsx | 20 + app/globals.css | 34 ++ app/layout.tsx | 41 ++ app/login/page.tsx | 109 +++++ app/member/muay-thai/absence/absence-form.tsx | 80 ++++ app/member/muay-thai/absence/page.tsx | 10 + .../certificate/certificate-upload.tsx | 156 +++++++ app/member/muay-thai/certificate/page.tsx | 17 + app/member/muay-thai/dashboard.tsx | 399 ++++++++++++++++++ app/member/muay-thai/page.tsx | 46 ++ app/member/page.tsx | 22 + app/member/yoga/dashboard.tsx | 164 +++++++ app/member/yoga/page.tsx | 43 ++ app/muay-thai/page.tsx | 125 ++++++ app/not-found.tsx | 20 + app/page.tsx | 83 ++++ app/yog4fight/page.tsx | 126 ++++++ components/Header.tsx | 46 ++ components/Providers.tsx | 7 + lib/auth.ts | 78 ++++ lib/prisma.ts | 9 + lib/session.ts | 23 + lib/upload.ts | 25 ++ next-env.d.ts | 5 + next.config.mjs | 27 ++ package.json | 37 ++ postcss.config.mjs | 6 + prisma/schema.prisma | 165 ++++++++ prisma/seed.ts | 56 +++ public/images/logo-muaythai.jpg | Bin 0 -> 32265 bytes public/images/logo-yoga.png | Bin 0 -> 313877 bytes public/manifest.json | 48 +++ scripts/generate-icons.mjs | 46 ++ tailwind.config.ts | 37 ++ tsconfig.json | 26 ++ types/next-auth.d.ts | 30 ++ 70 files changed, 4301 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 app/admin/admin-layout.tsx create mode 100644 app/admin/certificates/certificates-admin.tsx create mode 100644 app/admin/certificates/page.tsx create mode 100644 app/admin/homework/homework-admin.tsx create mode 100644 app/admin/homework/page.tsx create mode 100644 app/admin/layout.tsx create mode 100644 app/admin/media/media-admin.tsx create mode 100644 app/admin/media/page.tsx create mode 100644 app/admin/messages/messages-admin.tsx create mode 100644 app/admin/messages/page.tsx create mode 100644 app/admin/overview.tsx create mode 100644 app/admin/page.tsx create mode 100644 app/admin/users/page.tsx create mode 100644 app/admin/users/users-admin.tsx create mode 100644 app/admin/yoga/page.tsx create mode 100644 app/admin/yoga/yoga-admin.tsx create mode 100644 app/api/absences/route.ts create mode 100644 app/api/admin/messages/route.ts create mode 100644 app/api/admin/users/[id]/route.ts create mode 100644 app/api/admin/users/route.ts create mode 100644 app/api/admin/yoga-members/route.ts create mode 100644 app/api/admin/yoga-tiers/[id]/route.ts create mode 100644 app/api/admin/yoga-tiers/route.ts create mode 100644 app/api/auth/[...nextauth]/route.ts create mode 100644 app/api/certificates/route.ts create mode 100644 app/api/homework/route.ts create mode 100644 app/api/me/route.ts create mode 100644 app/api/media/route.ts create mode 100644 app/api/messages/route.ts create mode 100644 app/api/training/route.ts create mode 100644 app/api/upload/route.ts create mode 100644 app/api/yoga-sessions/route.ts create mode 100644 app/error.tsx create mode 100644 app/globals.css create mode 100644 app/layout.tsx create mode 100644 app/login/page.tsx create mode 100644 app/member/muay-thai/absence/absence-form.tsx create mode 100644 app/member/muay-thai/absence/page.tsx create mode 100644 app/member/muay-thai/certificate/certificate-upload.tsx create mode 100644 app/member/muay-thai/certificate/page.tsx create mode 100644 app/member/muay-thai/dashboard.tsx create mode 100644 app/member/muay-thai/page.tsx create mode 100644 app/member/page.tsx create mode 100644 app/member/yoga/dashboard.tsx create mode 100644 app/member/yoga/page.tsx create mode 100644 app/muay-thai/page.tsx create mode 100644 app/not-found.tsx create mode 100644 app/page.tsx create mode 100644 app/yog4fight/page.tsx create mode 100644 components/Header.tsx create mode 100644 components/Providers.tsx create mode 100644 lib/auth.ts create mode 100644 lib/prisma.ts create mode 100644 lib/session.ts create mode 100644 lib/upload.ts create mode 100644 next-env.d.ts create mode 100644 next.config.mjs create mode 100644 package.json create mode 100644 postcss.config.mjs create mode 100644 prisma/schema.prisma create mode 100644 prisma/seed.ts create mode 100644 public/images/logo-muaythai.jpg create mode 100644 public/images/logo-yoga.png create mode 100644 public/manifest.json create mode 100644 scripts/generate-icons.mjs create mode 100644 tailwind.config.ts create mode 100644 tsconfig.json create mode 100644 types/next-auth.d.ts diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..8b3dcfc --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +DATABASE_URL="postgresql://juanito_user:JuanitoSport2026!@10.10.10.10:5432/juanitosport?schema=public" +NEXTAUTH_SECRET="CHANGE_ME_PLEASE" +NEXTAUTH_URL="https://juanito.secourit.fr" +UPLOAD_DIR="/opt/juanitosport/uploads" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..343dd9b --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +node_modules/ +.next/ +out/ +build/ +.env +.env.local +.env.production +*.log +.DS_Store +public/sw/ +public/workbox-*.js +public/worker-*.js +public/ffmpeg/ diff --git a/app/admin/admin-layout.tsx b/app/admin/admin-layout.tsx new file mode 100644 index 0000000..369c07d --- /dev/null +++ b/app/admin/admin-layout.tsx @@ -0,0 +1,61 @@ +"use client"; + +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import Header from "@/components/Header"; + +const adminLinks = [ + { href: "/admin", label: "Vue d'ensemble", icon: "🏠" }, + { href: "/admin/users", label: "Membres", icon: "👥" }, + { href: "/admin/messages", label: "Messages", icon: "💬" }, + { href: "/admin/media", label: "Photos & vidéos", icon: "🎬" }, + { href: "/admin/homework", label: "Exercices", icon: "🏋️" }, + { href: "/admin/certificates", label: "Certificats", icon: "📄" }, + { href: "/admin/yoga", label: "Gestion Yoga", icon: "🧘" }, +]; + +export default function AdminLayout({ + children, +}: { + children: React.ReactNode; +}) { + const pathname = usePathname(); + + return ( +
+
+
+

+ ⚙️ Tableau de bord Juanito +

+

+ Gère ton club JuanitoSport +

+ + + + {children} +
+
+ ); +} \ No newline at end of file diff --git a/app/admin/certificates/certificates-admin.tsx b/app/admin/certificates/certificates-admin.tsx new file mode 100644 index 0000000..d3c87fe --- /dev/null +++ b/app/admin/certificates/certificates-admin.tsx @@ -0,0 +1,112 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; + +export default function AdminCertificates({ certificates }: any) { + const router = useRouter(); + const [filter, setFilter] = useState("PENDING"); + const [viewing, setViewing] = useState(null); + + const filtered = certificates.filter((c: any) => + filter === "ALL" ? true : c.status === filter + ); + + async function updateStatus(id: string, status: string) { + const res = await fetch("/api/certificates", { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ id, status }), + }); + if (res.ok) { + setViewing(null); + router.refresh(); + } + } + + const pendingCount = certificates.filter((c: any) => c.status === "PENDING").length; + + return ( +
+

+ 📄 Certificats médical + {pendingCount > 0 && ( + + {pendingCount} en attente + + )} +

+ +
+ {(["PENDING", "VALIDATED", "REJECTED", "ALL"] as const).map((f) => ( + + ))} +
+ +
+ {filtered.length === 0 && ( +

Aucun certificat ici.

+ )} + {filtered.map((c: any) => ( +
+
+
+

{c.user.name}

+

{c.user.email}

+

+ Reçu le {new Date(c.createdAt).toLocaleDateString("fr-FR")} +

+
+ +
+
+ + 👁️ Voir le fichier + + {c.status === "PENDING" && ( + <> + + + + )} +
+
+ ))} +
+
+ ); +} + +function StatusBadge({ status }: { status: string }) { + switch (status) { + case "VALIDATED": + return ✓ Validé; + case "REJECTED": + return ✗ Rejeté; + default: + return ⏳ En attente; + } +} \ No newline at end of file diff --git a/app/admin/certificates/page.tsx b/app/admin/certificates/page.tsx new file mode 100644 index 0000000..8752cc0 --- /dev/null +++ b/app/admin/certificates/page.tsx @@ -0,0 +1,13 @@ +import { prisma } from "@/lib/prisma"; +import AdminCertificates from "./certificates-admin"; + +export default async function CertificatesPage() { + const certificates = await prisma.certificate.findMany({ + orderBy: { createdAt: "desc" }, + include: { + user: { select: { id: true, name: true, email: true } }, + }, + }); + + return ; +} \ No newline at end of file diff --git a/app/admin/homework/homework-admin.tsx b/app/admin/homework/homework-admin.tsx new file mode 100644 index 0000000..6f4413b --- /dev/null +++ b/app/admin/homework/homework-admin.tsx @@ -0,0 +1,137 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; + +export default function AdminHomework({ homework, members }: any) { + const router = useRouter(); + const [title, setTitle] = useState(""); + const [description, setDescription] = useState(""); + const [videoFile, setVideoFile] = useState(null); + const [videoUrl, setVideoUrl] = useState(""); + const [target, setTarget] = useState("all"); + const [saving, setSaving] = useState(false); + const [error, setError] = useState(""); + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + if (!title || !description) return; + setSaving(true); + setError(""); + + try { + let finalVideoUrl = videoUrl; + if (videoFile) { + const formData = new FormData(); + formData.append("file", videoFile); + formData.append("subdir", "homework"); + const up = await fetch("/api/upload", { method: "POST", body: formData }); + if (!up.ok) throw new Error("Upload vidéo échoué"); + const { url } = await up.json(); + finalVideoUrl = url; + } + + const res = await fetch("/api/homework", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + title, + description, + videoUrl: finalVideoUrl, + userId: target === "all" ? null : target, + }), + }); + if (!res.ok) throw new Error("Enregistrement échoué"); + + setTitle(""); + setDescription(""); + setVideoFile(null); + setVideoUrl(""); + setTarget("all"); + router.refresh(); + } catch (err: any) { + setError(err.message || "Erreur"); + } finally { + setSaving(false); + } + } + + async function deleteHomework(id: string) { + if (!confirm("Supprimer cet exercice ?")) return; + await fetch("/api/homework", { + method: "DELETE", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ id }), + }); + router.refresh(); + } + + return ( +
+

🏋️ Exercices à travailler

+ +
+

📝 Nouvel exercice

+
+ + setTitle(e.target.value)} placeholder="Ex: 10 rounds de shadow box" required /> +
+
+ +