feat(gif-mood): hide/reveal GIFs from other participants

- Add hidden field to GifMoodUserRating (schema + migration)
- Add setGifMoodUserHidden service + action with SSE broadcast
- Current user sees Cacher/Révéler toggle in their section header
- Other users see a locked placeholder with item count when hidden

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-25 16:35:43 +01:00
parent ab00627a09
commit e7ce98320d
5 changed files with 135 additions and 8 deletions

View File

@@ -16,7 +16,7 @@ import {
arrayMove,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { setGifMoodUserRating, reorderGifMoodItems } from '@/actions/gif-mood';
import { setGifMoodUserRating, reorderGifMoodItems, setGifMoodUserHidden } from '@/actions/gif-mood';
import { Avatar } from '@/components/ui/Avatar';
import { GifMoodCard } from './GifMoodCard';
import { GifMoodAddForm } from './GifMoodAddForm';
@@ -55,7 +55,7 @@ interface GifMoodBoardProps {
name: string | null;
email: string;
};
ratings: { userId: string; rating: number }[];
ratings: { userId: string; rating: number | null; hidden: boolean }[];
canEdit: boolean;
}
@@ -218,6 +218,7 @@ export function GifMoodBoard({
}: GifMoodBoardProps) {
const [cols, setCols] = useState(4);
const [, startReorderTransition] = useTransition();
const [, startHiddenTransition] = useTransition();
// Optimistic reorder state for the current user's items
const [optimisticItems, setOptimisticItems] = useState<GifMoodItem[]>([]);
@@ -304,7 +305,10 @@ export function GifMoodBoard({
isCurrentUser && optimisticItems.length > 0 ? optimisticItems : serverItems;
const canAdd = canEdit && isCurrentUser && userItems.length < GIF_MOOD_MAX_ITEMS;
const accentColor = SECTION_COLORS[index % SECTION_COLORS.length];
const userRating = ratings.find((r) => r.userId === user.id)?.rating ?? null;
const userRatingEntry = ratings.find((r) => r.userId === user.id);
const userRating = userRatingEntry?.rating ?? null;
const isHidden = userRatingEntry?.hidden ?? false;
const showHidden = !isCurrentUser && isHidden;
return (
<section key={user.id}>
@@ -338,10 +342,60 @@ export function GifMoodBoard({
/>
</div>
</div>
{/* Hide/reveal toggle — current user only */}
{isCurrentUser && canEdit && userItems.length > 0 && (
<button
onClick={() => {
startHiddenTransition(async () => {
await setGifMoodUserHidden(sessionId, !isHidden);
});
}}
className={`flex items-center gap-1.5 text-xs px-3 py-1.5 rounded-full border transition-all ${
isHidden
? 'border-primary bg-primary/10 text-primary'
: 'border-border text-muted hover:text-foreground hover:border-foreground/30'
}`}
title={isHidden ? 'Révéler mes GIFs' : 'Cacher mes GIFs'}
>
{isHidden ? (
<>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
<circle cx="12" cy="12" r="3"/>
</svg>
Révéler
</>
) : (
<>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/>
<line x1="1" y1="1" x2="23" y2="23"/>
</svg>
Cacher
</>
)}
</button>
)}
</div>
{/* Hidden placeholder for other users */}
{showHidden ? (
<div className="flex items-center justify-center rounded-2xl border border-dashed border-border/60 py-10 gap-3">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="text-muted/50" aria-hidden>
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/>
<path d="M7 11V7a5 5 0 0 1 10 0v4"/>
</svg>
<p className="text-sm text-muted/60">
{userItems.length > 0
? `${userItems.length} GIF${userItems.length !== 1 ? 's' : ''} caché${userItems.length !== 1 ? 's' : ''}`
: 'GIFs cachés'}
</p>
</div>
) : null}
{/* Grid */}
{isCurrentUser && canEdit ? (
{!showHidden && isCurrentUser && canEdit ? (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
@@ -372,7 +426,7 @@ export function GifMoodBoard({
</div>
</SortableContext>
</DndContext>
) : (
) : !showHidden ? (
<div className={`grid ${GRID_COLS[cols]} gap-4 items-start`}>
{userItems.map((item) => (
<GifMoodCard
@@ -389,7 +443,7 @@ export function GifMoodBoard({
</div>
)}
</div>
)}
) : null}
</section>
);
})}