diff --git a/resources/js/components/achievements/AchievementCard.jsx b/resources/js/components/achievements/AchievementCard.jsx
index b0f8fa7a..06ff974b 100644
--- a/resources/js/components/achievements/AchievementCard.jsx
+++ b/resources/js/components/achievements/AchievementCard.jsx
@@ -1,4 +1,5 @@
import React from 'react'
+import { formatProfileDate } from '../../lib/profileFormatters'
import AchievementBadge from './AchievementBadge'
function cx(...parts) {
@@ -46,9 +47,9 @@ export default function AchievementCard({ achievement, className = '' }) {
{achievement?.unlocked_at ? (
- Unlocked {new Date(achievement.unlocked_at).toLocaleDateString()}
+ Unlocked {formatProfileDate(achievement.unlocked_at, { year: 'numeric', month: 'short', day: 'numeric' })}
) : null}
)
-}
\ No newline at end of file
+}
diff --git a/resources/js/components/profile/CreatorJourneySection.jsx b/resources/js/components/profile/CreatorJourneySection.jsx
index 9969a8d4..13df9282 100644
--- a/resources/js/components/profile/CreatorJourneySection.jsx
+++ b/resources/js/components/profile/CreatorJourneySection.jsx
@@ -1,14 +1,11 @@
import React from 'react'
+import { formatProfileDate, formatProfileNumber } from '../../lib/profileFormatters'
function formatDate(value) {
if (!value) return null
try {
- return new Intl.DateTimeFormat('en', {
- month: 'short',
- day: 'numeric',
- year: 'numeric',
- }).format(new Date(value))
+ return formatProfileDate(value, { month: 'short', day: 'numeric', year: 'numeric' })
} catch {
return null
}
@@ -17,7 +14,7 @@ function formatDate(value) {
function formatYear(value) {
if (!value) return null
try {
- return new Intl.DateTimeFormat('en', { year: 'numeric' }).format(new Date(value))
+ return formatProfileDate(value, { year: 'numeric' })
} catch {
return null
}
@@ -268,8 +265,8 @@ function YearlyProductivityChart({ recaps }) {
{uploads} upload{uploads !== 1 ? 's' : ''}
- {(item.metrics?.views ?? 0) > 0 ? ` · ${Number(item.metrics.views).toLocaleString()} views` : ''}
- {(item.metrics?.downloads ?? 0) > 0 ? ` · ${Number(item.metrics.downloads).toLocaleString()} dl` : ''}
+ {(item.metrics?.views ?? 0) > 0 ? ` · ${formatProfileNumber(item.metrics.views)} views` : ''}
+ {(item.metrics?.downloads ?? 0) > 0 ? ` · ${formatProfileNumber(item.metrics.downloads)} dl` : ''}
@@ -292,8 +289,8 @@ function YearlyProductivityChart({ recaps }) {
{/* Summary footer */}
{sorted.length} active years
- {sorted.reduce((s, r) => s + r.metrics.uploads_count, 0).toLocaleString()} total uploads
- {Number(sorted.reduce((s, r) => s + (r.metrics.views ?? 0), 0)).toLocaleString()} total views
+ {formatProfileNumber(sorted.reduce((s, r) => s + r.metrics.uploads_count, 0))} total uploads
+ {formatProfileNumber(sorted.reduce((s, r) => s + (r.metrics.views ?? 0), 0))} total views
)
@@ -550,8 +547,8 @@ export default function CreatorJourneySection({ journey, username }) {
{item.summary}
-
-
+
+
{(item.metrics?.featured_count ?? 0) > 0 && (
)}
diff --git a/resources/js/components/profile/collections/CollectionCard.jsx b/resources/js/components/profile/collections/CollectionCard.jsx
index a85bd292..774bfc0d 100644
--- a/resources/js/components/profile/collections/CollectionCard.jsx
+++ b/resources/js/components/profile/collections/CollectionCard.jsx
@@ -1,5 +1,6 @@
import React from 'react'
import CollectionVisibilityBadge from './CollectionVisibilityBadge'
+import { formatProfileDate, formatProfileNumber } from '../../../lib/profileFormatters'
async function requestJson(url, { method = 'GET', body } = {}) {
const response = await fetch(url, {
@@ -26,14 +27,7 @@ async function requestJson(url, { method = 'GET', body } = {}) {
function formatUpdated(value) {
if (!value) return 'Updated recently'
- const date = new Date(value)
- if (Number.isNaN(date.getTime())) return 'Updated recently'
-
- return new Intl.DateTimeFormat(undefined, {
- month: 'short',
- day: 'numeric',
- year: 'numeric',
- }).format(date)
+ return formatProfileDate(value, { month: 'short', day: 'numeric', year: 'numeric' }) || 'Updated recently'
}
function StatPill({ icon, label, value }) {
@@ -178,7 +172,7 @@ export default function CollectionCard({ collection, isOwner, onDelete, onToggle
{collection?.subtitle ?
{collection.subtitle}
: null}
{collection?.owner?.name ?
Curated by {collection.owner.name}{collection?.owner?.username ? ` • @${collection.owner.username}` : ''}
: null}
- {(collection?.artworks_count ?? 0).toLocaleString()} artworks
+ {formatProfileNumber(collection?.artworks_count ?? 0)} artworks
@@ -190,9 +184,9 @@ export default function CollectionCard({ collection, isOwner, onDelete, onToggle
) : null}
-
-
- {collection?.collaborators_count > 1 ? : null}
+
+
+ {collection?.collaborators_count > 1 ? : null}
diff --git a/resources/js/components/profile/tabs/TabAbout.jsx b/resources/js/components/profile/tabs/TabAbout.jsx
index 2e91c8f9..feede062 100644
--- a/resources/js/components/profile/tabs/TabAbout.jsx
+++ b/resources/js/components/profile/tabs/TabAbout.jsx
@@ -2,6 +2,7 @@ import React from 'react'
import { usePage } from '@inertiajs/react'
import CreatorJourneySection from '../CreatorJourneySection'
import { shinyFlagUrl } from '../../../utils/flagUrl'
+import { formatProfileDate, formatProfileNumber } from '../../../lib/profileFormatters'
const SOCIAL_ICONS = {
twitter: { icon: 'fa-brands fa-x-twitter', label: 'X / Twitter', hoverClass: 'hover:border-slate-300/30 hover:text-slate-100 hover:bg-white/[0.08]' },
@@ -66,7 +67,7 @@ function getContentTypeIcon(label) {
}
function formatNumber(value) {
- return Number(value ?? 0).toLocaleString()
+ return formatProfileNumber(value)
}
function formatRelativeDate(value) {
@@ -79,7 +80,7 @@ function formatRelativeDate(value) {
const now = new Date()
const diffSeconds = Math.round((date.getTime() - now.getTime()) / 1000)
const absSeconds = Math.abs(diffSeconds)
- const formatter = new Intl.RelativeTimeFormat('en', { numeric: 'auto' })
+ const formatter = new Intl.RelativeTimeFormat('en-US', { numeric: 'auto' })
if (absSeconds < 3600) {
return formatter.format(Math.round(diffSeconds / 60), 'minute')
@@ -110,7 +111,7 @@ function formatShortDate(value) {
const date = new Date(value)
if (Number.isNaN(date.getTime())) return null
- return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })
+ return formatProfileDate(date, { month: 'short', day: 'numeric', year: 'numeric' })
} catch {
return null
}
@@ -131,7 +132,7 @@ function formatContributionDate(value) {
const date = new Date(value)
if (Number.isNaN(date.getTime())) return null
- return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })
+ return formatProfileDate(date, { month: 'short', day: 'numeric', year: 'numeric' })
} catch {
return null
}
@@ -237,14 +238,13 @@ export default function TabAbout({ user, profile, stats, achievements, worldRewa
const flagUrl = shinyFlagUrl(profile?.country_code, props?.cdn?.files_url)
const joinDate = user.created_at
- ? new Date(user.created_at).toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' })
+ ? formatProfileDate(user.created_at, { month: 'long', day: 'numeric', year: 'numeric' })
: null
const lastVisit = user.last_visit_at
? (() => {
try {
- const d = new Date(user.last_visit_at)
- return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })
+ return formatProfileDate(user.last_visit_at, { month: 'short', day: 'numeric', year: 'numeric' })
} catch { return null }
})()
: null
@@ -254,7 +254,7 @@ export default function TabAbout({ user, profile, stats, achievements, worldRewa
const birthDate = profile?.birthdate
? (() => {
try {
- return new Date(profile.birthdate).toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' })
+ return formatProfileDate(profile.birthdate, { month: 'long', day: 'numeric', year: 'numeric' })
} catch { return null }
})()
: null
@@ -381,9 +381,9 @@ export default function TabAbout({ user, profile, stats, achievements, worldRewa
{entry.group?.headline ?
{truncateText(entry.group.headline, 100)}
: null}
{entry.summary ?
{entry.summary}
: null}
- {Number(entry.counts?.credited_artworks || 0).toLocaleString()} credited artworks
- {Number(entry.counts?.releases || 0).toLocaleString()} releases
- {Number(entry.counts?.projects || 0).toLocaleString()} projects
+ {formatProfileNumber(entry.counts?.credited_artworks || 0)} credited artworks
+ {formatProfileNumber(entry.counts?.releases || 0)} releases
+ {formatProfileNumber(entry.counts?.projects || 0)} projects
{entry.joined_at ? Joined {formatContributionDate(entry.joined_at)} : null}
{Array.isArray(entry.role_labels) && entry.role_labels.length > 0 ? (
diff --git a/resources/js/components/profile/tabs/TabFollowers.jsx b/resources/js/components/profile/tabs/TabFollowers.jsx
index 2a57b30c..f1595508 100644
--- a/resources/js/components/profile/tabs/TabFollowers.jsx
+++ b/resources/js/components/profile/tabs/TabFollowers.jsx
@@ -1,17 +1,11 @@
import React from 'react'
+import { formatProfileDate, formatProfileNumber } from '../../../lib/profileFormatters'
function formatFollowedAt(value) {
if (!value) return null
try {
- const date = new Date(value)
- if (Number.isNaN(date.getTime())) return null
-
- return date.toLocaleDateString(undefined, {
- year: 'numeric',
- month: 'short',
- day: 'numeric',
- })
+ return formatProfileDate(value, { year: 'numeric', month: 'short', day: 'numeric' })
} catch {
return null
}
@@ -36,7 +30,7 @@ export default function TabFollowers({ listing, username, followerCount }) {
Followers
- {total.toLocaleString()}
+ {formatProfileNumber(total)}
diff --git a/resources/js/components/profile/tabs/TabStats.jsx b/resources/js/components/profile/tabs/TabStats.jsx
index c3d722d4..2d324b82 100644
--- a/resources/js/components/profile/tabs/TabStats.jsx
+++ b/resources/js/components/profile/tabs/TabStats.jsx
@@ -1,4 +1,5 @@
import React from 'react'
+import { formatProfileNumber } from '../../../lib/profileFormatters'
function KpiCard({ icon, label, value, color = 'text-sky-400' }) {
return (
@@ -7,7 +8,7 @@ function KpiCard({ icon, label, value, color = 'text-sky-400' }) {
-
{Number(value ?? 0).toLocaleString()}
+
{formatProfileNumber(value)}
{label}
@@ -71,7 +72,7 @@ export default function TabStats({ stats, followerCount, followAnalytics }) {
Weighted Score
-
{Number(medalTotals?.score_total ?? 0).toLocaleString()}
+
{formatProfileNumber(medalTotals?.score_total ?? 0)}
@@ -83,7 +84,7 @@ export default function TabStats({ stats, followerCount, followAnalytics }) {
].map((item) => (
{item.label}
-
{Number(item.value).toLocaleString()}
+
{formatProfileNumber(item.value)}
))}
diff --git a/resources/js/components/profile/worlds/ProfileWorldHistoryCard.jsx b/resources/js/components/profile/worlds/ProfileWorldHistoryCard.jsx
index 2a0ea65c..17876235 100644
--- a/resources/js/components/profile/worlds/ProfileWorldHistoryCard.jsx
+++ b/resources/js/components/profile/worlds/ProfileWorldHistoryCard.jsx
@@ -1,14 +1,12 @@
import React from 'react'
+import { formatProfileDate } from '../../../lib/profileFormatters'
import ProfileWorldRecognitionBadge from './ProfileWorldRecognitionBadge'
function formatDate(value) {
if (!value) return null
try {
- const date = new Date(value)
- if (Number.isNaN(date.getTime())) return null
-
- return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })
+ return formatProfileDate(value, { month: 'short', day: 'numeric', year: 'numeric' })
} catch {
return null
}
@@ -119,4 +117,4 @@ export default function ProfileWorldHistoryCard({ entry }) {
)
-}
\ No newline at end of file
+}
diff --git a/resources/js/components/profile/worlds/ProfileWorldHistorySummary.jsx b/resources/js/components/profile/worlds/ProfileWorldHistorySummary.jsx
index 516da9ba..80e3712e 100644
--- a/resources/js/components/profile/worlds/ProfileWorldHistorySummary.jsx
+++ b/resources/js/components/profile/worlds/ProfileWorldHistorySummary.jsx
@@ -1,4 +1,5 @@
import React from 'react'
+import { formatProfileDate } from '../../../lib/profileFormatters'
import ProfileWorldStatsRow from './ProfileWorldStatsRow'
import ProfileWorldRecognitionBadge from './ProfileWorldRecognitionBadge'
@@ -6,10 +7,7 @@ function formatDate(value) {
if (!value) return null
try {
- const date = new Date(value)
- if (Number.isNaN(date.getTime())) return null
-
- return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })
+ return formatProfileDate(value, { month: 'short', day: 'numeric', year: 'numeric' })
} catch {
return null
}
@@ -51,4 +49,4 @@ export default function ProfileWorldHistorySummary({ history }) {
) : null}
)
-}
\ No newline at end of file
+}
diff --git a/resources/js/components/social/FollowButton.jsx b/resources/js/components/social/FollowButton.jsx
index 6a6f8d40..ab2f6680 100644
--- a/resources/js/components/social/FollowButton.jsx
+++ b/resources/js/components/social/FollowButton.jsx
@@ -1,5 +1,6 @@
import React, { useState } from 'react'
import NovaConfirmDialog from '../ui/NovaConfirmDialog'
+import { formatProfileNumber } from '../../lib/profileFormatters'
export default function FollowButton({
username,
@@ -79,7 +80,7 @@ export default function FollowButton({
>
{following ? (hovered ? 'Unfollow' : 'Following') : 'Follow'}
- {showCount ? {count.toLocaleString()} : null}
+ {showCount ? {formatProfileNumber(count)} : null}