All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 4m5s
- 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>
107 lines
2.9 KiB
TypeScript
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}
|
|
/>
|
|
</>
|
|
);
|
|
}
|