feat(profile): add a desktop Page-tags section #22
2
backend/Cargo.lock
generated
2
backend/Cargo.lock
generated
@@ -1517,7 +1517,7 @@ checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
|
||||
|
||||
[[package]]
|
||||
name = "mangalord"
|
||||
version = "0.88.0"
|
||||
version = "0.89.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mangalord"
|
||||
version = "0.88.0"
|
||||
version = "0.89.0"
|
||||
edition = "2021"
|
||||
default-run = "mangalord"
|
||||
|
||||
|
||||
@@ -50,6 +50,33 @@ test('Profile link in nav for authed users; landing shows counts', async ({ page
|
||||
await expect(page.getByTestId('overview-collections')).toBeVisible();
|
||||
});
|
||||
|
||||
test('Page tags tab reaches the page-tags list on desktop', async ({ page }) => {
|
||||
// Page tags are a first-class Library section on mobile; desktop reaches
|
||||
// them through this profile tab.
|
||||
await stubAuthenticated(page);
|
||||
await page.route('**/api/v1/me/page-tags?*', (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ items: [], page: { limit: 100, offset: 0, total: 0 } })
|
||||
})
|
||||
);
|
||||
// Registered after the general route so distinct URLs resolve here.
|
||||
await page.route('**/api/v1/me/page-tags/distinct*', (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ items: [] })
|
||||
})
|
||||
);
|
||||
|
||||
await page.goto('/profile');
|
||||
await expect(page.getByTestId('tab-page-tags')).toBeVisible();
|
||||
await page.getByTestId('tab-page-tags').click();
|
||||
await expect(page).toHaveURL(/\/profile\/page-tags$/);
|
||||
await expect(page.getByTestId('library-page-tags-empty')).toBeVisible();
|
||||
});
|
||||
|
||||
test('Account tab reaches the password form', async ({ page }) => {
|
||||
await stubAuthenticated(page);
|
||||
await page.goto('/profile');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mangalord-frontend",
|
||||
"version": "0.88.0",
|
||||
"version": "0.89.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import KeyRound from '@lucide/svelte/icons/key-round';
|
||||
import Bookmark from '@lucide/svelte/icons/bookmark';
|
||||
import FolderOpen from '@lucide/svelte/icons/folder-open';
|
||||
import Tag from '@lucide/svelte/icons/tag';
|
||||
import History from '@lucide/svelte/icons/history';
|
||||
|
||||
let { children } = $props();
|
||||
@@ -27,6 +28,7 @@
|
||||
{ href: '/profile/account', label: 'Account', icon: KeyRound, testid: 'tab-account', guestVisible: false },
|
||||
{ href: '/profile/bookmarks', label: 'Bookmarks', icon: Bookmark, testid: 'tab-bookmarks', guestVisible: false },
|
||||
{ href: '/profile/collections', label: 'Collections', icon: FolderOpen, testid: 'tab-collections', guestVisible: false },
|
||||
{ href: '/profile/page-tags', label: 'Page tags', icon: Tag, testid: 'tab-page-tags', guestVisible: false },
|
||||
{ href: '/profile/history', label: 'History', icon: History, testid: 'tab-history', guestVisible: false }
|
||||
];
|
||||
|
||||
|
||||
31
frontend/src/routes/profile/page-tags/+page.svelte
Normal file
31
frontend/src/routes/profile/page-tags/+page.svelte
Normal file
@@ -0,0 +1,31 @@
|
||||
<script lang="ts">
|
||||
import PageTagsList from '$lib/components/PageTagsList.svelte';
|
||||
|
||||
let { data } = $props();
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Mangalord | Page tags</title>
|
||||
</svelte:head>
|
||||
|
||||
{#if data.error}
|
||||
<p class="error" role="alert" data-testid="profile-page-tags-error">
|
||||
Couldn't load page tags: {data.error}
|
||||
</p>
|
||||
{:else if !data.authenticated}
|
||||
<p class="hint" data-testid="profile-page-tags-signin">
|
||||
<a href="/login?next=/profile/page-tags">Sign in</a> to see your page tags.
|
||||
</p>
|
||||
{:else}
|
||||
<PageTagsList initialItems={data.pageTags} initialDistinct={data.distinctPageTags} />
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.hint {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.error {
|
||||
color: var(--danger);
|
||||
}
|
||||
</style>
|
||||
32
frontend/src/routes/profile/page-tags/+page.ts
Normal file
32
frontend/src/routes/profile/page-tags/+page.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { ApiError } from '$lib/api/client';
|
||||
import { listMyPageTags, listMyDistinctPageTags } from '$lib/api/page_tags';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const ssr = false;
|
||||
|
||||
// Desktop home for the user's page tags — the parity counterpart to the
|
||||
// mobile Library "Page tags" tab. Loads the same data the library wrapper
|
||||
// feeds PageTagsList. 401 → unauthenticated path; the page shows a sign-in
|
||||
// prompt.
|
||||
export const load: PageLoad = async () => {
|
||||
try {
|
||||
const [pageTags, distinctPageTags] = await Promise.all([
|
||||
listMyPageTags({ limit: 100 }),
|
||||
listMyDistinctPageTags(undefined, 100)
|
||||
]);
|
||||
return {
|
||||
authenticated: true,
|
||||
pageTags: pageTags.items,
|
||||
distinctPageTags,
|
||||
error: null as string | null
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError && e.status === 401) {
|
||||
return { authenticated: false, pageTags: [], distinctPageTags: [], error: null };
|
||||
}
|
||||
if (e instanceof ApiError) {
|
||||
return { authenticated: true, pageTags: [], distinctPageTags: [], error: e.message };
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user