Commit pour save le repo suite au transfert
28
package.json
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "testauthclerk",
|
"name": "airsoftsearch",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -9,19 +9,35 @@
|
||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"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": "^19.0.0",
|
||||||
|
"react-bootstrap": "^2.10.7",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"next": "15.1.4"
|
"react-fontawesome": "^1.7.1",
|
||||||
|
"react-leaflet": "^5.0.0",
|
||||||
|
"slugify": "^1.6.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5",
|
"@eslint/eslintrc": "^3",
|
||||||
|
"@types/leaflet": "^1.9.16",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"postcss": "^8",
|
|
||||||
"tailwindcss": "^3.4.1",
|
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "15.1.4",
|
"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);
|
background: var(--background);
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
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 type { Metadata } from "next";
|
||||||
import { Geist, Geist_Mono } from "next/font/google";
|
|
||||||
import "./globals.css";
|
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 = {
|
export const metadata: Metadata = {
|
||||||
title: "Create Next App",
|
title: "Airsoft Search",
|
||||||
description: "Generated by create next app",
|
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({
|
export default function RootLayout({
|
||||||
|
@ -23,12 +21,17 @@ export default function RootLayout({
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<ClerkProvider appearance={{
|
||||||
|
baseTheme: dark,
|
||||||
|
}}>
|
||||||
|
<html lang="fr">
|
||||||
<body
|
<body
|
||||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
className="flex min-h-full flex-col"
|
||||||
>
|
>
|
||||||
|
<Menu />
|
||||||
{children}
|
{children}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</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": {
|
"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"]
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
|