From b593c60ab2e13f9925cff3403728306d8e94b8c1 Mon Sep 17 00:00:00 2001 From: UltraLionFr Date: Tue, 29 Jul 2025 05:15:33 +0200 Subject: [PATCH] Premier commit de la v2 de l'app --- README.md | 113 +- app/a-propos/page.tsx | 87 + app/ads/page.tsx | 11 + app/api/auth/[...nextauth]/route.ts | 3 + app/couleurs/page.tsx | 23 + app/dashboard/Sidebar.tsx | 63 + app/dashboard/StatCard.tsx | 28 + app/dashboard/actions.ts | 90 + app/dashboard/ajouter/AddProjectForm.tsx | 192 ++ app/dashboard/ajouter/page.tsx | 22 + app/dashboard/layout.tsx | 19 + app/dashboard/page.tsx | 45 + app/dashboard/profil/page.tsx | 77 + app/dashboard/projets/UserProjectsTable.tsx | 106 + .../projets/[id]/EditProjectForm.tsx | 165 ++ app/dashboard/projets/[id]/page.tsx | 41 + app/dashboard/projets/page.tsx | 41 + app/favicon.ico | Bin 25931 -> 0 bytes app/globals.css | 49 +- app/layout.tsx | 38 +- app/login/page.tsx | 14 + app/maps/page.tsx | 9 + app/not-found.tsx | 32 + app/outils/page.tsx | 10 + app/page.module.css | 167 -- app/page.tsx | 120 +- app/providers.tsx | 7 + app/user/[username]/page.tsx | 51 + auth.ts | 23 + components/AdsGalleryClient.tsx | 105 + components/AdsSideBanners.tsx | 50 + components/Banner.tsx | 29 + components/Footer.tsx | 30 + components/MapsClient.tsx | 59 + components/McSplash.tsx | 23 + components/MotionHeading.tsx | 26 + components/Navbar.tsx | 186 ++ components/OutilsPage.tsx | 84 + components/ProjectsTable.tsx | 241 ++ components/RedstoneParticles.tsx | 171 ++ lib/getAds.ts | 10 + lib/prisma.ts | 13 + lib/validators/project.ts | 15 + middleware.ts | 23 + next.config.ts | 24 +- package-lock.json | 2363 ++++++++++++++++- package.json | 19 +- postcss.config.mjs | 6 + .../20250727013821_init/migration.sql | 82 + .../20250727025700_add_mcname/migration.sql | 11 + .../20250727031005_project/migration.sql | 14 + .../migration.sql | 15 + .../migration.sql | 23 + prisma/migrations/migration_lock.toml | 3 + prisma/schema.prisma | 95 + public/ads/riseofkinkdom.jpeg | Bin 0 -> 454654 bytes public/ads/voat.png | Bin 0 -> 1259093 bytes public/aystone-saison2.png | Bin 0 -> 80056 bytes public/aystone.png | Bin 0 -> 12806 bytes public/charte-couleurs.png | Bin 0 -> 329107 bytes public/file.svg | 1 - public/globe.svg | 1 - public/next.svg | 1 - public/vercel.svg | 1 - public/window.svg | 1 - tsconfig.json | 4 +- types/next-auth.d.ts | 16 + 67 files changed, 5003 insertions(+), 388 deletions(-) create mode 100644 app/a-propos/page.tsx create mode 100644 app/ads/page.tsx create mode 100644 app/api/auth/[...nextauth]/route.ts create mode 100644 app/couleurs/page.tsx create mode 100644 app/dashboard/Sidebar.tsx create mode 100644 app/dashboard/StatCard.tsx create mode 100644 app/dashboard/actions.ts create mode 100644 app/dashboard/ajouter/AddProjectForm.tsx create mode 100644 app/dashboard/ajouter/page.tsx create mode 100644 app/dashboard/layout.tsx create mode 100644 app/dashboard/page.tsx create mode 100644 app/dashboard/profil/page.tsx create mode 100644 app/dashboard/projets/UserProjectsTable.tsx create mode 100644 app/dashboard/projets/[id]/EditProjectForm.tsx create mode 100644 app/dashboard/projets/[id]/page.tsx create mode 100644 app/dashboard/projets/page.tsx delete mode 100644 app/favicon.ico create mode 100644 app/login/page.tsx create mode 100644 app/maps/page.tsx create mode 100644 app/not-found.tsx create mode 100644 app/outils/page.tsx delete mode 100644 app/page.module.css create mode 100644 app/providers.tsx create mode 100644 app/user/[username]/page.tsx create mode 100644 auth.ts create mode 100644 components/AdsGalleryClient.tsx create mode 100644 components/AdsSideBanners.tsx create mode 100644 components/Banner.tsx create mode 100644 components/Footer.tsx create mode 100644 components/MapsClient.tsx create mode 100644 components/McSplash.tsx create mode 100644 components/MotionHeading.tsx create mode 100644 components/Navbar.tsx create mode 100644 components/OutilsPage.tsx create mode 100644 components/ProjectsTable.tsx create mode 100644 components/RedstoneParticles.tsx create mode 100644 lib/getAds.ts create mode 100644 lib/prisma.ts create mode 100644 lib/validators/project.ts create mode 100644 middleware.ts create mode 100644 postcss.config.mjs create mode 100644 prisma/migrations/20250727013821_init/migration.sql create mode 100644 prisma/migrations/20250727025700_add_mcname/migration.sql create mode 100644 prisma/migrations/20250727031005_project/migration.sql create mode 100644 prisma/migrations/20250727203115_add_availability_model/migration.sql create mode 100644 prisma/migrations/20250727203953_add_availability_model/migration.sql create mode 100644 prisma/migrations/migration_lock.toml create mode 100644 prisma/schema.prisma create mode 100644 public/ads/riseofkinkdom.jpeg create mode 100644 public/ads/voat.png create mode 100644 public/aystone-saison2.png create mode 100644 public/aystone.png create mode 100644 public/charte-couleurs.png delete mode 100644 public/file.svg delete mode 100644 public/globe.svg delete mode 100644 public/next.svg delete mode 100644 public/vercel.svg delete mode 100644 public/window.svg create mode 100644 types/next-auth.d.ts diff --git a/README.md b/README.md index e215bc4..4828f25 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,83 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). -## Getting Started - -First, run the development server: - -```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev ``` +aystone +├─ app +│ ├─ a-propos +│ │ └─ page.tsx +│ ├─ ads +│ │ └─ page.tsx +│ ├─ api +│ │ ├─ auth +│ │ │ └─ [...nextauth] +│ │ │ └─ route.ts +│ │ └─ planning +│ │ └─ route.ts +│ ├─ dashboard +│ │ ├─ Sidebar.tsx +│ │ ├─ StatCard.tsx +│ │ ├─ actions.ts +│ │ ├─ ajouter +│ │ │ ├─ AddProjectForm.tsx +│ │ │ └─ page.tsx +│ │ ├─ layout.tsx +│ │ ├─ page.tsx +│ │ ├─ profil +│ │ │ └─ page.tsx +│ │ └─ projets +│ │ ├─ [id] +│ │ │ ├─ EditProjectForm.tsx +│ │ │ └─ page.tsx +│ │ └─ page.tsx +│ ├─ globals.css +│ ├─ layout.tsx +│ ├─ login +│ │ └─ page.tsx +│ ├─ maps +│ │ └─ page.tsx +│ ├─ page.tsx +│ └─ providers.tsx +├─ auth.ts +├─ components +│ ├─ AdsGalleryClient.tsx +│ ├─ AdsSideBanners.tsx +│ ├─ AvailabilityPlanner.tsx +│ ├─ Banner.tsx +│ ├─ Footer.tsx +│ ├─ MapsClient.tsx +│ ├─ McSplash.tsx +│ ├─ MotionHeading.tsx +│ ├─ Navbar.tsx +│ ├─ ProjectsTable.tsx +│ └─ RedstoneParticles.tsx +├─ eslint.config.mjs +├─ lib +│ ├─ getAds.ts +│ └─ prisma.ts +├─ middleware.ts +├─ next.config.ts +├─ package-lock.json +├─ package.json +├─ postcss.config.mjs +├─ prisma +│ ├─ migrations +│ │ ├─ 20250727013821_init +│ │ │ └─ migration.sql +│ │ ├─ 20250727025700_add_mcname +│ │ │ └─ migration.sql +│ │ ├─ 20250727031005_project +│ │ │ └─ migration.sql +│ │ ├─ 20250727203115_add_availability_model +│ │ │ └─ migration.sql +│ │ └─ migration_lock.toml +│ └─ schema.prisma +├─ public +│ ├─ ads +│ │ ├─ riseofkinkdom.jpeg +│ │ └─ voat.png +│ ├─ aystone-saison2.png +│ └─ aystone.png +├─ tsconfig.json +└─ types + └─ next-auth.d.ts -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. - -This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. - -## Learn More - -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. +``` \ No newline at end of file diff --git a/app/a-propos/page.tsx b/app/a-propos/page.tsx new file mode 100644 index 0000000..d879b1f --- /dev/null +++ b/app/a-propos/page.tsx @@ -0,0 +1,87 @@ +'use client'; + +import { motion } from 'framer-motion'; + +const TAGS = [ + { id: 'spawn', label: 'Spawn', cls: 'bg-purple-700/30 text-purple-300' }, + { id: 'build', label: 'Build', cls: 'bg-emerald-600/25 text-emerald-300' }, + { id: 'usine', label: 'Usine', cls: 'bg-orange-600/20 text-orange-300' }, +]; + +export default function AProposPage() { + return ( +
+
+ + À propos + + + +

+ Cette application web permet à la communauté du serveur  + Aystone de référencer, suivre + et partager ses projets Minecraft dans les différents mondes : + Overworld, + Nether, + End. +

+ +

+ Le tableau liste : joueur, état, monde, nom, description, + coordonnées et tags personnalisés. L’interface responsive et + épurée garantit une expérience fluide sur mobile comme sur desktop. +

+ +

+ Sous le capot : Next 15, React 19, + Tailwind 4 et Framer Motion pour les + animations. Aucune donnée sensible n’est stockée : on se connecte + simplement via discord . +
+ Sur le dashboard, vous pouvez ajouter votre pseudo Minecraft dans la section Infos du compte +

+ +
+

Légende des tags

+
+ {TAGS.map(t => ( + + {t.label} + + ))} +
+
+ +
+

Code source

+

+ Le projet est entièrement open-source :{" "} + + dépôt GitHub + . + Déployé sur Coolify. +

+
+
+
+
+ ); +} diff --git a/app/ads/page.tsx b/app/ads/page.tsx new file mode 100644 index 0000000..e3e45a5 --- /dev/null +++ b/app/ads/page.tsx @@ -0,0 +1,11 @@ +import AdsGalleryClient from "@/components/AdsGalleryClient"; +import { getAdFiles } from "@/lib/getAds"; + +export const metadata = { + title: "Galerie des publicités – Aystone", +}; + +export default async function AdsGalleryPage() { + const files = await getAdFiles(); + return ; +} \ No newline at end of file diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000..020852e --- /dev/null +++ b/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,3 @@ +import { handlers } from "@/auth"; + +export const { GET, POST } = handlers; \ No newline at end of file diff --git a/app/couleurs/page.tsx b/app/couleurs/page.tsx new file mode 100644 index 0000000..21aed71 --- /dev/null +++ b/app/couleurs/page.tsx @@ -0,0 +1,23 @@ +import Image from "next/image"; + +export const metadata = { + title: "Charte des couleurs Minecraft", +}; +export default function CouleursPage() { + return ( +
+

+ 🎨 Charte des couleurs Minecraft +

+
+ Charte des couleurs Minecraft +
+
+ ); +} \ No newline at end of file diff --git a/app/dashboard/Sidebar.tsx b/app/dashboard/Sidebar.tsx new file mode 100644 index 0000000..bd14a49 --- /dev/null +++ b/app/dashboard/Sidebar.tsx @@ -0,0 +1,63 @@ +"use client"; + +import { + LayoutDashboard, + PlusCircle, + TableProperties, + User +} from "lucide-react"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; + +const LINKS = [ + { name: "Tableau", href: "/dashboard", icon: LayoutDashboard }, + { name: "Profil", href: "/dashboard/profil", icon: User }, + { name: "Ajouter", href: "/dashboard/ajouter", icon: PlusCircle }, + { name: "Mes Projets", href: "/dashboard/projets", icon: TableProperties }, +]; + +export default function Sidebar() { + const pathname = usePathname(); + + return ( + + ); +} \ No newline at end of file diff --git a/app/dashboard/StatCard.tsx b/app/dashboard/StatCard.tsx new file mode 100644 index 0000000..00ed09e --- /dev/null +++ b/app/dashboard/StatCard.tsx @@ -0,0 +1,28 @@ +"use client"; + +import { motion } from "framer-motion"; +import { ReactNode } from "react"; + +type Props = { + title: string; + value: string | number; + icon?: ReactNode; +}; + +export default function StatCard({ title, value, icon }: Props) { + return ( + + {icon && ( +
+ {icon} +
+ )} +

{title}

+

{value}

+
+ ); +} \ No newline at end of file diff --git a/app/dashboard/actions.ts b/app/dashboard/actions.ts new file mode 100644 index 0000000..5ab2590 --- /dev/null +++ b/app/dashboard/actions.ts @@ -0,0 +1,90 @@ +"use server"; + +import { auth } from "@/auth"; +import { prisma } from "@/lib/prisma"; +import { revalidatePath } from "next/cache"; +import { redirect } from "next/navigation"; + +export async function createProject(form: FormData) { + const session = await auth(); + if (!session) throw new Error("unauth"); + + const currentUser = await prisma.user.findUnique({ + where: { email: session.user!.email! }, + }); + + const joueur = + (form.get("joueur") as string | null)?.trim() || + currentUser?.mcName || + session.user!.name!; + + const coords = form.get("coords")?.toString().trim(); + const tagsRaw = form.get("tags")?.toString() ?? ""; + + const data = { + joueur, + etat: (form.get("etat") as string) ?? "En cours", + monde: (form.get("monde") as string) ?? "Overworld", + projet: (form.get("projet") as string).trim(), + description: (form.get("description") as string).trim(), + coords: coords && coords.length > 0 ? coords : null, + tags: tagsRaw + .split(",") + .map((t) => t.trim()) + .filter(Boolean), + }; + + await prisma.project.create({ data }); + revalidatePath("/dashboard"); + redirect("/dashboard"); +} + +export async function updateProject(projectId: string, form: FormData) { + const session = await auth(); + if (!session) throw new Error("unauth"); + + const coords = form.get("coords")?.toString().trim(); + const tagsRaw = form.get("tags")?.toString() ?? ""; + + const data = { + etat: (form.get("etat") as string), + monde: (form.get("monde") as string), + projet: (form.get("projet") as string).trim(), + description: (form.get("description") as string).trim(), + coords: coords && coords.length > 0 ? coords : null, + tags: tagsRaw + .split(",") + .map((t) => t.trim()) + .filter(Boolean), + }; + + await prisma.project.update({ + where: { id: projectId }, + data, + }); + + revalidatePath("/dashboard"); + redirect("/dashboard"); +} + +export async function updateMinecraftName(form: FormData) { + const session = await auth(); + if (!session) throw new Error("unauth"); + + const mcName = (form.get("mcName") as string).trim(); + if (!/^[A-Za-z0-9_]{3,16}$/.test(mcName)) throw new Error("invalid"); + + await prisma.user.upsert({ + where: { email: session.user!.email! }, + update: { mcName }, + create: { + email: session.user!.email!, + name: session.user!.name, + image: session.user!.image, + mcName, + }, + }); + + revalidatePath("/dashboard"); + revalidatePath("/"); +} \ No newline at end of file diff --git a/app/dashboard/ajouter/AddProjectForm.tsx b/app/dashboard/ajouter/AddProjectForm.tsx new file mode 100644 index 0000000..d1e685c --- /dev/null +++ b/app/dashboard/ajouter/AddProjectForm.tsx @@ -0,0 +1,192 @@ +"use client"; + +import { motion } from "framer-motion"; +import { Layers3, MapPin, Plus, Tags, User, X } from "lucide-react"; +import { useSession } from "next-auth/react"; +import { useState } from "react"; +import { createProject } from "../actions"; + +const PREDEF = ["build", "usine", "spawn"] as const; +type TagColor = (typeof PREDEF)[number]; + +const palette: Record = { + build: "bg-emerald-600/90", + usine: "bg-orange-600/90", + spawn: "bg-fuchsia-700/90", +}; + +const isValidCoord = (value: string) => /^-?\d*$/.test(value); + +export default function AddProjectForm() { + const { data: session } = useSession(); + const [tags, setTags] = useState([]); + const [tagInput, setInput] = useState(""); + + const [x, setX] = useState(""); + const [y, setY] = useState(""); + const [z, setZ] = useState(""); + + const addTag = (t: string) => { + const val = t.trim().toLowerCase(); + if (!val || tags.includes(val) || tags.length >= 5) return; + setTags([...tags, val]); + }; + + const del = (t: string) => setTags(tags.filter((x) => x !== t)); + + const getTagClass = (t: string) => { + return palette[t as TagColor] ?? "bg-gray-600"; + }; + + return ( + + {/* nom */} +
+ + +
+ + {/* joueur */} + {session?.user?.email === "ultralionfr@gmail.com" && ( +
+ + +
+ )} + + {/* coords */} +
+ {[ + { name: "x", value: x, setValue: setX }, + { name: "y", value: y, setValue: setY }, + { name: "z", value: z, setValue: setZ }, + ].map(({ name, value, setValue }) => ( +
+ + { + if (isValidCoord(e.target.value)) setValue(e.target.value); + }} + className="w-full rounded-lg bg-gray-700 py-2 pl-9 pr-3 text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-indigo-500" + /> +
+ ))} +
+ + {/* Tags */} +
+ + +
+ {tags.map((t) => ( + + ))} +
+ +
+ + +
+ setInput(e.target.value)} + onKeyDown={(e) => + e.key === "Enter" && (e.preventDefault(), addTag(tagInput), setInput("")) + } + placeholder="Ajouter un tag personnalisé" + className="w-full rounded-lg bg-gray-700 py-2 pl-3 pr-12 text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-indigo-500" + /> + +
+
+
+ + {/* description */} +