11<script setup lang="ts">
22import { debounce } from ' perfect-debounce'
3+ import { updateProfile as updateProfileUtil } from ' ~/utils/atproto/profile'
34import { normalizeSearchParam } from ' #shared/utils/url'
45
5- const route = useRoute (' /profile/[handle]' )
6- const router = useRouter ()
7-
86type LikesResult = {
97 records: {
108 value: {
@@ -13,6 +11,8 @@ type LikesResult = {
1311 }[]
1412}
1513
14+ const route = useRoute (' /profile/[handle]' )
15+ const router = useRouter ()
1616const handle = computed (() => route .params .handle )
1717
1818const { data : profile }: { data? : NPMXProfile } = useFetch (
@@ -23,6 +23,57 @@ const { data: profile }: { data?: NPMXProfile } = useFetch(
2323 },
2424)
2525
26+ const { user } = useAtproto ()
27+ const isEditing = ref (false )
28+ const displayNameInput = ref ()
29+ const descriptionInput = ref ()
30+ const websiteInput = ref ()
31+ const isUpdateProfileActionPending = ref (false )
32+
33+ watchEffect (() => {
34+ if (isEditing ) {
35+ if (profile ) {
36+ displayNameInput .value = profile .value .displayName
37+ descriptionInput .value = profile .value .description
38+ websiteInput .value = profile .value .website
39+ }
40+ }
41+ })
42+
43+ async function updateProfile() {
44+ if (! user .value .handle || ! displayNameInput .value ) {
45+ return
46+ }
47+
48+ isUpdateProfileActionPending .value = true
49+ const currentProfile = profile .value
50+
51+ // optimistic update
52+ profile .value = {
53+ displayName: displayNameInput .value ,
54+ description: descriptionInput .value ,
55+ website: websiteInput .value ,
56+ }
57+
58+ try {
59+ const result = await updateProfileUtil (handle , {
60+ displayName: displayNameInput .value ,
61+ description: descriptionInput .value ,
62+ website: websiteInput .value ,
63+ })
64+
65+ if (! result .success ) {
66+ profile .value = currentProfile
67+ }
68+
69+ isUpdateProfileActionPending .value = false
70+ isEditing .value = false
71+ } catch (e ) {
72+ profile .value = currentProfile
73+ isUpdateProfileActionPending .value = false
74+ }
75+ }
76+
2677const { data : likesData, status } = await useProfileLikes (handle )
2778
2879useSeoMeta ({
@@ -43,7 +94,49 @@ defineOgImageComponent('Default', {
4394 <main class =" container flex-1 flex flex-col py-8 sm:py-12 w-full" >
4495 <!-- Header -->
4596 <header class =" mb-8 pb-8 border-b border-border" >
46- <div class =" flex flex-col flex-wrap gap-4" >
97+ <!-- Editing Profile -->
98+ <div v-if =" isEditing" class =" flex flex-col flex-wrap gap-4" >
99+ <button @click =" isEditing = false" >Cancel</button >
100+ <button @click.prevent =" updateProfile" :disabled =" isUpdateProfileActionPending" >
101+ Save
102+ </button >
103+ <label for =" displayName" class =" text-sm flex flex-col gap-2" >
104+ Display Name
105+ <input
106+ required
107+ name =" displayName"
108+ type =" text"
109+ class =" w-full min-w-25 bg-bg-subtle border border-border rounded-md ps-3 pe-3 py-1.5 font-mono text-sm text-fg placeholder:text-fg-subtle transition-[border-color,outline-color] duration-300 hover:border-fg-subtle outline-2 outline-transparent focus:border-accent focus-visible:(outline-2 outline-accent/70)"
110+ v-model =" displayNameInput"
111+ />
112+ </label >
113+ <label for =" description" class =" text-sm flex flex-col gap-2" >
114+ Description
115+ <input
116+ name =" description"
117+ type =" text"
118+ placeholder =" No description"
119+ v-model =" descriptionInput"
120+ class =" w-full min-w-25 bg-bg-subtle border border-border rounded-md ps-3 pe-3 py-1.5 font-mono text-sm text-fg placeholder:text-fg-subtle transition-[border-color,outline-color] duration-300 hover:border-fg-subtle outline-2 outline-transparent focus:border-accent focus-visible:(outline-2 outline-accent/70)"
121+ />
122+ </label >
123+ <div class =" flex gap-4 items-center" >
124+ <h2 >@{{ handle }}</h2 >
125+ <div class =" link-subtle font-mono text-sm inline-flex items-center gap-1.5" >
126+ <span class =" i-carbon:link w-4 h-4" aria-hidden =" true" />
127+ <input
128+ name =" website"
129+ type =" "
130+ v-model =" websiteInput"
131+ class =" w-full min-w-25 bg-bg-subtle border border-border rounded-md ps-3 pe-3 py-1.5 font-mono text-sm text-fg placeholder:text-fg-subtle transition-[border-color,outline-color] duration-300 hover:border-fg-subtle outline-2 outline-transparent focus:border-accent focus-visible:(outline-2 outline-accent/70)"
132+ />
133+ </div >
134+ </div >
135+ </div >
136+
137+ <!-- Display Profile -->
138+ <div v-else class =" flex flex-col flex-wrap gap-4" >
139+ <button v-if =" user?.handle === handle" @click =" isEditing = true" >Edit</button >
47140 <h1 class =" font-mono text-2xl sm:text-3xl font-medium" >{{ profile.displayName }}</h1 >
48141 <p v-if =" profile.description" >{{ profile.description }}</p >
49142 <div class =" flex gap-4" >
0 commit comments