feat (v1.0.0): initial refactor and redesign

This commit is contained in:
Aidan 2025-10-09 04:12:05 -04:00
parent 3058aa1ab4
commit fe9b50b30e
134 changed files with 17792 additions and 3670 deletions

362
lib/types/ai.ts Normal file
View file

@ -0,0 +1,362 @@
/**
* Type definitions for AI usage analytics and token tracking.
*
* @remarks
* This module contains interfaces for Claude AI usage data, including:
* - Token consumption metrics (input, output, cache)
* - Cost calculations
* - Daily aggregations
* - Trend analysis
* - Model-specific breakdowns
*
* @module lib/types/ai
* @category Types
*/
/**
* Breakdown of AI usage metrics for a specific model.
*
* @remarks
* Contains token counts and cost for a single AI model within a time period.
* Used to track usage across different models (e.g., Claude Sonnet, Opus).
*
* @example
* ```ts
* const breakdown: ModelBreakdown = {
* modelName: 'claude-sonnet-4-20250514',
* inputTokens: 150000,
* outputTokens: 75000,
* cacheCreationTokens: 5000,
* cacheReadTokens: 50000,
* cost: 2.45
* }
* ```
*
* @public
*/
export interface ModelBreakdown {
/** Model identifier (e.g., 'claude-sonnet-4-20250514') */
modelName: string
/** Number of input tokens consumed */
inputTokens: number
/** Number of output tokens generated */
outputTokens: number
/** Number of tokens written to cache */
cacheCreationTokens: number
/** Number of tokens read from cache */
cacheReadTokens: number
/** Total cost in USD for this model's usage */
cost: number
}
/**
* Aggregated AI usage data for a single day.
*
* @remarks
* Represents all AI interactions for a 24-hour period, including:
* - Total token counts across all models
* - Total cost in USD
* - Per-model breakdowns
* - List of models used
*
* Date format is ISO 8601 (YYYY-MM-DD).
*
* @example
* ```ts
* const dailyData: DailyData = {
* date: '2025-01-15',
* inputTokens: 500000,
* outputTokens: 250000,
* cacheCreationTokens: 10000,
* cacheReadTokens: 100000,
* totalTokens: 860000,
* totalCost: 8.50,
* modelsUsed: ['claude-sonnet-4-20250514'],
* modelBreakdowns: [...]
* }
* ```
*
* @public
*/
export interface DailyData {
/** Date in ISO 8601 format (YYYY-MM-DD) */
date: string
/** Total input tokens for the day */
inputTokens: number
/** Total output tokens for the day */
outputTokens: number
/** Total cache creation tokens for the day */
cacheCreationTokens: number
/** Total cache read tokens for the day */
cacheReadTokens: number
/** Sum of all token types */
totalTokens: number
/** Total cost in USD for the day */
totalCost: number
/** List of model identifiers used this day */
modelsUsed: string[]
/** Per-model usage breakdowns */
modelBreakdowns: ModelBreakdown[]
}
/**
* Aggregated totals across all time periods.
*
* @remarks
* Represents cumulative usage metrics, typically used for:
* - All-time totals
* - Custom date range totals
* - Filtered subset totals
*
* @example
* ```ts
* const totals: Totals = {
* inputTokens: 15000000,
* outputTokens: 7500000,
* cacheCreationTokens: 100000,
* cacheReadTokens: 1000000,
* totalCost: 250.00,
* totalTokens: 23600000
* }
* ```
*
* @public
*/
export interface Totals {
/** Cumulative input tokens */
inputTokens: number
/** Cumulative output tokens */
outputTokens: number
/** Cumulative cache creation tokens */
cacheCreationTokens: number
/** Cumulative cache read tokens */
cacheReadTokens: number
/** Cumulative cost in USD */
totalCost: number
/** Sum of all cumulative tokens */
totalTokens: number
}
/**
* Complete AI usage dataset with daily breakdowns and totals.
*
* @remarks
* Primary data structure for AI analytics, containing:
* - Daily usage history
* - Aggregate totals
*
* Typically loaded from JSON data files or API responses.
*
* @example
* ```ts
* const data: CCData = {
* daily: [...], // Array of DailyData
* totals: {
* inputTokens: 15000000,
* outputTokens: 7500000,
* // ... other totals
* }
* }
* ```
*
* @public
*/
export interface CCData {
/** Array of daily usage data, typically sorted chronologically */
daily: DailyData[]
/** Aggregated totals across all daily data */
totals: Totals
}
/**
* Extended AI usage data supporting multiple sources (Claude Code, Codex, etc.).
*
* @remarks
* Used for dashboards that display multiple AI tool usages side-by-side.
* Each tool can have its own daily history and totals.
*
* @example
* ```ts
* const extended: ExtendedCCData = {
* totals: { ... }, // Combined totals
* claudeCode: {
* daily: [...],
* totals: { ... }
* },
* codex: {
* daily: [...],
* totals: { ... }
* }
* }
* ```
*
* @public
*/
export interface ExtendedCCData {
/** Combined totals across all sources (optional) */
totals?: Totals
/** Claude Code usage data */
claudeCode?: {
daily: DailyData[]
totals: Totals
}
/** Codex usage data */
codex?: {
daily: DailyData[]
totals: Totals
}
}
/**
* Time range selector keys for filtering AI usage data.
*
* @remarks
* Used in dropdown menus and filters to select predefined time ranges:
* - '7d': Last 7 days
* - '1m': Last 1 month
* - '3m': Last 3 months
* - '6m': Last 6 months
* - '1y': Last 1 year
* - 'all': All available data
*
* @example
* ```ts
* function filterByRange(data: DailyData[], range: TimeRangeKey) {
* // Implementation
* }
* ```
*
* @public
*/
export type TimeRangeKey = '7d' | '1m' | '3m' | '6m' | '1y' | 'all'
/**
* Single day cell data for heatmap visualization.
*
* @remarks
* Contains all data needed to render one cell in a GitHub-style contribution heatmap:
* - Date information
* - Usage metrics (tokens, cost)
* - Display formatting
*
* @example
* ```ts
* const heatmapDay: HeatmapDay = {
* date: '2025-01-15',
* value: 8.50,
* tokens: 860000,
* cost: 8.50,
* day: 1, // Monday
* formattedDate: 'Jan 15, 2025'
* }
* ```
*
* @public
*/
export interface HeatmapDay {
/** Date in ISO 8601 format (YYYY-MM-DD) */
date: string
/** Primary value for heatmap intensity (typically cost) */
value: number
/** Total tokens for tooltip display */
tokens: number
/** Total cost for tooltip display */
cost: number
/** Day of week (0 = Sunday, 6 = Saturday) */
day: number
/** Human-readable date string for tooltips */
formattedDate: string
}
/**
* Daily data extended with trend analysis for chart visualizations.
*
* @remarks
* Extends {@link DailyData} with:
* - Linear regression trend lines
* - Normalized token values for charting
*
* Used for displaying trend overlays on usage charts.
*
* @example
* ```ts
* const trendData: DailyDataWithTrend = {
* ...dailyData,
* costTrend: 8.75, // Predicted cost from regression
* tokensTrend: 0.9, // Predicted tokens in millions
* inputTokensNormalized: 500, // Input tokens / 1000
* outputTokensNormalized: 250, // Output tokens / 1000
* cacheTokensNormalized: 0.11 // Cache tokens / 1000000
* }
* ```
*
* @public
*/
export interface DailyDataWithTrend extends DailyData {
/** Predicted cost from linear regression (null if not enough data) */
costTrend: number | null
/** Predicted tokens in millions from linear regression (null if not enough data) */
tokensTrend: number | null
/** Input tokens divided by 1000 for chart display */
inputTokensNormalized: number
/** Output tokens divided by 1000 for chart display */
outputTokensNormalized: number
/** Cache tokens divided by 1000000 for chart display */
cacheTokensNormalized: number
}
/**
* Model usage statistics for pie/bar charts.
*
* @remarks
* Represents usage distribution across different AI models.
* Includes both absolute values and percentages for visualization.
*
* @example
* ```ts
* const usage: ModelUsage = {
* name: 'Claude Sonnet 4',
* value: 125.50,
* percentage: 65.2
* }
* ```
*
* @public
*/
export interface ModelUsage {
/** Human-readable model name */
name: string
/** Total cost or usage value */
value: number
/** Percentage of total usage (optional) */
percentage?: number
/** Allow additional properties for chart libraries */
[key: string]: string | number | undefined
}
/**
* Token type usage statistics for composition charts.
*
* @remarks
* Breaks down usage by token type (input, output, cache creation, cache read).
* Used for pie charts showing token distribution.
*
* @example
* ```ts
* const tokenUsage: TokenTypeUsage = {
* name: 'Input',
* value: 15000000,
* percentage: 63.5
* }
* ```
*
* @public
*/
export interface TokenTypeUsage {
/** Token type name ('Input', 'Output', 'Cache Creation', 'Cache Read') */
name: string
/** Total token count */
value: number
/** Percentage of total tokens (optional) */
percentage?: number
}

83
lib/types/common.ts Normal file
View file

@ -0,0 +1,83 @@
/**
* Common utility types used across my website
*
* @remarks
* This module provides fundamental type definitions for date handling,
* filtering, and pagination. These types are used throughout
* the application to ensure type safety and consistency.
*
* @module lib/types/common
* @category Types
* @public
*/
/**
* Represents a time range with start and end dates.
*
* @remarks
* Used for filtering data by date ranges, such as analytics queries,
* domain renewal periods, or session time windows.
*
* @example
* ```ts
* import type { DateRange } from '@/lib/types/common'
*
* // Last 30 days
* const last30Days: DateRange = {
* start: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000),
* end: new Date()
* }
*
* // Specific date range
* const Q1: DateRange = {
* start: new Date('2025-01-01'),
* end: new Date('2025-03-31')
* }
* ```
*
* @category Types
* @public
*/
export interface DateRange {
/** Start date of the range (inclusive) */
start: Date
/** End date of the range (inclusive) */
end: Date
}
/**
* Date object with pre-computed formatted strings.
*
* @remarks
* This type is useful when you need to display dates in multiple formats
* and want to avoid repeated formatting operations. Commonly used in
* components that display dates in both human-readable and machine-readable formats.
*
* @example
* ```ts
* import type { FormattedDate } from '@/lib/types/common'
* import { Formatter } from '@/lib/utils'
*
* const registrationDate: FormattedDate = {
* date: new Date('2023-05-15'),
* formatted: 'May 15, 2023',
* iso: '2023-05-15T00:00:00.000Z'
* }
*
* // Usage in components
* <time dateTime={registrationDate.iso}>
* {registrationDate.formatted}
* </time>
* ```
*
* @category Types
* @public
*/
export interface FormattedDate {
/** Original Date object */
date: Date
/** Human-readable formatted string (e.g., "May 15, 2023") */
formatted: string
/** ISO 8601 formatted string for machine readability */
iso: string
}

517
lib/types/device.ts Normal file
View file

@ -0,0 +1,517 @@
/**
* Device type definitions for portfolio showcase.
*
* Provides comprehensive type safety for device specifications, statistics,
* sections, and UI components. Supports both mobile devices and DAPs (Digital Audio Players).
*
* @module lib/types/device
* @category Types
*/
import React from 'react'
/**
* Icon component type for device-related icons.
*
* @public
*/
export type DeviceIcon = React.ComponentType<{
/** Optional className for styling */
className?: string
/** Optional size override */
size?: number
}>
/**
* Device type classification.
*
* @remarks
* - `mobile`: Smartphones and mobile devices
* - `dap`: Digital Audio Players (dedicated music players)
*
* @example
* ```ts
* const type: DeviceType = 'mobile'
* ```
*
* @public
*/
export type DeviceType = 'mobile' | 'dap'
/**
* Star rating display state.
*
* @remarks
* Used for rendering star ratings with half-star support.
*
* @public
*/
export type StarState = 'full' | 'half' | 'empty'
/**
* Type-safe external URL starting with http or https.
*
* @example
* ```ts
* const url: ExternalHref = 'https://example.com'
* ```
*
* @public
*/
export type ExternalHref = `http${string}`
/**
* Badge display configuration for device highlights.
*
* @example
* ```ts
* const badge: DeviceBadge = {
* label: 'Flagship',
* tone: 'highlight'
* }
* ```
*
* @public
*/
export interface DeviceBadge {
/** Badge text */
label: string
/** Visual tone (default: neutral, highlight: accent, muted: subtle) */
tone?: 'default' | 'highlight' | 'muted'
}
/**
* Individual stat item within a stat group.
*
* @example
* ```ts
* const stat: DeviceStatItem = {
* label: 'Display',
* value: '6.1" OLED',
* href: 'https://example.com/display-specs'
* }
* ```
*
* @public
*/
export interface DeviceStatItem {
/** Optional label for the stat */
label?: string
/** Stat value to display */
value: string
/** Optional external link for more information */
href?: string
}
/**
* Group of related device statistics.
*
* @example
* ```tsx
* import { CpuIcon } from 'lucide-react'
*
* const group: DeviceStatGroup = {
* title: 'Performance',
* icon: CpuIcon,
* accent: 'primary',
* items: [
* { label: 'Processor', value: 'Snapdragon 8 Gen 2' },
* { label: 'RAM', value: '8GB' },
* { label: 'Storage', value: '256GB' }
* ]
* }
* ```
*
* @public
*/
export interface DeviceStatGroup {
/** Group title */
title: string
/** Optional icon for visual identification */
icon?: DeviceIcon
/** List of stat items in this group */
items: DeviceStatItem[]
/** Visual accent style */
accent?: 'primary' | 'surface'
}
/**
* Row item within a device section showing key-value pairs.
*
* @example
* ```tsx
* import { BatteryIcon } from 'lucide-react'
*
* const row: DeviceSectionRow = {
* label: 'Battery',
* value: '5000 mAh',
* icon: BatteryIcon,
* note: 'Supports 65W fast charging'
* }
* ```
*
* @public
*/
export interface DeviceSectionRow {
/** Row label */
label: string
/** Row value */
value: string
/** Optional icon */
icon?: DeviceIcon
/** Optional external link */
href?: string
/** Optional additional note */
note?: string
}
/**
* List item within a device section.
*
* @example
* ```ts
* const item: DeviceSectionListItem = {
* label: 'USB-C 3.1',
* description: 'Fast data transfer and charging',
* href: 'https://example.com/usb-specs'
* }
* ```
*
* @public
*/
export interface DeviceSectionListItem {
/** Item label */
label: string
/** Optional description */
description?: string
/** Optional external link */
href?: string
}
/**
* Rating configuration for device sections.
*
* @example
* ```ts
* const rating: DeviceSectionRating = {
* value: 4.5,
* scale: 5,
* label: 'Overall Rating'
* }
* ```
*
* @public
*/
export interface DeviceSectionRating {
/** Rating value (supports half-stars) */
value: number
/** Maximum rating scale (default: 5) */
scale?: number
/** Optional rating label */
label?: string
}
/**
* Device section containing grouped information.
*
* @remarks
* Sections can contain one of: rows (key-value pairs), listItems (bullet lists),
* paragraphs (text content), or rating (star rating). Each section has an icon
* for visual identification.
*
* @example
* ```tsx
* import { CameraIcon } from 'lucide-react'
*
* const section: DeviceSection = {
* id: 'camera',
* title: 'Camera',
* icon: CameraIcon,
* rows: [
* { label: 'Main', value: '50MP f/1.8' },
* { label: 'Ultra-wide', value: '12MP f/2.2' },
* { label: 'Telephoto', value: '10MP f/2.4' }
* ],
* rating: { value: 4.5, scale: 5 }
* }
* ```
*
* @public
*/
export interface DeviceSection {
/** Unique section identifier */
id: string
/** Section title */
title: string
/** Section icon */
icon: DeviceIcon
/** Optional key-value rows */
rows?: DeviceSectionRow[]
/** Optional list items */
listItems?: DeviceSectionListItem[]
/** Optional text paragraphs */
paragraphs?: string[]
/** Optional rating */
rating?: DeviceSectionRating
}
/**
* Complete device specification.
*
* Contains all data needed to render a device page including metadata, statistics,
* sections, and related devices.
*
* @example
* ```tsx
* import { CpuIcon, BatteryIcon } from 'lucide-react'
*
* const device: DeviceSpec = {
* slug: 'pixel-8-pro',
* name: 'Google Pixel 8 Pro',
* codename: 'husky',
* type: 'mobile',
* manufacturer: 'Google',
* shortName: 'Pixel 8 Pro',
* status: 'Current',
* releaseYear: 2023,
* heroImage: {
* src: '/img/devices/pixel-8-pro.png',
* alt: 'Google Pixel 8 Pro',
* width: 800,
* height: 600
* },
* tagline: 'AI-powered flagship smartphone',
* summary: [
* 'Advanced Tensor G3 processor',
* 'Exceptional camera system',
* 'Premium build quality'
* ],
* badges: [
* { label: 'Flagship', tone: 'highlight' },
* { label: 'Current', tone: 'default' }
* ],
* stats: [
* {
* title: 'Performance',
* icon: CpuIcon,
* items: [
* { label: 'Processor', value: 'Google Tensor G3' },
* { label: 'RAM', value: '12GB' }
* ]
* }
* ],
* sections: [
* {
* id: 'battery',
* title: 'Battery',
* icon: BatteryIcon,
* rows: [{ label: 'Capacity', value: '5050 mAh' }]
* }
* ],
* related: ['pixel-7-pro', 'pixel-8'],
* updatedAt: '2024-01-15'
* }
* ```
*
* @public
*/
export interface DeviceSpec {
/** URL-friendly slug */
slug: string
/** Full device name */
name: string
/** Optional device codename */
codename?: string
/** Device type (mobile or dap) */
type: DeviceType
/** Manufacturer name */
manufacturer?: string
/** Short display name */
shortName?: string
/** Current status (e.g., 'Current', 'Retired') */
status?: string
/** Year of release */
releaseYear?: number
/** Hero image configuration */
heroImage: {
/** Image source path */
src: string
/** Alt text for accessibility */
alt: string
/** Optional width */
width?: number
/** Optional height */
height?: number
}
/** Marketing tagline */
tagline?: string
/** Summary bullet points */
summary?: string[]
/** Feature badges */
badges?: DeviceBadge[]
/** Stat groups */
stats: DeviceStatGroup[]
/** Content sections */
sections: DeviceSection[]
/** Related device slugs */
related?: string[]
/** Last update date (ISO format) */
updatedAt?: string
}
/**
* Collection of devices indexed by slug.
*
* @public
*/
export type DeviceCollection = Record<string, DeviceSpec>
/**
* Enriched device with computed metrics.
*
* Extends {@link DeviceSpec} with age calculations and display labels.
*
* @remarks
* This interface is generated by `DeviceService.enrichDevice()` method.
* All devices returned by DeviceService methods include these computed properties.
*
* @example
* ```ts
* import { DeviceService } from '@/lib/services'
*
* const enriched: DeviceWithMetrics = DeviceService.enrichDevice(device)
* console.log(enriched.ageInYears) // 1
* console.log(enriched.isCurrentYear) // false
* console.log(enriched.categoryLabel) // 'Mobile Devices'
* ```
*
* @public
*/
export interface DeviceWithMetrics extends DeviceSpec {
/** Device age in full years */
ageInYears: number
/** True if released in current year */
isCurrentYear: boolean
/** Display label for device category */
categoryLabel: string
}
/**
* Props for DevicePageShell component.
*
* @public
*/
export interface DevicePageShellProps {
/** Device data to render */
device: DeviceSpec
}
/**
* Props for DeviceHero component.
*
* @public
*/
export interface DeviceHeroProps {
/** Device data for hero section */
device: DeviceSpec
}
/**
* Props for StatsGrid component.
*
* @public
*/
export interface StatsGridProps {
/** Stat groups to display */
stats: DeviceStatGroup[]
}
/**
* Props for StatItem component.
*
* @public
*/
export interface StatItemProps {
/** Stat item to display */
item: DeviceStatItem
/** Optional group icon for fallback */
groupIcon?: DeviceStatGroup['icon']
}
/**
* Props for SectionsGrid component.
*
* @public
*/
export interface SectionsGridProps {
/** Sections to display in grid */
sections: DeviceSection[]
}
/**
* Props for SectionCard component.
*
* @public
*/
export interface SectionCardProps {
/** Section data to render */
section: DeviceSection
}
/**
* Props for SectionRow component.
*
* @public
*/
export interface SectionRowProps {
/** Row data to render */
row: NonNullable<DeviceSection['rows']>[number]
}
/**
* Props for Rating component.
*
* @public
*/
export interface RatingProps {
/** Rating data to render */
rating: NonNullable<DeviceSection['rating']>
}

380
lib/types/domain.ts Normal file
View file

@ -0,0 +1,380 @@
/**
* Domain type definitions for portfolio management.
*
* Provides comprehensive type safety for domain data, metrics, and UI components.
* All domain-related interfaces include renewal tracking, ownership metrics, and
* categorization for portfolio organization.
*
* @module lib/types/domain
* @category Types
*/
import type { ComponentType, Dispatch, SetStateAction } from 'react'
import type { IconType } from 'react-icons'
import type { LucideIcon } from 'lucide-react'
/**
* Domain status indicating current usage state.
*
* @remarks
* - `active`: Domain is actively being used for a website or service
* - `parked`: Domain is registered but not currently in use
* - `reserved`: Domain is reserved for future use
*
* @example
* ```ts
* const status: DomainStatus = 'active'
* ```
*
* @public
*/
export type DomainStatus = 'active' | 'parked' | 'reserved'
/**
* Domain category for portfolio organization.
*
* @remarks
* Categories help organize domains by purpose and importance:
* - `personal`: Personal branding or portfolio domains
* - `service`: Production services and applications
* - `project`: Project-specific or experimental domains
* - `fun`: Hobby or entertainment domains
* - `legacy`: Older domains maintained for historical reasons
*
* @example
* ```ts
* const category: DomainCategory = 'service'
* ```
*
* @public
*/
export type DomainCategory = 'personal' | 'service' | 'project' | 'fun' | 'legacy'
/**
* Supported domain registrar identifiers.
*
* @example
* ```ts
* const registrar: DomainRegistrarId = 'Namecheap'
* ```
*
* @public
*/
export type DomainRegistrarId = 'Spaceship' | 'Namecheap' | 'Name.com' | 'Dynadot'
/**
* Sort options for domain lists.
*
* @example
* ```ts
* const sortBy: DomainSortOption = 'expiration'
* ```
*
* @public
*/
export type DomainSortOption = 'name' | 'expiration' | 'ownership' | 'registrar'
/**
* Timeline event types for domain history.
*
* @example
* ```ts
* const eventType: DomainTimelineEventType = 'renewal'
* ```
*
* @public
*/
export type DomainTimelineEventType = 'registration' | 'renewal'
/**
* Domain renewal record tracking renewal history.
*
* @example
* ```ts
* const renewal: Renewal = {
* date: '2024-01-15',
* years: 2
* }
* ```
*
* @public
*/
export interface Renewal {
/** ISO date string of renewal (YYYY-MM-DD format) */
date: string
/** Number of years renewed */
years: number
}
/**
* Registrar configuration for UI display.
*
* @example
* ```tsx
* import { SiNamecheap } from 'react-icons/si'
*
* const config: RegistrarConfig = {
* name: 'Namecheap',
* icon: SiNamecheap,
* color: '#FF6C2C'
* }
* ```
*
* @public
*/
export interface RegistrarConfig {
/** Display name of the registrar */
name: string
/** Icon component (from react-icons or custom) */
icon: IconType | ComponentType<{className?: string}>
/** Brand color for visual identification */
color: string
}
/**
* Core domain data structure.
*
* Base interface containing all raw domain information without computed metrics.
* Use {@link DomainWithMetrics} for enriched data with ownership calculations.
*
* @example
* ```ts
* const domain: Domain = {
* domain: 'example.com',
* usage: 'Personal portfolio website',
* registrar: 'Namecheap',
* autoRenew: true,
* status: 'active',
* category: 'personal',
* tags: ['portfolio', 'web'],
* renewals: [
* { date: '2022-01-15', years: 1 },
* { date: '2023-01-15', years: 2 }
* ]
* }
* ```
*
* @public
*/
export interface Domain {
/** Domain name (e.g., 'example.com') */
domain: string
/** Description of domain usage/purpose */
usage: string
/** Registrar where domain is registered */
registrar: DomainRegistrarId
/** Whether auto-renewal is enabled */
autoRenew: boolean
/** Current status of the domain */
status: DomainStatus
/** Portfolio category for organization */
category: DomainCategory
/** Tags for filtering and search */
tags: string[]
/** Renewal history (chronological order) */
renewals: Renewal[]
}
/**
* Enriched domain data with computed ownership and expiration metrics.
*
* Extends {@link Domain} with calculated fields for ownership duration, expiration
* tracking, and renewal progress.
*
* @remarks
* All date calculations use UTC to ensure consistent timezone handling. The renewal
* progress percentage helps visualize time until next renewal is needed.
*
* This interface is generated by `DomainService.enrichDomain()` method.
* All domains returned by DomainService methods include these computed properties.
*
* @example
* ```ts
* import { DomainService } from '@/lib/services'
*
* const enriched: DomainWithMetrics = DomainService.enrichDomain(domain)
* console.log(enriched.ownershipYears) // 2
* console.log(enriched.daysUntilExpiration) // 347
* console.log(enriched.isExpiringSoon) // false
* console.log(enriched.renewalProgressPercent) // 15.2
* console.log(enriched.tld) // 'com'
* ```
*
* @public
*/
export interface DomainWithMetrics extends Domain {
/** Calculated expiration date based on last renewal */
expirationDate: Date
/** First renewal date (registration date) */
registrationDate: Date
/** Total days of ownership */
ownershipDays: number
/** Full years of ownership (floored) */
ownershipYears: number
/** Remaining months after full years */
ownershipMonths: number
/** Days until domain expires */
daysUntilExpiration: number
/** Percentage of current renewal period completed (0-100) */
renewalProgressPercent: number
/** True if expiring within 90 days */
isExpiringSoon: boolean
/** Date of next scheduled renewal */
nextRenewalDate: Date
/** Top-level domain extension (e.g., 'com', 'net') */
tld: string
}
/**
* Visual styling configuration for domain status/category badges.
*
* @example
* ```tsx
* import { Circle } from 'lucide-react'
*
* const option: DomainVisualOption = {
* label: 'Active',
* icon: Circle,
* color: 'text-green-400',
* bg: 'bg-green-500/20',
* border: 'border-green-500/30'
* }
* ```
*
* @public
*/
export interface DomainVisualOption {
/** Display label text */
label: string
/** Lucide icon component */
icon: LucideIcon
/** Tailwind text color class */
color: string
/** Tailwind background color class */
bg: string
/** Tailwind border color class */
border: string
}
/**
* Complete visual configuration mapping for all domain statuses and categories.
*
* @public
*/
export type DomainVisualConfig = {
/** Visual options for each status type */
status: Record<DomainStatus, DomainVisualOption>
/** Visual options for each category type */
category: Record<DomainCategory, DomainVisualOption>
}
/**
* Mapping of registrar IDs to their configuration.
*
* @public
*/
export type DomainRegistrarMap = Record<DomainRegistrarId, RegistrarConfig>
/**
* Domain timeline event for renewal/registration history display.
*
* @example
* ```ts
* const event: DomainTimelineEvent = {
* date: new Date('2024-01-15'),
* type: 'renewal',
* years: 2
* }
* ```
*
* @public
*/
export interface DomainTimelineEvent {
/** Event date */
date: Date
/** Event type (registration or renewal) */
type: DomainTimelineEventType
/** Number of years for the renewal */
years: number
}
/**
* Props for DomainCard component.
*
* @public
*/
export interface DomainCardProps {
/** Domain data to display */
domain: Domain
}
/**
* Props for DomainDetails component.
*
* @public
*/
export interface DomainDetailsProps {
/** Domain data to display */
domain: Domain
}
/**
* Props for DomainTimeline component.
*
* @public
*/
export interface DomainTimelineProps {
/** Domain data to display timeline for */
domain: Domain
}
/**
* Props for DomainFilters component.
*
* @public
*/
export interface DomainFiltersProps {
/** Search query change handler */
onSearchChange: Dispatch<SetStateAction<string>>
/** Category filter change handler */
onCategoryChange: Dispatch<SetStateAction<DomainCategory[]>>
/** Status filter change handler */
onStatusChange: Dispatch<SetStateAction<DomainStatus[]>>
/** Registrar filter change handler */
onRegistrarChange: Dispatch<SetStateAction<DomainRegistrarId[]>>
/** Sort option change handler */
onSortChange: Dispatch<SetStateAction<DomainSortOption>>
/** Available registrars for filter options */
registrars: DomainRegistrarId[]
}

6
lib/types/index.ts Normal file
View file

@ -0,0 +1,6 @@
export * from './domain'
export * from './device'
export * from './ai'
export * from './common'
export * from './navigation'
export * from './service'

71
lib/types/navigation.ts Normal file
View file

@ -0,0 +1,71 @@
import type { ComponentType, ReactNode } from 'react'
import type { GitHubRepoSummary } from '@/lib/github'
export type NavigationIcon = ComponentType<{ className?: string; size?: number } & Record<string, unknown>>
export type NavigationLink = {
label: string
href: string
icon: NavigationIcon
external?: boolean
}
export type NavigationDropdownLinkItem = NavigationLink & {
type: 'link'
}
export type NavigationDropdownGroup = {
title: string
links: NavigationDropdownLinkItem[]
}
export type NavigationDropdownNestedItem = {
type: 'nested'
label: string
icon: NavigationIcon
groups: NavigationDropdownGroup[]
}
export type NavigationDropdownItem =
| NavigationDropdownLinkItem
| NavigationDropdownNestedItem
export type NavigationDropdownConfig = {
items: NavigationDropdownItem[]
}
export type NavigationMenuLinkItem = NavigationLink & {
type: 'link'
id: string
}
export type NavigationMenuDropdownItem = {
type: 'dropdown'
id: string
label: string
href: string
icon: NavigationIcon
dropdown: NavigationDropdownConfig
}
export type NavigationMenuItem =
| NavigationMenuLinkItem
| NavigationMenuDropdownItem
export type FooterMenuRenderContext = {
githubUsername: string
githubRepos: GitHubRepoSummary[]
}
export type FooterMenuSection =
| {
type: 'links'
title: string
links: NavigationLink[]
}
| {
type: 'custom'
title: string
render: (context: FooterMenuRenderContext) => ReactNode
}

376
lib/types/service.ts Normal file
View file

@ -0,0 +1,376 @@
/**
* Shared type definitions for service layer operations.
*
* @remarks
* This module contains reusable types for common service patterns:
* - Filtering and sorting configurations
* - Statistics and aggregation results
* - Query options and pagination
*
* @module lib/types/service
* @category Types
*/
/**
* Sort order direction for list operations.
*
* @example
* ```ts
* const order: SortOrder = 'asc' // Ascending order
* const descOrder: SortOrder = 'desc' // Descending order
* ```
*
* @public
*/
export type SortOrder = 'asc' | 'desc'
/**
* Configuration for sorting operations on typed entities.
*
* @template T - The entity type being sorted
*
* @remarks
* Provides type-safe sorting configuration with:
* - Compile-time verification of sort key
* - Order direction (ascending/descending)
*
* @example
* ```ts
* interface User {
* name: string
* age: number
* createdAt: Date
* }
*
* const config: SortConfig<User> = {
* sortBy: 'age',
* order: 'desc'
* }
* ```
*
* @public
*/
export interface SortConfig<T> {
/** Property key to sort by (type-safe) */
sortBy: keyof T
/** Sort direction */
order: SortOrder
}
/**
* Generic filter configuration for entity queries.
*
* @template T - The entity type being filtered
*
* @remarks
* Provides a flexible filtering system where:
* - Keys are entity properties
* - Values can be exact matches or undefined (no filter)
*
* This enables type-safe, partial filtering of entities.
*
* @example
* ```ts
* interface Product {
* category: string
* inStock: boolean
* price: number
* }
*
* const filters: FilterConfig<Product> = {
* category: 'electronics',
* inStock: true
* // price is omitted (not filtered)
* }
* ```
*
* @public
*/
export type FilterConfig<T> = Partial<T>
/**
* Statistics result containing aggregate metrics.
*
* @remarks
* Common return type for service methods that compute statistics.
* Includes:
* - Total count
* - Category/group breakdowns
* - Additional metrics as key-value pairs
*
* @example
* ```ts
* const stats: StatsResult = {
* total: 150,
* byCategory: {
* active: 120,
* inactive: 30
* },
* averageAge: 2.5,
* newestItem: {...}
* }
* ```
*
* @public
*/
export interface StatsResult {
/** Total count of items */
total: number
/** Breakdown by categories */
byCategory?: Record<string, number>
/** Additional computed metrics */
[key: string]: number | Record<string, number> | unknown | undefined
}
/**
* Date range configuration for time-based queries.
*
* @remarks
* Supports both absolute dates and relative ranges.
* Used for filtering time-series data.
*
* @example
* ```ts
* // Absolute range
* const range: DateRangeConfig = {
* start: new Date('2025-01-01'),
* end: new Date('2025-01-31')
* }
*
* // Relative range
* const lastMonth: DateRangeConfig = {
* relativeDays: 30
* }
* ```
*
* @public
*/
export interface DateRangeConfig {
/** Start date (inclusive) */
start?: Date
/** End date (inclusive) */
end?: Date
/** Relative days from now (alternative to start/end) */
relativeDays?: number
/** Relative months from now (alternative to start/end) */
relativeMonths?: number
}
/**
* Pagination configuration for list operations.
*
* @remarks
* Standard pagination pattern with:
* - Page number (1-indexed)
* - Items per page
* - Optional offset-based pagination
*
* @example
* ```ts
* // Page-based pagination
* const config: PaginationConfig = {
* page: 2,
* pageSize: 25
* }
*
* // Offset-based pagination
* const offsetConfig: PaginationConfig = {
* page: 1,
* pageSize: 25,
* offset: 50
* }
* ```
*
* @public
*/
export interface PaginationConfig {
/** Current page number (1-indexed) */
page: number
/** Number of items per page */
pageSize: number
/** Optional offset for cursor-based pagination */
offset?: number
}
/**
* Paginated result set with metadata.
*
* @template T - Type of items in the result
*
* @remarks
* Standard response format for paginated queries, including:
* - Data items
* - Total count
* - Current page info
* - Navigation metadata
*
* @example
* ```ts
* const result: PaginatedResult<User> = {
* items: [...],
* total: 150,
* page: 2,
* pageSize: 25,
* totalPages: 6,
* hasMore: true
* }
* ```
*
* @public
*/
export interface PaginatedResult<T> {
/** Items for current page */
items: T[]
/** Total count across all pages */
total: number
/** Current page number */
page: number
/** Items per page */
pageSize: number
/** Total number of pages */
totalPages: number
/** Whether more pages exist */
hasMore: boolean
}
/**
* Query options combining common patterns.
*
* @template T - Entity type being queried
*
* @remarks
* Comprehensive query configuration supporting:
* - Filtering
* - Sorting
* - Pagination
* - Date ranges
*
* Designed for flexible, composable queries.
*
* @example
* ```ts
* interface Product {
* name: string
* price: number
* createdAt: Date
* }
*
* const options: QueryOptions<Product> = {
* filters: { price: 50 },
* sort: { sortBy: 'createdAt', order: 'desc' },
* pagination: { page: 1, pageSize: 20 },
* dateRange: { relativeDays: 30 }
* }
* ```
*
* @public
*/
export interface QueryOptions<T> {
/** Filter configuration */
filters?: FilterConfig<T>
/** Sort configuration */
sort?: SortConfig<T>
/** Pagination settings */
pagination?: PaginationConfig
/** Date range filter */
dateRange?: DateRangeConfig
}
/**
* Service method result wrapper with metadata.
*
* @template T - Type of the result data
*
* @remarks
* Standard envelope for service responses, providing:
* - Success/failure status
* - Result data or error
* - Optional metadata
*
* Enables consistent error handling across services.
*
* @example
* ```ts
* // Success result
* const success: ServiceResult<User> = {
* success: true,
* data: { id: 1, name: 'Alice' }
* }
*
* // Error result
* const error: ServiceResult<User> = {
* success: false,
* error: 'User not found'
* }
*
* // With metadata
* const withMeta: ServiceResult<User[]> = {
* success: true,
* data: [...],
* metadata: { cached: true, timestamp: Date.now() }
* }
* ```
*
* @public
*/
export interface ServiceResult<T> {
/** Whether the operation succeeded */
success: boolean
/** Result data (present if success=true) */
data?: T
/** Error message (present if success=false) */
error?: string
/** Optional metadata about the operation */
metadata?: Record<string, unknown>
}
/**
* Comparison operator for advanced filtering.
*
* @remarks
* Used in complex filter expressions to specify:
* - Equality checks
* - Range queries
* - Existence checks
*
* @example
* ```ts
* const op: FilterOperator = 'gte' // Greater than or equal
* ```
*
* @public
*/
export type FilterOperator = 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'nin' | 'exists'
/**
* Advanced filter expression with operators.
*
* @template T - Value type being filtered
*
* @remarks
* Enables complex query expressions beyond simple equality.
* Similar to MongoDB query syntax.
*
* @example
* ```ts
* // Range filter
* const ageFilter: FilterExpression<number> = {
* operator: 'gte',
* value: 18
* }
*
* // Array inclusion filter
* const statusFilter: FilterExpression<string> = {
* operator: 'in',
* value: ['active', 'pending']
* }
* ```
*
* @public
*/
export interface FilterExpression<T> {
/** Comparison operator */
operator: FilterOperator
/** Value to compare against */
value: T | T[]
}