feat: enhance real-time weather session updates by broadcasting user information and syncing local state in WeatherCard component
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 6m14s

This commit is contained in:
Julien Froidefond
2026-02-04 11:05:33 +01:00
parent e8ffccd286
commit 057732f00e
2 changed files with 65 additions and 4 deletions

View File

@@ -3,6 +3,8 @@
import { revalidatePath } from 'next/cache';
import { auth } from '@/lib/auth';
import * as weatherService from '@/services/weather';
import { getUserById } from '@/services/auth';
import { broadcastToWeatherSession } from '@/app/api/weather/[id]/subscribe/route';
// ============================================
// Session Actions
@@ -37,14 +39,29 @@ export async function updateWeatherSession(
try {
await weatherService.updateWeatherSession(sessionId, authSession.user.id, data);
// Get user info for broadcast
const user = await getUserById(authSession.user.id);
if (!user) {
return { success: false, error: 'Utilisateur non trouvé' };
}
// Emit event for real-time sync
await weatherService.createWeatherSessionEvent(
const event = await weatherService.createWeatherSessionEvent(
sessionId,
authSession.user.id,
'SESSION_UPDATED',
data
);
// Broadcast immediately via SSE
broadcastToWeatherSession(sessionId, {
type: 'SESSION_UPDATED',
payload: data,
userId: authSession.user.id,
user: { id: user.id, name: user.name, email: user.email },
timestamp: event.createdAt,
});
revalidatePath(`/weather/${sessionId}`);
revalidatePath('/weather');
revalidatePath('/sessions');
@@ -100,9 +117,15 @@ export async function createOrUpdateWeatherEntry(
try {
const entry = await weatherService.createOrUpdateWeatherEntry(sessionId, authSession.user.id, data);
// Get user info for broadcast
const user = await getUserById(authSession.user.id);
if (!user) {
return { success: false, error: 'Utilisateur non trouvé' };
}
// Emit event for real-time sync
const eventType = entry.createdAt.getTime() === entry.updatedAt.getTime() ? 'ENTRY_CREATED' : 'ENTRY_UPDATED';
await weatherService.createWeatherSessionEvent(
const event = await weatherService.createWeatherSessionEvent(
sessionId,
authSession.user.id,
eventType,
@@ -113,6 +136,19 @@ export async function createOrUpdateWeatherEntry(
}
);
// Broadcast immediately via SSE
broadcastToWeatherSession(sessionId, {
type: eventType,
payload: {
entryId: entry.id,
userId: entry.userId,
...data,
},
userId: authSession.user.id,
user: { id: user.id, name: user.name, email: user.email },
timestamp: event.createdAt,
});
revalidatePath(`/weather/${sessionId}`);
return { success: true, data: entry };
} catch (error) {
@@ -136,14 +172,29 @@ export async function deleteWeatherEntry(sessionId: string) {
try {
await weatherService.deleteWeatherEntry(sessionId, authSession.user.id);
// Get user info for broadcast
const user = await getUserById(authSession.user.id);
if (!user) {
return { success: false, error: 'Utilisateur non trouvé' };
}
// Emit event for real-time sync
await weatherService.createWeatherSessionEvent(
const event = await weatherService.createWeatherSessionEvent(
sessionId,
authSession.user.id,
'ENTRY_DELETED',
{ userId: authSession.user.id }
);
// Broadcast immediately via SSE
broadcastToWeatherSession(sessionId, {
type: 'ENTRY_DELETED',
payload: { userId: authSession.user.id },
userId: authSession.user.id,
user: { id: user.id, name: user.name, email: user.email },
timestamp: event.createdAt,
});
revalidatePath(`/weather/${sessionId}`);
return { success: true };
} catch (error) {

View File

@@ -1,6 +1,6 @@
'use client';
import { useState, useTransition } from 'react';
import { useState, useTransition, useEffect } from 'react';
import { createOrUpdateWeatherEntry } from '@/actions/weather';
import { Avatar } from '@/components/ui/Avatar';
import { Textarea } from '@/components/ui/Textarea';
@@ -61,6 +61,16 @@ export function WeatherCard({ sessionId, currentUserId, entry, canEdit }: Weathe
const isCurrentUser = entry.userId === currentUserId;
const canEditThis = canEdit && isCurrentUser;
// Sync local state with props when they change (e.g., from SSE refresh)
useEffect(() => {
// eslint-disable-next-line react-hooks/set-state-in-effect
setNotes(entry.notes || '');
setPerformanceEmoji(entry.performanceEmoji || null);
setMoralEmoji(entry.moralEmoji || null);
setFluxEmoji(entry.fluxEmoji || null);
setValueCreationEmoji(entry.valueCreationEmoji || null);
}, [entry.notes, entry.performanceEmoji, entry.moralEmoji, entry.fluxEmoji, entry.valueCreationEmoji]);
function handleEmojiChange(axis: 'performance' | 'moral' | 'flux' | 'valueCreation', emoji: string | null) {
if (!canEditThis) return;