diff --git a/CHANGELOG.md b/CHANGELOG.md
index d9df91f..dabfab1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
### Improved
+- **European date and time formatting.** All dates across Pulse now use day-first ordering (14 Mar 2025) and 24-hour time (14:30) instead of the US-style month-first format. This applies everywhere — dashboard charts, exports, billing dates, invoices, uptime checks, audit logs, and more.
+
+### Added
+
+- **Free plan now visible on the Pricing page.** The free tier is no longer hidden — it's displayed as the first option on the Pricing page so you can see exactly what you get before signing up: 1 site, 5,000 monthly pageviews, and 6 months of data retention, completely free.
+- **Free plan limited to 1 site.** Free accounts are now limited to a single site. If you need more, you can upgrade to Solo or above from the Pricing page.
+
+### Improved
+
- **Sites now show their verification status.** Each site on your dashboard now displays either a green "Active" badge (if verified) or an amber "Unverified" badge. When you verify your tracking script installation, the status is saved permanently — no more showing "Active" for sites that haven't been set up yet.
- **Verification status visible in Settings too.** Once your tracking script is verified, the Settings page shows a green confirmation bar instead of the verify button — so you can tell at a glance that everything is working. A "Re-verify" link is still there if you ever need to check again.
- **Cleaner page paths in your reports.** Pages like `/products?_t=123456` or `/about?session=abc` now correctly show as `/products` and `/about`. Only marketing attribution parameters (like UTM tags) are preserved for traffic source tracking — all other junk parameters are automatically removed, so your Top Pages and Journeys stay clean without us having to chase down every new parameter format.
diff --git a/app/admin/orgs/[id]/page.tsx b/app/admin/orgs/[id]/page.tsx
index 3039798..3559ff9 100644
--- a/app/admin/orgs/[id]/page.tsx
+++ b/app/admin/orgs/[id]/page.tsx
@@ -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)
diff --git a/app/admin/orgs/page.tsx b/app/admin/orgs/page.tsx
index 288107d..59747ec 100644
--- a/app/admin/orgs/page.tsx
+++ b/app/admin/orgs/page.tsx
@@ -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)
diff --git a/app/page.tsx b/app/page.tsx
index 663b2c4..04dd558 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -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',
diff --git a/app/sites/[id]/journeys/page.tsx b/app/sites/[id]/journeys/page.tsx
index ce95a71..55dcc29 100644
--- a/app/sites/[id]/journeys/page.tsx
+++ b/app/sites/[id]/journeys/page.tsx
@@ -39,10 +39,10 @@ export default function JourneysPage() {
const [entryPath, setEntryPath] = useState('')
const { data: transitionsData, isLoading: transitionsLoading } = useJourneyTransitions(
- siteId, dateRange.start, dateRange.end, depth, 2, entryPath || undefined
+ siteId, dateRange.start, dateRange.end, depth, 1, entryPath || undefined
)
const { data: topPaths, isLoading: topPathsLoading } = useJourneyTopPaths(
- siteId, dateRange.start, dateRange.end, 20, 2, entryPath || undefined
+ siteId, dateRange.start, dateRange.end, 20, 1, entryPath || undefined
)
const { data: entryPoints } = useJourneyEntryPoints(siteId, dateRange.start, dateRange.end)
const { data: dashboard } = useDashboard(siteId, dateRange.start, dateRange.end)
diff --git a/app/sites/[id]/settings/page.tsx b/app/sites/[id]/settings/page.tsx
index 8d0d1eb..de54aa6 100644
--- a/app/sites/[id]/settings/page.tsx
+++ b/app/sites/[id]/settings/page.tsx
@@ -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() {