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
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 6m14s
This commit is contained in:
@@ -3,6 +3,8 @@
|
|||||||
import { revalidatePath } from 'next/cache';
|
import { revalidatePath } from 'next/cache';
|
||||||
import { auth } from '@/lib/auth';
|
import { auth } from '@/lib/auth';
|
||||||
import * as weatherService from '@/services/weather';
|
import * as weatherService from '@/services/weather';
|
||||||
|
import { getUserById } from '@/services/auth';
|
||||||
|
import { broadcastToWeatherSession } from '@/app/api/weather/[id]/subscribe/route';
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// Session Actions
|
// Session Actions
|
||||||
@@ -37,14 +39,29 @@ export async function updateWeatherSession(
|
|||||||
try {
|
try {
|
||||||
await weatherService.updateWeatherSession(sessionId, authSession.user.id, data);
|
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
|
// Emit event for real-time sync
|
||||||
await weatherService.createWeatherSessionEvent(
|
const event = await weatherService.createWeatherSessionEvent(
|
||||||
sessionId,
|
sessionId,
|
||||||
authSession.user.id,
|
authSession.user.id,
|
||||||
'SESSION_UPDATED',
|
'SESSION_UPDATED',
|
||||||
data
|
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/${sessionId}`);
|
||||||
revalidatePath('/weather');
|
revalidatePath('/weather');
|
||||||
revalidatePath('/sessions');
|
revalidatePath('/sessions');
|
||||||
@@ -100,9 +117,15 @@ export async function createOrUpdateWeatherEntry(
|
|||||||
try {
|
try {
|
||||||
const entry = await weatherService.createOrUpdateWeatherEntry(sessionId, authSession.user.id, data);
|
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
|
// Emit event for real-time sync
|
||||||
const eventType = entry.createdAt.getTime() === entry.updatedAt.getTime() ? 'ENTRY_CREATED' : 'ENTRY_UPDATED';
|
const eventType = entry.createdAt.getTime() === entry.updatedAt.getTime() ? 'ENTRY_CREATED' : 'ENTRY_UPDATED';
|
||||||
await weatherService.createWeatherSessionEvent(
|
const event = await weatherService.createWeatherSessionEvent(
|
||||||
sessionId,
|
sessionId,
|
||||||
authSession.user.id,
|
authSession.user.id,
|
||||||
eventType,
|
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}`);
|
revalidatePath(`/weather/${sessionId}`);
|
||||||
return { success: true, data: entry };
|
return { success: true, data: entry };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -136,14 +172,29 @@ export async function deleteWeatherEntry(sessionId: string) {
|
|||||||
try {
|
try {
|
||||||
await weatherService.deleteWeatherEntry(sessionId, authSession.user.id);
|
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
|
// Emit event for real-time sync
|
||||||
await weatherService.createWeatherSessionEvent(
|
const event = await weatherService.createWeatherSessionEvent(
|
||||||
sessionId,
|
sessionId,
|
||||||
authSession.user.id,
|
authSession.user.id,
|
||||||
'ENTRY_DELETED',
|
'ENTRY_DELETED',
|
||||||
{ userId: authSession.user.id }
|
{ 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}`);
|
revalidatePath(`/weather/${sessionId}`);
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState, useTransition } from 'react';
|
import { useState, useTransition, useEffect } from 'react';
|
||||||
import { createOrUpdateWeatherEntry } from '@/actions/weather';
|
import { createOrUpdateWeatherEntry } from '@/actions/weather';
|
||||||
import { Avatar } from '@/components/ui/Avatar';
|
import { Avatar } from '@/components/ui/Avatar';
|
||||||
import { Textarea } from '@/components/ui/Textarea';
|
import { Textarea } from '@/components/ui/Textarea';
|
||||||
@@ -61,6 +61,16 @@ export function WeatherCard({ sessionId, currentUserId, entry, canEdit }: Weathe
|
|||||||
const isCurrentUser = entry.userId === currentUserId;
|
const isCurrentUser = entry.userId === currentUserId;
|
||||||
const canEditThis = canEdit && isCurrentUser;
|
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) {
|
function handleEmojiChange(axis: 'performance' | 'moral' | 'flux' | 'valueCreation', emoji: string | null) {
|
||||||
if (!canEditThis) return;
|
if (!canEditThis) return;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user