Skip to content

Commit 8da486a

Browse files
committed
Optimistically remove starred profile upon deletion
1 parent 32bc384 commit 8da486a

1 file changed

Lines changed: 83 additions & 61 deletions

File tree

web/components/searches/button.tsx

Lines changed: 83 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import {FilterFields} from "common/filters";
1010
import {api} from "web/lib/api";
1111
import {DisplayUser} from "common/api/user-types";
1212
import {Link} from "@react-email/components";
13-
import {DOMAIN} from "common/envs/constants";
13+
import {useState} from "react";
14+
import toast from "react-hot-toast";
1415

1516
export function BookmarkSearchButton(props: {
1617
bookmarkedSearches: BookmarkedSearchesType[]
@@ -63,33 +64,35 @@ function ButtonModal(props: {
6364
<Col className={MODAL_CLASS}>
6465
<h3>Saved Searches</h3>
6566
{bookmarkedSearches?.length ? (<>
66-
<p>We'll notify you daily when new people match your searches below.</p>
67-
<Col
68-
className={
69-
'border-ink-300bg-canvas-0 inline-flex flex-col gap-2 rounded-md border p-1 shadow-sm'
70-
}
71-
>
72-
<ol className="list-decimal list-inside space-y-2">
73-
{(bookmarkedSearches || []).map((search) => (
74-
<li key={search.id}
75-
className="items-center justify-between gap-2 list-item marker:text-ink-500 marker:font-bold">
76-
{formatFilters(search.search_filters as Partial<FilterFields>, search.location as locationType)?.join(" • ")}
77-
<button
78-
onClick={async () => {
79-
await deleteBookmarkedSearch(search.id)
80-
refreshBookmarkedSearches()
81-
}}
82-
className="inline-flex text-xl h-5 w-5 items-center justify-center rounded-full text-red-600 hover:bg-red-200 focus:outline-none focus:ring-2 focus:ring-red-400"
83-
>
84-
×
85-
</button>
86-
</li>
87-
))}
88-
</ol>
67+
<p>We'll notify you daily when new people match your searches below.</p>
68+
<Col
69+
className={
70+
'border-ink-300bg-canvas-0 inline-flex flex-col gap-2 rounded-md border p-1 shadow-sm'
71+
}
72+
>
73+
<ol className="list-decimal list-inside space-y-2">
74+
{(bookmarkedSearches || []).map((search) => (
75+
<li key={search.id}
76+
className="items-center justify-between gap-2 list-item marker:text-ink-500 marker:font-bold">
77+
{formatFilters(search.search_filters as Partial<FilterFields>, search.location as locationType)?.join(" • ")}
78+
<button
79+
onClick={async () => {
80+
await deleteBookmarkedSearch(search.id)
81+
refreshBookmarkedSearches()
82+
}}
83+
className="inline-flex text-xl h-5 w-5 items-center justify-center rounded-full text-red-600 hover:bg-red-200 focus:outline-none focus:ring-2 focus:ring-red-400"
84+
>
85+
×
86+
</button>
87+
</li>
88+
))}
89+
</ol>
8990

90-
</Col>
91-
</>
92-
) : <p>You haven't saved any search. To save one, click on Get Notified and we'll notify you daily when new people match it.</p>}
91+
</Col>
92+
</>
93+
) :
94+
<p>You haven't saved any search. To save one, click on Get Notified and we'll notify you daily when new people
95+
match it.</p>}
9396
{/*<BookmarkSearchContent*/}
9497
{/* total={bookmarkedSearches.length}*/}
9598
{/* compatibilityQuestion={bookmarkedSearches[questionIndex]}*/}
@@ -151,6 +154,10 @@ function StarModal(props: {
151154
refreshStars: () => void
152155
}) {
153156
const {open, setOpen, starredUsers, refreshStars} = props
157+
// Track items being optimistically removed so we can hide them immediately
158+
const [removingIds, setRemovingIds] = useState<Set<string>>(new Set())
159+
160+
const visibleUsers = (starredUsers || []).filter((u) => !removingIds.has(u.id))
154161

155162
return (
156163
<Modal
@@ -162,42 +169,57 @@ function StarModal(props: {
162169
>
163170
<Col className={MODAL_CLASS}>
164171
<h3>Saved Profiles</h3>
165-
{starredUsers?.length ? (<>
166-
<p>Here are the profiles you saved:</p>
167-
<Col
168-
className={
169-
'border-ink-300bg-canvas-0 inline-flex flex-col gap-2 rounded-md border p-1 shadow-sm'
170-
}
171-
>
172-
<ol className="list-decimal list-inside space-y-2">
173-
{(starredUsers || []).map((user) => (
174-
<li key={user.id}
175-
className="items-center justify-between gap-2 list-item marker:text-ink-500 marker:font-bold">
176-
{user.name} (<Link
177-
href={`https://${DOMAIN}/${user.username}`}
178-
// style={{color: "#2563eb", textDecoration: "none"}}
179-
>
180-
@{user.username}
181-
</Link>) {' '}
182-
<button
183-
onClick={async () => {
184-
await api('star-profile', {
185-
targetUserId: user.id,
186-
remove: true,
187-
})
188-
refreshStars()
189-
}}
190-
className="inline-flex text-xl h-5 w-5 items-center justify-center rounded-full text-red-600 hover:bg-red-200 focus:outline-none focus:ring-2 focus:ring-red-400"
191-
>
192-
×
193-
</button>
194-
</li>
195-
))}
196-
</ol>
172+
{visibleUsers?.length ? (<>
173+
<p>Here are the profiles you saved:</p>
174+
<Col
175+
className={
176+
'border-ink-300bg-canvas-0 inline-flex flex-col gap-2 rounded-md border p-1 shadow-sm'
177+
}
178+
>
179+
<ol className="list-decimal list-inside space-y-2">
180+
{visibleUsers.map((user) => (
181+
<li key={user.id}
182+
className="items-center justify-between gap-2 list-item marker:text-ink-500 marker:font-bold">
183+
{user.name} (<Link
184+
href={`/${user.username}`}
185+
// style={{color: "#2563eb", textDecoration: "none"}}
186+
>
187+
@{user.username}
188+
</Link>) {' '}
189+
<button
190+
onClick={() => {
191+
// Optimistically remove the user from the list
192+
setRemovingIds((prev) => new Set(prev).add(user.id))
193+
// Fire the API call without blocking UI
194+
api('star-profile', {
195+
targetUserId: user.id,
196+
remove: true,
197+
})
198+
.then(() => {
199+
// Sync with server state
200+
refreshStars()
201+
})
202+
.catch(() => {
203+
toast.error("Couldn't remove saved profile. Please try again.")
204+
// Revert optimistic removal on failure
205+
setRemovingIds((prev) => {
206+
const next = new Set(prev)
207+
next.delete(user.id)
208+
return next
209+
})
210+
})
211+
}}
212+
className="inline-flex text-xl h-5 w-5 items-center justify-center rounded-full text-red-600 hover:bg-red-200 focus:outline-none focus:ring-2 focus:ring-red-400"
213+
>
214+
×
215+
</button>
216+
</li>
217+
))}
218+
</ol>
197219

198-
</Col>
220+
</Col>
199221
</>
200-
) : <p>You haven't saved any profile. To save one, click on the star on their profile page.</p>}
222+
) : <p>You haven't saved any profile. To save one, click on the star on their profile page.</p>}
201223
{/*<BookmarkSearchContent*/}
202224
{/* total={bookmarkedSearches.length}*/}
203225
{/* compatibilityQuestion={bookmarkedSearches[questionIndex]}*/}

0 commit comments

Comments
 (0)