Compare commits

..

28 Commits

Author SHA1 Message Date
Julien Froidefond
0c47bf916c Refactor management components to remove loading state: Eliminate unused loading state and related conditional rendering from ChallengeManagement, EventManagement, FeedbackManagement, HouseManagement, and UserManagement components, simplifying the code and improving readability.
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 3m43s
2025-12-22 08:56:37 +01:00
Julien Froidefond
9bcafe54d3 Add profile and house background preferences to SitePreferences: Extend SitePreferences model and related services to include profileBackground and houseBackground fields. Update API and UI components to support new background settings, enhancing user customization options. 2025-12-22 08:54:51 +01:00
Julien Froidefond
14c767cfc0 Refactor AdminPage and remove AdminPanel component: Simplify admin navigation by redirecting to preferences page and eliminating the AdminPanel component, streamlining the admin interface.
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 6m21s
2025-12-19 14:02:06 +01:00
Julien Froidefond
82069c74bc Add HouseManagement integration to AdminPanel and implement removeMemberAsAdmin feature in HouseService: Enhance admin capabilities with new section for house management and functionality to remove members from houses by admins, including points deduction logic. 2025-12-19 13:58:04 +01:00
Julien Froidefond
a062f5573b Refactor Dockerfile to improve DATABASE_URL handling and enhance entrypoint script: Introduce ARG for DATABASE_URL during build, streamline migration commands, and add error handling for migration failures in the entrypoint script.
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 5m40s
2025-12-19 09:04:30 +01:00
Julien Froidefond
6e7c5d3eaf Update Dockerfile to include prisma.config.ts and streamline entrypoint script: Add copying of prisma.config.ts for Prisma 7 compatibility and remove unnecessary migration checks from the entrypoint script for improved clarity.
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 1m28s
2025-12-19 09:00:37 +01:00
Julien Froidefond
5dc178543e Update entrypoint script in Dockerfile and remove DATABASE_URL reference from schema.prisma: Modify entrypoint to check for prisma.config.ts instead of schema.prisma, and streamline migration command. Remove DATABASE_URL environment variable usage from schema.prisma for improved clarity.
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 1m9s
2025-12-19 08:58:48 +01:00
Julien Froidefond
881b8149e5 Refactor Dockerfile and schema.prisma for improved migration handling: Remove migration checks during build, enhance entrypoint script to validate DATABASE_URL and schema.prisma presence, and update schema.prisma to use environment variable for database URL.
Some checks failed
Deploy with Docker Compose / deploy (push) Failing after 14s
2025-12-19 08:47:06 +01:00
Julien Froidefond
d6a1e21e9f Update Docker configuration for Prisma migrations: Comment out migrations volume in docker-compose.yml for production use, and add checks in Dockerfile to verify the presence of migrations during build and entrypoint execution.
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 14s
2025-12-19 08:40:18 +01:00
Julien Froidefond
0b56d625ec Enhance HouseManagement and HousesPage components: Introduce invitation management features, including fetching and displaying pending invitations. Refactor data handling and UI updates for improved user experience and maintainability. Optimize state management with useCallback and useEffect for better performance.
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 2m43s
2025-12-18 09:16:13 +01:00
Julien Froidefond
f5dab3cb95 Refactor HousesPage and HouseManagement components: Introduce TypeScript types for house and invitation data structures to enhance type safety. Update data serialization logic for improved clarity and maintainability. Refactor UI components for better readability and consistency, including adjustments to conditional rendering and styling in HouseManagement. Optimize fetch logic in HousesSection with useCallback for performance improvements.
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 4m25s
2025-12-18 08:50:14 +01:00
Julien Froidefond
1b82bd9ee6 Implement house points system: Add houseJoinPoints, houseLeavePoints, and houseCreatePoints to SitePreferences model and update related services. Enhance house management features to award and deduct points for house creation, membership removal, and leaving a house. Update environment configuration for PostgreSQL and adjust UI components to reflect new functionalities.
Some checks failed
Deploy with Docker Compose / deploy (push) Has been cancelled
2025-12-18 08:48:31 +01:00
Julien Froidefond
12bc44e3ac Enhance UI consistency in House components: Replace SectionTitle with styled headings in HouseCard, HouseManagement, and HousesSection for improved visual hierarchy and readability. Update styles for better alignment and user experience.
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 2m37s
2025-12-18 08:15:34 +01:00
Julien Froidefond
4a415f79e0 Enhance UI consistency across multiple components: Update SectionTitle sizes and margins in StyleGuidePage, AdminPanel, ChallengesSection, HousesSection, LeaderboardSection, and ProfileForm. Add descriptive subtitles and additional text for improved user guidance. 2025-12-18 08:13:55 +01:00
Julien Froidefond
a62e61a314 Refactor event management and UI components: Update date handling in EventManagement to format dates for input, enhance EventsSection to display a message when no events are available, and improve styling in multiple components for better layout consistency.
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 3m36s
2025-12-18 08:08:39 +01:00
Julien Froidefond
91460930a4 Refactor Prisma logging and connection settings: Remove sensitive connection URL logging for security, and simplify logging configuration to only capture errors in the Prisma client setup.
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 11m29s
2025-12-17 15:16:04 +01:00
Julien Froidefond
fdedc1cf65 Update Dockerfile to enhance Prisma setup: Create migrations directory and copy schema.prisma along with migrations from builder, ensuring proper ownership for Next.js user. 2025-12-17 14:01:30 +01:00
Julien Froidefond
4fcf34c9aa Update background image positioning in multiple components: Change background image class from 'absolute' to 'fixed' in AdminPage, ChallengesSection, and BackgroundSection for improved layout consistency.
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 19m39s
2025-12-17 13:39:50 +01:00
Julien Froidefond
85ee812ab1 Add house leaderboard feature: Integrate house leaderboard functionality in LeaderboardPage and LeaderboardSection components. Update userStatsService to fetch house leaderboard data, and enhance UI to display house rankings, scores, and member details. Update Prisma schema to include house-related models and relationships, and seed database with initial house data.
Some checks failed
Deploy with Docker Compose / deploy (push) Has been cancelled
2025-12-17 13:35:18 +01:00
Julien Froidefond
cb02b494f4 Update environment configuration for PostgreSQL: Add new environment variables for NextAuth and PostgreSQL settings in .env file, update docker-compose.yml to utilize these variables, and enhance README documentation for environment setup. Ensure DATABASE_URL is constructed dynamically if not defined. 2025-12-17 13:15:40 +01:00
Julien Froidefond
2c7a346cde Remove scripts directory copy from Dockerfile to streamline build process and eliminate unnecessary files.
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 20s
2025-12-17 12:38:24 +01:00
Julien Froidefond
5875813f2f Refactor Docker configuration for PostgreSQL migration: Remove SQLite volume from docker-compose.yml, update Dockerfile to eliminate SQLite dependencies, and adjust README files to reflect PostgreSQL setup. Delete migration script and related documentation as part of the transition to PostgreSQL.
Some checks failed
Deploy with Docker Compose / deploy (push) Failing after 2m25s
2025-12-17 12:29:13 +01:00
Julien Froidefond
20c3043572 Update deploy workflow to include PostgreSQL data path: Add POSTGRES_DATA_PATH environment variable to the deployment configuration for improved database management.
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 13m19s
2025-12-17 11:55:57 +01:00
Julien Froidefond
8ad09ab9c8 Implement PostgreSQL support and update database configuration: Migrate from SQLite to PostgreSQL by updating the Prisma schema, Docker configuration, and environment variables. Add PostgreSQL dependencies and adjust the database connection logic in the application. Enhance .gitignore to exclude PostgreSQL-related files and directories.
Some checks failed
Deploy with Docker Compose / deploy (push) Has been cancelled
2025-12-17 11:41:32 +01:00
Julien Froidefond
1f59cc7f9d Enhance challenge management and badge components: Implement event dispatch for refreshing challenge badges after various actions (acceptance, rejection, deletion, cancellation) in ChallengeManagement and ChallengesSection. Update ChallengeBadge to listen for refresh events, ensuring accurate challenge count display and improved user experience.
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 10m9s
2025-12-17 10:44:56 +01:00
Julien Froidefond
67b3d9e2a9 Add API call optimization in EventsPageSection: Introduce a ref to prevent multiple API calls for event registrations, ensuring efficient data fetching. Update effect dependencies for improved performance and maintainability.
Some checks failed
Deploy with Docker Compose / deploy (push) Has been cancelled
2025-12-17 10:37:30 +01:00
Julien Froidefond
ba3b2c17b9 Add admin challenge acceptance functionality: Implement adminAcceptChallenge method in ChallengeService, allowing admins to accept pending challenges. Update ChallengeManagement component to include a button for accepting challenges, enhancing admin capabilities and user feedback handling.
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 4m48s
2025-12-17 08:14:58 +01:00
Julien Froidefond
7c0b3bc848 Add database migration command to entrypoint script: Include 'pnpm dlx prisma db push' to ensure database schema is updated during container startup, enhancing deployment reliability.
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 12s
2025-12-16 17:01:53 +01:00
124 changed files with 9184 additions and 492 deletions

30
.env
View File

@@ -5,8 +5,28 @@
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB. # Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings # See the documentation for all the connection string options: https://pris.ly/d/connection-strings
DATABASE_URL="file:./data/dev.db" # DATABASE_URL="file:./data/dev.db"
AUTH_SECRET="your-secret-key-change-this-in-production" # AUTH_SECRET="your-secret-key-change-this-in-production"
AUTH_URL="http://localhost:3000" # AUTH_URL="http://localhost:3000"
PRISMA_DATA_PATH="/Users/julien.froidefond/Sites/DAIS/public/got-gaming/data" # PRISMA_DATA_PATH="/Users/julien.froidefond/Sites/DAIS/public/got-gaming/data"
UPLOADS_PATH="/Users/julien.froidefond/Sites/DAIS/public/got-gaming/public/uploads" # UPLOADS_PATH="/Users/julien.froidefond/Sites/DAIS/public/got-gaming/public/uploads"
# NextAuth Configuration
NEXTAUTH_SECRET=change-this-secret-in-production
NEXTAUTH_URL=http://localhost:3000
# PostgreSQL Configuration
POSTGRES_USER=gotgaming
POSTGRES_PASSWORD=change-this-in-production
POSTGRES_DB=gotgaming
POSTGRES_HOST=localhost
POSTGRES_PORT=5433
# Database URL (construite automatiquement si non définie)
# Si vous définissez cette variable, elle sera utilisée telle quelle
# Sinon, elle sera construite à partir des variables POSTGRES_* ci-dessus
DATABASE_URL=postgresql://gotgaming:change-this-in-production@localhost:5433/gotgaming?schema=public
# Docker Volumes (optionnel)
POSTGRES_DATA_PATH=./data/postgres
UPLOADS_PATH=./public/uploads

19
.env.example Normal file
View File

@@ -0,0 +1,19 @@
# NextAuth Configuration
NEXTAUTH_SECRET=change-this-secret-in-production
NEXTAUTH_URL=http://localhost:3000
# PostgreSQL Configuration
POSTGRES_USER=gotgaming
POSTGRES_PASSWORD=change-this-in-production
POSTGRES_DB=gotgaming
POSTGRES_HOST=got-postgres
POSTGRES_PORT=5432
# Database URL (construite automatiquement si non définie)
# Si vous définissez cette variable, elle sera utilisée telle quelle
# Sinon, elle sera construite à partir des variables POSTGRES_* ci-dessus
# DATABASE_URL=postgresql://gotgaming:change-this-in-production@got-postgres:5432/gotgaming?schema=public
# Docker Volumes (optionnel)
POSTGRES_DATA_PATH=./data/postgres
UPLOADS_PATH=./public/uploads

View File

@@ -20,5 +20,7 @@ jobs:
NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }} NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }}
PRISMA_DATA_PATH: ${{ vars.PRISMA_DATA_PATH }} PRISMA_DATA_PATH: ${{ vars.PRISMA_DATA_PATH }}
UPLOADS_PATH: ${{ vars.UPLOADS_PATH }} UPLOADS_PATH: ${{ vars.UPLOADS_PATH }}
POSTGRES_DATA_PATH: ${{ vars.POSTGRES_DATA_PATH }}
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
run: | run: |
docker compose up -d --build docker compose up -d --build

7
.gitignore vendored
View File

@@ -25,6 +25,7 @@ yarn-debug.log*
yarn-error.log* yarn-error.log*
# local env files # local env files
.env
.env*.local .env*.local
# vercel # vercel
@@ -41,3 +42,9 @@ dev.db*
# prisma # prisma
/app/generated/prisma /app/generated/prisma
prisma/generated/
# database data
data/postgres/
data/*.db
data/*.db-journal

View File

@@ -19,7 +19,9 @@ RUN corepack enable && corepack prepare pnpm@latest --activate
COPY --from=deps /app/node_modules ./node_modules COPY --from=deps /app/node_modules ./node_modules
COPY . . COPY . .
ENV DATABASE_URL="file:/tmp/build.db" # ARG pour DATABASE_URL au build (valeur factice par défaut, car prisma generate n'a pas besoin de vraie DB)
ARG DATABASE_URL_BUILD="postgresql://user:pass@localhost:5432/db"
ENV DATABASE_URL=$DATABASE_URL_BUILD
RUN pnpm prisma generate RUN pnpm prisma generate
ENV NEXT_TELEMETRY_DISABLED=1 ENV NEXT_TELEMETRY_DISABLED=1
@@ -32,7 +34,7 @@ WORKDIR /app
ENV NODE_ENV=production ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1 ENV NEXT_TELEMETRY_DISABLED=1
RUN apk add --no-cache python3 make g++ sqlite RUN apk add --no-cache python3 make g++
RUN addgroup --system --gid 1001 nodejs && \ RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nextjs adduser --system --uid 1001 nextjs
@@ -45,28 +47,41 @@ COPY --from=builder --chown=nextjs:nodejs /app/public ./public
COPY --from=builder /app/package.json ./package.json COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/pnpm-lock.yaml ./pnpm-lock.yaml COPY --from=builder /app/pnpm-lock.yaml ./pnpm-lock.yaml
COPY --from=builder /app/next.config.js ./next.config.js COPY --from=builder /app/next.config.js ./next.config.js
COPY --from=builder /app/prisma ./prisma # Copier le répertoire prisma complet (schema + migrations)
COPY --from=builder /app/prisma.config.ts ./prisma.config.ts COPY --from=builder --chown=nextjs:nodejs /app/prisma ./prisma
# Copier prisma.config.ts (nécessaire pour Prisma 7)
ENV DATABASE_URL="file:/tmp/build.db" COPY --from=builder --chown=nextjs:nodejs /app/prisma.config.ts ./prisma.config.ts
# Installer seulement les dépendances de production puis générer Prisma Client # Installer seulement les dépendances de production puis générer Prisma Client
# ARG pour DATABASE_URL au build (valeur factice par défaut, car prisma generate n'a pas besoin de vraie DB)
# Au runtime, DATABASE_URL sera définie par docker-compose.yml (voir ligne 41)
ARG DATABASE_URL_BUILD="postgresql://user:pass@localhost:5432/db"
ENV DATABASE_URL=$DATABASE_URL_BUILD
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \ RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
pnpm install --frozen-lockfile --prod && \ pnpm install --frozen-lockfile --prod && \
pnpm dlx prisma generate pnpm dlx prisma generate
# Ne pas définir ENV DATABASE_URL ici - elle sera définie par docker-compose.yml au runtime
ENV DATABASE_URL="file:/app/data/dev.db" # Create uploads directories
RUN mkdir -p /app/public/uploads /app/public/uploads/backgrounds && \
# Create data directory for SQLite database and uploads directories chown -R nextjs:nodejs /app/public/uploads
RUN mkdir -p /app/data /app/public/uploads /app/public/uploads/backgrounds && \
chown -R nextjs:nodejs /app/data /app/public/uploads
RUN echo '#!/bin/sh' > /app/entrypoint.sh && \ RUN echo '#!/bin/sh' > /app/entrypoint.sh && \
echo 'set -e' >> /app/entrypoint.sh && \ echo 'set -e' >> /app/entrypoint.sh && \
echo 'mkdir -p /app/data' >> /app/entrypoint.sh && \
echo 'mkdir -p /app/public/uploads' >> /app/entrypoint.sh && \ echo 'mkdir -p /app/public/uploads' >> /app/entrypoint.sh && \
echo 'mkdir -p /app/public/uploads/backgrounds' >> /app/entrypoint.sh && \ echo 'mkdir -p /app/public/uploads/backgrounds' >> /app/entrypoint.sh && \
echo 'pnpm dlx prisma migrate deploy || true' >> /app/entrypoint.sh && \ echo 'if [ -z "$DATABASE_URL" ]; then' >> /app/entrypoint.sh && \
echo ' echo "ERROR: DATABASE_URL is not set"' >> /app/entrypoint.sh && \
echo ' exit 1' >> /app/entrypoint.sh && \
echo 'fi' >> /app/entrypoint.sh && \
echo 'export DATABASE_URL' >> /app/entrypoint.sh && \
echo 'cd /app' >> /app/entrypoint.sh && \
echo 'echo "Applying migrations..."' >> /app/entrypoint.sh && \
echo 'if ! pnpm dlx prisma migrate deploy; then' >> /app/entrypoint.sh && \
echo ' echo "Migration failed. Attempting to resolve failed migration..."' >> /app/entrypoint.sh && \
echo ' pnpm dlx prisma migrate resolve --applied 20251217101717_init_postgres 2>/dev/null || true' >> /app/entrypoint.sh && \
echo ' pnpm dlx prisma migrate deploy || echo "WARNING: Some migrations may need manual resolution"' >> /app/entrypoint.sh && \
echo 'fi' >> /app/entrypoint.sh && \
echo 'exec pnpm start' >> /app/entrypoint.sh && \ echo 'exec pnpm start' >> /app/entrypoint.sh && \
chmod +x /app/entrypoint.sh && \ chmod +x /app/entrypoint.sh && \
chown nextjs:nodejs /app/entrypoint.sh chown nextjs:nodejs /app/entrypoint.sh
@@ -76,6 +91,5 @@ USER nextjs
EXPOSE 3000 EXPOSE 3000
ENV PORT=3000 ENV PORT=3000
ENV HOSTNAME="0.0.0.0" ENV HOSTNAME="0.0.0.0"
ENV DATABASE_URL="file:/app/data/dev.db"
ENTRYPOINT ["./entrypoint.sh"] ENTRYPOINT ["./entrypoint.sh"]

View File

@@ -24,26 +24,48 @@ docker-compose logs -f
## Variables d'environnement ## Variables d'environnement
Créez un fichier `.env` à la racine du projet avec les variables suivantes : Créez un fichier `.env` à la racine du projet à partir du template `.env.example` :
```bash
cp .env.example .env
```
Puis modifiez les valeurs dans `.env` selon votre configuration :
```env ```env
# NextAuth Configuration
NEXTAUTH_SECRET=your-secret-key-here NEXTAUTH_SECRET=your-secret-key-here
NEXTAUTH_URL=http://localhost:3000 NEXTAUTH_URL=http://localhost:3000
DATABASE_URL=file:./prisma/dev.db
# PostgreSQL Configuration
POSTGRES_USER=gotgaming
POSTGRES_PASSWORD=change-this-in-production
POSTGRES_DB=gotgaming
# Database URL (optionnel - construite automatiquement si non définie)
# DATABASE_URL=postgresql://gotgaming:change-this-in-production@got-postgres:5432/gotgaming?schema=public
# Docker Volumes (optionnel)
POSTGRES_DATA_PATH=./data/postgres
UPLOADS_PATH=./public/uploads
``` ```
**Important** :
- Le fichier `.env` est ignoré par Git (ne pas commiter vos secrets)
- Si vous changez `POSTGRES_PASSWORD` après la première initialisation, vous devrez soit réinitialiser la base, soit changer le mot de passe manuellement dans PostgreSQL
## Volumes persistants ## Volumes persistants
### Base de données ### Base de données PostgreSQL
La base de données SQLite est persistée via un volume Docker. Par défaut, elle est stockée dans `/Volumes/EXTERNAL_USB/sites/got-gaming/data`, mais vous pouvez la personnaliser avec la variable d'environnement `PRISMA_DATA_PATH`. La base de données PostgreSQL est persistée via un volume Docker. Par défaut, elle est stockée dans `./data/postgres`, mais vous pouvez la personnaliser avec la variable d'environnement `POSTGRES_DATA_PATH`.
Les migrations Prisma sont appliquées automatiquement au démarrage du conteneur. Les migrations Prisma sont appliquées automatiquement au démarrage du conteneur.
Pour appliquer manuellement les migrations : Pour appliquer manuellement les migrations :
```bash ```bash
docker-compose exec got-app node node_modules/.bin/prisma migrate deploy docker-compose exec got-app pnpm dlx prisma migrate deploy
``` ```
### Images uploadées ### Images uploadées

View File

@@ -41,5 +41,5 @@ pnpm start
- React 18 - React 18
- TypeScript - TypeScript
- Tailwind CSS - Tailwind CSS
- Prisma (SQLite) - Prisma (PostgreSQL)
- NextAuth.js - NextAuth.js

View File

@@ -234,3 +234,37 @@ export async function reactivateChallenge(challengeId: string) {
}; };
} }
} }
export async function adminAcceptChallenge(challengeId: string) {
try {
await checkAdminAccess();
const challenge = await challengeService.adminAcceptChallenge(challengeId);
revalidatePath("/admin");
revalidatePath("/challenges");
return {
success: true,
message: "Défi accepté avec succès",
data: challenge,
};
} catch (error) {
console.error("Admin accept challenge error:", error);
if (error instanceof ValidationError) {
return { success: false, error: error.message };
}
if (error instanceof NotFoundError) {
return { success: false, error: error.message };
}
if (error instanceof Error && error.message.includes("Accès refusé")) {
return { success: false, error: error.message };
}
return {
success: false,
error: "Une erreur est survenue lors de l'acceptation du défi",
};
}
}

139
actions/admin/houses.ts Normal file
View File

@@ -0,0 +1,139 @@
"use server";
import { revalidatePath } from "next/cache";
import { auth } from "@/lib/auth";
import { houseService } from "@/services/houses/house.service";
import { Role } from "@/prisma/generated/prisma/client";
import {
ValidationError,
NotFoundError,
ConflictError,
ForbiddenError,
} from "@/services/errors";
function checkAdminAccess() {
return async () => {
const session = await auth();
if (!session?.user || session.user.role !== Role.ADMIN) {
throw new Error("Accès refusé");
}
return session;
};
}
export async function updateHouse(
houseId: string,
data: {
name?: string;
description?: string | null;
}
) {
try {
await checkAdminAccess()();
// L'admin peut modifier n'importe quelle maison sans vérifier les permissions normales
// On utilise directement le service mais on bypass les vérifications de propriétaire/admin
const house = await houseService.getHouseById(houseId);
if (!house) {
return { success: false, error: "Maison non trouvée" };
}
// Utiliser le service avec le creatorId pour bypass les vérifications
const updatedHouse = await houseService.updateHouse(
houseId,
house.creatorId, // Utiliser le creatorId pour bypass
data
);
revalidatePath("/admin");
revalidatePath("/houses");
return { success: true, data: updatedHouse };
} catch (error) {
console.error("Error updating house:", error);
if (error instanceof ValidationError) {
return { success: false, error: error.message };
}
if (error instanceof ConflictError) {
return { success: false, error: error.message };
}
if (error instanceof Error && error.message === "Accès refusé") {
return { success: false, error: "Accès refusé" };
}
return {
success: false,
error: "Erreur lors de la mise à jour de la maison",
};
}
}
export async function deleteHouse(houseId: string) {
try {
await checkAdminAccess()();
const house = await houseService.getHouseById(houseId);
if (!house) {
return { success: false, error: "Maison non trouvée" };
}
// L'admin peut supprimer n'importe quelle maison
// On utilise le creatorId pour bypass les vérifications
await houseService.deleteHouse(houseId, house.creatorId);
revalidatePath("/admin");
revalidatePath("/houses");
return { success: true };
} catch (error) {
console.error("Error deleting house:", error);
if (error instanceof NotFoundError) {
return { success: false, error: error.message };
}
if (error instanceof ForbiddenError) {
return { success: false, error: error.message };
}
if (error instanceof Error && error.message === "Accès refusé") {
return { success: false, error: "Accès refusé" };
}
return {
success: false,
error: "Erreur lors de la suppression de la maison",
};
}
}
export async function removeMember(houseId: string, memberId: string) {
try {
await checkAdminAccess()();
// L'admin peut retirer n'importe quel membre (sauf le propriétaire)
await houseService.removeMemberAsAdmin(houseId, memberId);
revalidatePath("/admin");
revalidatePath("/houses");
return { success: true, message: "Membre retiré de la maison" };
} catch (error) {
console.error("Error removing member:", error);
if (error instanceof NotFoundError) {
return { success: false, error: error.message };
}
if (error instanceof ForbiddenError) {
return { success: false, error: error.message };
}
if (error instanceof Error && error.message === "Accès refusé") {
return { success: false, error: "Accès refusé" };
}
return {
success: false,
error: "Erreur lors du retrait du membre",
};
}
}

View File

@@ -20,8 +20,13 @@ export async function updateSitePreferences(data: {
eventsBackground?: string | null; eventsBackground?: string | null;
leaderboardBackground?: string | null; leaderboardBackground?: string | null;
challengesBackground?: string | null; challengesBackground?: string | null;
profileBackground?: string | null;
houseBackground?: string | null;
eventRegistrationPoints?: number; eventRegistrationPoints?: number;
eventFeedbackPoints?: number; eventFeedbackPoints?: number;
houseJoinPoints?: number;
houseLeavePoints?: number;
houseCreatePoints?: number;
}) { }) {
try { try {
await checkAdminAccess()(); await checkAdminAccess()();
@@ -31,8 +36,13 @@ export async function updateSitePreferences(data: {
eventsBackground: data.eventsBackground, eventsBackground: data.eventsBackground,
leaderboardBackground: data.leaderboardBackground, leaderboardBackground: data.leaderboardBackground,
challengesBackground: data.challengesBackground, challengesBackground: data.challengesBackground,
profileBackground: data.profileBackground,
houseBackground: data.houseBackground,
eventRegistrationPoints: data.eventRegistrationPoints, eventRegistrationPoints: data.eventRegistrationPoints,
eventFeedbackPoints: data.eventFeedbackPoints, eventFeedbackPoints: data.eventFeedbackPoints,
houseJoinPoints: data.houseJoinPoints,
houseLeavePoints: data.houseLeavePoints,
houseCreatePoints: data.houseCreatePoints,
}); });
revalidatePath("/admin"); revalidatePath("/admin");
@@ -40,6 +50,8 @@ export async function updateSitePreferences(data: {
revalidatePath("/events"); revalidatePath("/events");
revalidatePath("/leaderboard"); revalidatePath("/leaderboard");
revalidatePath("/challenges"); revalidatePath("/challenges");
revalidatePath("/profile");
revalidatePath("/houses");
return { success: true, data: preferences }; return { success: true, data: preferences };
} catch (error) { } catch (error) {

View File

@@ -127,3 +127,5 @@ export async function cancelChallenge(challengeId: string) {
}; };
} }
} }

48
actions/houses/create.ts Normal file
View File

@@ -0,0 +1,48 @@
"use server";
import { revalidatePath } from "next/cache";
import { auth } from "@/lib/auth";
import { houseService } from "@/services/houses/house.service";
import {
ValidationError,
ConflictError,
} from "@/services/errors";
export async function createHouse(data: {
name: string;
description?: string | null;
}) {
try {
const session = await auth();
if (!session?.user?.id) {
return {
success: false,
error: "Vous devez être connecté pour créer une maison",
};
}
const house = await houseService.createHouse({
name: data.name,
description: data.description,
creatorId: session.user.id,
});
revalidatePath("/houses");
revalidatePath("/profile");
return { success: true, message: "Maison créée avec succès", data: house };
} catch (error) {
console.error("Create house error:", error);
if (error instanceof ValidationError || error instanceof ConflictError) {
return { success: false, error: error.message };
}
return {
success: false,
error: "Une erreur est survenue lors de la création de la maison",
};
}
}

View File

@@ -0,0 +1,173 @@
"use server";
import { revalidatePath } from "next/cache";
import { auth } from "@/lib/auth";
import { houseService } from "@/services/houses/house.service";
import {
ValidationError,
ConflictError,
ForbiddenError,
NotFoundError,
} from "@/services/errors";
export async function inviteUser(houseId: string, inviteeId: string) {
try {
const session = await auth();
if (!session?.user?.id) {
return {
success: false,
error: "Vous devez être connecté",
};
}
const invitation = await houseService.inviteUser({
houseId,
inviterId: session.user.id,
inviteeId,
});
revalidatePath("/houses");
revalidatePath(`/houses/${houseId}`);
return {
success: true,
message: "Invitation envoyée",
data: invitation,
};
} catch (error) {
console.error("Invite user error:", error);
if (
error instanceof ValidationError ||
error instanceof ConflictError ||
error instanceof ForbiddenError
) {
return { success: false, error: error.message };
}
return {
success: false,
error: "Une erreur est survenue lors de l'envoi de l'invitation",
};
}
}
export async function acceptInvitation(invitationId: string) {
try {
const session = await auth();
if (!session?.user?.id) {
return {
success: false,
error: "Vous devez être connecté",
};
}
const membership = await houseService.acceptInvitation(
invitationId,
session.user.id
);
revalidatePath("/houses");
revalidatePath("/profile");
revalidatePath("/invitations");
return {
success: true,
message: "Invitation acceptée",
data: membership,
};
} catch (error) {
console.error("Accept invitation error:", error);
if (
error instanceof ValidationError ||
error instanceof ConflictError ||
error instanceof ForbiddenError ||
error instanceof NotFoundError
) {
return { success: false, error: error.message };
}
return {
success: false,
error: "Une erreur est survenue lors de l'acceptation de l'invitation",
};
}
}
export async function rejectInvitation(invitationId: string) {
try {
const session = await auth();
if (!session?.user?.id) {
return {
success: false,
error: "Vous devez être connecté",
};
}
await houseService.rejectInvitation(invitationId, session.user.id);
revalidatePath("/houses");
revalidatePath("/invitations");
return { success: true, message: "Invitation refusée" };
} catch (error) {
console.error("Reject invitation error:", error);
if (
error instanceof ConflictError ||
error instanceof ForbiddenError ||
error instanceof NotFoundError
) {
return { success: false, error: error.message };
}
return {
success: false,
error: "Une erreur est survenue lors du refus de l'invitation",
};
}
}
export async function cancelInvitation(invitationId: string) {
try {
const session = await auth();
if (!session?.user?.id) {
return {
success: false,
error: "Vous devez être connecté",
};
}
// Récupérer l'invitation pour obtenir le houseId avant de l'annuler
const invitation = await houseService.getInvitationById(invitationId);
await houseService.cancelInvitation(invitationId, session.user.id);
revalidatePath("/houses");
if (invitation?.houseId) {
revalidatePath(`/houses/${invitation.houseId}`);
}
return { success: true, message: "Invitation annulée" };
} catch (error) {
console.error("Cancel invitation error:", error);
if (
error instanceof ConflictError ||
error instanceof ForbiddenError ||
error instanceof NotFoundError
) {
return { success: false, error: error.message };
}
return {
success: false,
error: "Une erreur est survenue lors de l'annulation de l'invitation",
};
}
}

163
actions/houses/requests.ts Normal file
View File

@@ -0,0 +1,163 @@
"use server";
import { revalidatePath } from "next/cache";
import { auth } from "@/lib/auth";
import { houseService } from "@/services/houses/house.service";
import {
ValidationError,
ConflictError,
ForbiddenError,
NotFoundError,
} from "@/services/errors";
export async function requestToJoin(houseId: string) {
try {
const session = await auth();
if (!session?.user?.id) {
return {
success: false,
error: "Vous devez être connecté",
};
}
const request = await houseService.requestToJoin({
houseId,
requesterId: session.user.id,
});
revalidatePath("/houses");
revalidatePath(`/houses/${houseId}`);
return {
success: true,
message: "Demande envoyée",
data: request,
};
} catch (error) {
console.error("Request to join error:", error);
if (
error instanceof ValidationError ||
error instanceof ConflictError
) {
return { success: false, error: error.message };
}
return {
success: false,
error: "Une erreur est survenue lors de l'envoi de la demande",
};
}
}
export async function acceptRequest(requestId: string) {
try {
const session = await auth();
if (!session?.user?.id) {
return {
success: false,
error: "Vous devez être connecté",
};
}
const membership = await houseService.acceptRequest(
requestId,
session.user.id
);
revalidatePath("/houses");
revalidatePath("/profile");
return {
success: true,
message: "Demande acceptée",
data: membership,
};
} catch (error) {
console.error("Accept request error:", error);
if (
error instanceof ConflictError ||
error instanceof ForbiddenError ||
error instanceof NotFoundError
) {
return { success: false, error: error.message };
}
return {
success: false,
error: "Une erreur est survenue lors de l'acceptation de la demande",
};
}
}
export async function rejectRequest(requestId: string) {
try {
const session = await auth();
if (!session?.user?.id) {
return {
success: false,
error: "Vous devez être connecté",
};
}
await houseService.rejectRequest(requestId, session.user.id);
revalidatePath("/houses");
return { success: true, message: "Demande refusée" };
} catch (error) {
console.error("Reject request error:", error);
if (
error instanceof ConflictError ||
error instanceof ForbiddenError ||
error instanceof NotFoundError
) {
return { success: false, error: error.message };
}
return {
success: false,
error: "Une erreur est survenue lors du refus de la demande",
};
}
}
export async function cancelRequest(requestId: string) {
try {
const session = await auth();
if (!session?.user?.id) {
return {
success: false,
error: "Vous devez être connecté",
};
}
await houseService.cancelRequest(requestId, session.user.id);
revalidatePath("/houses");
return { success: true, message: "Demande annulée" };
} catch (error) {
console.error("Cancel request error:", error);
if (
error instanceof ConflictError ||
error instanceof ForbiddenError ||
error instanceof NotFoundError
) {
return { success: false, error: error.message };
}
return {
success: false,
error: "Une erreur est survenue lors de l'annulation de la demande",
};
}
}

149
actions/houses/update.ts Normal file
View File

@@ -0,0 +1,149 @@
"use server";
import { revalidatePath } from "next/cache";
import { auth } from "@/lib/auth";
import { houseService } from "@/services/houses/house.service";
import {
ValidationError,
ConflictError,
ForbiddenError,
NotFoundError,
} from "@/services/errors";
export async function updateHouse(
houseId: string,
data: {
name?: string;
description?: string | null;
}
) {
try {
const session = await auth();
if (!session?.user?.id) {
return {
success: false,
error: "Vous devez être connecté",
};
}
const house = await houseService.updateHouse(houseId, session.user.id, data);
revalidatePath("/houses");
revalidatePath(`/houses/${houseId}`);
return { success: true, message: "Maison mise à jour", data: house };
} catch (error) {
console.error("Update house error:", error);
if (
error instanceof ValidationError ||
error instanceof ConflictError ||
error instanceof ForbiddenError
) {
return { success: false, error: error.message };
}
return {
success: false,
error: "Une erreur est survenue lors de la mise à jour de la maison",
};
}
}
export async function deleteHouse(houseId: string) {
try {
const session = await auth();
if (!session?.user?.id) {
return {
success: false,
error: "Vous devez être connecté",
};
}
await houseService.deleteHouse(houseId, session.user.id);
revalidatePath("/houses");
revalidatePath("/profile");
return { success: true, message: "Maison supprimée" };
} catch (error) {
console.error("Delete house error:", error);
if (error instanceof ForbiddenError) {
return { success: false, error: error.message };
}
return {
success: false,
error: "Une erreur est survenue lors de la suppression de la maison",
};
}
}
export async function leaveHouse(houseId: string) {
try {
const session = await auth();
if (!session?.user?.id) {
return {
success: false,
error: "Vous devez être connecté",
};
}
await houseService.leaveHouse(houseId, session.user.id);
revalidatePath("/houses");
revalidatePath("/profile");
return { success: true, message: "Vous avez quitté la maison" };
} catch (error) {
console.error("Leave house error:", error);
if (error instanceof ForbiddenError) {
return { success: false, error: error.message };
}
return {
success: false,
error: "Une erreur est survenue lors de la sortie de la maison",
};
}
}
export async function removeMember(houseId: string, memberId: string) {
try {
const session = await auth();
if (!session?.user?.id) {
return {
success: false,
error: "Vous devez être connecté",
};
}
await houseService.removeMember(houseId, memberId, session.user.id);
revalidatePath("/houses");
revalidatePath("/profile");
return { success: true, message: "Membre retiré de la maison" };
} catch (error) {
console.error("Remove member error:", error);
if (
error instanceof ForbiddenError ||
error instanceof NotFoundError
) {
return { success: false, error: error.message };
}
return {
success: false,
error: "Une erreur est survenue lors du retrait du membre",
};
}
}

View File

@@ -0,0 +1,26 @@
import ChallengeManagement from "@/components/admin/ChallengeManagement";
import { Card } from "@/components/ui";
import { challengeService } from "@/services/challenges/challenge.service";
export const dynamic = "force-dynamic";
export default async function AdminChallengesPage() {
const challenges = await challengeService.getAllChallenges();
// Sérialiser les dates pour le client
const serializedChallenges = challenges.map((challenge) => ({
...challenge,
createdAt: challenge.createdAt.toISOString(),
acceptedAt: challenge.acceptedAt?.toISOString() ?? null,
completedAt: challenge.completedAt?.toISOString() ?? null,
}));
return (
<Card variant="dark" className="p-6">
<h2 className="text-2xl font-gaming font-bold mb-6 text-pixel-gold">
Gestion des Défis
</h2>
<ChallengeManagement initialChallenges={serializedChallenges} />
</Card>
);
}

34
app/admin/events/page.tsx Normal file
View File

@@ -0,0 +1,34 @@
import EventManagement from "@/components/admin/EventManagement";
import { Card } from "@/components/ui";
import { eventService } from "@/services/events/event.service";
export const dynamic = "force-dynamic";
export default async function AdminEventsPage() {
const events = await eventService.getEventsWithStatus();
// Transformer les données pour la sérialisation
const serializedEvents = events.map((event) => ({
id: event.id,
date: event.date.toISOString(),
name: event.name,
description: event.description,
type: event.type,
status: event.status,
room: event.room,
time: event.time,
maxPlaces: event.maxPlaces,
createdAt: event.createdAt.toISOString(),
updatedAt: event.updatedAt.toISOString(),
registrationsCount: event.registrationsCount,
}));
return (
<Card variant="dark" className="p-6">
<h2 className="text-2xl font-gaming font-bold mb-6 text-pixel-gold">
Gestion des Événements
</h2>
<EventManagement initialEvents={serializedEvents} />
</Card>
);
}

View File

@@ -0,0 +1,77 @@
import FeedbackManagement from "@/components/admin/FeedbackManagement";
import { Card } from "@/components/ui";
import { eventFeedbackService } from "@/services/events/event-feedback.service";
export const dynamic = "force-dynamic";
export default async function AdminFeedbacksPage() {
const [feedbacksRaw, statistics] = await Promise.all([
eventFeedbackService.getAllFeedbacks(),
eventFeedbackService.getFeedbackStatistics(),
]);
// Type assertion car getAllFeedbacks inclut event et user par défaut
const feedbacks = feedbacksRaw as unknown as Array<{
id: string;
rating: number;
comment: string | null;
isRead: boolean;
createdAt: Date;
event: {
id: string;
name: string;
date: Date;
type: string;
};
user: {
id: string;
username: string;
email: string;
avatar: string | null;
score: number;
};
}>;
// Sérialiser les dates pour le client
const serializedFeedbacks = feedbacks.map((feedback) => ({
id: feedback.id,
rating: feedback.rating,
comment: feedback.comment,
isRead: feedback.isRead,
createdAt: feedback.createdAt.toISOString(),
event: {
id: feedback.event.id,
name: feedback.event.name,
date: feedback.event.date.toISOString(),
type: feedback.event.type,
},
user: {
id: feedback.user.id,
username: feedback.user.username,
email: feedback.user.email,
avatar: feedback.user.avatar,
score: feedback.user.score,
},
}));
const serializedStatistics = statistics.map((stat) => ({
eventId: stat.eventId,
eventName: stat.eventName,
eventDate: stat.eventDate?.toISOString() ?? null,
eventType: stat.eventType,
averageRating: stat.averageRating,
feedbackCount: stat.feedbackCount,
}));
return (
<Card variant="dark" className="p-6">
<h2 className="text-2xl font-gaming font-bold mb-6 text-pixel-gold">
Gestion des Feedbacks
</h2>
<FeedbackManagement
initialFeedbacks={serializedFeedbacks}
initialStatistics={serializedStatistics}
/>
</Card>
);
}

90
app/admin/houses/page.tsx Normal file
View File

@@ -0,0 +1,90 @@
import HouseManagement from "@/components/admin/HouseManagement";
import { Card } from "@/components/ui";
import { houseService } from "@/services/houses/house.service";
import { Prisma } from "@/prisma/generated/prisma/client";
export const dynamic = "force-dynamic";
export default async function AdminHousesPage() {
type HouseWithIncludes = Prisma.HouseGetPayload<{
include: {
creator: {
select: {
id: true;
username: true;
avatar: true;
};
};
memberships: {
include: {
user: {
select: {
id: true;
username: true;
avatar: true;
score: true;
level: true;
};
};
};
};
};
}>;
const houses = (await houseService.getAllHouses({
include: {
creator: {
select: {
id: true,
username: true,
avatar: true,
},
},
memberships: {
include: {
user: {
select: {
id: true,
username: true,
avatar: true,
score: true,
level: true,
},
},
},
orderBy: [{ role: "asc" }, { joinedAt: "asc" }],
},
},
orderBy: {
createdAt: "desc",
},
})) as unknown as HouseWithIncludes[];
// Transformer les données pour la sérialisation
const serializedHouses = houses.map((house) => ({
id: house.id,
name: house.name,
description: house.description,
creatorId: house.creatorId,
creator: house.creator,
createdAt: house.createdAt.toISOString(),
updatedAt: house.updatedAt.toISOString(),
membersCount: house.memberships?.length || 0,
memberships:
house.memberships?.map((membership) => ({
id: membership.id,
role: membership.role,
joinedAt: membership.joinedAt.toISOString(),
user: membership.user,
})) || [],
}));
return (
<Card variant="dark" className="p-6">
<h2 className="text-2xl font-gaming font-bold mb-6 text-pixel-gold">
Gestion des Maisons
</h2>
<HouseManagement initialHouses={serializedHouses} />
</Card>
);
}

52
app/admin/layout.tsx Normal file
View File

@@ -0,0 +1,52 @@
import { redirect } from "next/navigation";
import { auth } from "@/lib/auth";
import { Role } from "@/prisma/generated/prisma/client";
import NavigationWrapper from "@/components/navigation/NavigationWrapper";
import AdminNavigation from "@/components/admin/AdminNavigation";
import { SectionTitle } from "@/components/ui";
export const dynamic = "force-dynamic";
export default async function AdminLayout({
children,
}: {
children: React.ReactNode;
}) {
const session = await auth();
if (!session?.user) {
redirect("/login");
}
if (session.user.role !== Role.ADMIN) {
redirect("/");
}
return (
<main className="min-h-screen bg-black relative">
{/* Background Image */}
<div
className="fixed inset-0 bg-cover bg-center bg-no-repeat"
style={{
backgroundImage: `url('/got-light.jpg')`,
}}
>
{/* Dark overlay for readability */}
<div className="absolute inset-0 bg-gradient-to-b from-black/70 via-black/60 to-black/80"></div>
</div>
<NavigationWrapper />
<section className="relative w-full min-h-screen flex flex-col items-center overflow-hidden pt-24 pb-16">
<div className="relative z-10 w-full max-w-6xl mx-auto px-8 py-16">
<SectionTitle variant="gradient" size="md" className="mb-16 text-center">
ADMIN
</SectionTitle>
<AdminNavigation />
{children}
</div>
</section>
</main>
);
}

View File

@@ -1,41 +1,7 @@
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { auth } from "@/lib/auth";
import { sitePreferencesService } from "@/services/preferences/site-preferences.service";
import { Role } from "@/prisma/generated/prisma/client";
import NavigationWrapper from "@/components/navigation/NavigationWrapper";
import AdminPanel from "@/components/admin/AdminPanel";
export const dynamic = "force-dynamic"; export const dynamic = "force-dynamic";
export default async function AdminPage() { export default async function AdminPage() {
const session = await auth(); redirect("/admin/preferences");
if (!session?.user) {
redirect("/login");
}
if (session.user.role !== Role.ADMIN) {
redirect("/");
}
// Récupérer les préférences globales du site (ou créer si elles n'existent pas)
const sitePreferences =
await sitePreferencesService.getOrCreateSitePreferences();
return (
<main className="min-h-screen bg-black relative">
{/* Background Image */}
<div
className="absolute inset-0 bg-cover bg-center bg-no-repeat"
style={{
backgroundImage: `url('/got-light.jpg')`,
}}
>
{/* Dark overlay for readability */}
<div className="absolute inset-0 bg-gradient-to-b from-black/70 via-black/60 to-black/80"></div>
</div>
<NavigationWrapper />
<AdminPanel initialPreferences={sitePreferences} />
</main>
);
} }

View File

@@ -0,0 +1,30 @@
import { sitePreferencesService } from "@/services/preferences/site-preferences.service";
import BackgroundPreferences from "@/components/admin/BackgroundPreferences";
import EventPointsPreferences from "@/components/admin/EventPointsPreferences";
import EventFeedbackPointsPreferences from "@/components/admin/EventFeedbackPointsPreferences";
import HousePointsPreferences from "@/components/admin/HousePointsPreferences";
import { Card } from "@/components/ui";
export const dynamic = "force-dynamic";
export default async function AdminPreferencesPage() {
const sitePreferences =
await sitePreferencesService.getOrCreateSitePreferences();
return (
<Card variant="dark" className="p-4 sm:p-6">
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6">
<h2 className="text-xl sm:text-2xl font-gaming font-bold text-pixel-gold break-words">
Préférences UI Globales
</h2>
</div>
<div className="space-y-4">
<BackgroundPreferences initialPreferences={sitePreferences} />
<EventPointsPreferences initialPreferences={sitePreferences} />
<EventFeedbackPointsPreferences initialPreferences={sitePreferences} />
<HousePointsPreferences initialPreferences={sitePreferences} />
</div>
</Card>
);
}

42
app/admin/users/page.tsx Normal file
View File

@@ -0,0 +1,42 @@
import UserManagement from "@/components/admin/UserManagement";
import { Card } from "@/components/ui";
import { userService } from "@/services/users/user.service";
export const dynamic = "force-dynamic";
export default async function AdminUsersPage() {
const users = await userService.getAllUsers({
orderBy: {
score: "desc",
},
select: {
id: true,
username: true,
email: true,
role: true,
score: true,
level: true,
hp: true,
maxHp: true,
xp: true,
maxXp: true,
avatar: true,
createdAt: true,
},
});
// Sérialiser les dates pour le client
const serializedUsers = users.map((user) => ({
...user,
createdAt: user.createdAt.toISOString(),
}));
return (
<Card variant="dark" className="p-6">
<h2 className="text-2xl font-gaming font-bold mb-6 text-pixel-gold">
Gestion des Utilisateurs
</h2>
<UserManagement initialUsers={serializedUsers} />
</Card>
);
}

View File

@@ -0,0 +1,99 @@
import { NextResponse } from "next/server";
import { auth } from "@/lib/auth";
import { houseService } from "@/services/houses/house.service";
import { Role, Prisma } from "@/prisma/generated/prisma/client";
export async function GET() {
try {
const session = await auth();
if (!session?.user || session.user.role !== Role.ADMIN) {
return NextResponse.json({ error: "Accès refusé" }, { status: 403 });
}
// Récupérer toutes les maisons avec leurs membres
type HouseWithIncludes = Prisma.HouseGetPayload<{
include: {
creator: {
select: {
id: true;
username: true;
avatar: true;
};
};
memberships: {
include: {
user: {
select: {
id: true;
username: true;
avatar: true;
score: true;
level: true;
};
};
};
};
};
}>;
const houses = (await houseService.getAllHouses({
include: {
creator: {
select: {
id: true,
username: true,
avatar: true,
},
},
memberships: {
include: {
user: {
select: {
id: true,
username: true,
avatar: true,
score: true,
level: true,
},
},
},
orderBy: [
{ role: "asc" }, // OWNER, ADMIN, MEMBER
{ joinedAt: "asc" },
],
},
},
orderBy: {
createdAt: "desc",
},
})) as unknown as HouseWithIncludes[];
// Transformer les données pour la sérialisation
const housesWithData = houses.map((house) => ({
id: house.id,
name: house.name,
description: house.description,
creatorId: house.creatorId,
creator: house.creator,
createdAt: house.createdAt.toISOString(),
updatedAt: house.updatedAt.toISOString(),
membersCount: house.memberships?.length || 0,
memberships:
house.memberships?.map((membership) => ({
id: membership.id,
role: membership.role,
joinedAt: membership.joinedAt.toISOString(),
user: membership.user,
})) || [],
}));
return NextResponse.json(housesWithData);
} catch (error) {
console.error("Error fetching houses:", error);
return NextResponse.json(
{ error: "Erreur lors de la récupération des maisons" },
{ status: 500 }
);
}
}

View File

@@ -27,3 +27,5 @@ export async function GET() {
); );
} }
} }

View File

@@ -0,0 +1,51 @@
import { NextResponse } from "next/server";
import { auth } from "@/lib/auth";
import { houseService } from "@/services/houses/house.service";
export async function GET(
request: Request,
{ params }: { params: Promise<{ houseId: string }> }
) {
try {
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json(
{ error: "Vous devez être connecté" },
{ status: 401 }
);
}
const { houseId } = await params;
// Vérifier que l'utilisateur est membre de la maison
const isMember = await houseService.isUserMemberOfHouse(
session.user.id,
houseId
);
if (!isMember) {
return NextResponse.json(
{ error: "Vous devez être membre de cette maison" },
{ status: 403 }
);
}
const { searchParams } = new URL(request.url);
const status = searchParams.get("status") as "PENDING" | "ACCEPTED" | "REJECTED" | "CANCELLED" | null;
const invitations = await houseService.getHouseInvitations(
houseId,
status || undefined
);
return NextResponse.json(invitations);
} catch (error) {
console.error("Error fetching house invitations:", error);
return NextResponse.json(
{ error: "Erreur lors de la récupération des invitations" },
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,48 @@
import { NextResponse } from "next/server";
import { auth } from "@/lib/auth";
import { houseService } from "@/services/houses/house.service";
export async function GET(
request: Request,
{ params }: { params: Promise<{ houseId: string }> }
) {
try {
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json(
{ error: "Vous devez être connecté" },
{ status: 401 }
);
}
const { houseId } = await params;
// Vérifier que l'utilisateur est propriétaire ou admin
const isAuthorized = await houseService.isUserOwnerOrAdmin(
session.user.id,
houseId
);
if (!isAuthorized) {
return NextResponse.json(
{ error: "Vous n'avez pas les permissions pour voir les demandes" },
{ status: 403 }
);
}
const { searchParams } = new URL(request.url);
const status = searchParams.get("status") as "PENDING" | "ACCEPTED" | "REJECTED" | "CANCELLED" | null;
const requests = await houseService.getHouseRequests(houseId, status || undefined);
return NextResponse.json(requests);
} catch (error) {
console.error("Error fetching house requests:", error);
return NextResponse.json(
{ error: "Erreur lors de la récupération des demandes" },
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,59 @@
import { NextResponse } from "next/server";
import { auth } from "@/lib/auth";
import { houseService } from "@/services/houses/house.service";
export async function GET(
request: Request,
{ params }: { params: Promise<{ houseId: string }> }
) {
try {
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json(
{ error: "Vous devez être connecté" },
{ status: 401 }
);
}
const { houseId } = await params;
const house = await houseService.getHouseById(houseId, {
memberships: {
include: {
user: {
select: {
id: true,
username: true,
avatar: true,
score: true,
level: true,
},
},
},
},
creator: {
select: {
id: true,
username: true,
avatar: true,
},
},
});
if (!house) {
return NextResponse.json(
{ error: "Maison non trouvée" },
{ status: 404 }
);
}
return NextResponse.json(house);
} catch (error) {
console.error("Error fetching house:", error);
return NextResponse.json(
{ error: "Erreur lors de la récupération de la maison" },
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,48 @@
import { NextResponse } from "next/server";
import { auth } from "@/lib/auth";
import { houseService } from "@/services/houses/house.service";
export async function GET() {
try {
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json(
{ error: "Vous devez être connecté" },
{ status: 401 }
);
}
const house = await houseService.getUserHouse(session.user.id, {
memberships: {
include: {
user: {
select: {
id: true,
username: true,
avatar: true,
score: true,
level: true,
},
},
},
},
creator: {
select: {
id: true,
username: true,
avatar: true,
},
},
});
return NextResponse.json(house);
} catch (error) {
console.error("Error fetching user house:", error);
return NextResponse.json(
{ error: "Erreur lors de la récupération de votre maison" },
{ status: 500 }
);
}
}

87
app/api/houses/route.ts Normal file
View File

@@ -0,0 +1,87 @@
import { NextResponse } from "next/server";
import { auth } from "@/lib/auth";
import { houseService } from "@/services/houses/house.service";
export async function GET(request: Request) {
try {
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json(
{ error: "Vous devez être connecté" },
{ status: 401 }
);
}
const { searchParams } = new URL(request.url);
const search = searchParams.get("search");
const include = searchParams.get("include")?.split(",") || [];
const includeOptions: {
memberships?: {
include: {
user: {
select: {
id: boolean;
username: boolean;
avatar: boolean;
score?: boolean;
level?: boolean;
};
};
};
};
creator?: {
select: {
id: boolean;
username: boolean;
avatar: boolean;
};
};
} = {};
if (include.includes("members")) {
includeOptions.memberships = {
include: {
user: {
select: {
id: true,
username: true,
avatar: true,
score: true,
level: true,
},
},
},
};
}
if (include.includes("creator")) {
includeOptions.creator = {
select: {
id: true,
username: true,
avatar: true,
},
};
}
let houses;
if (search) {
houses = await houseService.searchHouses(search, {
include: includeOptions,
});
} else {
houses = await houseService.getAllHouses({
include: includeOptions,
});
}
return NextResponse.json(houses);
} catch (error) {
console.error("Error fetching houses:", error);
return NextResponse.json(
{ error: "Erreur lors de la récupération des maisons" },
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,23 @@
import { NextResponse } from "next/server";
import { auth } from "@/lib/auth";
import { houseService } from "@/services/houses/house.service";
export async function GET() {
try {
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json({ count: 0 });
}
// Compter les invitations ET les demandes d'adhésion en attente
const count = await houseService.getPendingHouseActionsCount(
session.user.id
);
return NextResponse.json({ count });
} catch (error) {
console.error("Error fetching pending house actions count:", error);
return NextResponse.json({ count: 0 });
}
}

View File

@@ -0,0 +1,36 @@
import { NextResponse } from "next/server";
import { auth } from "@/lib/auth";
import { houseService } from "@/services/houses/house.service";
export async function GET(request: Request) {
try {
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json(
{ error: "Vous devez être connecté" },
{ status: 401 }
);
}
const { searchParams } = new URL(request.url);
const statusParam = searchParams.get("status");
const status = statusParam && ["PENDING", "ACCEPTED", "REJECTED", "CANCELLED"].includes(statusParam)
? (statusParam as "PENDING" | "ACCEPTED" | "REJECTED" | "CANCELLED")
: undefined;
const invitations = await houseService.getUserInvitations(
session.user.id,
status
);
return NextResponse.json(invitations);
} catch (error) {
console.error("Error fetching invitations:", error);
return NextResponse.json(
{ error: "Erreur lors de la récupération des invitations" },
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,17 @@
import { NextResponse } from "next/server";
import { userStatsService } from "@/services/users/user-stats.service";
export async function GET() {
try {
const leaderboard = await userStatsService.getHouseLeaderboard(10);
return NextResponse.json(leaderboard);
} catch (error) {
console.error("Error fetching house leaderboard:", error);
return NextResponse.json(
{ error: "Erreur lors de la récupération du leaderboard des maisons" },
{ status: 500 }
);
}
}

View File

@@ -13,6 +13,8 @@ export async function GET() {
eventsBackground: null, eventsBackground: null,
leaderboardBackground: null, leaderboardBackground: null,
challengesBackground: null, challengesBackground: null,
profileBackground: null,
houseBackground: null,
}); });
} }
@@ -21,6 +23,8 @@ export async function GET() {
eventsBackground: sitePreferences.eventsBackground, eventsBackground: sitePreferences.eventsBackground,
leaderboardBackground: sitePreferences.leaderboardBackground, leaderboardBackground: sitePreferences.leaderboardBackground,
challengesBackground: sitePreferences.challengesBackground, challengesBackground: sitePreferences.challengesBackground,
profileBackground: sitePreferences.profileBackground,
houseBackground: sitePreferences.houseBackground,
}); });
} catch (error) { } catch (error) {
console.error("Error fetching preferences:", error); console.error("Error fetching preferences:", error);
@@ -30,6 +34,8 @@ export async function GET() {
eventsBackground: null, eventsBackground: null,
leaderboardBackground: null, leaderboardBackground: null,
challengesBackground: null, challengesBackground: null,
profileBackground: null,
houseBackground: null,
}, },
{ status: 200 } { status: 200 }
); );

View File

@@ -39,3 +39,5 @@ export async function GET() {
); );
} }
} }

207
app/houses/page.tsx Normal file
View File

@@ -0,0 +1,207 @@
import { redirect } from "next/navigation";
import { auth } from "@/lib/auth";
import { getBackgroundImage } from "@/lib/preferences";
import NavigationWrapper from "@/components/navigation/NavigationWrapper";
import HousesSection from "@/components/houses/HousesSection";
import { houseService } from "@/services/houses/house.service";
import { prisma } from "@/services/database";
import type {
House,
HouseMembership,
HouseInvitation,
} from "@/prisma/generated/prisma/client";
export const dynamic = "force-dynamic";
// Types pour les données sérialisées
type HouseWithRelations = House & {
creator?: {
id: string;
username: string;
avatar: string | null;
} | null;
creatorId?: string;
memberships?: Array<
HouseMembership & {
user: {
id: string;
username: string;
avatar: string | null;
score: number | null;
level: number | null;
};
}
>;
};
type InvitationWithRelations = HouseInvitation & {
house: {
id: string;
name: string;
};
inviter: {
id: string;
username: string;
avatar: string | null;
};
};
export default async function HousesPage() {
const session = await auth();
if (!session?.user?.id) {
redirect("/login");
}
const [housesData, myHouseData, invitationsData, users, backgroundImage] =
await Promise.all([
// Récupérer les maisons
houseService.getAllHouses({
include: {
memberships: {
include: {
user: {
select: {
id: true,
username: true,
avatar: true,
score: true,
level: true,
},
},
},
orderBy: [
{ role: "asc" }, // OWNER, ADMIN, MEMBER
{ user: { score: "desc" } }, // Puis par score décroissant
],
},
creator: {
select: {
id: true,
username: true,
avatar: true,
},
},
},
}),
// Récupérer la maison de l'utilisateur
houseService.getUserHouse(session.user.id, {
memberships: {
include: {
user: {
select: {
id: true,
username: true,
avatar: true,
score: true,
level: true,
},
},
},
},
creator: {
select: {
id: true,
username: true,
avatar: true,
},
},
}),
// Récupérer les invitations de l'utilisateur
houseService.getUserInvitations(session.user.id, "PENDING"),
// Récupérer tous les utilisateurs sans maison pour les invitations
prisma.user.findMany({
where: {
houseMemberships: {
none: {},
},
},
select: {
id: true,
username: true,
avatar: true,
},
orderBy: {
username: "asc",
},
}),
getBackgroundImage("houses", "/got-2.jpg"),
]);
// Sérialiser les données pour le client
const houses = (housesData as HouseWithRelations[]).map(
(house: HouseWithRelations) => ({
id: house.id,
name: house.name,
description: house.description,
creator: house.creator || {
id: house.creatorId || "",
username: "Unknown",
avatar: null,
},
memberships: (house.memberships || []).map((m) => ({
id: m.id,
role: m.role,
user: {
id: m.user.id,
username: m.user.username,
avatar: m.user.avatar,
score: m.user.score ?? 0,
level: m.user.level ?? 1,
},
})),
})
);
const myHouse = myHouseData
? {
id: myHouseData.id,
name: myHouseData.name,
description: myHouseData.description,
creator: (myHouseData as HouseWithRelations).creator || {
id: (myHouseData as HouseWithRelations).creatorId || "",
username: "Unknown",
avatar: null,
},
memberships: (
(myHouseData as HouseWithRelations).memberships || []
).map((m) => ({
id: m.id,
role: m.role,
user: {
id: m.user.id,
username: m.user.username,
avatar: m.user.avatar,
score: m.user.score ?? 0,
level: m.user.level ?? 1,
},
})),
}
: null;
const invitations = (invitationsData as InvitationWithRelations[]).map(
(inv: InvitationWithRelations) => ({
id: inv.id,
house: {
id: inv.house.id,
name: inv.house.name,
},
inviter: inv.inviter,
status: inv.status,
createdAt: inv.createdAt.toISOString(),
})
);
return (
<main className="min-h-screen bg-black relative">
<NavigationWrapper />
<HousesSection
initialHouses={houses}
initialMyHouse={myHouse}
initialUsers={users}
initialInvitations={invitations}
backgroundImage={backgroundImage}
/>
</main>
);
}

View File

@@ -7,8 +7,9 @@ export const dynamic = "force-dynamic";
export default async function LeaderboardPage() { export default async function LeaderboardPage() {
// Paralléliser les appels DB // Paralléliser les appels DB
const [leaderboard, backgroundImage] = await Promise.all([ const [leaderboard, houseLeaderboard, backgroundImage] = await Promise.all([
userStatsService.getLeaderboard(10), userStatsService.getLeaderboard(10),
userStatsService.getHouseLeaderboard(10),
getBackgroundImage("leaderboard", "/leaderboard-bg.jpg"), getBackgroundImage("leaderboard", "/leaderboard-bg.jpg"),
]); ]);
@@ -17,6 +18,7 @@ export default async function LeaderboardPage() {
<NavigationWrapper /> <NavigationWrapper />
<LeaderboardSection <LeaderboardSection
leaderboard={leaderboard} leaderboard={leaderboard}
houseLeaderboard={houseLeaderboard}
backgroundImage={backgroundImage} backgroundImage={backgroundImage}
/> />
</main> </main>

View File

@@ -20,7 +20,7 @@ export default async function Home() {
})); }));
return ( return (
<main className="min-h-screen bg-black relative"> <main className="min-h-screen relative" style={{ backgroundColor: "var(--background)" }}>
<NavigationWrapper /> <NavigationWrapper />
<HeroSection backgroundImage={backgroundImage} /> <HeroSection backgroundImage={backgroundImage} />
<EventsSection events={serializedEvents} /> <EventsSection events={serializedEvents} />

View File

@@ -29,7 +29,7 @@ export default async function ProfilePage() {
score: true, score: true,
createdAt: true, createdAt: true,
}), }),
getBackgroundImage("home", "/got-background.jpg"), getBackgroundImage("profile", "/got-background.jpg"),
]); ]);
if (!user) { if (!user) {

View File

@@ -31,7 +31,7 @@ export default function StyleGuidePage() {
<Navigation /> <Navigation />
<BackgroundSection backgroundImage="/got-2.jpg" className="pt-24 pb-16"> <BackgroundSection backgroundImage="/got-2.jpg" className="pt-24 pb-16">
<div className="w-full max-w-6xl mx-auto px-8"> <div className="w-full max-w-6xl mx-auto px-8">
<SectionTitle variant="gradient" size="xl" className="mb-12"> <SectionTitle variant="gradient" size="xl" className="mb-16">
STYLE GUIDE STYLE GUIDE
</SectionTitle> </SectionTitle>
<p className="text-gray-400 text-center mb-12 max-w-3xl mx-auto"> <p className="text-gray-400 text-center mb-12 max-w-3xl mx-auto">

View File

@@ -0,0 +1,41 @@
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { Button } from "@/components/ui";
const adminSections = [
{ id: "preferences", label: "Préférences UI", path: "/admin/preferences" },
{ id: "users", label: "Utilisateurs", path: "/admin/users" },
{ id: "events", label: "Événements", path: "/admin/events" },
{ id: "feedbacks", label: "Feedbacks", path: "/admin/feedbacks" },
{ id: "challenges", label: "Défis", path: "/admin/challenges" },
{ id: "houses", label: "Maisons", path: "/admin/houses" },
];
export default function AdminNavigation() {
const pathname = usePathname();
return (
<div className="flex gap-4 mb-8 justify-center flex-wrap">
{adminSections.map((section) => {
const isActive = pathname === section.path ||
(section.path === "/admin/preferences" && pathname === "/admin");
return (
<Button
key={section.id}
as={Link}
href={section.path}
variant={isActive ? "primary" : "secondary"}
size="md"
className={isActive ? "bg-pixel-gold/10" : ""}
>
{section.label}
</Button>
);
})}
</div>
);
}

View File

@@ -1,144 +0,0 @@
"use client";
import { useState } from "react";
import UserManagement from "@/components/admin/UserManagement";
import EventManagement from "@/components/admin/EventManagement";
import FeedbackManagement from "@/components/admin/FeedbackManagement";
import ChallengeManagement from "@/components/admin/ChallengeManagement";
import BackgroundPreferences from "@/components/admin/BackgroundPreferences";
import EventPointsPreferences from "@/components/admin/EventPointsPreferences";
import EventFeedbackPointsPreferences from "@/components/admin/EventFeedbackPointsPreferences";
import { Button, Card, SectionTitle } from "@/components/ui";
interface SitePreferences {
id: string;
homeBackground: string | null;
eventsBackground: string | null;
leaderboardBackground: string | null;
challengesBackground: string | null;
eventRegistrationPoints: number;
eventFeedbackPoints: number;
}
interface AdminPanelProps {
initialPreferences: SitePreferences;
}
type AdminSection =
| "preferences"
| "users"
| "events"
| "feedbacks"
| "challenges";
export default function AdminPanel({ initialPreferences }: AdminPanelProps) {
const [activeSection, setActiveSection] =
useState<AdminSection>("preferences");
return (
<section className="relative w-full min-h-screen flex flex-col items-center overflow-hidden pt-24 pb-16">
<div className="relative z-10 w-full max-w-6xl mx-auto px-8 py-16">
<SectionTitle variant="gradient" size="md" className="mb-8 text-center">
ADMIN
</SectionTitle>
{/* Navigation Tabs */}
<div className="flex gap-4 mb-8 justify-center flex-wrap">
<Button
onClick={() => setActiveSection("preferences")}
variant={activeSection === "preferences" ? "primary" : "secondary"}
size="md"
className={
activeSection === "preferences" ? "bg-pixel-gold/10" : ""
}
>
Préférences UI
</Button>
<Button
onClick={() => setActiveSection("users")}
variant={activeSection === "users" ? "primary" : "secondary"}
size="md"
className={activeSection === "users" ? "bg-pixel-gold/10" : ""}
>
Utilisateurs
</Button>
<Button
onClick={() => setActiveSection("events")}
variant={activeSection === "events" ? "primary" : "secondary"}
size="md"
className={activeSection === "events" ? "bg-pixel-gold/10" : ""}
>
Événements
</Button>
<Button
onClick={() => setActiveSection("feedbacks")}
variant={activeSection === "feedbacks" ? "primary" : "secondary"}
size="md"
className={activeSection === "feedbacks" ? "bg-pixel-gold/10" : ""}
>
Feedbacks
</Button>
<Button
onClick={() => setActiveSection("challenges")}
variant={activeSection === "challenges" ? "primary" : "secondary"}
size="md"
className={activeSection === "challenges" ? "bg-pixel-gold/10" : ""}
>
Défis
</Button>
</div>
{activeSection === "preferences" && (
<Card variant="dark" className="p-4 sm:p-6">
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6">
<h2 className="text-xl sm:text-2xl font-gaming font-bold text-pixel-gold break-words">
Préférences UI Globales
</h2>
</div>
<div className="space-y-4">
<BackgroundPreferences initialPreferences={initialPreferences} />
<EventPointsPreferences initialPreferences={initialPreferences} />
<EventFeedbackPointsPreferences initialPreferences={initialPreferences} />
</div>
</Card>
)}
{activeSection === "users" && (
<Card variant="dark" className="p-6">
<h2 className="text-2xl font-gaming font-bold mb-6 text-pixel-gold">
Gestion des Utilisateurs
</h2>
<UserManagement />
</Card>
)}
{activeSection === "events" && (
<Card variant="dark" className="p-6">
<h2 className="text-2xl font-gaming font-bold mb-6 text-pixel-gold">
Gestion des Événements
</h2>
<EventManagement />
</Card>
)}
{activeSection === "feedbacks" && (
<Card variant="dark" className="p-6">
<h2 className="text-2xl font-gaming font-bold mb-6 text-pixel-gold">
Gestion des Feedbacks
</h2>
<FeedbackManagement />
</Card>
)}
{activeSection === "challenges" && (
<Card variant="dark" className="p-6">
<h2 className="text-2xl font-gaming font-bold mb-6 text-pixel-gold">
Gestion des Défis
</h2>
<ChallengeManagement />
</Card>
)}
</div>
</section>
);
}

View File

@@ -11,6 +11,8 @@ interface SitePreferences {
eventsBackground: string | null; eventsBackground: string | null;
leaderboardBackground: string | null; leaderboardBackground: string | null;
challengesBackground: string | null; challengesBackground: string | null;
profileBackground: string | null;
houseBackground: string | null;
eventRegistrationPoints?: number; eventRegistrationPoints?: number;
} }
@@ -23,6 +25,8 @@ const DEFAULT_IMAGES = {
events: "/got-2.jpg", events: "/got-2.jpg",
leaderboard: "/leaderboard-bg.jpg", leaderboard: "/leaderboard-bg.jpg",
challenges: "/got-2.jpg", challenges: "/got-2.jpg",
profile: "/got-background.jpg",
houses: "/got-2.jpg",
}; };
export default function BackgroundPreferences({ export default function BackgroundPreferences({
@@ -64,6 +68,14 @@ export default function BackgroundPreferences({
initialPreferences.challengesBackground, initialPreferences.challengesBackground,
DEFAULT_IMAGES.challenges DEFAULT_IMAGES.challenges
), ),
profileBackground: getFormValue(
initialPreferences.profileBackground,
DEFAULT_IMAGES.profile
),
houseBackground: getFormValue(
initialPreferences.houseBackground,
DEFAULT_IMAGES.houses
),
}), }),
[initialPreferences] [initialPreferences]
); );
@@ -101,6 +113,14 @@ export default function BackgroundPreferences({
formData.challengesBackground, formData.challengesBackground,
DEFAULT_IMAGES.challenges DEFAULT_IMAGES.challenges
), ),
profileBackground: getApiValue(
formData.profileBackground,
DEFAULT_IMAGES.profile
),
houseBackground: getApiValue(
formData.houseBackground,
DEFAULT_IMAGES.houses
),
}; };
const result = await updateSitePreferences(apiData); const result = await updateSitePreferences(apiData);
@@ -125,6 +145,14 @@ export default function BackgroundPreferences({
result.data.challengesBackground, result.data.challengesBackground,
DEFAULT_IMAGES.challenges DEFAULT_IMAGES.challenges
), ),
profileBackground: getFormValue(
result.data.profileBackground,
DEFAULT_IMAGES.profile
),
houseBackground: getFormValue(
result.data.houseBackground,
DEFAULT_IMAGES.houses
),
}); });
setIsEditing(false); setIsEditing(false);
} else { } else {
@@ -157,6 +185,14 @@ export default function BackgroundPreferences({
preferences.challengesBackground, preferences.challengesBackground,
DEFAULT_IMAGES.challenges DEFAULT_IMAGES.challenges
), ),
profileBackground: getFormValue(
preferences.profileBackground,
DEFAULT_IMAGES.profile
),
houseBackground: getFormValue(
preferences.houseBackground,
DEFAULT_IMAGES.houses
),
}); });
} }
}; };
@@ -226,6 +262,26 @@ export default function BackgroundPreferences({
} }
label="Background Challenges" label="Background Challenges"
/> />
<ImageSelector
value={formData.profileBackground}
onChange={(url) =>
setFormData({
...formData,
profileBackground: url,
})
}
label="Background Profile"
/>
<ImageSelector
value={formData.houseBackground}
onChange={(url) =>
setFormData({
...formData,
houseBackground: url,
})
}
label="Background Houses"
/>
<div className="flex flex-col sm:flex-row gap-2 pt-4"> <div className="flex flex-col sm:flex-row gap-2 pt-4">
<Button onClick={handleSave} variant="success" size="md"> <Button onClick={handleSave} variant="success" size="md">
Enregistrer Enregistrer
@@ -461,6 +517,118 @@ export default function BackgroundPreferences({
); );
})()} })()}
</div> </div>
<div className="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4">
<span className="text-pixel-gold font-bold text-sm sm:text-base min-w-0 sm:min-w-[120px] flex-shrink-0">
Profile:
</span>
{(() => {
const currentImage =
preferences?.profileBackground &&
preferences.profileBackground.trim() !== ""
? preferences.profileBackground
: DEFAULT_IMAGES.profile;
const isDefault =
!preferences?.profileBackground ||
preferences.profileBackground.trim() === "";
return (
<div className="flex items-center gap-2 sm:gap-3 min-w-0 flex-1">
<div className="relative w-16 h-10 sm:w-20 sm:h-12 rounded border border-pixel-gold/30 overflow-hidden bg-black/60 flex-shrink-0">
<img
src={currentImage}
alt="Profile background"
className="w-full h-full object-cover"
onError={(e) => {
const target = e.currentTarget;
const currentSrc = target.src;
const fallbackSrc = "/got-background.jpg";
if (!currentSrc.includes(fallbackSrc)) {
target.src = fallbackSrc;
} else {
target.style.display = "none";
const fallbackDiv =
target.nextElementSibling as HTMLElement;
if (fallbackDiv) {
fallbackDiv.classList.remove("hidden");
}
}
}}
/>
<div className="absolute inset-0 flex items-center justify-center bg-black/60 text-gray-500 text-xs hidden">
No image
</div>
</div>
<div className="flex flex-col min-w-0 flex-1">
<span className="text-xs text-gray-400 truncate min-w-0">
{isDefault ? "Par défaut: " : ""}
{currentImage}
</span>
{isDefault && (
<span className="text-[10px] text-gray-500 italic">
(Image par défaut)
</span>
)}
</div>
</div>
);
})()}
</div>
<div className="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4">
<span className="text-pixel-gold font-bold text-sm sm:text-base min-w-0 sm:min-w-[120px] flex-shrink-0">
Houses:
</span>
{(() => {
const currentImage =
preferences?.houseBackground &&
preferences.houseBackground.trim() !== ""
? preferences.houseBackground
: DEFAULT_IMAGES.houses;
const isDefault =
!preferences?.houseBackground ||
preferences.houseBackground.trim() === "";
return (
<div className="flex items-center gap-2 sm:gap-3 min-w-0 flex-1">
<div className="relative w-16 h-10 sm:w-20 sm:h-12 rounded border border-pixel-gold/30 overflow-hidden bg-black/60 flex-shrink-0">
<img
src={currentImage}
alt="Houses background"
className="w-full h-full object-cover"
onError={(e) => {
const target = e.currentTarget;
const currentSrc = target.src;
const fallbackSrc = "/got-2.jpg";
if (!currentSrc.includes(fallbackSrc)) {
target.src = fallbackSrc;
} else {
target.style.display = "none";
const fallbackDiv =
target.nextElementSibling as HTMLElement;
if (fallbackDiv) {
fallbackDiv.classList.remove("hidden");
}
}
}}
/>
<div className="absolute inset-0 flex items-center justify-center bg-black/60 text-gray-500 text-xs hidden">
No image
</div>
</div>
<div className="flex flex-col min-w-0 flex-1">
<span className="text-xs text-gray-400 truncate min-w-0">
{isDefault ? "Par défaut: " : ""}
{currentImage}
</span>
{isDefault && (
<span className="text-[10px] text-gray-500 italic">
(Image par défaut)
</span>
)}
</div>
</div>
);
})()}
</div>
</div> </div>
)} )}
</Card> </Card>

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import { useEffect, useState, useTransition } from "react"; import { useState, useTransition } from "react";
import { import {
validateChallenge, validateChallenge,
rejectChallenge, rejectChallenge,
@@ -8,6 +8,7 @@ import {
deleteChallenge, deleteChallenge,
adminCancelChallenge, adminCancelChallenge,
reactivateChallenge, reactivateChallenge,
adminAcceptChallenge,
} from "@/actions/admin/challenges"; } from "@/actions/admin/challenges";
import { import {
Button, Button,
@@ -41,9 +42,12 @@ interface Challenge {
acceptedAt: string | null; acceptedAt: string | null;
} }
export default function ChallengeManagement() { interface ChallengeManagementProps {
const [challenges, setChallenges] = useState<Challenge[]>([]); initialChallenges: Challenge[];
const [loading, setLoading] = useState(true); }
export default function ChallengeManagement({ initialChallenges }: ChallengeManagementProps) {
const [challenges, setChallenges] = useState<Challenge[]>(initialChallenges);
const [selectedChallenge, setSelectedChallenge] = useState<Challenge | null>( const [selectedChallenge, setSelectedChallenge] = useState<Challenge | null>(
null null
); );
@@ -59,10 +63,6 @@ export default function ChallengeManagement() {
const [successMessage, setSuccessMessage] = useState<string | null>(null); const [successMessage, setSuccessMessage] = useState<string | null>(null);
const [errorMessage, setErrorMessage] = useState<string | null>(null); const [errorMessage, setErrorMessage] = useState<string | null>(null);
useEffect(() => {
fetchChallenges();
}, []);
const fetchChallenges = async () => { const fetchChallenges = async () => {
try { try {
const response = await fetch("/api/admin/challenges"); const response = await fetch("/api/admin/challenges");
@@ -72,8 +72,6 @@ export default function ChallengeManagement() {
} }
} catch (error) { } catch (error) {
console.error("Error fetching challenges:", error); console.error("Error fetching challenges:", error);
} finally {
setLoading(false);
} }
}; };
@@ -99,6 +97,8 @@ export default function ChallengeManagement() {
setWinnerId(""); setWinnerId("");
setAdminComment(""); setAdminComment("");
fetchChallenges(); fetchChallenges();
// Rafraîchir le badge des défis
window.dispatchEvent(new Event("refreshChallenges"));
setTimeout(() => setSuccessMessage(null), 5000); setTimeout(() => setSuccessMessage(null), 5000);
} else { } else {
setErrorMessage(result.error || "Erreur lors de la validation"); setErrorMessage(result.error || "Erreur lors de la validation");
@@ -125,6 +125,8 @@ export default function ChallengeManagement() {
setSelectedChallenge(null); setSelectedChallenge(null);
setAdminComment(""); setAdminComment("");
fetchChallenges(); fetchChallenges();
// Rafraîchir le badge des défis
window.dispatchEvent(new Event("refreshChallenges"));
setTimeout(() => setSuccessMessage(null), 5000); setTimeout(() => setSuccessMessage(null), 5000);
} else { } else {
setErrorMessage(result.error || "Erreur lors du rejet"); setErrorMessage(result.error || "Erreur lors du rejet");
@@ -180,6 +182,8 @@ export default function ChallengeManagement() {
if (result.success) { if (result.success) {
setSuccessMessage("Défi supprimé avec succès"); setSuccessMessage("Défi supprimé avec succès");
fetchChallenges(); fetchChallenges();
// Rafraîchir le badge des défis
window.dispatchEvent(new Event("refreshChallenges"));
setTimeout(() => setSuccessMessage(null), 5000); setTimeout(() => setSuccessMessage(null), 5000);
} else { } else {
setErrorMessage(result.error || "Erreur lors de la suppression"); setErrorMessage(result.error || "Erreur lors de la suppression");
@@ -199,6 +203,8 @@ export default function ChallengeManagement() {
if (result.success) { if (result.success) {
setSuccessMessage("Défi annulé avec succès"); setSuccessMessage("Défi annulé avec succès");
fetchChallenges(); fetchChallenges();
// Rafraîchir le badge des défis
window.dispatchEvent(new Event("refreshChallenges"));
setTimeout(() => setSuccessMessage(null), 5000); setTimeout(() => setSuccessMessage(null), 5000);
} else { } else {
setErrorMessage(result.error || "Erreur lors de l'annulation"); setErrorMessage(result.error || "Erreur lors de l'annulation");
@@ -218,6 +224,8 @@ export default function ChallengeManagement() {
if (result.success) { if (result.success) {
setSuccessMessage("Défi réactivé avec succès"); setSuccessMessage("Défi réactivé avec succès");
fetchChallenges(); fetchChallenges();
// Rafraîchir le badge des défis
window.dispatchEvent(new Event("refreshChallenges"));
setTimeout(() => setSuccessMessage(null), 5000); setTimeout(() => setSuccessMessage(null), 5000);
} else { } else {
setErrorMessage(result.error || "Erreur lors de la réactivation"); setErrorMessage(result.error || "Erreur lors de la réactivation");
@@ -226,11 +234,30 @@ export default function ChallengeManagement() {
}); });
}; };
if (loading) { const handleAdminAccept = async (challengeId: string) => {
return ( if (
<div className="text-center text-pixel-gold py-8">Chargement...</div> !confirm(
); "Êtes-vous sûr de vouloir accepter ce défi à la place de l'utilisateur ?"
} )
) {
return;
}
startTransition(async () => {
const result = await adminAcceptChallenge(challengeId);
if (result.success) {
setSuccessMessage("Défi accepté avec succès");
fetchChallenges();
// Rafraîchir le badge des défis
window.dispatchEvent(new Event("refreshChallenges"));
setTimeout(() => setSuccessMessage(null), 5000);
} else {
setErrorMessage(result.error || "Erreur lors de l'acceptation");
setTimeout(() => setErrorMessage(null), 5000);
}
});
};
if (challenges.length === 0) { if (challenges.length === 0) {
return <div className="text-center text-gray-400 py-8">Aucun défi</div>; return <div className="text-center text-gray-400 py-8">Aucun défi</div>;
@@ -376,6 +403,16 @@ export default function ChallengeManagement() {
> >
Modifier Modifier
</Button> </Button>
{challenge.status === "PENDING" && (
<Button
onClick={() => handleAdminAccept(challenge.id)}
variant="primary"
size="sm"
disabled={isPending}
>
Accepter le défi
</Button>
)}
{challenge.status === "ACCEPTED" && ( {challenge.status === "ACCEPTED" && (
<Button <Button
onClick={() => setSelectedChallenge(challenge)} onClick={() => setSelectedChallenge(challenge)}

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import { useState, useEffect, useTransition } from "react"; import { useState, useTransition } from "react";
import { calculateEventStatus } from "@/lib/eventStatus"; import { calculateEventStatus } from "@/lib/eventStatus";
import { createEvent, updateEvent, deleteEvent } from "@/actions/admin/events"; import { createEvent, updateEvent, deleteEvent } from "@/actions/admin/events";
import { import {
@@ -92,9 +92,12 @@ const getStatusLabel = (status: Event["status"]) => {
} }
}; };
export default function EventManagement() { interface EventManagementProps {
const [events, setEvents] = useState<Event[]>([]); initialEvents: Event[];
const [loading, setLoading] = useState(true); }
export default function EventManagement({ initialEvents }: EventManagementProps) {
const [events, setEvents] = useState<Event[]>(initialEvents);
const [editingEvent, setEditingEvent] = useState<Event | null>(null); const [editingEvent, setEditingEvent] = useState<Event | null>(null);
const [isCreating, setIsCreating] = useState(false); const [isCreating, setIsCreating] = useState(false);
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
@@ -116,10 +119,6 @@ export default function EventManagement() {
maxPlaces: undefined, maxPlaces: undefined,
}); });
useEffect(() => {
fetchEvents();
}, []);
const fetchEvents = async () => { const fetchEvents = async () => {
try { try {
const response = await fetch("/api/admin/events"); const response = await fetch("/api/admin/events");
@@ -129,8 +128,6 @@ export default function EventManagement() {
} }
} catch (error) { } catch (error) {
console.error("Error fetching events:", error); console.error("Error fetching events:", error);
} finally {
setLoading(false);
} }
}; };
@@ -151,8 +148,10 @@ export default function EventManagement() {
const handleEdit = (event: Event) => { const handleEdit = (event: Event) => {
setEditingEvent(event); setEditingEvent(event);
setIsCreating(false); setIsCreating(false);
// Convertir la date ISO en format YYYY-MM-DD pour l'input date
const dateValue = event.date ? new Date(event.date).toISOString().split('T')[0] : "";
setFormData({ setFormData({
date: event.date, date: dateValue,
name: event.name, name: event.name,
description: event.description, description: event.description,
type: event.type, type: event.type,
@@ -304,10 +303,6 @@ export default function EventManagement() {
}); });
}; };
if (loading) {
return <div className="text-center text-gray-400 py-8">Chargement...</div>;
}
return ( return (
<div className="space-y-4"> <div className="space-y-4">
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-3 mb-4"> <div className="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-3 mb-4">

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import { useState, useEffect } from "react"; import { useState } from "react";
import { import {
addFeedbackBonusPoints, addFeedbackBonusPoints,
markFeedbackAsRead, markFeedbackAsRead,
@@ -38,10 +38,17 @@ interface EventStatistics {
feedbackCount: number; feedbackCount: number;
} }
export default function FeedbackManagement() { interface FeedbackManagementProps {
const [feedbacks, setFeedbacks] = useState<Feedback[]>([]); initialFeedbacks: Feedback[];
const [statistics, setStatistics] = useState<EventStatistics[]>([]); initialStatistics: EventStatistics[];
const [loading, setLoading] = useState(true); }
export default function FeedbackManagement({
initialFeedbacks,
initialStatistics,
}: FeedbackManagementProps) {
const [feedbacks, setFeedbacks] = useState<Feedback[]>(initialFeedbacks);
const [statistics, setStatistics] = useState<EventStatistics[]>(initialStatistics);
const [error, setError] = useState(""); const [error, setError] = useState("");
const [selectedEvent, setSelectedEvent] = useState<string | null>(null); const [selectedEvent, setSelectedEvent] = useState<string | null>(null);
const [addingPoints, setAddingPoints] = useState<Record<string, boolean>>( const [addingPoints, setAddingPoints] = useState<Record<string, boolean>>(
@@ -49,10 +56,6 @@ export default function FeedbackManagement() {
); );
const [markingRead, setMarkingRead] = useState<Record<string, boolean>>({}); const [markingRead, setMarkingRead] = useState<Record<string, boolean>>({});
useEffect(() => {
fetchFeedbacks();
}, []);
const fetchFeedbacks = async () => { const fetchFeedbacks = async () => {
try { try {
const response = await fetch("/api/admin/feedback"); const response = await fetch("/api/admin/feedback");
@@ -65,8 +68,6 @@ export default function FeedbackManagement() {
setStatistics(data.statistics || []); setStatistics(data.statistics || []);
} catch { } catch {
setError("Erreur lors du chargement des feedbacks"); setError("Erreur lors du chargement des feedbacks");
} finally {
setLoading(false);
} }
}; };
@@ -159,14 +160,6 @@ export default function FeedbackManagement() {
); );
}); });
if (loading) {
return (
<div className="bg-black/60 border border-pixel-gold/30 rounded-lg p-4 sm:p-8">
<p className="text-gray-400 text-center text-sm">Chargement...</p>
</div>
);
}
return ( return (
<div className="space-y-4 sm:space-y-6"> <div className="space-y-4 sm:space-y-6">
{/* Statistiques par événement */} {/* Statistiques par événement */}

View File

@@ -0,0 +1,447 @@
"use client";
import { useState, useTransition } from "react";
import {
Input,
Textarea,
Button,
Card,
Badge,
Modal,
CloseButton,
Avatar,
} from "@/components/ui";
import { updateHouse, deleteHouse, removeMember } from "@/actions/admin/houses";
interface House {
id: string;
name: string;
description: string | null;
creatorId: string;
creator: {
id: string;
username: string;
avatar: string | null;
};
createdAt: string;
updatedAt: string;
membersCount: number;
memberships: Array<{
id: string;
role: string;
joinedAt: string;
user: {
id: string;
username: string;
avatar: string | null;
score: number;
level: number;
};
}>;
}
interface HouseFormData {
name: string;
description: string;
}
const getRoleLabel = (role: string) => {
switch (role) {
case "OWNER":
return "👑 Propriétaire";
case "ADMIN":
return "⚡ Admin";
case "MEMBER":
return "👤 Membre";
default:
return role;
}
};
const getRoleColor = (role: string) => {
switch (role) {
case "OWNER":
return "var(--accent)";
case "ADMIN":
return "var(--primary)";
case "MEMBER":
return "var(--muted-foreground)";
default:
return "var(--gray)";
}
};
interface HouseManagementProps {
initialHouses: House[];
}
export default function HouseManagement({ initialHouses }: HouseManagementProps) {
const [houses, setHouses] = useState<House[]>(initialHouses);
const [editingHouse, setEditingHouse] = useState<House | null>(null);
const [saving, setSaving] = useState(false);
const [deletingHouseId, setDeletingHouseId] = useState<string | null>(null);
const [viewingMembers, setViewingMembers] = useState<House | null>(null);
const [removingMemberId, setRemovingMemberId] = useState<string | null>(null);
const [formData, setFormData] = useState<HouseFormData>({
name: "",
description: "",
});
const [, startTransition] = useTransition();
const fetchHouses = async () => {
try {
const response = await fetch("/api/admin/houses");
if (response.ok) {
const data = await response.json();
setHouses(data);
}
} catch (error) {
console.error("Error fetching houses:", error);
}
};
const handleEdit = (house: House) => {
setEditingHouse(house);
setFormData({
name: house.name,
description: house.description || "",
});
};
const handleSave = async () => {
if (!editingHouse) return;
setSaving(true);
startTransition(async () => {
try {
const result = await updateHouse(editingHouse.id, {
name: formData.name,
description: formData.description || null,
});
if (result.success) {
await fetchHouses();
setEditingHouse(null);
setFormData({ name: "", description: "" });
} else {
alert(result.error || "Erreur lors de la mise à jour");
}
} catch (error) {
console.error("Error updating house:", error);
alert("Erreur lors de la mise à jour");
} finally {
setSaving(false);
}
});
};
const handleDelete = async (houseId: string) => {
if (
!confirm(
"Êtes-vous sûr de vouloir supprimer cette maison ? Cette action est irréversible et supprimera tous les membres."
)
) {
return;
}
setDeletingHouseId(houseId);
startTransition(async () => {
try {
const result = await deleteHouse(houseId);
if (result.success) {
await fetchHouses();
} else {
alert(result.error || "Erreur lors de la suppression");
}
} catch (error) {
console.error("Error deleting house:", error);
alert("Erreur lors de la suppression");
} finally {
setDeletingHouseId(null);
}
});
};
const handleCancel = () => {
setEditingHouse(null);
setFormData({ name: "", description: "" });
};
const handleRemoveMember = async (houseId: string, memberId: string) => {
if (
!confirm(
"Êtes-vous sûr de vouloir retirer ce membre de la maison ? Cette action lui retirera des points."
)
) {
return;
}
setRemovingMemberId(memberId);
startTransition(async () => {
try {
const result = await removeMember(houseId, memberId);
if (result.success) {
// Récupérer les maisons mises à jour
const response = await fetch("/api/admin/houses");
if (response.ok) {
const updatedHouses = await response.json();
setHouses(updatedHouses);
// Mettre à jour la modal si elle est ouverte
if (viewingMembers) {
const updatedHouse = updatedHouses.find((h: House) => h.id === houseId);
if (updatedHouse) {
setViewingMembers(updatedHouse);
} else {
// Si la maison n'existe plus, fermer la modal
setViewingMembers(null);
}
}
}
} else {
alert(result.error || "Erreur lors du retrait du membre");
}
} catch (error) {
console.error("Error removing member:", error);
alert("Erreur lors du retrait du membre");
} finally {
setRemovingMemberId(null);
}
});
};
const formatNumber = (num: number) => {
return num.toLocaleString("en-US");
};
return (
<div className="space-y-4">
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-3 mb-4">
<h3 className="text-lg sm:text-xl font-gaming font-bold text-pixel-gold break-words">
Maisons ({houses.length})
</h3>
</div>
{/* Modal d'édition */}
{editingHouse && (
<Modal isOpen={!!editingHouse} onClose={handleCancel} size="lg">
<div className="p-6">
<div className="flex items-center justify-between mb-4">
<h4 className="text-pixel-gold font-bold text-base sm:text-lg break-words">
Modifier la maison
</h4>
<CloseButton onClick={handleCancel} size="lg" />
</div>
<div className="space-y-4">
<Input
type="text"
label="Nom de la maison"
value={formData.name}
onChange={(e) =>
setFormData({ ...formData, name: e.target.value })
}
placeholder="Nom de la maison"
className="text-xs sm:text-sm px-3 py-2"
/>
<Textarea
label="Description"
value={formData.description}
onChange={(e) =>
setFormData({ ...formData, description: e.target.value })
}
placeholder="Description de la maison"
rows={4}
className="text-xs sm:text-sm px-3 py-2"
/>
<div className="flex flex-col sm:flex-row gap-2">
<Button
onClick={handleSave}
variant="success"
size="md"
disabled={saving}
>
{saving ? "Enregistrement..." : "Enregistrer"}
</Button>
<Button onClick={handleCancel} variant="secondary" size="md">
Annuler
</Button>
</div>
</div>
</div>
</Modal>
)}
{/* Modal des membres */}
{viewingMembers && (
<Modal
isOpen={!!viewingMembers}
onClose={() => setViewingMembers(null)}
size="lg"
>
<div className="p-6">
<div className="flex items-center justify-between mb-4">
<h4 className="text-pixel-gold font-bold text-base sm:text-lg break-words">
Membres de &quot;{viewingMembers.name}&quot;
</h4>
<CloseButton onClick={() => setViewingMembers(null)} size="lg" />
</div>
{viewingMembers.memberships.length === 0 ? (
<div className="text-center text-gray-400 py-8">
Aucun membre dans cette maison
</div>
) : (
<div className="space-y-3 max-h-[60vh] overflow-y-auto">
{viewingMembers.memberships.map((membership) => {
const roleColor = getRoleColor(membership.role);
return (
<Card
key={membership.id}
variant="default"
className="p-3 sm:p-4"
>
<div className="flex flex-col sm:flex-row sm:items-center gap-3">
<div className="flex items-center gap-3 flex-1 min-w-0">
<Avatar
src={membership.user.avatar}
username={membership.user.username}
size="md"
borderClassName="border-2"
style={{
borderColor: roleColor,
}}
/>
<div className="flex-1 min-w-0">
<h5 className="text-pixel-gold font-bold text-sm sm:text-base break-words">
{membership.user.username}
</h5>
<p className="text-gray-400 text-xs sm:text-sm">
Niveau {membership.user.level} Score:{" "}
{formatNumber(membership.user.score)}
</p>
</div>
</div>
<div className="flex items-center gap-2 flex-shrink-0">
<Badge
variant="default"
size="sm"
style={{
color: roleColor,
backgroundColor: `color-mix(in srgb, ${roleColor} 15%, transparent)`,
borderColor: `color-mix(in srgb, ${roleColor} 30%, transparent)`,
}}
>
{getRoleLabel(membership.role)}
</Badge>
{membership.role !== "OWNER" && (
<Button
onClick={() =>
handleRemoveMember(
viewingMembers.id,
membership.user.id
)
}
variant="danger"
size="sm"
disabled={removingMemberId === membership.user.id}
className="whitespace-nowrap"
>
{removingMemberId === membership.user.id
? "..."
: "Retirer"}
</Button>
)}
</div>
</div>
</Card>
);
})}
</div>
)}
</div>
</Modal>
)}
{houses.length === 0 ? (
<div className="text-center text-gray-400 py-8">
Aucune maison trouvée
</div>
) : (
<div className="space-y-3">
{houses.map((house) => {
return (
<Card key={house.id} variant="default" className="p-3 sm:p-4">
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-start gap-3">
<div className="flex-1 min-w-0">
<div className="flex flex-wrap items-center gap-2 sm:gap-3 mb-2">
<h4 className="text-pixel-gold font-bold text-base sm:text-lg break-words">
{house.name}
</h4>
<Badge variant="info" size="sm">
{house.membersCount} membre
{house.membersCount !== 1 ? "s" : ""}
</Badge>
</div>
{house.description && (
<p className="text-gray-400 text-xs sm:text-sm mb-2 break-words">
{house.description}
</p>
)}
<div className="flex flex-wrap items-center gap-2 sm:gap-4 mt-2">
<div className="flex items-center gap-2">
<Avatar
src={house.creator.avatar}
username={house.creator.username}
size="sm"
borderClassName="border border-pixel-gold/50"
/>
<p className="text-gray-500 text-[10px] sm:text-xs">
Créée par {house.creator.username}
</p>
</div>
<p className="text-gray-500 text-[10px] sm:text-xs whitespace-nowrap">
Créée le{" "}
{new Date(house.createdAt).toLocaleDateString("fr-FR")}
</p>
</div>
</div>
{!editingHouse && (
<div className="flex gap-2 sm:ml-4 flex-shrink-0 flex-wrap">
<Button
onClick={() => setViewingMembers(house)}
variant="primary"
size="sm"
className="whitespace-nowrap"
>
Membres ({house.membersCount})
</Button>
<Button
onClick={() => handleEdit(house)}
variant="primary"
size="sm"
className="whitespace-nowrap"
>
Modifier
</Button>
<Button
onClick={() => handleDelete(house.id)}
variant="danger"
size="sm"
disabled={deletingHouseId === house.id}
className="whitespace-nowrap"
>
{deletingHouseId === house.id ? "..." : "Supprimer"}
</Button>
</div>
)}
</div>
</Card>
);
})}
</div>
)}
</div>
);
}

View File

@@ -0,0 +1,269 @@
"use client";
import { useState, useEffect } from "react";
import { updateSitePreferences } from "@/actions/admin/preferences";
import { Button, Card, Input } from "@/components/ui";
interface SitePreferences {
id: string;
houseJoinPoints: number;
houseLeavePoints: number;
houseCreatePoints: number;
}
interface HousePointsPreferencesProps {
initialPreferences: SitePreferences;
}
export default function HousePointsPreferences({
initialPreferences,
}: HousePointsPreferencesProps) {
const [preferences, setPreferences] = useState<SitePreferences | null>(
initialPreferences
);
const [isEditing, setIsEditing] = useState(false);
const [formData, setFormData] = useState({
houseJoinPoints: initialPreferences.houseJoinPoints.toString(),
houseLeavePoints: initialPreferences.houseLeavePoints.toString(),
houseCreatePoints: initialPreferences.houseCreatePoints.toString(),
});
const [isSaving, setIsSaving] = useState(false);
// Synchroniser les préférences quand initialPreferences change
useEffect(() => {
setPreferences(initialPreferences);
setFormData({
houseJoinPoints: initialPreferences.houseJoinPoints.toString(),
houseLeavePoints: initialPreferences.houseLeavePoints.toString(),
houseCreatePoints: initialPreferences.houseCreatePoints.toString(),
});
}, [initialPreferences]);
const handleEdit = () => {
setIsEditing(true);
};
const handleSave = async () => {
const joinPoints = parseInt(formData.houseJoinPoints, 10);
const leavePoints = parseInt(formData.houseLeavePoints, 10);
const createPoints = parseInt(formData.houseCreatePoints, 10);
if (isNaN(joinPoints) || joinPoints < 0) {
alert("Le nombre de points pour rejoindre une maison doit être un nombre positif");
return;
}
if (isNaN(leavePoints) || leavePoints < 0) {
alert("Le nombre de points pour quitter une maison doit être un nombre positif");
return;
}
if (isNaN(createPoints) || createPoints < 0) {
alert("Le nombre de points pour créer une maison doit être un nombre positif");
return;
}
setIsSaving(true);
try {
const result = await updateSitePreferences({
houseJoinPoints: joinPoints,
houseLeavePoints: leavePoints,
houseCreatePoints: createPoints,
});
if (result.success && result.data) {
setPreferences(result.data);
setFormData({
houseJoinPoints: result.data.houseJoinPoints.toString(),
houseLeavePoints: result.data.houseLeavePoints.toString(),
houseCreatePoints: result.data.houseCreatePoints.toString(),
});
setIsEditing(false);
} else {
console.error("Error updating preferences:", result.error);
alert(result.error || "Erreur lors de la mise à jour");
}
} catch (error) {
console.error("Error updating preferences:", error);
alert("Erreur lors de la mise à jour");
} finally {
setIsSaving(false);
}
};
const handleCancel = () => {
setIsEditing(false);
if (preferences) {
setFormData({
houseJoinPoints: preferences.houseJoinPoints.toString(),
houseLeavePoints: preferences.houseLeavePoints.toString(),
houseCreatePoints: preferences.houseCreatePoints.toString(),
});
}
};
return (
<Card variant="default" className="p-3 sm:p-4">
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-start gap-3 mb-4">
<div className="min-w-0 flex-1">
<h3 className="text-pixel-gold font-bold text-base sm:text-lg break-words">
Points des Maisons
</h3>
<p className="text-gray-400 text-xs sm:text-sm">
Nombre de points attribués ou retirés pour les actions liées aux maisons
</p>
</div>
{!isEditing && (
<Button
onClick={handleEdit}
variant="primary"
size="sm"
className="whitespace-nowrap flex-shrink-0"
>
Modifier
</Button>
)}
</div>
{isEditing ? (
<div className="space-y-4">
<div>
<label
htmlFor="houseJoinPoints"
className="block text-sm font-medium text-pixel-gold mb-2"
>
Points pour rejoindre une maison
</label>
<Input
id="houseJoinPoints"
type="number"
min="0"
value={formData.houseJoinPoints}
onChange={(e) =>
setFormData({
...formData,
houseJoinPoints: e.target.value,
})
}
placeholder="100"
className="w-full"
/>
<p className="text-xs text-gray-400 mt-1">
Les utilisateurs gagneront ce nombre de points lorsqu&apos;ils rejoignent une maison
</p>
</div>
<div>
<label
htmlFor="houseLeavePoints"
className="block text-sm font-medium text-pixel-gold mb-2"
>
Points retirés en quittant une maison
</label>
<Input
id="houseLeavePoints"
type="number"
min="0"
value={formData.houseLeavePoints}
onChange={(e) =>
setFormData({
...formData,
houseLeavePoints: e.target.value,
})
}
placeholder="100"
className="w-full"
/>
<p className="text-xs text-gray-400 mt-1">
Les utilisateurs perdront ce nombre de points lorsqu&apos;ils quittent une maison
</p>
</div>
<div>
<label
htmlFor="houseCreatePoints"
className="block text-sm font-medium text-pixel-gold mb-2"
>
Points pour créer une maison
</label>
<Input
id="houseCreatePoints"
type="number"
min="0"
value={formData.houseCreatePoints}
onChange={(e) =>
setFormData({
...formData,
houseCreatePoints: e.target.value,
})
}
placeholder="100"
className="w-full"
/>
<p className="text-xs text-gray-400 mt-1">
Les utilisateurs gagneront ce nombre de points lorsqu&apos;ils créent une maison
</p>
</div>
<div className="flex flex-col sm:flex-row gap-2 pt-4">
<Button
onClick={handleSave}
variant="success"
size="md"
disabled={isSaving}
>
{isSaving ? "Enregistrement..." : "Enregistrer"}
</Button>
<Button
onClick={handleCancel}
variant="secondary"
size="md"
disabled={isSaving}
>
Annuler
</Button>
</div>
</div>
) : (
<div className="space-y-3">
<div className="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4">
<span className="text-pixel-gold font-bold text-sm sm:text-base min-w-0 sm:min-w-[200px] flex-shrink-0">
Points pour rejoindre:
</span>
<div className="flex items-center gap-2">
<span className="text-lg sm:text-xl font-bold text-white">
{preferences?.houseJoinPoints ?? 100}
</span>
<span className="text-xs sm:text-sm text-gray-400">points</span>
</div>
</div>
<div className="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4">
<span className="text-pixel-gold font-bold text-sm sm:text-base min-w-0 sm:min-w-[200px] flex-shrink-0">
Points retirés en quittant:
</span>
<div className="flex items-center gap-2">
<span className="text-lg sm:text-xl font-bold text-white">
{preferences?.houseLeavePoints ?? 100}
</span>
<span className="text-xs sm:text-sm text-gray-400">points</span>
</div>
</div>
<div className="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4">
<span className="text-pixel-gold font-bold text-sm sm:text-base min-w-0 sm:min-w-[200px] flex-shrink-0">
Points pour créer:
</span>
<div className="flex items-center gap-2">
<span className="text-lg sm:text-xl font-bold text-white">
{preferences?.houseCreatePoints ?? 100}
</span>
<span className="text-xs sm:text-sm text-gray-400">points</span>
</div>
</div>
</div>
)}
</Card>
);
}

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import { useState, useEffect, useTransition } from "react"; import { useState, useTransition } from "react";
import { import {
Avatar, Avatar,
Input, Input,
@@ -37,19 +37,18 @@ interface EditingUser {
role: string | null; role: string | null;
} }
export default function UserManagement() { interface UserManagementProps {
const [users, setUsers] = useState<User[]>([]); initialUsers: User[];
const [loading, setLoading] = useState(true); }
export default function UserManagement({ initialUsers }: UserManagementProps) {
const [users, setUsers] = useState<User[]>(initialUsers);
const [editingUser, setEditingUser] = useState<EditingUser | null>(null); const [editingUser, setEditingUser] = useState<EditingUser | null>(null);
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const [deletingUserId, setDeletingUserId] = useState<string | null>(null); const [deletingUserId, setDeletingUserId] = useState<string | null>(null);
const [, startTransition] = useTransition(); const [, startTransition] = useTransition();
const [uploadingAvatar, setUploadingAvatar] = useState<string | null>(null); const [uploadingAvatar, setUploadingAvatar] = useState<string | null>(null);
useEffect(() => {
fetchUsers();
}, []);
const fetchUsers = async () => { const fetchUsers = async () => {
try { try {
const response = await fetch("/api/admin/users"); const response = await fetch("/api/admin/users");
@@ -59,8 +58,6 @@ export default function UserManagement() {
} }
} catch (error) { } catch (error) {
console.error("Error fetching users:", error); console.error("Error fetching users:", error);
} finally {
setLoading(false);
} }
}; };
@@ -185,10 +182,6 @@ export default function UserManagement() {
? Math.max(0, currentEditingUserData.xp + editingUser.xpDelta) ? Math.max(0, currentEditingUserData.xp + editingUser.xpDelta)
: 0; : 0;
if (loading) {
return <div className="text-center text-gray-400 py-8">Chargement...</div>;
}
return ( return (
<div className="space-y-4"> <div className="space-y-4">
{users.length === 0 ? ( {users.length === 0 ? (

View File

@@ -90,6 +90,8 @@ export default function ChallengesSection({
setSuccessMessage("Défi créé avec succès !"); setSuccessMessage("Défi créé avec succès !");
setShowCreateForm(false); setShowCreateForm(false);
fetchChallenges(); fetchChallenges();
// Rafraîchir le badge des défis
window.dispatchEvent(new Event("refreshChallenges"));
setTimeout(() => setSuccessMessage(null), 5000); setTimeout(() => setSuccessMessage(null), 5000);
} else { } else {
setErrorMessage(result.error || "Erreur lors de la création du défi"); setErrorMessage(result.error || "Erreur lors de la création du défi");
@@ -107,6 +109,8 @@ export default function ChallengesSection({
"Défi accepté ! En attente de désignation du gagnant." "Défi accepté ! En attente de désignation du gagnant."
); );
fetchChallenges(); fetchChallenges();
// Rafraîchir le badge des défis
window.dispatchEvent(new Event("refreshChallenges"));
setTimeout(() => setSuccessMessage(null), 5000); setTimeout(() => setSuccessMessage(null), 5000);
} else { } else {
setErrorMessage(result.error || "Erreur lors de l'acceptation"); setErrorMessage(result.error || "Erreur lors de l'acceptation");
@@ -122,6 +126,8 @@ export default function ChallengesSection({
if (result.success) { if (result.success) {
setSuccessMessage("Défi annulé"); setSuccessMessage("Défi annulé");
fetchChallenges(); fetchChallenges();
// Rafraîchir le badge des défis
window.dispatchEvent(new Event("refreshChallenges"));
setTimeout(() => setSuccessMessage(null), 5000); setTimeout(() => setSuccessMessage(null), 5000);
} else { } else {
setErrorMessage(result.error || "Erreur lors de l'annulation"); setErrorMessage(result.error || "Erreur lors de l'annulation");
@@ -134,7 +140,7 @@ export default function ChallengesSection({
<section className="relative w-full min-h-screen flex flex-col items-center overflow-hidden pt-24 pb-16"> <section className="relative w-full min-h-screen flex flex-col items-center overflow-hidden pt-24 pb-16">
{/* Background Image */} {/* Background Image */}
<div <div
className="absolute inset-0 bg-cover bg-center bg-no-repeat" className="fixed inset-0 bg-cover bg-center bg-no-repeat"
style={{ style={{
backgroundImage: `url('${backgroundImage}')`, backgroundImage: `url('${backgroundImage}')`,
}} }}
@@ -153,9 +159,18 @@ export default function ChallengesSection({
</div> </div>
<div className="relative z-10 w-full max-w-6xl mx-auto px-4 sm:px-8 py-16"> <div className="relative z-10 w-full max-w-6xl mx-auto px-4 sm:px-8 py-16">
<SectionTitle variant="gradient" size="md" className="mb-8 text-center"> <SectionTitle
variant="gradient"
size="xl"
subtitle="Défiez vos collègues et gagnez des points"
className="mb-16"
>
DÉFIS ENTRE JOUEURS DÉFIS ENTRE JOUEURS
</SectionTitle> </SectionTitle>
<p className="text-gray-400 text-sm max-w-2xl mx-auto text-center mb-16">
Créez des défis personnalisés, acceptez ceux de vos collègues et
remportez des récompenses en points pour monter dans le classement
</p>
{successMessage && ( {successMessage && (
<Alert variant="success" className="mb-4"> <Alert variant="success" className="mb-4">

View File

@@ -115,6 +115,8 @@ export default function EventsPageSection({
// Ref pour tracker si on a déjà utilisé les données initiales // Ref pour tracker si on a déjà utilisé les données initiales
const hasUsedInitialData = useRef(hasInitialData); const hasUsedInitialData = useRef(hasInitialData);
// Ref pour tracker si on a déjà fait les appels API
const hasFetchedRegistrations = useRef(false);
// Séparer et trier les événements (du plus récent au plus ancien) // Séparer et trier les événements (du plus récent au plus ancien)
// Le statut est calculé automatiquement en fonction de la date // Le statut est calculé automatiquement en fonction de la date
@@ -179,11 +181,24 @@ export default function EventsPageSection({
return; return;
} }
// Si on a déjà fait les appels API, ne pas refaire
if (hasFetchedRegistrations.current) {
return;
}
// Si pas de session, ne rien faire (on garde les données vides) // Si pas de session, ne rien faire (on garde les données vides)
if (!session?.user?.id) { if (!session?.user?.id) {
return; return;
} }
// Si pas d'événements, ne rien faire
if (events.length === 0) {
return;
}
// Marquer qu'on va faire les appels
hasFetchedRegistrations.current = true;
// Charger les inscriptions depuis l'API seulement si on n'a pas de données initiales // Charger les inscriptions depuis l'API seulement si on n'a pas de données initiales
// On charge pour tous les événements (passés et à venir) pour permettre le feedback // On charge pour tous les événements (passés et à venir) pour permettre le feedback
const checkRegistrations = async () => { const checkRegistrations = async () => {
@@ -206,7 +221,8 @@ export default function EventsPageSection({
}; };
checkRegistrations(); checkRegistrations();
}, [session?.user?.id, events]); // eslint-disable-next-line react-hooks/exhaustive-deps
}, [session?.user?.id]);
// Fonctions pour le calendrier // Fonctions pour le calendrier
const getDaysInMonth = (date: Date) => { const getDaysInMonth = (date: Date) => {

View File

@@ -9,34 +9,61 @@ interface EventsSectionProps {
} }
export default function EventsSection({ events }: EventsSectionProps) { export default function EventsSection({ events }: EventsSectionProps) {
if (events.length === 0) {
return null;
}
return ( return (
<section className="w-full bg-gray-950 border-t border-pixel-gold/30 py-16"> <section
className="w-full py-16 border-t relative z-10"
style={{
backgroundColor: "var(--card-column)",
borderColor: "color-mix(in srgb, var(--pixel-gold) 50%, transparent)",
borderTopWidth: "2px",
}}
>
<div className="max-w-7xl mx-auto px-8"> <div className="max-w-7xl mx-auto px-8">
<div className="flex flex-col md:flex-row items-center justify-around gap-8"> {events.length === 0 ? (
{events.map((event, index) => ( <div className="text-center">
<div key={index} className="flex flex-col items-center"> <p
<div className="flex flex-col items-center mb-4"> className="text-base"
<span className="text-pixel-gold text-xs uppercase tracking-widest mb-2"> style={{ color: "var(--muted-foreground)" }}
Événement >
</span> Aucun événement à venir pour le moment
<div className="w-16 h-px bg-pixel-gold"></div> </p>
</div>
) : (
<div className="flex flex-col md:flex-row items-center justify-around gap-8">
{events.map((event, index) => (
<div key={index} className="flex flex-col items-center">
<div className="flex flex-col items-center mb-4">
<span
className="text-xs uppercase tracking-widest mb-2"
style={{ color: "var(--pixel-gold)" }}
>
Événement
</span>
<div
className="w-16 h-px"
style={{ backgroundColor: "var(--pixel-gold)" }}
></div>
</div>
<div
className="text-lg font-bold mb-2 uppercase tracking-wide"
style={{ color: "var(--foreground)" }}
>
{new Date(event.date).toLocaleDateString("fr-FR", {
day: "numeric",
month: "long",
year: "numeric",
})}
</div>
<div
className="text-base text-center"
style={{ color: "var(--foreground)" }}
>
{event.name}
</div>
</div> </div>
<div className="text-white text-lg font-bold mb-2 uppercase tracking-wide"> ))}
{new Date(event.date).toLocaleDateString("fr-FR", { </div>
day: "numeric", )}
month: "long",
year: "numeric",
})}
</div>
<div className="text-white text-base text-center">
{event.name}
</div>
</div>
))}
</div>
</div> </div>
</section> </section>
); );

View File

@@ -0,0 +1,169 @@
"use client";
import { useState } from "react";
import { useSession } from "next-auth/react";
import Card from "@/components/ui/Card";
import Button from "@/components/ui/Button";
import Avatar from "@/components/ui/Avatar";
import { requestToJoin } from "@/actions/houses/requests";
import { useTransition } from "react";
import Alert from "@/components/ui/Alert";
interface House {
id: string;
name: string;
description: string | null;
creator: {
id: string;
username: string;
avatar: string | null;
};
memberships?: Array<{
id: string;
role: string;
user: {
id: string;
username: string;
avatar: string | null;
score?: number;
level?: number;
};
}>;
_count?: {
memberships: number;
};
}
interface HouseCardProps {
house: House;
onRequestSent?: () => void;
}
export default function HouseCard({ house, onRequestSent }: HouseCardProps) {
const { data: session } = useSession();
const [isPending, startTransition] = useTransition();
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState<string | null>(null);
const isMember = house.memberships?.some(
(m) => m.user.id === session?.user?.id
);
const memberCount = house._count?.memberships || house.memberships?.length || 0;
const handleRequestToJoin = () => {
if (!session?.user?.id) return;
setError(null);
setSuccess(null);
startTransition(async () => {
const result = await requestToJoin(house.id);
if (result.success) {
// Rafraîchir le badge d'invitations/demandes dans le header
window.dispatchEvent(new Event("refreshInvitations"));
setSuccess("Demande envoyée avec succès");
onRequestSent?.();
} else {
setError(result.error || "Erreur lors de l'envoi de la demande");
}
});
};
return (
<Card className="p-4 sm:p-6">
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-start gap-4 mb-4">
<div className="flex-1 min-w-0">
<h3 className="text-lg sm:text-xl font-bold mb-2 break-words" style={{ color: "var(--foreground)" }}>
{house.name}
</h3>
{house.description && (
<p className="text-sm mb-2 break-words" style={{ color: "var(--muted-foreground)" }}>
{house.description}
</p>
)}
<div className="flex flex-wrap items-center gap-2 sm:gap-4 text-xs" style={{ color: "var(--muted-foreground)" }}>
<span>Créée par {house.creator.username}</span>
<span className="hidden sm:inline"></span>
<span>{memberCount} membre{memberCount > 1 ? "s" : ""}</span>
</div>
</div>
</div>
{error && (
<Alert variant="error" className="mb-4">
{error}
</Alert>
)}
{success && (
<Alert variant="success" className="mb-4">
{success}
</Alert>
)}
{session?.user?.id && !isMember && (
<Button
onClick={handleRequestToJoin}
disabled={isPending}
variant="primary"
size="sm"
className="w-full sm:w-auto"
>
{isPending ? "Envoi..." : "Demander à rejoindre"}
</Button>
)}
{isMember && (
<div className="text-xs mb-4" style={{ color: "var(--success)" }}>
Vous êtes membre
</div>
)}
{/* Members List */}
{house.memberships && house.memberships.length > 0 && (
<div className="mt-4 pt-4 border-t" style={{ borderColor: "var(--border)" }}>
<h4 className="text-xs font-bold uppercase tracking-wider mb-3" style={{ color: "var(--muted-foreground)" }}>
Membres ({house.memberships.length})
</h4>
<div className="flex flex-wrap gap-2">
{house.memberships.map((membership) => (
<div
key={membership.id}
className="flex items-center gap-2 p-2 rounded"
style={{ backgroundColor: "var(--card-hover)" }}
title={`${membership.user.username} (${membership.role})${membership.user.score !== undefined ? ` - ${membership.user.score} pts` : ""}`}
>
<Avatar
src={membership.user.avatar}
username={membership.user.username}
size="sm"
className="flex-shrink-0"
borderClassName="border-pixel-gold/30"
/>
<div className="min-w-0">
<div className="flex items-center gap-1">
<span className="text-xs font-semibold truncate" style={{ color: "var(--foreground)" }}>
{membership.user.username}
</span>
{membership.role === "OWNER" && (
<span className="text-[10px] uppercase" style={{ color: "var(--accent)" }}>
👑
</span>
)}
</div>
{membership.user.score !== undefined && membership.user.level !== undefined && (
<div className="text-[10px]" style={{ color: "var(--muted-foreground)" }}>
{membership.user.score} pts Lv.{membership.user.level}
</div>
)}
</div>
</div>
))}
</div>
</div>
)}
</Card>
);
}

View File

@@ -0,0 +1,94 @@
"use client";
import { useState, useTransition } from "react";
import Button from "@/components/ui/Button";
import Input from "@/components/ui/Input";
import Textarea from "@/components/ui/Textarea";
import Alert from "@/components/ui/Alert";
import { createHouse } from "@/actions/houses/create";
import { updateHouse } from "@/actions/houses/update";
interface HouseFormProps {
house?: {
id: string;
name: string;
description: string | null;
};
onSuccess?: () => void;
onCancel?: () => void;
}
export default function HouseForm({
house,
onSuccess,
onCancel,
}: HouseFormProps) {
const [name, setName] = useState(house?.name || "");
const [description, setDescription] = useState(house?.description || "");
const [error, setError] = useState<string | null>(null);
const [isPending, startTransition] = useTransition();
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
setError(null);
startTransition(async () => {
const result = house
? await updateHouse(house.id, { name, description: description || null })
: await createHouse({ name, description: description || null });
if (result.success) {
// Rafraîchir le score dans le header si on crée une maison (pas si on met à jour)
if (!house) {
window.dispatchEvent(new Event("refreshUserScore"));
}
onSuccess?.();
} else {
setError(result.error || "Une erreur est survenue");
}
});
};
return (
<form onSubmit={handleSubmit} className="space-y-4">
{error && <Alert variant="error">{error}</Alert>}
<Input
label="Nom de la maison"
value={name}
onChange={(e) => setName(e.target.value)}
required
minLength={3}
maxLength={50}
disabled={isPending}
/>
<Textarea
label="Description (optionnelle)"
value={description}
onChange={(e) => setDescription(e.target.value)}
maxLength={500}
disabled={isPending}
rows={4}
/>
<div className="flex flex-col sm:flex-row gap-2">
<Button type="submit" disabled={isPending} variant="primary" className="w-full sm:w-auto">
{isPending ? "Enregistrement..." : house ? "Modifier" : "Créer"}
</Button>
{onCancel && (
<Button
type="button"
onClick={onCancel}
disabled={isPending}
variant="secondary"
className="w-full sm:w-auto"
>
Annuler
</Button>
)}
</div>
</form>
);
}

View File

@@ -0,0 +1,628 @@
"use client";
import { useState, useEffect, useTransition, useCallback } from "react";
import { useSession } from "next-auth/react";
import Card from "@/components/ui/Card";
import Button from "@/components/ui/Button";
import HouseForm from "./HouseForm";
import RequestList from "./RequestList";
import Alert from "@/components/ui/Alert";
import { deleteHouse, leaveHouse, removeMember } from "@/actions/houses/update";
import { inviteUser, cancelInvitation } from "@/actions/houses/invitations";
interface House {
id: string;
name: string;
description: string | null;
creator: {
id: string;
username: string;
avatar: string | null;
};
memberships?: Array<{
id: string;
role: string;
user: {
id: string;
username: string;
avatar: string | null;
score?: number;
level?: number;
};
}>;
}
interface User {
id: string;
username: string;
avatar: string | null;
}
interface HouseInvitation {
id: string;
invitee: {
id: string;
username: string;
avatar: string | null;
};
inviter: {
id: string;
username: string;
avatar: string | null;
};
status: string;
createdAt: string;
}
interface HouseManagementProps {
house: House | null;
users?: User[];
requests?: Array<{
id: string;
requester: {
id: string;
username: string;
avatar: string | null;
};
status: string;
createdAt: string;
}>;
onUpdate?: () => void;
}
interface Request {
id: string;
requester: {
id: string;
username: string;
avatar: string | null;
};
status: string;
createdAt: string;
}
export default function HouseManagement({
house,
users = [],
requests: initialRequests = [],
onUpdate,
}: HouseManagementProps) {
const { data: session } = useSession();
const [isEditing, setIsEditing] = useState(false);
const [showInviteForm, setShowInviteForm] = useState(false);
const [selectedUserId, setSelectedUserId] = useState("");
const [requests, setRequests] = useState<Request[]>(initialRequests);
const [invitations, setInvitations] = useState<HouseInvitation[]>([]);
const [isPending, startTransition] = useTransition();
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState<string | null>(null);
const userRole = house?.memberships?.find(
(m) => m.user.id === session?.user?.id
)?.role;
const isOwner = userRole === "OWNER";
const isAdmin = userRole === "ADMIN" || isOwner;
const pendingRequests = requests.filter((r) => r.status === "PENDING");
useEffect(() => {
const fetchRequests = async () => {
if (!house || !isAdmin) return;
try {
const response = await fetch(
`/api/houses/${house.id}/requests?status=PENDING`
);
if (response.ok) {
const data = await response.json();
setRequests(data);
}
} catch (error) {
console.error("Error fetching requests:", error);
}
};
fetchRequests();
}, [house, isAdmin]);
const fetchInvitations = useCallback(async () => {
if (!house || !isAdmin) return;
try {
const response = await fetch(
`/api/houses/${house.id}/invitations?status=PENDING`
);
if (response.ok) {
const data = await response.json();
setInvitations(data);
}
} catch (error) {
console.error("Error fetching invitations:", error);
}
}, [house, isAdmin]);
useEffect(() => {
// Utiliser un timeout pour éviter l'appel synchrone de setState dans l'effect
const timeout = setTimeout(() => {
fetchInvitations();
}, 0);
return () => clearTimeout(timeout);
}, [fetchInvitations]);
const handleUpdate = useCallback(() => {
fetchInvitations();
onUpdate?.();
}, [fetchInvitations, onUpdate]);
const handleDelete = () => {
if (
!house ||
!confirm("Êtes-vous sûr de vouloir supprimer cette maison ?")
) {
return;
}
setError(null);
startTransition(async () => {
const result = await deleteHouse(house.id);
if (result.success) {
// Rafraîchir le score dans le header (le créateur perd des points)
window.dispatchEvent(new Event("refreshUserScore"));
handleUpdate();
} else {
setError(result.error || "Erreur lors de la suppression");
}
});
};
const handleLeave = () => {
if (!house || !confirm("Êtes-vous sûr de vouloir quitter cette maison ?")) {
return;
}
setError(null);
startTransition(async () => {
const result = await leaveHouse(house.id);
if (result.success) {
window.dispatchEvent(new Event("refreshUserScore"));
handleUpdate();
} else {
setError(result.error || "Erreur lors de la sortie");
}
});
};
const handleInvite = () => {
if (!house || !selectedUserId) return;
setError(null);
setSuccess(null);
startTransition(async () => {
const result = await inviteUser(house.id, selectedUserId);
if (result.success) {
// Rafraîchir le badge d'invitations/demandes dans le header (pour l'invité)
window.dispatchEvent(new Event("refreshInvitations"));
setSuccess("Invitation envoyée");
setShowInviteForm(false);
setSelectedUserId("");
// Rafraîchir la liste des invitations
await fetchInvitations();
handleUpdate();
} else {
setError(result.error || "Erreur lors de l'envoi de l'invitation");
}
});
};
const availableUsers = users.filter(
(u) =>
u.id !== session?.user?.id &&
!house?.memberships?.some((m) => m.user.id === u.id)
);
if (!house) {
return (
<Card className="p-6">
<h2
className="text-lg sm:text-xl font-bold mb-4"
style={{ color: "var(--foreground)" }}
>
Ma Maison
</h2>
<p
className="text-sm mb-4"
style={{ color: "var(--muted-foreground)" }}
>
Vous n&apos;êtes membre d&apos;aucune maison pour le moment.
</p>
</Card>
);
}
return (
<div className="space-y-6">
<Card
className="p-4 sm:p-6"
style={{
borderColor: `color-mix(in srgb, var(--accent) 40%, var(--border))`,
borderWidth: "2px",
boxShadow: `0 0 20px color-mix(in srgb, var(--accent) 10%, transparent)`,
}}
>
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-start gap-4 mb-4">
<div className="flex-1 min-w-0">
<h3
className="text-xl sm:text-2xl font-bold mb-2 break-words"
style={{
color: "var(--accent)",
textShadow: `0 0 10px color-mix(in srgb, var(--accent) 30%, transparent)`,
}}
>
{house.name}
</h3>
{house.description && (
<p
className="text-sm mt-2 break-words"
style={{ color: "var(--muted-foreground)" }}
>
{house.description}
</p>
)}
</div>
<div className="flex flex-wrap gap-2 sm:flex-nowrap">
{isAdmin && (
<>
<Button
onClick={() => setIsEditing(!isEditing)}
variant="secondary"
size="sm"
className="flex-1 sm:flex-none"
>
{isEditing ? "Annuler" : "Modifier"}
</Button>
{isOwner && (
<Button
onClick={handleDelete}
variant="danger"
size="sm"
className="flex-1 sm:flex-none"
>
Supprimer
</Button>
)}
</>
)}
{!isOwner && (
<Button
onClick={handleLeave}
variant="danger"
size="sm"
className="flex-1 sm:flex-none"
>
Quitter
</Button>
)}
</div>
</div>
{error && (
<Alert variant="error" className="mb-4">
{error}
</Alert>
)}
{success && (
<Alert variant="success" className="mb-4">
{success}
</Alert>
)}
{isEditing ? (
<HouseForm
house={house}
onSuccess={() => {
setIsEditing(false);
handleUpdate();
}}
onCancel={() => setIsEditing(false)}
/>
) : (
<div>
<h4
className="text-sm font-semibold uppercase tracking-wider mb-3"
style={{
color: "var(--primary)",
borderBottom: `2px solid color-mix(in srgb, var(--primary) 30%, transparent)`,
paddingBottom: "0.5rem",
}}
>
Membres ({house.memberships?.length ?? 0})
{isAdmin && invitations.length > 0 && (
<span className="ml-2 text-xs normal-case" style={{ color: "var(--muted-foreground)" }}>
{invitations.length} invitation{invitations.length > 1 ? "s" : ""} en cours
</span>
)}
</h4>
<div className="space-y-2">
{(house.memberships || []).map((membership) => {
const isCurrentUser = membership.user.id === session?.user?.id;
const roleColor =
membership.role === "OWNER"
? "var(--accent)"
: membership.role === "ADMIN"
? "var(--primary)"
: "var(--muted-foreground)";
return (
<div
key={membership.id}
className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 p-3 rounded"
style={{
backgroundColor: isCurrentUser
? "color-mix(in srgb, var(--primary) 10%, var(--card-hover))"
: "var(--card-hover)",
borderLeft: `3px solid ${roleColor}`,
borderColor: isCurrentUser
? "var(--primary)"
: "transparent",
}}
>
<div className="flex items-center gap-2 min-w-0 flex-1">
{membership.user.avatar && (
<img
src={membership.user.avatar}
alt={membership.user.username}
className="w-8 h-8 rounded-full flex-shrink-0 border-2"
style={{ borderColor: roleColor }}
/>
)}
<div className="min-w-0">
<span
className="font-semibold block sm:inline"
style={{
color: isCurrentUser
? "var(--primary)"
: "var(--foreground)",
}}
>
{membership.user.username}
{isCurrentUser && " (Vous)"}
</span>
<span
className="text-xs block sm:inline sm:ml-2"
style={{ color: "var(--muted-foreground)" }}
>
<span style={{ color: "var(--success)" }}>
{membership.user.score} pts
</span>
{" • "}
<span style={{ color: "var(--blue)" }}>
Niveau {membership.user.level}
</span>
</span>
</div>
</div>
<div className="flex items-center gap-2 flex-shrink-0">
<span
className="text-xs uppercase px-2 py-1 rounded font-bold"
style={{
color: roleColor,
backgroundColor: `color-mix(in srgb, ${roleColor} 15%, transparent)`,
border: `1px solid color-mix(in srgb, ${roleColor} 30%, transparent)`,
}}
>
{membership.role === "OWNER" && "👑 "}
{membership.role}
</span>
{isAdmin &&
!isCurrentUser &&
(isOwner || membership.role === "MEMBER") &&
membership.role !== "OWNER" && (
<Button
onClick={() => {
if (
confirm(
`Êtes-vous sûr de vouloir retirer ${membership.user.username} de la maison ?`
)
) {
startTransition(async () => {
const result = await removeMember(
house.id,
membership.user.id
);
if (result.success) {
// Rafraîchir le score dans le header (le membre retiré perd des points)
window.dispatchEvent(
new Event("refreshUserScore")
);
handleUpdate();
} else {
setError(
result.error ||
"Erreur lors du retrait du membre"
);
}
});
}
}}
disabled={isPending}
variant="danger"
size="sm"
>
Retirer
</Button>
)}
</div>
</div>
);
})}
</div>
{isAdmin && invitations.length > 0 && (
<div className="mt-4">
<h5
className="text-xs font-semibold uppercase tracking-wider mb-2"
style={{
color: "var(--primary)",
opacity: 0.7,
}}
>
Invitations en cours
</h5>
<div className="space-y-2">
{invitations
.filter((inv) => inv.status === "PENDING")
.map((invitation) => (
<div
key={invitation.id}
className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 p-3 rounded"
style={{
backgroundColor: "var(--card-hover)",
borderLeft: `3px solid var(--primary)`,
opacity: 0.8,
}}
>
<div className="flex items-center gap-2 min-w-0 flex-1">
{invitation.invitee.avatar && (
<img
src={invitation.invitee.avatar}
alt={invitation.invitee.username}
className="w-8 h-8 rounded-full flex-shrink-0 border-2"
style={{ borderColor: "var(--primary)" }}
/>
)}
<div className="min-w-0">
<span
className="font-semibold block sm:inline"
style={{ color: "var(--foreground)" }}
>
{invitation.invitee.username}
</span>
<span
className="text-xs block sm:inline sm:ml-2"
style={{ color: "var(--muted-foreground)" }}
>
Invité par {invitation.inviter.username}
</span>
</div>
</div>
<div className="flex items-center gap-2 flex-shrink-0">
<span
className="text-xs uppercase px-2 py-1 rounded font-bold"
style={{
color: "var(--primary)",
backgroundColor: `color-mix(in srgb, var(--primary) 15%, transparent)`,
border: `1px solid color-mix(in srgb, var(--primary) 30%, transparent)`,
}}
>
En attente
</span>
<Button
onClick={() => {
if (
confirm(
`Êtes-vous sûr de vouloir annuler l'invitation pour ${invitation.invitee.username} ?`
)
) {
startTransition(async () => {
const result = await cancelInvitation(
invitation.id
);
if (result.success) {
window.dispatchEvent(
new Event("refreshInvitations")
);
handleUpdate();
} else {
setError(
result.error ||
"Erreur lors de l'annulation"
);
}
});
}
}}
disabled={isPending}
variant="danger"
size="sm"
>
Annuler
</Button>
</div>
</div>
))}
</div>
</div>
)}
{isAdmin && (
<div className="mt-4">
{showInviteForm ? (
<div className="space-y-2">
<select
value={selectedUserId}
onChange={(e) => setSelectedUserId(e.target.value)}
className="w-full p-2 rounded border"
style={{
backgroundColor: "var(--input)",
borderColor: "var(--border)",
color: "var(--foreground)",
}}
>
<option value="">Sélectionner un utilisateur</option>
{availableUsers.map((user) => (
<option key={user.id} value={user.id}>
{user.username}
</option>
))}
</select>
<div className="flex gap-2">
<Button
onClick={handleInvite}
disabled={!selectedUserId || isPending}
variant="primary"
size="sm"
>
{isPending ? "Envoi..." : "Inviter"}
</Button>
<Button
onClick={() => {
setShowInviteForm(false);
setSelectedUserId("");
}}
variant="secondary"
size="sm"
>
Annuler
</Button>
</div>
</div>
) : (
<Button
onClick={() => setShowInviteForm(true)}
variant="primary"
size="sm"
>
Inviter un utilisateur
</Button>
)}
</div>
)}
</div>
)}
</Card>
{isAdmin && pendingRequests.length > 0 && (
<Card className="p-4 sm:p-6">
<h2
className="text-lg sm:text-xl font-bold mb-4"
style={{
color: "var(--purple)",
borderBottom: `2px solid color-mix(in srgb, var(--purple) 30%, transparent)`,
paddingBottom: "0.5rem",
}}
>
Demandes d&apos;adhésion
</h2>
<RequestList requests={pendingRequests} onUpdate={handleUpdate} />
</Card>
)}
</div>
);
}

View File

@@ -0,0 +1,265 @@
"use client";
import { useState, useEffect, useCallback } from "react";
import { useSession } from "next-auth/react";
import Card from "@/components/ui/Card";
import Button from "@/components/ui/Button";
import SectionTitle from "@/components/ui/SectionTitle";
import BackgroundSection from "@/components/ui/BackgroundSection";
import HouseCard from "./HouseCard";
import HouseForm from "./HouseForm";
import HouseManagement from "./HouseManagement";
import InvitationList from "./InvitationList";
import Input from "@/components/ui/Input";
interface House {
id: string;
name: string;
description: string | null;
creator: {
id: string;
username: string;
avatar: string | null;
};
memberships?: Array<{
id: string;
role: string;
user: {
id: string;
username: string;
avatar: string | null;
score?: number;
level?: number;
};
}>;
_count?: {
memberships: number;
};
}
interface User {
id: string;
username: string;
avatar: string | null;
}
interface HousesSectionProps {
initialHouses?: House[];
initialMyHouse?: House | null;
initialUsers?: User[];
initialInvitations?: Array<{
id: string;
house: {
id: string;
name: string;
};
inviter: {
id: string;
username: string;
avatar: string | null;
};
status: string;
createdAt: string;
}>;
backgroundImage: string;
}
export default function HousesSection({
initialHouses = [],
initialMyHouse = null,
initialUsers = [],
initialInvitations = [],
backgroundImage,
}: HousesSectionProps) {
const { data: session } = useSession();
const [houses, setHouses] = useState<House[]>(initialHouses);
const [myHouse, setMyHouse] = useState<House | null>(initialMyHouse);
const [invitations, setInvitations] = useState(initialInvitations);
const [showCreateForm, setShowCreateForm] = useState(false);
const [searchTerm, setSearchTerm] = useState("");
const fetchHouses = useCallback(async () => {
try {
const params = new URLSearchParams();
if (searchTerm) {
params.append("search", searchTerm);
}
params.append("include", "members,creator");
const response = await fetch(`/api/houses?${params}`);
if (response.ok) {
const data = await response.json();
setHouses(data);
}
} catch (error) {
console.error("Error fetching houses:", error);
}
}, [searchTerm]);
const fetchMyHouse = async () => {
try {
const response = await fetch("/api/houses/my-house");
if (response.ok) {
const data = await response.json();
setMyHouse(data);
} else if (response.status === 404) {
setMyHouse(null);
}
} catch (error) {
console.error("Error fetching my house:", error);
}
};
const fetchInvitations = async () => {
try {
const response = await fetch("/api/invitations?status=PENDING");
if (response.ok) {
const data = await response.json();
setInvitations(data);
}
} catch (error) {
console.error("Error fetching invitations:", error);
}
};
useEffect(() => {
if (searchTerm) {
const timeout = setTimeout(() => {
fetchHouses();
}, 300);
return () => clearTimeout(timeout);
} else {
// Utiliser un timeout pour éviter setState synchrone dans effect
const timeout = setTimeout(() => {
fetchHouses();
}, 0);
return () => clearTimeout(timeout);
}
}, [searchTerm, fetchHouses]);
const handleUpdate = () => {
fetchMyHouse();
fetchHouses();
fetchInvitations();
};
const filteredHouses = houses.filter((house) => {
if (!myHouse) return true;
return house.id !== myHouse.id;
});
return (
<BackgroundSection backgroundImage={backgroundImage}>
{/* Title Section */}
<SectionTitle
variant="gradient"
size="xl"
subtitle="Rejoignez une maison ou créez la vôtre"
className="mb-16"
>
MAISONS
</SectionTitle>
<p className="text-gray-400 text-sm max-w-2xl mx-auto text-center mb-16">
Formez des équipes, créez votre propre maison et rivalisez avec les
autres maisons pour dominer le classement collectif
</p>
<div className="space-y-4 sm:space-y-6">
{session?.user && (
<>
{invitations.length > 0 && (
<Card className="p-4 sm:p-6">
<h2
className="text-lg sm:text-xl font-bold mb-4"
style={{ color: "var(--foreground)" }}
>
Mes Invitations
</h2>
<InvitationList
invitations={invitations}
onUpdate={handleUpdate}
/>
</Card>
)}
<Card className="p-4 sm:p-6">
<h2
className="text-lg sm:text-xl font-bold mb-4"
style={{ color: "var(--foreground)" }}
>
Ma Maison
</h2>
{myHouse ? (
<HouseManagement
house={myHouse}
users={initialUsers}
onUpdate={handleUpdate}
/>
) : (
<div>
{showCreateForm ? (
<HouseForm
onSuccess={() => {
setShowCreateForm(false);
handleUpdate();
}}
onCancel={() => setShowCreateForm(false)}
/>
) : (
<div>
<p
className="text-sm mb-4 break-words"
style={{ color: "var(--muted-foreground)" }}
>
Vous n&apos;êtes membre d&apos;aucune maison. Créez-en
une ou demandez à rejoindre une maison existante.
</p>
<Button
onClick={() => setShowCreateForm(true)}
variant="primary"
className="w-full sm:w-auto"
>
Créer une maison
</Button>
</div>
)}
</div>
)}
</Card>
</>
)}
<Card className="p-4 sm:p-6">
<h2
className="text-lg sm:text-xl font-bold mb-4"
style={{ color: "var(--foreground)" }}
>
Toutes les Maisons
</h2>
<div className="mb-4">
<Input
placeholder="Rechercher une maison..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
{filteredHouses.length === 0 ? (
<p className="text-sm" style={{ color: "var(--muted-foreground)" }}>
Aucune maison trouvée
</p>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{filteredHouses.map((house) => (
<HouseCard
key={house.id}
house={house}
onRequestSent={handleUpdate}
/>
))}
</div>
)}
</Card>
</div>
</BackgroundSection>
);
}

View File

@@ -0,0 +1,129 @@
"use client";
import { useState, useTransition } from "react";
import Card from "@/components/ui/Card";
import Button from "@/components/ui/Button";
import {
acceptInvitation,
rejectInvitation,
} from "@/actions/houses/invitations";
import Alert from "@/components/ui/Alert";
interface Invitation {
id: string;
house: {
id: string;
name: string;
};
inviter: {
id: string;
username: string;
avatar: string | null;
};
status: string;
createdAt: string;
}
interface InvitationListProps {
invitations: Invitation[];
onUpdate?: () => void;
}
export default function InvitationList({
invitations,
onUpdate,
}: InvitationListProps) {
const [isPending, startTransition] = useTransition();
const [error, setError] = useState<string | null>(null);
const handleAccept = (invitationId: string) => {
setError(null);
startTransition(async () => {
const result = await acceptInvitation(invitationId);
if (result.success) {
// Rafraîchir le score dans le header (l'utilisateur reçoit des points)
window.dispatchEvent(new Event("refreshUserScore"));
// Rafraîchir le badge d'invitations dans le header
window.dispatchEvent(new Event("refreshInvitations"));
onUpdate?.();
} else {
setError(result.error || "Erreur lors de l'acceptation");
}
});
};
const handleReject = (invitationId: string) => {
setError(null);
startTransition(async () => {
const result = await rejectInvitation(invitationId);
if (result.success) {
// Rafraîchir le badge d'invitations dans le header
window.dispatchEvent(new Event("refreshInvitations"));
onUpdate?.();
} else {
setError(result.error || "Erreur lors du refus");
}
});
};
if (invitations.length === 0) {
return (
<p className="text-sm" style={{ color: "var(--muted-foreground)" }}>
Aucune invitation en attente
</p>
);
}
return (
<div className="space-y-4">
{error && <Alert variant="error">{error}</Alert>}
{invitations.map((invitation) => (
<Card key={invitation.id} className="p-4">
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-start gap-3">
<div className="flex-1 min-w-0">
<h4 className="font-bold mb-1 break-words" style={{ color: "var(--foreground)" }}>
Invitation de {invitation.inviter.username}
</h4>
<p className="text-sm mb-2 break-words" style={{ color: "var(--muted-foreground)" }}>
Pour rejoindre la maison <strong>{invitation.house.name}</strong>
</p>
</div>
{invitation.status === "PENDING" && (
<div className="flex gap-2 sm:flex-nowrap">
<Button
onClick={() => handleAccept(invitation.id)}
disabled={isPending}
variant="success"
size="sm"
className="flex-1 sm:flex-none"
>
Accepter
</Button>
<Button
onClick={() => handleReject(invitation.id)}
disabled={isPending}
variant="danger"
size="sm"
className="flex-1 sm:flex-none"
>
Refuser
</Button>
</div>
)}
{invitation.status === "ACCEPTED" && (
<span className="text-xs flex-shrink-0" style={{ color: "var(--success)" }}>
Acceptée
</span>
)}
{invitation.status === "REJECTED" && (
<span className="text-xs flex-shrink-0" style={{ color: "var(--destructive)" }}>
Refusée
</span>
)}
</div>
</Card>
))}
</div>
);
}

View File

@@ -0,0 +1,125 @@
"use client";
import { useState, useTransition } from "react";
import Card from "@/components/ui/Card";
import Button from "@/components/ui/Button";
import {
acceptRequest,
rejectRequest,
} from "@/actions/houses/requests";
import Alert from "@/components/ui/Alert";
interface Request {
id: string;
requester: {
id: string;
username: string;
avatar: string | null;
};
status: string;
createdAt: string;
}
interface RequestListProps {
requests: Request[];
onUpdate?: () => void;
}
export default function RequestList({
requests,
onUpdate,
}: RequestListProps) {
const [isPending, startTransition] = useTransition();
const [error, setError] = useState<string | null>(null);
const handleAccept = (requestId: string) => {
setError(null);
startTransition(async () => {
const result = await acceptRequest(requestId);
if (result.success) {
// Rafraîchir le score dans le header (le requester reçoit des points)
window.dispatchEvent(new Event("refreshUserScore"));
// Rafraîchir le badge d'invitations/demandes dans le header (le requester n'a plus de demande en attente)
window.dispatchEvent(new Event("refreshInvitations"));
onUpdate?.();
} else {
setError(result.error || "Erreur lors de l'acceptation");
}
});
};
const handleReject = (requestId: string) => {
setError(null);
startTransition(async () => {
const result = await rejectRequest(requestId);
if (result.success) {
// Rafraîchir le badge d'invitations/demandes dans le header (le requester n'a plus de demande en attente)
window.dispatchEvent(new Event("refreshInvitations"));
onUpdate?.();
} else {
setError(result.error || "Erreur lors du refus");
}
});
};
if (requests.length === 0) {
return (
<p className="text-sm" style={{ color: "var(--muted-foreground)" }}>
Aucune demande en attente
</p>
);
}
return (
<div className="space-y-4">
{error && <Alert variant="error">{error}</Alert>}
{requests.map((request) => (
<Card key={request.id} className="p-4">
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-start gap-3">
<div className="flex-1 min-w-0">
<h4 className="font-bold mb-1 break-words" style={{ color: "var(--foreground)" }}>
{request.requester.username}
</h4>
<p className="text-sm break-words" style={{ color: "var(--muted-foreground)" }}>
souhaite rejoindre votre maison
</p>
</div>
{request.status === "PENDING" && (
<div className="flex gap-2 sm:flex-nowrap">
<Button
onClick={() => handleAccept(request.id)}
disabled={isPending}
variant="success"
size="sm"
className="flex-1 sm:flex-none"
>
Accepter
</Button>
<Button
onClick={() => handleReject(request.id)}
disabled={isPending}
variant="danger"
size="sm"
className="flex-1 sm:flex-none"
>
Refuser
</Button>
</div>
)}
{request.status === "ACCEPTED" && (
<span className="text-xs flex-shrink-0" style={{ color: "var(--success)" }}>
Acceptée
</span>
)}
{request.status === "REJECTED" && (
<span className="text-xs flex-shrink-0" style={{ color: "var(--destructive)" }}>
Refusée
</span>
)}
</div>
</Card>
))}
</div>
);
}

View File

@@ -5,7 +5,7 @@ import Link from "next/link";
export default function Footer() { export default function Footer() {
return ( return (
<footer <footer
className="w-full py-6 px-4 sm:px-8 border-t" className="w-full py-6 px-4 sm:px-8 border-t relative z-10"
style={{ style={{
backgroundColor: "var(--background)", backgroundColor: "var(--background)",
borderColor: "color-mix(in srgb, var(--gray-800) 30%, transparent)", borderColor: "color-mix(in srgb, var(--gray-800) 30%, transparent)",

View File

@@ -26,8 +26,29 @@ interface LeaderboardEntry {
characterClass?: CharacterClass | null; characterClass?: CharacterClass | null;
} }
interface HouseMember {
id: string;
username: string;
avatar: string | null;
score: number;
level: number;
role: string;
}
interface HouseLeaderboardEntry {
rank: number;
houseId: string;
houseName: string;
totalScore: number;
memberCount: number;
averageScore: number;
description: string | null;
members: HouseMember[];
}
interface LeaderboardSectionProps { interface LeaderboardSectionProps {
leaderboard: LeaderboardEntry[]; leaderboard: LeaderboardEntry[];
houseLeaderboard: HouseLeaderboardEntry[];
backgroundImage: string; backgroundImage: string;
} }
@@ -38,25 +59,34 @@ const formatScore = (score: number): string => {
export default function LeaderboardSection({ export default function LeaderboardSection({
leaderboard, leaderboard,
houseLeaderboard,
backgroundImage, backgroundImage,
}: LeaderboardSectionProps) { }: LeaderboardSectionProps) {
const [selectedEntry, setSelectedEntry] = useState<LeaderboardEntry | null>( const [selectedEntry, setSelectedEntry] = useState<LeaderboardEntry | null>(
null null
); );
const [selectedHouse, setSelectedHouse] = useState<HouseLeaderboardEntry | null>(
null
);
return ( return (
<BackgroundSection backgroundImage={backgroundImage}> <BackgroundSection backgroundImage={backgroundImage}>
{/* Title Section */} {/* Title Section */}
<SectionTitle <SectionTitle
variant="gradient" variant="gradient"
size="lg" size="xl"
subtitle="Top Players" subtitle="Top Players"
className="mb-12 overflow-hidden" className="mb-16"
> >
LEADERBOARD LEADERBOARD
</SectionTitle> </SectionTitle>
<p className="text-gray-400 text-sm max-w-2xl mx-auto text-center mb-16">
Consultez le classement des meilleurs joueurs et des maisons les plus
performantes. Montez dans les rangs en participant aux événements et en
relevant des défis
</p>
{/* Leaderboard Table */} {/* Players Leaderboard Table */}
<div className="bg-black/60 border border-pixel-gold/30 rounded-lg backdrop-blur-sm overflow-x-auto"> <div className="bg-black/60 border border-pixel-gold/30 rounded-lg backdrop-blur-sm overflow-x-auto">
{/* Header */} {/* Header */}
<div className="bg-gray-900/80 border-b border-pixel-gold/30 grid grid-cols-12 gap-2 sm:gap-4 p-2 sm:p-4 font-bold text-[10px] sm:text-xs uppercase tracking-widest text-gray-300"> <div className="bg-gray-900/80 border-b border-pixel-gold/30 grid grid-cols-12 gap-2 sm:gap-4 p-2 sm:p-4 font-bold text-[10px] sm:text-xs uppercase tracking-widest text-gray-300">
@@ -143,6 +173,90 @@ export default function LeaderboardSection({
</div> </div>
</div> </div>
{/* House Leaderboard Table */}
<div className="mt-12">
<SectionTitle
variant="gradient"
size="md"
subtitle="Top Houses"
className="mb-8 overflow-hidden"
>
MAISONS
</SectionTitle>
<div className="bg-black/60 border border-pixel-gold/30 rounded-lg backdrop-blur-sm overflow-x-auto">
{/* Header */}
<div className="bg-gray-900/80 border-b border-pixel-gold/30 grid grid-cols-12 gap-2 sm:gap-4 p-2 sm:p-4 font-bold text-[10px] sm:text-xs uppercase tracking-widest text-gray-300">
<div className="col-span-2 sm:col-span-1 text-center">Rank</div>
<div className="col-span-5 sm:col-span-6">Maison</div>
<div className="col-span-3 text-right">Score Total</div>
<div className="col-span-2 text-right">Membres</div>
</div>
{/* Entries */}
<div className="divide-y divide-pixel-gold/10 overflow-visible">
{houseLeaderboard.map((house) => (
<div
key={house.houseId}
className={`grid grid-cols-12 gap-2 sm:gap-4 p-2 sm:p-4 hover:bg-gray-900/50 transition relative cursor-pointer ${
house.rank <= 3
? "bg-gradient-to-r from-pixel-gold/10 via-pixel-gold/5 to-transparent"
: "bg-black/40"
}`}
onClick={() => setSelectedHouse(house)}
>
{/* Rank */}
<div className="col-span-2 sm:col-span-1 flex items-center justify-center">
<span
className={`inline-flex items-center justify-center w-8 h-8 sm:w-10 sm:h-10 rounded-full font-bold text-xs sm:text-sm ${
house.rank === 1
? "bg-gradient-to-br from-pixel-gold to-orange-500 text-black shadow-lg shadow-pixel-gold/50"
: house.rank === 2
? "bg-gradient-to-br from-gray-400 to-gray-500 text-black"
: house.rank === 3
? "bg-gradient-to-br from-orange-700 to-orange-800 text-white"
: "bg-gray-900 text-gray-400 border border-gray-800"
}`}
>
{house.rank}
</span>
</div>
{/* House Name */}
<div className="col-span-5 sm:col-span-6 flex items-center gap-2 sm:gap-3 min-w-0">
<div className="flex items-center gap-1 sm:gap-2 min-w-0">
<span
className={`font-bold text-xs sm:text-sm break-words ${
house.rank <= 3 ? "text-pixel-gold" : "text-white"
}`}
>
{house.houseName}
</span>
{house.rank <= 3 && (
<span className="text-pixel-gold text-xs"></span>
)}
</div>
</div>
{/* Total Score */}
<div className="col-span-3 flex items-center justify-end">
<span className="font-mono text-gray-300 text-xs sm:text-sm">
{formatScore(house.totalScore)}
</span>
</div>
{/* Member Count */}
<div className="col-span-2 flex items-center justify-end">
<span className="font-bold text-gray-400 text-xs sm:text-sm">
{house.memberCount}
</span>
</div>
</div>
))}
</div>
</div>
</div>
{/* Footer Info */} {/* Footer Info */}
<div className="mt-8 text-center"> <div className="mt-8 text-center">
<p className="text-gray-500 text-sm"> <p className="text-gray-500 text-sm">
@@ -151,6 +265,112 @@ export default function LeaderboardSection({
<p className="text-gray-600 text-xs mt-2">Rankings update every hour</p> <p className="text-gray-600 text-xs mt-2">Rankings update every hour</p>
</div> </div>
{/* House Modal */}
{selectedHouse && (
<Modal
isOpen={!!selectedHouse}
onClose={() => setSelectedHouse(null)}
size="md"
>
<div className="p-4 sm:p-8">
{/* Header */}
<div className="flex items-center justify-between mb-6">
<h2 className="text-xl sm:text-3xl font-bold text-pixel-gold uppercase tracking-wider break-words">
{selectedHouse.houseName}
</h2>
<CloseButton onClick={() => setSelectedHouse(null)} size="md" />
</div>
{/* Stats */}
<div className="grid grid-cols-3 gap-4 mb-6">
<Card variant="default" className="p-4">
<div className="text-xs text-gray-400 uppercase tracking-widest mb-1">
Rank
</div>
<div className="text-2xl font-bold text-pixel-gold">
#{selectedHouse.rank}
</div>
</Card>
<Card variant="default" className="p-4">
<div className="text-xs text-gray-400 uppercase tracking-widest mb-1">
Score Total
</div>
<div className="text-2xl font-bold text-pixel-gold">
{formatScore(selectedHouse.totalScore)}
</div>
</Card>
<Card variant="default" className="p-4">
<div className="text-xs text-gray-400 uppercase tracking-widest mb-1">
Membres
</div>
<div className="text-2xl font-bold text-pixel-gold">
{selectedHouse.memberCount}
</div>
</Card>
</div>
{/* Members List */}
<div className="border-t border-pixel-gold/30 pt-6 mb-6">
<div className="text-xs text-pixel-gold uppercase tracking-widest mb-4 font-bold">
Membres ({selectedHouse.memberCount})
</div>
<div className="space-y-2">
{selectedHouse.members.map((member) => (
<div
key={member.id}
className="flex items-center justify-between p-3 rounded"
style={{ backgroundColor: "var(--card-hover)" }}
>
<div className="flex items-center gap-3">
<Avatar
src={member.avatar}
username={member.username}
size="sm"
className="flex-shrink-0"
borderClassName="border-pixel-gold/30"
/>
<div>
<div className="flex items-center gap-2">
<span className="font-semibold text-sm" style={{ color: "var(--foreground)" }}>
{member.username}
</span>
<span className="text-xs uppercase" style={{ color: "var(--accent)" }}>
{member.role}
</span>
</div>
<div className="text-xs" style={{ color: "var(--muted-foreground)" }}>
Niveau {member.level}
</div>
</div>
</div>
<div className="text-right">
<div className="font-mono text-sm font-bold" style={{ color: "var(--foreground)" }}>
{formatScore(member.score)}
</div>
<div className="text-xs" style={{ color: "var(--muted-foreground)" }}>
points
</div>
</div>
</div>
))}
</div>
</div>
{/* Description */}
{selectedHouse.description && (
<div className="border-t border-pixel-gold/30 pt-6">
<div className="text-xs text-pixel-gold uppercase tracking-widest mb-3 font-bold">
Description
</div>
<p className="text-gray-200 leading-relaxed whitespace-pre-wrap break-words">
{selectedHouse.description}
</p>
</div>
)}
</div>
</Modal>
)}
{/* Character Modal */} {/* Character Modal */}
{selectedEntry && ( {selectedEntry && (
<Modal <Modal

View File

@@ -14,15 +14,14 @@ export default function ChallengeBadge({
}: ChallengeBadgeProps) { }: ChallengeBadgeProps) {
const [count, setCount] = useState(initialCount); const [count, setCount] = useState(initialCount);
// Utiliser le count initial (déjà récupéré côté serveur)
useEffect(() => { useEffect(() => {
// Si on a déjà un initialCount, l'utiliser et ne pas faire d'appel immédiat setCount(initialCount);
// On rafraîchit seulement après un délai pour éviter les appels redondants }, [initialCount]);
if (initialCount > 0) {
setCount(initialCount);
}
// Récupérer le nombre de défis actifs (seulement si pas d'initialCount ou pour rafraîchir) // Écouter les événements de refresh des défis (déclenché après acceptation/annulation)
const fetchActiveCount = async () => { useEffect(() => {
const handleRefreshChallenges = async () => {
try { try {
const response = await fetch("/api/challenges/active-count"); const response = await fetch("/api/challenges/active-count");
const data = await response.json(); const data = await response.json();
@@ -32,16 +31,11 @@ export default function ChallengeBadge({
} }
}; };
// Si pas d'initialCount, charger immédiatement, sinon attendre 30s avant le premier refresh window.addEventListener("refreshChallenges", handleRefreshChallenges);
if (initialCount === 0) { return () => {
fetchActiveCount(); window.removeEventListener("refreshChallenges", handleRefreshChallenges);
} };
}, []);
// Rafraîchir toutes les 30 secondes
const interval = setInterval(fetchActiveCount, 30000);
return () => clearInterval(interval);
}, [initialCount]);
return ( return (
<Link <Link

View File

@@ -0,0 +1,73 @@
"use client";
import { useEffect, useState } from "react";
import Link from "next/link";
interface InvitationBadgeProps {
initialCount?: number;
onNavigate?: () => void;
}
export default function InvitationBadge({
initialCount = 0,
onNavigate,
}: InvitationBadgeProps) {
const [count, setCount] = useState(initialCount);
// Utiliser le count initial (déjà récupéré côté serveur)
useEffect(() => {
setCount(initialCount);
}, [initialCount]);
// Écouter les événements de refresh des invitations (déclenché après acceptation/refus)
useEffect(() => {
const handleRefreshInvitations = async () => {
try {
const response = await fetch("/api/invitations/pending-count");
const data = await response.json();
setCount(data.count || 0);
} catch (error) {
console.error("Error fetching pending invitations count:", error);
}
};
window.addEventListener("refreshInvitations", handleRefreshInvitations);
return () => {
window.removeEventListener("refreshInvitations", handleRefreshInvitations);
};
}, []);
return (
<Link
href="/houses"
onClick={onNavigate}
className={`inline-flex items-center gap-1.5 transition text-xs font-normal uppercase tracking-widest ${
onNavigate ? "py-2" : ""
}`}
style={{ color: "var(--foreground)" }}
onMouseEnter={(e) =>
(e.currentTarget.style.color = "var(--accent-color)")
}
onMouseLeave={(e) => (e.currentTarget.style.color = "var(--foreground)")}
title={
count > 0
? `${count} action${count > 1 ? "s" : ""} en attente (invitations et demandes)`
: "Maisons"
}
>
<span>MAISONS</span>
{count > 0 && (
<span
className="flex h-5 w-5 min-w-[20px] items-center justify-center rounded-full text-[10px] font-bold leading-none"
style={{
backgroundColor: "var(--accent)",
color: "var(--background)",
}}
>
{count > 9 ? "9+" : count}
</span>
)}
</Link>
);
}

View File

@@ -7,6 +7,7 @@ import { usePathname } from "next/navigation";
import PlayerStats from "@/components/profile/PlayerStats"; import PlayerStats from "@/components/profile/PlayerStats";
import { Button, ThemeToggle } from "@/components/ui"; import { Button, ThemeToggle } from "@/components/ui";
import ChallengeBadge from "./ChallengeBadge"; import ChallengeBadge from "./ChallengeBadge";
import InvitationBadge from "./InvitationBadge";
interface UserData { interface UserData {
username: string; username: string;
@@ -23,12 +24,14 @@ interface NavigationProps {
initialUserData?: UserData | null; initialUserData?: UserData | null;
initialIsAdmin?: boolean; initialIsAdmin?: boolean;
initialActiveChallengesCount?: number; initialActiveChallengesCount?: number;
initialPendingInvitationsCount?: number;
} }
export default function Navigation({ export default function Navigation({
initialUserData, initialUserData,
initialIsAdmin, initialIsAdmin,
initialActiveChallengesCount = 0, initialActiveChallengesCount = 0,
initialPendingInvitationsCount = 0,
}: NavigationProps) { }: NavigationProps) {
const { data: session } = useSession(); const { data: session } = useSession();
const [isMenuOpen, setIsMenuOpen] = useState(false); const [isMenuOpen, setIsMenuOpen] = useState(false);
@@ -118,7 +121,10 @@ export default function Navigation({
LEADERBOARD LEADERBOARD
</Link> </Link>
{isAuthenticated && ( {isAuthenticated && (
<ChallengeBadge initialCount={initialActiveChallengesCount} /> <>
<InvitationBadge initialCount={initialPendingInvitationsCount} />
<ChallengeBadge initialCount={initialActiveChallengesCount} />
</>
)} )}
{isAdmin && ( {isAdmin && (
<Link <Link
@@ -279,10 +285,16 @@ export default function Navigation({
LEADERBOARD LEADERBOARD
</Link> </Link>
{isAuthenticated && ( {isAuthenticated && (
<ChallengeBadge <>
initialCount={initialActiveChallengesCount} <InvitationBadge
onNavigate={() => setIsMenuOpen(false)} initialCount={initialPendingInvitationsCount}
/> onNavigate={() => setIsMenuOpen(false)}
/>
<ChallengeBadge
initialCount={initialActiveChallengesCount}
onNavigate={() => setIsMenuOpen(false)}
/>
</>
)} )}
{isAdmin && ( {isAdmin && (
<Link <Link

View File

@@ -1,6 +1,7 @@
import { auth } from "@/lib/auth"; import { auth } from "@/lib/auth";
import { userService } from "@/services/users/user.service"; import { userService } from "@/services/users/user.service";
import { challengeService } from "@/services/challenges/challenge.service"; import { challengeService } from "@/services/challenges/challenge.service";
import { houseService } from "@/services/houses/house.service";
import Navigation from "./Navigation"; import Navigation from "./Navigation";
interface UserData { interface UserData {
@@ -20,10 +21,11 @@ export default async function NavigationWrapper() {
let userData: UserData | null = null; let userData: UserData | null = null;
const isAdmin = session?.user?.role === "ADMIN"; const isAdmin = session?.user?.role === "ADMIN";
let activeChallengesCount = 0; let activeChallengesCount = 0;
let pendingHouseActionsCount = 0;
if (session?.user?.id) { if (session?.user?.id) {
// Paralléliser les appels DB // Paralléliser les appels DB
const [user, count] = await Promise.all([ const [user, challengesCount, houseActionsCount] = await Promise.all([
userService.getUserById(session.user.id, { userService.getUserById(session.user.id, {
username: true, username: true,
avatar: true, avatar: true,
@@ -35,13 +37,15 @@ export default async function NavigationWrapper() {
score: true, score: true,
}), }),
challengeService.getActiveChallengesCount(session.user.id), challengeService.getActiveChallengesCount(session.user.id),
houseService.getPendingHouseActionsCount(session.user.id),
]); ]);
if (user) { if (user) {
userData = user; userData = user;
} }
activeChallengesCount = count; activeChallengesCount = challengesCount;
pendingHouseActionsCount = houseActionsCount;
} }
return ( return (
@@ -49,6 +53,7 @@ export default async function NavigationWrapper() {
initialUserData={userData} initialUserData={userData}
initialIsAdmin={isAdmin} initialIsAdmin={isAdmin}
initialActiveChallengesCount={activeChallengesCount} initialActiveChallengesCount={activeChallengesCount}
initialPendingInvitationsCount={pendingHouseActionsCount}
/> />
); );
} }

View File

@@ -171,12 +171,17 @@ export default function ProfileForm({
{/* Title Section */} {/* Title Section */}
<SectionTitle <SectionTitle
variant="gradient" variant="gradient"
size="lg" size="xl"
subtitle="Gérez votre profil" subtitle="Gérez votre profil"
className="mb-12" className="mb-16"
> >
PROFIL PROFIL
</SectionTitle> </SectionTitle>
<p className="text-gray-400 text-sm max-w-2xl mx-auto text-center mb-16">
Personnalisez votre avatar, votre bio, votre classe de personnage et
consultez vos statistiques. Gérez vos préférences et votre mot de
passe
</p>
{/* Profile Card */} {/* Profile Card */}
<Card variant="default" className="overflow-hidden"> <Card variant="default" className="overflow-hidden">

View File

@@ -10,6 +10,7 @@ interface AvatarProps {
className?: string; className?: string;
borderClassName?: string; borderClassName?: string;
fallbackText?: string; fallbackText?: string;
style?: React.CSSProperties;
} }
const sizeClasses = { const sizeClasses = {
@@ -28,6 +29,7 @@ export default function Avatar({
className = "", className = "",
borderClassName = "", borderClassName = "",
fallbackText, fallbackText,
style,
}: AvatarProps) { }: AvatarProps) {
const [avatarError, setAvatarError] = useState(false); const [avatarError, setAvatarError] = useState(false);
const prevSrcRef = useRef<string | null | undefined>(undefined); const prevSrcRef = useRef<string | null | undefined>(undefined);
@@ -53,6 +55,7 @@ export default function Avatar({
style={{ style={{
backgroundColor: "var(--card)", backgroundColor: "var(--card)",
borderColor: "var(--border)", borderColor: "var(--border)",
...style,
}} }}
> >
{displaySrc ? ( {displaySrc ? (

View File

@@ -22,7 +22,7 @@ export default function BackgroundSection({
> >
{/* Background Image */} {/* Background Image */}
<div <div
className="absolute inset-0 bg-cover bg-center bg-no-repeat" className="fixed inset-0 bg-cover bg-center bg-no-repeat z-0"
style={{ style={{
backgroundImage: `url('${backgroundImage}')`, backgroundImage: `url('${backgroundImage}')`,
}} }}

View File

@@ -1,13 +1,17 @@
"use client"; "use client";
import { ButtonHTMLAttributes, ReactNode, ElementType } from "react"; import { ButtonHTMLAttributes, ReactNode, ElementType } from "react";
import Link from "next/link";
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
variant?: "primary" | "secondary" | "success" | "danger" | "ghost"; variant?: "primary" | "secondary" | "success" | "danger" | "ghost";
size?: "sm" | "md" | "lg"; size?: "sm" | "md" | "lg";
children: ReactNode; children: ReactNode;
as?: ElementType; as?: ElementType;
} } & (
| { as?: Exclude<ElementType, typeof Link> }
| { as: typeof Link; href: string }
);
const variantClasses = { const variantClasses = {
primary: "btn-primary border transition-colors", primary: "btn-primary border transition-colors",

View File

@@ -1,6 +1,31 @@
version: "3.8" version: "3.8"
services: services:
got-postgres:
image: postgres:15-alpine
container_name: got-mc-postgres
environment:
POSTGRES_USER: ${POSTGRES_USER:-gotgaming}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-change-this-in-production}
POSTGRES_DB: ${POSTGRES_DB:-gotgaming}
POSTGRES_INITDB_ARGS: "-E UTF8 --locale=C"
volumes:
- ${POSTGRES_DATA_PATH:-./data/postgres}:/var/lib/postgresql/data
ports:
- "5433:5432"
restart: unless-stopped
command: postgres -c max_connections=100 -c shared_buffers=256MB -c effective_cache_size=1GB
healthcheck:
test:
[
"CMD-SHELL",
"pg_isready -U ${POSTGRES_USER:-gotgaming} -d ${POSTGRES_DB:-gotgaming}",
]
interval: 5s
timeout: 3s
retries: 10
start_period: 10s
got-app: got-app:
build: build:
context: . context: .
@@ -10,15 +35,21 @@ services:
- "3040:3000" - "3040:3000"
environment: environment:
- NODE_ENV=production - NODE_ENV=production
- DATABASE_URL=file:/app/data/dev.db - POSTGRES_USER=${POSTGRES_USER:-gotgaming}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-change-this-in-production}
- POSTGRES_DB=${POSTGRES_DB:-gotgaming}
- DATABASE_URL=postgresql://${POSTGRES_USER:-gotgaming}:${POSTGRES_PASSWORD:-change-this-in-production}@got-postgres:5432/${POSTGRES_DB:-gotgaming}?schema=public
- NEXTAUTH_URL=${NEXTAUTH_URL:-http://localhost:3000} - NEXTAUTH_URL=${NEXTAUTH_URL:-http://localhost:3000}
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET:-change-this-secret-in-production} - NEXTAUTH_SECRET=${NEXTAUTH_SECRET:-change-this-secret-in-production}
volumes: volumes:
# Persist database (override DATA_PATH env var to change location)
- ${PRISMA_DATA_PATH:-/Volumes/EXTERNAL_USB/sites/got-gaming/data}:/app/data
# Persist uploaded images (avatars and backgrounds) # Persist uploaded images (avatars and backgrounds)
- ${UPLOADS_PATH:-./public/uploads}:/app/public/uploads - ${UPLOADS_PATH:-./public/uploads}:/app/public/uploads
- ./prisma/migrations:/app/prisma/migrations # Migrations: décommenter uniquement en développement local pour modifier les migrations sans rebuild
# En production, les migrations sont incluses dans l'image Docker
# - ./prisma/migrations:/app/prisma/migrations
depends_on:
got-postgres:
condition: service_healthy
restart: unless-stopped restart: unless-stopped
labels: labels:
- "com.centurylinklabs.watchtower.enable=false" - "com.centurylinklabs.watchtower.enable=false"

View File

@@ -6,6 +6,8 @@ interface Preferences {
eventsBackground: string | null; eventsBackground: string | null;
leaderboardBackground: string | null; leaderboardBackground: string | null;
challengesBackground: string | null; challengesBackground: string | null;
profileBackground: string | null;
houseBackground: string | null;
} }
export function usePreferences() { export function usePreferences() {
@@ -23,6 +25,8 @@ export function usePreferences() {
eventsBackground: null, eventsBackground: null,
leaderboardBackground: null, leaderboardBackground: null,
challengesBackground: null, challengesBackground: null,
profileBackground: null,
houseBackground: null,
} }
); );
setLoading(false); setLoading(false);
@@ -33,6 +37,8 @@ export function usePreferences() {
eventsBackground: null, eventsBackground: null,
leaderboardBackground: null, leaderboardBackground: null,
challengesBackground: null, challengesBackground: null,
profileBackground: null,
houseBackground: null,
}); });
setLoading(false); setLoading(false);
}); });
@@ -42,7 +48,7 @@ export function usePreferences() {
} }
export function useBackgroundImage( export function useBackgroundImage(
page: "home" | "events" | "leaderboard" | "challenges", page: "home" | "events" | "leaderboard" | "challenges" | "profile" | "houses",
defaultImage: string defaultImage: string
) { ) {
const { preferences } = usePreferences(); const { preferences } = usePreferences();
@@ -51,7 +57,9 @@ export function useBackgroundImage(
useEffect(() => { useEffect(() => {
if (preferences) { if (preferences) {
const imageKey = `${page}Background` as keyof Preferences; // Mapping spécial pour "houses" -> "house" (car la colonne est houseBackground)
const dbPage = page === "houses" ? "house" : page;
const imageKey = `${dbPage}Background` as keyof Preferences;
const customImage = preferences[imageKey]; const customImage = preferences[imageKey];
const rawImage = customImage || defaultImage; const rawImage = customImage || defaultImage;
// Normaliser l'URL pour utiliser l'API si nécessaire // Normaliser l'URL pour utiliser l'API si nécessaire

View File

@@ -1,7 +1,7 @@
import { sitePreferencesService } from "@/services/preferences/site-preferences.service"; import { sitePreferencesService } from "@/services/preferences/site-preferences.service";
export async function getBackgroundImage( export async function getBackgroundImage(
page: "home" | "events" | "leaderboard" | "challenges", page: "home" | "events" | "leaderboard" | "challenges" | "profile" | "houses",
defaultImage: string defaultImage: string
): Promise<string> { ): Promise<string> {
return sitePreferencesService.getBackgroundImage(page, defaultImage); return sitePreferencesService.getBackgroundImage(page, defaultImage);

View File

@@ -1,10 +1,32 @@
import { PrismaClient } from "@/prisma/generated/prisma/client"; import { PrismaClient } from "@/prisma/generated/prisma/client";
import { PrismaBetterSqlite3 } from "@prisma/adapter-better-sqlite3"; import { PrismaPg } from "@prisma/adapter-pg";
import { Pool } from "pg";
const adapter = new PrismaBetterSqlite3({ // Construire DATABASE_URL si elle n'est pas définie, en utilisant les variables individuelles
url: process.env.DATABASE_URL || "file:./data/dev.db", let databaseUrl = process.env.DATABASE_URL;
if (!databaseUrl) {
const user = process.env.POSTGRES_USER || "gotgaming";
const password = process.env.POSTGRES_PASSWORD || "change-this-in-production";
const host = process.env.POSTGRES_HOST || "got-postgres";
const port = process.env.POSTGRES_PORT || "5432";
const db = process.env.POSTGRES_DB || "gotgaming";
// Encoder le mot de passe pour l'URL
const encodedPassword = encodeURIComponent(password);
databaseUrl = `postgresql://${user}:${encodedPassword}@${host}:${port}/${db}?schema=public`;
}
if (typeof databaseUrl !== "string") {
throw new Error("DATABASE_URL must be a string");
}
const pool = new Pool({
connectionString: databaseUrl,
}); });
const adapter = new PrismaPg(pool);
const globalForPrisma = globalThis as unknown as { const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined; prisma: PrismaClient | undefined;
}; };
@@ -13,10 +35,7 @@ export const prisma =
globalForPrisma.prisma ?? globalForPrisma.prisma ??
new PrismaClient({ new PrismaClient({
adapter, adapter,
log: log: ["error"],
process.env.NODE_ENV === "development"
? ["query", "error", "warn"]
: ["error"],
}); });
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma; if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;

View File

@@ -17,25 +17,24 @@
"pnpm": { "pnpm": {
"onlyBuiltDependencies": [ "onlyBuiltDependencies": [
"prisma", "prisma",
"@prisma/engines", "@prisma/engines"
"better-sqlite3"
] ]
}, },
"dependencies": { "dependencies": {
"@prisma/adapter-better-sqlite3": "^7.1.0", "@prisma/adapter-pg": "^7.1.0",
"@prisma/client": "^7.1.0", "@prisma/client": "^7.1.0",
"bcryptjs": "^3.0.3", "bcryptjs": "^3.0.3",
"better-sqlite3": "^12.5.0",
"next": "15.5.9", "next": "15.5.9",
"next-auth": "5.0.0-beta.30", "next-auth": "5.0.0-beta.30",
"pg": "^8.16.3",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0" "react-dom": "^19.0.0"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.39.1", "@eslint/js": "^9.39.1",
"@types/bcryptjs": "^3.0.0", "@types/bcryptjs": "^3.0.0",
"@types/better-sqlite3": "^7.6.13",
"@types/node": "^22.0.0", "@types/node": "^22.0.0",
"@types/pg": "^8.16.0",
"@types/react": "^19.0.0", "@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0", "@types/react-dom": "^19.0.0",
"@typescript-eslint/eslint-plugin": "^8.49.0", "@typescript-eslint/eslint-plugin": "^8.49.0",

225
pnpm-lock.yaml generated
View File

@@ -8,7 +8,7 @@ importers:
.: .:
dependencies: dependencies:
'@prisma/adapter-better-sqlite3': '@prisma/adapter-pg':
specifier: ^7.1.0 specifier: ^7.1.0
version: 7.1.0 version: 7.1.0
'@prisma/client': '@prisma/client':
@@ -17,15 +17,15 @@ importers:
bcryptjs: bcryptjs:
specifier: ^3.0.3 specifier: ^3.0.3
version: 3.0.3 version: 3.0.3
better-sqlite3:
specifier: ^12.5.0
version: 12.5.0
next: next:
specifier: 15.5.9 specifier: 15.5.9
version: 15.5.9(@babel/core@7.28.5)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) version: 15.5.9(@babel/core@7.28.5)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
next-auth: next-auth:
specifier: 5.0.0-beta.30 specifier: 5.0.0-beta.30
version: 5.0.0-beta.30(next@15.5.9(@babel/core@7.28.5)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1) version: 5.0.0-beta.30(next@15.5.9(@babel/core@7.28.5)(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1)
pg:
specifier: ^8.16.3
version: 8.16.3
react: react:
specifier: ^19.0.0 specifier: ^19.0.0
version: 19.2.1 version: 19.2.1
@@ -39,12 +39,12 @@ importers:
'@types/bcryptjs': '@types/bcryptjs':
specifier: ^3.0.0 specifier: ^3.0.0
version: 3.0.0 version: 3.0.0
'@types/better-sqlite3':
specifier: ^7.6.13
version: 7.6.13
'@types/node': '@types/node':
specifier: ^22.0.0 specifier: ^22.0.0
version: 22.19.1 version: 22.19.1
'@types/pg':
specifier: ^8.16.0
version: 8.16.0
'@types/react': '@types/react':
specifier: ^19.0.0 specifier: ^19.0.0
version: 19.2.7 version: 19.2.7
@@ -668,8 +668,8 @@ packages:
'@panva/hkdf@1.2.1': '@panva/hkdf@1.2.1':
resolution: {integrity: sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==} resolution: {integrity: sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==}
'@prisma/adapter-better-sqlite3@7.1.0': '@prisma/adapter-pg@7.1.0':
resolution: {integrity: sha512-Ex4CimAONWMoUrhU27lpGXb4MdX/59qj+4PBTIuPVJLXZfTxSWuU8KowlRtq1w5iE91WiwMgU1KgeBOKJ81nEA==} resolution: {integrity: sha512-DSAnUwkKfX4bUzhkrjGN4IBQzwg0nvFw2W17H0Oa532I5w9nLtTJ9mAEGDs1nUBEGRAsa0c7qsf8CSgfJ4DsBQ==}
'@prisma/client-runtime-utils@7.1.0': '@prisma/client-runtime-utils@7.1.0':
resolution: {integrity: sha512-39xmeBrNTN40FzF34aJMjfX1PowVCqoT3UKUWBBSP3aXV05NRqGBC3x2wCDs96ti6ZgdiVzqnRDHtbzU8X+lPQ==} resolution: {integrity: sha512-39xmeBrNTN40FzF34aJMjfX1PowVCqoT3UKUWBBSP3aXV05NRqGBC3x2wCDs96ti6ZgdiVzqnRDHtbzU8X+lPQ==}
@@ -742,9 +742,6 @@ packages:
resolution: {integrity: sha512-WRZOuCuaz8UcZZE4R5HXTco2goQSI2XxjGY3hbM/xDvwmqFWd4ivooImsMx65OKM6CtNKbnZ5YL+YwAwK7c1dg==} resolution: {integrity: sha512-WRZOuCuaz8UcZZE4R5HXTco2goQSI2XxjGY3hbM/xDvwmqFWd4ivooImsMx65OKM6CtNKbnZ5YL+YwAwK7c1dg==}
deprecated: This is a stub types definition. bcryptjs provides its own type definitions, so you do not need this installed. deprecated: This is a stub types definition. bcryptjs provides its own type definitions, so you do not need this installed.
'@types/better-sqlite3@7.6.13':
resolution: {integrity: sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==}
'@types/estree@1.0.8': '@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
@@ -757,6 +754,9 @@ packages:
'@types/node@22.19.1': '@types/node@22.19.1':
resolution: {integrity: sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==} resolution: {integrity: sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==}
'@types/pg@8.16.0':
resolution: {integrity: sha512-RmhMd/wD+CF8Dfo+cVIy3RR5cl8CyfXQ0tGgW6XBL8L4LM/UTEbNXYRbLwU6w+CgrKBNbrQWt4FUtTfaU5jSYQ==}
'@types/react-dom@19.2.3': '@types/react-dom@19.2.3':
resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
peerDependencies: peerDependencies:
@@ -2072,6 +2072,40 @@ packages:
perfect-debounce@1.0.0: perfect-debounce@1.0.0:
resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==}
pg-cloudflare@1.2.7:
resolution: {integrity: sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==}
pg-connection-string@2.9.1:
resolution: {integrity: sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==}
pg-int8@1.0.1:
resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==}
engines: {node: '>=4.0.0'}
pg-pool@3.10.1:
resolution: {integrity: sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==}
peerDependencies:
pg: '>=8.0'
pg-protocol@1.10.3:
resolution: {integrity: sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==}
pg-types@2.2.0:
resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==}
engines: {node: '>=4'}
pg@8.16.3:
resolution: {integrity: sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==}
engines: {node: '>= 16.0.0'}
peerDependencies:
pg-native: '>=3.0.1'
peerDependenciesMeta:
pg-native:
optional: true
pgpass@1.0.5:
resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==}
picocolors@1.1.1: picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@@ -2149,6 +2183,26 @@ packages:
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
engines: {node: ^10 || ^12 || >=14} engines: {node: ^10 || ^12 || >=14}
postgres-array@2.0.0:
resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==}
engines: {node: '>=4'}
postgres-array@3.0.4:
resolution: {integrity: sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==}
engines: {node: '>=12'}
postgres-bytea@1.0.1:
resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==}
engines: {node: '>=0.10.0'}
postgres-date@1.0.7:
resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==}
engines: {node: '>=0.10.0'}
postgres-interval@1.2.0:
resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==}
engines: {node: '>=0.10.0'}
postgres@3.4.7: postgres@3.4.7:
resolution: {integrity: sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==} resolution: {integrity: sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==}
engines: {node: '>=12'} engines: {node: '>=12'}
@@ -2372,6 +2426,10 @@ packages:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
split2@4.2.0:
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
engines: {node: '>= 10.x'}
sqlstring@2.3.3: sqlstring@2.3.3:
resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==} resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
@@ -2598,6 +2656,10 @@ packages:
wrappy@1.0.2: wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
xtend@4.0.2:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}
yallist@3.1.1: yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
@@ -3083,10 +3145,13 @@ snapshots:
'@panva/hkdf@1.2.1': {} '@panva/hkdf@1.2.1': {}
'@prisma/adapter-better-sqlite3@7.1.0': '@prisma/adapter-pg@7.1.0':
dependencies: dependencies:
'@prisma/driver-adapter-utils': 7.1.0 '@prisma/driver-adapter-utils': 7.1.0
better-sqlite3: 12.5.0 pg: 8.16.3
postgres-array: 3.0.4
transitivePeerDependencies:
- pg-native
'@prisma/client-runtime-utils@7.1.0': {} '@prisma/client-runtime-utils@7.1.0': {}
@@ -3184,10 +3249,6 @@ snapshots:
dependencies: dependencies:
bcryptjs: 3.0.3 bcryptjs: 3.0.3
'@types/better-sqlite3@7.6.13':
dependencies:
'@types/node': 22.19.1
'@types/estree@1.0.8': {} '@types/estree@1.0.8': {}
'@types/json-schema@7.0.15': {} '@types/json-schema@7.0.15': {}
@@ -3198,6 +3259,12 @@ snapshots:
dependencies: dependencies:
undici-types: 6.21.0 undici-types: 6.21.0
'@types/pg@8.16.0':
dependencies:
'@types/node': 22.19.1
pg-protocol: 1.10.3
pg-types: 2.2.0
'@types/react-dom@19.2.3(@types/react@19.2.7)': '@types/react-dom@19.2.3(@types/react@19.2.7)':
dependencies: dependencies:
'@types/react': 19.2.7 '@types/react': 19.2.7
@@ -3479,7 +3546,8 @@ snapshots:
balanced-match@1.0.2: {} balanced-match@1.0.2: {}
base64-js@1.5.1: {} base64-js@1.5.1:
optional: true
baseline-browser-mapping@2.9.5: {} baseline-browser-mapping@2.9.5: {}
@@ -3489,18 +3557,21 @@ snapshots:
dependencies: dependencies:
bindings: 1.5.0 bindings: 1.5.0
prebuild-install: 7.1.3 prebuild-install: 7.1.3
optional: true
binary-extensions@2.3.0: {} binary-extensions@2.3.0: {}
bindings@1.5.0: bindings@1.5.0:
dependencies: dependencies:
file-uri-to-path: 1.0.0 file-uri-to-path: 1.0.0
optional: true
bl@4.1.0: bl@4.1.0:
dependencies: dependencies:
buffer: 5.7.1 buffer: 5.7.1
inherits: 2.0.4 inherits: 2.0.4
readable-stream: 3.6.2 readable-stream: 3.6.2
optional: true
brace-expansion@1.1.12: brace-expansion@1.1.12:
dependencies: dependencies:
@@ -3527,6 +3598,7 @@ snapshots:
dependencies: dependencies:
base64-js: 1.5.1 base64-js: 1.5.1
ieee754: 1.2.1 ieee754: 1.2.1
optional: true
c12@3.1.0: c12@3.1.0:
dependencies: dependencies:
@@ -3598,7 +3670,8 @@ snapshots:
dependencies: dependencies:
readdirp: 4.1.2 readdirp: 4.1.2
chownr@1.1.4: {} chownr@1.1.4:
optional: true
citty@0.1.6: citty@0.1.6:
dependencies: dependencies:
@@ -3663,8 +3736,10 @@ snapshots:
decompress-response@6.0.0: decompress-response@6.0.0:
dependencies: dependencies:
mimic-response: 3.1.0 mimic-response: 3.1.0
optional: true
deep-extend@0.6.0: {} deep-extend@0.6.0:
optional: true
deep-is@0.1.4: {} deep-is@0.1.4: {}
@@ -3688,7 +3763,8 @@ snapshots:
destr@2.0.5: {} destr@2.0.5: {}
detect-libc@2.1.2: {} detect-libc@2.1.2:
optional: true
didyoumean@1.2.2: {} didyoumean@1.2.2: {}
@@ -3722,6 +3798,7 @@ snapshots:
end-of-stream@1.4.5: end-of-stream@1.4.5:
dependencies: dependencies:
once: 1.4.0 once: 1.4.0
optional: true
es-abstract@1.24.0: es-abstract@1.24.0:
dependencies: dependencies:
@@ -4060,7 +4137,8 @@ snapshots:
esutils@2.0.3: {} esutils@2.0.3: {}
expand-template@2.0.3: {} expand-template@2.0.3:
optional: true
exsolve@1.0.8: {} exsolve@1.0.8: {}
@@ -4102,7 +4180,8 @@ snapshots:
dependencies: dependencies:
flat-cache: 4.0.1 flat-cache: 4.0.1
file-uri-to-path@1.0.0: {} file-uri-to-path@1.0.0:
optional: true
fill-range@7.1.1: fill-range@7.1.1:
dependencies: dependencies:
@@ -4131,7 +4210,8 @@ snapshots:
fraction.js@5.3.4: {} fraction.js@5.3.4: {}
fs-constants@1.0.0: {} fs-constants@1.0.0:
optional: true
fsevents@2.3.3: fsevents@2.3.3:
optional: true optional: true
@@ -4196,7 +4276,8 @@ snapshots:
nypm: 0.6.2 nypm: 0.6.2
pathe: 2.0.3 pathe: 2.0.3
github-from-package@0.0.0: {} github-from-package@0.0.0:
optional: true
glob-parent@5.1.2: glob-parent@5.1.2:
dependencies: dependencies:
@@ -4259,7 +4340,8 @@ snapshots:
dependencies: dependencies:
safer-buffer: 2.1.2 safer-buffer: 2.1.2
ieee754@1.2.1: {} ieee754@1.2.1:
optional: true
ignore@5.3.2: {} ignore@5.3.2: {}
@@ -4272,9 +4354,11 @@ snapshots:
imurmurhash@0.1.4: {} imurmurhash@0.1.4: {}
inherits@2.0.4: {} inherits@2.0.4:
optional: true
ini@1.3.8: {} ini@1.3.8:
optional: true
internal-slot@1.1.0: internal-slot@1.1.0:
dependencies: dependencies:
@@ -4496,7 +4580,8 @@ snapshots:
braces: 3.0.3 braces: 3.0.3
picomatch: 2.3.1 picomatch: 2.3.1
mimic-response@3.1.0: {} mimic-response@3.1.0:
optional: true
minimatch@3.1.2: minimatch@3.1.2:
dependencies: dependencies:
@@ -4508,7 +4593,8 @@ snapshots:
minimist@1.2.8: {} minimist@1.2.8: {}
mkdirp-classic@0.5.3: {} mkdirp-classic@0.5.3:
optional: true
ms@2.1.3: {} ms@2.1.3: {}
@@ -4536,7 +4622,8 @@ snapshots:
nanoid@3.3.11: {} nanoid@3.3.11: {}
napi-build-utils@2.0.0: {} napi-build-utils@2.0.0:
optional: true
napi-postinstall@0.3.4: {} napi-postinstall@0.3.4: {}
@@ -4574,6 +4661,7 @@ snapshots:
node-abi@3.85.0: node-abi@3.85.0:
dependencies: dependencies:
semver: 7.7.3 semver: 7.7.3
optional: true
node-fetch-native@1.6.7: {} node-fetch-native@1.6.7: {}
@@ -4642,6 +4730,7 @@ snapshots:
once@1.4.0: once@1.4.0:
dependencies: dependencies:
wrappy: 1.0.2 wrappy: 1.0.2
optional: true
optionator@0.9.4: optionator@0.9.4:
dependencies: dependencies:
@@ -4680,6 +4769,41 @@ snapshots:
perfect-debounce@1.0.0: {} perfect-debounce@1.0.0: {}
pg-cloudflare@1.2.7:
optional: true
pg-connection-string@2.9.1: {}
pg-int8@1.0.1: {}
pg-pool@3.10.1(pg@8.16.3):
dependencies:
pg: 8.16.3
pg-protocol@1.10.3: {}
pg-types@2.2.0:
dependencies:
pg-int8: 1.0.1
postgres-array: 2.0.0
postgres-bytea: 1.0.1
postgres-date: 1.0.7
postgres-interval: 1.2.0
pg@8.16.3:
dependencies:
pg-connection-string: 2.9.1
pg-pool: 3.10.1(pg@8.16.3)
pg-protocol: 1.10.3
pg-types: 2.2.0
pgpass: 1.0.5
optionalDependencies:
pg-cloudflare: 1.2.7
pgpass@1.0.5:
dependencies:
split2: 4.2.0
picocolors@1.1.1: {} picocolors@1.1.1: {}
picomatch@2.3.1: {} picomatch@2.3.1: {}
@@ -4742,6 +4866,18 @@ snapshots:
picocolors: 1.1.1 picocolors: 1.1.1
source-map-js: 1.2.1 source-map-js: 1.2.1
postgres-array@2.0.0: {}
postgres-array@3.0.4: {}
postgres-bytea@1.0.1: {}
postgres-date@1.0.7: {}
postgres-interval@1.2.0:
dependencies:
xtend: 4.0.2
postgres@3.4.7: {} postgres@3.4.7: {}
preact-render-to-string@6.5.11(preact@10.24.3): preact-render-to-string@6.5.11(preact@10.24.3):
@@ -4764,6 +4900,7 @@ snapshots:
simple-get: 4.0.1 simple-get: 4.0.1
tar-fs: 2.1.4 tar-fs: 2.1.4
tunnel-agent: 0.6.0 tunnel-agent: 0.6.0
optional: true
prelude-ls@1.2.1: {} prelude-ls@1.2.1: {}
@@ -4802,6 +4939,7 @@ snapshots:
dependencies: dependencies:
end-of-stream: 1.4.5 end-of-stream: 1.4.5
once: 1.4.0 once: 1.4.0
optional: true
punycode@2.3.1: {} punycode@2.3.1: {}
@@ -4820,6 +4958,7 @@ snapshots:
ini: 1.3.8 ini: 1.3.8
minimist: 1.2.8 minimist: 1.2.8
strip-json-comments: 2.0.1 strip-json-comments: 2.0.1
optional: true
react-dom@19.2.1(react@19.2.1): react-dom@19.2.1(react@19.2.1):
dependencies: dependencies:
@@ -4839,6 +4978,7 @@ snapshots:
inherits: 2.0.4 inherits: 2.0.4
string_decoder: 1.3.0 string_decoder: 1.3.0
util-deprecate: 1.0.2 util-deprecate: 1.0.2
optional: true
readdirp@3.6.0: readdirp@3.6.0:
dependencies: dependencies:
@@ -4904,7 +5044,8 @@ snapshots:
has-symbols: 1.1.0 has-symbols: 1.1.0
isarray: 2.0.5 isarray: 2.0.5
safe-buffer@5.2.1: {} safe-buffer@5.2.1:
optional: true
safe-push-apply@1.0.0: safe-push-apply@1.0.0:
dependencies: dependencies:
@@ -5019,16 +5160,20 @@ snapshots:
signal-exit@4.1.0: {} signal-exit@4.1.0: {}
simple-concat@1.0.1: {} simple-concat@1.0.1:
optional: true
simple-get@4.0.1: simple-get@4.0.1:
dependencies: dependencies:
decompress-response: 6.0.0 decompress-response: 6.0.0
once: 1.4.0 once: 1.4.0
simple-concat: 1.0.1 simple-concat: 1.0.1
optional: true
source-map-js@1.2.1: {} source-map-js@1.2.1: {}
split2@4.2.0: {}
sqlstring@2.3.3: {} sqlstring@2.3.3: {}
stable-hash@0.0.5: {} stable-hash@0.0.5: {}
@@ -5093,10 +5238,12 @@ snapshots:
string_decoder@1.3.0: string_decoder@1.3.0:
dependencies: dependencies:
safe-buffer: 5.2.1 safe-buffer: 5.2.1
optional: true
strip-bom@3.0.0: {} strip-bom@3.0.0: {}
strip-json-comments@2.0.1: {} strip-json-comments@2.0.1:
optional: true
strip-json-comments@3.1.1: {} strip-json-comments@3.1.1: {}
@@ -5157,6 +5304,7 @@ snapshots:
mkdirp-classic: 0.5.3 mkdirp-classic: 0.5.3
pump: 3.0.3 pump: 3.0.3
tar-stream: 2.2.0 tar-stream: 2.2.0
optional: true
tar-stream@2.2.0: tar-stream@2.2.0:
dependencies: dependencies:
@@ -5165,6 +5313,7 @@ snapshots:
fs-constants: 1.0.0 fs-constants: 1.0.0
inherits: 2.0.4 inherits: 2.0.4
readable-stream: 3.6.2 readable-stream: 3.6.2
optional: true
thenify-all@1.6.0: thenify-all@1.6.0:
dependencies: dependencies:
@@ -5210,6 +5359,7 @@ snapshots:
tunnel-agent@0.6.0: tunnel-agent@0.6.0:
dependencies: dependencies:
safe-buffer: 5.2.1 safe-buffer: 5.2.1
optional: true
type-check@0.4.0: type-check@0.4.0:
dependencies: dependencies:
@@ -5359,7 +5509,10 @@ snapshots:
word-wrap@1.2.5: {} word-wrap@1.2.5: {}
wrappy@1.0.2: {} wrappy@1.0.2:
optional: true
xtend@4.0.2: {}
yallist@3.1.1: {} yallist@3.1.1: {}

View File

@@ -52,3 +52,23 @@ export type SitePreferences = Prisma.SitePreferencesModel
* *
*/ */
export type Challenge = Prisma.ChallengeModel export type Challenge = Prisma.ChallengeModel
/**
* Model House
*
*/
export type House = Prisma.HouseModel
/**
* Model HouseMembership
*
*/
export type HouseMembership = Prisma.HouseMembershipModel
/**
* Model HouseInvitation
*
*/
export type HouseInvitation = Prisma.HouseInvitationModel
/**
* Model HouseRequest
*
*/
export type HouseRequest = Prisma.HouseRequestModel

View File

@@ -74,3 +74,23 @@ export type SitePreferences = Prisma.SitePreferencesModel
* *
*/ */
export type Challenge = Prisma.ChallengeModel export type Challenge = Prisma.ChallengeModel
/**
* Model House
*
*/
export type House = Prisma.HouseModel
/**
* Model HouseMembership
*
*/
export type HouseMembership = Prisma.HouseMembershipModel
/**
* Model HouseInvitation
*
*/
export type HouseInvitation = Prisma.HouseInvitationModel
/**
* Model HouseRequest
*
*/
export type HouseRequest = Prisma.HouseRequestModel

View File

@@ -16,8 +16,8 @@ import type * as Prisma from "./internal/prismaNamespace"
export type StringFilter<$PrismaModel = never> = { export type StringFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
in?: string[] in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
notIn?: string[] notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
lt?: string | Prisma.StringFieldRefInput<$PrismaModel> lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel> lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel> gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
@@ -25,20 +25,21 @@ export type StringFilter<$PrismaModel = never> = {
contains?: string | Prisma.StringFieldRefInput<$PrismaModel> contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
mode?: Prisma.QueryMode
not?: Prisma.NestedStringFilter<$PrismaModel> | string not?: Prisma.NestedStringFilter<$PrismaModel> | string
} }
export type EnumRoleFilter<$PrismaModel = never> = { export type EnumRoleFilter<$PrismaModel = never> = {
equals?: $Enums.Role | Prisma.EnumRoleFieldRefInput<$PrismaModel> equals?: $Enums.Role | Prisma.EnumRoleFieldRefInput<$PrismaModel>
in?: $Enums.Role[] in?: $Enums.Role[] | Prisma.ListEnumRoleFieldRefInput<$PrismaModel>
notIn?: $Enums.Role[] notIn?: $Enums.Role[] | Prisma.ListEnumRoleFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumRoleFilter<$PrismaModel> | $Enums.Role not?: Prisma.NestedEnumRoleFilter<$PrismaModel> | $Enums.Role
} }
export type IntFilter<$PrismaModel = never> = { export type IntFilter<$PrismaModel = never> = {
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
in?: number[] in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
notIn?: number[] notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
lt?: number | Prisma.IntFieldRefInput<$PrismaModel> lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
lte?: number | Prisma.IntFieldRefInput<$PrismaModel> lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
gt?: number | Prisma.IntFieldRefInput<$PrismaModel> gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
@@ -48,8 +49,8 @@ export type IntFilter<$PrismaModel = never> = {
export type StringNullableFilter<$PrismaModel = never> = { export type StringNullableFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
in?: string[] | null in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null
notIn?: string[] | null notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null
lt?: string | Prisma.StringFieldRefInput<$PrismaModel> lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel> lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel> gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
@@ -57,13 +58,14 @@ export type StringNullableFilter<$PrismaModel = never> = {
contains?: string | Prisma.StringFieldRefInput<$PrismaModel> contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
mode?: Prisma.QueryMode
not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null
} }
export type DateTimeFilter<$PrismaModel = never> = { export type DateTimeFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
in?: Date[] | string[] in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
notIn?: Date[] | string[] notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
@@ -73,8 +75,8 @@ export type DateTimeFilter<$PrismaModel = never> = {
export type EnumCharacterClassNullableFilter<$PrismaModel = never> = { export type EnumCharacterClassNullableFilter<$PrismaModel = never> = {
equals?: $Enums.CharacterClass | Prisma.EnumCharacterClassFieldRefInput<$PrismaModel> | null equals?: $Enums.CharacterClass | Prisma.EnumCharacterClassFieldRefInput<$PrismaModel> | null
in?: $Enums.CharacterClass[] | null in?: $Enums.CharacterClass[] | Prisma.ListEnumCharacterClassFieldRefInput<$PrismaModel> | null
notIn?: $Enums.CharacterClass[] | null notIn?: $Enums.CharacterClass[] | Prisma.ListEnumCharacterClassFieldRefInput<$PrismaModel> | null
not?: Prisma.NestedEnumCharacterClassNullableFilter<$PrismaModel> | $Enums.CharacterClass | null not?: Prisma.NestedEnumCharacterClassNullableFilter<$PrismaModel> | $Enums.CharacterClass | null
} }
@@ -85,8 +87,8 @@ export type SortOrderInput = {
export type StringWithAggregatesFilter<$PrismaModel = never> = { export type StringWithAggregatesFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
in?: string[] in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
notIn?: string[] notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
lt?: string | Prisma.StringFieldRefInput<$PrismaModel> lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel> lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel> gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
@@ -94,6 +96,7 @@ export type StringWithAggregatesFilter<$PrismaModel = never> = {
contains?: string | Prisma.StringFieldRefInput<$PrismaModel> contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
mode?: Prisma.QueryMode
not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string
_count?: Prisma.NestedIntFilter<$PrismaModel> _count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedStringFilter<$PrismaModel> _min?: Prisma.NestedStringFilter<$PrismaModel>
@@ -102,8 +105,8 @@ export type StringWithAggregatesFilter<$PrismaModel = never> = {
export type EnumRoleWithAggregatesFilter<$PrismaModel = never> = { export type EnumRoleWithAggregatesFilter<$PrismaModel = never> = {
equals?: $Enums.Role | Prisma.EnumRoleFieldRefInput<$PrismaModel> equals?: $Enums.Role | Prisma.EnumRoleFieldRefInput<$PrismaModel>
in?: $Enums.Role[] in?: $Enums.Role[] | Prisma.ListEnumRoleFieldRefInput<$PrismaModel>
notIn?: $Enums.Role[] notIn?: $Enums.Role[] | Prisma.ListEnumRoleFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumRoleWithAggregatesFilter<$PrismaModel> | $Enums.Role not?: Prisma.NestedEnumRoleWithAggregatesFilter<$PrismaModel> | $Enums.Role
_count?: Prisma.NestedIntFilter<$PrismaModel> _count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedEnumRoleFilter<$PrismaModel> _min?: Prisma.NestedEnumRoleFilter<$PrismaModel>
@@ -112,8 +115,8 @@ export type EnumRoleWithAggregatesFilter<$PrismaModel = never> = {
export type IntWithAggregatesFilter<$PrismaModel = never> = { export type IntWithAggregatesFilter<$PrismaModel = never> = {
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
in?: number[] in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
notIn?: number[] notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
lt?: number | Prisma.IntFieldRefInput<$PrismaModel> lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
lte?: number | Prisma.IntFieldRefInput<$PrismaModel> lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
gt?: number | Prisma.IntFieldRefInput<$PrismaModel> gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
@@ -128,8 +131,8 @@ export type IntWithAggregatesFilter<$PrismaModel = never> = {
export type StringNullableWithAggregatesFilter<$PrismaModel = never> = { export type StringNullableWithAggregatesFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
in?: string[] | null in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null
notIn?: string[] | null notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null
lt?: string | Prisma.StringFieldRefInput<$PrismaModel> lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel> lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel> gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
@@ -137,6 +140,7 @@ export type StringNullableWithAggregatesFilter<$PrismaModel = never> = {
contains?: string | Prisma.StringFieldRefInput<$PrismaModel> contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
mode?: Prisma.QueryMode
not?: Prisma.NestedStringNullableWithAggregatesFilter<$PrismaModel> | string | null not?: Prisma.NestedStringNullableWithAggregatesFilter<$PrismaModel> | string | null
_count?: Prisma.NestedIntNullableFilter<$PrismaModel> _count?: Prisma.NestedIntNullableFilter<$PrismaModel>
_min?: Prisma.NestedStringNullableFilter<$PrismaModel> _min?: Prisma.NestedStringNullableFilter<$PrismaModel>
@@ -145,8 +149,8 @@ export type StringNullableWithAggregatesFilter<$PrismaModel = never> = {
export type DateTimeWithAggregatesFilter<$PrismaModel = never> = { export type DateTimeWithAggregatesFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
in?: Date[] | string[] in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
notIn?: Date[] | string[] notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
@@ -159,8 +163,8 @@ export type DateTimeWithAggregatesFilter<$PrismaModel = never> = {
export type EnumCharacterClassNullableWithAggregatesFilter<$PrismaModel = never> = { export type EnumCharacterClassNullableWithAggregatesFilter<$PrismaModel = never> = {
equals?: $Enums.CharacterClass | Prisma.EnumCharacterClassFieldRefInput<$PrismaModel> | null equals?: $Enums.CharacterClass | Prisma.EnumCharacterClassFieldRefInput<$PrismaModel> | null
in?: $Enums.CharacterClass[] | null in?: $Enums.CharacterClass[] | Prisma.ListEnumCharacterClassFieldRefInput<$PrismaModel> | null
notIn?: $Enums.CharacterClass[] | null notIn?: $Enums.CharacterClass[] | Prisma.ListEnumCharacterClassFieldRefInput<$PrismaModel> | null
not?: Prisma.NestedEnumCharacterClassNullableWithAggregatesFilter<$PrismaModel> | $Enums.CharacterClass | null not?: Prisma.NestedEnumCharacterClassNullableWithAggregatesFilter<$PrismaModel> | $Enums.CharacterClass | null
_count?: Prisma.NestedIntNullableFilter<$PrismaModel> _count?: Prisma.NestedIntNullableFilter<$PrismaModel>
_min?: Prisma.NestedEnumCharacterClassNullableFilter<$PrismaModel> _min?: Prisma.NestedEnumCharacterClassNullableFilter<$PrismaModel>
@@ -169,15 +173,15 @@ export type EnumCharacterClassNullableWithAggregatesFilter<$PrismaModel = never>
export type EnumEventTypeFilter<$PrismaModel = never> = { export type EnumEventTypeFilter<$PrismaModel = never> = {
equals?: $Enums.EventType | Prisma.EnumEventTypeFieldRefInput<$PrismaModel> equals?: $Enums.EventType | Prisma.EnumEventTypeFieldRefInput<$PrismaModel>
in?: $Enums.EventType[] in?: $Enums.EventType[] | Prisma.ListEnumEventTypeFieldRefInput<$PrismaModel>
notIn?: $Enums.EventType[] notIn?: $Enums.EventType[] | Prisma.ListEnumEventTypeFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumEventTypeFilter<$PrismaModel> | $Enums.EventType not?: Prisma.NestedEnumEventTypeFilter<$PrismaModel> | $Enums.EventType
} }
export type IntNullableFilter<$PrismaModel = never> = { export type IntNullableFilter<$PrismaModel = never> = {
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
in?: number[] | null in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
notIn?: number[] | null notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
lt?: number | Prisma.IntFieldRefInput<$PrismaModel> lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
lte?: number | Prisma.IntFieldRefInput<$PrismaModel> lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
gt?: number | Prisma.IntFieldRefInput<$PrismaModel> gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
@@ -187,8 +191,8 @@ export type IntNullableFilter<$PrismaModel = never> = {
export type EnumEventTypeWithAggregatesFilter<$PrismaModel = never> = { export type EnumEventTypeWithAggregatesFilter<$PrismaModel = never> = {
equals?: $Enums.EventType | Prisma.EnumEventTypeFieldRefInput<$PrismaModel> equals?: $Enums.EventType | Prisma.EnumEventTypeFieldRefInput<$PrismaModel>
in?: $Enums.EventType[] in?: $Enums.EventType[] | Prisma.ListEnumEventTypeFieldRefInput<$PrismaModel>
notIn?: $Enums.EventType[] notIn?: $Enums.EventType[] | Prisma.ListEnumEventTypeFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumEventTypeWithAggregatesFilter<$PrismaModel> | $Enums.EventType not?: Prisma.NestedEnumEventTypeWithAggregatesFilter<$PrismaModel> | $Enums.EventType
_count?: Prisma.NestedIntFilter<$PrismaModel> _count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedEnumEventTypeFilter<$PrismaModel> _min?: Prisma.NestedEnumEventTypeFilter<$PrismaModel>
@@ -197,8 +201,8 @@ export type EnumEventTypeWithAggregatesFilter<$PrismaModel = never> = {
export type IntNullableWithAggregatesFilter<$PrismaModel = never> = { export type IntNullableWithAggregatesFilter<$PrismaModel = never> = {
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
in?: number[] | null in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
notIn?: number[] | null notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
lt?: number | Prisma.IntFieldRefInput<$PrismaModel> lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
lte?: number | Prisma.IntFieldRefInput<$PrismaModel> lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
gt?: number | Prisma.IntFieldRefInput<$PrismaModel> gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
@@ -226,15 +230,15 @@ export type BoolWithAggregatesFilter<$PrismaModel = never> = {
export type EnumChallengeStatusFilter<$PrismaModel = never> = { export type EnumChallengeStatusFilter<$PrismaModel = never> = {
equals?: $Enums.ChallengeStatus | Prisma.EnumChallengeStatusFieldRefInput<$PrismaModel> equals?: $Enums.ChallengeStatus | Prisma.EnumChallengeStatusFieldRefInput<$PrismaModel>
in?: $Enums.ChallengeStatus[] in?: $Enums.ChallengeStatus[] | Prisma.ListEnumChallengeStatusFieldRefInput<$PrismaModel>
notIn?: $Enums.ChallengeStatus[] notIn?: $Enums.ChallengeStatus[] | Prisma.ListEnumChallengeStatusFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumChallengeStatusFilter<$PrismaModel> | $Enums.ChallengeStatus not?: Prisma.NestedEnumChallengeStatusFilter<$PrismaModel> | $Enums.ChallengeStatus
} }
export type DateTimeNullableFilter<$PrismaModel = never> = { export type DateTimeNullableFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null
in?: Date[] | string[] | null in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
notIn?: Date[] | string[] | null notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
@@ -244,8 +248,8 @@ export type DateTimeNullableFilter<$PrismaModel = never> = {
export type EnumChallengeStatusWithAggregatesFilter<$PrismaModel = never> = { export type EnumChallengeStatusWithAggregatesFilter<$PrismaModel = never> = {
equals?: $Enums.ChallengeStatus | Prisma.EnumChallengeStatusFieldRefInput<$PrismaModel> equals?: $Enums.ChallengeStatus | Prisma.EnumChallengeStatusFieldRefInput<$PrismaModel>
in?: $Enums.ChallengeStatus[] in?: $Enums.ChallengeStatus[] | Prisma.ListEnumChallengeStatusFieldRefInput<$PrismaModel>
notIn?: $Enums.ChallengeStatus[] notIn?: $Enums.ChallengeStatus[] | Prisma.ListEnumChallengeStatusFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumChallengeStatusWithAggregatesFilter<$PrismaModel> | $Enums.ChallengeStatus not?: Prisma.NestedEnumChallengeStatusWithAggregatesFilter<$PrismaModel> | $Enums.ChallengeStatus
_count?: Prisma.NestedIntFilter<$PrismaModel> _count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedEnumChallengeStatusFilter<$PrismaModel> _min?: Prisma.NestedEnumChallengeStatusFilter<$PrismaModel>
@@ -254,8 +258,8 @@ export type EnumChallengeStatusWithAggregatesFilter<$PrismaModel = never> = {
export type DateTimeNullableWithAggregatesFilter<$PrismaModel = never> = { export type DateTimeNullableWithAggregatesFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null
in?: Date[] | string[] | null in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
notIn?: Date[] | string[] | null notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
@@ -266,10 +270,61 @@ export type DateTimeNullableWithAggregatesFilter<$PrismaModel = never> = {
_max?: Prisma.NestedDateTimeNullableFilter<$PrismaModel> _max?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>
} }
export type EnumHouseRoleFilter<$PrismaModel = never> = {
equals?: $Enums.HouseRole | Prisma.EnumHouseRoleFieldRefInput<$PrismaModel>
in?: $Enums.HouseRole[] | Prisma.ListEnumHouseRoleFieldRefInput<$PrismaModel>
notIn?: $Enums.HouseRole[] | Prisma.ListEnumHouseRoleFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumHouseRoleFilter<$PrismaModel> | $Enums.HouseRole
}
export type EnumHouseRoleWithAggregatesFilter<$PrismaModel = never> = {
equals?: $Enums.HouseRole | Prisma.EnumHouseRoleFieldRefInput<$PrismaModel>
in?: $Enums.HouseRole[] | Prisma.ListEnumHouseRoleFieldRefInput<$PrismaModel>
notIn?: $Enums.HouseRole[] | Prisma.ListEnumHouseRoleFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumHouseRoleWithAggregatesFilter<$PrismaModel> | $Enums.HouseRole
_count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedEnumHouseRoleFilter<$PrismaModel>
_max?: Prisma.NestedEnumHouseRoleFilter<$PrismaModel>
}
export type EnumInvitationStatusFilter<$PrismaModel = never> = {
equals?: $Enums.InvitationStatus | Prisma.EnumInvitationStatusFieldRefInput<$PrismaModel>
in?: $Enums.InvitationStatus[] | Prisma.ListEnumInvitationStatusFieldRefInput<$PrismaModel>
notIn?: $Enums.InvitationStatus[] | Prisma.ListEnumInvitationStatusFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumInvitationStatusFilter<$PrismaModel> | $Enums.InvitationStatus
}
export type EnumInvitationStatusWithAggregatesFilter<$PrismaModel = never> = {
equals?: $Enums.InvitationStatus | Prisma.EnumInvitationStatusFieldRefInput<$PrismaModel>
in?: $Enums.InvitationStatus[] | Prisma.ListEnumInvitationStatusFieldRefInput<$PrismaModel>
notIn?: $Enums.InvitationStatus[] | Prisma.ListEnumInvitationStatusFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumInvitationStatusWithAggregatesFilter<$PrismaModel> | $Enums.InvitationStatus
_count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedEnumInvitationStatusFilter<$PrismaModel>
_max?: Prisma.NestedEnumInvitationStatusFilter<$PrismaModel>
}
export type EnumRequestStatusFilter<$PrismaModel = never> = {
equals?: $Enums.RequestStatus | Prisma.EnumRequestStatusFieldRefInput<$PrismaModel>
in?: $Enums.RequestStatus[] | Prisma.ListEnumRequestStatusFieldRefInput<$PrismaModel>
notIn?: $Enums.RequestStatus[] | Prisma.ListEnumRequestStatusFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumRequestStatusFilter<$PrismaModel> | $Enums.RequestStatus
}
export type EnumRequestStatusWithAggregatesFilter<$PrismaModel = never> = {
equals?: $Enums.RequestStatus | Prisma.EnumRequestStatusFieldRefInput<$PrismaModel>
in?: $Enums.RequestStatus[] | Prisma.ListEnumRequestStatusFieldRefInput<$PrismaModel>
notIn?: $Enums.RequestStatus[] | Prisma.ListEnumRequestStatusFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumRequestStatusWithAggregatesFilter<$PrismaModel> | $Enums.RequestStatus
_count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedEnumRequestStatusFilter<$PrismaModel>
_max?: Prisma.NestedEnumRequestStatusFilter<$PrismaModel>
}
export type NestedStringFilter<$PrismaModel = never> = { export type NestedStringFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
in?: string[] in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
notIn?: string[] notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
lt?: string | Prisma.StringFieldRefInput<$PrismaModel> lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel> lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel> gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
@@ -282,15 +337,15 @@ export type NestedStringFilter<$PrismaModel = never> = {
export type NestedEnumRoleFilter<$PrismaModel = never> = { export type NestedEnumRoleFilter<$PrismaModel = never> = {
equals?: $Enums.Role | Prisma.EnumRoleFieldRefInput<$PrismaModel> equals?: $Enums.Role | Prisma.EnumRoleFieldRefInput<$PrismaModel>
in?: $Enums.Role[] in?: $Enums.Role[] | Prisma.ListEnumRoleFieldRefInput<$PrismaModel>
notIn?: $Enums.Role[] notIn?: $Enums.Role[] | Prisma.ListEnumRoleFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumRoleFilter<$PrismaModel> | $Enums.Role not?: Prisma.NestedEnumRoleFilter<$PrismaModel> | $Enums.Role
} }
export type NestedIntFilter<$PrismaModel = never> = { export type NestedIntFilter<$PrismaModel = never> = {
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
in?: number[] in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
notIn?: number[] notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
lt?: number | Prisma.IntFieldRefInput<$PrismaModel> lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
lte?: number | Prisma.IntFieldRefInput<$PrismaModel> lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
gt?: number | Prisma.IntFieldRefInput<$PrismaModel> gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
@@ -300,8 +355,8 @@ export type NestedIntFilter<$PrismaModel = never> = {
export type NestedStringNullableFilter<$PrismaModel = never> = { export type NestedStringNullableFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
in?: string[] | null in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null
notIn?: string[] | null notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null
lt?: string | Prisma.StringFieldRefInput<$PrismaModel> lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel> lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel> gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
@@ -314,8 +369,8 @@ export type NestedStringNullableFilter<$PrismaModel = never> = {
export type NestedDateTimeFilter<$PrismaModel = never> = { export type NestedDateTimeFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
in?: Date[] | string[] in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
notIn?: Date[] | string[] notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
@@ -325,15 +380,15 @@ export type NestedDateTimeFilter<$PrismaModel = never> = {
export type NestedEnumCharacterClassNullableFilter<$PrismaModel = never> = { export type NestedEnumCharacterClassNullableFilter<$PrismaModel = never> = {
equals?: $Enums.CharacterClass | Prisma.EnumCharacterClassFieldRefInput<$PrismaModel> | null equals?: $Enums.CharacterClass | Prisma.EnumCharacterClassFieldRefInput<$PrismaModel> | null
in?: $Enums.CharacterClass[] | null in?: $Enums.CharacterClass[] | Prisma.ListEnumCharacterClassFieldRefInput<$PrismaModel> | null
notIn?: $Enums.CharacterClass[] | null notIn?: $Enums.CharacterClass[] | Prisma.ListEnumCharacterClassFieldRefInput<$PrismaModel> | null
not?: Prisma.NestedEnumCharacterClassNullableFilter<$PrismaModel> | $Enums.CharacterClass | null not?: Prisma.NestedEnumCharacterClassNullableFilter<$PrismaModel> | $Enums.CharacterClass | null
} }
export type NestedStringWithAggregatesFilter<$PrismaModel = never> = { export type NestedStringWithAggregatesFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
in?: string[] in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
notIn?: string[] notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
lt?: string | Prisma.StringFieldRefInput<$PrismaModel> lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel> lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel> gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
@@ -349,8 +404,8 @@ export type NestedStringWithAggregatesFilter<$PrismaModel = never> = {
export type NestedEnumRoleWithAggregatesFilter<$PrismaModel = never> = { export type NestedEnumRoleWithAggregatesFilter<$PrismaModel = never> = {
equals?: $Enums.Role | Prisma.EnumRoleFieldRefInput<$PrismaModel> equals?: $Enums.Role | Prisma.EnumRoleFieldRefInput<$PrismaModel>
in?: $Enums.Role[] in?: $Enums.Role[] | Prisma.ListEnumRoleFieldRefInput<$PrismaModel>
notIn?: $Enums.Role[] notIn?: $Enums.Role[] | Prisma.ListEnumRoleFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumRoleWithAggregatesFilter<$PrismaModel> | $Enums.Role not?: Prisma.NestedEnumRoleWithAggregatesFilter<$PrismaModel> | $Enums.Role
_count?: Prisma.NestedIntFilter<$PrismaModel> _count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedEnumRoleFilter<$PrismaModel> _min?: Prisma.NestedEnumRoleFilter<$PrismaModel>
@@ -359,8 +414,8 @@ export type NestedEnumRoleWithAggregatesFilter<$PrismaModel = never> = {
export type NestedIntWithAggregatesFilter<$PrismaModel = never> = { export type NestedIntWithAggregatesFilter<$PrismaModel = never> = {
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
in?: number[] in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
notIn?: number[] notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
lt?: number | Prisma.IntFieldRefInput<$PrismaModel> lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
lte?: number | Prisma.IntFieldRefInput<$PrismaModel> lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
gt?: number | Prisma.IntFieldRefInput<$PrismaModel> gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
@@ -375,8 +430,8 @@ export type NestedIntWithAggregatesFilter<$PrismaModel = never> = {
export type NestedFloatFilter<$PrismaModel = never> = { export type NestedFloatFilter<$PrismaModel = never> = {
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> equals?: number | Prisma.FloatFieldRefInput<$PrismaModel>
in?: number[] in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel>
notIn?: number[] notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel>
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel> lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel> lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel> gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
@@ -386,8 +441,8 @@ export type NestedFloatFilter<$PrismaModel = never> = {
export type NestedStringNullableWithAggregatesFilter<$PrismaModel = never> = { export type NestedStringNullableWithAggregatesFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
in?: string[] | null in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null
notIn?: string[] | null notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null
lt?: string | Prisma.StringFieldRefInput<$PrismaModel> lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel> lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel> gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
@@ -403,8 +458,8 @@ export type NestedStringNullableWithAggregatesFilter<$PrismaModel = never> = {
export type NestedIntNullableFilter<$PrismaModel = never> = { export type NestedIntNullableFilter<$PrismaModel = never> = {
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
in?: number[] | null in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
notIn?: number[] | null notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
lt?: number | Prisma.IntFieldRefInput<$PrismaModel> lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
lte?: number | Prisma.IntFieldRefInput<$PrismaModel> lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
gt?: number | Prisma.IntFieldRefInput<$PrismaModel> gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
@@ -414,8 +469,8 @@ export type NestedIntNullableFilter<$PrismaModel = never> = {
export type NestedDateTimeWithAggregatesFilter<$PrismaModel = never> = { export type NestedDateTimeWithAggregatesFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
in?: Date[] | string[] in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
notIn?: Date[] | string[] notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
@@ -428,8 +483,8 @@ export type NestedDateTimeWithAggregatesFilter<$PrismaModel = never> = {
export type NestedEnumCharacterClassNullableWithAggregatesFilter<$PrismaModel = never> = { export type NestedEnumCharacterClassNullableWithAggregatesFilter<$PrismaModel = never> = {
equals?: $Enums.CharacterClass | Prisma.EnumCharacterClassFieldRefInput<$PrismaModel> | null equals?: $Enums.CharacterClass | Prisma.EnumCharacterClassFieldRefInput<$PrismaModel> | null
in?: $Enums.CharacterClass[] | null in?: $Enums.CharacterClass[] | Prisma.ListEnumCharacterClassFieldRefInput<$PrismaModel> | null
notIn?: $Enums.CharacterClass[] | null notIn?: $Enums.CharacterClass[] | Prisma.ListEnumCharacterClassFieldRefInput<$PrismaModel> | null
not?: Prisma.NestedEnumCharacterClassNullableWithAggregatesFilter<$PrismaModel> | $Enums.CharacterClass | null not?: Prisma.NestedEnumCharacterClassNullableWithAggregatesFilter<$PrismaModel> | $Enums.CharacterClass | null
_count?: Prisma.NestedIntNullableFilter<$PrismaModel> _count?: Prisma.NestedIntNullableFilter<$PrismaModel>
_min?: Prisma.NestedEnumCharacterClassNullableFilter<$PrismaModel> _min?: Prisma.NestedEnumCharacterClassNullableFilter<$PrismaModel>
@@ -438,15 +493,15 @@ export type NestedEnumCharacterClassNullableWithAggregatesFilter<$PrismaModel =
export type NestedEnumEventTypeFilter<$PrismaModel = never> = { export type NestedEnumEventTypeFilter<$PrismaModel = never> = {
equals?: $Enums.EventType | Prisma.EnumEventTypeFieldRefInput<$PrismaModel> equals?: $Enums.EventType | Prisma.EnumEventTypeFieldRefInput<$PrismaModel>
in?: $Enums.EventType[] in?: $Enums.EventType[] | Prisma.ListEnumEventTypeFieldRefInput<$PrismaModel>
notIn?: $Enums.EventType[] notIn?: $Enums.EventType[] | Prisma.ListEnumEventTypeFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumEventTypeFilter<$PrismaModel> | $Enums.EventType not?: Prisma.NestedEnumEventTypeFilter<$PrismaModel> | $Enums.EventType
} }
export type NestedEnumEventTypeWithAggregatesFilter<$PrismaModel = never> = { export type NestedEnumEventTypeWithAggregatesFilter<$PrismaModel = never> = {
equals?: $Enums.EventType | Prisma.EnumEventTypeFieldRefInput<$PrismaModel> equals?: $Enums.EventType | Prisma.EnumEventTypeFieldRefInput<$PrismaModel>
in?: $Enums.EventType[] in?: $Enums.EventType[] | Prisma.ListEnumEventTypeFieldRefInput<$PrismaModel>
notIn?: $Enums.EventType[] notIn?: $Enums.EventType[] | Prisma.ListEnumEventTypeFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumEventTypeWithAggregatesFilter<$PrismaModel> | $Enums.EventType not?: Prisma.NestedEnumEventTypeWithAggregatesFilter<$PrismaModel> | $Enums.EventType
_count?: Prisma.NestedIntFilter<$PrismaModel> _count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedEnumEventTypeFilter<$PrismaModel> _min?: Prisma.NestedEnumEventTypeFilter<$PrismaModel>
@@ -455,8 +510,8 @@ export type NestedEnumEventTypeWithAggregatesFilter<$PrismaModel = never> = {
export type NestedIntNullableWithAggregatesFilter<$PrismaModel = never> = { export type NestedIntNullableWithAggregatesFilter<$PrismaModel = never> = {
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
in?: number[] | null in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
notIn?: number[] | null notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
lt?: number | Prisma.IntFieldRefInput<$PrismaModel> lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
lte?: number | Prisma.IntFieldRefInput<$PrismaModel> lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
gt?: number | Prisma.IntFieldRefInput<$PrismaModel> gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
@@ -471,8 +526,8 @@ export type NestedIntNullableWithAggregatesFilter<$PrismaModel = never> = {
export type NestedFloatNullableFilter<$PrismaModel = never> = { export type NestedFloatNullableFilter<$PrismaModel = never> = {
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> | null equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> | null
in?: number[] | null in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> | null
notIn?: number[] | null notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> | null
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel> lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel> lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel> gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
@@ -495,15 +550,15 @@ export type NestedBoolWithAggregatesFilter<$PrismaModel = never> = {
export type NestedEnumChallengeStatusFilter<$PrismaModel = never> = { export type NestedEnumChallengeStatusFilter<$PrismaModel = never> = {
equals?: $Enums.ChallengeStatus | Prisma.EnumChallengeStatusFieldRefInput<$PrismaModel> equals?: $Enums.ChallengeStatus | Prisma.EnumChallengeStatusFieldRefInput<$PrismaModel>
in?: $Enums.ChallengeStatus[] in?: $Enums.ChallengeStatus[] | Prisma.ListEnumChallengeStatusFieldRefInput<$PrismaModel>
notIn?: $Enums.ChallengeStatus[] notIn?: $Enums.ChallengeStatus[] | Prisma.ListEnumChallengeStatusFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumChallengeStatusFilter<$PrismaModel> | $Enums.ChallengeStatus not?: Prisma.NestedEnumChallengeStatusFilter<$PrismaModel> | $Enums.ChallengeStatus
} }
export type NestedDateTimeNullableFilter<$PrismaModel = never> = { export type NestedDateTimeNullableFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null
in?: Date[] | string[] | null in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
notIn?: Date[] | string[] | null notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
@@ -513,8 +568,8 @@ export type NestedDateTimeNullableFilter<$PrismaModel = never> = {
export type NestedEnumChallengeStatusWithAggregatesFilter<$PrismaModel = never> = { export type NestedEnumChallengeStatusWithAggregatesFilter<$PrismaModel = never> = {
equals?: $Enums.ChallengeStatus | Prisma.EnumChallengeStatusFieldRefInput<$PrismaModel> equals?: $Enums.ChallengeStatus | Prisma.EnumChallengeStatusFieldRefInput<$PrismaModel>
in?: $Enums.ChallengeStatus[] in?: $Enums.ChallengeStatus[] | Prisma.ListEnumChallengeStatusFieldRefInput<$PrismaModel>
notIn?: $Enums.ChallengeStatus[] notIn?: $Enums.ChallengeStatus[] | Prisma.ListEnumChallengeStatusFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumChallengeStatusWithAggregatesFilter<$PrismaModel> | $Enums.ChallengeStatus not?: Prisma.NestedEnumChallengeStatusWithAggregatesFilter<$PrismaModel> | $Enums.ChallengeStatus
_count?: Prisma.NestedIntFilter<$PrismaModel> _count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedEnumChallengeStatusFilter<$PrismaModel> _min?: Prisma.NestedEnumChallengeStatusFilter<$PrismaModel>
@@ -523,8 +578,8 @@ export type NestedEnumChallengeStatusWithAggregatesFilter<$PrismaModel = never>
export type NestedDateTimeNullableWithAggregatesFilter<$PrismaModel = never> = { export type NestedDateTimeNullableWithAggregatesFilter<$PrismaModel = never> = {
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null
in?: Date[] | string[] | null in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
notIn?: Date[] | string[] | null notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
@@ -535,4 +590,55 @@ export type NestedDateTimeNullableWithAggregatesFilter<$PrismaModel = never> = {
_max?: Prisma.NestedDateTimeNullableFilter<$PrismaModel> _max?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>
} }
export type NestedEnumHouseRoleFilter<$PrismaModel = never> = {
equals?: $Enums.HouseRole | Prisma.EnumHouseRoleFieldRefInput<$PrismaModel>
in?: $Enums.HouseRole[] | Prisma.ListEnumHouseRoleFieldRefInput<$PrismaModel>
notIn?: $Enums.HouseRole[] | Prisma.ListEnumHouseRoleFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumHouseRoleFilter<$PrismaModel> | $Enums.HouseRole
}
export type NestedEnumHouseRoleWithAggregatesFilter<$PrismaModel = never> = {
equals?: $Enums.HouseRole | Prisma.EnumHouseRoleFieldRefInput<$PrismaModel>
in?: $Enums.HouseRole[] | Prisma.ListEnumHouseRoleFieldRefInput<$PrismaModel>
notIn?: $Enums.HouseRole[] | Prisma.ListEnumHouseRoleFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumHouseRoleWithAggregatesFilter<$PrismaModel> | $Enums.HouseRole
_count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedEnumHouseRoleFilter<$PrismaModel>
_max?: Prisma.NestedEnumHouseRoleFilter<$PrismaModel>
}
export type NestedEnumInvitationStatusFilter<$PrismaModel = never> = {
equals?: $Enums.InvitationStatus | Prisma.EnumInvitationStatusFieldRefInput<$PrismaModel>
in?: $Enums.InvitationStatus[] | Prisma.ListEnumInvitationStatusFieldRefInput<$PrismaModel>
notIn?: $Enums.InvitationStatus[] | Prisma.ListEnumInvitationStatusFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumInvitationStatusFilter<$PrismaModel> | $Enums.InvitationStatus
}
export type NestedEnumInvitationStatusWithAggregatesFilter<$PrismaModel = never> = {
equals?: $Enums.InvitationStatus | Prisma.EnumInvitationStatusFieldRefInput<$PrismaModel>
in?: $Enums.InvitationStatus[] | Prisma.ListEnumInvitationStatusFieldRefInput<$PrismaModel>
notIn?: $Enums.InvitationStatus[] | Prisma.ListEnumInvitationStatusFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumInvitationStatusWithAggregatesFilter<$PrismaModel> | $Enums.InvitationStatus
_count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedEnumInvitationStatusFilter<$PrismaModel>
_max?: Prisma.NestedEnumInvitationStatusFilter<$PrismaModel>
}
export type NestedEnumRequestStatusFilter<$PrismaModel = never> = {
equals?: $Enums.RequestStatus | Prisma.EnumRequestStatusFieldRefInput<$PrismaModel>
in?: $Enums.RequestStatus[] | Prisma.ListEnumRequestStatusFieldRefInput<$PrismaModel>
notIn?: $Enums.RequestStatus[] | Prisma.ListEnumRequestStatusFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumRequestStatusFilter<$PrismaModel> | $Enums.RequestStatus
}
export type NestedEnumRequestStatusWithAggregatesFilter<$PrismaModel = never> = {
equals?: $Enums.RequestStatus | Prisma.EnumRequestStatusFieldRefInput<$PrismaModel>
in?: $Enums.RequestStatus[] | Prisma.ListEnumRequestStatusFieldRefInput<$PrismaModel>
notIn?: $Enums.RequestStatus[] | Prisma.ListEnumRequestStatusFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumRequestStatusWithAggregatesFilter<$PrismaModel> | $Enums.RequestStatus
_count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedEnumRequestStatusFilter<$PrismaModel>
_max?: Prisma.NestedEnumRequestStatusFilter<$PrismaModel>
}

View File

@@ -52,3 +52,32 @@ export const ChallengeStatus = {
} as const } as const
export type ChallengeStatus = (typeof ChallengeStatus)[keyof typeof ChallengeStatus] export type ChallengeStatus = (typeof ChallengeStatus)[keyof typeof ChallengeStatus]
export const HouseRole = {
OWNER: 'OWNER',
ADMIN: 'ADMIN',
MEMBER: 'MEMBER'
} as const
export type HouseRole = (typeof HouseRole)[keyof typeof HouseRole]
export const InvitationStatus = {
PENDING: 'PENDING',
ACCEPTED: 'ACCEPTED',
REJECTED: 'REJECTED',
CANCELLED: 'CANCELLED'
} as const
export type InvitationStatus = (typeof InvitationStatus)[keyof typeof InvitationStatus]
export const RequestStatus = {
PENDING: 'PENDING',
ACCEPTED: 'ACCEPTED',
REJECTED: 'REJECTED',
CANCELLED: 'CANCELLED'
} as const
export type RequestStatus = (typeof RequestStatus)[keyof typeof RequestStatus]

File diff suppressed because one or more lines are too long

View File

@@ -390,7 +390,11 @@ export const ModelName = {
EventRegistration: 'EventRegistration', EventRegistration: 'EventRegistration',
EventFeedback: 'EventFeedback', EventFeedback: 'EventFeedback',
SitePreferences: 'SitePreferences', SitePreferences: 'SitePreferences',
Challenge: 'Challenge' Challenge: 'Challenge',
House: 'House',
HouseMembership: 'HouseMembership',
HouseInvitation: 'HouseInvitation',
HouseRequest: 'HouseRequest'
} as const } as const
export type ModelName = (typeof ModelName)[keyof typeof ModelName] export type ModelName = (typeof ModelName)[keyof typeof ModelName]
@@ -406,7 +410,7 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
omit: GlobalOmitOptions omit: GlobalOmitOptions
} }
meta: { meta: {
modelProps: "user" | "userPreferences" | "event" | "eventRegistration" | "eventFeedback" | "sitePreferences" | "challenge" modelProps: "user" | "userPreferences" | "event" | "eventRegistration" | "eventFeedback" | "sitePreferences" | "challenge" | "house" | "houseMembership" | "houseInvitation" | "houseRequest"
txIsolationLevel: TransactionIsolationLevel txIsolationLevel: TransactionIsolationLevel
} }
model: { model: {
@@ -928,6 +932,302 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
} }
} }
} }
House: {
payload: Prisma.$HousePayload<ExtArgs>
fields: Prisma.HouseFieldRefs
operations: {
findUnique: {
args: Prisma.HouseFindUniqueArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HousePayload> | null
}
findUniqueOrThrow: {
args: Prisma.HouseFindUniqueOrThrowArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HousePayload>
}
findFirst: {
args: Prisma.HouseFindFirstArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HousePayload> | null
}
findFirstOrThrow: {
args: Prisma.HouseFindFirstOrThrowArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HousePayload>
}
findMany: {
args: Prisma.HouseFindManyArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HousePayload>[]
}
create: {
args: Prisma.HouseCreateArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HousePayload>
}
createMany: {
args: Prisma.HouseCreateManyArgs<ExtArgs>
result: BatchPayload
}
createManyAndReturn: {
args: Prisma.HouseCreateManyAndReturnArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HousePayload>[]
}
delete: {
args: Prisma.HouseDeleteArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HousePayload>
}
update: {
args: Prisma.HouseUpdateArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HousePayload>
}
deleteMany: {
args: Prisma.HouseDeleteManyArgs<ExtArgs>
result: BatchPayload
}
updateMany: {
args: Prisma.HouseUpdateManyArgs<ExtArgs>
result: BatchPayload
}
updateManyAndReturn: {
args: Prisma.HouseUpdateManyAndReturnArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HousePayload>[]
}
upsert: {
args: Prisma.HouseUpsertArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HousePayload>
}
aggregate: {
args: Prisma.HouseAggregateArgs<ExtArgs>
result: runtime.Types.Utils.Optional<Prisma.AggregateHouse>
}
groupBy: {
args: Prisma.HouseGroupByArgs<ExtArgs>
result: runtime.Types.Utils.Optional<Prisma.HouseGroupByOutputType>[]
}
count: {
args: Prisma.HouseCountArgs<ExtArgs>
result: runtime.Types.Utils.Optional<Prisma.HouseCountAggregateOutputType> | number
}
}
}
HouseMembership: {
payload: Prisma.$HouseMembershipPayload<ExtArgs>
fields: Prisma.HouseMembershipFieldRefs
operations: {
findUnique: {
args: Prisma.HouseMembershipFindUniqueArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HouseMembershipPayload> | null
}
findUniqueOrThrow: {
args: Prisma.HouseMembershipFindUniqueOrThrowArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HouseMembershipPayload>
}
findFirst: {
args: Prisma.HouseMembershipFindFirstArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HouseMembershipPayload> | null
}
findFirstOrThrow: {
args: Prisma.HouseMembershipFindFirstOrThrowArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HouseMembershipPayload>
}
findMany: {
args: Prisma.HouseMembershipFindManyArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HouseMembershipPayload>[]
}
create: {
args: Prisma.HouseMembershipCreateArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HouseMembershipPayload>
}
createMany: {
args: Prisma.HouseMembershipCreateManyArgs<ExtArgs>
result: BatchPayload
}
createManyAndReturn: {
args: Prisma.HouseMembershipCreateManyAndReturnArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HouseMembershipPayload>[]
}
delete: {
args: Prisma.HouseMembershipDeleteArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HouseMembershipPayload>
}
update: {
args: Prisma.HouseMembershipUpdateArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HouseMembershipPayload>
}
deleteMany: {
args: Prisma.HouseMembershipDeleteManyArgs<ExtArgs>
result: BatchPayload
}
updateMany: {
args: Prisma.HouseMembershipUpdateManyArgs<ExtArgs>
result: BatchPayload
}
updateManyAndReturn: {
args: Prisma.HouseMembershipUpdateManyAndReturnArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HouseMembershipPayload>[]
}
upsert: {
args: Prisma.HouseMembershipUpsertArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HouseMembershipPayload>
}
aggregate: {
args: Prisma.HouseMembershipAggregateArgs<ExtArgs>
result: runtime.Types.Utils.Optional<Prisma.AggregateHouseMembership>
}
groupBy: {
args: Prisma.HouseMembershipGroupByArgs<ExtArgs>
result: runtime.Types.Utils.Optional<Prisma.HouseMembershipGroupByOutputType>[]
}
count: {
args: Prisma.HouseMembershipCountArgs<ExtArgs>
result: runtime.Types.Utils.Optional<Prisma.HouseMembershipCountAggregateOutputType> | number
}
}
}
HouseInvitation: {
payload: Prisma.$HouseInvitationPayload<ExtArgs>
fields: Prisma.HouseInvitationFieldRefs
operations: {
findUnique: {
args: Prisma.HouseInvitationFindUniqueArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HouseInvitationPayload> | null
}
findUniqueOrThrow: {
args: Prisma.HouseInvitationFindUniqueOrThrowArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HouseInvitationPayload>
}
findFirst: {
args: Prisma.HouseInvitationFindFirstArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HouseInvitationPayload> | null
}
findFirstOrThrow: {
args: Prisma.HouseInvitationFindFirstOrThrowArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HouseInvitationPayload>
}
findMany: {
args: Prisma.HouseInvitationFindManyArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HouseInvitationPayload>[]
}
create: {
args: Prisma.HouseInvitationCreateArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HouseInvitationPayload>
}
createMany: {
args: Prisma.HouseInvitationCreateManyArgs<ExtArgs>
result: BatchPayload
}
createManyAndReturn: {
args: Prisma.HouseInvitationCreateManyAndReturnArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HouseInvitationPayload>[]
}
delete: {
args: Prisma.HouseInvitationDeleteArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HouseInvitationPayload>
}
update: {
args: Prisma.HouseInvitationUpdateArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HouseInvitationPayload>
}
deleteMany: {
args: Prisma.HouseInvitationDeleteManyArgs<ExtArgs>
result: BatchPayload
}
updateMany: {
args: Prisma.HouseInvitationUpdateManyArgs<ExtArgs>
result: BatchPayload
}
updateManyAndReturn: {
args: Prisma.HouseInvitationUpdateManyAndReturnArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HouseInvitationPayload>[]
}
upsert: {
args: Prisma.HouseInvitationUpsertArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HouseInvitationPayload>
}
aggregate: {
args: Prisma.HouseInvitationAggregateArgs<ExtArgs>
result: runtime.Types.Utils.Optional<Prisma.AggregateHouseInvitation>
}
groupBy: {
args: Prisma.HouseInvitationGroupByArgs<ExtArgs>
result: runtime.Types.Utils.Optional<Prisma.HouseInvitationGroupByOutputType>[]
}
count: {
args: Prisma.HouseInvitationCountArgs<ExtArgs>
result: runtime.Types.Utils.Optional<Prisma.HouseInvitationCountAggregateOutputType> | number
}
}
}
HouseRequest: {
payload: Prisma.$HouseRequestPayload<ExtArgs>
fields: Prisma.HouseRequestFieldRefs
operations: {
findUnique: {
args: Prisma.HouseRequestFindUniqueArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HouseRequestPayload> | null
}
findUniqueOrThrow: {
args: Prisma.HouseRequestFindUniqueOrThrowArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HouseRequestPayload>
}
findFirst: {
args: Prisma.HouseRequestFindFirstArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HouseRequestPayload> | null
}
findFirstOrThrow: {
args: Prisma.HouseRequestFindFirstOrThrowArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HouseRequestPayload>
}
findMany: {
args: Prisma.HouseRequestFindManyArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HouseRequestPayload>[]
}
create: {
args: Prisma.HouseRequestCreateArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HouseRequestPayload>
}
createMany: {
args: Prisma.HouseRequestCreateManyArgs<ExtArgs>
result: BatchPayload
}
createManyAndReturn: {
args: Prisma.HouseRequestCreateManyAndReturnArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HouseRequestPayload>[]
}
delete: {
args: Prisma.HouseRequestDeleteArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HouseRequestPayload>
}
update: {
args: Prisma.HouseRequestUpdateArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HouseRequestPayload>
}
deleteMany: {
args: Prisma.HouseRequestDeleteManyArgs<ExtArgs>
result: BatchPayload
}
updateMany: {
args: Prisma.HouseRequestUpdateManyArgs<ExtArgs>
result: BatchPayload
}
updateManyAndReturn: {
args: Prisma.HouseRequestUpdateManyAndReturnArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HouseRequestPayload>[]
}
upsert: {
args: Prisma.HouseRequestUpsertArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$HouseRequestPayload>
}
aggregate: {
args: Prisma.HouseRequestAggregateArgs<ExtArgs>
result: runtime.Types.Utils.Optional<Prisma.AggregateHouseRequest>
}
groupBy: {
args: Prisma.HouseRequestGroupByArgs<ExtArgs>
result: runtime.Types.Utils.Optional<Prisma.HouseRequestGroupByOutputType>[]
}
count: {
args: Prisma.HouseRequestCountArgs<ExtArgs>
result: runtime.Types.Utils.Optional<Prisma.HouseRequestCountAggregateOutputType> | number
}
}
}
} }
} & { } & {
other: { other: {
@@ -958,6 +1258,9 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
*/ */
export const TransactionIsolationLevel = runtime.makeStrictEnum({ export const TransactionIsolationLevel = runtime.makeStrictEnum({
ReadUncommitted: 'ReadUncommitted',
ReadCommitted: 'ReadCommitted',
RepeatableRead: 'RepeatableRead',
Serializable: 'Serializable' Serializable: 'Serializable'
} as const) } as const)
@@ -1046,8 +1349,13 @@ export const SitePreferencesScalarFieldEnum = {
eventsBackground: 'eventsBackground', eventsBackground: 'eventsBackground',
leaderboardBackground: 'leaderboardBackground', leaderboardBackground: 'leaderboardBackground',
challengesBackground: 'challengesBackground', challengesBackground: 'challengesBackground',
profileBackground: 'profileBackground',
houseBackground: 'houseBackground',
eventRegistrationPoints: 'eventRegistrationPoints', eventRegistrationPoints: 'eventRegistrationPoints',
eventFeedbackPoints: 'eventFeedbackPoints', eventFeedbackPoints: 'eventFeedbackPoints',
houseJoinPoints: 'houseJoinPoints',
houseLeavePoints: 'houseLeavePoints',
houseCreatePoints: 'houseCreatePoints',
createdAt: 'createdAt', createdAt: 'createdAt',
updatedAt: 'updatedAt' updatedAt: 'updatedAt'
} as const } as const
@@ -1075,6 +1383,54 @@ export const ChallengeScalarFieldEnum = {
export type ChallengeScalarFieldEnum = (typeof ChallengeScalarFieldEnum)[keyof typeof ChallengeScalarFieldEnum] export type ChallengeScalarFieldEnum = (typeof ChallengeScalarFieldEnum)[keyof typeof ChallengeScalarFieldEnum]
export const HouseScalarFieldEnum = {
id: 'id',
name: 'name',
description: 'description',
creatorId: 'creatorId',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
} as const
export type HouseScalarFieldEnum = (typeof HouseScalarFieldEnum)[keyof typeof HouseScalarFieldEnum]
export const HouseMembershipScalarFieldEnum = {
id: 'id',
houseId: 'houseId',
userId: 'userId',
role: 'role',
joinedAt: 'joinedAt'
} as const
export type HouseMembershipScalarFieldEnum = (typeof HouseMembershipScalarFieldEnum)[keyof typeof HouseMembershipScalarFieldEnum]
export const HouseInvitationScalarFieldEnum = {
id: 'id',
houseId: 'houseId',
inviterId: 'inviterId',
inviteeId: 'inviteeId',
status: 'status',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
} as const
export type HouseInvitationScalarFieldEnum = (typeof HouseInvitationScalarFieldEnum)[keyof typeof HouseInvitationScalarFieldEnum]
export const HouseRequestScalarFieldEnum = {
id: 'id',
houseId: 'houseId',
requesterId: 'requesterId',
status: 'status',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
} as const
export type HouseRequestScalarFieldEnum = (typeof HouseRequestScalarFieldEnum)[keyof typeof HouseRequestScalarFieldEnum]
export const SortOrder = { export const SortOrder = {
asc: 'asc', asc: 'asc',
desc: 'desc' desc: 'desc'
@@ -1083,6 +1439,14 @@ export const SortOrder = {
export type SortOrder = (typeof SortOrder)[keyof typeof SortOrder] export type SortOrder = (typeof SortOrder)[keyof typeof SortOrder]
export const QueryMode = {
default: 'default',
insensitive: 'insensitive'
} as const
export type QueryMode = (typeof QueryMode)[keyof typeof QueryMode]
export const NullsOrder = { export const NullsOrder = {
first: 'first', first: 'first',
last: 'last' last: 'last'
@@ -1104,6 +1468,13 @@ export type StringFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel,
/**
* Reference to a field of type 'String[]'
*/
export type ListStringFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'String[]'>
/** /**
* Reference to a field of type 'Role' * Reference to a field of type 'Role'
*/ */
@@ -1111,6 +1482,13 @@ export type EnumRoleFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel
/**
* Reference to a field of type 'Role[]'
*/
export type ListEnumRoleFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Role[]'>
/** /**
* Reference to a field of type 'Int' * Reference to a field of type 'Int'
*/ */
@@ -1118,6 +1496,13 @@ export type IntFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'In
/**
* Reference to a field of type 'Int[]'
*/
export type ListIntFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Int[]'>
/** /**
* Reference to a field of type 'DateTime' * Reference to a field of type 'DateTime'
*/ */
@@ -1125,6 +1510,13 @@ export type DateTimeFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel
/**
* Reference to a field of type 'DateTime[]'
*/
export type ListDateTimeFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'DateTime[]'>
/** /**
* Reference to a field of type 'CharacterClass' * Reference to a field of type 'CharacterClass'
*/ */
@@ -1132,6 +1524,13 @@ export type EnumCharacterClassFieldRefInput<$PrismaModel> = FieldRefInputType<$P
/**
* Reference to a field of type 'CharacterClass[]'
*/
export type ListEnumCharacterClassFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'CharacterClass[]'>
/** /**
* Reference to a field of type 'EventType' * Reference to a field of type 'EventType'
*/ */
@@ -1139,6 +1538,13 @@ export type EnumEventTypeFieldRefInput<$PrismaModel> = FieldRefInputType<$Prisma
/**
* Reference to a field of type 'EventType[]'
*/
export type ListEnumEventTypeFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'EventType[]'>
/** /**
* Reference to a field of type 'Boolean' * Reference to a field of type 'Boolean'
*/ */
@@ -1153,12 +1559,68 @@ export type EnumChallengeStatusFieldRefInput<$PrismaModel> = FieldRefInputType<$
/**
* Reference to a field of type 'ChallengeStatus[]'
*/
export type ListEnumChallengeStatusFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'ChallengeStatus[]'>
/**
* Reference to a field of type 'HouseRole'
*/
export type EnumHouseRoleFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'HouseRole'>
/**
* Reference to a field of type 'HouseRole[]'
*/
export type ListEnumHouseRoleFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'HouseRole[]'>
/**
* Reference to a field of type 'InvitationStatus'
*/
export type EnumInvitationStatusFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'InvitationStatus'>
/**
* Reference to a field of type 'InvitationStatus[]'
*/
export type ListEnumInvitationStatusFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'InvitationStatus[]'>
/**
* Reference to a field of type 'RequestStatus'
*/
export type EnumRequestStatusFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'RequestStatus'>
/**
* Reference to a field of type 'RequestStatus[]'
*/
export type ListEnumRequestStatusFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'RequestStatus[]'>
/** /**
* Reference to a field of type 'Float' * Reference to a field of type 'Float'
*/ */
export type FloatFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Float'> export type FloatFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Float'>
/**
* Reference to a field of type 'Float[]'
*/
export type ListFloatFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Float[]'>
/** /**
* Batch Payload for updateMany & deleteMany & createMany * Batch Payload for updateMany & deleteMany & createMany
*/ */
@@ -1261,6 +1723,10 @@ export type GlobalOmitConfig = {
eventFeedback?: Prisma.EventFeedbackOmit eventFeedback?: Prisma.EventFeedbackOmit
sitePreferences?: Prisma.SitePreferencesOmit sitePreferences?: Prisma.SitePreferencesOmit
challenge?: Prisma.ChallengeOmit challenge?: Prisma.ChallengeOmit
house?: Prisma.HouseOmit
houseMembership?: Prisma.HouseMembershipOmit
houseInvitation?: Prisma.HouseInvitationOmit
houseRequest?: Prisma.HouseRequestOmit
} }
/* Types for Logging */ /* Types for Logging */

View File

@@ -57,7 +57,11 @@ export const ModelName = {
EventRegistration: 'EventRegistration', EventRegistration: 'EventRegistration',
EventFeedback: 'EventFeedback', EventFeedback: 'EventFeedback',
SitePreferences: 'SitePreferences', SitePreferences: 'SitePreferences',
Challenge: 'Challenge' Challenge: 'Challenge',
House: 'House',
HouseMembership: 'HouseMembership',
HouseInvitation: 'HouseInvitation',
HouseRequest: 'HouseRequest'
} as const } as const
export type ModelName = (typeof ModelName)[keyof typeof ModelName] export type ModelName = (typeof ModelName)[keyof typeof ModelName]
@@ -67,6 +71,9 @@ export type ModelName = (typeof ModelName)[keyof typeof ModelName]
*/ */
export const TransactionIsolationLevel = { export const TransactionIsolationLevel = {
ReadUncommitted: 'ReadUncommitted',
ReadCommitted: 'ReadCommitted',
RepeatableRead: 'RepeatableRead',
Serializable: 'Serializable' Serializable: 'Serializable'
} as const } as const
@@ -155,8 +162,13 @@ export const SitePreferencesScalarFieldEnum = {
eventsBackground: 'eventsBackground', eventsBackground: 'eventsBackground',
leaderboardBackground: 'leaderboardBackground', leaderboardBackground: 'leaderboardBackground',
challengesBackground: 'challengesBackground', challengesBackground: 'challengesBackground',
profileBackground: 'profileBackground',
houseBackground: 'houseBackground',
eventRegistrationPoints: 'eventRegistrationPoints', eventRegistrationPoints: 'eventRegistrationPoints',
eventFeedbackPoints: 'eventFeedbackPoints', eventFeedbackPoints: 'eventFeedbackPoints',
houseJoinPoints: 'houseJoinPoints',
houseLeavePoints: 'houseLeavePoints',
houseCreatePoints: 'houseCreatePoints',
createdAt: 'createdAt', createdAt: 'createdAt',
updatedAt: 'updatedAt' updatedAt: 'updatedAt'
} as const } as const
@@ -184,6 +196,54 @@ export const ChallengeScalarFieldEnum = {
export type ChallengeScalarFieldEnum = (typeof ChallengeScalarFieldEnum)[keyof typeof ChallengeScalarFieldEnum] export type ChallengeScalarFieldEnum = (typeof ChallengeScalarFieldEnum)[keyof typeof ChallengeScalarFieldEnum]
export const HouseScalarFieldEnum = {
id: 'id',
name: 'name',
description: 'description',
creatorId: 'creatorId',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
} as const
export type HouseScalarFieldEnum = (typeof HouseScalarFieldEnum)[keyof typeof HouseScalarFieldEnum]
export const HouseMembershipScalarFieldEnum = {
id: 'id',
houseId: 'houseId',
userId: 'userId',
role: 'role',
joinedAt: 'joinedAt'
} as const
export type HouseMembershipScalarFieldEnum = (typeof HouseMembershipScalarFieldEnum)[keyof typeof HouseMembershipScalarFieldEnum]
export const HouseInvitationScalarFieldEnum = {
id: 'id',
houseId: 'houseId',
inviterId: 'inviterId',
inviteeId: 'inviteeId',
status: 'status',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
} as const
export type HouseInvitationScalarFieldEnum = (typeof HouseInvitationScalarFieldEnum)[keyof typeof HouseInvitationScalarFieldEnum]
export const HouseRequestScalarFieldEnum = {
id: 'id',
houseId: 'houseId',
requesterId: 'requesterId',
status: 'status',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
} as const
export type HouseRequestScalarFieldEnum = (typeof HouseRequestScalarFieldEnum)[keyof typeof HouseRequestScalarFieldEnum]
export const SortOrder = { export const SortOrder = {
asc: 'asc', asc: 'asc',
desc: 'desc' desc: 'desc'
@@ -192,6 +252,14 @@ export const SortOrder = {
export type SortOrder = (typeof SortOrder)[keyof typeof SortOrder] export type SortOrder = (typeof SortOrder)[keyof typeof SortOrder]
export const QueryMode = {
default: 'default',
insensitive: 'insensitive'
} as const
export type QueryMode = (typeof QueryMode)[keyof typeof QueryMode]
export const NullsOrder = { export const NullsOrder = {
first: 'first', first: 'first',
last: 'last' last: 'last'

View File

@@ -15,4 +15,8 @@ export type * from './models/EventRegistration'
export type * from './models/EventFeedback' export type * from './models/EventFeedback'
export type * from './models/SitePreferences' export type * from './models/SitePreferences'
export type * from './models/Challenge' export type * from './models/Challenge'
export type * from './models/House'
export type * from './models/HouseMembership'
export type * from './models/HouseInvitation'
export type * from './models/HouseRequest'
export type * from './commonInputTypes' export type * from './commonInputTypes'

View File

@@ -780,6 +780,7 @@ export type ChallengeCreateOrConnectWithoutChallengerInput = {
export type ChallengeCreateManyChallengerInputEnvelope = { export type ChallengeCreateManyChallengerInputEnvelope = {
data: Prisma.ChallengeCreateManyChallengerInput | Prisma.ChallengeCreateManyChallengerInput[] data: Prisma.ChallengeCreateManyChallengerInput | Prisma.ChallengeCreateManyChallengerInput[]
skipDuplicates?: boolean
} }
export type ChallengeCreateWithoutChallengedInput = { export type ChallengeCreateWithoutChallengedInput = {
@@ -821,6 +822,7 @@ export type ChallengeCreateOrConnectWithoutChallengedInput = {
export type ChallengeCreateManyChallengedInputEnvelope = { export type ChallengeCreateManyChallengedInputEnvelope = {
data: Prisma.ChallengeCreateManyChallengedInput | Prisma.ChallengeCreateManyChallengedInput[] data: Prisma.ChallengeCreateManyChallengedInput | Prisma.ChallengeCreateManyChallengedInput[]
skipDuplicates?: boolean
} }
export type ChallengeCreateWithoutAdminInput = { export type ChallengeCreateWithoutAdminInput = {
@@ -862,6 +864,7 @@ export type ChallengeCreateOrConnectWithoutAdminInput = {
export type ChallengeCreateManyAdminInputEnvelope = { export type ChallengeCreateManyAdminInputEnvelope = {
data: Prisma.ChallengeCreateManyAdminInput | Prisma.ChallengeCreateManyAdminInput[] data: Prisma.ChallengeCreateManyAdminInput | Prisma.ChallengeCreateManyAdminInput[]
skipDuplicates?: boolean
} }
export type ChallengeCreateWithoutWinnerInput = { export type ChallengeCreateWithoutWinnerInput = {
@@ -903,6 +906,7 @@ export type ChallengeCreateOrConnectWithoutWinnerInput = {
export type ChallengeCreateManyWinnerInputEnvelope = { export type ChallengeCreateManyWinnerInputEnvelope = {
data: Prisma.ChallengeCreateManyWinnerInput | Prisma.ChallengeCreateManyWinnerInput[] data: Prisma.ChallengeCreateManyWinnerInput | Prisma.ChallengeCreateManyWinnerInput[]
skipDuplicates?: boolean
} }
export type ChallengeUpsertWithWhereUniqueWithoutChallengerInput = { export type ChallengeUpsertWithWhereUniqueWithoutChallengerInput = {
@@ -2040,6 +2044,7 @@ export type ChallengeCreateManyArgs<ExtArgs extends runtime.Types.Extensions.Int
* The data used to create many Challenges. * The data used to create many Challenges.
*/ */
data: Prisma.ChallengeCreateManyInput | Prisma.ChallengeCreateManyInput[] data: Prisma.ChallengeCreateManyInput | Prisma.ChallengeCreateManyInput[]
skipDuplicates?: boolean
} }
/** /**
@@ -2058,6 +2063,7 @@ export type ChallengeCreateManyAndReturnArgs<ExtArgs extends runtime.Types.Exten
* The data used to create many Challenges. * The data used to create many Challenges.
*/ */
data: Prisma.ChallengeCreateManyInput | Prisma.ChallengeCreateManyInput[] data: Prisma.ChallengeCreateManyInput | Prisma.ChallengeCreateManyInput[]
skipDuplicates?: boolean
/** /**
* Choose, which related nodes to fetch as well * Choose, which related nodes to fetch as well
*/ */

View File

@@ -1447,6 +1447,7 @@ export type EventCreateManyArgs<ExtArgs extends runtime.Types.Extensions.Interna
* The data used to create many Events. * The data used to create many Events.
*/ */
data: Prisma.EventCreateManyInput | Prisma.EventCreateManyInput[] data: Prisma.EventCreateManyInput | Prisma.EventCreateManyInput[]
skipDuplicates?: boolean
} }
/** /**
@@ -1465,6 +1466,7 @@ export type EventCreateManyAndReturnArgs<ExtArgs extends runtime.Types.Extension
* The data used to create many Events. * The data used to create many Events.
*/ */
data: Prisma.EventCreateManyInput | Prisma.EventCreateManyInput[] data: Prisma.EventCreateManyInput | Prisma.EventCreateManyInput[]
skipDuplicates?: boolean
} }
/** /**

View File

@@ -550,6 +550,7 @@ export type EventFeedbackCreateOrConnectWithoutUserInput = {
export type EventFeedbackCreateManyUserInputEnvelope = { export type EventFeedbackCreateManyUserInputEnvelope = {
data: Prisma.EventFeedbackCreateManyUserInput | Prisma.EventFeedbackCreateManyUserInput[] data: Prisma.EventFeedbackCreateManyUserInput | Prisma.EventFeedbackCreateManyUserInput[]
skipDuplicates?: boolean
} }
export type EventFeedbackUpsertWithWhereUniqueWithoutUserInput = { export type EventFeedbackUpsertWithWhereUniqueWithoutUserInput = {
@@ -609,6 +610,7 @@ export type EventFeedbackCreateOrConnectWithoutEventInput = {
export type EventFeedbackCreateManyEventInputEnvelope = { export type EventFeedbackCreateManyEventInputEnvelope = {
data: Prisma.EventFeedbackCreateManyEventInput | Prisma.EventFeedbackCreateManyEventInput[] data: Prisma.EventFeedbackCreateManyEventInput | Prisma.EventFeedbackCreateManyEventInput[]
skipDuplicates?: boolean
} }
export type EventFeedbackUpsertWithWhereUniqueWithoutEventInput = { export type EventFeedbackUpsertWithWhereUniqueWithoutEventInput = {
@@ -1450,6 +1452,7 @@ export type EventFeedbackCreateManyArgs<ExtArgs extends runtime.Types.Extensions
* The data used to create many EventFeedbacks. * The data used to create many EventFeedbacks.
*/ */
data: Prisma.EventFeedbackCreateManyInput | Prisma.EventFeedbackCreateManyInput[] data: Prisma.EventFeedbackCreateManyInput | Prisma.EventFeedbackCreateManyInput[]
skipDuplicates?: boolean
} }
/** /**
@@ -1468,6 +1471,7 @@ export type EventFeedbackCreateManyAndReturnArgs<ExtArgs extends runtime.Types.E
* The data used to create many EventFeedbacks. * The data used to create many EventFeedbacks.
*/ */
data: Prisma.EventFeedbackCreateManyInput | Prisma.EventFeedbackCreateManyInput[] data: Prisma.EventFeedbackCreateManyInput | Prisma.EventFeedbackCreateManyInput[]
skipDuplicates?: boolean
/** /**
* Choose, which related nodes to fetch as well * Choose, which related nodes to fetch as well
*/ */

View File

@@ -406,6 +406,7 @@ export type EventRegistrationCreateOrConnectWithoutUserInput = {
export type EventRegistrationCreateManyUserInputEnvelope = { export type EventRegistrationCreateManyUserInputEnvelope = {
data: Prisma.EventRegistrationCreateManyUserInput | Prisma.EventRegistrationCreateManyUserInput[] data: Prisma.EventRegistrationCreateManyUserInput | Prisma.EventRegistrationCreateManyUserInput[]
skipDuplicates?: boolean
} }
export type EventRegistrationUpsertWithWhereUniqueWithoutUserInput = { export type EventRegistrationUpsertWithWhereUniqueWithoutUserInput = {
@@ -453,6 +454,7 @@ export type EventRegistrationCreateOrConnectWithoutEventInput = {
export type EventRegistrationCreateManyEventInputEnvelope = { export type EventRegistrationCreateManyEventInputEnvelope = {
data: Prisma.EventRegistrationCreateManyEventInput | Prisma.EventRegistrationCreateManyEventInput[] data: Prisma.EventRegistrationCreateManyEventInput | Prisma.EventRegistrationCreateManyEventInput[]
skipDuplicates?: boolean
} }
export type EventRegistrationUpsertWithWhereUniqueWithoutEventInput = { export type EventRegistrationUpsertWithWhereUniqueWithoutEventInput = {
@@ -1238,6 +1240,7 @@ export type EventRegistrationCreateManyArgs<ExtArgs extends runtime.Types.Extens
* The data used to create many EventRegistrations. * The data used to create many EventRegistrations.
*/ */
data: Prisma.EventRegistrationCreateManyInput | Prisma.EventRegistrationCreateManyInput[] data: Prisma.EventRegistrationCreateManyInput | Prisma.EventRegistrationCreateManyInput[]
skipDuplicates?: boolean
} }
/** /**
@@ -1256,6 +1259,7 @@ export type EventRegistrationCreateManyAndReturnArgs<ExtArgs extends runtime.Typ
* The data used to create many EventRegistrations. * The data used to create many EventRegistrations.
*/ */
data: Prisma.EventRegistrationCreateManyInput | Prisma.EventRegistrationCreateManyInput[] data: Prisma.EventRegistrationCreateManyInput | Prisma.EventRegistrationCreateManyInput[]
skipDuplicates?: boolean
/** /**
* Choose, which related nodes to fetch as well * Choose, which related nodes to fetch as well
*/ */

View File

@@ -29,11 +29,17 @@ export type AggregateSitePreferences = {
export type SitePreferencesAvgAggregateOutputType = { export type SitePreferencesAvgAggregateOutputType = {
eventRegistrationPoints: number | null eventRegistrationPoints: number | null
eventFeedbackPoints: number | null eventFeedbackPoints: number | null
houseJoinPoints: number | null
houseLeavePoints: number | null
houseCreatePoints: number | null
} }
export type SitePreferencesSumAggregateOutputType = { export type SitePreferencesSumAggregateOutputType = {
eventRegistrationPoints: number | null eventRegistrationPoints: number | null
eventFeedbackPoints: number | null eventFeedbackPoints: number | null
houseJoinPoints: number | null
houseLeavePoints: number | null
houseCreatePoints: number | null
} }
export type SitePreferencesMinAggregateOutputType = { export type SitePreferencesMinAggregateOutputType = {
@@ -42,8 +48,13 @@ export type SitePreferencesMinAggregateOutputType = {
eventsBackground: string | null eventsBackground: string | null
leaderboardBackground: string | null leaderboardBackground: string | null
challengesBackground: string | null challengesBackground: string | null
profileBackground: string | null
houseBackground: string | null
eventRegistrationPoints: number | null eventRegistrationPoints: number | null
eventFeedbackPoints: number | null eventFeedbackPoints: number | null
houseJoinPoints: number | null
houseLeavePoints: number | null
houseCreatePoints: number | null
createdAt: Date | null createdAt: Date | null
updatedAt: Date | null updatedAt: Date | null
} }
@@ -54,8 +65,13 @@ export type SitePreferencesMaxAggregateOutputType = {
eventsBackground: string | null eventsBackground: string | null
leaderboardBackground: string | null leaderboardBackground: string | null
challengesBackground: string | null challengesBackground: string | null
profileBackground: string | null
houseBackground: string | null
eventRegistrationPoints: number | null eventRegistrationPoints: number | null
eventFeedbackPoints: number | null eventFeedbackPoints: number | null
houseJoinPoints: number | null
houseLeavePoints: number | null
houseCreatePoints: number | null
createdAt: Date | null createdAt: Date | null
updatedAt: Date | null updatedAt: Date | null
} }
@@ -66,8 +82,13 @@ export type SitePreferencesCountAggregateOutputType = {
eventsBackground: number eventsBackground: number
leaderboardBackground: number leaderboardBackground: number
challengesBackground: number challengesBackground: number
profileBackground: number
houseBackground: number
eventRegistrationPoints: number eventRegistrationPoints: number
eventFeedbackPoints: number eventFeedbackPoints: number
houseJoinPoints: number
houseLeavePoints: number
houseCreatePoints: number
createdAt: number createdAt: number
updatedAt: number updatedAt: number
_all: number _all: number
@@ -77,11 +98,17 @@ export type SitePreferencesCountAggregateOutputType = {
export type SitePreferencesAvgAggregateInputType = { export type SitePreferencesAvgAggregateInputType = {
eventRegistrationPoints?: true eventRegistrationPoints?: true
eventFeedbackPoints?: true eventFeedbackPoints?: true
houseJoinPoints?: true
houseLeavePoints?: true
houseCreatePoints?: true
} }
export type SitePreferencesSumAggregateInputType = { export type SitePreferencesSumAggregateInputType = {
eventRegistrationPoints?: true eventRegistrationPoints?: true
eventFeedbackPoints?: true eventFeedbackPoints?: true
houseJoinPoints?: true
houseLeavePoints?: true
houseCreatePoints?: true
} }
export type SitePreferencesMinAggregateInputType = { export type SitePreferencesMinAggregateInputType = {
@@ -90,8 +117,13 @@ export type SitePreferencesMinAggregateInputType = {
eventsBackground?: true eventsBackground?: true
leaderboardBackground?: true leaderboardBackground?: true
challengesBackground?: true challengesBackground?: true
profileBackground?: true
houseBackground?: true
eventRegistrationPoints?: true eventRegistrationPoints?: true
eventFeedbackPoints?: true eventFeedbackPoints?: true
houseJoinPoints?: true
houseLeavePoints?: true
houseCreatePoints?: true
createdAt?: true createdAt?: true
updatedAt?: true updatedAt?: true
} }
@@ -102,8 +134,13 @@ export type SitePreferencesMaxAggregateInputType = {
eventsBackground?: true eventsBackground?: true
leaderboardBackground?: true leaderboardBackground?: true
challengesBackground?: true challengesBackground?: true
profileBackground?: true
houseBackground?: true
eventRegistrationPoints?: true eventRegistrationPoints?: true
eventFeedbackPoints?: true eventFeedbackPoints?: true
houseJoinPoints?: true
houseLeavePoints?: true
houseCreatePoints?: true
createdAt?: true createdAt?: true
updatedAt?: true updatedAt?: true
} }
@@ -114,8 +151,13 @@ export type SitePreferencesCountAggregateInputType = {
eventsBackground?: true eventsBackground?: true
leaderboardBackground?: true leaderboardBackground?: true
challengesBackground?: true challengesBackground?: true
profileBackground?: true
houseBackground?: true
eventRegistrationPoints?: true eventRegistrationPoints?: true
eventFeedbackPoints?: true eventFeedbackPoints?: true
houseJoinPoints?: true
houseLeavePoints?: true
houseCreatePoints?: true
createdAt?: true createdAt?: true
updatedAt?: true updatedAt?: true
_all?: true _all?: true
@@ -213,8 +255,13 @@ export type SitePreferencesGroupByOutputType = {
eventsBackground: string | null eventsBackground: string | null
leaderboardBackground: string | null leaderboardBackground: string | null
challengesBackground: string | null challengesBackground: string | null
profileBackground: string | null
houseBackground: string | null
eventRegistrationPoints: number eventRegistrationPoints: number
eventFeedbackPoints: number eventFeedbackPoints: number
houseJoinPoints: number
houseLeavePoints: number
houseCreatePoints: number
createdAt: Date createdAt: Date
updatedAt: Date updatedAt: Date
_count: SitePreferencesCountAggregateOutputType | null _count: SitePreferencesCountAggregateOutputType | null
@@ -248,8 +295,13 @@ export type SitePreferencesWhereInput = {
eventsBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null eventsBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null
leaderboardBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null leaderboardBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null
challengesBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null challengesBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null
profileBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null
houseBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null
eventRegistrationPoints?: Prisma.IntFilter<"SitePreferences"> | number eventRegistrationPoints?: Prisma.IntFilter<"SitePreferences"> | number
eventFeedbackPoints?: Prisma.IntFilter<"SitePreferences"> | number eventFeedbackPoints?: Prisma.IntFilter<"SitePreferences"> | number
houseJoinPoints?: Prisma.IntFilter<"SitePreferences"> | number
houseLeavePoints?: Prisma.IntFilter<"SitePreferences"> | number
houseCreatePoints?: Prisma.IntFilter<"SitePreferences"> | number
createdAt?: Prisma.DateTimeFilter<"SitePreferences"> | Date | string createdAt?: Prisma.DateTimeFilter<"SitePreferences"> | Date | string
updatedAt?: Prisma.DateTimeFilter<"SitePreferences"> | Date | string updatedAt?: Prisma.DateTimeFilter<"SitePreferences"> | Date | string
} }
@@ -260,8 +312,13 @@ export type SitePreferencesOrderByWithRelationInput = {
eventsBackground?: Prisma.SortOrderInput | Prisma.SortOrder eventsBackground?: Prisma.SortOrderInput | Prisma.SortOrder
leaderboardBackground?: Prisma.SortOrderInput | Prisma.SortOrder leaderboardBackground?: Prisma.SortOrderInput | Prisma.SortOrder
challengesBackground?: Prisma.SortOrderInput | Prisma.SortOrder challengesBackground?: Prisma.SortOrderInput | Prisma.SortOrder
profileBackground?: Prisma.SortOrderInput | Prisma.SortOrder
houseBackground?: Prisma.SortOrderInput | Prisma.SortOrder
eventRegistrationPoints?: Prisma.SortOrder eventRegistrationPoints?: Prisma.SortOrder
eventFeedbackPoints?: Prisma.SortOrder eventFeedbackPoints?: Prisma.SortOrder
houseJoinPoints?: Prisma.SortOrder
houseLeavePoints?: Prisma.SortOrder
houseCreatePoints?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder
} }
@@ -275,8 +332,13 @@ export type SitePreferencesWhereUniqueInput = Prisma.AtLeast<{
eventsBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null eventsBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null
leaderboardBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null leaderboardBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null
challengesBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null challengesBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null
profileBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null
houseBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null
eventRegistrationPoints?: Prisma.IntFilter<"SitePreferences"> | number eventRegistrationPoints?: Prisma.IntFilter<"SitePreferences"> | number
eventFeedbackPoints?: Prisma.IntFilter<"SitePreferences"> | number eventFeedbackPoints?: Prisma.IntFilter<"SitePreferences"> | number
houseJoinPoints?: Prisma.IntFilter<"SitePreferences"> | number
houseLeavePoints?: Prisma.IntFilter<"SitePreferences"> | number
houseCreatePoints?: Prisma.IntFilter<"SitePreferences"> | number
createdAt?: Prisma.DateTimeFilter<"SitePreferences"> | Date | string createdAt?: Prisma.DateTimeFilter<"SitePreferences"> | Date | string
updatedAt?: Prisma.DateTimeFilter<"SitePreferences"> | Date | string updatedAt?: Prisma.DateTimeFilter<"SitePreferences"> | Date | string
}, "id"> }, "id">
@@ -287,8 +349,13 @@ export type SitePreferencesOrderByWithAggregationInput = {
eventsBackground?: Prisma.SortOrderInput | Prisma.SortOrder eventsBackground?: Prisma.SortOrderInput | Prisma.SortOrder
leaderboardBackground?: Prisma.SortOrderInput | Prisma.SortOrder leaderboardBackground?: Prisma.SortOrderInput | Prisma.SortOrder
challengesBackground?: Prisma.SortOrderInput | Prisma.SortOrder challengesBackground?: Prisma.SortOrderInput | Prisma.SortOrder
profileBackground?: Prisma.SortOrderInput | Prisma.SortOrder
houseBackground?: Prisma.SortOrderInput | Prisma.SortOrder
eventRegistrationPoints?: Prisma.SortOrder eventRegistrationPoints?: Prisma.SortOrder
eventFeedbackPoints?: Prisma.SortOrder eventFeedbackPoints?: Prisma.SortOrder
houseJoinPoints?: Prisma.SortOrder
houseLeavePoints?: Prisma.SortOrder
houseCreatePoints?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder
_count?: Prisma.SitePreferencesCountOrderByAggregateInput _count?: Prisma.SitePreferencesCountOrderByAggregateInput
@@ -307,8 +374,13 @@ export type SitePreferencesScalarWhereWithAggregatesInput = {
eventsBackground?: Prisma.StringNullableWithAggregatesFilter<"SitePreferences"> | string | null eventsBackground?: Prisma.StringNullableWithAggregatesFilter<"SitePreferences"> | string | null
leaderboardBackground?: Prisma.StringNullableWithAggregatesFilter<"SitePreferences"> | string | null leaderboardBackground?: Prisma.StringNullableWithAggregatesFilter<"SitePreferences"> | string | null
challengesBackground?: Prisma.StringNullableWithAggregatesFilter<"SitePreferences"> | string | null challengesBackground?: Prisma.StringNullableWithAggregatesFilter<"SitePreferences"> | string | null
profileBackground?: Prisma.StringNullableWithAggregatesFilter<"SitePreferences"> | string | null
houseBackground?: Prisma.StringNullableWithAggregatesFilter<"SitePreferences"> | string | null
eventRegistrationPoints?: Prisma.IntWithAggregatesFilter<"SitePreferences"> | number eventRegistrationPoints?: Prisma.IntWithAggregatesFilter<"SitePreferences"> | number
eventFeedbackPoints?: Prisma.IntWithAggregatesFilter<"SitePreferences"> | number eventFeedbackPoints?: Prisma.IntWithAggregatesFilter<"SitePreferences"> | number
houseJoinPoints?: Prisma.IntWithAggregatesFilter<"SitePreferences"> | number
houseLeavePoints?: Prisma.IntWithAggregatesFilter<"SitePreferences"> | number
houseCreatePoints?: Prisma.IntWithAggregatesFilter<"SitePreferences"> | number
createdAt?: Prisma.DateTimeWithAggregatesFilter<"SitePreferences"> | Date | string createdAt?: Prisma.DateTimeWithAggregatesFilter<"SitePreferences"> | Date | string
updatedAt?: Prisma.DateTimeWithAggregatesFilter<"SitePreferences"> | Date | string updatedAt?: Prisma.DateTimeWithAggregatesFilter<"SitePreferences"> | Date | string
} }
@@ -319,8 +391,13 @@ export type SitePreferencesCreateInput = {
eventsBackground?: string | null eventsBackground?: string | null
leaderboardBackground?: string | null leaderboardBackground?: string | null
challengesBackground?: string | null challengesBackground?: string | null
profileBackground?: string | null
houseBackground?: string | null
eventRegistrationPoints?: number eventRegistrationPoints?: number
eventFeedbackPoints?: number eventFeedbackPoints?: number
houseJoinPoints?: number
houseLeavePoints?: number
houseCreatePoints?: number
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
} }
@@ -331,8 +408,13 @@ export type SitePreferencesUncheckedCreateInput = {
eventsBackground?: string | null eventsBackground?: string | null
leaderboardBackground?: string | null leaderboardBackground?: string | null
challengesBackground?: string | null challengesBackground?: string | null
profileBackground?: string | null
houseBackground?: string | null
eventRegistrationPoints?: number eventRegistrationPoints?: number
eventFeedbackPoints?: number eventFeedbackPoints?: number
houseJoinPoints?: number
houseLeavePoints?: number
houseCreatePoints?: number
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
} }
@@ -343,8 +425,13 @@ export type SitePreferencesUpdateInput = {
eventsBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null eventsBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
leaderboardBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null leaderboardBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
challengesBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null challengesBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
profileBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
houseBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
eventRegistrationPoints?: Prisma.IntFieldUpdateOperationsInput | number eventRegistrationPoints?: Prisma.IntFieldUpdateOperationsInput | number
eventFeedbackPoints?: Prisma.IntFieldUpdateOperationsInput | number eventFeedbackPoints?: Prisma.IntFieldUpdateOperationsInput | number
houseJoinPoints?: Prisma.IntFieldUpdateOperationsInput | number
houseLeavePoints?: Prisma.IntFieldUpdateOperationsInput | number
houseCreatePoints?: Prisma.IntFieldUpdateOperationsInput | number
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
} }
@@ -355,8 +442,13 @@ export type SitePreferencesUncheckedUpdateInput = {
eventsBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null eventsBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
leaderboardBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null leaderboardBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
challengesBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null challengesBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
profileBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
houseBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
eventRegistrationPoints?: Prisma.IntFieldUpdateOperationsInput | number eventRegistrationPoints?: Prisma.IntFieldUpdateOperationsInput | number
eventFeedbackPoints?: Prisma.IntFieldUpdateOperationsInput | number eventFeedbackPoints?: Prisma.IntFieldUpdateOperationsInput | number
houseJoinPoints?: Prisma.IntFieldUpdateOperationsInput | number
houseLeavePoints?: Prisma.IntFieldUpdateOperationsInput | number
houseCreatePoints?: Prisma.IntFieldUpdateOperationsInput | number
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
} }
@@ -367,8 +459,13 @@ export type SitePreferencesCreateManyInput = {
eventsBackground?: string | null eventsBackground?: string | null
leaderboardBackground?: string | null leaderboardBackground?: string | null
challengesBackground?: string | null challengesBackground?: string | null
profileBackground?: string | null
houseBackground?: string | null
eventRegistrationPoints?: number eventRegistrationPoints?: number
eventFeedbackPoints?: number eventFeedbackPoints?: number
houseJoinPoints?: number
houseLeavePoints?: number
houseCreatePoints?: number
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
} }
@@ -379,8 +476,13 @@ export type SitePreferencesUpdateManyMutationInput = {
eventsBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null eventsBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
leaderboardBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null leaderboardBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
challengesBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null challengesBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
profileBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
houseBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
eventRegistrationPoints?: Prisma.IntFieldUpdateOperationsInput | number eventRegistrationPoints?: Prisma.IntFieldUpdateOperationsInput | number
eventFeedbackPoints?: Prisma.IntFieldUpdateOperationsInput | number eventFeedbackPoints?: Prisma.IntFieldUpdateOperationsInput | number
houseJoinPoints?: Prisma.IntFieldUpdateOperationsInput | number
houseLeavePoints?: Prisma.IntFieldUpdateOperationsInput | number
houseCreatePoints?: Prisma.IntFieldUpdateOperationsInput | number
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
} }
@@ -391,8 +493,13 @@ export type SitePreferencesUncheckedUpdateManyInput = {
eventsBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null eventsBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
leaderboardBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null leaderboardBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
challengesBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null challengesBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
profileBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
houseBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
eventRegistrationPoints?: Prisma.IntFieldUpdateOperationsInput | number eventRegistrationPoints?: Prisma.IntFieldUpdateOperationsInput | number
eventFeedbackPoints?: Prisma.IntFieldUpdateOperationsInput | number eventFeedbackPoints?: Prisma.IntFieldUpdateOperationsInput | number
houseJoinPoints?: Prisma.IntFieldUpdateOperationsInput | number
houseLeavePoints?: Prisma.IntFieldUpdateOperationsInput | number
houseCreatePoints?: Prisma.IntFieldUpdateOperationsInput | number
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
} }
@@ -403,8 +510,13 @@ export type SitePreferencesCountOrderByAggregateInput = {
eventsBackground?: Prisma.SortOrder eventsBackground?: Prisma.SortOrder
leaderboardBackground?: Prisma.SortOrder leaderboardBackground?: Prisma.SortOrder
challengesBackground?: Prisma.SortOrder challengesBackground?: Prisma.SortOrder
profileBackground?: Prisma.SortOrder
houseBackground?: Prisma.SortOrder
eventRegistrationPoints?: Prisma.SortOrder eventRegistrationPoints?: Prisma.SortOrder
eventFeedbackPoints?: Prisma.SortOrder eventFeedbackPoints?: Prisma.SortOrder
houseJoinPoints?: Prisma.SortOrder
houseLeavePoints?: Prisma.SortOrder
houseCreatePoints?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder
} }
@@ -412,6 +524,9 @@ export type SitePreferencesCountOrderByAggregateInput = {
export type SitePreferencesAvgOrderByAggregateInput = { export type SitePreferencesAvgOrderByAggregateInput = {
eventRegistrationPoints?: Prisma.SortOrder eventRegistrationPoints?: Prisma.SortOrder
eventFeedbackPoints?: Prisma.SortOrder eventFeedbackPoints?: Prisma.SortOrder
houseJoinPoints?: Prisma.SortOrder
houseLeavePoints?: Prisma.SortOrder
houseCreatePoints?: Prisma.SortOrder
} }
export type SitePreferencesMaxOrderByAggregateInput = { export type SitePreferencesMaxOrderByAggregateInput = {
@@ -420,8 +535,13 @@ export type SitePreferencesMaxOrderByAggregateInput = {
eventsBackground?: Prisma.SortOrder eventsBackground?: Prisma.SortOrder
leaderboardBackground?: Prisma.SortOrder leaderboardBackground?: Prisma.SortOrder
challengesBackground?: Prisma.SortOrder challengesBackground?: Prisma.SortOrder
profileBackground?: Prisma.SortOrder
houseBackground?: Prisma.SortOrder
eventRegistrationPoints?: Prisma.SortOrder eventRegistrationPoints?: Prisma.SortOrder
eventFeedbackPoints?: Prisma.SortOrder eventFeedbackPoints?: Prisma.SortOrder
houseJoinPoints?: Prisma.SortOrder
houseLeavePoints?: Prisma.SortOrder
houseCreatePoints?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder
} }
@@ -432,8 +552,13 @@ export type SitePreferencesMinOrderByAggregateInput = {
eventsBackground?: Prisma.SortOrder eventsBackground?: Prisma.SortOrder
leaderboardBackground?: Prisma.SortOrder leaderboardBackground?: Prisma.SortOrder
challengesBackground?: Prisma.SortOrder challengesBackground?: Prisma.SortOrder
profileBackground?: Prisma.SortOrder
houseBackground?: Prisma.SortOrder
eventRegistrationPoints?: Prisma.SortOrder eventRegistrationPoints?: Prisma.SortOrder
eventFeedbackPoints?: Prisma.SortOrder eventFeedbackPoints?: Prisma.SortOrder
houseJoinPoints?: Prisma.SortOrder
houseLeavePoints?: Prisma.SortOrder
houseCreatePoints?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder
} }
@@ -441,6 +566,9 @@ export type SitePreferencesMinOrderByAggregateInput = {
export type SitePreferencesSumOrderByAggregateInput = { export type SitePreferencesSumOrderByAggregateInput = {
eventRegistrationPoints?: Prisma.SortOrder eventRegistrationPoints?: Prisma.SortOrder
eventFeedbackPoints?: Prisma.SortOrder eventFeedbackPoints?: Prisma.SortOrder
houseJoinPoints?: Prisma.SortOrder
houseLeavePoints?: Prisma.SortOrder
houseCreatePoints?: Prisma.SortOrder
} }
@@ -451,8 +579,13 @@ export type SitePreferencesSelect<ExtArgs extends runtime.Types.Extensions.Inter
eventsBackground?: boolean eventsBackground?: boolean
leaderboardBackground?: boolean leaderboardBackground?: boolean
challengesBackground?: boolean challengesBackground?: boolean
profileBackground?: boolean
houseBackground?: boolean
eventRegistrationPoints?: boolean eventRegistrationPoints?: boolean
eventFeedbackPoints?: boolean eventFeedbackPoints?: boolean
houseJoinPoints?: boolean
houseLeavePoints?: boolean
houseCreatePoints?: boolean
createdAt?: boolean createdAt?: boolean
updatedAt?: boolean updatedAt?: boolean
}, ExtArgs["result"]["sitePreferences"]> }, ExtArgs["result"]["sitePreferences"]>
@@ -463,8 +596,13 @@ export type SitePreferencesSelectCreateManyAndReturn<ExtArgs extends runtime.Typ
eventsBackground?: boolean eventsBackground?: boolean
leaderboardBackground?: boolean leaderboardBackground?: boolean
challengesBackground?: boolean challengesBackground?: boolean
profileBackground?: boolean
houseBackground?: boolean
eventRegistrationPoints?: boolean eventRegistrationPoints?: boolean
eventFeedbackPoints?: boolean eventFeedbackPoints?: boolean
houseJoinPoints?: boolean
houseLeavePoints?: boolean
houseCreatePoints?: boolean
createdAt?: boolean createdAt?: boolean
updatedAt?: boolean updatedAt?: boolean
}, ExtArgs["result"]["sitePreferences"]> }, ExtArgs["result"]["sitePreferences"]>
@@ -475,8 +613,13 @@ export type SitePreferencesSelectUpdateManyAndReturn<ExtArgs extends runtime.Typ
eventsBackground?: boolean eventsBackground?: boolean
leaderboardBackground?: boolean leaderboardBackground?: boolean
challengesBackground?: boolean challengesBackground?: boolean
profileBackground?: boolean
houseBackground?: boolean
eventRegistrationPoints?: boolean eventRegistrationPoints?: boolean
eventFeedbackPoints?: boolean eventFeedbackPoints?: boolean
houseJoinPoints?: boolean
houseLeavePoints?: boolean
houseCreatePoints?: boolean
createdAt?: boolean createdAt?: boolean
updatedAt?: boolean updatedAt?: boolean
}, ExtArgs["result"]["sitePreferences"]> }, ExtArgs["result"]["sitePreferences"]>
@@ -487,13 +630,18 @@ export type SitePreferencesSelectScalar = {
eventsBackground?: boolean eventsBackground?: boolean
leaderboardBackground?: boolean leaderboardBackground?: boolean
challengesBackground?: boolean challengesBackground?: boolean
profileBackground?: boolean
houseBackground?: boolean
eventRegistrationPoints?: boolean eventRegistrationPoints?: boolean
eventFeedbackPoints?: boolean eventFeedbackPoints?: boolean
houseJoinPoints?: boolean
houseLeavePoints?: boolean
houseCreatePoints?: boolean
createdAt?: boolean createdAt?: boolean
updatedAt?: boolean updatedAt?: boolean
} }
export type SitePreferencesOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "homeBackground" | "eventsBackground" | "leaderboardBackground" | "challengesBackground" | "eventRegistrationPoints" | "eventFeedbackPoints" | "createdAt" | "updatedAt", ExtArgs["result"]["sitePreferences"]> export type SitePreferencesOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "homeBackground" | "eventsBackground" | "leaderboardBackground" | "challengesBackground" | "profileBackground" | "houseBackground" | "eventRegistrationPoints" | "eventFeedbackPoints" | "houseJoinPoints" | "houseLeavePoints" | "houseCreatePoints" | "createdAt" | "updatedAt", ExtArgs["result"]["sitePreferences"]>
export type $SitePreferencesPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = { export type $SitePreferencesPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
name: "SitePreferences" name: "SitePreferences"
@@ -504,8 +652,13 @@ export type $SitePreferencesPayload<ExtArgs extends runtime.Types.Extensions.Int
eventsBackground: string | null eventsBackground: string | null
leaderboardBackground: string | null leaderboardBackground: string | null
challengesBackground: string | null challengesBackground: string | null
profileBackground: string | null
houseBackground: string | null
eventRegistrationPoints: number eventRegistrationPoints: number
eventFeedbackPoints: number eventFeedbackPoints: number
houseJoinPoints: number
houseLeavePoints: number
houseCreatePoints: number
createdAt: Date createdAt: Date
updatedAt: Date updatedAt: Date
}, ExtArgs["result"]["sitePreferences"]> }, ExtArgs["result"]["sitePreferences"]>
@@ -936,8 +1089,13 @@ export interface SitePreferencesFieldRefs {
readonly eventsBackground: Prisma.FieldRef<"SitePreferences", 'String'> readonly eventsBackground: Prisma.FieldRef<"SitePreferences", 'String'>
readonly leaderboardBackground: Prisma.FieldRef<"SitePreferences", 'String'> readonly leaderboardBackground: Prisma.FieldRef<"SitePreferences", 'String'>
readonly challengesBackground: Prisma.FieldRef<"SitePreferences", 'String'> readonly challengesBackground: Prisma.FieldRef<"SitePreferences", 'String'>
readonly profileBackground: Prisma.FieldRef<"SitePreferences", 'String'>
readonly houseBackground: Prisma.FieldRef<"SitePreferences", 'String'>
readonly eventRegistrationPoints: Prisma.FieldRef<"SitePreferences", 'Int'> readonly eventRegistrationPoints: Prisma.FieldRef<"SitePreferences", 'Int'>
readonly eventFeedbackPoints: Prisma.FieldRef<"SitePreferences", 'Int'> readonly eventFeedbackPoints: Prisma.FieldRef<"SitePreferences", 'Int'>
readonly houseJoinPoints: Prisma.FieldRef<"SitePreferences", 'Int'>
readonly houseLeavePoints: Prisma.FieldRef<"SitePreferences", 'Int'>
readonly houseCreatePoints: Prisma.FieldRef<"SitePreferences", 'Int'>
readonly createdAt: Prisma.FieldRef<"SitePreferences", 'DateTime'> readonly createdAt: Prisma.FieldRef<"SitePreferences", 'DateTime'>
readonly updatedAt: Prisma.FieldRef<"SitePreferences", 'DateTime'> readonly updatedAt: Prisma.FieldRef<"SitePreferences", 'DateTime'>
} }
@@ -1145,6 +1303,7 @@ export type SitePreferencesCreateManyArgs<ExtArgs extends runtime.Types.Extensio
* The data used to create many SitePreferences. * The data used to create many SitePreferences.
*/ */
data: Prisma.SitePreferencesCreateManyInput | Prisma.SitePreferencesCreateManyInput[] data: Prisma.SitePreferencesCreateManyInput | Prisma.SitePreferencesCreateManyInput[]
skipDuplicates?: boolean
} }
/** /**
@@ -1163,6 +1322,7 @@ export type SitePreferencesCreateManyAndReturnArgs<ExtArgs extends runtime.Types
* The data used to create many SitePreferences. * The data used to create many SitePreferences.
*/ */
data: Prisma.SitePreferencesCreateManyInput | Prisma.SitePreferencesCreateManyInput[] data: Prisma.SitePreferencesCreateManyInput | Prisma.SitePreferencesCreateManyInput[]
skipDuplicates?: boolean
} }
/** /**

File diff suppressed because it is too large Load Diff

View File

@@ -1201,6 +1201,7 @@ export type UserPreferencesCreateManyArgs<ExtArgs extends runtime.Types.Extensio
* The data used to create many UserPreferences. * The data used to create many UserPreferences.
*/ */
data: Prisma.UserPreferencesCreateManyInput | Prisma.UserPreferencesCreateManyInput[] data: Prisma.UserPreferencesCreateManyInput | Prisma.UserPreferencesCreateManyInput[]
skipDuplicates?: boolean
} }
/** /**
@@ -1219,6 +1220,7 @@ export type UserPreferencesCreateManyAndReturnArgs<ExtArgs extends runtime.Types
* The data used to create many UserPreferences. * The data used to create many UserPreferences.
*/ */
data: Prisma.UserPreferencesCreateManyInput | Prisma.UserPreferencesCreateManyInput[] data: Prisma.UserPreferencesCreateManyInput | Prisma.UserPreferencesCreateManyInput[]
skipDuplicates?: boolean
/** /**
* Choose, which related nodes to fetch as well * Choose, which related nodes to fetch as well
*/ */

Some files were not shown because too many files have changed in this diff Show More