feat(frontend): mobile account hub + library wrapper (0.60.0)
Phase 5 (final) of the mobile redesign: /profile/account becomes an
inset-grouped iOS-style hub on mobile and the new /library route hosts
a SegmentedControl over Bookmarks / Collections / History. Profile-
layout horizontal tabs hide below 640px. Desktop layout is unchanged.
- New /library/+page.ts loads bookmarks, collections, and read-history
in parallel so segmented-control tabs swap instantly without a new
round trip. 401 → unauthenticated path with sign-in prompt.
- New /library/+page.svelte renders the SegmentedControl with ?tab=
as the source of truth. Bookmarks reuses the existing BookmarkList,
Collections reuses CollectionsGrid, History inlines a slim cover +
title + "Continue Ch. N" row list. `goto(..., { replaceState: true })`
drives the URL — plain `replaceState` from $app/navigation doesn't
reliably re-trigger $page-derived state.
- BottomNav's Library tab now points at /library (Phase 1 placeholder
was /bookmarks). The Phase 1 mobile-chrome spec is updated to expect
the new target and mocks the additional library data endpoints.
- /profile/+layout.svelte hides the horizontal tabs below 640px — the
bottom-nav Library/Account tabs plus the account hub carry mobile
cross-section navigation.
- /profile/account/+page.svelte gains a mobile hub: a centered avatar
+ username + "Member since" header, a row group with Profile /
Preferences / Change password rows (the last opening a bottom
Sheet hosting the existing password form snippet), and a separate
group with a red "Log out" row that reuses the layout's logout
flow (logout API → session.setUser(null) → preferences.clearForLogout
→ goto('/login')). Desktop keeps the inline card with the form.
- matchMedia gates the hub vs. desktop card so the password form
testids never duplicate on the page — the snippet is rendered in
exactly one mount at a time.
- 9 Playwright tests cover the Library nav handoff, segmented-control
sub-tab swap with URL, sign-in CTAs on both routes, account hub
composition, password sheet open flow, logout flow (POST /auth/
logout + redirect to /login), profile tabs hiding on mobile, and
the desktop regression where the inline card is the only password
surface.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
44
frontend/src/routes/library/+page.ts
Normal file
44
frontend/src/routes/library/+page.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { ApiError } from '$lib/api/client';
|
||||
import { listMyBookmarks } from '$lib/api/bookmarks';
|
||||
import { listMyCollections } from '$lib/api/collections';
|
||||
import { listMyReadProgress } from '$lib/api/read_progress';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const ssr = false;
|
||||
|
||||
/**
|
||||
* Loads bookmarks + collections + reading-history in one shot so the
|
||||
* Library segmented control can swap between sub-tabs without firing a
|
||||
* second round trip per tap. 401 → unauthenticated path; the page
|
||||
* surfaces a sign-in prompt and renders empty lists.
|
||||
*/
|
||||
export const load: PageLoad = async () => {
|
||||
const empty = {
|
||||
authenticated: true,
|
||||
bookmarks: [] as Awaited<ReturnType<typeof listMyBookmarks>>['items'],
|
||||
collections: [] as Awaited<ReturnType<typeof listMyCollections>>['items'],
|
||||
history: [] as Awaited<ReturnType<typeof listMyReadProgress>>['items'],
|
||||
error: null as string | null
|
||||
};
|
||||
try {
|
||||
const [bookmarks, collections, history] = await Promise.all([
|
||||
listMyBookmarks(),
|
||||
listMyCollections({ limit: 200 }),
|
||||
listMyReadProgress({ limit: 100 })
|
||||
]);
|
||||
return {
|
||||
...empty,
|
||||
bookmarks: bookmarks.items,
|
||||
collections: collections.items,
|
||||
history: history.items
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError && e.status === 401) {
|
||||
return { ...empty, authenticated: false };
|
||||
}
|
||||
if (e instanceof ApiError) {
|
||||
return { ...empty, error: e.message };
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user