Refactor measurement system imports and enhance filter formatting

This commit is contained in:
MartinBraquet
2026-02-19 01:30:59 +01:00
parent cb98314bec
commit cf843f66c4
6 changed files with 155 additions and 116 deletions

View File

@@ -1,9 +1,9 @@
import {MeasurementSystem} from 'web/hooks/use-measurement-system'
// Conversion factors
const INCHES_TO_CM = 2.54
const MILES_TO_KM = 1.60934
export type MeasurementSystem = 'metric' | 'imperial'
/**
* Format height in inches according to the specified measurement system
*/

View File

@@ -1,33 +1,34 @@
// Define nice labels for each key
import {FilterFields, initialFilters} from "common/filters";
import {wantsKidsNames} from "common/wants-kids";
import {hasKidsNames} from "common/has-kids";
import {FilterFields, initialFilters} from 'common/filters'
import {wantsKidsNames} from 'common/wants-kids'
import {hasKidsNames} from 'common/has-kids'
import {milesToKm} from "common/measurement-utils";
const filterLabels: Record<string, string> = {
geodbCityIds: "",
location: "",
name: "Searching",
genders: "",
education_levels: "Education",
pref_age_max: "Max age",
pref_age_min: "Min age",
drinks_max: "Max drinks",
drinks_min: "Min drinks",
relationship_status: "",
has_kids: "",
wants_kids_strength: "Kids",
is_smoker: "",
pref_relation_styles: "Seeking",
interests: "",
causes: "",
work: "",
religion: "",
pref_gender: "",
orderBy: "",
diet: "Diet",
political_beliefs: "Political views",
languages: "",
mbti: "MBTI",
geodbCityIds: '',
location: '',
name: 'Searching',
genders: '',
education_levels: 'Education',
pref_age_max: 'Max age',
pref_age_min: 'Min age',
drinks_max: 'Max drinks',
drinks_min: 'Min drinks',
relationship_status: '',
has_kids: '',
wants_kids_strength: 'Kids',
is_smoker: '',
pref_relation_styles: 'Seeking',
interests: '',
causes: '',
work: '',
religion: '',
pref_gender: '',
orderBy: '',
diet: 'Diet',
political_beliefs: 'Political views',
languages: '',
mbti: 'MBTI',
}
export type locationType = {
@@ -49,18 +50,18 @@ const skippedKeys = [
'radius',
]
export function formatFilters(
filters: Partial<FilterFields>,
location: locationType | null,
choicesIdsToLabels: Record<string, any>
choicesIdsToLabels: Record<string, any>,
measurementSystem?: 'metric' | 'imperial'
): String[] | null {
const entries: String[] = []
let ageEntry = null
let ageMin: number | undefined | null = filters.pref_age_min
if (ageMin == 18) ageMin = undefined
let ageMax = filters.pref_age_max;
let ageMax = filters.pref_age_max
if (ageMax == 100) ageMax = undefined
if (ageMin || ageMax) {
let text: string = 'Age: '
@@ -89,7 +90,8 @@ export function formatFilters(
let stringValue = value
if (key === 'has_kids') stringValue = hasKidsNames[value as number]
if (key === 'wants_kids_strength') stringValue = wantsKidsNames[value as number]
if (key === 'wants_kids_strength')
stringValue = wantsKidsNames[value as number]
if (Array.isArray(value)) {
if (choicesIdsToLabels[key]) {
value = value.map((id) => choicesIdsToLabels[key][id])
@@ -110,11 +112,18 @@ export function formatFilters(
if (ageEntry) entries.push(ageEntry)
if (location?.location?.name) {
const locString = `${location?.location?.name} (${location?.radius}mi)`
const radius = location?.radius || 0
let formattedRadius: string
if (measurementSystem === 'metric') {
formattedRadius = `${Math.round(milesToKm(radius))} km`
} else {
formattedRadius = `${Math.round(radius)}mi`
}
const locString = `${location?.location?.name} (${formattedRadius})`
entries.push(locString)
}
if (entries.length === 0) return ['Anyone']
return entries
}
}

View File

@@ -13,7 +13,7 @@ import {uniqBy} from 'lodash'
import {buildArray} from 'common/util/array'
import {OriginLocation} from 'common/filters'
import {useMeasurementSystem} from 'web/hooks/use-measurement-system'
import {formatDistance, kmToMiles, milesToKm} from 'web/lib/measurement-utils'
import {formatDistance, kmToMiles, milesToKm} from 'common/measurement-utils'
export function LocationFilterText(props: {
location: OriginLocation | undefined | null

View File

@@ -28,8 +28,7 @@ import {UserHandles} from 'web/components/user/user-handles'
import {Profile} from 'common/profiles/profile'
import {UserActivity} from 'common/user'
import {ClockIcon} from '@heroicons/react/solid'
import {MeasurementSystem} from 'web/hooks/use-measurement-system'
import {formatHeight} from 'web/lib/measurement-utils'
import {formatHeight, MeasurementSystem} from 'common/measurement-utils'
import {MAX_INT, MIN_INT} from 'common/constants'
import {GiFruitBowl} from 'react-icons/gi'
import {FaBriefcase, FaHandsHelping, FaHeart, FaStar} from 'react-icons/fa'

View File

@@ -1,23 +1,24 @@
import {User} from "common/user";
import {Button} from "web/components/buttons/button";
import {Modal, MODAL_CLASS, SCROLLABLE_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 {deleteBookmarkedSearch} from "web/lib/supabase/searches";
import {formatFilters, locationType} from "common/searches";
import {FilterFields} from "common/filters";
import {api} from "web/lib/api";
import {XIcon} from "@heroicons/react/outline";
import {DisplayUser} from "common/api/user-types";
import {useState} from "react";
import {useT} from "web/lib/locale";
import toast from "react-hot-toast";
import Link from "next/link";
import {useAllChoices} from "web/hooks/use-choices";
import clsx from "clsx";
import {Row} from "web/components/layout/row";
import {Avatar} from "web/components/widgets/avatar";
import {User} from 'common/user'
import {Button} from 'web/components/buttons/button'
import {Modal, MODAL_CLASS, SCROLLABLE_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 {deleteBookmarkedSearch} from 'web/lib/supabase/searches'
import {formatFilters, locationType} from 'common/searches'
import {FilterFields} from 'common/filters'
import {api} from 'web/lib/api'
import {XIcon} from '@heroicons/react/outline'
import {DisplayUser} from 'common/api/user-types'
import {useState} from 'react'
import {useT} from 'web/lib/locale'
import toast from 'react-hot-toast'
import Link from 'next/link'
import {useAllChoices} from 'web/hooks/use-choices'
import clsx from 'clsx'
import {Row} from 'web/components/layout/row'
import {Avatar} from 'web/components/widgets/avatar'
import {useMeasurementSystem} from 'web/hooks/use-measurement-system'
export function BookmarkSearchButton(props: {
bookmarkedSearches: BookmarkedSearchesType[]
@@ -25,12 +26,7 @@ export function BookmarkSearchButton(props: {
open: boolean
setOpen: (checked: boolean) => void
}) {
const {
bookmarkedSearches,
refreshBookmarkedSearches,
open,
setOpen,
} = props
const {bookmarkedSearches, refreshBookmarkedSearches, open, setOpen} = props
const user = useUser()
const t = useT()
@@ -51,9 +47,7 @@ export function BookmarkSearchButton(props: {
)
}
export function ResetFiltersButton(props: {
clearFilters: () => void
}) {
export function ResetFiltersButton(props: { clearFilters: () => void }) {
const {clearFilters} = props
const t = useT()
return (
@@ -65,7 +59,6 @@ export function ResetFiltersButton(props: {
)
}
function ButtonModal(props: {
open: boolean
setOpen: (open: boolean) => void
@@ -76,6 +69,7 @@ function ButtonModal(props: {
const {open, setOpen, bookmarkedSearches, refreshBookmarkedSearches} = props
const t = useT()
const choicesIdsToLabels = useAllChoices()
const {measurementSystem} = useMeasurementSystem()
return (
<Modal
open={open}
@@ -86,35 +80,53 @@ function ButtonModal(props: {
>
<Col className={MODAL_CLASS}>
<h3>{t('saved_searches.title', 'Saved Searches')}</h3>
{bookmarkedSearches?.length ? (<>
<p>{t('saved_searches.notification_note', "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 shadow-sm'
}
>
<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>, search.location as locationType, choicesIdsToLabels)?.join(" • ")}
<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>
</Col>
</>
) :
<p>{t('saved_searches.empty_state', "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>}
{bookmarkedSearches?.length ? (
<>
<p>
{t(
'saved_searches.notification_note',
"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 shadow-sm'
}
>
<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>,
search.location as locationType,
choicesIdsToLabels,
measurementSystem
)?.join(' • ')}
<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>
</Col>
</>
) : (
<p>
{t(
'saved_searches.empty_state',
"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]}*/}
@@ -142,12 +154,7 @@ export function BookmarkStarButton(props: {
open: boolean
setOpen: (checked: boolean) => void
}) {
const {
starredUsers,
refreshStars,
open,
setOpen,
} = props
const {starredUsers, refreshStars, open, setOpen} = props
const user = useUser()
const t = useT()
@@ -168,7 +175,6 @@ export function BookmarkStarButton(props: {
)
}
function StarModal(props: {
open: boolean
setOpen: (open: boolean) => void
@@ -181,7 +187,9 @@ function StarModal(props: {
const [removingIds, setRemovingIds] = useState<Set<string>>(new Set())
const t = useT()
const visibleUsers = (starredUsers || []).filter((u) => !removingIds.has(u.id))
const visibleUsers = (starredUsers || []).filter(
(u) => !removingIds.has(u.id)
)
return (
<Modal
@@ -193,20 +201,37 @@ function StarModal(props: {
>
<Col className={MODAL_CLASS}>
<h3>{t('saved_people.title', 'Saved People')}</h3>
{visibleUsers?.length ? (<>
<p>{t('saved_people.list_header', 'Here are the people you saved:')}</p>
<Col className={clsx("divide-y divide-canvas-300 w-full pr-4", SCROLLABLE_MODAL_CLASS)}>
{visibleUsers?.length ? (
<>
<p>
{t('saved_people.list_header', 'Here are the people you saved:')}
</p>
<Col
className={clsx(
'divide-y divide-canvas-300 w-full pr-4',
SCROLLABLE_MODAL_CLASS
)}
>
{visibleUsers.map((u) => (
<Row key={u.id} className="items-center justify-between py-2 gap-2">
<Row
key={u.id}
className="items-center justify-between py-2 gap-2"
>
<Link
className="w-full rounded-md hover:bg-canvas-100 p-2"
href={'/' + u.username}
>
<Row className="items-center gap-3">
<Avatar size="md" username={u.username} avatarUrl={u.avatarUrl ?? undefined}/>
<Avatar
size="md"
username={u.username}
avatarUrl={u.avatarUrl ?? undefined}
/>
<Col>
<div className="font-medium">{u.name}</div>
<div className="text-ink-500 text-sm">@{u.username}</div>
<div className="text-ink-500 text-sm">
@{u.username}
</div>
</Col>
</Row>
</Link>
@@ -224,7 +249,9 @@ function StarModal(props: {
refreshStars()
})
.catch(() => {
toast.error("Couldn't remove saved profile. Please try again.")
toast.error(
"Couldn't remove saved profile. Please try again."
)
// Revert optimistic removal on failure
setRemovingIds((prev) => {
const next = new Set(prev)
@@ -241,7 +268,12 @@ function StarModal(props: {
))}
</Col>
</>
) : <p>You haven't saved any profile. To save one, click on the star on their profile page.</p>}
) : (
<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]}*/}
@@ -261,4 +293,4 @@ function StarModal(props: {
</Col>
</Modal>
)
}
}

View File

@@ -1,7 +1,6 @@
import {usePersistentLocalState} from 'web/hooks/use-persistent-local-state'
import {getLocale} from "web/lib/locale-cookie";
export type MeasurementSystem = 'metric' | 'imperial'
import {MeasurementSystem} from "common/measurement-utils";
export const useMeasurementSystem = () => {
// Get default based on locale