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:
@@ -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>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user