Pretty print bookmarked searches

This commit is contained in:
MartinBraquet
2025-09-14 22:18:21 +02:00
parent 2ea4eae9d6
commit 99adb77fcb
3 changed files with 144 additions and 46 deletions

View File

@@ -16,6 +16,7 @@ import {submitBookmarkedSearch} from "web/lib/supabase/searches";
import {useUser} from "web/hooks/use-user";
import {isEqual} from "lodash";
import {initialFilters} from "web/components/filters/use-filters";
import toast from "react-hot-toast";
export type FilterFields = {
orderBy: 'last_online_time' | 'created_time' | 'compatibility_score'
@@ -195,8 +196,13 @@ export const Search = (props: {
}
loading={loadingBookmark}
onClick={() => {
if (bookmarkedSearches.length >= 10) {
toast.error('You cannot bookmark more searches; please delete one first.')
setOpenBookmarks(true)
return
}
setLoadingBookmark(true)
submitBookmarkedSearch(filters, user?.id)
submitBookmarkedSearch(filters, locationFilterProps, user?.id)
.finally(() => {
setLoadingBookmark(false)
setBookmarked(true)
@@ -214,7 +220,8 @@ export const Search = (props: {
<BookmarkSearchButton
refreshBookmarkedSearches={refreshBookmarkedSearches}
bookmarkedSearches={bookmarkedSearches}
openBookmarks={openBookmarks}
open={openBookmarks}
setOpen={setOpenBookmarks}
/>
</Row>
</Col>

View File

@@ -1,32 +1,30 @@
import {User} from "common/user";
import {useEffect, useState} from "react";
import {ReactElement} from "react";
import {Button} from "web/components/buttons/button";
import {Modal, MODAL_CLASS} from "web/components/layout/modal";
import {Col} from "web/components/layout/col";
import {BookmarkedSearchesType} from "web/hooks/use-bookmarked-searches";
import {useUser} from "web/hooks/use-user";
import {initialFilters} from "web/components/filters/use-filters";
import {Row} from "web/components/layout/row";
import {deleteBookmarkedSearch} from "web/lib/supabase/searches";
import {FilterFields} from "web/components/filters/search";
import {hasKidsNames} from "web/components/filters/has-kids-filter";
import {wantsKidsNames} from "web/components/filters/wants-kids-filter";
export function BookmarkSearchButton(props: {
bookmarkedSearches: BookmarkedSearchesType[]
refreshBookmarkedSearches: () => void
openBookmarks?: boolean
open: boolean
setOpen: (checked: boolean) => void
}) {
const {
bookmarkedSearches,
refreshBookmarkedSearches,
openBookmarks,
open,
setOpen,
} = props
const [open, setOpen] = useState(false)
const user = useUser()
useEffect(() => {
if (openBookmarks) setOpen(true)
}, [openBookmarks]);
if (!user) return null
return (
<>
@@ -44,6 +42,109 @@ export function BookmarkSearchButton(props: {
)
}
// Define nice labels for each key
const filterLabels: Record<string, string> = {
geodbCityIds: "",
location: "",
name: "Searching",
genders: "",
pref_age_max: "Max age",
pref_age_min: "Min age",
has_kids: "",
wants_kids_strength: "",
is_smoker: "",
pref_relation_styles: "Seeking",
pref_gender: "",
orderBy: "",
}
export type locationType = {
location: {
name: string
}
radius: number
}
export type FilterFieldsWithLocation = FilterFields & {
location: locationType
}
function formatFilters(filters: Partial<FilterFieldsWithLocation>): ReactElement | null {
const entries: ReactElement[] = []
let ageEntry = null
let ageMin: number | undefined = filters.pref_age_min
if (ageMin == 18) ageMin = undefined
let ageMax = filters.pref_age_max;
if (ageMax == 99) ageMax = undefined
if (ageMin || ageMax) {
let text: string = 'Age: '
if (ageMin) text = `${text}${ageMin}`
if (ageMax) {
if (ageMin) {
text = `${text}-${ageMax}`
} else {
text = `${text}up to ${ageMax}`
}
} else {
text = `${text}+`
}
ageEntry = <span>{text}</span>
}
Object.entries(filters).forEach(([key, value]) => {
const typedKey = key as keyof FilterFields
if (!value) return
if (typedKey == 'pref_age_min' || typedKey == 'pref_age_max' || typedKey == 'geodbCityIds') return
if (Array.isArray(value) && value.length === 0) return
if (initialFilters[typedKey] === value) return
const label = filterLabels[typedKey] ?? key
let stringValue = value
if (key === 'has_kids') stringValue = hasKidsNames[value as number]
if (key === 'wants_kids_strength') stringValue = wantsKidsNames[value as number]
if (key === 'location') stringValue = `${(value as locationType)?.location?.name} (${(value as locationType)?.radius}mi)`
if (Array.isArray(value)) stringValue = value.join(', ')
if (!label) {
const str = String(stringValue)
stringValue = str.charAt(0).toUpperCase() + str.slice(1)
}
let display: ReactElement
display = key === 'name'
? <i>{stringValue as string}</i>
: <>{stringValue}</>
entries.push(
<span key={key}>
{label}
{label ? ': ' : ''}
{display}
</span>
)
})
if (ageEntry) entries.push(ageEntry)
if (entries.length === 0) return null
// Join with " • " separators
return (
<span>
{entries.map((entry, i) => (
<span key={i}>
{entry}
{i < entries.length - 1 ? ' • ' : ''}
</span>
))}
</span>
)
}
function ButtonModal(props: {
open: boolean
setOpen: (open: boolean) => void
@@ -61,44 +162,31 @@ function ButtonModal(props: {
}}
>
<Col className={MODAL_CLASS}>
<div>Bookmarked Searches</div>
<p className='text-xs'>We'll notify you daily when new people match your searches below.</p>
<h3>Bookmarked Searches</h3>
<p className='text-sm'>We'll notify you daily when new people match your searches below.</p>
<Col
className={
'border-ink-300 text-ink-400 bg-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 text-sm shadow-sm'
}
>
{(bookmarkedSearches || []).map((search) => (
<Row key={search.id}>
<p>
{JSON.stringify(
Object.fromEntries(
Object.entries(search.search_filters as Record<string, any>).filter(([key, value]) => {
// skip null/undefined
if (value == null) return false
<ol className="list-decimal list-inside space-y-2">
{(bookmarkedSearches || []).map((search) => (
<li key={search.id}
className="items-center justify-between gap-2 list-item marker:text-ink-500 marker:font-bold">
{formatFilters(search.search_filters as Partial<FilterFields>)}
<button
onClick={async () => {
await deleteBookmarkedSearch(search.id)
refreshBookmarkedSearches()
}}
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>
// skip empty arrays
if (Array.isArray(value) && value.length === 0) return false
// keep if different from initialFilters
return initialFilters[key as keyof FilterFields] !== value
})
)
)}
</p>
<button
onClick={async () => {
await deleteBookmarkedSearch(search.id)
refreshBookmarkedSearches()
}}
className="inline-flex h-4 w-4 items-center justify-center rounded-full text-red-600 hover:bg-red-200 focus:outline-none focus:ring-2 focus:ring-red-400"
>
×
</button>
</Row>
))}
</Col>
{/*<BookmarkSearchContent*/}
{/* total={bookmarkedSearches.length}*/}

View File

@@ -23,11 +23,14 @@ export type BookmarkedSearchSubmitType = Omit<
export const submitBookmarkedSearch = async (
filters: Partial<FilterFields>,
userId: string | undefined | null
locationFilterProps: any,
userId: string | undefined | null,
) => {
if (!filters) return
if (!userId) return
const row = {search_filters: filters, creator_id: userId}
const fullFilter = {...filters, ...{location: locationFilterProps}}
const row = {search_filters: fullFilter, creator_id: userId}
const input = {
...filterKeys(row, (key, _) => !['id', 'created_time', 'last_notified_at'].includes(key)),
} as BookmarkedSearchSubmitType