Add option to save / bookmark profiles

This commit is contained in:
MartinBraquet
2025-10-15 15:45:47 +02:00
parent 5d763c18c8
commit 32bc3847fa
8 changed files with 170 additions and 26 deletions

View File

@@ -10,13 +10,14 @@ import {Select} from 'web/components/widgets/select'
import {DesktopFilters} from './desktop-filters'
import {LocationFilterProps} from './location-filter'
import {MobileFilters} from './mobile-filters'
import {BookmarkSearchButton} from "web/components/searches/button";
import {BookmarkSearchButton, BookmarkStarButton} from "web/components/searches/button";
import {BookmarkedSearchesType} from "web/hooks/use-bookmarked-searches";
import {submitBookmarkedSearch} from "web/lib/supabase/searches";
import {useUser} from "web/hooks/use-user";
import {isEqual} from "lodash";
import toast from "react-hot-toast";
import {FilterFields, initialFilters} from "common/filters";
import {DisplayUser} from "common/api/user-types";
function isOrderBy(input: string): input is FilterFields['orderBy'] {
return ['last_online_time', 'created_time', 'compatibility_score'].includes(
@@ -96,7 +97,8 @@ function getRandomPair(count = 3): string {
const MAX_BOOKMARKED_SEARCHES = 10;
export const Search = (props: {
youProfile: Profile | undefined | null
starredUserIds: string[]
starredUsers: DisplayUser[]
refreshStars: () => void
// filter props
filters: Partial<FilterFields>
updateFilter: (newState: Partial<FilterFields>) => void
@@ -117,6 +119,8 @@ export const Search = (props: {
filters,
bookmarkedSearches,
refreshBookmarkedSearches,
starredUsers,
refreshStars,
} = props
const [openFiltersModal, setOpenFiltersModal] = useState(false)
@@ -128,6 +132,7 @@ export const Search = (props: {
const [bookmarked, setBookmarked] = useState(false);
const [loadingBookmark, setLoadingBookmark] = useState(false);
const [openBookmarks, setOpenBookmarks] = useState(false);
const [openStarBookmarks, setOpenStarBookmarks] = useState(false);
const user = useUser()
useEffect(() => {
@@ -254,7 +259,7 @@ export const Search = (props: {
color={'none'}
className={'bg-canvas-100 hover:bg-canvas-200'}
>
{bookmarked ? 'Bookmarked!' : loadingBookmark ? '' : 'Get Notified'}
{bookmarked ? 'Saved!' : loadingBookmark ? '' : 'Get Notified'}
</Button>
<BookmarkSearchButton
@@ -263,6 +268,16 @@ export const Search = (props: {
open={openBookmarks}
setOpen={setOpenBookmarks}
/>
<BookmarkStarButton
refreshStars={refreshStars}
starredUsers={starredUsers}
open={openStarBookmarks}
setOpen={(checked) => {
setOpenStarBookmarks(checked)
refreshStars()
}}
/>
</Row>
</Col>
)

View File

@@ -66,7 +66,7 @@ export const ProfileGrid = (props: {
{!isLoadingMore && !isReloading && other_profiles.length === 0 && (
<div className="py-8 text-center">
<p>No profiles found.</p>
<p>Feel free to click on Get Notified and we'll notify you when new users match it!</p>
<p>Feel free to click on Get Notified and we'll notify you when new users match your search!</p>
</div>
)}
</div>

View File

@@ -22,6 +22,7 @@ import {useState} from 'react'
import {VisibilityConfirmationModal} from './visibility-confirmation-modal'
import {deleteAccount} from "web/lib/util/delete";
import toast from "react-hot-toast";
import {StarButton} from "web/components/widgets/star-button";
export default function ProfileHeader(props: {
user: User
@@ -38,8 +39,8 @@ export default function ProfileHeader(props: {
profile,
userActivity,
simpleView,
// starredUserIds,
// refreshStars,
starredUserIds,
refreshStars,
showMessageButton,
refreshProfile,
} = props
@@ -145,13 +146,13 @@ export default function ProfileHeader(props: {
className="sm:flex"
username={user.username}
/>
{/*{currentUser && (*/}
{/* <StarButton*/}
{/* targetProfile={profile}*/}
{/* isStarred={starredUserIds.includes(user.id)}*/}
{/* refresh={refreshStars}*/}
{/* />*/}
{/*)}*/}
{currentUser && (
<StarButton
targetProfile={profile}
isStarred={starredUserIds.includes(user.id)}
refresh={refreshStars}
/>
)}
{currentUser && showMessageButton && (
<SendMessageButton toUser={user} currentUser={currentUser}/>
)}

View File

@@ -31,11 +31,12 @@ export function ProfileInfo(props: {
// const currentProfile = useProfile()
// const isCurrentUser = currentUser?.id === user.id
const {data: starredUserIds, refresh: refreshStars} = useGetter(
const {data: starredUsers, refresh: refreshStars} = useGetter(
'stars',
currentUser?.id,
getStars
)
const starredUserIds = starredUsers?.map((u) => u.id)
// const { data, refresh } = useAPIGetter('get-likes-and-ships', {
// userId: user.id,

View File

@@ -13,7 +13,6 @@ import {usePersistentInMemoryState} from 'web/hooks/use-persistent-in-memory-sta
import {useUser} from 'web/hooks/use-user'
import {api} from 'web/lib/api'
import {useBookmarkedSearches} from "web/hooks/use-bookmarked-searches";
import {orderProfiles} from "common/filters";
import {useFilters} from "web/components/filters/use-filters";
export function ProfilesHome() {
@@ -64,9 +63,12 @@ export function ProfilesHome() {
});
}, [filters]);
const {data: starredUserIds, refresh: refreshStars} = useGetter('star', user?.id, getStars);
const compatibleProfiles = useCompatibleProfiles(user?.id);
const displayProfiles = profiles && orderProfiles(profiles, starredUserIds);
const {data: starredUsers, refresh: refreshStars} = useGetter('star', user?.id, getStars)
const starredUserIds = starredUsers?.map((u) => u.id)
const compatibleProfiles = useCompatibleProfiles(user?.id)
// const displayProfiles = profiles && orderProfiles(profiles, starredUserIds);
const displayProfiles = profiles
const loadMore = useCallback(async () => {
if (!profiles || isLoadingMore) return false;
@@ -96,7 +98,8 @@ export function ProfilesHome() {
<Title className="!mb-2 text-3xl">Profiles</Title>
<Search
youProfile={you}
starredUserIds={starredUserIds ?? []}
starredUsers={starredUsers ?? []}
refreshStars={refreshStars}
filters={filters}
updateFilter={updateFilter}
clearFilters={clearFilters}

View File

@@ -7,6 +7,10 @@ import {useUser} from "web/hooks/use-user";
import {deleteBookmarkedSearch} from "web/lib/supabase/searches";
import {formatFilters, locationType} from "common/searches";
import {FilterFields} from "common/filters";
import {api} from "web/lib/api";
import {DisplayUser} from "common/api/user-types";
import {Link} from "@react-email/components";
import {DOMAIN} from "common/envs/constants";
export function BookmarkSearchButton(props: {
bookmarkedSearches: BookmarkedSearchesType[]
@@ -26,7 +30,7 @@ export function BookmarkSearchButton(props: {
return (
<>
<Button onClick={() => setOpen(true)} color="gray-outline" size={'xs'}>
My Bookmarked Searches
Saved Searches
</Button>
<ButtonModal
open={open}
@@ -57,11 +61,12 @@ function ButtonModal(props: {
}}
>
<Col className={MODAL_CLASS}>
<h3>Bookmarked Searches</h3>
<p className='text-sm'>We'll notify you daily when new people match your searches below.</p>
<h3>Saved Searches</h3>
{bookmarkedSearches?.length ? (<>
<p>We'll notify you daily when new people match your searches below.</p>
<Col
className={
'border-ink-300bg-canvas-0 inline-flex flex-col gap-2 rounded-md border p-1 text-sm shadow-sm'
'border-ink-300bg-canvas-0 inline-flex flex-col gap-2 rounded-md border p-1 shadow-sm'
}
>
<ol className="list-decimal list-inside space-y-2">
@@ -83,6 +88,116 @@ function ButtonModal(props: {
</ol>
</Col>
</>
) : <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>}
{/*<BookmarkSearchContent*/}
{/* total={bookmarkedSearches.length}*/}
{/* compatibilityQuestion={bookmarkedSearches[questionIndex]}*/}
{/* user={user}*/}
{/* onSubmit={() => {*/}
{/* setOpen(false)*/}
{/* }}*/}
{/* isLastQuestion={questionIndex === bookmarkedSearches.length - 1}*/}
{/* onNext={() => {*/}
{/* if (questionIndex === bookmarkedSearches.length - 1) {*/}
{/* setOpen(false)*/}
{/* } else {*/}
{/* setQuestionIndex(questionIndex + 1)*/}
{/* }*/}
{/* }}*/}
{/*/>*/}
</Col>
</Modal>
)
}
export function BookmarkStarButton(props: {
starredUsers: DisplayUser[]
refreshStars: () => void
open: boolean
setOpen: (checked: boolean) => void
}) {
const {
starredUsers,
refreshStars,
open,
setOpen,
} = props
const user = useUser()
if (!user) return null
return (
<>
<Button onClick={() => setOpen(true)} color="gray-outline" size={'xs'}>
Saved Profiles
</Button>
<StarModal
open={open}
setOpen={setOpen}
user={user}
starredUsers={starredUsers}
refreshStars={refreshStars}
/>
</>
)
}
function StarModal(props: {
open: boolean
setOpen: (open: boolean) => void
user: User
starredUsers: DisplayUser[]
refreshStars: () => void
}) {
const {open, setOpen, starredUsers, refreshStars} = props
return (
<Modal
open={open}
setOpen={setOpen}
// onClose={() => {
// refreshBookmarkedSearches()
// }}
>
<Col className={MODAL_CLASS}>
<h3>Saved Profiles</h3>
{starredUsers?.length ? (<>
<p>Here are the profiles you saved:</p>
<Col
className={
'border-ink-300bg-canvas-0 inline-flex flex-col gap-2 rounded-md border p-1 shadow-sm'
}
>
<ol className="list-decimal list-inside space-y-2">
{(starredUsers || []).map((user) => (
<li key={user.id}
className="items-center justify-between gap-2 list-item marker:text-ink-500 marker:font-bold">
{user.name} (<Link
href={`https://${DOMAIN}/${user.username}`}
// style={{color: "#2563eb", textDecoration: "none"}}
>
@{user.username}
</Link>) {' '}
<button
onClick={async () => {
await api('star-profile', {
targetUserId: user.id,
remove: true,
})
refreshStars()
}}
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"
>
×
</button>
</li>
))}
</ol>
</Col>
</>
) : <p>You haven't saved any profile. To save one, click on the star on their profile page.</p>}
{/*<BookmarkSearchContent*/}
{/* total={bookmarkedSearches.length}*/}
{/* compatibilityQuestion={bookmarkedSearches[questionIndex]}*/}

View File

@@ -52,7 +52,7 @@ export const StarButton = (props: {
>
<StarIcon
className={clsx(
'h-10 w-10 transition-colors group-hover:fill-yellow-400/70',
'h-8 w-8 transition-colors group-hover:fill-yellow-400/70',
isStarred &&
'fill-yellow-400 stroke-yellow-500 dark:stroke-yellow-600'
)}
@@ -63,7 +63,7 @@ export const StarButton = (props: {
if (hideTooltip) return button
return (
<Tooltip text={isStarred ? 'Remove star' : 'Add star'} noTap>
<Tooltip text={isStarred ? 'Unsave Profile' : 'Save Profile'} noTap>
{button}
</Tooltip>
)

View File

@@ -1,5 +1,6 @@
import { run } from 'common/supabase/utils'
import { db } from 'web/lib/supabase/db'
import {DisplayUser} from "common/api/user-types";
export const getStars = async (creatorId: string) => {
const { data } = await run(
@@ -12,5 +13,13 @@ export const getStars = async (creatorId: string) => {
if (!data) return []
return data.map((d) => d.target_id as string)
const ids = data.map((d) => d.target_id as string)
const {data: users} = await run(
db
.from('users')
.select(`id, name, username`)
.in('id', ids)
)
return users as unknown as DisplayUser[]
}