This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / web / src / lib / components / profile / ProfileEditForm.svelte
3.8 kB 123 lines
1<script lang="ts"> 2 import { untrack } from "svelte"; 3 import Check from "$icon/check"; 4 import Link from "$icon/link"; 5 import MapPin from "$icon/map-pin"; 6 import X from "$icon/x"; 7 import Button from "$lib/components/ui/Button.svelte"; 8 import { getAuth } from "$lib/auth.svelte"; 9 import { putProfile } from "$lib/api/profile"; 10 import type { ProfileRecord } from "$lib/api/records"; 11 import { createAction } from "$lib/action.svelte"; 12 13 interface Props { 14 profile: ProfileRecord | null; 15 onSaved: (record: ProfileRecord) => void; 16 onCancel: () => void; 17 } 18 19 let { profile, onSaved, onCancel }: Props = $props(); 20 21 const auth = getAuth(); 22 23 // seed once; the profile prop can't change while the form is open. 24 const seed = untrack(() => profile); 25 let description = $state(seed?.description ?? ""); 26 let pronouns = $state(seed?.pronouns ?? ""); 27 let location = $state(seed?.location ?? ""); 28 let bluesky = $state(seed?.bluesky ?? false); 29 let links = $state( 30 Array.from({ length: 5 }, (_, index) => ({ 31 value: (seed?.links?.[index] as string | undefined) ?? "" 32 })) 33 ); 34 const inputClass = 35 "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"; 36 37 const save = createAction( 38 async (event: SubmitEvent) => { 39 event.preventDefault(); 40 const agent = auth.agent; 41 if (!agent) return; 42 const cleanLinks = links.map((link) => link.value.trim()).filter(Boolean); 43 // put replaces the whole record, so carry unedited fields (avatar, pins, stats). 44 const record: ProfileRecord = { 45 ...profile, 46 $type: "sh.tangled.actor.profile", 47 bluesky, 48 description: description.trim() || undefined, 49 pronouns: pronouns.trim() || undefined, 50 location: location.trim() || undefined, 51 links: cleanLinks.length > 0 ? (cleanLinks as ProfileRecord["links"]) : undefined 52 }; 53 await putProfile(agent, record); 54 onSaved(record); 55 }, 56 () => "Could not save profile. Try again." 57 ); 58</script> 59 60<form class="my-2 flex max-w-full flex-col gap-4 text-sm" onsubmit={save.run}> 61 <div class="flex flex-col gap-1"> 62 <label for="profile-bio">Bio</label> 63 <textarea 64 id="profile-bio" 65 class={inputClass} 66 rows="3" 67 placeholder="Write a bio" 68 bind:value={description}></textarea> 69 </div> 70 71 <div class="flex flex-col gap-1"> 72 <label for="profile-pronouns">Pronouns</label> 73 <input 74 id="profile-pronouns" 75 type="text" 76 class={inputClass} 77 placeholder="they/them" 78 bind:value={pronouns} 79 /> 80 </div> 81 82 <div class="flex flex-col gap-1"> 83 <label for="profile-location">Location</label> 84 <div class="flex w-full items-center gap-2"> 85 <MapPin class="size-4 shrink-0" aria-hidden="true" /> 86 <input id="profile-location" type="text" class={inputClass} bind:value={location} /> 87 </div> 88 </div> 89 90 <div class="flex flex-col gap-1"> 91 <span>Social links</span> 92 <label class="flex items-center gap-2 py-1"> 93 <input type="checkbox" bind:checked={bluesky} /> 94 Link to Bluesky account 95 </label> 96 {#each links as link, index (index)} 97 <div class="flex w-full items-center gap-2"> 98 <Link class="size-4 shrink-0" aria-hidden="true" /> 99 <input 100 type="text" 101 class={inputClass} 102 placeholder={`Social link ${index + 1}`} 103 bind:value={link.value} 104 /> 105 </div> 106 {/each} 107 </div> 108 109 {#if save.error} 110 <p class="typography-paragraph-small text-foreground-danger">{save.error}</p> 111 {/if} 112 113 <div class="flex items-center justify-between gap-2"> 114 <Button type="submit" variant="default" class="w-full gap-2" loading={save.loading}> 115 <Check class="size-4" aria-hidden="true" /> 116 Save 117 </Button> 118 <Button variant="default" class="w-full gap-2" disabled={save.loading} onclick={onCancel}> 119 <X class="size-4" aria-hidden="true" /> 120 Cancel 121 </Button> 122 </div> 123</form>