feat (v1.0.0): initial refactor and redesign
This commit is contained in:
parent
3058aa1ab4
commit
fe9b50b30e
134 changed files with 17792 additions and 3670 deletions
178
lib/domains/config.ts
Normal file
178
lib/domains/config.ts
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
/**
|
||||
* Domain visual configuration for status and category badges, icons, and colors.
|
||||
*
|
||||
* @remarks
|
||||
* This module provides the centralized visual configuration for domain portfolio display,
|
||||
* including icons, colors, backgrounds, and borders for all domain statuses and categories.
|
||||
* All color values use Tailwind CSS classes for consistency with the theme system.
|
||||
*
|
||||
* @module lib/domains/config
|
||||
* @category Domains
|
||||
* @public
|
||||
*/
|
||||
|
||||
import {
|
||||
CheckCircle,
|
||||
Archive,
|
||||
Construction,
|
||||
User,
|
||||
Briefcase,
|
||||
Rocket,
|
||||
PartyPopper,
|
||||
Package
|
||||
} from 'lucide-react'
|
||||
import type { DomainVisualConfig } from '@/lib/types'
|
||||
|
||||
/**
|
||||
* Visual configuration for domain status and category display.
|
||||
*
|
||||
* @remarks
|
||||
* Provides a complete mapping of domain statuses and categories to their visual representation.
|
||||
* Used throughout the domain portfolio for consistent badge styling, icons, and colors.
|
||||
*
|
||||
* **Configuration includes:**
|
||||
* - **status**: Visual config for active, parked, and reserved domains
|
||||
* - **category**: Visual config for personal, service, project, fun, and legacy domains
|
||||
*
|
||||
* Each configuration includes:
|
||||
* - `label`: Human-readable display text
|
||||
* - `icon`: Lucide React icon component
|
||||
* - `color`: Tailwind text color class
|
||||
* - `bg`: Tailwind background color class (semi-transparent)
|
||||
* - `border`: Tailwind border color class (semi-transparent)
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* import { domainVisualConfig } from '@/lib/domains/config'
|
||||
*
|
||||
* const DomainStatusBadge = ({ status }: { status: DomainStatus }) => {
|
||||
* const config = domainVisualConfig.status[status]
|
||||
* const Icon = config.icon
|
||||
*
|
||||
* return (
|
||||
* <span className={`${config.color} ${config.bg} ${config.border}`}>
|
||||
* <Icon className="w-4 h-4" />
|
||||
* {config.label}
|
||||
* </span>
|
||||
* )
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @category Domains
|
||||
* @public
|
||||
*/
|
||||
export const domainVisualConfig: DomainVisualConfig = {
|
||||
status: {
|
||||
active: {
|
||||
label: 'Active',
|
||||
icon: CheckCircle,
|
||||
color: 'text-slate-400',
|
||||
bg: 'bg-slate-500/10',
|
||||
border: 'border-slate-500/20'
|
||||
},
|
||||
parked: {
|
||||
label: 'Parked',
|
||||
icon: Archive,
|
||||
color: 'text-gray-400',
|
||||
bg: 'bg-gray-600/10',
|
||||
border: 'border-gray-600/20'
|
||||
},
|
||||
reserved: {
|
||||
label: 'Reserved',
|
||||
icon: Construction,
|
||||
color: 'text-slate-400',
|
||||
bg: 'bg-slate-600/10',
|
||||
border: 'border-slate-600/20'
|
||||
}
|
||||
},
|
||||
category: {
|
||||
personal: {
|
||||
label: 'Personal',
|
||||
icon: User,
|
||||
color: 'text-slate-400',
|
||||
bg: 'bg-slate-500/10',
|
||||
border: 'border-slate-500/20'
|
||||
},
|
||||
service: {
|
||||
label: 'Service',
|
||||
icon: Briefcase,
|
||||
color: 'text-slate-400',
|
||||
bg: 'bg-slate-500/10',
|
||||
border: 'border-slate-500/20'
|
||||
},
|
||||
project: {
|
||||
label: 'Project',
|
||||
icon: Rocket,
|
||||
color: 'text-slate-400',
|
||||
bg: 'bg-slate-500/10',
|
||||
border: 'border-slate-500/20'
|
||||
},
|
||||
fun: {
|
||||
label: 'Fun',
|
||||
icon: PartyPopper,
|
||||
color: 'text-slate-400',
|
||||
bg: 'bg-slate-500/10',
|
||||
border: 'border-slate-500/20'
|
||||
},
|
||||
legacy: {
|
||||
label: 'Legacy',
|
||||
icon: Package,
|
||||
color: 'text-slate-400',
|
||||
bg: 'bg-slate-500/10',
|
||||
border: 'border-slate-500/20'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Available sort options for domain list displays.
|
||||
*
|
||||
* @remarks
|
||||
* Provides a predefined list of sort options for domain portfolio UI components.
|
||||
* Each option includes a value (for logic) and label (for display).
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* import { sortOptions } from '@/lib/domains/config'
|
||||
*
|
||||
* const DomainSortSelect = () => (
|
||||
* <select>
|
||||
* {sortOptions.map(option => (
|
||||
* <option key={option.value} value={option.value}>
|
||||
* {option.label}
|
||||
* </option>
|
||||
* ))}
|
||||
* </select>
|
||||
* )
|
||||
* ```
|
||||
*
|
||||
* @category Domains
|
||||
* @public
|
||||
*/
|
||||
export const sortOptions = [
|
||||
{ value: 'name', label: 'Name (A-Z)' },
|
||||
{ value: 'expiration', label: 'Expiration Date' },
|
||||
{ value: 'ownership', label: 'Ownership Duration' },
|
||||
{ value: 'registrar', label: 'Registrar' }
|
||||
] as const
|
||||
|
||||
/**
|
||||
* Days threshold for "expiring soon" warning indicators.
|
||||
*
|
||||
* @remarks
|
||||
* Domains expiring within this many days should display warning indicators.
|
||||
* Default is 30 days.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { expirationThreshold } from '@/lib/domains/config'
|
||||
* import { getDaysUntilExpiration } from '@/lib/domains/utils'
|
||||
*
|
||||
* const daysUntil = getDaysUntilExpiration(domain)
|
||||
* const isExpiringSoon = daysUntil <= expirationThreshold
|
||||
* ```
|
||||
*
|
||||
* @category Domains
|
||||
* @public
|
||||
*/
|
||||
export const expirationThreshold = 30 // days
|
||||
222
lib/domains/data.ts
Normal file
222
lib/domains/data.ts
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
import { SiNamecheap, SiSpaceship } from 'react-icons/si'
|
||||
import NameIcon from '@/components/icons/NameIcon'
|
||||
import DynadotIcon from '@/components/icons/DynadotIcon'
|
||||
import { RegistrarConfig, Domain } from '@/lib/types'
|
||||
|
||||
export const registrars: Record<string, RegistrarConfig> = {
|
||||
'Spaceship': {
|
||||
name: 'Spaceship',
|
||||
icon: SiSpaceship,
|
||||
color: 'text-white'
|
||||
},
|
||||
'Namecheap': {
|
||||
name: 'Namecheap',
|
||||
icon: SiNamecheap,
|
||||
color: 'text-orange-400'
|
||||
},
|
||||
'Name.com': {
|
||||
name: 'Name.com',
|
||||
icon: NameIcon,
|
||||
color: 'text-green-300'
|
||||
},
|
||||
'Dynadot': {
|
||||
name: 'Dynadot',
|
||||
icon: DynadotIcon,
|
||||
color: 'text-blue-400'
|
||||
},
|
||||
}
|
||||
|
||||
export const domains: Domain[] = [
|
||||
{
|
||||
domain: "aidan.so",
|
||||
usage: "The home of my primary website",
|
||||
registrar: "Dynadot",
|
||||
autoRenew: false,
|
||||
status: "active",
|
||||
category: "personal",
|
||||
tags: ["homepage", "nextjs"],
|
||||
renewals: [
|
||||
{ date: "2025-10-09", years: 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
domain: "aidxn.cc",
|
||||
usage: "The old domain of my primary website",
|
||||
registrar: "Spaceship",
|
||||
autoRenew: false,
|
||||
status: "active",
|
||||
category: "personal",
|
||||
tags: ["homepage", "nextjs"],
|
||||
renewals: [
|
||||
{ date: "2025-01-04", years: 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
domain: "pontushost.com",
|
||||
usage: "My hosting provider project",
|
||||
registrar: "Spaceship",
|
||||
autoRenew: false,
|
||||
status: "active",
|
||||
category: "service",
|
||||
tags: ["hosting", "services"],
|
||||
renewals: [
|
||||
{ date: "2025-08-18", years: 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
domain: "disfunction.blog",
|
||||
usage: "My blog's official home",
|
||||
registrar: "Spaceship",
|
||||
autoRenew: false,
|
||||
status: "active",
|
||||
category: "personal",
|
||||
tags: ["blog"],
|
||||
renewals: [
|
||||
{ date: "2025-02-02", years: 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
domain: "androidintegrity.org",
|
||||
usage: "A project to fix Play Integrity",
|
||||
registrar: "Spaceship",
|
||||
autoRenew: false,
|
||||
status: "reserved",
|
||||
category: "project",
|
||||
tags: ["android", "open-source"],
|
||||
renewals: [
|
||||
{ date: "2024-11-24", years: 3 },
|
||||
]
|
||||
},
|
||||
{
|
||||
domain: "librecloud.cc",
|
||||
usage: "My old cloud services provider project",
|
||||
registrar: "Spaceship",
|
||||
autoRenew: false,
|
||||
status: "parked",
|
||||
category: "legacy",
|
||||
tags: ["cloud", "services"],
|
||||
renewals: [
|
||||
{ date: "2025-02-02", years: 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
domain: "ihate.college",
|
||||
usage: "One of my fun domains, used for p0ntus mail and services",
|
||||
registrar: "Spaceship",
|
||||
autoRenew: false,
|
||||
status: "active",
|
||||
category: "project",
|
||||
tags: ["email", "humor", "vanity"],
|
||||
renewals: [
|
||||
{ date: "2025-01-05", years: 1 },
|
||||
]
|
||||
},
|
||||
{
|
||||
domain: "pontus.pics",
|
||||
usage: "An unused domain for an upcoming image hosting service",
|
||||
registrar: "Spaceship",
|
||||
autoRenew: false,
|
||||
status: "reserved",
|
||||
category: "project",
|
||||
tags: ["images", "hosting", "future"],
|
||||
renewals: [
|
||||
{ date: "2024-12-17", years: 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
domain: "p0ntus.com",
|
||||
usage: "My active cloud services project",
|
||||
registrar: "Spaceship",
|
||||
autoRenew: false,
|
||||
status: "active",
|
||||
category: "service",
|
||||
tags: ["cloud", "services"],
|
||||
renewals: [
|
||||
{ date: "2024-11-14", years: 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
domain: "modules.lol",
|
||||
usage: "An 'app store' of Magisk modules and FOSS Android apps",
|
||||
registrar: "Spaceship",
|
||||
autoRenew: false,
|
||||
status: "active",
|
||||
category: "project",
|
||||
tags: ["android", "apps", "open-source"],
|
||||
renewals: [
|
||||
{ date: "2024-12-17", years: 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
domain: "dontbeevil.lol",
|
||||
usage: "A public Matrix homeserver",
|
||||
registrar: "Namecheap",
|
||||
autoRenew: false,
|
||||
status: "active",
|
||||
category: "project",
|
||||
tags: ["matrix", "services", "humor", "vanity"],
|
||||
renewals: [
|
||||
{ date: "2025-01-08", years: 1 },
|
||||
]
|
||||
},
|
||||
{
|
||||
domain: "wikitools.cloud",
|
||||
usage: "Unused (for now!)",
|
||||
registrar: "Namecheap",
|
||||
autoRenew: false,
|
||||
status: "reserved",
|
||||
category: "project",
|
||||
tags: ["tools", "wiki", "future"],
|
||||
renewals: [
|
||||
{ date: "2025-01-04", years: 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
domain: "dont-be-evil.lol",
|
||||
usage: "A joke domain for p0ntus mail",
|
||||
registrar: "Spaceship",
|
||||
autoRenew: false,
|
||||
status: "parked",
|
||||
category: "fun",
|
||||
tags: ["email", "humor", "vanity"],
|
||||
renewals: [
|
||||
{ date: "2025-01-08", years: 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
domain: "pontusmail.org",
|
||||
usage: "An email domain for p0ntus Mail",
|
||||
registrar: "Spaceship",
|
||||
autoRenew: false,
|
||||
status: "active",
|
||||
category: "service",
|
||||
tags: ["email", "services"],
|
||||
renewals: [
|
||||
{ date: "2025-12-17", years: 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
domain: "strongintegrity.life",
|
||||
usage: "A Play Integrity meme domain used for p0ntus mail (now inactive)",
|
||||
registrar: "Spaceship",
|
||||
autoRenew: false,
|
||||
status: "reserved",
|
||||
category: "fun",
|
||||
tags: ["email", "humor", "android"],
|
||||
renewals: [
|
||||
{ date: "2025-01-08", years: 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
domain: "kowalski.social",
|
||||
usage: "A domain for ABOCN's Kowalski project",
|
||||
registrar: "Name.com",
|
||||
autoRenew: true,
|
||||
status: "active",
|
||||
category: "project",
|
||||
tags: ["social", "abocn"],
|
||||
renewals: [
|
||||
{ date: "2025-07-03", years: 1 }
|
||||
]
|
||||
}
|
||||
]
|
||||
590
lib/domains/utils.ts
Normal file
590
lib/domains/utils.ts
Normal file
|
|
@ -0,0 +1,590 @@
|
|||
/**
|
||||
* Domain utility functions for date calculations and data enrichment.
|
||||
*
|
||||
* @remarks
|
||||
* This module provides convenience wrapper functions around DomainService methods,
|
||||
* offering a simpler API for common domain operations like date calculations,
|
||||
* ownership metrics, and timeline generation.
|
||||
*
|
||||
* **Key features:**
|
||||
* - Date extraction (registration, expiration, renewal)
|
||||
* - Ownership duration calculations (years, months, days)
|
||||
* - Expiration warnings and progress tracking
|
||||
* - Timeline event generation
|
||||
* - TLD extraction
|
||||
*
|
||||
* **Design pattern:**
|
||||
* Most functions delegate to {@link DomainService.enrichDomain} which computes
|
||||
* all metrics at once, making these wrappers efficient for accessing individual metrics.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { getOwnershipDuration, isExpiringSoon } from '@/lib/domains/utils'
|
||||
*
|
||||
* const years = getOwnershipDuration(domain)
|
||||
* const expiring = isExpiringSoon(domain, 90) // 90 days threshold
|
||||
* ```
|
||||
*
|
||||
* @module lib/domains/utils
|
||||
* @category Domains
|
||||
* @public
|
||||
*/
|
||||
|
||||
import type { Domain } from '@/lib/types'
|
||||
import { DomainService } from '@/lib/services'
|
||||
import type { DomainTimelineEvent } from '@/lib/types/domain'
|
||||
|
||||
export type { Domain, Renewal } from '@/lib/types'
|
||||
|
||||
/**
|
||||
* Gets the registration date for a domain.
|
||||
* @param domain - Domain object
|
||||
* @returns Registration date
|
||||
* @see {@link DomainService.enrichDomain}
|
||||
* @category Domains
|
||||
* @public
|
||||
*/
|
||||
export function getRegistrationDate(domain: Domain): Date {
|
||||
const enriched = DomainService.enrichDomain(domain)
|
||||
return enriched.registrationDate
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the expiration date for a domain based on renewal history.
|
||||
*
|
||||
* @param domain - Domain object with renewal records
|
||||
* @returns Computed expiration date
|
||||
*
|
||||
* @remarks
|
||||
* This function delegates to {@link DomainService.enrichDomain} which computes
|
||||
* the expiration date by summing all renewal periods from the initial registration.
|
||||
* The calculation accounts for multi-year renewals and uses the last renewal's
|
||||
* end date as the expiration.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { getExpirationDate } from '@/lib/domains/utils'
|
||||
*
|
||||
* const expirationDate = getExpirationDate(domain)
|
||||
* console.log(expirationDate) // 2026-03-15T00:00:00.000Z
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Check if domain is expired
|
||||
* const expDate = getExpirationDate(domain)
|
||||
* const isExpired = expDate < new Date()
|
||||
* if (isExpired) {
|
||||
* console.log('Domain has expired!')
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @see {@link DomainService.enrichDomain}
|
||||
* @category Domains
|
||||
* @public
|
||||
*/
|
||||
export function getExpirationDate(domain: Domain): Date {
|
||||
const enriched = DomainService.enrichDomain(domain)
|
||||
return enriched.expirationDate
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ownership duration in years for a domain.
|
||||
*
|
||||
* @param domain - Domain object with renewal history
|
||||
* @returns Ownership duration in whole years (floor value)
|
||||
*
|
||||
* @remarks
|
||||
* This function calculates how many full years the domain has been owned
|
||||
* since the initial registration date. The value is floored, so a domain
|
||||
* owned for 2 years and 11 months returns 2.
|
||||
*
|
||||
* Uses {@link DomainService.enrichDomain} for efficient metric computation.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { getOwnershipDuration } from '@/lib/domains/utils'
|
||||
*
|
||||
* const years = getOwnershipDuration(domain)
|
||||
* console.log(`Owned for ${years} years`) // "Owned for 3 years"
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Display ownership milestone
|
||||
* const years = getOwnershipDuration(domain)
|
||||
* if (years >= 5) {
|
||||
* console.log('Long-term domain!')
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @see {@link DomainService.enrichDomain}
|
||||
* @see {@link getOwnershipMonths} For month-level precision
|
||||
* @see {@link getOwnershipDays} For day-level precision
|
||||
* @category Domains
|
||||
* @public
|
||||
*/
|
||||
export function getOwnershipDuration(domain: Domain): number {
|
||||
const enriched = DomainService.enrichDomain(domain)
|
||||
return enriched.ownershipYears
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ownership duration in months for a domain.
|
||||
*
|
||||
* @param domain - Domain object with renewal history
|
||||
* @returns Total ownership duration in months
|
||||
*
|
||||
* @remarks
|
||||
* This function calculates the precise number of months between the registration
|
||||
* date and the current date. Month boundaries are respected - if the current
|
||||
* day is earlier than the registration day, the month count is decremented.
|
||||
*
|
||||
* For year-level precision, use {@link getOwnershipDuration}.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { getOwnershipMonths } from '@/lib/domains/utils'
|
||||
*
|
||||
* const months = getOwnershipMonths(domain)
|
||||
* console.log(`Owned for ${months} months`) // "Owned for 38 months"
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Calculate years and remaining months
|
||||
* const totalMonths = getOwnershipMonths(domain)
|
||||
* const years = Math.floor(totalMonths / 12)
|
||||
* const months = totalMonths % 12
|
||||
* console.log(`${years}y ${months}mo`) // "3y 2mo"
|
||||
* ```
|
||||
*
|
||||
* @see {@link DomainService.enrichDomain}
|
||||
* @see {@link getOwnershipDuration} For year-level precision
|
||||
* @see {@link getOwnershipDays} For day-level precision
|
||||
* @category Domains
|
||||
* @public
|
||||
*/
|
||||
export function getOwnershipMonths(domain: Domain): number {
|
||||
const enriched = DomainService.enrichDomain(domain)
|
||||
return enriched.ownershipMonths
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ownership duration in days for a domain.
|
||||
*
|
||||
* @param domain - Domain object with renewal history
|
||||
* @returns Total ownership duration in days
|
||||
*
|
||||
* @remarks
|
||||
* This function provides the most precise ownership duration metric by
|
||||
* calculating the exact number of days between registration and the current date.
|
||||
* Uses ceiling to ensure partial days are counted.
|
||||
*
|
||||
* For less granular metrics, use {@link getOwnershipDuration} or {@link getOwnershipMonths}.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { getOwnershipDays } from '@/lib/domains/utils'
|
||||
*
|
||||
* const days = getOwnershipDays(domain)
|
||||
* console.log(`Owned for ${days} days`) // "Owned for 1,247 days"
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Check if domain is newly registered
|
||||
* const days = getOwnershipDays(domain)
|
||||
* if (days < 30) {
|
||||
* console.log('New domain!')
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @see {@link DomainService.enrichDomain}
|
||||
* @see {@link getOwnershipDuration} For year-level precision
|
||||
* @see {@link getOwnershipMonths} For month-level precision
|
||||
* @category Domains
|
||||
* @public
|
||||
*/
|
||||
export function getOwnershipDays(domain: Domain): number {
|
||||
const enriched = DomainService.enrichDomain(domain)
|
||||
return enriched.ownershipDays
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the total renewal period in days from registration to expiration.
|
||||
*
|
||||
* @param domain - Domain object with renewal history
|
||||
* @returns Total days from initial registration to current expiration date
|
||||
*
|
||||
* @remarks
|
||||
* This function computes the complete lifecycle span of all renewals combined.
|
||||
* Unlike {@link getOwnershipDays} which measures time from registration to now,
|
||||
* this measures the total committed period (registration to expiration).
|
||||
*
|
||||
* Useful for calculating average renewal period length or total commitment duration.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { getTotalRenewalPeriodDays } from '@/lib/domains/utils'
|
||||
*
|
||||
* const totalDays = getTotalRenewalPeriodDays(domain)
|
||||
* console.log(`Total period: ${totalDays} days`) // "Total period: 1,825 days"
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Calculate average renewal length
|
||||
* const totalDays = getTotalRenewalPeriodDays(domain)
|
||||
* const renewalCount = domain.renewals.length
|
||||
* const avgDays = totalDays / renewalCount
|
||||
* console.log(`Avg renewal: ${Math.round(avgDays / 365)} years`)
|
||||
* ```
|
||||
*
|
||||
* @see {@link getOwnershipDays} For current ownership duration
|
||||
* @see {@link getDaysUntilExpiration} For remaining days
|
||||
* @category Domains
|
||||
* @public
|
||||
*/
|
||||
export function getTotalRenewalPeriodDays(domain: Domain): number {
|
||||
const registrationDate = getRegistrationDate(domain)
|
||||
const expirationDate = getExpirationDate(domain)
|
||||
const diffTime = expirationDate.getTime() - registrationDate.getTime()
|
||||
return Math.ceil(diffTime / (1000 * 60 * 60 * 24))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the renewal progress as a percentage (0-100).
|
||||
*
|
||||
* @param domain - Domain object with renewal history
|
||||
* @returns Percentage of current renewal period elapsed (0-100)
|
||||
*
|
||||
* @remarks
|
||||
* This function calculates how far through the total renewal period
|
||||
* the domain ownership currently is. The value represents the percentage
|
||||
* from registration date to expiration date.
|
||||
*
|
||||
* - 0% = just registered
|
||||
* - 50% = halfway through renewal period
|
||||
* - 100% = at or past expiration
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { getRenewalProgress } from '@/lib/domains/utils'
|
||||
*
|
||||
* const progress = getRenewalProgress(domain)
|
||||
* console.log(`${progress.toFixed(1)}% complete`) // "67.3% complete"
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Display progress bar
|
||||
* const progress = getRenewalProgress(domain)
|
||||
* const barWidth = Math.round(progress)
|
||||
* console.log('█'.repeat(barWidth) + '░'.repeat(100 - barWidth))
|
||||
* ```
|
||||
*
|
||||
* @see {@link DomainService.enrichDomain}
|
||||
* @see {@link getDaysUntilExpiration} For days remaining
|
||||
* @category Domains
|
||||
* @public
|
||||
*/
|
||||
export function getRenewalProgress(domain: Domain): number {
|
||||
const enriched = DomainService.enrichDomain(domain)
|
||||
return enriched.renewalProgressPercent
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of days until domain expiration.
|
||||
*
|
||||
* @param domain - Domain object with renewal history
|
||||
* @returns Days until expiration (negative if already expired)
|
||||
*
|
||||
* @remarks
|
||||
* This function calculates days from the current date to the domain's
|
||||
* expiration date. The value can be:
|
||||
*
|
||||
* - Positive: Days remaining before expiration
|
||||
* - Zero: Expires today
|
||||
* - Negative: Days since expiration (domain expired)
|
||||
*
|
||||
* Commonly used with {@link isExpiringSoon} for renewal alerts.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { getDaysUntilExpiration } from '@/lib/domains/utils'
|
||||
*
|
||||
* const days = getDaysUntilExpiration(domain)
|
||||
* console.log(`${days} days until expiration`) // "45 days until expiration"
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Display appropriate message based on status
|
||||
* const days = getDaysUntilExpiration(domain)
|
||||
* if (days < 0) {
|
||||
* console.log(`Expired ${Math.abs(days)} days ago!`)
|
||||
* } else if (days === 0) {
|
||||
* console.log('Expires today!')
|
||||
* } else if (days < 30) {
|
||||
* console.log(`Renew soon - ${days} days left`)
|
||||
* } else {
|
||||
* console.log(`${days} days remaining`)
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @see {@link DomainService.enrichDomain}
|
||||
* @see {@link isExpiringSoon} For boolean expiration check
|
||||
* @category Domains
|
||||
* @public
|
||||
*/
|
||||
export function getDaysUntilExpiration(domain: Domain): number {
|
||||
const enriched = DomainService.enrichDomain(domain)
|
||||
return enriched.daysUntilExpiration
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a domain is expiring soon based on a configurable threshold.
|
||||
*
|
||||
* @param domain - Domain object with renewal history
|
||||
* @param thresholdDays - Number of days to consider "expiring soon" (default: 90)
|
||||
* @returns True if domain expires within the threshold period
|
||||
*
|
||||
* @remarks
|
||||
* This function provides a simple boolean check for domains requiring renewal attention.
|
||||
* The default 90-day threshold is typical for domain renewal planning, but can be
|
||||
* adjusted based on your renewal workflow.
|
||||
*
|
||||
* Returns true if:
|
||||
* - Domain expires in fewer days than the threshold
|
||||
* - Domain is already expired (negative days)
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { isExpiringSoon } from '@/lib/domains/utils'
|
||||
*
|
||||
* // Check with default 90-day threshold
|
||||
* if (isExpiringSoon(domain)) {
|
||||
* console.log('Renewal required soon!')
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Custom threshold for urgent renewals
|
||||
* const urgent = isExpiringSoon(domain, 30)
|
||||
* const warning = isExpiringSoon(domain, 90)
|
||||
*
|
||||
* if (urgent) {
|
||||
* console.log('🚨 URGENT: Renew within 30 days!')
|
||||
* } else if (warning) {
|
||||
* console.log('⚠️ Renewal recommended')
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Filter domains needing renewal
|
||||
* const domains = DomainService.getAllDomains()
|
||||
* const needsRenewal = domains.filter(d => isExpiringSoon(d, 60))
|
||||
* console.log(`${needsRenewal.length} domains need renewal`)
|
||||
* ```
|
||||
*
|
||||
* @see {@link getDaysUntilExpiration} For exact day count
|
||||
* @category Domains
|
||||
* @public
|
||||
*/
|
||||
export function isExpiringSoon(domain: Domain, thresholdDays: number = 90): boolean {
|
||||
return getDaysUntilExpiration(domain) <= thresholdDays
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a Date object to a human-readable string.
|
||||
*
|
||||
* @param date - Date object to format
|
||||
* @returns Formatted date string in "MMM DD, YYYY" format (e.g., "Jan 15, 2025")
|
||||
*
|
||||
* @remarks
|
||||
* Uses US locale formatting with abbreviated month names. The format is
|
||||
* consistent across the application for displaying domain-related dates.
|
||||
*
|
||||
* Output format: "Month Day, Year" where:
|
||||
* - Month: 3-letter abbreviation (Jan, Feb, Mar, etc.)
|
||||
* - Day: Numeric day of month
|
||||
* - Year: Full 4-digit year
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { formatDate } from '@/lib/domains/utils'
|
||||
*
|
||||
* const date = new Date('2025-03-15')
|
||||
* console.log(formatDate(date)) // "Mar 15, 2025"
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Format expiration date for display
|
||||
* import { getExpirationDate, formatDate } from '@/lib/domains/utils'
|
||||
*
|
||||
* const expDate = getExpirationDate(domain)
|
||||
* const formatted = formatDate(expDate)
|
||||
* console.log(`Expires: ${formatted}`) // "Expires: Dec 31, 2026"
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Format all renewal dates
|
||||
* domain.renewals.forEach(renewal => {
|
||||
* const date = formatDate(new Date(renewal.date))
|
||||
* console.log(`${date}: ${renewal.years} year(s)`)
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* @category Domains
|
||||
* @public
|
||||
*/
|
||||
export function formatDate(date: Date): string {
|
||||
return date.toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a timeline of renewal events from domain renewal history.
|
||||
*
|
||||
* @param domain - Domain object with renewal records
|
||||
* @returns Array of timeline events with dates, types, and renewal periods
|
||||
*
|
||||
* @remarks
|
||||
* This function transforms the domain's renewal array into a structured
|
||||
* timeline suitable for visualization. Each event includes:
|
||||
*
|
||||
* - `date`: Date object of the renewal/registration
|
||||
* - `type`: 'registration' for first renewal, 'renewal' for subsequent ones
|
||||
* - `years`: Number of years renewed for that period
|
||||
*
|
||||
* The first renewal in the array is always treated as the initial registration.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { getRenewalTimeline } from '@/lib/domains/utils'
|
||||
*
|
||||
* const timeline = getRenewalTimeline(domain)
|
||||
* timeline.forEach(event => {
|
||||
* console.log(`${event.type}: ${event.date} (${event.years}y)`)
|
||||
* })
|
||||
* // Output:
|
||||
* // registration: 2020-01-15 (2y)
|
||||
* // renewal: 2022-01-15 (3y)
|
||||
* // renewal: 2025-01-15 (1y)
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Build visual timeline
|
||||
* const timeline = getRenewalTimeline(domain)
|
||||
* const formatted = timeline.map(event => ({
|
||||
* ...event,
|
||||
* icon: event.type === 'registration' ? '🎯' : '🔄',
|
||||
* label: `${formatDate(event.date)} - ${event.years} year(s)`
|
||||
* }))
|
||||
* ```
|
||||
*
|
||||
* @see {@link DomainTimelineEvent} For event type definition
|
||||
* @category Domains
|
||||
* @public
|
||||
*/
|
||||
export function getRenewalTimeline(domain: Domain): DomainTimelineEvent[] {
|
||||
return domain.renewals.map((renewal, index) => ({
|
||||
date: new Date(renewal.date),
|
||||
type: index === 0 ? 'registration' : 'renewal',
|
||||
years: renewal.years
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the top-level domain (TLD) from a domain name.
|
||||
*
|
||||
* @param domain - Domain object
|
||||
* @returns TLD including the dot (e.g., '.com', '.dev', '.co.uk')
|
||||
*
|
||||
* @remarks
|
||||
* This function extracts the TLD portion of the domain name, which includes
|
||||
* the dot separator. For complex TLDs like '.co.uk', only the final segment
|
||||
* is returned ('.uk').
|
||||
*
|
||||
* Uses {@link DomainService.enrichDomain} for consistent TLD extraction logic.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { getTLD } from '@/lib/domains/utils'
|
||||
*
|
||||
* const domain = { domain: 'example.com', ... }
|
||||
* console.log(getTLD(domain)) // ".com"
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Group domains by TLD
|
||||
* const domains = DomainService.getAllDomains()
|
||||
* const byTLD = domains.reduce((acc, d) => {
|
||||
* const tld = getTLD(d)
|
||||
* acc[tld] = (acc[tld] || 0) + 1
|
||||
* return acc
|
||||
* }, {} as Record<string, number>)
|
||||
* console.log(byTLD) // { '.com': 15, '.dev': 3, '.io': 2 }
|
||||
* ```
|
||||
*
|
||||
* @see {@link DomainService.enrichDomain}
|
||||
* @category Domains
|
||||
* @public
|
||||
*/
|
||||
export function getTLD(domain: Domain): string {
|
||||
const enriched = DomainService.enrichDomain(domain)
|
||||
return enriched.tld
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next renewal date for a domain (alias for expiration date).
|
||||
*
|
||||
* @param domain - Domain object with renewal history
|
||||
* @returns Next renewal date (same as expiration date)
|
||||
*
|
||||
* @remarks
|
||||
* This function is a semantic alias for {@link getExpirationDate}. Both return
|
||||
* the same value - the date when the current renewal period ends and the domain
|
||||
* must be renewed to avoid expiration.
|
||||
*
|
||||
* Use this function when you want to emphasize the "renewal" aspect rather than
|
||||
* the "expiration" aspect of the date.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { getNextRenewalDate } from '@/lib/domains/utils'
|
||||
*
|
||||
* const renewalDate = getNextRenewalDate(domain)
|
||||
* console.log(`Next renewal: ${renewalDate}`) // "Next renewal: 2026-03-15..."
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Calculate time until next renewal
|
||||
* import { getNextRenewalDate, formatDate } from '@/lib/domains/utils'
|
||||
*
|
||||
* const nextRenewal = getNextRenewalDate(domain)
|
||||
* const daysUntil = Math.ceil((nextRenewal.getTime() - Date.now()) / (1000 * 60 * 60 * 24))
|
||||
* console.log(`Renewal in ${daysUntil} days (${formatDate(nextRenewal)})`)
|
||||
* ```
|
||||
*
|
||||
* @see {@link DomainService.enrichDomain}
|
||||
* @see {@link getExpirationDate} For the same value with different semantics
|
||||
* @see {@link getDaysUntilExpiration} For days until renewal needed
|
||||
* @category Domains
|
||||
* @public
|
||||
*/
|
||||
export function getNextRenewalDate(domain: Domain): Date {
|
||||
const enriched = DomainService.enrichDomain(domain)
|
||||
return enriched.nextRenewalDate
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue