feat: centralise date/time formatting with European conventions
All dates now use day-first ordering (14 Mar 2025) and 24-hour time (14:30) via a single formatDate.ts module, replacing scattered inline toLocaleDateString/toLocaleTimeString calls across 12 files.
This commit is contained in:
@@ -4,13 +4,7 @@ import { useEffect, useState } from 'react'
|
||||
import { useParams, useRouter } from 'next/navigation'
|
||||
import { getAdminOrg, grantPlan, type AdminOrgDetail } from '@/lib/api/admin'
|
||||
import { Button, LoadingOverlay, Select, toast } from '@ciphera-net/ui'
|
||||
|
||||
function formatDate(d: Date) {
|
||||
return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })
|
||||
}
|
||||
function formatDateTime(d: Date) {
|
||||
return d.toLocaleDateString('en-US', { dateStyle: 'long' }) + ' ' + d.toLocaleTimeString('en-US', { hour: 'numeric', minute: 'numeric' })
|
||||
}
|
||||
import { formatDate, formatDateTime } from '@/lib/utils/formatDate'
|
||||
function addMonths(d: Date, months: number) {
|
||||
const out = new Date(d)
|
||||
out.setMonth(out.getMonth() + months)
|
||||
|
||||
@@ -4,10 +4,7 @@ import { useCallback, useEffect, useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { listAdminOrgs, type AdminOrgSummary } from '@/lib/api/admin'
|
||||
import { Button, LoadingOverlay, toast } from '@ciphera-net/ui'
|
||||
|
||||
function formatDate(d: Date) {
|
||||
return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })
|
||||
}
|
||||
import { formatDate } from '@/lib/utils/formatDate'
|
||||
|
||||
function CopyableOrgId({ id }: { id: string }) {
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
@@ -17,6 +17,7 @@ import { BarChartIcon, LockIcon, ZapIcon, CheckCircleIcon, XIcon, GlobeIcon } fr
|
||||
import { toast } from '@ciphera-net/ui'
|
||||
import { getAuthErrorMessage } from '@ciphera-net/ui'
|
||||
import { getSitesLimitForPlan } from '@/lib/plans'
|
||||
import { formatDate } from '@/lib/utils/formatDate'
|
||||
|
||||
function DashboardPreview() {
|
||||
return (
|
||||
@@ -461,7 +462,7 @@ export default function HomePage() {
|
||||
const ts = subscription.next_invoice_period_end ?? subscription.current_period_end
|
||||
const d = ts ? new Date(typeof ts === 'number' ? ts * 1000 : ts) : null
|
||||
const dateStr = d && !Number.isNaN(d.getTime()) && d.getTime() !== 0
|
||||
? d.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })
|
||||
? formatDate(d)
|
||||
: null
|
||||
const amount = (subscription.next_invoice_amount_due / 100).toLocaleString('en-US', {
|
||||
style: 'currency',
|
||||
|
||||
@@ -7,6 +7,7 @@ import { createGoal, updateGoal, deleteGoal, type Goal } from '@/lib/api/goals'
|
||||
import { createReportSchedule, updateReportSchedule, deleteReportSchedule, testReportSchedule, type ReportSchedule, type CreateReportScheduleRequest, type EmailConfig, type WebhookConfig } from '@/lib/api/report-schedules'
|
||||
import { toast } from '@ciphera-net/ui'
|
||||
import { getAuthErrorMessage } from '@ciphera-net/ui'
|
||||
import { formatDateTime } from '@/lib/utils/formatDate'
|
||||
import { SettingsFormSkeleton, GoalsListSkeleton, useMinimumLoading, useSkeletonFade } from '@/components/skeletons'
|
||||
import VerificationModal from '@/components/sites/VerificationModal'
|
||||
import ScriptSetupBlock from '@/components/sites/ScriptSetupBlock'
|
||||
@@ -1341,7 +1342,7 @@ export default function SiteSettingsPage() {
|
||||
<div className="flex items-center gap-3 mt-1 text-xs text-neutral-400 dark:text-neutral-500">
|
||||
<span>
|
||||
Last sent: {schedule.last_sent_at
|
||||
? new Date(schedule.last_sent_at).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric', hour: '2-digit', minute: '2-digit' })
|
||||
? formatDateTime(new Date(schedule.last_sent_at))
|
||||
: 'Never'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -21,6 +21,7 @@ import { useTheme } from '@ciphera-net/ui'
|
||||
import { getAuthErrorMessage } from '@ciphera-net/ui'
|
||||
import { Button, Modal } from '@ciphera-net/ui'
|
||||
import { UptimeSkeleton, ChecksSkeleton, useMinimumLoading, useSkeletonFade } from '@/components/skeletons'
|
||||
import { formatDateFull, formatTime, formatDateTimeShort } from '@/lib/utils/formatDate'
|
||||
import {
|
||||
AreaChart,
|
||||
Area,
|
||||
@@ -165,11 +166,7 @@ function StatusBarTooltip({
|
||||
}) {
|
||||
if (!visible) return null
|
||||
|
||||
const formattedDate = new Date(date + 'T00:00:00').toLocaleDateString('en-US', {
|
||||
weekday: 'short',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
})
|
||||
const formattedDate = formatDateFull(new Date(date + 'T00:00:00'))
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -275,10 +272,7 @@ function ResponseTimeChart({ checks }: { checks: UptimeCheck[] }) {
|
||||
.reverse()
|
||||
.filter((c) => c.response_time_ms !== null)
|
||||
.map((c) => ({
|
||||
time: new Date(c.checked_at).toLocaleTimeString('en-US', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
}),
|
||||
time: formatTime(new Date(c.checked_at)),
|
||||
ms: c.response_time_ms as number,
|
||||
status: c.status,
|
||||
}))
|
||||
@@ -500,12 +494,7 @@ function MonitorCard({
|
||||
<div className="flex items-center gap-2">
|
||||
<div className={`w-2 h-2 rounded-full ${getStatusDotColor(check.status)}`} />
|
||||
<span className="text-neutral-600 dark:text-neutral-300 text-xs">
|
||||
{new Date(check.checked_at).toLocaleString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})}
|
||||
{formatDateTimeShort(new Date(check.checked_at))}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
|
||||
Reference in New Issue
Block a user