diff --git a/app/api/tickets/[id]/messages/route.ts b/app/api/tickets/[id]/messages/route.ts
index f27b5a1..9a3d7b1 100644
--- a/app/api/tickets/[id]/messages/route.ts
+++ b/app/api/tickets/[id]/messages/route.ts
@@ -1,11 +1,11 @@
import { NextResponse } from "next/server";
-import { cookies } from "next/headers";
-import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs";
+import { createSbServer, createSbServiceRole } from "@/lib/supabaseServer";
+import { sendSupportReplyEmail, sendInternalTicketReplyEmail } from "@/lib/emailMigrationHelpers";
export const dynamic = "force-dynamic";
export async function GET(_: Request, { params }: { params: { id: string } }) {
- const sb = createRouteHandlerClient({ cookies });
+ const sb = createSbServer();
// Récupérer les messages
const { data: messages, error } = await sb
@@ -40,7 +40,7 @@ export async function GET(_: Request, { params }: { params: { id: string } }) {
}
export async function POST(req: Request, { params }: { params: { id: string } }) {
- const sb = createRouteHandlerClient({ cookies });
+ const sb = createSbServer();
const { data: { user } } = await sb.auth.getUser();
if (!user) return new NextResponse("Unauthorized", { status: 401 });
@@ -70,6 +70,158 @@ export async function POST(req: Request, { params }: { params: { id: string } })
});
if (error) return NextResponse.json({ error: error.message }, { status: 400 });
+ // Si c'est un message du staff et qu'il n'est pas interne, envoyer une notification par email
+ if (isStaff && !internal) {
+ try {
+ console.log('đ§ [POST messages] Envoi de notification email...');
+
+ // Récupérer les informations du ticket
+ const { data: ticket } = await sb
+ .from("tickets")
+ .select("id, subject, created_by, org_id")
+ .eq("id", params.id)
+ .single();
+
+ console.log('đ§ [POST messages] Ticket:', { id: ticket?.id, created_by: ticket?.created_by, org_id: ticket?.org_id });
+
+ if (ticket && ticket.created_by) {
+ // Récupérer les informations de l'organisation
+ const { data: organization } = await sb
+ .from("organizations")
+ .select("name, structure_api")
+ .eq("id", ticket.org_id)
+ .single();
+
+ console.log('đ§ [POST messages] Organization:', { name: organization?.name, structure_api: organization?.structure_api });
+
+ // Récupérer le code employeur depuis organization_details
+ const { data: orgDetails } = await sb
+ .from("organization_details")
+ .select("code_employeur")
+ .eq("org_id", ticket.org_id)
+ .maybeSingle();
+
+ console.log('đ§ [POST messages] Org details:', { code_employeur: orgDetails?.code_employeur });
+
+ // Utiliser le service role pour accéder aux données auth
+ const sbAdmin = createSbServiceRole();
+
+ // Récupérer l'email et le prénom du créateur du ticket
+ const { data: creatorUser, error: creatorError } = await sbAdmin.auth.admin.getUserById(ticket.created_by);
+ console.log('đ§ [POST messages] Creator user:', {
+ hasData: !!creatorUser,
+ email: creatorUser?.user?.email,
+ error: creatorError
+ });
+
+ // Récupérer le nom du staff qui répond
+ const { data: staffProfile, error: staffError } = await sbAdmin.auth.admin.getUserById(user.id);
+ console.log('đ§ [POST messages] Staff profile:', {
+ hasData: !!staffProfile,
+ email: staffProfile?.user?.email,
+ error: staffError
+ });
+
+ if (creatorUser?.user?.email) {
+ // Récupérer le prénom depuis user_metadata
+ const firstName = creatorUser.user.user_metadata?.display_name ||
+ creatorUser.user.user_metadata?.first_name ||
+ undefined;
+
+ // Récupérer le nom du staff depuis user_metadata
+ const staffName = staffProfile?.user?.user_metadata?.display_name ||
+ staffProfile?.user?.user_metadata?.first_name ||
+ staffProfile?.user?.email?.split('@')[0] ||
+ 'L\'équipe support';
+
+ console.log('đ§ [POST messages] Envoi vers:', creatorUser.user.email);
+ console.log('đ§ [POST messages] DonnĂ©es email:', {
+ firstName,
+ staffName,
+ ticketId: ticket.id,
+ ticketSubject: ticket.subject,
+ organizationName: organization?.name,
+ employerCode: orgDetails?.code_employeur
+ });
+
+ // Utiliser le helper pour envoyer l'email
+ await sendSupportReplyEmail(creatorUser.user.email, {
+ firstName,
+ ticketId: ticket.id,
+ ticketSubject: ticket.subject,
+ staffName,
+ staffMessage: text,
+ organizationName: organization?.name,
+ employerCode: orgDetails?.code_employeur,
+ });
+
+ console.log(`â
[POST messages] Notification email envoyée à ${creatorUser.user.email} pour le ticket ${ticket.id}`);
+ } else {
+ console.error('â [POST messages] Email du crĂ©ateur introuvable');
+ }
+ } else {
+ console.error('â [POST messages] Ticket ou created_by introuvable');
+ }
+ } catch (emailError) {
+ // On ne fait pas Ă©chouer la requĂȘte si l'email Ă©choue
+ console.error('â [POST messages] Erreur lors de l\'envoi de la notification email:', emailError);
+ }
+ }
+
+ // Si c'est un message d'un utilisateur (non-staff), envoyer une notification interne
+ if (!isStaff) {
+ try {
+ console.log('đ§ [POST messages] Envoi de notification interne...');
+
+ // Récupérer les informations du ticket
+ const { data: ticket } = await sb
+ .from("tickets")
+ .select("id, subject, status, org_id")
+ .eq("id", params.id)
+ .single();
+
+ if (ticket) {
+ // Récupérer les informations de l'organisation
+ const { data: organization } = await sb
+ .from("organizations")
+ .select("name")
+ .eq("id", ticket.org_id)
+ .single();
+
+ // Récupérer le code employeur depuis organization_details
+ const { data: orgDetails } = await sb
+ .from("organization_details")
+ .select("code_employeur")
+ .eq("org_id", ticket.org_id)
+ .maybeSingle();
+
+ // Utiliser le service role pour accéder aux données auth
+ const sbAdmin = createSbServiceRole();
+
+ // Récupérer les infos de l'utilisateur qui répond
+ const { data: userData } = await sbAdmin.auth.admin.getUserById(user.id);
+ const userName = userData?.user?.user_metadata?.display_name
+ || userData?.user?.user_metadata?.first_name
+ || 'Utilisateur inconnu';
+
+ await sendInternalTicketReplyEmail({
+ ticketId: ticket.id,
+ ticketSubject: ticket.subject,
+ ticketStatus: ticket.status,
+ userMessage: text,
+ userName,
+ userEmail: user.email || 'Email non disponible',
+ organizationName: organization?.name,
+ employerCode: orgDetails?.code_employeur,
+ });
+
+ console.log(`â
[POST messages] Notification interne envoyée pour le ticket ${ticket.id}`);
+ }
+ } catch (emailError) {
+ console.error('â [POST messages] Erreur lors de l\'envoi de la notification interne:', emailError);
+ }
+ }
+
return NextResponse.json({ ok: true }, { status: 201 });
}
diff --git a/app/api/tickets/[id]/recipient-info/route.ts b/app/api/tickets/[id]/recipient-info/route.ts
new file mode 100644
index 0000000..e002ae3
--- /dev/null
+++ b/app/api/tickets/[id]/recipient-info/route.ts
@@ -0,0 +1,86 @@
+import { NextResponse } from "next/server";
+import { createSbServer, createSbServiceRole } from "@/lib/supabaseServer";
+
+export const dynamic = "force-dynamic";
+
+export async function GET(_: Request, { params }: { params: { id: string } }) {
+ const sb = createSbServer();
+ const { data: { user } } = await sb.auth.getUser();
+ if (!user) return new NextResponse("Unauthorized", { status: 401 });
+
+ // Vérifier si l'utilisateur est staff
+ const { data: staffUser } = await sb
+ .from("staff_users")
+ .select("is_staff")
+ .eq("user_id", user.id)
+ .maybeSingle();
+
+ if (!staffUser?.is_staff) {
+ return new NextResponse("Forbidden", { status: 403 });
+ }
+
+ // Récupérer le ticket
+ const { data: ticket } = await sb
+ .from("tickets")
+ .select("created_by, org_id")
+ .eq("id", params.id)
+ .single();
+
+ console.log('đ [recipient-info] Ticket ID:', params.id);
+ console.log('đ [recipient-info] Ticket data:', ticket);
+
+ if (!ticket || !ticket.created_by) {
+ console.error('â [recipient-info] Ticket ou created_by introuvable');
+ return NextResponse.json({ error: "Ticket ou créateur introuvable" }, { status: 404 });
+ }
+
+ console.log('đ [recipient-info] Created by:', ticket.created_by);
+
+ // Utiliser le service role pour accéder aux données auth
+ const sbAdmin = createSbServiceRole();
+
+ // Récupérer l'email et les métadonnées de l'utilisateur créateur
+ const { data: creatorUser, error: userError } = await sbAdmin.auth.admin.getUserById(ticket.created_by);
+
+ console.log('đ [recipient-info] getUserById result:', {
+ hasData: !!creatorUser,
+ hasUser: !!creatorUser?.user,
+ hasEmail: !!creatorUser?.user?.email,
+ error: userError
+ });
+
+ if (!creatorUser?.user?.email) {
+ console.error('â [recipient-info] Email introuvable pour user:', ticket.created_by);
+ console.error('â [recipient-info] creatorUser:', creatorUser);
+ console.error('â [recipient-info] userError:', userError);
+ return NextResponse.json({ error: "Email introuvable" }, { status: 404 });
+ }
+
+ // Récupérer le nom depuis les métadonnées utilisateur
+ let name = creatorUser.user.email;
+ const metadata = creatorUser.user.user_metadata;
+
+ console.log('đ [recipient-info] User metadata:', JSON.stringify(metadata, null, 2));
+
+ // Priorité 1: display_name (c'est là que Supabase Auth stocke le prénom)
+ if (metadata?.display_name) {
+ name = metadata.display_name;
+ console.log('â
[recipient-info] Nom trouvé dans display_name:', name);
+ }
+ // Priorité 2: first_name + last_name
+ else if (metadata?.first_name) {
+ name = metadata.last_name
+ ? `${metadata.first_name} ${metadata.last_name}`
+ : metadata.first_name;
+ console.log('â
[recipient-info] Nom trouvé dans first_name:', name);
+ }
+ else {
+ console.log('â ïž [recipient-info] Aucun nom trouvĂ©, utilisation de l\'email');
+ }
+ // Sinon: email par défaut (déjà défini)
+
+ return NextResponse.json({
+ email: creatorUser.user.email,
+ name: name
+ });
+}
diff --git a/app/api/tickets/route.ts b/app/api/tickets/route.ts
index d2adcbe..42f64d4 100644
--- a/app/api/tickets/route.ts
+++ b/app/api/tickets/route.ts
@@ -1,6 +1,8 @@
import { NextResponse } from "next/server";
import { cookies } from "next/headers";
import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs";
+import { sendInternalTicketCreatedEmail } from "@/lib/emailMigrationHelpers";
+import { createSbServer, createSbServiceRole } from "@/lib/supabaseServer";
export const dynamic = "force-dynamic";
@@ -94,6 +96,53 @@ export async function POST(req: Request) {
.update({ message_count: 1 })
.eq("id", ticket.id);
+ // Envoyer une notification interne par email à paie@odentas.fr si le ticket n'est pas créé par le staff
+ if (!isStaff) {
+ try {
+ console.log('[TICKET CREATE] Sending internal notification email...');
+
+ // Récupérer les infos de l'utilisateur
+ const sbAdmin = createSbServiceRole();
+ const { data: userData } = await sbAdmin.auth.admin.getUserById(user.id);
+ const userName = userData?.user?.user_metadata?.display_name
+ || userData?.user?.user_metadata?.first_name
+ || 'Utilisateur inconnu';
+
+ // Récupérer les infos de l'organisation
+ const { data: organization } = await sb
+ .from('organizations')
+ .select('name')
+ .eq('id', org_id)
+ .single();
+
+ // Récupérer le code employeur
+ const { data: orgDetails } = await sb
+ .from('organization_details')
+ .select('code_employeur')
+ .eq('org_id', org_id)
+ .single();
+
+ // Déterminer la catégorie du ticket (basée sur le sujet pour l'instant)
+ const category = 'Support général';
+
+ await sendInternalTicketCreatedEmail({
+ ticketId: ticket.id,
+ ticketSubject: subject,
+ ticketCategory: category,
+ ticketMessage: message,
+ userName,
+ userEmail: user.email || 'Email non disponible',
+ organizationName: organization?.name,
+ employerCode: orgDetails?.code_employeur,
+ });
+
+ console.log('[TICKET CREATE] Internal notification email sent successfully');
+ } catch (emailError) {
+ // Ne pas bloquer la création du ticket si l'email échoue
+ console.error('[TICKET CREATE] Failed to send internal notification email:', emailError);
+ }
+ }
+
return NextResponse.json(ticket, { status: 201 });
}
diff --git a/app/layout.tsx b/app/layout.tsx
index 471598c..941e31b 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -5,6 +5,7 @@ import Providers from "@/components/Providers";
import ProgressBar from "@/components/ProgressBar";
import { PostHogPageView } from "@/components/PostHogPageView";
import PostHogIdentifier from "@/components/PostHogIdentifier";
+import PopupInfoSuivi from "@/components/PopupInfoSuivi";
import { useEffect, Suspense } from "react";
/**
@@ -72,6 +73,8 @@ export default function RootLayout({ children }: { children: React.ReactNode })
{children}
+ {/* Popup d'information sur la confidentialité et le suivi */}
+
{/* BugReporter temporairement masqué */}