From 739b0bf87dadf2ad0cb00e9466e90d61de0f480b Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Wed, 18 Feb 2026 08:39:15 +0100 Subject: [PATCH] feat: refactor session components to utilize BaseSessionLiveWrapper, streamlining sharing functionality and reducing code duplication across various session types --- src/app/sessions/WorkshopTabs.tsx | 14 +- .../collaboration/BaseSessionLiveWrapper.tsx | 100 +++++++++++ .../collaboration/CollaborationToolbar.tsx | 51 ++++++ .../collaboration/CollaboratorAvatars.tsx | 37 ++++ .../collaboration/SessionLiveWrapper.tsx | 149 ++++------------ src/components/collaboration/ShareButton.tsx | 23 +++ src/components/collaboration/ShareModal.tsx | 15 +- src/components/collaboration/index.ts | 5 + .../MotivatorLiveWrapper.tsx | 149 ++++------------ src/components/weather/WeatherLiveWrapper.tsx | 159 ++++-------------- .../WeeklyCheckInLiveWrapper.tsx | 149 ++++------------ .../year-review/YearReviewLiveWrapper.tsx | 149 ++++------------ src/hooks/useMotivatorLive.ts | 33 ---- src/hooks/useSessionLive.ts | 33 ---- src/hooks/useWeatherLive.ts | 31 ---- src/hooks/useWeeklyCheckInLive.ts | 31 ---- src/hooks/useYearReviewLive.ts | 33 ---- src/lib/share-utils.ts | 15 ++ 18 files changed, 381 insertions(+), 795 deletions(-) create mode 100644 src/components/collaboration/BaseSessionLiveWrapper.tsx create mode 100644 src/components/collaboration/CollaborationToolbar.tsx create mode 100644 src/components/collaboration/CollaboratorAvatars.tsx create mode 100644 src/components/collaboration/ShareButton.tsx delete mode 100644 src/hooks/useMotivatorLive.ts delete mode 100644 src/hooks/useSessionLive.ts delete mode 100644 src/hooks/useWeatherLive.ts delete mode 100644 src/hooks/useWeeklyCheckInLive.ts delete mode 100644 src/hooks/useYearReviewLive.ts diff --git a/src/app/sessions/WorkshopTabs.tsx b/src/app/sessions/WorkshopTabs.tsx index 9a56cb9..33d9fdf 100644 --- a/src/app/sessions/WorkshopTabs.tsx +++ b/src/app/sessions/WorkshopTabs.tsx @@ -27,24 +27,14 @@ import { } from '@/lib/workshops'; import { useClickOutside } from '@/hooks/useClickOutside'; +import type { Share } from '@/lib/share-utils'; + const TYPE_TABS = [ { value: 'all' as const, icon: '📋', label: 'Tous' }, { value: 'team' as const, icon: '🏢', label: 'Équipe' }, ...WORKSHOPS.map((w) => ({ value: w.id, icon: w.icon, label: w.labelShort })), ]; -interface ShareUser { - id: string; - name: string | null; - email: string; -} - -interface Share { - id: string; - role: 'VIEWER' | 'EDITOR'; - user: ShareUser; -} - interface ResolvedCollaborator { raw: string; matchedUser: { diff --git a/src/components/collaboration/BaseSessionLiveWrapper.tsx b/src/components/collaboration/BaseSessionLiveWrapper.tsx new file mode 100644 index 0000000..8ac17e3 --- /dev/null +++ b/src/components/collaboration/BaseSessionLiveWrapper.tsx @@ -0,0 +1,100 @@ +'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'; + +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; + 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(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 ( + <> + setShareModalOpen(true)} + /> + + {/* Content */} +
{children}
+ + {/* Share Modal */} + 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} + /> + + ); +} diff --git a/src/components/collaboration/CollaborationToolbar.tsx b/src/components/collaboration/CollaborationToolbar.tsx new file mode 100644 index 0000000..c76b55f --- /dev/null +++ b/src/components/collaboration/CollaborationToolbar.tsx @@ -0,0 +1,51 @@ +'use client'; + +import { LiveIndicator } from './LiveIndicator'; +import { ShareButton } from './ShareButton'; +import { CollaboratorAvatars } from './CollaboratorAvatars'; +import type { Share } from '@/lib/share-utils'; + +interface CollaborationToolbarProps { + isConnected: boolean; + error: string | null; + lastEventUser: string | null; + canEdit: boolean; + shares: Share[]; + onShareClick: () => void; +} + +export function CollaborationToolbar({ + isConnected, + error, + lastEventUser, + canEdit, + shares, + onShareClick, +}: CollaborationToolbarProps) { + return ( +
+
+ + + {lastEventUser && ( +
+ ✏️ + {lastEventUser} édite... +
+ )} + + {!canEdit && ( +
+ 👁️ + Mode lecture +
+ )} +
+ +
+ + +
+
+ ); +} diff --git a/src/components/collaboration/CollaboratorAvatars.tsx b/src/components/collaboration/CollaboratorAvatars.tsx new file mode 100644 index 0000000..cbbe987 --- /dev/null +++ b/src/components/collaboration/CollaboratorAvatars.tsx @@ -0,0 +1,37 @@ +'use client'; + +import { Avatar } from '@/components/ui/Avatar'; +import type { Share } from '@/lib/share-utils'; + +interface CollaboratorAvatarsProps { + shares: Share[]; + maxVisible?: number; +} + +export function CollaboratorAvatars({ shares, maxVisible = 3 }: CollaboratorAvatarsProps) { + if (shares.length === 0) { + return null; + } + + const visibleShares = shares.slice(0, maxVisible); + const remainingCount = shares.length - maxVisible; + + return ( +
+ {visibleShares.map((share) => ( + + ))} + {remainingCount > 0 && ( +
+ +{remainingCount} +
+ )} +
+ ); +} diff --git a/src/components/collaboration/SessionLiveWrapper.tsx b/src/components/collaboration/SessionLiveWrapper.tsx index bab4b6a..67b899d 100644 --- a/src/components/collaboration/SessionLiveWrapper.tsx +++ b/src/components/collaboration/SessionLiveWrapper.tsx @@ -1,27 +1,8 @@ 'use client'; -import { useState, useCallback } from 'react'; -import { useSessionLive, type LiveEvent } from '@/hooks/useSessionLive'; -import { LiveIndicator } from './LiveIndicator'; -import { ShareModal } from './ShareModal'; +import { BaseSessionLiveWrapper } from './BaseSessionLiveWrapper'; import { shareSessionAction, removeShareAction } from '@/actions/share'; -import { Button } from '@/components/ui/Button'; -import { Avatar } from '@/components/ui/Avatar'; -import type { ShareRole } from '@prisma/client'; -import type { TeamWithMembers } from '@/lib/share-utils'; - -interface ShareUser { - id: string; - name: string | null; - email: string; -} - -interface Share { - id: string; - role: ShareRole; - user: ShareUser; - createdAt: Date; -} +import type { TeamWithMembers, Share } from '@/lib/share-utils'; interface SessionLiveWrapperProps { sessionId: string; @@ -44,105 +25,33 @@ export function SessionLiveWrapper({ userTeams = [], children, }: SessionLiveWrapperProps) { - const [shareModalOpen, setShareModalOpen] = useState(false); - const [lastEventUser, setLastEventUser] = useState(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 } = useSessionLive({ - sessionId, - currentUserId, - onEvent: handleEvent, - }); - return ( - <> - {/* Header toolbar */} -
-
- - - {lastEventUser && ( -
- ✏️ - {lastEventUser} édite... -
- )} - - {!canEdit && ( -
- 👁️ - Mode lecture -
- )} -
- -
- {/* Collaborators avatars */} - {shares.length > 0 && ( -
- {shares.slice(0, 3).map((share) => ( - - ))} - {shares.length > 3 && ( -
- +{shares.length - 3} -
- )} -
- )} - - -
-
- - {/* Content */} -
{children}
- - {/* Share Modal */} - setShareModalOpen(false)} - title="Partager la session" - sessionSubtitle="Session" - sessionTitle={sessionTitle} - shares={shares} - isOwner={isOwner} - userTeams={userTeams} - currentUserId={currentUserId} - onShareWithEmail={(email, role) => shareSessionAction(sessionId, email, role)} - onRemoveShare={(userId) => removeShareAction(sessionId, userId)} - helpText={ - <> - Éditeur : peut modifier les items et actions -
- Lecteur : peut uniquement consulter - - } - /> - + + Éditeur : peut modifier les items et actions +
+ Lecteur : peut uniquement consulter + + ), + }, + onShareWithEmail: (email, role) => shareSessionAction(sessionId, email, role), + onRemoveShare: (userId) => removeShareAction(sessionId, userId), + }} + > + {children} +
); } diff --git a/src/components/collaboration/ShareButton.tsx b/src/components/collaboration/ShareButton.tsx new file mode 100644 index 0000000..a6efefa --- /dev/null +++ b/src/components/collaboration/ShareButton.tsx @@ -0,0 +1,23 @@ +'use client'; + +import { Button } from '@/components/ui/Button'; + +interface ShareButtonProps { + onClick: () => void; +} + +export function ShareButton({ onClick }: ShareButtonProps) { + return ( + + ); +} diff --git a/src/components/collaboration/ShareModal.tsx b/src/components/collaboration/ShareModal.tsx index 0b94463..82f429b 100644 --- a/src/components/collaboration/ShareModal.tsx +++ b/src/components/collaboration/ShareModal.tsx @@ -8,22 +8,9 @@ import { Button } from '@/components/ui/Button'; import { Badge } from '@/components/ui/Badge'; import { Avatar } from '@/components/ui/Avatar'; import { Select } from '@/components/ui/Select'; -import { getTeamMembersForShare, type TeamWithMembers } from '@/lib/share-utils'; +import { getTeamMembersForShare, type TeamWithMembers, type Share } from '@/lib/share-utils'; import type { ShareRole } from '@prisma/client'; -interface ShareUser { - id: string; - name: string | null; - email: string; -} - -interface Share { - id: string; - role: ShareRole; - user: ShareUser; - createdAt?: Date; -} - type ShareTab = 'teamMember' | 'team' | 'email'; interface ShareModalProps { diff --git a/src/components/collaboration/index.ts b/src/components/collaboration/index.ts index d983c7d..a41327c 100644 --- a/src/components/collaboration/index.ts +++ b/src/components/collaboration/index.ts @@ -1,3 +1,8 @@ export { LiveIndicator } from './LiveIndicator'; export { ShareModal } from './ShareModal'; export { SessionLiveWrapper } from './SessionLiveWrapper'; +export { BaseSessionLiveWrapper } from './BaseSessionLiveWrapper'; +export { ShareButton } from './ShareButton'; +export { CollaboratorAvatars } from './CollaboratorAvatars'; +export { CollaborationToolbar } from './CollaborationToolbar'; +export type { LiveApiPath } from './BaseSessionLiveWrapper'; diff --git a/src/components/moving-motivators/MotivatorLiveWrapper.tsx b/src/components/moving-motivators/MotivatorLiveWrapper.tsx index 4bd4779..ce18210 100644 --- a/src/components/moving-motivators/MotivatorLiveWrapper.tsx +++ b/src/components/moving-motivators/MotivatorLiveWrapper.tsx @@ -1,27 +1,8 @@ 'use client'; -import { useState, useCallback } from 'react'; -import { useMotivatorLive, type MotivatorLiveEvent } from '@/hooks/useMotivatorLive'; -import { LiveIndicator } from '@/components/collaboration/LiveIndicator'; -import { ShareModal } from '@/components/collaboration/ShareModal'; +import { BaseSessionLiveWrapper } from '@/components/collaboration/BaseSessionLiveWrapper'; import { shareMotivatorSession, removeMotivatorShare } from '@/actions/moving-motivators'; -import type { TeamWithMembers } from '@/lib/share-utils'; -import { Button } from '@/components/ui/Button'; -import { Avatar } from '@/components/ui/Avatar'; -import type { ShareRole } from '@prisma/client'; - -interface ShareUser { - id: string; - name: string | null; - email: string; -} - -interface Share { - id: string; - role: ShareRole; - user: ShareUser; - createdAt: Date; -} +import type { TeamWithMembers, Share } from '@/lib/share-utils'; interface MotivatorLiveWrapperProps { sessionId: string; @@ -44,105 +25,33 @@ export function MotivatorLiveWrapper({ userTeams = [], children, }: MotivatorLiveWrapperProps) { - const [shareModalOpen, setShareModalOpen] = useState(false); - const [lastEventUser, setLastEventUser] = useState(null); - - const handleEvent = useCallback((event: MotivatorLiveEvent) => { - // 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 } = useMotivatorLive({ - sessionId, - currentUserId, - onEvent: handleEvent, - }); - return ( - <> - {/* Header toolbar */} -
-
- - - {lastEventUser && ( -
- ✏️ - {lastEventUser} édite... -
- )} - - {!canEdit && ( -
- 👁️ - Mode lecture -
- )} -
- -
- {/* Collaborators avatars */} - {shares.length > 0 && ( -
- {shares.slice(0, 3).map((share) => ( - - ))} - {shares.length > 3 && ( -
- +{shares.length - 3} -
- )} -
- )} - - -
-
- - {/* Content */} -
{children}
- - {/* Share Modal */} - setShareModalOpen(false)} - title="Partager la session" - sessionSubtitle="Session Moving Motivators" - sessionTitle={sessionTitle} - shares={shares} - isOwner={isOwner} - userTeams={userTeams} - currentUserId={currentUserId} - onShareWithEmail={(email, role) => shareMotivatorSession(sessionId, email, role)} - onRemoveShare={(userId) => removeMotivatorShare(sessionId, userId)} - helpText={ - <> - Éditeur : peut modifier les cartes et leurs positions -
- Lecteur : peut uniquement consulter - - } - /> - + + Éditeur : peut modifier les cartes et leurs positions +
+ Lecteur : peut uniquement consulter + + ), + }, + onShareWithEmail: (email, role) => shareMotivatorSession(sessionId, email, role), + onRemoveShare: (userId) => removeMotivatorShare(sessionId, userId), + }} + > + {children} +
); } diff --git a/src/components/weather/WeatherLiveWrapper.tsx b/src/components/weather/WeatherLiveWrapper.tsx index d764256..db58fdf 100644 --- a/src/components/weather/WeatherLiveWrapper.tsx +++ b/src/components/weather/WeatherLiveWrapper.tsx @@ -1,33 +1,8 @@ 'use client'; -import { useState, useCallback } from 'react'; -import { useWeatherLive, type WeatherLiveEvent } from '@/hooks/useWeatherLive'; -import { LiveIndicator } from '@/components/collaboration/LiveIndicator'; -import { ShareModal } from '@/components/collaboration/ShareModal'; +import { BaseSessionLiveWrapper } from '@/components/collaboration/BaseSessionLiveWrapper'; import { shareWeatherSession, shareWeatherSessionToTeam, removeWeatherShare } from '@/actions/weather'; -import { Button } from '@/components/ui/Button'; -import { Avatar } from '@/components/ui/Avatar'; -import type { ShareRole } from '@prisma/client'; - -interface ShareUser { - id: string; - name: string | null; - email: string; -} - -interface Share { - id: string; - role: ShareRole; - user: ShareUser; - createdAt: Date; -} - -interface Team { - id: string; - name: string; - description: string | null; - userRole: 'ADMIN' | 'MEMBER'; -} +import type { TeamWithMembers, Share } from '@/lib/share-utils'; interface WeatherLiveWrapperProps { sessionId: string; @@ -36,7 +11,7 @@ interface WeatherLiveWrapperProps { shares: Share[]; isOwner: boolean; canEdit: boolean; - userTeams?: Team[]; + userTeams?: TeamWithMembers[]; children: React.ReactNode; } @@ -50,106 +25,34 @@ export function WeatherLiveWrapper({ userTeams = [], children, }: WeatherLiveWrapperProps) { - const [shareModalOpen, setShareModalOpen] = useState(false); - const [lastEventUser, setLastEventUser] = useState(null); - - const handleEvent = useCallback((event: WeatherLiveEvent) => { - // 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 } = useWeatherLive({ - sessionId, - currentUserId, - onEvent: handleEvent, - }); - return ( - <> - {/* Header toolbar */} -
-
- - - {lastEventUser && ( -
- ✏️ - {lastEventUser} édite... -
- )} - - {!canEdit && ( -
- 👁️ - Mode lecture -
- )} -
- -
- {/* Collaborators avatars */} - {shares.length > 0 && ( -
- {shares.slice(0, 3).map((share) => ( - - ))} - {shares.length > 3 && ( -
- +{shares.length - 3} -
- )} -
- )} - - -
-
- - {/* Content */} -
{children}
- - {/* Share Modal */} - setShareModalOpen(false)} - title="Partager la météo" - sessionSubtitle="Météo personnelle" - sessionTitle={sessionTitle} - shares={shares} - isOwner={isOwner} - userTeams={userTeams} - currentUserId={currentUserId} - onShareWithEmail={(email, role) => shareWeatherSession(sessionId, email, role)} - onShareWithTeam={(teamId, role) => shareWeatherSessionToTeam(sessionId, teamId, role)} - onRemoveShare={(userId) => removeWeatherShare(sessionId, userId)} - helpText={ - <> - Éditeur : peut modifier sa météo et voir celle des autres -
- Lecteur : peut uniquement consulter - - } - /> - + + Éditeur : peut modifier sa météo et voir celle des autres +
+ Lecteur : peut uniquement consulter + + ), + }, + onShareWithEmail: (email, role) => shareWeatherSession(sessionId, email, role), + onShareWithTeam: (teamId, role) => shareWeatherSessionToTeam(sessionId, teamId, role), + onRemoveShare: (userId) => removeWeatherShare(sessionId, userId), + }} + > + {children} +
); } diff --git a/src/components/weekly-checkin/WeeklyCheckInLiveWrapper.tsx b/src/components/weekly-checkin/WeeklyCheckInLiveWrapper.tsx index 2f51e33..a92f654 100644 --- a/src/components/weekly-checkin/WeeklyCheckInLiveWrapper.tsx +++ b/src/components/weekly-checkin/WeeklyCheckInLiveWrapper.tsx @@ -1,27 +1,8 @@ 'use client'; -import { useState, useCallback } from 'react'; -import { useWeeklyCheckInLive, type WeeklyCheckInLiveEvent } from '@/hooks/useWeeklyCheckInLive'; -import { LiveIndicator } from '@/components/collaboration/LiveIndicator'; -import { ShareModal } from '@/components/collaboration/ShareModal'; +import { BaseSessionLiveWrapper } from '@/components/collaboration/BaseSessionLiveWrapper'; import { shareWeeklyCheckInSession, removeWeeklyCheckInShare } from '@/actions/weekly-checkin'; -import type { TeamWithMembers } from '@/lib/share-utils'; -import { Button } from '@/components/ui/Button'; -import { Avatar } from '@/components/ui/Avatar'; -import type { ShareRole } from '@prisma/client'; - -interface ShareUser { - id: string; - name: string | null; - email: string; -} - -interface Share { - id: string; - role: ShareRole; - user: ShareUser; - createdAt: Date; -} +import type { TeamWithMembers, Share } from '@/lib/share-utils'; interface WeeklyCheckInLiveWrapperProps { sessionId: string; @@ -44,105 +25,33 @@ export function WeeklyCheckInLiveWrapper({ userTeams = [], children, }: WeeklyCheckInLiveWrapperProps) { - const [shareModalOpen, setShareModalOpen] = useState(false); - const [lastEventUser, setLastEventUser] = useState(null); - - const handleEvent = useCallback((event: WeeklyCheckInLiveEvent) => { - // 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 } = useWeeklyCheckInLive({ - sessionId, - currentUserId, - onEvent: handleEvent, - }); - return ( - <> - {/* Header toolbar */} -
-
- - - {lastEventUser && ( -
- ✏️ - {lastEventUser} édite... -
- )} - - {!canEdit && ( -
- 👁️ - Mode lecture -
- )} -
- -
- {/* Collaborators avatars */} - {shares.length > 0 && ( -
- {shares.slice(0, 3).map((share) => ( - - ))} - {shares.length > 3 && ( -
- +{shares.length - 3} -
- )} -
- )} - - -
-
- - {/* Content */} -
{children}
- - {/* Share Modal */} - setShareModalOpen(false)} - title="Options de partage" - sessionSubtitle="Check-in hebdomadaire" - sessionTitle={sessionTitle} - shares={shares} - isOwner={isOwner} - userTeams={userTeams} - currentUserId={currentUserId} - onShareWithEmail={(email, role) => shareWeeklyCheckInSession(sessionId, email, role)} - onRemoveShare={(userId) => removeWeeklyCheckInShare(sessionId, userId)} - helpText={ - <> - Éditeur : peut modifier les items et leurs catégories -
- Lecteur : peut uniquement consulter - - } - /> - + + Éditeur : peut modifier les items et leurs catégories +
+ Lecteur : peut uniquement consulter + + ), + }, + onShareWithEmail: (email, role) => shareWeeklyCheckInSession(sessionId, email, role), + onRemoveShare: (userId) => removeWeeklyCheckInShare(sessionId, userId), + }} + > + {children} +
); } diff --git a/src/components/year-review/YearReviewLiveWrapper.tsx b/src/components/year-review/YearReviewLiveWrapper.tsx index 3945009..03fbd63 100644 --- a/src/components/year-review/YearReviewLiveWrapper.tsx +++ b/src/components/year-review/YearReviewLiveWrapper.tsx @@ -1,27 +1,8 @@ 'use client'; -import { useState, useCallback } from 'react'; -import { useYearReviewLive, type YearReviewLiveEvent } from '@/hooks/useYearReviewLive'; -import { LiveIndicator } from '@/components/collaboration/LiveIndicator'; -import { ShareModal } from '@/components/collaboration/ShareModal'; +import { BaseSessionLiveWrapper } from '@/components/collaboration/BaseSessionLiveWrapper'; import { shareYearReviewSession, removeYearReviewShare } from '@/actions/year-review'; -import type { TeamWithMembers } from '@/lib/share-utils'; -import { Button } from '@/components/ui/Button'; -import { Avatar } from '@/components/ui/Avatar'; -import type { ShareRole } from '@prisma/client'; - -interface ShareUser { - id: string; - name: string | null; - email: string; -} - -interface Share { - id: string; - role: ShareRole; - user: ShareUser; - createdAt: Date; -} +import type { TeamWithMembers, Share } from '@/lib/share-utils'; interface YearReviewLiveWrapperProps { sessionId: string; @@ -44,106 +25,34 @@ export function YearReviewLiveWrapper({ userTeams = [], children, }: YearReviewLiveWrapperProps) { - const [shareModalOpen, setShareModalOpen] = useState(false); - const [lastEventUser, setLastEventUser] = useState(null); - - const handleEvent = useCallback((event: YearReviewLiveEvent) => { - // 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 } = useYearReviewLive({ - sessionId, - currentUserId, - onEvent: handleEvent, - }); - return ( - <> - {/* Header toolbar */} -
-
- - - {lastEventUser && ( -
- ✏️ - {lastEventUser} édite... -
- )} - - {!canEdit && ( -
- 👁️ - Mode lecture -
- )} -
- -
- {/* Collaborators avatars */} - {shares.length > 0 && ( -
- {shares.slice(0, 3).map((share) => ( - - ))} - {shares.length > 3 && ( -
- +{shares.length - 3} -
- )} -
- )} - - -
-
- - {/* Content */} -
{children}
- - {/* Share Modal */} - setShareModalOpen(false)} - title="Partager le bilan" - sessionSubtitle="Bilan annuel" - sessionTitle={sessionTitle} - shares={shares} - isOwner={isOwner} - userTeams={userTeams} - currentUserId={currentUserId} - onShareWithEmail={(email, role) => shareYearReviewSession(sessionId, email, role)} - onRemoveShare={(userId) => removeYearReviewShare(sessionId, userId)} - helpText={ - <> - Éditeur : peut modifier les items et leurs catégories -
- Lecteur : peut uniquement consulter - - } - /> - + + Éditeur : peut modifier les items et leurs catégories +
+ Lecteur : peut uniquement consulter + + ), + }, + onShareWithEmail: (email, role) => shareYearReviewSession(sessionId, email, role), + onRemoveShare: (userId) => removeYearReviewShare(sessionId, userId), + }} + > + {children} +
); } diff --git a/src/hooks/useMotivatorLive.ts b/src/hooks/useMotivatorLive.ts deleted file mode 100644 index d522459..0000000 --- a/src/hooks/useMotivatorLive.ts +++ /dev/null @@ -1,33 +0,0 @@ -'use client'; - -import { useLive, type LiveEvent } from './useLive'; - -export type MotivatorLiveEvent = LiveEvent; - -interface UseMotivatorLiveOptions { - sessionId: string; - currentUserId?: string; - enabled?: boolean; - onEvent?: (event: MotivatorLiveEvent) => void; -} - -interface UseMotivatorLiveReturn { - isConnected: boolean; - lastEvent: MotivatorLiveEvent | null; - error: string | null; -} - -export function useMotivatorLive({ - sessionId, - currentUserId, - enabled = true, - onEvent, -}: UseMotivatorLiveOptions): UseMotivatorLiveReturn { - return useLive({ - sessionId, - apiPath: 'motivators', - currentUserId, - enabled, - onEvent, - }); -} diff --git a/src/hooks/useSessionLive.ts b/src/hooks/useSessionLive.ts deleted file mode 100644 index 7aca652..0000000 --- a/src/hooks/useSessionLive.ts +++ /dev/null @@ -1,33 +0,0 @@ -'use client'; - -import { useLive, type LiveEvent } from './useLive'; - -interface UseSessionLiveOptions { - sessionId: string; - currentUserId?: string; - enabled?: boolean; - onEvent?: (event: LiveEvent) => void; -} - -interface UseSessionLiveReturn { - isConnected: boolean; - lastEvent: LiveEvent | null; - error: string | null; -} - -export function useSessionLive({ - sessionId, - currentUserId, - enabled = true, - onEvent, -}: UseSessionLiveOptions): UseSessionLiveReturn { - return useLive({ - sessionId, - apiPath: 'sessions', - currentUserId, - enabled, - onEvent, - }); -} - -export type { LiveEvent }; diff --git a/src/hooks/useWeatherLive.ts b/src/hooks/useWeatherLive.ts deleted file mode 100644 index cdc1e98..0000000 --- a/src/hooks/useWeatherLive.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { useLive, type LiveEvent } from './useLive'; - -interface UseWeatherLiveOptions { - sessionId: string; - currentUserId?: string; - enabled?: boolean; - onEvent?: (event: WeatherLiveEvent) => void; -} - -interface UseWeatherLiveReturn { - isConnected: boolean; - lastEvent: WeatherLiveEvent | null; - error: string | null; -} - -export type WeatherLiveEvent = LiveEvent; - -export function useWeatherLive({ - sessionId, - currentUserId, - enabled = true, - onEvent, -}: UseWeatherLiveOptions): UseWeatherLiveReturn { - return useLive({ - sessionId, - apiPath: 'weather', - currentUserId, - enabled, - onEvent, - }); -} diff --git a/src/hooks/useWeeklyCheckInLive.ts b/src/hooks/useWeeklyCheckInLive.ts deleted file mode 100644 index 474c9ad..0000000 --- a/src/hooks/useWeeklyCheckInLive.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { useLive, type LiveEvent } from './useLive'; - -interface UseWeeklyCheckInLiveOptions { - sessionId: string; - currentUserId?: string; - enabled?: boolean; - onEvent?: (event: WeeklyCheckInLiveEvent) => void; -} - -interface UseWeeklyCheckInLiveReturn { - isConnected: boolean; - lastEvent: WeeklyCheckInLiveEvent | null; - error: string | null; -} - -export type WeeklyCheckInLiveEvent = LiveEvent; - -export function useWeeklyCheckInLive({ - sessionId, - currentUserId, - enabled = true, - onEvent, -}: UseWeeklyCheckInLiveOptions): UseWeeklyCheckInLiveReturn { - return useLive({ - sessionId, - apiPath: 'weekly-checkin', - currentUserId, - enabled, - onEvent, - }); -} diff --git a/src/hooks/useYearReviewLive.ts b/src/hooks/useYearReviewLive.ts deleted file mode 100644 index e6f3bc7..0000000 --- a/src/hooks/useYearReviewLive.ts +++ /dev/null @@ -1,33 +0,0 @@ -'use client'; - -import { useLive, type LiveEvent } from './useLive'; - -interface UseYearReviewLiveOptions { - sessionId: string; - currentUserId?: string; - enabled?: boolean; - onEvent?: (event: YearReviewLiveEvent) => void; -} - -interface UseYearReviewLiveReturn { - isConnected: boolean; - lastEvent: YearReviewLiveEvent | null; - error: string | null; -} - -export type YearReviewLiveEvent = LiveEvent; - -export function useYearReviewLive({ - sessionId, - currentUserId, - enabled = true, - onEvent, -}: UseYearReviewLiveOptions): UseYearReviewLiveReturn { - return useLive({ - sessionId, - apiPath: 'year-review', - currentUserId, - enabled, - onEvent, - }); -} diff --git a/src/lib/share-utils.ts b/src/lib/share-utils.ts index c3a722c..4e47abb 100644 --- a/src/lib/share-utils.ts +++ b/src/lib/share-utils.ts @@ -2,6 +2,21 @@ * Shared utilities for share modals across workshop types. */ +import type { ShareRole } from '@prisma/client'; + +export interface ShareUser { + id: string; + name: string | null; + email: string; +} + +export interface Share { + id: string; + role: ShareRole; + user: ShareUser; + createdAt?: Date; +} + export interface TeamMemberUser { id: string; email: string;