website refactor
This commit is contained in:
@@ -1,47 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import type { UserDto, DashboardStats, UserListResponse } from '@/lib/api/admin/AdminApiClient';
|
||||
import { AdminUserViewModel, DashboardStatsViewModel, UserListViewModel } from './AdminUserViewModel';
|
||||
|
||||
/**
|
||||
* AdminViewModelPresenter
|
||||
*
|
||||
* Presenter layer for transforming API DTOs to ViewModels.
|
||||
* Runs in client code only ('use client').
|
||||
* Deterministic, side-effect free transformations.
|
||||
*/
|
||||
export class AdminViewModelPresenter {
|
||||
/**
|
||||
* Map a single user DTO to a View Model
|
||||
*/
|
||||
static mapUser(dto: UserDto): AdminUserViewModel {
|
||||
return new AdminUserViewModel(dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map an array of user DTOs to View Models
|
||||
*/
|
||||
static mapUsers(dtos: UserDto[]): AdminUserViewModel[] {
|
||||
return dtos.map(dto => this.mapUser(dto));
|
||||
}
|
||||
|
||||
/**
|
||||
* Map dashboard stats DTO to View Model
|
||||
*/
|
||||
static mapDashboardStats(dto: DashboardStats): DashboardStatsViewModel {
|
||||
return new DashboardStatsViewModel(dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map user list response to View Model
|
||||
*/
|
||||
static mapUserList(response: UserListResponse): UserListViewModel {
|
||||
return new UserListViewModel({
|
||||
users: response.users,
|
||||
total: response.total,
|
||||
page: response.page,
|
||||
limit: response.limit,
|
||||
totalPages: response.totalPages,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
import type { DashboardPageDto } from '@/lib/page-queries/DashboardPageDto';
|
||||
import type { DashboardViewData } from '@/templates/DashboardViewData';
|
||||
import {
|
||||
formatDashboardDate,
|
||||
formatRating,
|
||||
formatRank,
|
||||
formatConsistency,
|
||||
formatRaceCount,
|
||||
formatFriendCount,
|
||||
formatLeaguePosition,
|
||||
formatPoints,
|
||||
formatTotalDrivers,
|
||||
} from '@/lib/display-objects/DashboardDisplay';
|
||||
|
||||
/**
|
||||
* DashboardPresenter - Client-side presenter for dashboard page
|
||||
* Transforms Page DTO into ViewData for the template
|
||||
* Deterministic; no hooks; no side effects
|
||||
*/
|
||||
export class DashboardPresenter {
|
||||
static createViewData(pageDto: DashboardPageDto): DashboardViewData {
|
||||
return {
|
||||
currentDriver: {
|
||||
name: pageDto.currentDriver?.name || '',
|
||||
avatarUrl: pageDto.currentDriver?.avatarUrl || '',
|
||||
country: pageDto.currentDriver?.country || '',
|
||||
rating: pageDto.currentDriver ? formatRating(pageDto.currentDriver.rating) : '0.0',
|
||||
rank: pageDto.currentDriver ? formatRank(pageDto.currentDriver.globalRank) : '0',
|
||||
totalRaces: pageDto.currentDriver ? formatRaceCount(pageDto.currentDriver.totalRaces) : '0',
|
||||
wins: pageDto.currentDriver ? formatRaceCount(pageDto.currentDriver.wins) : '0',
|
||||
podiums: pageDto.currentDriver ? formatRaceCount(pageDto.currentDriver.podiums) : '0',
|
||||
consistency: pageDto.currentDriver ? formatConsistency(pageDto.currentDriver.consistency) : '0%',
|
||||
},
|
||||
nextRace: pageDto.nextRace ? (() => {
|
||||
const dateInfo = formatDashboardDate(new Date(pageDto.nextRace.scheduledAt));
|
||||
return {
|
||||
id: pageDto.nextRace.id,
|
||||
track: pageDto.nextRace.track,
|
||||
car: pageDto.nextRace.car,
|
||||
scheduledAt: pageDto.nextRace.scheduledAt,
|
||||
formattedDate: dateInfo.date,
|
||||
formattedTime: dateInfo.time,
|
||||
timeUntil: dateInfo.relative,
|
||||
isMyLeague: pageDto.nextRace.isMyLeague,
|
||||
};
|
||||
})() : null,
|
||||
upcomingRaces: pageDto.upcomingRaces.map((race) => {
|
||||
const dateInfo = formatDashboardDate(new Date(race.scheduledAt));
|
||||
return {
|
||||
id: race.id,
|
||||
track: race.track,
|
||||
car: race.car,
|
||||
scheduledAt: race.scheduledAt,
|
||||
formattedDate: dateInfo.date,
|
||||
formattedTime: dateInfo.time,
|
||||
timeUntil: dateInfo.relative,
|
||||
isMyLeague: race.isMyLeague,
|
||||
};
|
||||
}),
|
||||
leagueStandings: pageDto.leagueStandingsSummaries.map((standing) => ({
|
||||
leagueId: standing.leagueId,
|
||||
leagueName: standing.leagueName,
|
||||
position: formatLeaguePosition(standing.position),
|
||||
points: formatPoints(standing.points),
|
||||
totalDrivers: formatTotalDrivers(standing.totalDrivers),
|
||||
})),
|
||||
feedItems: pageDto.feedSummary.items.map((item) => ({
|
||||
id: item.id,
|
||||
type: item.type,
|
||||
headline: item.headline,
|
||||
body: item.body,
|
||||
timestamp: item.timestamp,
|
||||
formattedTime: formatDashboardDate(new Date(item.timestamp)).relative,
|
||||
ctaHref: item.ctaHref,
|
||||
ctaLabel: item.ctaLabel,
|
||||
})),
|
||||
friends: pageDto.friends.map((friend) => ({
|
||||
id: friend.id,
|
||||
name: friend.name,
|
||||
avatarUrl: friend.avatarUrl,
|
||||
country: friend.country,
|
||||
})),
|
||||
activeLeaguesCount: formatRaceCount(pageDto.activeLeaguesCount),
|
||||
friendCount: formatFriendCount(pageDto.friends.length),
|
||||
hasUpcomingRaces: pageDto.upcomingRaces.length > 0,
|
||||
hasLeagueStandings: pageDto.leagueStandingsSummaries.length > 0,
|
||||
hasFeedItems: pageDto.feedSummary.items.length > 0,
|
||||
hasFriends: pageDto.friends.length > 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
212
apps/website/lib/presenters/LeagueDetailPresenter.ts
Normal file
212
apps/website/lib/presenters/LeagueDetailPresenter.ts
Normal file
@@ -0,0 +1,212 @@
|
||||
'use client';
|
||||
|
||||
/**
|
||||
* LeagueDetailPresenter
|
||||
* Pure client-side presenter for LeagueDetailTemplate
|
||||
* Converts ViewModels to ViewData and removes DisplayObject usage
|
||||
*/
|
||||
|
||||
import type { Presenter } from '@/lib/contracts/presenters/Presenter';
|
||||
import type { DriverSummaryData, LeagueDetailViewData, LeagueInfoData, LiveRaceData, SponsorInfo, SponsorMetric } from '@/lib/view-data/LeagueDetailViewData';
|
||||
import type { DriverSummary, LeagueDetailPageViewModel } from '@/lib/view-models/LeagueDetailPageViewModel';
|
||||
import type { RaceViewModel } from '@/lib/view-models/RaceViewModel';
|
||||
import { Eye, TrendingUp, Users, Zap } from 'lucide-react';
|
||||
|
||||
interface SponsorshipSlot {
|
||||
tier: 'main' | 'secondary';
|
||||
available: boolean;
|
||||
price: number;
|
||||
benefits: string[];
|
||||
}
|
||||
|
||||
interface LeagueDetailInput {
|
||||
viewModel: LeagueDetailPageViewModel;
|
||||
leagueId: string;
|
||||
isSponsor: boolean;
|
||||
}
|
||||
|
||||
// League role display data (moved from LeagueRoleDisplay)
|
||||
const leagueRoleDisplay = {
|
||||
owner: {
|
||||
text: 'Owner',
|
||||
badgeClasses: 'bg-yellow-500/10 text-yellow-500 border-yellow-500/30',
|
||||
},
|
||||
admin: {
|
||||
text: 'Admin',
|
||||
badgeClasses: 'bg-purple-500/10 text-purple-400 border-purple-500/30',
|
||||
},
|
||||
steward: {
|
||||
text: 'Steward',
|
||||
badgeClasses: 'bg-blue-500/10 text-blue-400 border-blue-500/30',
|
||||
},
|
||||
member: {
|
||||
text: 'Member',
|
||||
badgeClasses: 'bg-primary-blue/10 text-primary-blue border-primary-blue/30',
|
||||
},
|
||||
} as const;
|
||||
|
||||
export class LeagueDetailPresenter implements Presenter<LeagueDetailInput, LeagueDetailViewData> {
|
||||
/**
|
||||
* Convert RaceViewModel[] to LiveRaceData[]
|
||||
*/
|
||||
private static convertRunningRaces(races: RaceViewModel[]): LiveRaceData[] {
|
||||
return races.map(race => ({
|
||||
id: race.id,
|
||||
name: race.name,
|
||||
date: race.date,
|
||||
registeredCount: race.registeredCount,
|
||||
strengthOfField: race.strengthOfField,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert DriverSummary to DriverSummaryData with role badge info
|
||||
*/
|
||||
private static convertDriverSummary(
|
||||
summary: DriverSummary | null,
|
||||
role: 'owner' | 'admin' | 'steward' | 'member',
|
||||
leagueId: string
|
||||
): DriverSummaryData | null {
|
||||
if (!summary) return null;
|
||||
|
||||
const roleDisplay = leagueRoleDisplay[role];
|
||||
|
||||
return {
|
||||
driverId: summary.driver.id,
|
||||
driverName: summary.driver.name,
|
||||
avatarUrl: summary.driver.avatarUrl,
|
||||
rating: summary.rating,
|
||||
rank: summary.rank,
|
||||
roleBadgeText: roleDisplay.text,
|
||||
roleBadgeClasses: roleDisplay.badgeClasses,
|
||||
profileUrl: `/drivers/${summary.driver.id}?from=league-management&leagueId=${leagueId}`,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform input to output
|
||||
*/
|
||||
present(input: LeagueDetailInput): LeagueDetailViewData {
|
||||
const { viewModel, leagueId, isSponsor } = input;
|
||||
|
||||
// Build info data
|
||||
const info: LeagueInfoData = {
|
||||
name: viewModel.name,
|
||||
description: viewModel.description ?? '',
|
||||
membersCount: viewModel.memberships.length,
|
||||
racesCount: viewModel.completedRacesCount,
|
||||
avgSOF: viewModel.averageSOF,
|
||||
structure: `Solo • ${viewModel.settings.maxDrivers ?? 32} max`,
|
||||
scoring: viewModel.scoringConfig?.scoringPresetName ?? 'Standard',
|
||||
createdAt: viewModel.createdAt,
|
||||
discordUrl: viewModel.socialLinks?.discordUrl,
|
||||
youtubeUrl: viewModel.socialLinks?.youtubeUrl,
|
||||
websiteUrl: viewModel.socialLinks?.websiteUrl,
|
||||
};
|
||||
|
||||
// Convert running races
|
||||
const runningRaces = LeagueDetailPresenter.convertRunningRaces(viewModel.runningRaces);
|
||||
|
||||
// Convert sponsors
|
||||
const sponsors: SponsorInfo[] = viewModel.sponsors.map(s => ({
|
||||
id: s.id,
|
||||
name: s.name,
|
||||
tier: s.tier,
|
||||
logoUrl: s.logoUrl,
|
||||
websiteUrl: s.websiteUrl,
|
||||
tagline: s.tagline,
|
||||
}));
|
||||
|
||||
// Convert driver summaries with role badges
|
||||
const ownerSummary = LeagueDetailPresenter.convertDriverSummary(viewModel.ownerSummary, 'owner', leagueId);
|
||||
const adminSummaries = viewModel.adminSummaries
|
||||
.map(s => LeagueDetailPresenter.convertDriverSummary(s, 'admin', leagueId))
|
||||
.filter((s): s is DriverSummaryData => s !== null);
|
||||
const stewardSummaries = viewModel.stewardSummaries
|
||||
.map(s => LeagueDetailPresenter.convertDriverSummary(s, 'steward', leagueId))
|
||||
.filter((s): s is DriverSummaryData => s !== null);
|
||||
|
||||
// Sponsor insights (only if sponsor mode)
|
||||
const sponsorInsights = isSponsor ? {
|
||||
avgViewsPerRace: viewModel.sponsorInsights.avgViewsPerRace,
|
||||
engagementRate: viewModel.sponsorInsights.engagementRate,
|
||||
estimatedReach: viewModel.sponsorInsights.estimatedReach,
|
||||
tier: viewModel.sponsorInsights.tier,
|
||||
trustScore: viewModel.sponsorInsights.trustScore,
|
||||
discordMembers: viewModel.sponsorInsights.discordMembers,
|
||||
monthlyActivity: viewModel.sponsorInsights.monthlyActivity,
|
||||
mainSponsorAvailable: viewModel.sponsorInsights.mainSponsorAvailable,
|
||||
secondarySlotsAvailable: viewModel.sponsorInsights.secondarySlotsAvailable,
|
||||
mainSponsorPrice: viewModel.sponsorInsights.mainSponsorPrice,
|
||||
secondaryPrice: viewModel.sponsorInsights.secondaryPrice,
|
||||
totalImpressions: viewModel.sponsorInsights.totalImpressions,
|
||||
metrics: [
|
||||
{
|
||||
icon: Eye,
|
||||
label: 'Avg Views/Race',
|
||||
value: viewModel.sponsorInsights.avgViewsPerRace,
|
||||
color: 'text-primary-blue',
|
||||
},
|
||||
{
|
||||
icon: TrendingUp,
|
||||
label: 'Engagement',
|
||||
value: viewModel.sponsorInsights.engagementRate,
|
||||
color: 'text-performance-green',
|
||||
},
|
||||
{
|
||||
icon: Users,
|
||||
label: 'Est. Reach',
|
||||
value: viewModel.sponsorInsights.estimatedReach,
|
||||
color: 'text-purple-400',
|
||||
},
|
||||
{
|
||||
icon: Zap,
|
||||
label: 'Avg SOF',
|
||||
value: viewModel.averageSOF ?? '—',
|
||||
color: 'text-warning-amber',
|
||||
},
|
||||
],
|
||||
slots: [
|
||||
{
|
||||
tier: 'main' as const,
|
||||
available: viewModel.sponsorInsights.mainSponsorAvailable,
|
||||
price: viewModel.sponsorInsights.mainSponsorPrice,
|
||||
benefits: ['Hood placement', 'League banner', 'Prominent logo'],
|
||||
},
|
||||
{
|
||||
tier: 'secondary' as const,
|
||||
available: viewModel.sponsorInsights.secondarySlotsAvailable > 0,
|
||||
price: viewModel.sponsorInsights.secondaryPrice,
|
||||
benefits: ['Side logo placement', 'League page listing'],
|
||||
},
|
||||
{
|
||||
tier: 'secondary' as const,
|
||||
available: viewModel.sponsorInsights.secondarySlotsAvailable > 1,
|
||||
price: viewModel.sponsorInsights.secondaryPrice,
|
||||
benefits: ['Side logo placement', 'League page listing'],
|
||||
},
|
||||
],
|
||||
} : null;
|
||||
|
||||
return {
|
||||
leagueId: viewModel.id,
|
||||
name: viewModel.name,
|
||||
description: viewModel.description ?? '',
|
||||
info,
|
||||
runningRaces,
|
||||
sponsors,
|
||||
ownerSummary,
|
||||
adminSummaries,
|
||||
stewardSummaries,
|
||||
sponsorInsights,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Static helper for backward compatibility
|
||||
*/
|
||||
static createViewData(viewModel: LeagueDetailPageViewModel, leagueId: string, isSponsor: boolean): LeagueDetailViewData {
|
||||
const presenter = new LeagueDetailPresenter();
|
||||
return presenter.present({ viewModel, leagueId, isSponsor });
|
||||
}
|
||||
}
|
||||
103
apps/website/lib/presenters/LeagueStandingsPresenter.ts
Normal file
103
apps/website/lib/presenters/LeagueStandingsPresenter.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
'use client';
|
||||
|
||||
/**
|
||||
* LeagueStandingsPresenter
|
||||
* Pure client-side presenter for LeagueStandingsTemplate
|
||||
* Converts ViewModels to ViewData
|
||||
*/
|
||||
|
||||
import type { Presenter } from '@/lib/contracts/presenters/Presenter';
|
||||
import type { LeagueMembership } from '@/lib/types/LeagueMembership';
|
||||
import type { DriverData, LeagueMembershipData, LeagueStandingsViewData, StandingEntryData } from '@/lib/view-data/LeagueStandingsViewData';
|
||||
import type { DriverViewModel } from '@/lib/view-models/DriverViewModel';
|
||||
import type { StandingEntryViewModel } from '@/lib/view-models/StandingEntryViewModel';
|
||||
|
||||
interface LeagueStandingsInput {
|
||||
standings: StandingEntryViewModel[];
|
||||
drivers: DriverViewModel[];
|
||||
memberships: LeagueMembership[];
|
||||
leagueId: string;
|
||||
currentDriverId: string | null;
|
||||
isAdmin: boolean;
|
||||
}
|
||||
|
||||
export class LeagueStandingsPresenter implements Presenter<LeagueStandingsInput, LeagueStandingsViewData> {
|
||||
/**
|
||||
* Convert StandingEntryViewModel to StandingEntryData
|
||||
*/
|
||||
private static convertStanding(standing: StandingEntryViewModel): StandingEntryData {
|
||||
return {
|
||||
driverId: standing.driverId,
|
||||
position: standing.position,
|
||||
totalPoints: standing.points,
|
||||
racesFinished: standing.races,
|
||||
racesStarted: standing.races,
|
||||
avgFinish: null, // Not available in current ViewModel
|
||||
penaltyPoints: 0, // Not available in current ViewModel
|
||||
bonusPoints: 0, // Not available in current ViewModel
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert DriverViewModel to DriverData
|
||||
*/
|
||||
private static convertDriver(driver: DriverViewModel): DriverData {
|
||||
return {
|
||||
id: driver.id,
|
||||
name: driver.name,
|
||||
avatarUrl: driver.avatarUrl,
|
||||
iracingId: driver.iracingId,
|
||||
rating: driver.rating,
|
||||
country: driver.country,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert LeagueMembership to LeagueMembershipData
|
||||
*/
|
||||
private static convertMembership(membership: LeagueMembership): LeagueMembershipData {
|
||||
return {
|
||||
driverId: membership.driverId,
|
||||
leagueId: membership.leagueId,
|
||||
role: membership.role,
|
||||
joinedAt: membership.joinedAt,
|
||||
status: membership.status,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform input to output
|
||||
*/
|
||||
present(input: LeagueStandingsInput): LeagueStandingsViewData {
|
||||
return {
|
||||
standings: input.standings.map(s => LeagueStandingsPresenter.convertStanding(s)),
|
||||
drivers: input.drivers.map(d => LeagueStandingsPresenter.convertDriver(d)),
|
||||
memberships: input.memberships.map(m => LeagueStandingsPresenter.convertMembership(m)),
|
||||
leagueId: input.leagueId,
|
||||
currentDriverId: input.currentDriverId,
|
||||
isAdmin: input.isAdmin,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Static helper for backward compatibility
|
||||
*/
|
||||
static createViewData(
|
||||
standings: StandingEntryViewModel[],
|
||||
drivers: DriverViewModel[],
|
||||
memberships: LeagueMembership[],
|
||||
leagueId: string,
|
||||
currentDriverId: string | null,
|
||||
isAdmin: boolean
|
||||
): LeagueStandingsViewData {
|
||||
const presenter = new LeagueStandingsPresenter();
|
||||
return presenter.present({
|
||||
standings,
|
||||
drivers,
|
||||
memberships,
|
||||
leagueId,
|
||||
currentDriverId,
|
||||
isAdmin,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ProfileLeaguesPageDto } from '@/lib/page-queries/page-queries/ProfileLeaguesPageQuery';
|
||||
import type { ProfileLeaguesViewData } from '@/templates/view-data/ProfileLeaguesViewData';
|
||||
import type { ProfileLeaguesViewData } from '@/lib/view-data/ProfileLeaguesViewData';
|
||||
|
||||
/**
|
||||
* Presenter for Profile Leagues page
|
||||
|
||||
23
apps/website/lib/presenters/SessionPresenter.ts
Normal file
23
apps/website/lib/presenters/SessionPresenter.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
'use client';
|
||||
|
||||
import type { AuthSessionDTO } from '@/lib/types/generated/AuthSessionDTO';
|
||||
import { SessionViewModel } from '@/lib/view-models/SessionViewModel';
|
||||
|
||||
/**
|
||||
* Session Presenter
|
||||
*
|
||||
* Converts AuthSessionDTO to SessionViewModel for client-side presentation.
|
||||
* Pure and deterministic - no side effects.
|
||||
*/
|
||||
export class SessionPresenter {
|
||||
/**
|
||||
* Present session data as a view model
|
||||
*/
|
||||
present(sessionDto: AuthSessionDTO | null): SessionViewModel | null {
|
||||
if (!sessionDto || !sessionDto.user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new SessionViewModel(sessionDto.user);
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import type { TeamDetailPageDto } from '@/lib/page-queries/TeamDetailPageQuery';
|
||||
import type { TeamDetailViewData, TeamDetailData, TeamMemberData } from '@/templates/TeamDetailViewData';
|
||||
|
||||
/**
|
||||
* TeamDetailPresenter - Client-side presenter for team detail page
|
||||
* Transforms PageQuery DTO into ViewData for the template
|
||||
* Deterministic; no hooks; no side effects
|
||||
*/
|
||||
export class TeamDetailPresenter {
|
||||
static createViewData(pageDto: TeamDetailPageDto): TeamDetailViewData {
|
||||
const team: TeamDetailData = {
|
||||
id: pageDto.team.id,
|
||||
name: pageDto.team.name,
|
||||
tag: pageDto.team.tag,
|
||||
description: pageDto.team.description,
|
||||
ownerId: pageDto.team.ownerId,
|
||||
leagues: pageDto.team.leagues,
|
||||
createdAt: pageDto.team.createdAt,
|
||||
specialization: pageDto.team.specialization,
|
||||
region: pageDto.team.region,
|
||||
languages: pageDto.team.languages,
|
||||
category: pageDto.team.category,
|
||||
membership: pageDto.team.membership,
|
||||
canManage: pageDto.team.canManage,
|
||||
};
|
||||
|
||||
const memberships: TeamMemberData[] = pageDto.memberships.map(membership => ({
|
||||
driverId: membership.driverId,
|
||||
driverName: membership.driverName,
|
||||
role: membership.role,
|
||||
joinedAt: membership.joinedAt,
|
||||
isActive: membership.isActive,
|
||||
avatarUrl: membership.avatarUrl,
|
||||
}));
|
||||
|
||||
// Calculate isAdmin based on current driver's role
|
||||
const currentDriverMembership = memberships.find(m => m.driverId === pageDto.currentDriverId);
|
||||
const isAdmin = currentDriverMembership?.role === 'owner' || currentDriverMembership?.role === 'manager';
|
||||
|
||||
return {
|
||||
team,
|
||||
memberships,
|
||||
currentDriverId: pageDto.currentDriverId,
|
||||
isAdmin,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { TeamsPageDto } from '@/lib/page-queries/TeamsPageQuery';
|
||||
import type { TeamsPageDto } from '@/lib/page-queries/page-queries/TeamsPageQuery';
|
||||
import type { TeamsViewData, TeamSummaryData } from '@/templates/TeamsViewData';
|
||||
|
||||
/**
|
||||
@@ -18,4 +18,4 @@ export class TeamsPresenter {
|
||||
|
||||
return { teams };
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user