docs
This commit is contained in:
138
apps/website/lib/display-objects/DashboardDisplay.ts
Normal file
138
apps/website/lib/display-objects/DashboardDisplay.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* Dashboard Display Objects
|
||||
*
|
||||
* Deterministic formatting for dashboard data without Intl.* or toLocale*
|
||||
*/
|
||||
|
||||
export interface DashboardStatDisplayData {
|
||||
icon: string;
|
||||
color: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface DashboardDateDisplayData {
|
||||
date: string;
|
||||
time: string;
|
||||
relative: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stat card display configurations
|
||||
*/
|
||||
export const dashboardStatDisplay = {
|
||||
wins: {
|
||||
icon: 'Trophy',
|
||||
color: 'bg-performance-green/20 text-performance-green',
|
||||
label: 'Wins',
|
||||
},
|
||||
podiums: {
|
||||
icon: 'Medal',
|
||||
color: 'bg-warning-amber/20 text-warning-amber',
|
||||
label: 'Podiums',
|
||||
},
|
||||
consistency: {
|
||||
icon: 'Target',
|
||||
color: 'bg-primary-blue/20 text-primary-blue',
|
||||
label: 'Consistency',
|
||||
},
|
||||
activeLeagues: {
|
||||
icon: 'Users',
|
||||
color: 'bg-purple-500/20 text-purple-400',
|
||||
label: 'Active Leagues',
|
||||
},
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Format date for display (deterministic, no Intl)
|
||||
*/
|
||||
export function formatDashboardDate(date: Date): DashboardDateDisplayData {
|
||||
const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
||||
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||
|
||||
const dayName = days[date.getDay()];
|
||||
const month = months[date.getMonth()];
|
||||
const day = date.getDate();
|
||||
const year = date.getFullYear();
|
||||
|
||||
const hours = date.getHours().toString().padStart(2, '0');
|
||||
const minutes = date.getMinutes().toString().padStart(2, '0');
|
||||
|
||||
// Calculate relative time (deterministic)
|
||||
const now = new Date();
|
||||
const diffMs = date.getTime() - now.getTime();
|
||||
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
||||
const diffDays = Math.floor(diffHours / 24);
|
||||
|
||||
let relative: string;
|
||||
if (diffHours < 0) {
|
||||
relative = 'Past';
|
||||
} else if (diffHours === 0) {
|
||||
relative = 'Now';
|
||||
} else if (diffHours < 24) {
|
||||
relative = `${diffHours}h`;
|
||||
} else {
|
||||
relative = `${diffDays}d`;
|
||||
}
|
||||
|
||||
return {
|
||||
date: `${dayName}, ${month} ${day}, ${year}`,
|
||||
time: `${hours}:${minutes}`,
|
||||
relative,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Format rating for display
|
||||
*/
|
||||
export function formatRating(rating: number): string {
|
||||
return rating.toFixed(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format rank for display
|
||||
*/
|
||||
export function formatRank(rank: number): string {
|
||||
return rank.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Format consistency percentage
|
||||
*/
|
||||
export function formatConsistency(consistency: number): string {
|
||||
return `${consistency}%`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format race count
|
||||
*/
|
||||
export function formatRaceCount(count: number): string {
|
||||
return count.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Format friend count
|
||||
*/
|
||||
export function formatFriendCount(count: number): string {
|
||||
return count.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Format league position
|
||||
*/
|
||||
export function formatLeaguePosition(position: number): string {
|
||||
return `#${position}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format points
|
||||
*/
|
||||
export function formatPoints(points: number): string {
|
||||
return points.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Format total drivers
|
||||
*/
|
||||
export function formatTotalDrivers(total: number): string {
|
||||
return total.toString();
|
||||
}
|
||||
261
apps/website/lib/display-objects/ProfileDisplay.ts
Normal file
261
apps/website/lib/display-objects/ProfileDisplay.ts
Normal file
@@ -0,0 +1,261 @@
|
||||
/**
|
||||
* Profile Display Objects
|
||||
*
|
||||
* Deterministic formatting for profile data.
|
||||
* NO Intl.*, NO Date.toLocale*, NO dynamic formatting.
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// COUNTRY FLAG DISPLAY
|
||||
// ============================================================================
|
||||
|
||||
export interface CountryFlagDisplayData {
|
||||
flag: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export const countryFlagDisplay: Record<string, CountryFlagDisplayData> = {
|
||||
// Common country codes - add as needed
|
||||
US: { flag: '🇺🇸', label: 'United States' },
|
||||
GB: { flag: '🇬🇧', label: 'United Kingdom' },
|
||||
DE: { flag: '🇩🇪', label: 'Germany' },
|
||||
FR: { flag: '🇫🇷', label: 'France' },
|
||||
IT: { flag: '🇮🇹', label: 'Italy' },
|
||||
ES: { flag: '🇪🇸', label: 'Spain' },
|
||||
JP: { flag: '🇯🇵', label: 'Japan' },
|
||||
AU: { flag: '🇦🇺', label: 'Australia' },
|
||||
CA: { flag: '🇨🇦', label: 'Canada' },
|
||||
BR: { flag: '🇧🇷', label: 'Brazil' },
|
||||
// Fallback for unknown codes
|
||||
DEFAULT: { flag: '🏁', label: 'Unknown' },
|
||||
} as const;
|
||||
|
||||
export function getCountryFlagDisplay(countryCode: string): CountryFlagDisplayData {
|
||||
const code = countryCode.toUpperCase();
|
||||
return countryFlagDisplay[code] || countryFlagDisplay.DEFAULT;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ACHIEVEMENT RARITY DISPLAY
|
||||
// ============================================================================
|
||||
|
||||
export interface AchievementRarityDisplayData {
|
||||
text: string;
|
||||
badgeClasses: string;
|
||||
borderClasses: string;
|
||||
}
|
||||
|
||||
export const achievementRarityDisplay: Record<string, AchievementRarityDisplayData> = {
|
||||
common: {
|
||||
text: 'Common',
|
||||
badgeClasses: 'bg-gray-400/10 text-gray-400',
|
||||
borderClasses: 'border-gray-400/30',
|
||||
},
|
||||
rare: {
|
||||
text: 'Rare',
|
||||
badgeClasses: 'bg-primary-blue/10 text-primary-blue',
|
||||
borderClasses: 'border-primary-blue/30',
|
||||
},
|
||||
epic: {
|
||||
text: 'Epic',
|
||||
badgeClasses: 'bg-purple-400/10 text-purple-400',
|
||||
borderClasses: 'border-purple-400/30',
|
||||
},
|
||||
legendary: {
|
||||
text: 'Legendary',
|
||||
badgeClasses: 'bg-yellow-400/10 text-yellow-400',
|
||||
borderClasses: 'border-yellow-400/30',
|
||||
},
|
||||
} as const;
|
||||
|
||||
export function getAchievementRarityDisplay(rarity: string): AchievementRarityDisplayData {
|
||||
return achievementRarityDisplay[rarity] || achievementRarityDisplay.common;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ACHIEVEMENT ICON DISPLAY
|
||||
// ============================================================================
|
||||
|
||||
export type AchievementIconType = 'trophy' | 'medal' | 'star' | 'crown' | 'target' | 'zap';
|
||||
|
||||
export interface AchievementIconDisplayData {
|
||||
name: string;
|
||||
// Icon component will be resolved in UI layer
|
||||
}
|
||||
|
||||
export const achievementIconDisplay: Record<AchievementIconType, AchievementIconDisplayData> = {
|
||||
trophy: { name: 'Trophy' },
|
||||
medal: { name: 'Medal' },
|
||||
star: { name: 'Star' },
|
||||
crown: { name: 'Crown' },
|
||||
target: { name: 'Target' },
|
||||
zap: { name: 'Zap' },
|
||||
} as const;
|
||||
|
||||
export function getAchievementIconDisplay(icon: string): AchievementIconDisplayData {
|
||||
return achievementIconDisplay[icon as AchievementIconType] || achievementIconDisplay.trophy;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SOCIAL PLATFORM DISPLAY
|
||||
// ============================================================================
|
||||
|
||||
export interface SocialPlatformDisplayData {
|
||||
name: string;
|
||||
hoverClasses: string;
|
||||
}
|
||||
|
||||
export const socialPlatformDisplay: Record<string, SocialPlatformDisplayData> = {
|
||||
twitter: {
|
||||
name: 'Twitter',
|
||||
hoverClasses: 'hover:text-sky-400 hover:bg-sky-400/10',
|
||||
},
|
||||
youtube: {
|
||||
name: 'YouTube',
|
||||
hoverClasses: 'hover:text-red-500 hover:bg-red-500/10',
|
||||
},
|
||||
twitch: {
|
||||
name: 'Twitch',
|
||||
hoverClasses: 'hover:text-purple-400 hover:bg-purple-400/10',
|
||||
},
|
||||
discord: {
|
||||
name: 'Discord',
|
||||
hoverClasses: 'hover:text-indigo-400 hover:bg-indigo-400/10',
|
||||
},
|
||||
} as const;
|
||||
|
||||
export function getSocialPlatformDisplay(platform: string): SocialPlatformDisplayData {
|
||||
return socialPlatformDisplay[platform] || socialPlatformDisplay.discord;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DATE FORMATTING (DETERMINISTIC)
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Format date string to "Month Year" format
|
||||
* Input: ISO date string (e.g., "2024-01-15T10:30:00Z")
|
||||
* Output: "Jan 2024"
|
||||
*/
|
||||
export function formatMonthYear(dateString: string): string {
|
||||
const date = new Date(dateString);
|
||||
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||
const month = months[date.getUTCMonth()];
|
||||
const year = date.getUTCFullYear();
|
||||
return `${month} ${year}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format date string to "Month Day, Year" format
|
||||
* Input: ISO date string
|
||||
* Output: "Jan 15, 2024"
|
||||
*/
|
||||
export function formatMonthDayYear(dateString: string): string {
|
||||
const date = new Date(dateString);
|
||||
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||
const month = months[date.getUTCMonth()];
|
||||
const day = date.getUTCDate();
|
||||
const year = date.getUTCFullYear();
|
||||
return `${month} ${day}, ${year}`;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// STATISTICS FORMATTING
|
||||
// ============================================================================
|
||||
|
||||
export interface StatDisplayData {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format percentage with 1 decimal place
|
||||
* Input: 0.1234
|
||||
* Output: "12.3%"
|
||||
*/
|
||||
export function formatPercentage(value: number | null): string {
|
||||
if (value === null || value === undefined) return '0.0%';
|
||||
return `${(value * 100).toFixed(1)}%`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format finish position
|
||||
* Input: 1
|
||||
* Output: "P1"
|
||||
*/
|
||||
export function formatFinishPosition(position: number | null): string {
|
||||
if (position === null || position === undefined) return 'P-';
|
||||
return `P${position}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format average finish with 1 decimal place
|
||||
* Input: 3.456
|
||||
* Output: "P3.5"
|
||||
*/
|
||||
export function formatAvgFinish(avg: number | null): string {
|
||||
if (avg === null || avg === undefined) return 'P-';
|
||||
return `P${avg.toFixed(1)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format rating (whole number)
|
||||
* Input: 1234.56
|
||||
* Output: "1235"
|
||||
*/
|
||||
export function formatRating(rating: number | null): string {
|
||||
if (rating === null || rating === undefined) return '0';
|
||||
return Math.round(rating).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Format consistency percentage
|
||||
* Input: 87.5
|
||||
* Output: "88%"
|
||||
*/
|
||||
export function formatConsistency(consistency: number | null): string {
|
||||
if (consistency === null || consistency === undefined) return '0%';
|
||||
return `${Math.round(consistency)}%`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format percentile
|
||||
* Input: 15.5
|
||||
* Output: "Top 16%"
|
||||
*/
|
||||
export function formatPercentile(percentile: number | null): string {
|
||||
if (percentile === null || percentile === undefined) return 'Top -%';
|
||||
return `Top ${Math.round(percentile)}%`;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TEAM ROLE DISPLAY
|
||||
// ============================================================================
|
||||
|
||||
export interface TeamRoleDisplayData {
|
||||
text: string;
|
||||
badgeClasses: string;
|
||||
}
|
||||
|
||||
export const teamRoleDisplay: Record<string, TeamRoleDisplayData> = {
|
||||
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 function getTeamRoleDisplay(role: string): TeamRoleDisplayData {
|
||||
return teamRoleDisplay[role] || teamRoleDisplay.member;
|
||||
}
|
||||
Reference in New Issue
Block a user