Add event registration and feedback points to site preferences: Update SitePreferences model and related components to include eventRegistrationPoints and eventFeedbackPoints, ensuring proper handling of user scores during event interactions.

This commit is contained in:
Julien Froidefond
2025-12-16 16:38:01 +01:00
parent f45cc1839e
commit 3dd82c2bd4
20 changed files with 652 additions and 19 deletions

View File

@@ -20,6 +20,8 @@ export async function updateSitePreferences(data: {
eventsBackground?: string | null;
leaderboardBackground?: string | null;
challengesBackground?: string | null;
eventRegistrationPoints?: number;
eventFeedbackPoints?: number;
}) {
try {
await checkAdminAccess()();
@@ -29,6 +31,8 @@ export async function updateSitePreferences(data: {
eventsBackground: data.eventsBackground,
leaderboardBackground: data.leaderboardBackground,
challengesBackground: data.challengesBackground,
eventRegistrationPoints: data.eventRegistrationPoints,
eventFeedbackPoints: data.eventFeedbackPoints,
});
revalidatePath("/admin");

View File

@@ -128,6 +128,9 @@ export default function FeedbackPageClient({
});
}
// Rafraîchir le score dans le header
window.dispatchEvent(new Event("refreshUserScore"));
// Rediriger après 2 secondes
setTimeout(() => {
router.push("/events");

View File

@@ -6,6 +6,8 @@ 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 {
@@ -14,6 +16,8 @@ interface SitePreferences {
eventsBackground: string | null;
leaderboardBackground: string | null;
challengesBackground: string | null;
eventRegistrationPoints: number;
eventFeedbackPoints: number;
}
interface AdminPanelProps {
@@ -93,6 +97,8 @@ export default function AdminPanel({ initialPreferences }: AdminPanelProps) {
</div>
<div className="space-y-4">
<BackgroundPreferences initialPreferences={initialPreferences} />
<EventPointsPreferences initialPreferences={initialPreferences} />
<EventFeedbackPointsPreferences initialPreferences={initialPreferences} />
</div>
</Card>
)}

View File

@@ -11,6 +11,7 @@ interface SitePreferences {
eventsBackground: string | null;
leaderboardBackground: string | null;
challengesBackground: string | null;
eventRegistrationPoints?: number;
}
interface BackgroundPreferencesProps {

View File

@@ -0,0 +1,166 @@
"use client";
import { useState, useEffect } from "react";
import { updateSitePreferences } from "@/actions/admin/preferences";
import { Button, Card, Input } from "@/components/ui";
interface SitePreferences {
id: string;
eventFeedbackPoints: number;
}
interface EventFeedbackPointsPreferencesProps {
initialPreferences: SitePreferences;
}
export default function EventFeedbackPointsPreferences({
initialPreferences,
}: EventFeedbackPointsPreferencesProps) {
const [preferences, setPreferences] = useState<SitePreferences | null>(
initialPreferences
);
const [isEditing, setIsEditing] = useState(false);
const [formData, setFormData] = useState({
eventFeedbackPoints: initialPreferences.eventFeedbackPoints.toString(),
});
const [isSaving, setIsSaving] = useState(false);
// Synchroniser les préférences quand initialPreferences change
useEffect(() => {
setPreferences(initialPreferences);
setFormData({
eventFeedbackPoints: initialPreferences.eventFeedbackPoints.toString(),
});
}, [initialPreferences]);
const handleEdit = () => {
setIsEditing(true);
};
const handleSave = async () => {
const points = parseInt(formData.eventFeedbackPoints, 10);
if (isNaN(points) || points < 0) {
alert("Le nombre de points doit être un nombre positif");
return;
}
setIsSaving(true);
try {
const result = await updateSitePreferences({
eventFeedbackPoints: points,
});
if (result.success && result.data) {
setPreferences(result.data);
setFormData({
eventFeedbackPoints: result.data.eventFeedbackPoints.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({
eventFeedbackPoints: preferences.eventFeedbackPoints.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 de feedback sur les événements
</h3>
<p className="text-gray-400 text-xs sm:text-sm">
Nombre de points attribués lorsqu&apos;un utilisateur donne un feedback à un événement (première fois uniquement)
</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="eventFeedbackPoints"
className="block text-sm font-medium text-pixel-gold mb-2"
>
Points de feedback
</label>
<Input
id="eventFeedbackPoints"
type="number"
min="0"
value={formData.eventFeedbackPoints}
onChange={(e) =>
setFormData({
...formData,
eventFeedbackPoints: e.target.value,
})
}
placeholder="100"
className="w-full"
/>
<p className="text-xs text-gray-400 mt-1">
Les utilisateurs gagneront ce nombre de points lors de leur premier feedback sur un événement
</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="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 actuels:
</span>
<div className="flex items-center gap-2">
<span className="text-lg sm:text-xl font-bold text-white">
{preferences?.eventFeedbackPoints ?? 100}
</span>
<span className="text-xs sm:text-sm text-gray-400">points</span>
</div>
</div>
)}
</Card>
);
}

View File

@@ -0,0 +1,166 @@
"use client";
import { useState, useEffect } from "react";
import { updateSitePreferences } from "@/actions/admin/preferences";
import { Button, Card, Input } from "@/components/ui";
interface SitePreferences {
id: string;
eventRegistrationPoints: number;
}
interface EventPointsPreferencesProps {
initialPreferences: SitePreferences;
}
export default function EventPointsPreferences({
initialPreferences,
}: EventPointsPreferencesProps) {
const [preferences, setPreferences] = useState<SitePreferences | null>(
initialPreferences
);
const [isEditing, setIsEditing] = useState(false);
const [formData, setFormData] = useState({
eventRegistrationPoints: initialPreferences.eventRegistrationPoints.toString(),
});
const [isSaving, setIsSaving] = useState(false);
// Synchroniser les préférences quand initialPreferences change
useEffect(() => {
setPreferences(initialPreferences);
setFormData({
eventRegistrationPoints: initialPreferences.eventRegistrationPoints.toString(),
});
}, [initialPreferences]);
const handleEdit = () => {
setIsEditing(true);
};
const handleSave = async () => {
const points = parseInt(formData.eventRegistrationPoints, 10);
if (isNaN(points) || points < 0) {
alert("Le nombre de points doit être un nombre positif");
return;
}
setIsSaving(true);
try {
const result = await updateSitePreferences({
eventRegistrationPoints: points,
});
if (result.success && result.data) {
setPreferences(result.data);
setFormData({
eventRegistrationPoints: result.data.eventRegistrationPoints.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({
eventRegistrationPoints: preferences.eventRegistrationPoints.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 d&apos;inscription aux événements
</h3>
<p className="text-gray-400 text-xs sm:text-sm">
Nombre de points attribués lorsqu&apos;un utilisateur s&apos;inscrit à un événement
</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="eventRegistrationPoints"
className="block text-sm font-medium text-pixel-gold mb-2"
>
Points d&apos;inscription
</label>
<Input
id="eventRegistrationPoints"
type="number"
min="0"
value={formData.eventRegistrationPoints}
onChange={(e) =>
setFormData({
...formData,
eventRegistrationPoints: e.target.value,
})
}
placeholder="100"
className="w-full"
/>
<p className="text-xs text-gray-400 mt-1">
Les utilisateurs gagneront ce nombre de points lors de leur inscription à un événement
</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="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 actuels:
</span>
<div className="flex items-center gap-2">
<span className="text-lg sm:text-xl font-bold text-white">
{preferences?.eventRegistrationPoints ?? 100}
</span>
<span className="text-xs sm:text-sm text-gray-400">points</span>
</div>
</div>
)}
</Card>
);
}

View File

@@ -564,6 +564,8 @@ export default function EventsPageSection({
...prev,
[eventId]: true,
}));
// Rafraîchir le score dans le header
window.dispatchEvent(new Event("refreshUserScore"));
} else {
setError(result.error || "Une erreur est survenue");
}
@@ -583,6 +585,8 @@ export default function EventsPageSection({
...prev,
[eventId]: false,
}));
// Rafraîchir le score dans le header
window.dispatchEvent(new Event("refreshUserScore"));
} else {
setError(result.error || "Une erreur est survenue");
}

View File

@@ -155,6 +155,9 @@ export default function FeedbackModal({
});
}
// Rafraîchir le score dans le header
window.dispatchEvent(new Event("refreshUserScore"));
// Fermer la modale après 1.5 secondes
setTimeout(() => {
onClose();

View File

@@ -1,6 +1,6 @@
"use client";
import { useEffect, useState } from "react";
import { useEffect, useState, useCallback } from "react";
import { useSession } from "next-auth/react";
import Link from "next/link";
import { Avatar } from "@/components/ui";
@@ -42,6 +42,31 @@ export default function PlayerStats({ initialUserData }: PlayerStatsProps) {
initialUserData || defaultUserData
);
const refreshUserData = useCallback(async () => {
if (!session?.user?.id) return;
try {
const res = await fetch(`/api/users/${session.user.id}`);
const data = await res.json();
if (data) {
requestAnimationFrame(() => {
setUserData({
username: data.username || "Guest",
avatar: data.avatar,
hp: data.hp || 1000,
maxHp: data.maxHp || 1000,
xp: data.xp || 0,
maxXp: data.maxXp || 5000,
level: data.level || 1,
score: data.score || 0,
});
});
}
} catch (error) {
console.error("Error refreshing user data:", error);
}
}, [session]);
useEffect(() => {
// Si on a déjà des données initiales, ne rien faire (déjà initialisé dans useState)
if (initialUserData) {
@@ -92,6 +117,18 @@ export default function PlayerStats({ initialUserData }: PlayerStatsProps) {
}
}, [session, initialUserData]);
// Écouter les événements de refresh du score
useEffect(() => {
const handleRefreshScore = () => {
refreshUserData();
};
window.addEventListener("refreshUserScore", handleRefreshScore);
return () => {
window.removeEventListener("refreshUserScore", handleRefreshScore);
};
}, [refreshUserData]);
const { username, avatar, level, score } = userData;
return (
@@ -136,7 +173,6 @@ export default function PlayerStats({ initialUserData }: PlayerStatsProps) {
</div>
</div>
</div>
</div>
);
}

File diff suppressed because one or more lines are too long

View File

@@ -1045,6 +1045,8 @@ export const SitePreferencesScalarFieldEnum = {
eventsBackground: 'eventsBackground',
leaderboardBackground: 'leaderboardBackground',
challengesBackground: 'challengesBackground',
eventRegistrationPoints: 'eventRegistrationPoints',
eventFeedbackPoints: 'eventFeedbackPoints',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
} as const

View File

@@ -154,6 +154,8 @@ export const SitePreferencesScalarFieldEnum = {
eventsBackground: 'eventsBackground',
leaderboardBackground: 'leaderboardBackground',
challengesBackground: 'challengesBackground',
eventRegistrationPoints: 'eventRegistrationPoints',
eventFeedbackPoints: 'eventFeedbackPoints',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
} as const

View File

@@ -20,16 +20,30 @@ export type SitePreferencesModel = runtime.Types.Result.DefaultSelection<Prisma.
export type AggregateSitePreferences = {
_count: SitePreferencesCountAggregateOutputType | null
_avg: SitePreferencesAvgAggregateOutputType | null
_sum: SitePreferencesSumAggregateOutputType | null
_min: SitePreferencesMinAggregateOutputType | null
_max: SitePreferencesMaxAggregateOutputType | null
}
export type SitePreferencesAvgAggregateOutputType = {
eventRegistrationPoints: number | null
eventFeedbackPoints: number | null
}
export type SitePreferencesSumAggregateOutputType = {
eventRegistrationPoints: number | null
eventFeedbackPoints: number | null
}
export type SitePreferencesMinAggregateOutputType = {
id: string | null
homeBackground: string | null
eventsBackground: string | null
leaderboardBackground: string | null
challengesBackground: string | null
eventRegistrationPoints: number | null
eventFeedbackPoints: number | null
createdAt: Date | null
updatedAt: Date | null
}
@@ -40,6 +54,8 @@ export type SitePreferencesMaxAggregateOutputType = {
eventsBackground: string | null
leaderboardBackground: string | null
challengesBackground: string | null
eventRegistrationPoints: number | null
eventFeedbackPoints: number | null
createdAt: Date | null
updatedAt: Date | null
}
@@ -50,18 +66,32 @@ export type SitePreferencesCountAggregateOutputType = {
eventsBackground: number
leaderboardBackground: number
challengesBackground: number
eventRegistrationPoints: number
eventFeedbackPoints: number
createdAt: number
updatedAt: number
_all: number
}
export type SitePreferencesAvgAggregateInputType = {
eventRegistrationPoints?: true
eventFeedbackPoints?: true
}
export type SitePreferencesSumAggregateInputType = {
eventRegistrationPoints?: true
eventFeedbackPoints?: true
}
export type SitePreferencesMinAggregateInputType = {
id?: true
homeBackground?: true
eventsBackground?: true
leaderboardBackground?: true
challengesBackground?: true
eventRegistrationPoints?: true
eventFeedbackPoints?: true
createdAt?: true
updatedAt?: true
}
@@ -72,6 +102,8 @@ export type SitePreferencesMaxAggregateInputType = {
eventsBackground?: true
leaderboardBackground?: true
challengesBackground?: true
eventRegistrationPoints?: true
eventFeedbackPoints?: true
createdAt?: true
updatedAt?: true
}
@@ -82,6 +114,8 @@ export type SitePreferencesCountAggregateInputType = {
eventsBackground?: true
leaderboardBackground?: true
challengesBackground?: true
eventRegistrationPoints?: true
eventFeedbackPoints?: true
createdAt?: true
updatedAt?: true
_all?: true
@@ -122,6 +156,18 @@ export type SitePreferencesAggregateArgs<ExtArgs extends runtime.Types.Extension
* Count returned SitePreferences
**/
_count?: true | SitePreferencesCountAggregateInputType
/**
* {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs}
*
* Select which fields to average
**/
_avg?: SitePreferencesAvgAggregateInputType
/**
* {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs}
*
* Select which fields to sum
**/
_sum?: SitePreferencesSumAggregateInputType
/**
* {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs}
*
@@ -155,6 +201,8 @@ export type SitePreferencesGroupByArgs<ExtArgs extends runtime.Types.Extensions.
take?: number
skip?: number
_count?: SitePreferencesCountAggregateInputType | true
_avg?: SitePreferencesAvgAggregateInputType
_sum?: SitePreferencesSumAggregateInputType
_min?: SitePreferencesMinAggregateInputType
_max?: SitePreferencesMaxAggregateInputType
}
@@ -165,9 +213,13 @@ export type SitePreferencesGroupByOutputType = {
eventsBackground: string | null
leaderboardBackground: string | null
challengesBackground: string | null
eventRegistrationPoints: number
eventFeedbackPoints: number
createdAt: Date
updatedAt: Date
_count: SitePreferencesCountAggregateOutputType | null
_avg: SitePreferencesAvgAggregateOutputType | null
_sum: SitePreferencesSumAggregateOutputType | null
_min: SitePreferencesMinAggregateOutputType | null
_max: SitePreferencesMaxAggregateOutputType | null
}
@@ -196,6 +248,8 @@ export type SitePreferencesWhereInput = {
eventsBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null
leaderboardBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null
challengesBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null
eventRegistrationPoints?: Prisma.IntFilter<"SitePreferences"> | number
eventFeedbackPoints?: Prisma.IntFilter<"SitePreferences"> | number
createdAt?: Prisma.DateTimeFilter<"SitePreferences"> | Date | string
updatedAt?: Prisma.DateTimeFilter<"SitePreferences"> | Date | string
}
@@ -206,6 +260,8 @@ export type SitePreferencesOrderByWithRelationInput = {
eventsBackground?: Prisma.SortOrderInput | Prisma.SortOrder
leaderboardBackground?: Prisma.SortOrderInput | Prisma.SortOrder
challengesBackground?: Prisma.SortOrderInput | Prisma.SortOrder
eventRegistrationPoints?: Prisma.SortOrder
eventFeedbackPoints?: Prisma.SortOrder
createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder
}
@@ -219,6 +275,8 @@ export type SitePreferencesWhereUniqueInput = Prisma.AtLeast<{
eventsBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null
leaderboardBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null
challengesBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null
eventRegistrationPoints?: Prisma.IntFilter<"SitePreferences"> | number
eventFeedbackPoints?: Prisma.IntFilter<"SitePreferences"> | number
createdAt?: Prisma.DateTimeFilter<"SitePreferences"> | Date | string
updatedAt?: Prisma.DateTimeFilter<"SitePreferences"> | Date | string
}, "id">
@@ -229,11 +287,15 @@ export type SitePreferencesOrderByWithAggregationInput = {
eventsBackground?: Prisma.SortOrderInput | Prisma.SortOrder
leaderboardBackground?: Prisma.SortOrderInput | Prisma.SortOrder
challengesBackground?: Prisma.SortOrderInput | Prisma.SortOrder
eventRegistrationPoints?: Prisma.SortOrder
eventFeedbackPoints?: Prisma.SortOrder
createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder
_count?: Prisma.SitePreferencesCountOrderByAggregateInput
_avg?: Prisma.SitePreferencesAvgOrderByAggregateInput
_max?: Prisma.SitePreferencesMaxOrderByAggregateInput
_min?: Prisma.SitePreferencesMinOrderByAggregateInput
_sum?: Prisma.SitePreferencesSumOrderByAggregateInput
}
export type SitePreferencesScalarWhereWithAggregatesInput = {
@@ -245,6 +307,8 @@ export type SitePreferencesScalarWhereWithAggregatesInput = {
eventsBackground?: Prisma.StringNullableWithAggregatesFilter<"SitePreferences"> | string | null
leaderboardBackground?: Prisma.StringNullableWithAggregatesFilter<"SitePreferences"> | string | null
challengesBackground?: Prisma.StringNullableWithAggregatesFilter<"SitePreferences"> | string | null
eventRegistrationPoints?: Prisma.IntWithAggregatesFilter<"SitePreferences"> | number
eventFeedbackPoints?: Prisma.IntWithAggregatesFilter<"SitePreferences"> | number
createdAt?: Prisma.DateTimeWithAggregatesFilter<"SitePreferences"> | Date | string
updatedAt?: Prisma.DateTimeWithAggregatesFilter<"SitePreferences"> | Date | string
}
@@ -255,6 +319,8 @@ export type SitePreferencesCreateInput = {
eventsBackground?: string | null
leaderboardBackground?: string | null
challengesBackground?: string | null
eventRegistrationPoints?: number
eventFeedbackPoints?: number
createdAt?: Date | string
updatedAt?: Date | string
}
@@ -265,6 +331,8 @@ export type SitePreferencesUncheckedCreateInput = {
eventsBackground?: string | null
leaderboardBackground?: string | null
challengesBackground?: string | null
eventRegistrationPoints?: number
eventFeedbackPoints?: number
createdAt?: Date | string
updatedAt?: Date | string
}
@@ -275,6 +343,8 @@ export type SitePreferencesUpdateInput = {
eventsBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
leaderboardBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
challengesBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
eventRegistrationPoints?: Prisma.IntFieldUpdateOperationsInput | number
eventFeedbackPoints?: Prisma.IntFieldUpdateOperationsInput | number
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
}
@@ -285,6 +355,8 @@ export type SitePreferencesUncheckedUpdateInput = {
eventsBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
leaderboardBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
challengesBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
eventRegistrationPoints?: Prisma.IntFieldUpdateOperationsInput | number
eventFeedbackPoints?: Prisma.IntFieldUpdateOperationsInput | number
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
}
@@ -295,6 +367,8 @@ export type SitePreferencesCreateManyInput = {
eventsBackground?: string | null
leaderboardBackground?: string | null
challengesBackground?: string | null
eventRegistrationPoints?: number
eventFeedbackPoints?: number
createdAt?: Date | string
updatedAt?: Date | string
}
@@ -305,6 +379,8 @@ export type SitePreferencesUpdateManyMutationInput = {
eventsBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
leaderboardBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
challengesBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
eventRegistrationPoints?: Prisma.IntFieldUpdateOperationsInput | number
eventFeedbackPoints?: Prisma.IntFieldUpdateOperationsInput | number
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
}
@@ -315,6 +391,8 @@ export type SitePreferencesUncheckedUpdateManyInput = {
eventsBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
leaderboardBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
challengesBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
eventRegistrationPoints?: Prisma.IntFieldUpdateOperationsInput | number
eventFeedbackPoints?: Prisma.IntFieldUpdateOperationsInput | number
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
}
@@ -325,16 +403,25 @@ export type SitePreferencesCountOrderByAggregateInput = {
eventsBackground?: Prisma.SortOrder
leaderboardBackground?: Prisma.SortOrder
challengesBackground?: Prisma.SortOrder
eventRegistrationPoints?: Prisma.SortOrder
eventFeedbackPoints?: Prisma.SortOrder
createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder
}
export type SitePreferencesAvgOrderByAggregateInput = {
eventRegistrationPoints?: Prisma.SortOrder
eventFeedbackPoints?: Prisma.SortOrder
}
export type SitePreferencesMaxOrderByAggregateInput = {
id?: Prisma.SortOrder
homeBackground?: Prisma.SortOrder
eventsBackground?: Prisma.SortOrder
leaderboardBackground?: Prisma.SortOrder
challengesBackground?: Prisma.SortOrder
eventRegistrationPoints?: Prisma.SortOrder
eventFeedbackPoints?: Prisma.SortOrder
createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder
}
@@ -345,10 +432,17 @@ export type SitePreferencesMinOrderByAggregateInput = {
eventsBackground?: Prisma.SortOrder
leaderboardBackground?: Prisma.SortOrder
challengesBackground?: Prisma.SortOrder
eventRegistrationPoints?: Prisma.SortOrder
eventFeedbackPoints?: Prisma.SortOrder
createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder
}
export type SitePreferencesSumOrderByAggregateInput = {
eventRegistrationPoints?: Prisma.SortOrder
eventFeedbackPoints?: Prisma.SortOrder
}
export type SitePreferencesSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
@@ -357,6 +451,8 @@ export type SitePreferencesSelect<ExtArgs extends runtime.Types.Extensions.Inter
eventsBackground?: boolean
leaderboardBackground?: boolean
challengesBackground?: boolean
eventRegistrationPoints?: boolean
eventFeedbackPoints?: boolean
createdAt?: boolean
updatedAt?: boolean
}, ExtArgs["result"]["sitePreferences"]>
@@ -367,6 +463,8 @@ export type SitePreferencesSelectCreateManyAndReturn<ExtArgs extends runtime.Typ
eventsBackground?: boolean
leaderboardBackground?: boolean
challengesBackground?: boolean
eventRegistrationPoints?: boolean
eventFeedbackPoints?: boolean
createdAt?: boolean
updatedAt?: boolean
}, ExtArgs["result"]["sitePreferences"]>
@@ -377,6 +475,8 @@ export type SitePreferencesSelectUpdateManyAndReturn<ExtArgs extends runtime.Typ
eventsBackground?: boolean
leaderboardBackground?: boolean
challengesBackground?: boolean
eventRegistrationPoints?: boolean
eventFeedbackPoints?: boolean
createdAt?: boolean
updatedAt?: boolean
}, ExtArgs["result"]["sitePreferences"]>
@@ -387,11 +487,13 @@ export type SitePreferencesSelectScalar = {
eventsBackground?: boolean
leaderboardBackground?: boolean
challengesBackground?: boolean
eventRegistrationPoints?: boolean
eventFeedbackPoints?: boolean
createdAt?: 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" | "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" | "eventRegistrationPoints" | "eventFeedbackPoints" | "createdAt" | "updatedAt", ExtArgs["result"]["sitePreferences"]>
export type $SitePreferencesPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
name: "SitePreferences"
@@ -402,6 +504,8 @@ export type $SitePreferencesPayload<ExtArgs extends runtime.Types.Extensions.Int
eventsBackground: string | null
leaderboardBackground: string | null
challengesBackground: string | null
eventRegistrationPoints: number
eventFeedbackPoints: number
createdAt: Date
updatedAt: Date
}, ExtArgs["result"]["sitePreferences"]>
@@ -832,6 +936,8 @@ export interface SitePreferencesFieldRefs {
readonly eventsBackground: Prisma.FieldRef<"SitePreferences", 'String'>
readonly leaderboardBackground: Prisma.FieldRef<"SitePreferences", 'String'>
readonly challengesBackground: Prisma.FieldRef<"SitePreferences", 'String'>
readonly eventRegistrationPoints: Prisma.FieldRef<"SitePreferences", 'Int'>
readonly eventFeedbackPoints: Prisma.FieldRef<"SitePreferences", 'Int'>
readonly createdAt: Prisma.FieldRef<"SitePreferences", 'DateTime'>
readonly updatedAt: Prisma.FieldRef<"SitePreferences", 'DateTime'>
}

View File

@@ -0,0 +1,18 @@
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_SitePreferences" (
"id" TEXT NOT NULL PRIMARY KEY DEFAULT 'global',
"homeBackground" TEXT,
"eventsBackground" TEXT,
"leaderboardBackground" TEXT,
"challengesBackground" TEXT,
"eventRegistrationPoints" INTEGER NOT NULL DEFAULT 100,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
INSERT INTO "new_SitePreferences" ("challengesBackground", "createdAt", "eventsBackground", "homeBackground", "id", "leaderboardBackground", "updatedAt") SELECT "challengesBackground", "createdAt", "eventsBackground", "homeBackground", "id", "leaderboardBackground", "updatedAt" FROM "SitePreferences";
DROP TABLE "SitePreferences";
ALTER TABLE "new_SitePreferences" RENAME TO "SitePreferences";
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;

View File

@@ -0,0 +1,19 @@
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_SitePreferences" (
"id" TEXT NOT NULL PRIMARY KEY DEFAULT 'global',
"homeBackground" TEXT,
"eventsBackground" TEXT,
"leaderboardBackground" TEXT,
"challengesBackground" TEXT,
"eventRegistrationPoints" INTEGER NOT NULL DEFAULT 100,
"eventFeedbackPoints" INTEGER NOT NULL DEFAULT 50,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
INSERT INTO "new_SitePreferences" ("challengesBackground", "createdAt", "eventRegistrationPoints", "eventsBackground", "homeBackground", "id", "leaderboardBackground", "updatedAt") SELECT "challengesBackground", "createdAt", "eventRegistrationPoints", "eventsBackground", "homeBackground", "id", "leaderboardBackground", "updatedAt" FROM "SitePreferences";
DROP TABLE "SitePreferences";
ALTER TABLE "new_SitePreferences" RENAME TO "SitePreferences";
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;

View File

@@ -0,0 +1,19 @@
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_SitePreferences" (
"id" TEXT NOT NULL PRIMARY KEY DEFAULT 'global',
"homeBackground" TEXT,
"eventsBackground" TEXT,
"leaderboardBackground" TEXT,
"challengesBackground" TEXT,
"eventRegistrationPoints" INTEGER NOT NULL DEFAULT 100,
"eventFeedbackPoints" INTEGER NOT NULL DEFAULT 100,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
INSERT INTO "new_SitePreferences" ("challengesBackground", "createdAt", "eventFeedbackPoints", "eventRegistrationPoints", "eventsBackground", "homeBackground", "id", "leaderboardBackground", "updatedAt") SELECT "challengesBackground", "createdAt", "eventFeedbackPoints", "eventRegistrationPoints", "eventsBackground", "homeBackground", "id", "leaderboardBackground", "updatedAt" FROM "SitePreferences";
DROP TABLE "SitePreferences";
ALTER TABLE "new_SitePreferences" RENAME TO "SitePreferences";
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;

View File

@@ -100,6 +100,8 @@ model SitePreferences {
eventsBackground String?
leaderboardBackground String?
challengesBackground String?
eventRegistrationPoints Int @default(100)
eventFeedbackPoints Int @default(100)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

View File

@@ -2,6 +2,7 @@ import { prisma } from "../database";
import type { EventFeedback, Prisma } from "@/prisma/generated/prisma/client";
import { ValidationError, NotFoundError } from "../errors";
import { eventService } from "./event.service";
import { sitePreferencesService } from "../preferences/site-preferences.service";
export interface CreateOrUpdateFeedbackInput {
rating: number;
@@ -168,11 +169,34 @@ export class EventFeedbackService {
throw new NotFoundError("Événement");
}
// Créer ou mettre à jour le feedback
return this.createOrUpdateFeedback(userId, eventId, {
rating: data.rating,
comment: data.comment || null,
});
// Vérifier si c'est un nouveau feedback ou une mise à jour
const existingFeedback = await this.getUserFeedback(userId, eventId);
const isNewFeedback = !existingFeedback;
// Récupérer les points à attribuer depuis les préférences du site
const sitePreferences = await sitePreferencesService.getOrCreateSitePreferences();
const pointsToAward = sitePreferences.eventFeedbackPoints || 100;
// Créer ou mettre à jour le feedback et attribuer les points (seulement pour nouveau feedback)
const [feedback] = await Promise.all([
this.createOrUpdateFeedback(userId, eventId, {
rating: data.rating,
comment: data.comment || null,
}),
// Attribuer les points seulement si c'est un nouveau feedback
isNewFeedback
? prisma.user.update({
where: { id: userId },
data: {
score: {
increment: pointsToAward,
},
},
})
: Promise.resolve(null),
]);
return feedback;
}
}

View File

@@ -3,6 +3,7 @@ import type { EventRegistration } from "@/prisma/generated/prisma/client";
import { ValidationError, NotFoundError, ConflictError } from "../errors";
import { eventService } from "./event.service";
import { calculateEventStatus } from "@/lib/eventStatus";
import { sitePreferencesService } from "../preferences/site-preferences.service";
/**
* Service de gestion des inscriptions aux événements
@@ -24,18 +25,39 @@ export class EventRegistrationService {
}
/**
* Désinscrit un utilisateur d'un événement
* Désinscrit un utilisateur d'un événement et retire les points attribués
*/
async unregisterUserFromEvent(
userId: string,
eventId: string
): Promise<void> {
await prisma.eventRegistration.deleteMany({
where: {
userId,
eventId,
},
});
// Vérifier que l'utilisateur est bien inscrit avant de retirer les points
const isRegistered = await this.checkUserRegistration(userId, eventId);
if (!isRegistered) {
return; // Pas d'inscription, rien à faire
}
// Récupérer les points à retirer depuis les préférences du site
const sitePreferences = await sitePreferencesService.getOrCreateSitePreferences();
const pointsToRemove = sitePreferences.eventRegistrationPoints || 100;
// Supprimer l'inscription et retirer les points en parallèle
await Promise.all([
prisma.eventRegistration.deleteMany({
where: {
userId,
eventId,
},
}),
prisma.user.update({
where: { id: userId },
data: {
score: {
decrement: pointsToRemove,
},
},
}),
]);
}
/**
@@ -123,8 +145,24 @@ export class EventRegistrationService {
throw new ConflictError("Vous êtes déjà inscrit à cet événement");
}
// Créer l'inscription
return this.registerUserToEvent(userId, eventId);
// Récupérer les points à attribuer depuis les préférences du site
const sitePreferences = await sitePreferencesService.getOrCreateSitePreferences();
const pointsToAward = sitePreferences.eventRegistrationPoints || 100;
// Créer l'inscription et attribuer les points en parallèle
const [registration] = await Promise.all([
this.registerUserToEvent(userId, eventId),
prisma.user.update({
where: { id: userId },
data: {
score: {
increment: pointsToAward,
},
},
}),
]);
return registration;
}
}

View File

@@ -7,6 +7,8 @@ export interface UpdateSitePreferencesInput {
eventsBackground?: string | null;
leaderboardBackground?: string | null;
challengesBackground?: string | null;
eventRegistrationPoints?: number;
eventFeedbackPoints?: number;
}
/**
@@ -38,6 +40,8 @@ export class SitePreferencesService {
eventsBackground: null,
leaderboardBackground: null,
challengesBackground: null,
eventRegistrationPoints: 100,
eventFeedbackPoints: 100,
},
});
}
@@ -70,6 +74,14 @@ export class SitePreferencesService {
data.challengesBackground === ""
? null
: (data.challengesBackground ?? undefined),
eventRegistrationPoints:
data.eventRegistrationPoints !== undefined
? data.eventRegistrationPoints
: undefined,
eventFeedbackPoints:
data.eventFeedbackPoints !== undefined
? data.eventFeedbackPoints
: undefined,
},
create: {
id: "global",
@@ -85,6 +97,8 @@ export class SitePreferencesService {
data.challengesBackground === ""
? null
: (data.challengesBackground ?? null),
eventRegistrationPoints: data.eventRegistrationPoints ?? 100,
eventFeedbackPoints: data.eventFeedbackPoints ?? 100,
},
});
}