page wrapper
This commit is contained in:
@@ -1,139 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import TeamDetailTemplate from '@/templates/TeamDetailTemplate';
|
||||
import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId';
|
||||
|
||||
// Shared state components
|
||||
import { StateContainer } from '@/components/shared/state/StateContainer';
|
||||
import { useTeamDetails } from '@/hooks/team/useTeamDetails';
|
||||
import { useTeamMembers } from '@/hooks/team/useTeamMembers';
|
||||
import { useInject } from '@/lib/di/hooks/useInject';
|
||||
import { TEAM_SERVICE_TOKEN } from '@/lib/di/tokens';
|
||||
import { Users } from 'lucide-react';
|
||||
|
||||
type Tab = 'overview' | 'roster' | 'standings' | 'admin';
|
||||
|
||||
export default function TeamDetailInteractive() {
|
||||
const params = useParams();
|
||||
const teamId = params.id as string;
|
||||
const router = useRouter();
|
||||
const currentDriverId = useEffectiveDriverId();
|
||||
const teamService = useInject(TEAM_SERVICE_TOKEN);
|
||||
|
||||
const [activeTab, setActiveTab] = useState<Tab>('overview');
|
||||
|
||||
// Fetch team details using DI + React-Query
|
||||
const { data: teamDetails, isLoading: teamLoading, error: teamError, retry: teamRetry } = useTeamDetails(teamId, currentDriverId);
|
||||
|
||||
// Fetch team members using DI + React-Query
|
||||
const { data: memberships, isLoading: membersLoading, error: membersError, retry: membersRetry } = useTeamMembers(
|
||||
teamId,
|
||||
currentDriverId,
|
||||
teamDetails?.ownerId || ''
|
||||
);
|
||||
|
||||
const isLoading = teamLoading || membersLoading;
|
||||
const error = teamError || membersError;
|
||||
const retry = async () => {
|
||||
await teamRetry();
|
||||
await membersRetry();
|
||||
};
|
||||
|
||||
// Determine admin status
|
||||
const isAdmin = teamDetails?.isOwner ||
|
||||
(memberships || []).some((m: any) => m.driverId === currentDriverId && (m.role === 'manager' || m.role === 'owner'));
|
||||
|
||||
const handleUpdate = () => {
|
||||
retry();
|
||||
};
|
||||
|
||||
const handleRemoveMember = async (driverId: string) => {
|
||||
if (!confirm('Are you sure you want to remove this member?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const performer = await teamService.getMembership(teamId, currentDriverId);
|
||||
if (!performer || (performer.role !== 'owner' && performer.role !== 'manager')) {
|
||||
throw new Error('Only owners or admins can remove members');
|
||||
}
|
||||
|
||||
const membership = await teamService.getMembership(teamId, driverId);
|
||||
if (!membership) {
|
||||
throw new Error('Member not found');
|
||||
}
|
||||
if (membership.role === 'owner') {
|
||||
throw new Error('Cannot remove the team owner');
|
||||
}
|
||||
|
||||
await teamService.removeMembership(teamId, driverId);
|
||||
handleUpdate();
|
||||
} catch (error) {
|
||||
alert(error instanceof Error ? error.message : 'Failed to remove member');
|
||||
}
|
||||
};
|
||||
|
||||
const handleChangeRole = async (driverId: string, newRole: 'owner' | 'admin' | 'member') => {
|
||||
try {
|
||||
const performer = await teamService.getMembership(teamId, currentDriverId);
|
||||
if (!performer || (performer.role !== 'owner' && performer.role !== 'manager')) {
|
||||
throw new Error('Only owners or admins can update roles');
|
||||
}
|
||||
|
||||
const membership = await teamService.getMembership(teamId, driverId);
|
||||
if (!membership) {
|
||||
throw new Error('Member not found');
|
||||
}
|
||||
if (membership.role === 'owner') {
|
||||
throw new Error('Cannot change the owner role');
|
||||
}
|
||||
|
||||
// Convert 'admin' to 'manager' for the service
|
||||
const serviceRole = newRole === 'admin' ? 'manager' : newRole;
|
||||
await teamService.updateMembership(teamId, driverId, serviceRole);
|
||||
handleUpdate();
|
||||
} catch (error) {
|
||||
alert(error instanceof Error ? error.message : 'Failed to change role');
|
||||
}
|
||||
};
|
||||
|
||||
const handleGoBack = () => {
|
||||
window.history.back();
|
||||
};
|
||||
|
||||
return (
|
||||
<StateContainer
|
||||
data={teamDetails}
|
||||
isLoading={isLoading}
|
||||
error={error}
|
||||
retry={retry}
|
||||
config={{
|
||||
loading: { variant: 'skeleton', message: 'Loading team details...' },
|
||||
error: { variant: 'full-screen' },
|
||||
empty: {
|
||||
icon: Users,
|
||||
title: 'Team not found',
|
||||
description: 'The team may have been deleted or you may not have access',
|
||||
action: { label: 'Back to Teams', onClick: () => router.push('/teams') }
|
||||
}
|
||||
}}
|
||||
>
|
||||
{(teamData) => (
|
||||
<TeamDetailTemplate
|
||||
team={teamData!}
|
||||
memberships={memberships || []}
|
||||
activeTab={activeTab}
|
||||
loading={isLoading}
|
||||
isAdmin={isAdmin}
|
||||
onTabChange={setActiveTab}
|
||||
onUpdate={handleUpdate}
|
||||
onRemoveMember={handleRemoveMember}
|
||||
onChangeRole={handleChangeRole}
|
||||
onGoBack={handleGoBack}
|
||||
/>
|
||||
)}
|
||||
</StateContainer>
|
||||
);
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import TeamDetailTemplate from '@/templates/TeamDetailTemplate';
|
||||
import type { TeamDetailsViewModel } from '@/lib/view-models/TeamDetailsViewModel';
|
||||
import type { TeamMemberViewModel } from '@/lib/view-models/TeamMemberViewModel';
|
||||
|
||||
// This is a server component that can be used for static rendering
|
||||
// It receives pre-fetched data and renders the template
|
||||
|
||||
interface TeamDetailStaticProps {
|
||||
team: TeamDetailsViewModel | null;
|
||||
memberships: TeamMemberViewModel[];
|
||||
currentDriverId: string;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
export default function TeamDetailStatic({
|
||||
team,
|
||||
memberships,
|
||||
currentDriverId,
|
||||
isLoading = false
|
||||
}: TeamDetailStaticProps) {
|
||||
// Determine admin status
|
||||
const isAdmin = team ? (
|
||||
team.isOwner ||
|
||||
memberships.some((m) => m.driverId === currentDriverId && (m.role === 'manager' || m.role === 'owner'))
|
||||
) : false;
|
||||
|
||||
// For static rendering, we don't have interactive state
|
||||
// So we pass empty values and handlers that won't be used
|
||||
return (
|
||||
<TeamDetailTemplate
|
||||
team={team}
|
||||
memberships={memberships}
|
||||
activeTab="overview"
|
||||
loading={isLoading}
|
||||
isAdmin={isAdmin}
|
||||
onTabChange={() => {}}
|
||||
onUpdate={() => {}}
|
||||
onRemoveMember={() => {}}
|
||||
onChangeRole={() => {}}
|
||||
onGoBack={() => {}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,3 +1,102 @@
|
||||
import TeamDetailInteractive from './TeamDetailInteractive';
|
||||
import { PageWrapper } from '@/components/shared/state/PageWrapper';
|
||||
import { PageDataFetcher } from '@/lib/page/PageDataFetcher';
|
||||
import { TeamService } from '@/lib/services/teams/TeamService';
|
||||
import { TeamsApiClient } from '@/lib/api/teams/TeamsApiClient';
|
||||
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
|
||||
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
|
||||
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { TeamDetailsViewModel } from '@/lib/view-models/TeamDetailsViewModel';
|
||||
import { TeamMemberViewModel } from '@/lib/view-models/TeamMemberViewModel';
|
||||
import TeamDetailTemplate from '@/templates/TeamDetailTemplate';
|
||||
|
||||
export default TeamDetailInteractive;
|
||||
// Template wrapper to adapt TeamDetailTemplate for SSR
|
||||
interface TeamDetailData {
|
||||
team: TeamDetailsViewModel;
|
||||
memberships: TeamMemberViewModel[];
|
||||
isAdmin: boolean;
|
||||
}
|
||||
|
||||
function TeamDetailTemplateWrapper({ data }: { data: TeamDetailData }) {
|
||||
return (
|
||||
<TeamDetailTemplate
|
||||
team={data.team}
|
||||
memberships={data.memberships}
|
||||
activeTab="overview"
|
||||
loading={false}
|
||||
isAdmin={data.isAdmin}
|
||||
// Event handlers are no-ops for SSR (client will handle real interactions)
|
||||
onTabChange={() => {}}
|
||||
onUpdate={() => {}}
|
||||
onRemoveMember={() => {}}
|
||||
onChangeRole={() => {}}
|
||||
onGoBack={() => {}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default async function Page({ params }: { params: { id: string } }) {
|
||||
// Validate params
|
||||
if (!params.id) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
// Fetch data using PageDataFetcher.fetchManual
|
||||
const data = await PageDataFetcher.fetchManual(async () => {
|
||||
// Manual dependency creation
|
||||
const baseUrl = getWebsiteApiBaseUrl();
|
||||
const logger = new ConsoleLogger();
|
||||
const errorReporter = new EnhancedErrorReporter(logger, {
|
||||
showUserNotifications: true,
|
||||
logToConsole: true,
|
||||
reportToExternal: process.env.NODE_ENV === 'production',
|
||||
});
|
||||
|
||||
// Create API client
|
||||
const teamsApiClient = new TeamsApiClient(baseUrl, errorReporter, logger);
|
||||
|
||||
// Create service
|
||||
const service = new TeamService(teamsApiClient);
|
||||
|
||||
// For server-side, we need a current driver ID
|
||||
// This would typically come from session, but for server components we'll use a placeholder
|
||||
const currentDriverId = ''; // Placeholder - would need session handling
|
||||
|
||||
// Fetch team details
|
||||
const teamData = await service.getTeamDetails(params.id, currentDriverId);
|
||||
|
||||
if (!teamData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Fetch team members
|
||||
const membersData = await service.getTeamMembers(params.id, currentDriverId, teamData.ownerId || '');
|
||||
|
||||
// Determine admin status
|
||||
const isAdmin = teamData.isOwner ||
|
||||
(membersData || []).some((m: any) => m.driverId === currentDriverId && (m.role === 'manager' || m.role === 'owner'));
|
||||
|
||||
return {
|
||||
team: teamData,
|
||||
memberships: membersData || [],
|
||||
isAdmin,
|
||||
};
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return (
|
||||
<PageWrapper
|
||||
data={data}
|
||||
Template={TeamDetailTemplateWrapper}
|
||||
loading={{ variant: 'skeleton', message: 'Loading team details...' }}
|
||||
errorConfig={{ variant: 'full-screen' }}
|
||||
empty={{
|
||||
title: 'Team not found',
|
||||
description: 'The team you are looking for does not exist or has been removed.',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user