feat: create account ui

This commit is contained in:
Shibo Lyu 2024-10-16 02:22:31 +08:00
parent e3e3481739
commit 2b47eeb146
14 changed files with 303 additions and 17 deletions

View file

@ -5,7 +5,8 @@
import SettingsList from './settings/SettingsList.svelte';
onNavigate((navigation) => {
if (!document.startViewTransition) return;
if (!document.startViewTransition || navigation.from?.url.href === navigation.to?.url.href)
return;
return new Promise((resolve) => {
document.startViewTransition(async () => {
@ -15,7 +16,7 @@
});
});
$: isSettings = $page.route.id?.startsWith('/(app)/settings');
$: isSettings = $page.route.id?.startsWith('/(app)/settings') ?? true;
$: mainVisible =
!!$page.params.chatId ||
(isSettings && !$page.route.id?.startsWith('/(app)/settings/_mobile_empty'));
@ -30,7 +31,7 @@
>
<ChatList />
{#if isSettings}
<SettingsList class="absolute inset-0 z-10 size-full [view-transition-name:settings-list]" />
<SettingsList class="absolute inset-0 z-10 size-full origin-top-left" />
{/if}
</aside>
{#if mainVisible}

View file

@ -3,6 +3,7 @@
import { GroupedListSection, GroupedListItem } from '$lib/components/GroupedList';
import { tw } from '$lib/tw';
import {
ArrowRightEndOnRectangle,
Bell,
Cog,
DevicePhoneMobile,
@ -11,22 +12,32 @@
QuestionMarkCircle,
UserPlus
} from 'svelte-hero-icons';
import { scale } from 'svelte/transition';
import SettingsListItem from './SettingsListItem.svelte';
import PageHeader from '$lib/components/PageHeader.svelte';
let className = '';
export { className as class };
</script>
<div class={tw('flex flex-col bg-sb-secondary shadow-md', className)}>
<div class="flex items-center border-b border-ss-secondary bg-sb-primary p-2 shadow-sm">
<div
class={tw('flex flex-col bg-sb-secondary shadow-md', className)}
transition:scale={{ duration: 250, start: 0.95 }}
>
<PageHeader>
<Button href="/">Done</Button>
<h2 class="flex-1 truncate text-center font-semibold text-sf-primary">Settings</h2>
</div>
<h2 class="flex-1 truncate text-center">Settings</h2>
<Button href="/account/profile">Edit</Button>
</PageHeader>
<div class="flex-1 overflow-y-scroll">
<GroupedListSection>
<GroupedListItem icon={UserPlus}>Add Account</GroupedListItem>
<SettingsListItem icon={ArrowRightEndOnRectangle} route="/account/add">
Sign in
</SettingsListItem>
<SettingsListItem icon={UserPlus} route="/account/new">Create Account</SettingsListItem>
</GroupedListSection>
<GroupedListSection>
<GroupedListItem icon={Cog} selected>General</GroupedListItem>
<SettingsListItem icon={Cog} route="">General</SettingsListItem>
<GroupedListItem icon={Bell}>Notifications</GroupedListItem>
<GroupedListItem icon={LockClosed}>Privacy and Security</GroupedListItem>
<GroupedListItem icon={DevicePhoneMobile}>Devices</GroupedListItem>

View file

@ -0,0 +1,15 @@
<script lang="ts">
import { page } from '$app/stores';
import { GroupedListItem } from '$lib/components/GroupedList';
import { type IconSource } from 'svelte-hero-icons';
export let icon: IconSource | undefined = undefined;
export let route: string | undefined = undefined;
$: selected = route
? $page.route.id?.startsWith(`/(app)/settings${route}`)
: $page.route.id === '/(app)/settings';
$: href = `/settings${route}`;
</script>
<GroupedListItem {icon} {selected} {href}><slot /></GroupedListItem>

View file

@ -0,0 +1,12 @@
<script lang="ts">
import { GroupedListContainer, GroupedListSection } from '$lib/components/GroupedList';
import PageHeader from '$lib/components/PageHeader.svelte';
</script>
<PageHeader><h3 class="flex-1">Sign in</h3></PageHeader>
<GroupedListContainer>
<GroupedListSection>
<p slot="footer">New here? <a class="text-accent-500" href="new">Create a new account</a>.</p>
</GroupedListSection>
</GroupedListContainer>

View file

@ -0,0 +1,129 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { openAccountStore } from '$lib/accounts/accountStore';
import Button from '$lib/components/Button.svelte';
import {
GroupedListContainer,
GroupedListSection,
GroupedListItem,
GroupedListInputItem
} from '$lib/components/GroupedList';
import LoadingIndicator from '$lib/components/LoadingIndicator.svelte';
import PageHeader from '$lib/components/PageHeader.svelte';
import ProfilePicture from '$lib/components/ProfilePicture.svelte';
import RichTextInput from '$lib/components/RichTextInput.svelte';
import type { BlahProfile } from '@blah-im/core/identity';
import { onMount } from 'svelte';
import type { Delta, Editor } from 'typewriter-editor';
let name: string = '';
let editor: Editor | undefined;
let delta: Delta;
let plainText: string = '';
let password: string = '';
let repeatPassword: string = '';
let isBusy: boolean = false;
let bioPlaceholder = 'Introduce yourself.';
const bioPlaceholders = [
'a 23 yo. designer from Tokyo.',
'a 19 yo. student from New York.',
'a 30 yo. developer from Berlin.',
'a 25 yo. artist from Paris.',
'a 28 yo. writer from London.'
];
$: passwordMatch = password === repeatPassword;
$: canCreate = name.length > 0 && password.length > 0 && passwordMatch;
onMount(() => {
const bioPlaceholderRotateRef = setInterval(() => {
bioPlaceholder = bioPlaceholders[Math.floor(Math.random() * bioPlaceholders.length)];
}, 5000);
return () => clearInterval(bioPlaceholderRotateRef);
});
async function createAccount() {
const profile: BlahProfile = {
typ: 'profile',
name,
bio: plainText,
preferred_chat_server_urls: [],
id_urls: []
};
isBusy = true;
try {
const accountStore = await openAccountStore();
await accountStore.createAccount(profile, password);
} catch (error) {
console.error(error);
}
isBusy = false;
goto('/settings');
}
</script>
<PageHeader>
<h3 class="flex-1">Create Account</h3>
{#if isBusy}
<LoadingIndicator class="size-4" />
{:else}
<Button variant="primary" disabled={!canCreate} on:click={createAccount}>Create</Button>
{/if}
</PageHeader>
<GroupedListContainer>
<GroupedListSection>
<GroupedListItem>
<ProfilePicture size={64} account={undefined} />
<input
type="text"
bind:value={name}
placeholder="Your Name"
disabled={isBusy}
class="ms-3 flex-1 bg-transparent text-lg leading-loose caret-accent-500 outline-none placeholder:opacity-50"
/>
</GroupedListItem>
</GroupedListSection>
<GroupedListSection>
<h4 slot="header">Bio</h4>
<RichTextInput
class="p-4 shadow-none ring-0"
bind:editor
bind:delta
bind:plainText
placeholder={bioPlaceholder}
/>
<p slot="footer">Introduce yourself. This will be public for everyone to see.</p>
</GroupedListSection>
<GroupedListSection>
<h4 slot="header">Security</h4>
<GroupedListInputItem>
Password
<input type="password" bind:value={password} placeholder="Password" disabled={isBusy} />
</GroupedListInputItem>
<GroupedListInputItem>
Repeat Password
<input
type="password"
bind:value={repeatPassword}
placeholder="Repeat Password"
disabled={isBusy}
/>
</GroupedListInputItem>
<div slot="footer" class="space-y-1">
<p>
Sensitive actions like signing in on new devices require your password. Make sure it's
unique and secure. You'll lose access to your account if you forget it.
</p>
{#if !passwordMatch && repeatPassword}
<p><strong>Passwords do not match.</strong></p>
{/if}
</div>
</GroupedListSection>
</GroupedListContainer>