mirror of
https://github.com/Blah-IM/Weblah.git
synced 2025-04-30 16:21:09 +00:00
feat[wip]: domain setup dialog
This commit is contained in:
parent
1880726a8c
commit
b3e16f2063
6 changed files with 133 additions and 21 deletions
|
@ -12,13 +12,13 @@
|
|||
|
||||
const className = $derived(
|
||||
tw(
|
||||
'text-sf-secondary bg-sb-primary ring-ss-secondary inline-flex cursor-default items-center justify-center rounded-md px-2.5 py-1 shadow-xs ring-1',
|
||||
'text-sf-secondary bg-sb-primary inset-ring-ss-secondary inline-flex cursor-default items-center justify-center rounded-md px-2.5 py-1 shadow-xs inset-ring',
|
||||
'hover:ring-ss-primary font-normal transition-shadow duration-200 active:shadow-inner',
|
||||
variant === 'primary' && [
|
||||
'relative text-slate-50 ring-0 duration-200',
|
||||
'before:absolute before:-inset-px before:rounded-[7px]',
|
||||
'before:from-accent-400 before:to-accent-500 before:bg-linear-to-b before:from-40% before:ring-1 before:ring-black/10 before:ring-inset',
|
||||
'dark:before:from-accent-500 dark:before:to-accent-600 before:transition-shadow active:before:shadow-inner'
|
||||
'dark:before:from-accent-600 dark:before:to-accent-700 before:transition-shadow active:before:shadow-inner'
|
||||
],
|
||||
externalClass
|
||||
)
|
||||
|
|
43
src/lib/components/Dialog.svelte
Normal file
43
src/lib/components/Dialog.svelte
Normal file
|
@ -0,0 +1,43 @@
|
|||
<script lang="ts">
|
||||
import { tw } from '$lib/tw';
|
||||
import { Dialog } from 'bits-ui';
|
||||
import { fade, fly } from 'svelte/transition';
|
||||
|
||||
const { Root, Overlay, Portal, Content } = Dialog;
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
children?: import('svelte').Snippet;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
let { open = $bindable(false), children, class: className }: Props = $props();
|
||||
</script>
|
||||
|
||||
<Root bind:open>
|
||||
<Portal>
|
||||
<Overlay forceMount>
|
||||
{#snippet child({ props, open })}
|
||||
{#if open}
|
||||
<div {...props} class="fixed inset-0 z-50 bg-black/50" transition:fade></div>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</Overlay>
|
||||
<Content forceMount>
|
||||
{#snippet child({ props, open })}
|
||||
{#if open}
|
||||
<div
|
||||
{...props}
|
||||
class={tw(
|
||||
'bg-sb-secondary border-ss-secondary shadow-3xl fixed inset-1/2 z-50 -translate-1/2 overflow-hidden rounded-lg border sm:min-h-64 sm:min-w-lg',
|
||||
className
|
||||
)}
|
||||
transition:fly={{ y: -25 }}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</Content>
|
||||
</Portal>
|
||||
</Root>
|
|
@ -1,9 +1,15 @@
|
|||
<script lang="ts">
|
||||
interface Props {
|
||||
children?: import('svelte').Snippet;
|
||||
}
|
||||
import { tw } from '$lib/tw';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
let { children }: Props = $props();
|
||||
type Props = {
|
||||
children?: import('svelte').Snippet;
|
||||
class?: string;
|
||||
} & Omit<HTMLAttributes<HTMLDivElement>, 'class'>;
|
||||
|
||||
let { children, class: className, ...rest }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="mx-auto max-w-3xl">{@render children?.()}</div>
|
||||
<div class={tw('mx-auto max-w-3xl', className)} {...rest}>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
|
|
|
@ -11,7 +11,10 @@
|
|||
class?: string;
|
||||
} & (
|
||||
| ({ href: string } & Omit<HTMLAnchorAttributes, 'class' | 'href'>)
|
||||
| ({ onclick: Required<HTMLButtonAttributes['onclick']> } & Omit<HTMLButtonAttributes, 'class'>)
|
||||
| ({ onclick: Exclude<HTMLButtonAttributes['onclick'], null | undefined> } & Omit<
|
||||
HTMLButtonAttributes,
|
||||
'class' | 'onclick'
|
||||
>)
|
||||
| Omit<HTMLAttributes<HTMLDivElement>, 'onclick'>
|
||||
);
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<script lang="ts">
|
||||
import Button from '$lib/components/Button.svelte';
|
||||
import Dialog from '$lib/components/Dialog.svelte';
|
||||
import { GroupedListItem } from '$lib/components/GroupedList';
|
||||
import LoadingIndicator from '$lib/components/LoadingIndicator.svelte';
|
||||
import { idURLToUsername, validateIDURL } from '$lib/idURL';
|
||||
import { idURLToUsername, validateIDURL, type IDURLValidity } from '$lib/idURL';
|
||||
import type { BlahIdentity } from '@blah-im/core/identity';
|
||||
import { AtSymbol, ExclamationCircle, ExclamationTriangle, Icon } from 'svelte-hero-icons';
|
||||
import { AtSymbol, ExclamationCircle, Icon } from 'svelte-hero-icons';
|
||||
import UsernameSetupSelfhostDialog from './UsernameSetupSelfhostDialog.svelte';
|
||||
|
||||
interface Props {
|
||||
url: string;
|
||||
|
@ -12,27 +12,36 @@
|
|||
}
|
||||
let { url, identity }: Props = $props();
|
||||
|
||||
let validationResult = $state<IDURLValidity | null>(null);
|
||||
let showFixDialog = $state(false);
|
||||
|
||||
async function validate() {
|
||||
if (!identity) return null;
|
||||
|
||||
return await validateIDURL(url, identity);
|
||||
validationResult = await validateIDURL(url, identity);
|
||||
}
|
||||
|
||||
$effect.pre(() => {
|
||||
if (identity && !showFixDialog) {
|
||||
validate();
|
||||
}
|
||||
});
|
||||
|
||||
const isInvalid = $derived(validationResult && !validationResult.valid);
|
||||
</script>
|
||||
|
||||
<GroupedListItem>
|
||||
<GroupedListItem onclick={isInvalid ? () => (showFixDialog = true) : undefined}>
|
||||
<div class="flex items-center gap-0.5">
|
||||
<Icon micro src={AtSymbol} class="size-3.5 opacity-90" />
|
||||
<span>{idURLToUsername(url)}</span>
|
||||
</div>
|
||||
|
||||
{#snippet badge()}
|
||||
{#await validate()}
|
||||
<LoadingIndicator />
|
||||
{:then result}
|
||||
{#if result && !result.valid}
|
||||
<Icon mini src={ExclamationCircle} class="size-5 fill-red-500 dark:fill-red-400" />
|
||||
<Button>Fix</Button>
|
||||
{#if isInvalid}
|
||||
<Icon mini src={ExclamationCircle} class="size-5 fill-red-500 dark:fill-red-400" />
|
||||
{#if url && identity}
|
||||
<UsernameSetupSelfhostDialog bind:open={showFixDialog} {url} {identity} />
|
||||
{/if}
|
||||
{/await}
|
||||
{/if}
|
||||
{/snippet}
|
||||
</GroupedListItem>
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
<script lang="ts">
|
||||
import Button from '$lib/components/Button.svelte';
|
||||
import Dialog from '$lib/components/Dialog.svelte';
|
||||
import { GroupedListContainer, GroupedListSection } from '$lib/components/GroupedList';
|
||||
import GroupedListContent from '$lib/components/GroupedList/GroupedListContent.svelte';
|
||||
import PageHeader from '$lib/components/PageHeader.svelte';
|
||||
import { idURLToUsername } from '$lib/idURL';
|
||||
import type { BlahIdentity } from '@blah-im/core/identity';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
url: string;
|
||||
identity: BlahIdentity;
|
||||
}
|
||||
|
||||
let { open = $bindable(), url, identity }: Props = $props();
|
||||
|
||||
const username = $derived(idURLToUsername(url));
|
||||
const profileDescriptionString = $derived(
|
||||
JSON.stringify(identity.generateIdentityDescription(), null, 2)
|
||||
);
|
||||
</script>
|
||||
|
||||
<Dialog bind:open class="flex h-2/3 flex-col">
|
||||
<PageHeader>
|
||||
<h3 class="flex-1">Setup Domain {username}</h3>
|
||||
<Button variant="primary" onclick={() => (open = false)}>Done</Button>
|
||||
</PageHeader>
|
||||
|
||||
<GroupedListContainer class="w-full grow overflow-x-auto">
|
||||
<GroupedListSection>
|
||||
{#snippet header()}
|
||||
<div class="-me-4 flex min-w-0 items-end gap-2 text-base normal-case">
|
||||
<p class="text-sf-primary">
|
||||
For others to validate your domain as your username, put the content below at
|
||||
<code>/.well-known/blah/profile.json</code>
|
||||
under your domain.
|
||||
</p>
|
||||
<Button>Copy</Button>
|
||||
</div>
|
||||
{/snippet}
|
||||
<GroupedListContent class="p-0">
|
||||
<textarea
|
||||
readonly
|
||||
class="text-sf-primary block h-100 w-full resize-none overflow-x-auto px-4 py-3 font-mono"
|
||||
value={profileDescriptionString}
|
||||
></textarea>
|
||||
</GroupedListContent>
|
||||
</GroupedListSection>
|
||||
</GroupedListContainer>
|
||||
</Dialog>
|
Loading…
Add table
Reference in a new issue