From b16652a41baff4c18693b6819ef26c6eedf7a2f4 Mon Sep 17 00:00:00 2001 From: Parth Patidar Date: Mon, 11 May 2026 03:10:29 +0530 Subject: [PATCH] build analytics dashboard page at /devcard/analytics --- .../routes/devcard/analytics/+page.server.ts | 34 ++ .../src/routes/devcard/analytics/+page.svelte | 409 ++++++++++++++++++ 2 files changed, 443 insertions(+) create mode 100644 apps/web/src/routes/devcard/analytics/+page.server.ts create mode 100644 apps/web/src/routes/devcard/analytics/+page.svelte diff --git a/apps/web/src/routes/devcard/analytics/+page.server.ts b/apps/web/src/routes/devcard/analytics/+page.server.ts new file mode 100644 index 0000000..6fa597a --- /dev/null +++ b/apps/web/src/routes/devcard/analytics/+page.server.ts @@ -0,0 +1,34 @@ +import type { PageServerLoad } from './$types'; + +const API_BASE = process.env.BACKEND_URL || 'http://localhost:3000'; + +export const load: PageServerLoad = async ({ fetch }) => { + try { + const [overviewRes, viewsRes] = await Promise.all([ + fetch(`${API_BASE}/api/analytics/overview`), + fetch(`${API_BASE}/api/analytics/views`) + ]); + + if (!overviewRes.ok || !viewsRes.ok) { + // If unauthorized, we might return mock data for demonstration purposes + // in this dev phase, but officially we should return error. + // Let's return error to follow "nothing that can break in production" + return { + overview: null, + views: null, + error: 'Please log in to view analytics' + }; + } + + const overview = await overviewRes.json(); + const views = await viewsRes.json(); + + return { overview, views, error: null }; + } catch (err) { + return { + overview: null, + views: null, + error: 'Analytics service unavailable' + }; + } +}; diff --git a/apps/web/src/routes/devcard/analytics/+page.svelte b/apps/web/src/routes/devcard/analytics/+page.svelte new file mode 100644 index 0000000..e9a4616 --- /dev/null +++ b/apps/web/src/routes/devcard/analytics/+page.svelte @@ -0,0 +1,409 @@ + + + + Analytics Dashboard — DevCard + + +
+
+
+

Analytics Dashboard

+

Track your DevCard performance and reach.

+
+ +
+ + {#if error} +
+
🔒
+

{error}

+

Accessing the dashboard requires an active session.

+ Return Home +
+ {:else if overview} + +
+
+ Total Views +
{overview.totalViews}
+ +
+
+ Views Today +
{overview.viewsToday}
+ +
+
+ Unique Viewers +
{overview.uniqueViewers}
+ +
+
+ Total Follows +
{overview.totalFollows}
+ +
+
+ +
+ +
+

Recent Activity

+
+ {#each overview.recentViews as view} +
+
+ {#if view.viewer?.avatarUrl} + + {:else} +
{view.viewer?.displayName?.charAt(0) || '?'}
+ {/if} +
+
+ {view.viewer?.displayName || 'Anonymous User'} + viewed via {view.source} +
+
{formatDate(view.createdAt)}
+
+ {/each} + {#if overview.recentViews.length === 0} +

No recent activity found.

+ {/if} +
+
+ + +
+
+

Detailed View Logs

+ {views?.meta?.total || 0} Total +
+
+ + + + + + + + + + + + {#each views?.data || [] as view} + + + + + + + + {/each} + +
ViewerCardSourceIP AddressDate
+
+ {view.viewer?.displayName || 'Guest'} + {#if view.viewer?.username} + @{view.viewer.username} + {/if} +
+
{view.card?.title || 'Profile'}{view.source}{view.viewerIp || '—'}{formatDate(view.createdAt)}
+ {#if !views?.data?.length} +
No detailed logs available yet.
+ {/if} +
+
+
+ {/if} +
+ +