Commit pour save le repo suite au transfert

This commit is contained in:
Fred 2025-02-14 17:27:54 +01:00
parent 1933879194
commit a0ba1720d1
42 changed files with 3012 additions and 134 deletions

1235
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
{
"name": "testauthclerk",
"name": "airsoftsearch",
"version": "0.1.0",
"private": true,
"scripts": {
@ -9,19 +9,35 @@
"lint": "next lint"
},
"dependencies": {
"@clerk/nextjs": "^6.9.11",
"@clerk/themes": "^2.2.7",
"@fortawesome/free-solid-svg-icons": "^6.7.2",
"@fortawesome/react-fontawesome": "^0.2.2",
"@headlessui/react": "^2.2.0",
"@heroicons/react": "^2.2.0",
"@material-tailwind/react": "^2.1.10",
"@prisma/client": "^6.2.1",
"bootstrap": "^5.3.3",
"leaflet": "^1.9.4",
"next": "15.1.4",
"react": "^19.0.0",
"react-bootstrap": "^2.10.7",
"react-dom": "^19.0.0",
"next": "15.1.4"
"react-fontawesome": "^1.7.1",
"react-leaflet": "^5.0.0",
"slugify": "^1.6.6"
},
"devDependencies": {
"typescript": "^5",
"@eslint/eslintrc": "^3",
"@types/leaflet": "^1.9.16",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"eslint": "^9",
"eslint-config-next": "15.1.4",
"@eslint/eslintrc": "^3"
"postcss": "^8",
"prisma": "^6.2.1",
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
}

63
prisma/old_schema.prisma Normal file
View File

@ -0,0 +1,63 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Organisation {
id String @id @default(cuid())
name String @default("")
tag String @default("")
type String @default("")
logo String @default("")
slug String @default("")
}
model Membre {
id String @id @default(cuid())
userId String @default("")
organisationid String @default("")
role String @default("")
slug String @default("")
}
model User {
id String @id @default(cuid())
userId String @default("")
userName String @default("")
categorie String @default("")
role String @default("")
slug String @default("")
}
model GameSession{
id String @id @default(cuid())
organisationId String @default("")
title String @default("")
date DateTime
startTime DateTime @db.Time
endTime DateTime @db.Time
paf Int @default(0)
maxPlayer Int @default(0)
slug String @default("")
}
model GameSessionTag{
id String @id @default(cuid())
gameSessionId String @default("")
tagId String @default("")
slug String @default("")
}
model Tag{
id String @id @default(cuid())
title String @default("")
slug String @default("")
}

58
prisma/schema.prisma Normal file
View File

@ -0,0 +1,58 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Organisation {
id String @id @default(uuid())
name String @unique
description String?
logo String?
type String?
tag String?
slug String?
members UserOnOrganisation[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model User {
id String @id @default(uuid())
userId String
email String
name String?
slug String?
organisations UserOnOrganisation[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Role {
id String @id @default(uuid())
name String @unique
usersOnOrganisations UserOnOrganisation[]
}
model UserOnOrganisation {
userId String
organisationId String
roleId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
organisation Organisation @relation(fields: [organisationId], references: [id], onDelete: Cascade)
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
@@id([userId, organisationId, roleId])
}

BIN
public/assets/LogoEAN.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 KiB

BIN
public/assets/image0_0.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
public/assets/logoAS.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
public/assets/logoTaff.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

View File

View File

@ -0,0 +1,76 @@
'use server'
import prisma from "@/lib/db";
import { currentUser } from "@clerk/nextjs/server";
import { getOrganisation } from "./organisationActions";
//import { getUserNameWithId } from "./usersActions";
export async function createMember(id : string){
try{
const user = await currentUser()
if(!user?.id){
throw new Error("Error with user")
}
await prisma.membre.create({
data :{
userId : user?.id,
organisationid : id,
role: 'ADMIN'
}
})
return {success : true}
}catch(error){
return{success : false, error : error instanceof Error ? error.message : 'An unknown error occured'}
}
}
export async function getMember(id : string){
try{
const user = await currentUser()
if(!user?.id){
throw new Error("Error with user")
}
const organisation = await getOrganisation(id)
const result = await prisma.membre.findFirst(
{
where :{
userId : user?.id,
organisationid: organisation.Organisation?.id
},
select:{
role:true
}
})
return {success : true, role: result?.role }
}catch(error){
return{success : false, error : error instanceof Error ? error.message : 'An unknown error occured'}
}
}
export async function getMembers(id: string){
try{
const user = await currentUser()
const organisation = await getOrganisation(id)
if(!user?.id){
throw new Error("Error with user")
}
const result = await prisma.membre.findMany(
{
where :{
userId : user?.id,
organisationid: organisation.Organisation?.id
}
})
const updatedResult = await Promise.all(result.map(async (user) => {
const userName = await getUserNameWithId(user.userId); // Récupère le userName
return { ...user, userName }; // Ajoute le userName à l'élément user
}));
return {success : true, listMembre: updatedResult }
}catch(error){
return{success : false, error : error instanceof Error ? error.message : 'An unknown error occured'}
}
}

View File

@ -0,0 +1,285 @@
'use server'
import prisma from "@/lib/db";
import { currentUser } from "@clerk/nextjs/server";
//import { createMember } from "./memberActions";
import slugify from "slugify";
import {uploadImage,deleteImage} from '@/lib/uploadFile'
export async function updateOrganisation(
nameOrganisation : string,
idOrganisation : string,
logoOrganisation : string,
typeOrganisation : string,
tagOrganisation : string){
try{
const slugOrganisation = slugify(nameOrganisation)
await prisma.organisation.upsert({
where: { id: idOrganisation},
update: {name : nameOrganisation, logo : logoOrganisation, type : typeOrganisation,tag : tagOrganisation, slug: slugOrganisation },
create: {name : nameOrganisation, logo : logoOrganisation, type : typeOrganisation,tag : tagOrganisation, slug: slugOrganisation }
})
return {success : true}
}catch(error){
return{success : false, error : error instanceof Error ? error.message : 'An unknown error occured'}
}
}
/*export async function createOrganisation(nameOrganisation : string,logoOrganisation : string, typeOrganisation : string,tagOrganisation: string){
try{
let slugOrganisation = ""
if(slugOrganisation){
let slugCandidate = slugify(nameOrganisation,{lower:true,strict:true})
let slugExist = await getOrganisation(slugCandidate)
let counter = 1
while(slugExist){
slugCandidate=`${slugCandidate}-${counter}`
slugExist = await getOrganisation(slugCandidate)
counter++
}
slugOrganisation = slugCandidate
}
const user = await currentUser()
if(!user?.id){
throw new Error("Error with user")
}
if(!logoOrganisation){
logoOrganisation = '/assets/image0_0.jpg'
}
const result = await prisma.organisation.create({
data : {
name : nameOrganisation,
logo : logoOrganisation,
type : typeOrganisation,
tag : tagOrganisation,
slug : slugOrganisation
}
})
await createMember(result.id)
return {success : true}
}catch(error){
return{success : false, error : error instanceof Error ? error.message : 'An unknown error occured'}
}
}
export async function getOrganisations(){
try{
const user = await currentUser()
if(!user){
throw new Error ("Error with credential")
}
const listOrganisation = await prisma.membre.findMany(
{where : {
userId:user.id
},
select:{
organisationid: true
}})
const listTemp: ({ id: string; name: string; tag: string; type: string; logo: string; } | null)[] = []
const organisationPromise = listOrganisation.map(async organisation => {
const temp = await prisma.organisation.findUnique({
where : {
id: organisation.organisationid
}
})
return temp
})
const result = await Promise.all(organisationPromise)
listTemp.push(...result)
return {success : true, listOrganisation : listTemp}
}catch(error){
return{success : false, error : error instanceof Error ? error.message : 'An unknown error occured'}
}
}
export async function deleteOrganisation(idOrganisation : string){
try{
await prisma.organisation.delete(
{
where: {
id : idOrganisation
},
})
return {success : true}
}catch(error){
return{success : false, error : error instanceof Error ? error.message : 'An unknown error occured'}
}
}
export async function getOrganisation(slugOrganisation : string ){
try{
const user = currentUser()
if(!user){
throw new Error("Error with credential")
}
const result = await prisma.organisation.findFirst({
where :{
slug:slugOrganisation
}
})
if(!result){
throw new Error ("Not found")
}
return {success : true, Organisation : result}
}catch(error){
return{success : false, error : error instanceof Error ? error.message : 'An unknown error occured'}
}
}
*/
export async function createOrganisation(formData : FormData){
try{
const {title,file,selectType,tag} = Object.fromEntries(formData)
let slugOrganisation = ""
let logo = ""
if(!slugOrganisation){
let slugCandidate = slugify(title.toString() ,{lower:true,strict:true})
let slugExist = await prisma.organisation.findFirst({
where : {
slug : slugCandidate
}
})
let counter = 1
while(slugExist){
slugCandidate=`${slugCandidate}-${counter}`
slugExist = await prisma.organisation.findFirst({
where : {
slug : slugCandidate
}
})
counter++
}
slugOrganisation = slugCandidate
}
const resultUpdateImage = await uploadImage(file,slugOrganisation)
const user = await currentUser()
if(!user?.id){
throw new Error("Error with user")
}
if(!resultUpdateImage.success){
logo = '/assets/image0_0.jpg'
}else{
logo = resultUpdateImage.imageUrl
}
const result = await prisma.organisation.create({
data : {
name : title.toString(),
logo : logo,
type : selectType.toString(),
tag : tag.toString(),
slug : slugOrganisation
}
})
return {success : true, result : result}
}catch(error){
return{success : false, error : error instanceof Error ? error.message : 'An unknown error occured'}
}
}
export async function getListOrganisations(userId : string){
try{
const result = await prisma.organisation.findMany({
where :{
members:{
some : {
userId
}
},
},
select:{
id : true,
name: true,
logo: true,
slug: true
}
})
if(!result){
return{success : false, error : 'Error when retrieve all organisation', result : result}
}
return{success : true, result: result}
}catch(error){
return{success : false, error : error instanceof Error ? error.message : 'An unknown error occured'}
}
}
export async function getOrganisation(slug : string){
try {
const user = await currentUser(); // 🔍 Récupère l'utilisateur connecté
if (!user || !user.id) {
return { success: false, error: "Utilisateur non authentifié" };
}
const organisation = await prisma.organisation.findFirst({
where: {
slug:slug
},
include: {
members: {
include: {
user: { select: { id: true, userId: true,name: true } }, // Infos de l'utilisateur
role: { select: { id: true, name: true } } // Infos du rôle
}
}
}
});
const userCurrent = organisation?.members.find(member => member.user.userId === user?.id);
if (!organisation) {
return { success: false, error: "Organisation not found" };
}
return {
success: true,
organisation :{
name : organisation.name,
tag : organisation.tag,
slug : organisation.slug,
logo : organisation.logo,
members : organisation.members
},
userCurrent: userCurrent ? {
role: userCurrent.role
} : null
};
} catch (error) {
return { success: false, error: error instanceof Error ? error.message : "An unknown error occurred" };
}
}
export async function deleteOrganisation(slug : string){
try{
const verifOrga = await prisma.organisation.findFirst({
where : {
slug : slug
}
})
if(verifOrga){
const result = await prisma.organisation.delete({
where :{
id: verifOrga.id
}
})
if(result){
if(!result.logo?.includes("image0_0")){
deleteImage(result.logo)
}
return { success: true };
}
}
return { success: false, error: "pas d'orga trouvé" };
}catch(error){
return { success: false, error: error instanceof Error ? error.message : "An unknown error occurred" };
}
}

View File

@ -0,0 +1,34 @@
import prisma from "@/lib/db";
export async function createRole(name : string){
try{
const result = await prisma.role.create({
data : {
name:name
}
})
if(!result){
return{success : false}
}
return({success : true, result : result})
}catch(error){
return{success : false, error : error instanceof Error ? error.message : 'An unknown error occured'}
}
}
export async function getRole(name : string){
try{
const result = await prisma.role.findFirst({
where : {
name: name
}
})
if(!result){
return{success : false}
}
return({success : true, result : result})
}catch(error){
return{success : false, error : error instanceof Error ? error.message : 'An unknown error occured'}
}
}

View File

@ -0,0 +1,22 @@
"use server"
import prisma from "@/lib/db";
export async function createUserOnOrganisation(userId : string ,organisationId : string, roleId : string){
try{
const userOrg = await prisma.userOnOrganisation.create({
data: {
userId,
organisationId,
roleId
},
});
if(!userOrg){
return({success : false})
}
return({success : true, userOrg : userOrg})
}catch(error){
return{success : false, error : error instanceof Error ? error.message : 'An unknown error occured'}
}
}

View File

@ -0,0 +1,74 @@
'use server'
import prisma from "@/lib/db";
import { currentUser } from "@clerk/nextjs/server"
import slugify from "slugify";
export async function createUser(){
try{
const user = await currentUser()
let slugUser = ""
if(!slugUser){
let slugCandidate = slugify(user?.username || "",{lower:true,strict:true})
let slugExist = await prisma.user.findFirst({
where : {
slug: slugCandidate
}
})
let counter = 1
while(slugExist){
slugCandidate=`${slugCandidate}-${counter}`
slugExist = await prisma.user.findFirst({
where : {
slug: slugCandidate
}
})
counter++
}
slugUser = slugCandidate
}
if(!user){
throw new Error("error with user")
}
const result = await prisma.user.create({
data :{
userId : user?.id,
email : user?.emailAddresses[0].emailAddress,
name : user?.username,
slug: slugUser
}
})
if(!result ){
return ({success : false})
}
return ({success : true, id : result.id})
}catch(error){
return{success : false, error : error instanceof Error ? error.message : 'An unknown error occured'}
}
}
export async function getUser() {
try{
const user = await currentUser()
if(!user){
throw new Error("error with user")
}
const result = await prisma.user.findFirst({
where :{
userId : user?.id
}
})
if(!result ){
return ({success : false})
}
return ({success : true, id : result.id})
}catch(error){
return{success : false, error : error instanceof Error ? error.message : 'An unknown error occured'}
}
}

View File

@ -0,0 +1,143 @@
'use client'
import React, { useState,useEffect } from 'react';
import 'leaflet/dist/leaflet.css';
//import L from 'leaflet'
import { LatLngTuple } from 'leaflet';
import '../../styles/map.css'
//import MapSearch from './MapSearch';
import { Button,Form } from 'react-bootstrap';
//import 'bootstrap/dist/css/bootstrap.min.css';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import {faPersonRifle,faCalendarDay,faFile,faEuroSign,faClock,faPeopleGroup} from '@fortawesome/free-solid-svg-icons'
//import ModalMapInfo from '../Informations/ModalMapInscription';
import dynamic from 'next/dynamic';
import L from 'leaflet'; // Import du type L
interface PositionInfo {
name: string;
position: LatLngTuple;
customIcon: L.Icon<{ iconUrl: string; iconSize: [number, number]; iconAnchor: [number, number]; popupAnchor: [number, number]; }>;
date: string;
heure: string;
paf: string;
description: string;
nbParticipant: string;
}
const MapWithNoSSR = dynamic(() => import('react-leaflet').then(mod => mod.MapContainer), {
ssr: false, // Empêche l'exécution côté serveur
});
const TileLayer = dynamic(() => import('react-leaflet').then(mod => mod.TileLayer), { ssr: false });
const Marker = dynamic(() => import('react-leaflet').then(mod => mod.Marker), { ssr: false });
const Popup = dynamic(() => import('react-leaflet').then(mod => mod.Popup), { ssr: false });
const ZoomControl = dynamic(() => import('react-leaflet').then(mod => mod.ZoomControl), { ssr: false });
export default function Home(){
// Hook d'état pour listInfoPosition
const [listInfoPosition,setListInfoPosition] = useState<PositionInfo[]>([]);
const position : LatLngTuple = [46.603354, 3.141249]
const [LModule, setLModule] = useState<typeof L | null>(null); // pour stocker L côté client
useEffect(() => {
if (typeof window !== 'undefined') {
import('leaflet').then(L => {
setLModule(L); // Charge le module `L` côté client
});
}
}, []); // Cela ne s'exécute qu'une seule fois au montage du composant côté client
useEffect(() => {
const positionTemp : PositionInfo[] = []
if (LModule) {
const customIcon = new LModule.Icon({
iconUrl: '/assets/logoEAN.png', // Assurez-vous que le fichier est dans le dossier 'public/images/'
iconSize: [40, 40],
iconAnchor: [16, 32],
popupAnchor: [0, -32],
});
const customIconTaff = new LModule.Icon({
iconUrl: '/assets/logoTaff.png', // Assurez-vous que le fichier est dans le dossier 'public/images/'
iconSize: [32, 32],
iconAnchor: [16, 32],
popupAnchor: [0, -32],
});
let positionTempInfo : PositionInfo =
{
name : "Equipe Airsoft Nord",
position : [50.78794581996757, 2.402059344182279],
customIcon : customIcon,
date: "05/01/2025",
heure : "8h30-16h",
paf : "8€",
description : " Ce dimanche 05 janvier, les EAN organisent leur premiere partie de l'année sur leur terrain.",
nbParticipant : "0/20"
}
positionTemp.push(positionTempInfo)
positionTempInfo =
{
name : "Taff",
position : [50.35071567603138, 3.309760157697385],
customIcon : customIconTaff,
date: "05/01/2025",
heure : "8h30-16h",
paf : "10€",
description : "salut c&apos;est la taff",
nbParticipant : "0/40"
}
positionTemp.push(positionTempInfo)
setListInfoPosition(positionTemp)
}
}, [LModule]);
useEffect(() => {
},[])
return(
<div style={{ height: "94vh" }}>
{/*<MapSearch />*/}
<MapWithNoSSR center={position} zoom={7} style={{ height: "100%", width: "100%",zIndex:0 }} minZoom={7} zoomControl={false}>
{/* Chargement des tuiles OpenStreetMap */}
<ZoomControl position="topright" />
<TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{/* Ajout d'un marker */}
{listInfoPosition.map((data,index) =>
<Marker key={index} position={data.position} icon={data.customIcon}>
<Popup >
<div className="container">
<Form>
<Form.Group>
<Form.Label><FontAwesomeIcon icon={faPersonRifle} /> : <strong>{data.name}</strong></Form.Label>
</Form.Group>
<Form.Group>
<Form.Label><FontAwesomeIcon icon={faCalendarDay} /> : {data.date} <FontAwesomeIcon icon={faClock} /> : {data.heure}</Form.Label>
</Form.Group>
<Form.Group>
<Form.Label><FontAwesomeIcon icon={faFile} /> : {data.description}</Form.Label>
</Form.Group>
<Form.Group>
<Form.Label><FontAwesomeIcon icon={faEuroSign} /> : {data.paf}</Form.Label>
</Form.Group>
<Form.Group>
<Form.Label><FontAwesomeIcon icon={faPeopleGroup} /> : {data.nbParticipant}</Form.Label>
</Form.Group>
<Form.Group className="d-grid gap-2">
<Button size='sm' variant='dark' >S&apos;inscrire</Button>
</Form.Group>
</Form>
</div>
</Popup>
</Marker>
)}
</MapWithNoSSR>
{/*<ModalMapInfo isOpen={isModalOpen} onClose={closeModal}/>*/}
</div>
)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 76 KiB

View File

@ -19,3 +19,37 @@ body {
background: var(--background);
font-family: Arial, Helvetica, sans-serif;
}
.u-main-container {
@apply w-full sm:max-w-md md:max-w-lg lg:max-w-7xl mx-auto p-3
}
.u-padding-content-container{
@apply pt-32 pb-44
}
.u-articles-grid{
@apply grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-x-4 gap-y-8 mt-4 mb-12 list-none
}
/* text */
.t-main-title{
@apply font-bold text-zinc-900 text-4xl mb-2
}
.t-main-subtitle{
@apply mb-12 text-zinc-700
}
.f-label{
@apply block ml-2 mb-2 text-gray-700 font-semibold
}
.f-input {
@apply shadow appearance-none border rounded p-3 mr-2 text-gray-700 leading-tight mb-5 focus:outline-slate-400 w-1/2
}
.f-auth-input {
@apply shadow appearance-none border rounded w-full p-3 text-gray-700 leading-tight mb-5 focus:outline-slate-400
}

View File

@ -1,20 +1,18 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
//import "../styles/menu.css"
//import 'bootstrap/dist/css/bootstrap.min.css';
import {
ClerkProvider
} from '@clerk/nextjs'
import Menu from '../components/menu'
import { dark } from '@clerk/themes'
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
title: "Airsoft Search",
description: "Airsoft Search is a platform dedicated to airsoft players worldwide, helping them easily find upcoming events, matches, and meetups near them or internationally. With an intuitive interface, discover scheduled games, register quickly, and join a community of passionate players. Whether you're a beginner or an experienced player, Airsoft Search simplifies the planning of your sessions and connects you with other players globally.",
keywords: "airsoft events, airsoft matches, airsoft community, find airsoft games, airsoft parties, airsoft meetup, airsoft players worldwide, global airsoft events, airsoft calendar, airsoft groups",
};
export default function RootLayout({
@ -23,12 +21,17 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
<html lang="en">
<ClerkProvider appearance={{
baseTheme: dark,
}}>
<html lang="fr">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
className="flex min-h-full flex-col"
>
{children}
<Menu />
{children}
</body>
</html>
</ClerkProvider>
);
}

9
src/app/loading.tsx Normal file
View File

@ -0,0 +1,9 @@
import React from 'react'
export default function Loading() {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-16 w-16 border-t-4 border-blue-500 border-solid"></div>
</div>
);
}

BIN
src/app/old_favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -0,0 +1,37 @@
"use client"
import React,{useEffect, useState} from 'react'
import Image from 'next/image';
export default function pageInfoSession({params}) {
const [image, setImage] = useState(null);
const [slugSession,setSlugSession] = useState("")
/*useEffect(() => {
const getSlug = async () => {
const {slugSession} = await params
setSlugSession(slugSession)
}
getSlug()
},[])*/
// Fonction qui gère la sélection d'une nouvelle image
const handleImageChange = (e) => {
const file = e.target.files[0];
if (file) {
// Crée un URL temporaire pour l'image sélectionnée
const imageUrl = URL.createObjectURL(file);
setImage(imageUrl);
}
};
return (
<main>
<div>Partie : {slugSession}</div>
</main>
)
}

View File

@ -0,0 +1,9 @@
import React from 'react'
export default function Loading() {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-16 w-16 border-t-4 border-blue-500 border-solid"></div>
</div>
);
}

View File

@ -0,0 +1,7 @@
import React from 'react'
export default function pageGame() {
return (
<div>Game</div>
)
}

View File

@ -0,0 +1,9 @@
import React from 'react'
export default function Loading() {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-16 w-16 border-t-4 border-blue-500 border-solid"></div>
</div>
);
}

View File

@ -0,0 +1,218 @@
"use client"
import {useEffect, useRef, useState} from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import {faTrash,faPenToSquare,faCamera,faCheck, faCancel} from '@fortawesome/free-solid-svg-icons'
import Image from 'next/image'
import logoDefault from 'p/assets/image0_0.jpg'
import {getOrganisationMethods,deleteOrganisationMethods } from "@/methods/organisationMethods"
import { useRouter } from 'next/navigation'
import ConfirmDeleteModal from '@/components/ConfirmeDeleteModal'
export default function pageOrganisation({params}) {
const router = useRouter()
const [infoOrganisation,setInfoOrganisation] = useState("")
const [listMember,setListMember] = useState([])
const [error,setError] = useState("")
const [role,setRole] = useState("")
const [isEnable,setIsEnable] = useState(false)
const refImageChange = useRef(null)
const [image, setImage] = useState(null);
const [isModalOpen, setModalOpen] = useState(false);
const [hiddenError,setHiddenError] = useState(true)
async function getSlug(){
const {slugOrganisation } = await params
return slugOrganisation
}
useEffect(() => {
fetchOrganisation()
},[])
function handleEnableModify(e){
e.preventDefault()
setIsEnable(true)
}
function handleEnableChangeImage(e){
e.preventDefault()
refImageChange.current.click()
}
// Fonction qui gère la sélection d'une nouvelle image
const handleImageChange = (e) => {
const file = e.target.files[0];
if (file) {
// Crée un URL temporaire pour l'image sélectionnée
const imageUrl = URL.createObjectURL(file);
setImage(imageUrl);
}
};
async function fetchOrganisation() {
try{
const result = await getOrganisationMethods(await getSlug())
console.log(result)
if(result.success){
setInfoOrganisation(result.result.organisation)
setListMember(result.result.organisation.members)
setImage(result.result.organisation.logo)
setRole(result.result.userCurrent.role.name)
}
}catch(e){
setError("une erreur s'est produite : " + e)
}
}
async function handleDisableModify(e){
e.preventDefault()
try{
fetchOrganisation()
}catch(e){
setError("une erreur s'est produite : " + e)
}
setIsEnable(false)
}
async function handleDeleteOrga(e){
e.preventDefault()
setHiddenError(true)
try{
const result = await deleteOrganisationMethods(await getSlug())
if(result){
router.push('/organisations')
}
setModalOpen(false);
setError('Erreur lors de la suppression essayer plus tard')
setHiddenError(false)
}catch(err){
setError("une erreur s'est produite : " + e)
}
}
return (
<main className='u-main-container'>
<div hidden={hiddenError}
className='text-center text-sm bg-red-100 border border-red-600 rounded'>
<p className='text-red-600'>{error}</p>
</div>
<div className='shadow-md bg-zinc-700 rounded-t flex justify-between items-center p-2 mb-3 relative' >
<div className='relative w-1/5'>
<Image
src={image ? image : logoDefault}
alt="logo Organisation"
width={500}
height={500}
className='w-full mr-3 object-contain rounded-xl cursor-pointer border border-gray-500'
onClick={handleEnableChangeImage}/>
<FontAwesomeIcon
icon={faCamera}
className="absolute bottom-0 right-0 bg-gray-700 text-white p-1 rounded-full w-5 h-5 cursor-pointer"
onClick={handleEnableChangeImage}
/>
<input
ref={refImageChange}
type="file"
id="image-input"
style={{ display: 'none' }}
accept="image/*"
/>
</div>
{role === "ADMIN" &&
<div className='absolute top-0 right-0 flex space-x-2 p-2 mt-2'>
<button
type='button'
className={`${isEnable ? 'hidden' : 'block' } border border-gray-500 rounded-xl px-3 py-1 bg-gray-200 text-xs flex items-center space-x-1`}
onClick={handleEnableModify}
hidden={isEnable}><FontAwesomeIcon icon={faPenToSquare }/> &nbsp;Modify</button>
<button
type='button'
//onClick={handleDeleteOrga}
onClick={() => setModalOpen(true)}
className={`${isEnable ? 'block' : 'hidden' } border border-red-500 rounded-xl px-3 py-1 bg-red-500 text- text-white text-xs flex items-center space-x-1`}
><FontAwesomeIcon icon={faTrash }/>&nbsp; Delete</button>
</div>
}
</div>
<form >
<fieldset className='border rounded border-gray-400 mb-4 shadow-md'>
<legend className='text-sm font-semibold px-2'>Détail </legend>
<div className='flex flex-row pt-4'>
<label className='f-label w-1/2 pt-3 pr-3'>Name</label>
<input
type="text"
disabled={!isEnable}
className="f-input"
value={infoOrganisation.name || ''}
onChange={e => setInfoOrganisation({...infoOrganisation, name: e.target.value})}></input>
</div>
<div className='flex flex-row'>
<label className='f-label w-1/2 pt-3 pr-3'>Tag :</label>
<input
type="text"
disabled={!isEnable}
className="f-input"
value={infoOrganisation.tag || ''}
onChange={e => setInfoOrganisation({...infoOrganisation, tag: e.target.value})}></input>
</div>
<div className='flex flex-row'>
<label className='f-label w-1/2 pt-3 pr-3'>Team :</label>
<input
type="text"
disabled={!isEnable}
className="f-input"
value={infoOrganisation.team || ''}
onChange={e => setInfoOrganisation({...infoOrganisation, team: e.target.value})}></input>
</div>
<div className='flex flex-row justify-end space-x-2 mr-4 mb-2'>
<button
type="button"
hidden={!isEnable}
className='m-1 px-2 py-1 bg-green-700 text-white text-xs font-semibold rounded text-center'><FontAwesomeIcon icon={faCheck }/>&nbsp;Validate</button>
<button
type="button"
hidden={!isEnable}
className='m-1 px-2 py-1 bg-red-700 text-white text-xs font-semibold rounded text-center'
onClick={handleDisableModify}><FontAwesomeIcon icon={faCancel }/>&nbsp; Cancel</button>
</div>
</fieldset>
</form>
<fieldset className='border rounded border-gray-400 mb-4 py-2 shadow-md'>
<legend className='text-sm font-semibold px-2 '>Membres et role </legend>
{
Array.isArray(listMember) && listMember.length > 0 ? (
listMember.map((member) => (
<div className='flex flex-row' key={member.user.id}>
<p className='text-sm w-1/3 text-center' scope="row" >{member.user.name} </p>
<p className='text-sm text-center' scope="row" >|</p>
<p className='text-sm w-1/3 text-center' scope="row" >{member.role.name}</p>
<p className='text-sm text-center' scope="row" >|</p>
<div className='flex flex-row space-x-2'>
<button type="button" className='items-center flex'>test</button>
</div>
</div>
))
) : (
<p className='w-full text-center' >Aucun membre trouvé</p>
)
}
</fieldset>
<ConfirmDeleteModal
isOpen={isModalOpen}
onClose={() => setModalOpen(false)}
onConfirm={handleDeleteOrga}
/>
</main>
)
}

View File

@ -0,0 +1,63 @@
"use client"
import { useState,useRef } from "react"
//import { createOrganisation } from "@/actions/organisationActions";
import { createOrganisationMathods } from "@/methods/organisationMethods";
import { useRouter } from "next/navigation";
export default function pageCreate() {
const router = useRouter()
const [valueTagState,setValueTagState] = useState('')
const valueTitle = useRef(null)
const valueTag = useRef(null)
async function handleSubmit(e){
e.preventDefault()
const formData = new FormData(e.target)
const result = await createOrganisationMathods(formData)
if(result.success){
console.log(result.slugOrga)
router.push(`/organisations/${result.slugOrga}`)
}
}
function handleCreateTag(e){
const title = valueTitle.current.value
let words = title.split(" ")
let result = words.map(word =>{
return word.charAt(0).toUpperCase()
})
setValueTagState(result.join(''))
valueTag.current.value = valueTagState.toString()
}
return (
<main className='u-main-container'>
<h1 className='text-2xl my-2'>Create a organisation</h1>
<p className='text-sm text-gray-400 my-2'>* required</p>
<form className='pb-6' onSubmit={handleSubmit}>
<label htmlFor='title' className='f-label'>Name of the organization* </label>
<input onChange={handleCreateTag} ref={valueTitle} type="text" name="title" className="shadow border rounded w-full p-3 mb-7 text-gray-700 focus:outline-slate-400" id='title' placeholder='Title' required/>
<label htmlFor='tag' className='f-label'>Tag* </label>
<input type="text" name="tag" ref={valueTag} className="shadow border rounded w-full p-3 mb-7 text-gray-700 focus:outline-slate-400" id='tag' placeholder='Tag' required/>
<label htmlFor='avatar' className='f-label'>Logo</label>
{/*<input type="file" name="file" className="shadow border rounded w-full p-3 mb-7 text-gray-700 focus:outline-slate-400" id='file' placeholder='file'/>*/}
<input type="file" name="file" className="shadow border rounded w-full p-3 mb-7 text-gray-700 focus:outline-slate-400" id='file' accept="image/*"/>
<label htmlFor='selectType' className='f-label'>Logo</label>
<select type="selectType" name="selectType" className="shadow border rounded w-full p-3 mb-7 text-gray-700 focus:outline-slate-400" id='selectType' placeholder='file'>
<option value="">--Please choose an option--</option>
<option value="association">Association</option>
<option value="team">Team</option>
</select>
<button type="submit" className="bg-green-600 rounded text-white p-3 w-full">Create a organisation</button>
<p className='text-red-600 hidden'>Error</p>
</form>
</main>
)
}

View File

@ -0,0 +1,9 @@
import React from 'react'
export default function Loading() {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-16 w-16 border-t-4 border-blue-500 border-solid"></div>
</div>
);
}

View File

@ -0,0 +1,88 @@
'use client'
import React, { useEffect, useState } from "react";
import Image from "next/image";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import {faSearch,faPlus} from '@fortawesome/free-solid-svg-icons'
import { useRouter } from "next/navigation";
import Link from "next/link";
import { getListOrganisationsMethod } from "@/methods/organisationMethods";
export default function OrganisationsPage(){
const [listOrganisation,setListOrganisation] = useState([])
const router = useRouter()
const [loading, setLoading] = useState(false);
function handleCreateOrganisation(e){
router.push('/organisations/create')
}
function handleSearchOrganisation(e){
router.push('/organisations/search')
}
useEffect(() => {
const fetchOrganisation = async() => {
try{
const result = await getListOrganisationsMethod()
if(result.success){
const listTemp = result.result?.result
setListOrganisation(listTemp)
}
}catch(e){
setError("une erreur s'est produite : " + e)
}
}
fetchOrganisation()
setLoading(false)
},[])
if (loading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-16 w-16 border-t-4 border-blue-500 border-solid"></div>
</div>
);
}
return (
<main className="u-main-container bg-white">
<div>
<div className="bg-gray-500 border-solid border text-white shadow-lg rounded flex justify-between items-center p-2">
<p className="mx-2">Vos organisations :</p>
<div className="flex space-x-2">
<button
type="button"
onClick={handleCreateOrganisation}
className="shadow text-xs text-black bg-gray-100 border border-gray-500 rounded-full px-3 py-1 hover:bg-gray-300"
>
<FontAwesomeIcon icon={faPlus} /> Add
</button>
<button
type="button"
onClick={handleSearchOrganisation}
className="shadow text-xs text-black bg-green-100 border border-green-500 rounded-full px-3 py-1 hover:bg-green-300"
>
<FontAwesomeIcon icon={faSearch} /> Search
</button>
</div>
</div>
{listOrganisation.map((organisation) => (
<Link key={organisation.id} href={`/organisations/${organisation.slug}`}>
<li className="bg-white rounded-sm list-none type shadow-md hover:shadow-xl border hover:border-zinc-300">
<div >
<div className="flex p-1 grow " >
<Image src={organisation.logo} width={100} height={50} alt="logo"></Image>
<div className="ml-2 mr-5 w-full flex flex-col justify-center text-center overflow-hidden">
<p className="text-gray-700 text-sm hover:text-gray-600 p-1">{organisation.name}</p>
</div>
</div>
</div>
</li>
</Link>
))}
</div>
</main>
)
}

View File

@ -0,0 +1,7 @@
import React from 'react'
export default function pageSearch() {
return (
<div></div>
)
}

View File

@ -1,101 +0,0 @@
import Image from "next/image";
export default function Home() {
return (
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
<Image
className="dark:invert"
src="/next.svg"
alt="Next.js logo"
width={180}
height={38}
priority
/>
<ol className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
<li className="mb-2">
Get started by editing{" "}
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold">
src/app/page.tsx
</code>
.
</li>
<li>Save and see your changes instantly.</li>
</ol>
<div className="flex gap-4 items-center flex-col sm:flex-row">
<a
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5"
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
className="dark:invert"
src="/vercel.svg"
alt="Vercel logomark"
width={20}
height={20}
/>
Deploy now
</a>
<a
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Read our docs
</a>
</div>
</main>
<footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/file.svg"
alt="File icon"
width={16}
height={16}
/>
Learn
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/window.svg"
alt="Window icon"
width={16}
height={16}
/>
Examples
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/globe.svg"
alt="Globe icon"
width={16}
height={16}
/>
Go to nextjs.org
</a>
</footer>
</div>
);
}

View File

@ -0,0 +1,14 @@
import { SignIn } from "@clerk/nextjs";
export default function SignInPage(){
return (
<div className="flex justify-center items-center">
<SignIn
appearance={{
elements: {
footerAction: { display: "none" },
},
}}/>
</div>
)
}

View File

@ -0,0 +1,13 @@
import { SignUp } from "@clerk/nextjs";
export default function SingUpPage(){
return (
<div className="flex justify-center items-center">
<SignUp appearance={{
elements: {
footerAction: { display: "none" },
},
}}/>
</div>
)
}

View File

@ -0,0 +1,5 @@
import { AuthenticateWithRedirectCallback } from '@clerk/nextjs'
export default function Page() {
return <AuthenticateWithRedirectCallback />
}

View File

@ -0,0 +1,45 @@
"use client";
import { useEffect } from "react";
interface ConfirmDeleteModalProps {
isOpen: boolean;
onClose: () => void;
onConfirm: () => void;
}
export default function ConfirmDeleteModal({ isOpen, onClose, onConfirm }: ConfirmDeleteModalProps) {
// Empêche le défilement quand la modale est ouverte
useEffect(() => {
if (isOpen) document.body.style.overflow = "hidden";
else document.body.style.overflow = "auto";
}, [isOpen]);
if (!isOpen) return null;
return (
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50">
{/* Contenu de la modale */}
<div className="bg-white p-6 rounded-xl shadow-lg max-w-sm w-full border border-gray-500">
<h2 className="text-lg font-semibold text-gray-800">Confirmer la suppression</h2>
<p className="text-gray-600 mt-2">Êtes-vous sûr de vouloir supprimer cette donnée ? Cette action est irréversible.</p>
{/* Boutons */}
<div className="flex justify-end mt-4 space-x-2">
<button
onClick={onClose}
className="px-4 py-2 text-gray-700 bg-gray-200 rounded-lg hover:bg-gray-300 transition"
>
Annuler
</button>
<button
onClick={onConfirm}
className="px-4 py-2 text-white bg-red-600 rounded-lg hover:bg-red-700 transition"
>
Supprimer
</button>
</div>
</div>
</div>
);
}

181
src/components/menu.jsx Normal file
View File

@ -0,0 +1,181 @@
'use client'
import { Disclosure, DisclosureButton, DisclosurePanel, Menu, MenuButton, MenuItem, MenuItems} from '@headlessui/react'
import { Bars3Icon, BellIcon, XMarkIcon } from '@heroicons/react/24/outline'
import Link from 'next/link'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import {faUser,faSignIn,faSignOut} from '@fortawesome/free-solid-svg-icons'
import { useRouter } from 'next/navigation'
import Image from 'next/image'
import logoAs from "../../public/assets/logoAS.png"
import {
SignedIn,
SignedOut,
UserButton,
} from '@clerk/nextjs'
import { useEffect } from 'react'
import {createUser,getUser} from "@/actions/usersActions"
const NavbarsMenu = () => {
/*useEffect(() =>{
const verifUser = async () =>{
try{
let resultVerif = await getUser()
if(!resultVerif.success){
const verifCreateUser = await createUser()
if(!verifCreateUser.success){
throw new Error("Error with create credential")
}
}
}catch(err){
throw new Error("Error with credential")
}
}
verifUser()
},[])*/
const router = useRouter()
const handleRedirectConnexion = () => {
router.push('/sign-in'); // Redirige vers la page "/sign-in"
};
const handleRedirectInscription = () => {
router.push('/sign-in'); // Redirige vers la page "/sign-in"
};
const navigation = [
{ name: 'Organisations', href: '/organisations', current: false }
]
function classNames(...classes) {
return classes.filter(Boolean).join(' ')
}
return (
<Disclosure as="nav" className="bg-gray-800">
<div className="mx-auto max-w-7xl px-2 sm:px-6 lg:px-8">
<div className="relative flex h-16 items-center justify-between">
<div className="absolute inset-y-0 left-0 flex items-center sm:hidden">
{/* Mobile menu button*/}
<DisclosureButton className="group relative inline-flex items-center justify-center rounded-md p-2 text-gray-400 hover:bg-gray-700 hover:text-white focus:ring-2 focus:ring-white focus:outline-hidden focus:ring-inset">
<span className="absolute -inset-0.5" />
<span className="sr-only">Open main menu</span>
<Bars3Icon aria-hidden="true" className="block size-6 group-data-open:hidden" />
<XMarkIcon aria-hidden="true" className="hidden size-6 group-data-open:block" />
</DisclosureButton>
</div>
<div className="flex flex-1 items-center justify-center sm:items-stretch sm:justify-start">
<div className="flex shrink-0 items-center">
<Link href='/'><Image
alt="Airsoft Search"
src={logoAs}
className="h-8 w-auto"
width={50}
height={50}
/></Link>
</div>
<div className="hidden sm:ml-6 sm:block">
<div className="flex space-x-4">
{navigation.map((item) => (
<a
key={item.name}
href={item.href}
aria-current={item.current ? 'page' : undefined}
className={classNames(
item.current ? 'bg-gray-900 text-white' : 'text-gray-300 hover:bg-gray-700 hover:text-white',
'rounded-md px-3 py-2 text-sm font-medium',
)}
>
{item.name}
</a>
))}
</div>
</div>
</div>
<div className="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0">
<button
type="button"
className="relative rounded-full bg-gray-800 p-1 text-gray-400 hover:text-white focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800 focus:outline-hidden"
>
<span className="absolute -inset-1.5" />
<span className="sr-only">View notifications</span>
<BellIcon aria-hidden="true" className="size-6" />
</button>
{/* Profile dropdown */}
<Menu as="div" className="relative ml-3">
<div>
<SignedOut>
<MenuButton className="relative flex rounded-full bg-gray-800 text-sm focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800 focus:outline-hidden">
<span className="absolute -inset-1.5" />
<span className="sr-only">Open user menu</span>
<FontAwesomeIcon icon={faUser} />
</MenuButton>
</SignedOut>
</div>
<MenuItems
transition
className="absolute right-0 z-10 mt-2 w-40 origin-top-right rounded-md bg-white py-1 ring-1 shadow-lg ring-black/5 transition focus:outline-hidden data-closed:scale-95 data-closed:transform data-closed:opacity-0 data-enter:duration-100 data-enter:ease-out data-leave:duration-75 data-leave:ease-in"
>
<MenuItem>
<div
className="block px-4 py-2 text-sm text-gray-700 data-focus:bg-gray-100 data-focus:outline-hidden"
>
<button onClick={handleRedirectConnexion} >
<FontAwesomeIcon icon={faSignIn} /> Connexion
</button>
</div>
</MenuItem>
<MenuItem>
<div
className="block px-4 py-2 text-sm text-gray-700 data-focus:bg-gray-100 data-focus:outline-hidden"
>
<button onClick={handleRedirectInscription}>
<FontAwesomeIcon icon={faSignOut} /> Inscription
</button>
</div>
</MenuItem>
</MenuItems>
<SignedIn>
<UserButton />
</SignedIn>
</Menu>
</div>
</div>
</div>
<DisclosurePanel className="sm:hidden">
<div className="space-y-1 px-2 pt-2 pb-3">
{navigation.map((item) => (
<DisclosureButton
key={item.name}
as="a"
href={item.href}
aria-current={item.current ? 'page' : undefined}
className={classNames(
item.current ? 'bg-gray-900 text-white' : 'text-gray-300 hover:bg-gray-700 hover:text-white',
'block rounded-md px-3 py-2 text-base font-medium',
)}
>
{item.name}
</DisclosureButton>
))}
</div>
</DisclosurePanel>
</Disclosure>
);
};
export default NavbarsMenu;

16
src/lib/db.ts Normal file
View File

@ -0,0 +1,16 @@
import { PrismaClient } from "@prisma/client";
const prismaClientSingleton = () => {
return new PrismaClient()
}
declare const globalThis:{
prismaGlobal : ReturnType<typeof prismaClientSingleton>
} & typeof global
const prisma = globalThis.prismaGlobal ?? prismaClientSingleton()
export default prisma
if (process.env.NODE_ENV !== "production") globalThis.prismaGlobal = prisma;

60
src/lib/uploadFile.js Normal file
View File

@ -0,0 +1,60 @@
"use server";
import fs from "fs";
import path from "path";
export async function uploadImage(file,slug) {
"use server";
// Vérifier le type de fichier (optionnel)
const allowedTypes = ["image/png", "image/jpeg", "image/jpg", "image/webp"];
if (!allowedTypes.includes(file.type)) {
return { error: "Format non supporté (PNG, JPG, WEBP seulement)" };
}
// Lire le fichier en tant que buffer
const bytes = await file.arrayBuffer();
const buffer = Buffer.from(bytes);
// Définir le dossier d'upload
const uploadDir = path.join(process.cwd(), "public/assets/avatar/avatarOrganisations");
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, { recursive: true });
}
// Générer un nouveau nom unique
const fileExt = path.extname(file.name); // Obtenir l'extension (.jpg, .png, etc.)
const newFileName = `${slug}${fileExt}`; // Exemple: image_123e4567-e89b-12d3-a456-426614174000.png
const filePath = path.join(uploadDir, newFileName);
// Écrire le fichier avec le nouveau nom
fs.writeFileSync(filePath, buffer);
// URL accessible de l'image
const imageUrl = `/assets/avatar/avatarOrganisations/${newFileName}`;
// Revalider le cache pour rafraîchir l'affichage
return { success: true, imageUrl };
}
export async function deleteImage(imagePath) {
if (!imagePath) {
return { error: "Chemin de l'image non fourni" };
}
// Assurer que le fichier est bien dans /public/assets
const fullPath = path.join(process.cwd(), "public", imagePath);
if (!fs.existsSync(fullPath)) {
return { error: "Fichier introuvable" };
}
try {
fs.unlinkSync(fullPath); // Supprime l'image
return { success: true, message: "Image supprimée avec succès" };
} catch (error) {
return { error: "Erreur lors de la suppression de l'image" , message : error.message };
}
}

View File

@ -0,0 +1,84 @@
"use server"
import {createOrganisation,getListOrganisations, getOrganisation, deleteOrganisation} from '@/actions/organisationActions'
import { createRole, getRole } from '@/actions/roleActions'
import {createUserOnOrganisation} from '@/actions/userOnOrganisation'
import { createUser, getUser } from '@/actions/usersActions'
export async function createOrganisationMathods(formData : FormData){
try{
let role = await getRole("ADMIN")
if(!role.success){
const roleCreate = await createRole("ADMIN")
if(!roleCreate.success){
throw new Error("Error while create role")
}
role = roleCreate
}
let verifUser = await getUser()
if(!verifUser?.success){
const create = await createUser()
if(!create.success){
throw new Error ("Error when create user")
}
verifUser = create
}
const result = await createOrganisation(formData)
if(result.success){
const resultCreate = await createUserOnOrganisation(verifUser?.id || "",result.result?.id || "", role.result?.id || "")
if(resultCreate){
return{success : true, slugOrga : result.result?.slug}
}
}
return{success : false}
}catch(error){
return{success : false, error : error instanceof Error ? error.message : 'An unknown error occured'}
}
}
export async function getListOrganisationsMethod(){
try{
const verifUser = await getUser()
if(verifUser.success){
const result = await getListOrganisations(verifUser?.id || "")
if(result.success){
return{success : true, result : result}
}
}
return{success : false, error : 'An unknown error occured'}
}catch(error){
return{success : false, error : error instanceof Error ? error.message : 'An unknown error occured'}
}
}
export async function getOrganisationMethods(slug : string){
try{
const result = await getOrganisation(slug)
if(result.success){
return{success : true, result : result}
}
return{success : false, error : 'An unknown error occured'}
}catch(error){
return{success : false, error : error instanceof Error ? error.message : 'An unknown error occured'}
}
}
export async function deleteOrganisationMethods(slug : string){
try{
const result = await deleteOrganisation(slug)
if(result.success){
return{success : true, result : result}
}
return{success : false, error : 'An unknown error occured'}
}catch(error){
return{success : false, error : error instanceof Error ? error.message : 'An unknown error occured'}
}
}

17
src/middleware.ts Normal file
View File

@ -0,0 +1,17 @@
import { clerkMiddleware,createRouteMatcher } from "@clerk/nextjs/server";
const isProtectedRoute = createRouteMatcher(['/organisations(.*)'])
export default clerkMiddleware(async (auth,req) =>{
if(isProtectedRoute(req)) await auth.protect()
})
export const config = {
matcher: [
// Skip Next.js internals and all static files, unless found in search params
'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
// Always run for API routes
'/(api|trpc)(.*)',
],
};

23
src/styles/map.css Normal file
View File

@ -0,0 +1,23 @@
#Map { height: 180px; }
.container {
display: flex; /* Utilisation de Flexbox */
justify-content: space-between; /* Aligner les éléments sur toute la largeur */
align-items: center; /* Centrer verticalement les éléments */
z-index: 2;
}
/* Bloc de texte à gauche */
.PopUp-block-text {
flex: auto; /* Permet au bloc de texte de prendre l'espace restant */
}
/* Bloc d'image à droite */
.PopUp-block-img {
flex: auto; /* L'image garde sa taille naturelle */
}
.PopUp-block-img img {
max-width: 75%; /* Assurer que l'image s'adapte à son conteneur */
height: auto; /* Garder les proportions de l'image */
}

41
src/styles/menu.css Normal file
View File

@ -0,0 +1,41 @@
.menu {
background: linear-gradient(rgb(68, 68, 68),rgb(187, 186, 186));
/*ckground-color: #000000;*/
color: gold;
/* padding: 10px;*/
display: flex; /* Utilisation de flexbox */
align-items: center; /* Aligner verticalement les éléments */
/*justify-content: center; /* Centrer les éléments horizontalement */
}
.menu img {
margin-right: 20px; /* Espacement entre l'image et le menu */
height: 40px; /* Définir une hauteur fixe pour l'image (ajuste la valeur selon tes besoins) */
}
.menu ul {
list-style-type: none;
padding: 0;
display: flex; /* Aligner les éléments de la liste horizontalement */
}
.menu li {
margin-right: 20px;
padding-top: 12px;
}
.menu a {
text-decoration: none;
font-weight: bold;
}
.menu a:hover {
color: #f5d081;
}
.dropDownMenu{
/*background: linear-gradient(rgb(68, 68, 68),rgb(187, 186, 186));*/
background: gray;
margin-right:"10px"
}

View File

@ -19,9 +19,10 @@
}
],
"paths": {
"@/*": ["./src/*"]
"@/*": ["./src/*"],
"p/*": ["./public/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "src/app/organisations/[slug]/game/session/[slug]/page.jsx", "src/app/organisations/[[...rest]]/game/session/page.jsx", "src/components/menu.jsx", "src/app/organisations/components/listOrganisation.jsx", "src/app/organisations/page.jsx"],
"exclude": ["node_modules"]
}