Files
workshop-manager/src/components/collaboration/BaseSessionLiveWrapper.tsx
Froidefond Julien 766f3d5a59
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 4m5s
feat: add GIF Mood Board workshop
- New workshop where each team member shares up to 5 GIFs with notes to express their weekly mood
- Per-user week rating (1-5 stars) visible next to each member's section
- Masonry-style grid with adjustable column count (3/4/5) toggle
- Handwriting font (Caveat) for GIF notes
- Full real-time collaboration via SSE
- Clean migration (add_gif_mood_workshop) safe for production deploy
- DB backup via cp before each migration in docker-entrypoint

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 10:04:56 +01:00

107 lines
2.9 KiB
TypeScript

'use client';
import { useState, useCallback } from 'react';
import { useLive, type LiveEvent } from '@/hooks/useLive';
import { CollaborationToolbar } from './CollaborationToolbar';
import { ShareModal } from './ShareModal';
import type { ShareRole } from '@prisma/client';
import type { TeamWithMembers, Share } from '@/lib/share-utils';
export type LiveApiPath = 'sessions' | 'motivators' | 'weather' | 'year-review' | 'weekly-checkin' | 'gif-mood';
interface ShareModalConfig {
title: string;
sessionSubtitle: string;
helpText: React.ReactNode;
}
interface BaseSessionLiveWrapperConfig {
apiPath: LiveApiPath;
shareModal: ShareModalConfig;
onShareWithEmail: (
email: string,
role: ShareRole
) => Promise<{ success: boolean; error?: string }>;
onRemoveShare: (userId: string) => Promise<unknown>;
onShareWithTeam?: (
teamId: string,
role: ShareRole
) => Promise<{ success: boolean; error?: string }>;
}
interface BaseSessionLiveWrapperProps {
sessionId: string;
sessionTitle: string;
currentUserId: string;
shares: Share[];
isOwner: boolean;
canEdit: boolean;
userTeams?: TeamWithMembers[];
children: React.ReactNode;
config: BaseSessionLiveWrapperConfig;
}
export function BaseSessionLiveWrapper({
sessionId,
sessionTitle,
currentUserId,
shares,
isOwner,
canEdit,
userTeams = [],
children,
config,
}: BaseSessionLiveWrapperProps) {
const [shareModalOpen, setShareModalOpen] = useState(false);
const [lastEventUser, setLastEventUser] = useState<string | null>(null);
const handleEvent = useCallback((event: LiveEvent) => {
// Show who made the last change
if (event.user?.name || event.user?.email) {
setLastEventUser(event.user.name || event.user.email);
// Clear after 3 seconds
setTimeout(() => setLastEventUser(null), 3000);
}
}, []);
const { isConnected, error } = useLive({
sessionId,
apiPath: config.apiPath,
currentUserId,
onEvent: handleEvent,
});
return (
<>
<CollaborationToolbar
isConnected={isConnected}
error={error}
lastEventUser={lastEventUser}
canEdit={canEdit}
shares={shares}
onShareClick={() => setShareModalOpen(true)}
/>
{/* Content */}
<div className={!canEdit ? 'pointer-events-none opacity-90' : ''}>{children}</div>
{/* Share Modal */}
<ShareModal
isOpen={shareModalOpen}
onClose={() => setShareModalOpen(false)}
title={config.shareModal.title}
sessionSubtitle={config.shareModal.sessionSubtitle}
sessionTitle={sessionTitle}
shares={shares}
isOwner={isOwner}
userTeams={userTeams}
currentUserId={currentUserId}
onShareWithEmail={config.onShareWithEmail}
onShareWithTeam={config.onShareWithTeam}
onRemoveShare={config.onRemoveShare}
helpText={config.shareModal.helpText}
/>
</>
);
}