Something went wrong. Try again.
Monorepo for Tangled
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131<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 { getAuth } from "$lib/auth.svelte"; import { putProfile } from "$lib/api/profile"; import type { ProfileRecord } from "$lib/api/records";
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) ?? "" })) ); let busy = $state(false); let error = $state<string | null>(null);
const inputClass = "w-full rounded border border-border-default bg-background-default px-2 py-1 outline-none focus:border-border-strong focus:ring-1 focus:ring-border-strong";
const save = async (event: SubmitEvent) => { event.preventDefault(); const agent = auth.agent; if (!agent || busy) return; busy = true; error = null; const cleanLinks = links.map((link) => link.value.trim()).filter(Boolean); // put replaces the whole record, so carry unedited fields (avatar, pins, stats). 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 }; try { await putProfile(agent, record); onSaved(record); } catch { error = "Could not save profile. Try again."; } finally { busy = false; } };</script>
<form class="my-2 flex max-w-full flex-col gap-4 text-sm" onsubmit={save}> <div class="flex flex-col gap-1"> <label for="profile-bio">Bio</label> <textarea id="profile-bio" class={inputClass} rows="3" placeholder="Write a bio" bind:value={description}></textarea> </div>
<div class="flex flex-col gap-1"> <label for="profile-pronouns">Pronouns</label> <input id="profile-pronouns" type="text" class={inputClass} placeholder="they/them" bind:value={pronouns} /> </div>
<div class="flex flex-col gap-1"> <label for="profile-location">Location</label> <div class="flex w-full items-center gap-2"> <MapPin class="size-4 shrink-0" aria-hidden="true" /> <input id="profile-location" type="text" class={inputClass} bind:value={location} /> </div> </div>
<div class="flex flex-col gap-1"> <span>Social links</span> <label class="flex items-center gap-2 py-1"> <input type="checkbox" bind:checked={bluesky} /> Link to Bluesky account </label> {#each links as link, index (index)} <div class="flex w-full items-center gap-2"> <Link class="size-4 shrink-0" aria-hidden="true" /> <input type="text" class={inputClass} placeholder={`Social link ${index + 1}`} bind:value={link.value} /> </div> {/each} </div>
{#if error} <p class="text-xs text-foreground-danger">{error}</p> {/if}
<div class="flex items-center justify-between gap-2"> <Button type="submit" variant="default" class="w-full gap-2" loading={busy}> <Check class="size-4" aria-hidden="true" /> Save </Button> <Button variant="default" class="w-full gap-2" disabled={busy} onclick={onCancel}> <X class="size-4" aria-hidden="true" /> Cancel </Button> </div></form>