Replace account and license-keys pages with profile page and dashboard
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
let {
|
||||
title,
|
||||
description = '',
|
||||
children
|
||||
}: {
|
||||
title: string;
|
||||
description?: string;
|
||||
children: Snippet;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<section class="mt-8">
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold text-slate-900">{title}</h2>
|
||||
{#if description}
|
||||
<p class="mt-1 text-sm text-slate-500">{description}</p>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
{@render children()}
|
||||
</div>
|
||||
</section>
|
||||
@@ -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 };
|
||||
};
|
||||
|
||||
@@ -1,33 +1,90 @@
|
||||
<script lang="ts">
|
||||
import { resolve } from '$app/paths';
|
||||
import DashboardSection from '$lib/components/DashboardSection.svelte';
|
||||
import type { PageProps } from './$types';
|
||||
|
||||
let { data }: PageProps = $props();
|
||||
|
||||
function formatDate(value: Date | string): string {
|
||||
const date = value instanceof Date ? value : new Date(value);
|
||||
return date.toLocaleDateString(undefined, {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric'
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold tracking-tight text-slate-900 sm:text-3xl">
|
||||
Welcome, {data.firstName}!
|
||||
</h1>
|
||||
<p class="mt-1 text-sm text-slate-500 sm:text-base">Here's an overview of your account.</p>
|
||||
|
||||
<div class="mt-6 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<a
|
||||
href={resolve('/license-keys')}
|
||||
class="group flex flex-col gap-1 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm transition hover:border-indigo-200 hover:shadow"
|
||||
>
|
||||
<span class="text-sm font-medium text-slate-600">Active License Keys</span>
|
||||
<span class="text-3xl font-bold text-indigo-600">{data.activeKeys}</span>
|
||||
<span class="text-sm text-slate-500">{data.totalKeys} total</span>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href={resolve('/account')}
|
||||
class="group flex flex-col gap-1 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm transition hover:border-indigo-200 hover:shadow"
|
||||
>
|
||||
<span class="text-sm font-medium text-slate-600">Account</span>
|
||||
<span class="text-2xl font-semibold text-slate-900">Manage Profile</span>
|
||||
<span class="text-sm text-slate-500">Edit your details</span>
|
||||
</a>
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold tracking-tight text-slate-900 sm:text-3xl">Dashboard</h1>
|
||||
<p class="mt-1 text-sm text-slate-500 sm:text-base">Manage your account and license keys.</p>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
Dashboard sections. This page is the account management surface; personal
|
||||
information (name, email, phone, addresses) lives on the Profile page.
|
||||
Planned sections to add below as <DashboardSection> blocks:
|
||||
- Schedules: create and manage listening schedules.
|
||||
- Devices / playback.
|
||||
- Billing / orders.
|
||||
-->
|
||||
|
||||
<DashboardSection title="License Keys" description="Your active and inactive license keys.">
|
||||
{#if data.licenseKeys.length > 0}
|
||||
<div class="overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm">
|
||||
<!-- Desktop / tablet: table -->
|
||||
<table class="hidden w-full text-left text-sm sm:table">
|
||||
<thead
|
||||
class="border-b border-slate-200 bg-slate-50 text-xs tracking-wide text-slate-500 uppercase"
|
||||
>
|
||||
<tr>
|
||||
<th class="px-6 py-3 font-semibold">Key</th>
|
||||
<th class="px-6 py-3 font-semibold">Status</th>
|
||||
<th class="px-6 py-3 font-semibold">Created</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100">
|
||||
{#each data.licenseKeys as key (key.key)}
|
||||
<tr class="hover:bg-slate-50">
|
||||
<td class="px-6 py-4 font-mono text-slate-800">{key.key}</td>
|
||||
<td class="px-6 py-4">
|
||||
<span
|
||||
class="inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium {key.active
|
||||
? 'bg-green-100 text-green-700'
|
||||
: 'bg-slate-100 text-slate-600'}"
|
||||
>
|
||||
{key.active ? 'Active' : 'Inactive'}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-slate-600">{formatDate(key.createdAt)}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Mobile: card list -->
|
||||
<ul class="divide-y divide-slate-100 sm:hidden">
|
||||
{#each data.licenseKeys as key (key.key)}
|
||||
<li class="flex flex-col gap-2 px-4 py-4">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<span class="min-w-0 font-mono text-sm break-all text-slate-800">{key.key}</span>
|
||||
<span
|
||||
class="shrink-0 rounded-full px-2.5 py-0.5 text-xs font-medium {key.active
|
||||
? 'bg-green-100 text-green-700'
|
||||
: 'bg-slate-100 text-slate-600'}"
|
||||
>
|
||||
{key.active ? 'Active' : 'Inactive'}
|
||||
</span>
|
||||
</div>
|
||||
<span class="text-xs text-slate-500">Created {formatDate(key.createdAt)}</span>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="rounded-2xl border border-dashed border-slate-300 bg-white p-8 text-center">
|
||||
<p class="text-sm text-slate-600">You don't have any license keys yet.</p>
|
||||
</div>
|
||||
{/if}
|
||||
</DashboardSection>
|
||||
</div>
|
||||
|
||||
@@ -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<typeof vi.fn>;
|
||||
const licenseKeyGroupBy = prisma.licenseKey.groupBy as ReturnType<typeof vi.fn>;
|
||||
const licenseKeyFindMany = prisma.licenseKey.findMany as ReturnType<typeof vi.fn>;
|
||||
|
||||
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' }
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 };
|
||||
};
|
||||
@@ -1,66 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { PageProps } from './$types';
|
||||
|
||||
let { data }: PageProps = $props();
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold tracking-tight text-slate-900 sm:text-3xl">License Keys</h1>
|
||||
<p class="mt-1 text-sm text-slate-500">Your active and inactive license keys.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if data.licenseKeys.length > 0}
|
||||
<div class="mt-6 overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm">
|
||||
<!-- Desktop / tablet: table -->
|
||||
<table class="hidden w-full text-left text-sm sm:table">
|
||||
<thead
|
||||
class="border-b border-slate-200 bg-slate-50 text-xs tracking-wide text-slate-500 uppercase"
|
||||
>
|
||||
<tr>
|
||||
<th class="px-6 py-3 font-semibold">Key</th>
|
||||
<th class="px-6 py-3 font-semibold">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100">
|
||||
{#each data.licenseKeys as key (key.key)}
|
||||
<tr class="hover:bg-slate-50">
|
||||
<td class="px-6 py-4 font-mono text-slate-800">{key.key}</td>
|
||||
<td class="px-6 py-4">
|
||||
<span
|
||||
class="inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium {key.active
|
||||
? 'bg-green-100 text-green-700'
|
||||
: 'bg-slate-100 text-slate-600'}"
|
||||
>
|
||||
{key.active ? 'Active' : 'Inactive'}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Mobile: card list -->
|
||||
<ul class="divide-y divide-slate-100 sm:hidden">
|
||||
{#each data.licenseKeys as key (key.key)}
|
||||
<li class="flex items-center justify-between gap-4 px-4 py-4">
|
||||
<span class="min-w-0 font-mono text-sm break-all text-slate-800">{key.key}</span>
|
||||
<span
|
||||
class="shrink-0 rounded-full px-2.5 py-0.5 text-xs font-medium {key.active
|
||||
? 'bg-green-100 text-green-700'
|
||||
: 'bg-slate-100 text-slate-600'}"
|
||||
>
|
||||
{key.active ? 'Active' : 'Inactive'}
|
||||
</span>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="mt-6 rounded-2xl border border-dashed border-slate-300 bg-white p-8 text-center">
|
||||
<p class="text-sm text-slate-600">You don't have any license keys yet.</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -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<typeof vi.fn>;
|
||||
|
||||
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([]);
|
||||
});
|
||||
});
|
||||
+2
-2
@@ -24,8 +24,8 @@
|
||||
</script>
|
||||
|
||||
<div class="mx-auto w-full max-w-3xl">
|
||||
<h1 class="text-2xl font-bold tracking-tight text-slate-900 sm:text-3xl">Account</h1>
|
||||
<p class="mt-1 text-sm text-slate-500 sm:text-base">Manage your profile and addresses.</p>
|
||||
<h1 class="text-2xl font-bold tracking-tight text-slate-900 sm:text-3xl">Profile</h1>
|
||||
<p class="mt-1 text-sm text-slate-500 sm:text-base">Manage your account details and addresses.</p>
|
||||
|
||||
<!-- Email (read-only) -->
|
||||
<div class="mt-6 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm sm:p-6">
|
||||
+1
-1
@@ -26,7 +26,7 @@ const addressCreate = prisma.address.create as ReturnType<typeof vi.fn>;
|
||||
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();
|
||||
+2
-2
@@ -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();
|
||||
});
|
||||
@@ -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 };
|
||||
|
||||
@@ -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 });
|
||||
});
|
||||
|
||||
@@ -114,16 +114,10 @@
|
||||
Dashboard
|
||||
</a>
|
||||
<a
|
||||
href={resolve('/account')}
|
||||
class={`rounded-md px-3 py-2 text-sm font-medium transition ${activeClass('/account')}`}
|
||||
href={resolve('/profile')}
|
||||
class={`rounded-md px-3 py-2 text-sm font-medium transition ${activeClass('/profile')}`}
|
||||
>
|
||||
Account
|
||||
</a>
|
||||
<a
|
||||
href={resolve('/license-keys')}
|
||||
class={`rounded-md px-3 py-2 text-sm font-medium transition ${activeClass('/license-keys')}`}
|
||||
>
|
||||
License Keys
|
||||
Profile
|
||||
</a>
|
||||
|
||||
{#if isAdmin}
|
||||
@@ -277,18 +271,11 @@
|
||||
Dashboard
|
||||
</a>
|
||||
<a
|
||||
href={resolve('/account')}
|
||||
href={resolve('/profile')}
|
||||
onclick={closeMenu}
|
||||
class={`rounded-lg px-4 py-3 text-base font-medium transition ${activeClass('/account')}`}
|
||||
class={`rounded-lg px-4 py-3 text-base font-medium transition ${activeClass('/profile')}`}
|
||||
>
|
||||
Account
|
||||
</a>
|
||||
<a
|
||||
href={resolve('/license-keys')}
|
||||
onclick={closeMenu}
|
||||
class={`rounded-lg px-4 py-3 text-base font-medium transition ${activeClass('/license-keys')}`}
|
||||
>
|
||||
License Keys
|
||||
Profile
|
||||
</a>
|
||||
|
||||
{#if isAdmin}
|
||||
@@ -382,8 +369,8 @@
|
||||
>
|
||||
<p>© {new Date().getFullYear()} Radiant Wave Technologies. All rights reserved.</p>
|
||||
{#if $session.data?.user}
|
||||
<a href={resolve('/account')} class="transition hover:text-slate-900">
|
||||
Manage your account
|
||||
<a href={resolve('/profile')} class="transition hover:text-slate-900">
|
||||
Manage your profile
|
||||
</a>
|
||||
{:else}
|
||||
<a href={resolve('/login')} class="transition hover:text-slate-900">Sign in</a>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.href = '/account';
|
||||
window.location.href = '/dashboard';
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={async () => {
|
||||
await authClient.signIn.social({ provider: 'google', callbackURL: '/account' });
|
||||
await authClient.signIn.social({ provider: 'google', callbackURL: '/dashboard' });
|
||||
}}
|
||||
class="mx-auto mt-4 flex h-11 items-center justify-center rounded-lg bg-white hover:bg-slate-50"
|
||||
>
|
||||
|
||||
@@ -48,7 +48,7 @@ describe('login page', () => {
|
||||
await expect.element(page.getByText('Invalid credentials')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls signIn.social with Google provider and /account callbackURL', async () => {
|
||||
it('calls signIn.social with Google provider and /dashboard callbackURL', async () => {
|
||||
signIn.social.mockResolvedValue({});
|
||||
|
||||
render(Login);
|
||||
@@ -58,7 +58,7 @@ describe('login page', () => {
|
||||
|
||||
expect(signIn.social).toHaveBeenCalledWith({
|
||||
provider: 'google',
|
||||
callbackURL: '/account'
|
||||
callbackURL: '/dashboard'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={async () => {
|
||||
await authClient.signIn.social({ provider: 'google', callbackURL: '/account/setup' });
|
||||
await authClient.signIn.social({ provider: 'google', callbackURL: '/profile/setup' });
|
||||
}}
|
||||
class="mt-4 flex h-11 w-full items-center justify-center rounded-lg border border-slate-300 bg-white hover:bg-slate-50"
|
||||
>
|
||||
|
||||
@@ -44,7 +44,7 @@ describe('register page', () => {
|
||||
await expect.element(page.getByAltText('Sign Up with Google')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls signIn.social with Google provider and /account/setup callbackURL', async () => {
|
||||
it('calls signIn.social with Google provider and /profile/setup callbackURL', async () => {
|
||||
signIn.social.mockResolvedValue({});
|
||||
|
||||
render(Register);
|
||||
@@ -53,7 +53,7 @@ describe('register page', () => {
|
||||
|
||||
expect(signIn.social).toHaveBeenCalledWith({
|
||||
provider: 'google',
|
||||
callbackURL: '/account/setup'
|
||||
callbackURL: '/profile/setup'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user