From 1aecc51bbdc2cb54773e237f3ac11b49e5e2bf58 Mon Sep 17 00:00:00 2001 From: NeilClack Date: Thu, 10 Sep 2026 13:11:41 -0500 Subject: [PATCH] Replace account and license-keys pages with profile page and dashboard --- src/lib/components/DashboardSection.svelte | 25 +++++ .../(user)/dashboard/+page.server.ts | 19 +--- .../(protected)/(user)/dashboard/+page.svelte | 105 ++++++++++++++---- .../(user)/dashboard/page.server.spec.ts | 54 ++++----- .../(user)/license-keys/+page.server.ts | 16 --- .../(user)/license-keys/+page.svelte | 66 ----------- .../(user)/license-keys/page.server.spec.ts | 59 ---------- .../{account => profile}/+page.server.ts | 0 .../(user)/{account => profile}/+page.svelte | 4 +- .../{account => profile}/page.server.spec.ts | 2 +- .../setup/+page.server.ts | 0 .../{account => profile}/setup/+page.svelte | 0 .../setup/page.server.spec.ts | 4 +- src/routes/(protected)/+layout.server.ts | 6 +- src/routes/(protected)/layout.server.spec.ts | 8 +- src/routes/+layout.svelte | 29 ++--- src/routes/login/+page.svelte | 4 +- src/routes/login/page.svelte.spec.ts | 4 +- src/routes/register/+page.svelte | 2 +- src/routes/register/page.svelte.spec.ts | 4 +- 20 files changed, 157 insertions(+), 254 deletions(-) create mode 100644 src/lib/components/DashboardSection.svelte delete mode 100644 src/routes/(protected)/(user)/license-keys/+page.server.ts delete mode 100644 src/routes/(protected)/(user)/license-keys/+page.svelte delete mode 100644 src/routes/(protected)/(user)/license-keys/page.server.spec.ts rename src/routes/(protected)/(user)/{account => profile}/+page.server.ts (100%) rename src/routes/(protected)/(user)/{account => profile}/+page.svelte (99%) rename src/routes/(protected)/(user)/{account => profile}/page.server.spec.ts (99%) rename src/routes/(protected)/(user)/{account => profile}/setup/+page.server.ts (100%) rename src/routes/(protected)/(user)/{account => profile}/setup/+page.svelte (100%) rename src/routes/(protected)/(user)/{account => profile}/setup/page.server.spec.ts (96%) diff --git a/src/lib/components/DashboardSection.svelte b/src/lib/components/DashboardSection.svelte new file mode 100644 index 0000000..f176d8f --- /dev/null +++ b/src/lib/components/DashboardSection.svelte @@ -0,0 +1,25 @@ + + +
+
+

{title}

+ {#if description} +

{description}

+ {/if} +
+
+ {@render children()} +
+
diff --git a/src/routes/(protected)/(user)/dashboard/+page.server.ts b/src/routes/(protected)/(user)/dashboard/+page.server.ts index 8e187e0..ff23f60 100644 --- a/src/routes/(protected)/(user)/dashboard/+page.server.ts +++ b/src/routes/(protected)/(user)/dashboard/+page.server.ts @@ -7,23 +7,10 @@ export const load: PageServerLoad = async ({ locals }) => { throw error(403, 'not authorized'); } - const profile = await prisma.profile.findFirst({ + const licenseKeys = await prisma.licenseKey.findMany({ where: { userId: locals.user.id }, - select: { firstName: true } + orderBy: { createdAt: 'desc' } }); - const keyStats = await prisma.licenseKey.groupBy({ - by: ['active'], - where: { userId: locals.user.id }, - _count: true - }); - - const activeKeys = keyStats.find((s) => s.active)?._count ?? 0; - const totalKeys = keyStats.reduce((sum, s) => sum + s._count, 0); - - return { - firstName: profile?.firstName ?? 'User', - activeKeys, - totalKeys - }; + return { licenseKeys }; }; diff --git a/src/routes/(protected)/(user)/dashboard/+page.svelte b/src/routes/(protected)/(user)/dashboard/+page.svelte index 028a834..8675bc9 100644 --- a/src/routes/(protected)/(user)/dashboard/+page.svelte +++ b/src/routes/(protected)/(user)/dashboard/+page.svelte @@ -1,33 +1,90 @@
-

- Welcome, {data.firstName}! -

-

Here's an overview of your account.

- -
- - Active License Keys - {data.activeKeys} - {data.totalKeys} total - - - - Account - Manage Profile - Edit your details - +
+

Dashboard

+

Manage your account and license keys.

+ + + + + {#if data.licenseKeys.length > 0} +
+ + + + + + + + + + + {#each data.licenseKeys as key (key.key)} + + + + + + {/each} + + + + +
    + {#each data.licenseKeys as key (key.key)} +
  • +
    + {key.key} + + {key.active ? 'Active' : 'Inactive'} + +
    + Created {formatDate(key.createdAt)} +
  • + {/each} +
+
+ {:else} +
+

You don't have any license keys yet.

+
+ {/if} +
diff --git a/src/routes/(protected)/(user)/dashboard/page.server.spec.ts b/src/routes/(protected)/(user)/dashboard/page.server.spec.ts index fe27011..6d861ba 100644 --- a/src/routes/(protected)/(user)/dashboard/page.server.spec.ts +++ b/src/routes/(protected)/(user)/dashboard/page.server.spec.ts @@ -1,24 +1,21 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { isHttpError } from '@sveltejs/kit'; -import { createLocals, mockUser } from '$lib/test-utils'; +import { createLocals, mockUser, mockLicenseKey } from '$lib/test-utils'; vi.mock('$lib/db', () => ({ prisma: { - profile: { findFirst: vi.fn() }, - licenseKey: { groupBy: vi.fn() } + licenseKey: { findMany: vi.fn() } } })); import { prisma } from '$lib/db'; import { load } from './+page.server'; -const profileFindFirst = prisma.profile.findFirst as ReturnType; -const licenseKeyGroupBy = prisma.licenseKey.groupBy as ReturnType; +const licenseKeyFindMany = prisma.licenseKey.findMany as ReturnType; describe('(protected)/dashboard page server load', () => { beforeEach(() => { - profileFindFirst.mockReset(); - licenseKeyGroupBy.mockReset(); + licenseKeyFindMany.mockReset(); }); it('throws 403 when locals.user is undefined', async () => { @@ -28,44 +25,35 @@ describe('(protected)/dashboard page server load', () => { expect(isHttpError(err, 403)).toBe(true); }); - it('returns firstName from profile', async () => { - profileFindFirst.mockResolvedValue({ firstName: 'Jane' }); - licenseKeyGroupBy.mockResolvedValue([]); + it('returns license keys for the authenticated user', async () => { + const keys = [ + mockLicenseKey(), + mockLicenseKey({ id: 'key-2', key: 'EEEE-FFFF-GGGG-HHHH', active: false }) + ]; + licenseKeyFindMany.mockResolvedValue(keys); const locals = createLocals(mockUser() as any); const result = (await load({ locals } as any)) as any; - expect(result.firstName).toBe('Jane'); + expect(result.licenseKeys).toEqual(keys); }); - it('defaults firstName to "User" when profile is null', async () => { - profileFindFirst.mockResolvedValue(null); - licenseKeyGroupBy.mockResolvedValue([]); + it('returns empty array when user has no keys', async () => { + licenseKeyFindMany.mockResolvedValue([]); const locals = createLocals(mockUser() as any); const result = (await load({ locals } as any)) as any; - expect(result.firstName).toBe('User'); + expect(result.licenseKeys).toEqual([]); }); - it('computes activeKeys and totalKeys from groupBy results', async () => { - profileFindFirst.mockResolvedValue({ firstName: 'Jane' }); - licenseKeyGroupBy.mockResolvedValue([ - { active: true, _count: 3 }, - { active: false, _count: 2 } - ]); + it('loads license keys filtered by user, newest first', async () => { + licenseKeyFindMany.mockResolvedValue([]); const locals = createLocals(mockUser() as any); - const result = (await load({ locals } as any)) as any; - expect(result.activeKeys).toBe(3); - expect(result.totalKeys).toBe(5); - }); + await load({ locals } as any); - it('returns 0 for activeKeys when none are active', async () => { - profileFindFirst.mockResolvedValue({ firstName: 'Jane' }); - licenseKeyGroupBy.mockResolvedValue([{ active: false, _count: 4 }]); - - const locals = createLocals(mockUser() as any); - const result = (await load({ locals } as any)) as any; - expect(result.activeKeys).toBe(0); - expect(result.totalKeys).toBe(4); + expect(licenseKeyFindMany).toHaveBeenCalledWith({ + where: { userId: 'user-1' }, + orderBy: { createdAt: 'desc' } + }); }); }); diff --git a/src/routes/(protected)/(user)/license-keys/+page.server.ts b/src/routes/(protected)/(user)/license-keys/+page.server.ts deleted file mode 100644 index b684dec..0000000 --- a/src/routes/(protected)/(user)/license-keys/+page.server.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { PageServerLoad } from './$types'; -import { prisma } from '$lib/db'; -import { error } from '@sveltejs/kit'; - -export const load: PageServerLoad = async ({ locals }) => { - if (!locals.user?.id) { - throw error(403, 'not authorized'); - } - - const licenseKeys = await prisma.licenseKey.findMany({ - where: { userId: locals.user.id }, - orderBy: { key: 'asc' } - }); - - return { licenseKeys }; -}; diff --git a/src/routes/(protected)/(user)/license-keys/+page.svelte b/src/routes/(protected)/(user)/license-keys/+page.svelte deleted file mode 100644 index 7767494..0000000 --- a/src/routes/(protected)/(user)/license-keys/+page.svelte +++ /dev/null @@ -1,66 +0,0 @@ - - -
-
-
-

License Keys

-

Your active and inactive license keys.

-
-
- - {#if data.licenseKeys.length > 0} -
- - - - - - - - - - {#each data.licenseKeys as key (key.key)} - - - - - {/each} - - - - -
    - {#each data.licenseKeys as key (key.key)} -
  • - {key.key} - - {key.active ? 'Active' : 'Inactive'} - -
  • - {/each} -
-
- {:else} -
-

You don't have any license keys yet.

-
- {/if} -
diff --git a/src/routes/(protected)/(user)/license-keys/page.server.spec.ts b/src/routes/(protected)/(user)/license-keys/page.server.spec.ts deleted file mode 100644 index 5e30c24..0000000 --- a/src/routes/(protected)/(user)/license-keys/page.server.spec.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { isHttpError } from '@sveltejs/kit'; -import { createLocals, mockUser, mockLicenseKey } from '$lib/test-utils'; - -vi.mock('$lib/db', () => ({ - prisma: { - licenseKey: { findMany: vi.fn() } - } -})); - -import { prisma } from '$lib/db'; -import { load } from './+page.server'; - -const findMany = prisma.licenseKey.findMany as ReturnType; - -describe('(protected)/license-keys page server load', () => { - beforeEach(() => { - findMany.mockReset(); - }); - - it('throws 403 when locals.user is undefined', async () => { - const err = await Promise.resolve(load({ locals: createLocals() } as any)).catch( - (e: unknown) => e - ); - expect(isHttpError(err, 403)).toBe(true); - }); - - it('returns license keys for the authenticated user', async () => { - const keys = [ - mockLicenseKey(), - mockLicenseKey({ id: 'key-2', key: 'EEEE-FFFF-GGGG-HHHH', active: false }) - ]; - findMany.mockResolvedValue(keys); - - const locals = createLocals(mockUser() as any); - const result = (await load({ locals } as any)) as any; - expect(result.licenseKeys).toEqual(keys); - }); - - it('passes userId in the where clause', async () => { - findMany.mockResolvedValue([]); - - const locals = createLocals(mockUser() as any); - await load({ locals } as any); - - expect(findMany).toHaveBeenCalledWith({ - where: { userId: 'user-1' }, - orderBy: { key: 'asc' } - }); - }); - - it('returns empty array when user has no keys', async () => { - findMany.mockResolvedValue([]); - - const locals = createLocals(mockUser() as any); - const result = (await load({ locals } as any)) as any; - expect(result.licenseKeys).toEqual([]); - }); -}); diff --git a/src/routes/(protected)/(user)/account/+page.server.ts b/src/routes/(protected)/(user)/profile/+page.server.ts similarity index 100% rename from src/routes/(protected)/(user)/account/+page.server.ts rename to src/routes/(protected)/(user)/profile/+page.server.ts diff --git a/src/routes/(protected)/(user)/account/+page.svelte b/src/routes/(protected)/(user)/profile/+page.svelte similarity index 99% rename from src/routes/(protected)/(user)/account/+page.svelte rename to src/routes/(protected)/(user)/profile/+page.svelte index a4fa191..2f5095a 100644 --- a/src/routes/(protected)/(user)/account/+page.svelte +++ b/src/routes/(protected)/(user)/profile/+page.svelte @@ -24,8 +24,8 @@
-

Account

-

Manage your profile and addresses.

+

Profile

+

Manage your account details and addresses.

diff --git a/src/routes/(protected)/(user)/account/page.server.spec.ts b/src/routes/(protected)/(user)/profile/page.server.spec.ts similarity index 99% rename from src/routes/(protected)/(user)/account/page.server.spec.ts rename to src/routes/(protected)/(user)/profile/page.server.spec.ts index c09dc29..4d48099 100644 --- a/src/routes/(protected)/(user)/account/page.server.spec.ts +++ b/src/routes/(protected)/(user)/profile/page.server.spec.ts @@ -26,7 +26,7 @@ const addressCreate = prisma.address.create as ReturnType; const invokeAction = (name: string, request: Request, locals: App.Locals) => (actions as any)[name]({ request, locals } as any); -describe('(protected)/account page server', () => { +describe('(protected)/profile page server', () => { beforeEach(() => { userFindUnique.mockReset(); profileFindFirst.mockReset(); diff --git a/src/routes/(protected)/(user)/account/setup/+page.server.ts b/src/routes/(protected)/(user)/profile/setup/+page.server.ts similarity index 100% rename from src/routes/(protected)/(user)/account/setup/+page.server.ts rename to src/routes/(protected)/(user)/profile/setup/+page.server.ts diff --git a/src/routes/(protected)/(user)/account/setup/+page.svelte b/src/routes/(protected)/(user)/profile/setup/+page.svelte similarity index 100% rename from src/routes/(protected)/(user)/account/setup/+page.svelte rename to src/routes/(protected)/(user)/profile/setup/+page.svelte diff --git a/src/routes/(protected)/(user)/account/setup/page.server.spec.ts b/src/routes/(protected)/(user)/profile/setup/page.server.spec.ts similarity index 96% rename from src/routes/(protected)/(user)/account/setup/page.server.spec.ts rename to src/routes/(protected)/(user)/profile/setup/page.server.spec.ts index 5a62593..0368421 100644 --- a/src/routes/(protected)/(user)/account/setup/page.server.spec.ts +++ b/src/routes/(protected)/(user)/profile/setup/page.server.spec.ts @@ -27,7 +27,7 @@ import { load, actions } from './+page.server'; const invokeDefault = (request: Request) => actions.default({ request } as any); -describe('account setup page server load', () => { +describe('profile setup page server load', () => { beforeEach(() => { create.mockReset(); }); @@ -48,7 +48,7 @@ describe('account setup page server load', () => { }); }); -describe('account setup page server action', () => { +describe('profile setup page server action', () => { beforeEach(() => { create.mockReset(); }); diff --git a/src/routes/(protected)/+layout.server.ts b/src/routes/(protected)/+layout.server.ts index 8f3c813..351f7c4 100644 --- a/src/routes/(protected)/+layout.server.ts +++ b/src/routes/(protected)/+layout.server.ts @@ -14,9 +14,9 @@ export const load: LayoutServerLoad = async ({ locals, url }) => { if (!user) { throw error(403, 'not authorized'); } - if (user.profiles.length === 0 && !url.pathname.startsWith('/account/setup')) { - // Skip redirect when already on /account/setup to avoid redirect loop - throw redirect(303, '/account/setup'); + if (user.profiles.length === 0 && !url.pathname.startsWith('/profile/setup')) { + // Skip redirect when already on /profile/setup to avoid redirect loop + throw redirect(303, '/profile/setup'); } return { userRoleId: user.roleId }; diff --git a/src/routes/(protected)/layout.server.spec.ts b/src/routes/(protected)/layout.server.spec.ts index d664dc0..542480c 100644 --- a/src/routes/(protected)/layout.server.spec.ts +++ b/src/routes/(protected)/layout.server.spec.ts @@ -39,7 +39,7 @@ describe('(protected) layout server load', () => { expect(isHttpError(err, 403)).toBe(true); }); - it('redirects to /account/setup when user has no profile', async () => { + it('redirects to /profile/setup when user has no profile', async () => { findUnique.mockResolvedValue({ id: 'user-1', roleId: null, profiles: [] }); const locals = createLocals(mockUser() as any); @@ -48,15 +48,15 @@ describe('(protected) layout server load', () => { expect(isRedirect(err)).toBe(true); if (isRedirect(err)) { expect(err.status).toBe(303); - expect(err.location).toBe('/account/setup'); + expect(err.location).toBe('/profile/setup'); } }); - it('skips redirect when already on /account/setup', async () => { + it('skips redirect when already on /profile/setup', async () => { findUnique.mockResolvedValue({ id: 'user-1', roleId: null, profiles: [] }); const locals = createLocals(mockUser() as any); - const url = new URL('http://localhost/account/setup'); + const url = new URL('http://localhost/profile/setup'); const result = await load({ locals, url } as any); expect(result).toEqual({ userRoleId: null }); }); diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index f7c3e12..5b145a0 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -114,16 +114,10 @@ Dashboard - Account - - - License Keys + Profile {#if isAdmin} @@ -277,18 +271,11 @@ Dashboard - Account - - - License Keys + Profile {#if isAdmin} @@ -382,8 +369,8 @@ >

© {new Date().getFullYear()} Radiant Wave Technologies. All rights reserved.

{#if $session.data?.user} - - Manage your account + + Manage your profile {:else} Sign in diff --git a/src/routes/login/+page.svelte b/src/routes/login/+page.svelte index c9a2a12..4a65f2e 100644 --- a/src/routes/login/+page.svelte +++ b/src/routes/login/+page.svelte @@ -22,7 +22,7 @@ return; } - window.location.href = '/account'; + window.location.href = '/dashboard'; } @@ -104,7 +104,7 @@