refactor: Account management and settings navigation

Move profile page to /settings/account/profile and replace
CurrentAccountPicture with CurrentAccountIndicator component
using the new account manager pattern. Update identity type
to accept null instead of undefined.
This commit is contained in:
Shibo Lyu 2025-04-14 01:33:24 +08:00
parent 73522535f9
commit 924427a810
9 changed files with 39 additions and 51 deletions

View file

@ -3,7 +3,7 @@
import { AvatarBeam } from 'svelte-boring-avatars'; import { AvatarBeam } from 'svelte-boring-avatars';
interface Props { interface Props {
identity: BlahIdentityDescription | undefined; identity: BlahIdentityDescription | null;
size?: number; size?: number;
} }

View file

@ -22,9 +22,9 @@
}); });
let isSettings = $derived($page.route.id?.startsWith('/(app)/settings') ?? true); let isSettings = $derived($page.route.id?.startsWith('/(app)/settings') ?? true);
let mainVisible = let mainVisible = $derived(
$derived(!!$page.params.chatId || !!$page.params.chatId || (isSettings && $page.route.id !== '/(app)/settings')
(isSettings && !$page.route.id?.startsWith('/(app)/settings/_mobile_empty'))); );
</script> </script>
<div <div
@ -32,7 +32,7 @@
data-weblah-main-visible={mainVisible ? 'true' : undefined} data-weblah-main-visible={mainVisible ? 'true' : undefined}
> >
<aside <aside
class="relative h-[100dvh] min-h-0 overflow-hidden border-ss-primary bg-sb-primary shadow-lg [view-transition-name:chat-list] after:pointer-events-none after:absolute after:inset-0 after:size-full after:bg-transparent group-data-weblah-main-visible:after:bg-black/30 sm:w-1/3 sm:border-e sm:after:hidden lg:w-1/4" class="border-ss-primary bg-sb-primary relative h-[100dvh] min-h-0 overflow-hidden shadow-lg [view-transition-name:chat-list] after:pointer-events-none after:absolute after:inset-0 after:size-full after:bg-transparent group-data-weblah-main-visible:after:bg-black/30 sm:w-1/3 sm:border-e sm:after:hidden lg:w-1/4"
> >
<ChatList /> <ChatList />
{#if isSettings} {#if isSettings}
@ -41,7 +41,7 @@
</aside> </aside>
{#if mainVisible} {#if mainVisible}
<main <main
class="absolute inset-0 w-full bg-sb-secondary shadow-lg [view-transition-name:main] sm:relative sm:flex-1 sm:shadow-none" class="bg-sb-secondary absolute inset-0 w-full shadow-lg [view-transition-name:main] sm:relative sm:flex-1 sm:shadow-none"
> >
{@render children?.()} {@render children?.()}
</main> </main>

View file

@ -3,7 +3,7 @@
import InputFrame from '$lib/components/InputFrame.svelte'; import InputFrame from '$lib/components/InputFrame.svelte';
import { Icon, MagnifyingGlass, PencilSquare, XCircle } from 'svelte-hero-icons'; import { Icon, MagnifyingGlass, PencilSquare, XCircle } from 'svelte-hero-icons';
import { tw } from '$lib/tw'; import { tw } from '$lib/tw';
import CurrentAccountPicture from './CurrentAccountPicture.svelte'; import CurrentAccountIndicator from './CurrentAccountIndicator.svelte';
interface Props { interface Props {
searchQuery?: string; searchQuery?: string;
@ -20,15 +20,12 @@
</script> </script>
<header class="border-ss-secondary flex items-center justify-stretch gap-2 border-b p-2 shadow-xs"> <header class="border-ss-secondary flex items-center justify-stretch gap-2 border-b p-2 shadow-xs">
<a <CurrentAccountIndicator
class={tw( class={tw(
'transition-[opacity,transform] duration-200', 'transition-[opacity,transform] duration-200',
isSearchFocused && '-translate-x-full opacity-0' isSearchFocused && '-translate-x-full opacity-0'
)} )}
href="/settings" />
>
<CurrentAccountPicture />
</a>
<InputFrame <InputFrame
class={tw('z-10 h-8 flex-1 transition-all duration-200', isSearchFocused && '-mx-10')} class={tw('z-10 h-8 flex-1 transition-all duration-200', isSearchFocused && '-mx-10')}
> >

View file

@ -0,0 +1,19 @@
<script lang="ts">
import manager from '$lib/accounts/manager.svelte';
import ProfilePicture from '$lib/components/ProfilePicture.svelte';
import { tw } from '$lib/tw';
import type { HTMLAttributes } from 'svelte/elements';
interface Props extends HTMLAttributes<HTMLAnchorElement> {
class?: string;
}
const { class: classNames, ...rest }: Props = $props();
const currentAccount = $derived(manager.currentAccount);
const href = $derived(currentAccount ? '/settings/account/profile' : '/settings/account/add');
</script>
<a {href} class={tw('cursor-default', classNames)} {...rest}>
<ProfilePicture identity={currentAccount} size={32} />
</a>

View file

@ -1,30 +0,0 @@
<script lang="ts">
import {
currentAccountStore,
openAccountStore,
type AccountStore
} from '$lib/accounts/accountStore';
import ProfilePicture from '$lib/components/ProfilePicture.svelte';
import { onMount } from 'svelte';
interface Props {
size?: number;
}
let { size = 32 }: Props = $props();
let accountStore: AccountStore | undefined = $state();
onMount(() => {
openAccountStore().then((store) => {
accountStore = store;
});
});
</script>
{#if accountStore && $accountStore}
{@const currentAccount = $accountStore.find((account) => account.id_key === $currentAccountStore)}
<ProfilePicture identity={currentAccount} {size} />
{:else}
<ProfilePicture identity={undefined} {size} />
{/if}

View file

@ -35,7 +35,7 @@
<SettingsAccountSections /> <SettingsAccountSections />
<GroupedListSection> <GroupedListSection>
<SettingsListItem icon={User} route="">My Profile</SettingsListItem> <SettingsListItem icon={User} route="/account/profile">My Profile</SettingsListItem>
<GroupedListItem icon={DevicePhoneMobile}>Devices</GroupedListItem> <GroupedListItem icon={DevicePhoneMobile}>Devices</GroupedListItem>
</GroupedListSection> </GroupedListSection>

View file

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { page } from '$app/stores'; import { page } from '$app/state';
import { GroupedListItem } from '$lib/components/GroupedList'; import { GroupedListItem } from '$lib/components/GroupedList';
import { type IconSource } from 'svelte-hero-icons'; import { type IconSource } from 'svelte-hero-icons';
@ -11,9 +11,11 @@
let { icon = undefined, route = undefined, children }: Props = $props(); let { icon = undefined, route = undefined, children }: Props = $props();
let selected = $derived(route let selected = $derived(
? $page.route.id?.startsWith(`/(app)/settings${route}`) route
: $page.route.id === '/(app)/settings'); ? page.route.id?.startsWith(`/(app)/settings${route}`)
: page.route.id === '/(app)/settings'
);
let href = $derived(`/settings${route}`); let href = $derived(`/settings${route}`);
</script> </script>

View file

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { page } from '$app/state'; import { page } from '$app/state';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import accountsManager from '$lib/accounts/manager.svelte'; import manager from '$lib/accounts/manager.svelte';
import Button from '$lib/components/Button.svelte'; import Button from '$lib/components/Button.svelte';
import { import {
GroupedListContainer, GroupedListContainer,
@ -47,8 +47,8 @@
isBusy = true; isBusy = true;
try { try {
const idKeyId = await accountsManager.createAccount(profile, password); const idKeyId = await manager.createAccount(profile, password);
accountsManager.currentAccountId = idKeyId; manager.currentAccountId = idKeyId;
goto('/settings'); goto('/settings');
} catch (error) { } catch (error) {
console.error(error); console.error(error);
@ -69,7 +69,7 @@
<GroupedListContainer> <GroupedListContainer>
<GroupedListSection> <GroupedListSection>
<GroupedListContent class="flex items-center"> <GroupedListContent class="flex items-center">
<ProfilePicture size={64} identity={undefined} /> <ProfilePicture size={64} identity={null} />
<input <input
type="text" type="text"
bind:value={name} bind:value={name}