Something went wrong. Try again.
Monorepo for Tangled
Something went wrong. Try again.
Svelte
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119<script lang="ts"> import { untrack } from "svelte"; import Check from "$icon/check"; import Link from "$icon/link"; import MapPin from "$icon/map-pin"; import X from "$icon/x"; import Button from "$lib/components/ui/Button.svelte"; import Checkbox from "$lib/components/ui/Checkbox.svelte"; import Field from "$lib/components/ui/Field.svelte"; import Input from "$lib/components/ui/Input.svelte"; import Textarea from "$lib/components/ui/Textarea.svelte"; import { getAuth } from "$lib/auth.svelte"; import { putProfile } from "$lib/api/profile"; import { awaitIndexedOrSaved } from "$lib/api/awaitRecord"; import { createBobbinClient } from "$lib/api/client"; import type { ProfileRecord } from "$lib/api/records"; import { createAction } from "$lib/action.svelte";
interface Props { profile: ProfileRecord | null; onSaved: (record: ProfileRecord) => void; onCancel: () => void; }
let { profile, onSaved, onCancel }: Props = $props();
const auth = getAuth();
// seed once; the profile prop can't change while the form is open. const seed = untrack(() => profile); let description = $state(seed?.description ?? ""); let pronouns = $state(seed?.pronouns ?? ""); let location = $state(seed?.location ?? ""); let bluesky = $state(seed?.bluesky ?? false); let links = $state( Array.from({ length: 5 }, (_, index) => ({ value: (seed?.links?.[index] as string | undefined) ?? "" })) ); const save = createAction(async (event: SubmitEvent) => { event.preventDefault(); const agent = auth.agent; if (!agent) return; const cleanLinks = links.map((link) => link.value.trim()).filter(Boolean); const record: ProfileRecord = { ...profile, $type: "sh.tangled.actor.profile", bluesky, description: description.trim() || undefined, pronouns: pronouns.trim() || undefined, location: location.trim() || undefined, links: cleanLinks.length > 0 ? (cleanLinks as ProfileRecord["links"]) : undefined }; const written = await putProfile(agent, record); const ctx = createBobbinClient({ serviceUrl: auth.bobbinUrl }); await awaitIndexedOrSaved(ctx, written); onSaved(record); });</script>
<form class="my-xs flex max-w-full flex-col gap-md typography-paragraph-small" onsubmit={save.run}> <Field label="Bio" labelCase="normal"> {#snippet children({ id })} <Textarea {id} rows={3} resizeable placeholder="Write a bio" bind:value={description} /> {/snippet} </Field>
<Field label="Pronouns" labelCase="normal"> {#snippet children({ id })} <Input {id} placeholder="they/them" bind:value={pronouns} /> {/snippet} </Field>
<Field label="Location" labelCase="normal"> {#snippet children({ id })} <Input {id} iconLeft={MapPin} bind:value={location} /> {/snippet} </Field>
<div class="flex flex-col gap-2xs"> <span>Social links</span> <Checkbox class="py-2xs" bind:checked={bluesky}>Link to Bluesky account</Checkbox> {#each links as link, index (index)} <Input iconLeft={Link} placeholder={`Social link ${index + 1}`} bind:value={link.value} /> {/each} </div>
{#if save.error} <p class="typography-paragraph-small text-foreground-danger">{save.error}</p> {/if}
<div class="flex items-center justify-between gap-xs"> <Button insetShadow={true} variant="default" class="w-full gap-xs" disabled={save.loading} onclick={onCancel} icon={X} > Cancel </Button> <Button insetShadow={true} type="submit" variant="default" class="w-full gap-xs" loading={save.loading} icon={Check} > Save </Button> </div></form>