Commit pour save le repo suite au transfert
28
package.json
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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("")
|
||||
}
|
|
@ -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])
|
||||
}
|
After Width: | Height: | Size: 249 KiB |
After Width: | Height: | Size: 76 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 101 KiB |
|
@ -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'}
|
||||
}
|
||||
}
|
|
@ -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" };
|
||||
}
|
||||
}
|
|
@ -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'}
|
||||
}
|
||||
}
|
||||
|
|
@ -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'}
|
||||
}
|
||||
|
||||
}
|
|
@ -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'}
|
||||
}
|
||||
|
||||
}
|
|
@ -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'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='© <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'inscrire</Button>
|
||||
</Form.Group>
|
||||
</Form>
|
||||
</div>
|
||||
</Popup>
|
||||
</Marker>
|
||||
)}
|
||||
</MapWithNoSSR>
|
||||
{/*<ModalMapInfo isOpen={isModalOpen} onClose={closeModal}/>*/}
|
||||
</div>
|
||||
)
|
||||
}
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 76 KiB |
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
>
|
||||
<Menu />
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
</ClerkProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
After Width: | Height: | Size: 25 KiB |
|
@ -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>
|
||||
|
||||
|
||||
|
||||
)
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import React from 'react'
|
||||
|
||||
export default function pageGame() {
|
||||
return (
|
||||
<div>Game</div>
|
||||
)
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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 }/> 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 }/> 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 }/> 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 }/> 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>
|
||||
)
|
||||
}
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import React from 'react'
|
||||
|
||||
export default function pageSearch() {
|
||||
return (
|
||||
<div></div>
|
||||
)
|
||||
}
|
101
src/app/page.tsx
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import { AuthenticateWithRedirectCallback } from '@clerk/nextjs'
|
||||
|
||||
export default function Page() {
|
||||
return <AuthenticateWithRedirectCallback />
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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 };
|
||||
}
|
||||
}
|
||||
|
|
@ -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'}
|
||||
}
|
||||
}
|
|
@ -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)(.*)',
|
||||
],
|
||||
};
|
|
@ -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 */
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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"]
|
||||
}
|
||||
|
|