mirror of
https://github.com/Blah-IM/Weblah.git
synced 2025-07-08 23:15:33 +00:00
feat: create account ui
This commit is contained in:
parent
e3e3481739
commit
2b47eeb146
14 changed files with 303 additions and 17 deletions
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
|
15
src/routes/(app)/settings/SettingsListItem.svelte
Normal file
15
src/routes/(app)/settings/SettingsListItem.svelte
Normal 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>
|
12
src/routes/(app)/settings/account/add/+page.svelte
Normal file
12
src/routes/(app)/settings/account/add/+page.svelte
Normal 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>
|
129
src/routes/(app)/settings/account/new/+page.svelte
Normal file
129
src/routes/(app)/settings/account/new/+page.svelte
Normal 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>
|
Loading…
Add table
Add a link
Reference in a new issue