Phase 5: Animations & Final Polish

This commit is contained in:
Usman Baig
2026-02-05 17:54:04 +01:00
parent db4d7f6cde
commit b47f3a2986
11 changed files with 152 additions and 49 deletions

View File

@@ -3,6 +3,7 @@
import { useAuth } from '@/lib/auth/context'
import { useEffect, useState } from 'react'
import { useParams, useRouter } from 'next/navigation'
import { motion } from 'framer-motion'
import { getSite, type Site } from '@/lib/api/sites'
import { getStats, getRealtime, getDailyStats, getTopPages, getTopReferrers, getCountries, getCities, getRegions, getBrowsers, getOS, getDevices, getScreenResolutions, getEntryPages, getExitPages, getDashboard, getPerformanceByPage, type Stats, type DailyStat, type PerformanceByPageStat } from '@/lib/api/stats'
import { formatNumber, formatDuration, getDateRange } from '@/lib/utils/format'
@@ -225,7 +226,12 @@ export default function SiteDashboardPage() {
}
return (
<div className="w-full max-w-6xl mx-auto px-4 sm:px-6 py-8">
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.2 }}
className="w-full max-w-6xl mx-auto px-4 sm:px-6 py-8"
>
<div className="mb-8">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-4">
@@ -417,6 +423,6 @@ export default function SiteDashboardPage() {
topPages={topPages}
topReferrers={topReferrers}
/>
</div>
</motion.div>
)
}

View File

@@ -7,6 +7,7 @@ import { getRealtimeVisitors, getSessionDetails, type Visitor, type SessionEvent
import { toast } from '@ciphera-net/ui'
import { getAuthErrorMessage } from '@/lib/utils/authErrors'
import { LoadingOverlay, UserIcon } from '@ciphera-net/ui'
import { motion, AnimatePresence } from 'framer-motion'
function formatTimeAgo(dateString: string) {
const date = new Date(dateString)
@@ -135,14 +136,19 @@ export default function RealtimePage() {
</div>
) : (
<div className="divide-y divide-neutral-100 dark:divide-neutral-800">
{visitors.map((visitor) => (
<button
key={visitor.session_id}
onClick={() => handleSelectVisitor(visitor)}
className={`w-full text-left p-4 hover:bg-neutral-50 dark:hover:bg-neutral-800/50 transition-colors ${
selectedVisitor?.session_id === visitor.session_id ? 'bg-neutral-50 dark:bg-neutral-800/50 ring-1 ring-inset ring-neutral-200 dark:ring-neutral-700' : ''
}`}
>
<AnimatePresence mode="popLayout">
{visitors.map((visitor) => (
<motion.button
key={visitor.session_id}
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -10 }}
transition={{ duration: 0.2 }}
onClick={() => handleSelectVisitor(visitor)}
className={`w-full text-left p-4 hover:bg-neutral-50 dark:hover:bg-neutral-800/50 transition-colors ${
selectedVisitor?.session_id === visitor.session_id ? 'bg-neutral-50 dark:bg-neutral-800/50 ring-1 ring-inset ring-neutral-200 dark:ring-neutral-700' : ''
}`}
>
<div className="flex justify-between items-start mb-1">
<div className="font-medium text-neutral-900 dark:text-white truncate pr-2">
{visitor.country ? `${getFlagEmoji(visitor.country)} ${visitor.city || 'Unknown City'}` : 'Unknown Location'}
@@ -164,8 +170,9 @@ export default function RealtimePage() {
{visitor.pageviews} views
</span>
</div>
</button>
))}
</motion.button>
))}
</AnimatePresence>
</div>
)}
</div>