Improve wants kids and has kids filters

This commit is contained in:
MartinBraquet
2026-02-27 22:42:34 +01:00
parent 6920b8293d
commit 74fc6a744e
12 changed files with 783 additions and 760 deletions

View File

@@ -340,7 +340,8 @@
"filter.label.pref_age_min": "Âge min",
"filter.label.pref_gender": "Genre recherché",
"filter.label.pref_relation_styles": "Recherche",
"filter.label.wants_kids_strength": "Enfants",
"filter.label.wants_kids_strength": "Désir d'enfants",
"filter.label.has_kids": "Enfants",
"filter.location": "Habite",
"filter.location.any": "n'importe où",
"filter.location.search_city": "Rechercher une ville...",
@@ -360,9 +361,9 @@
"filter.last_active.3months": "3 derniers mois",
"filter.reset": "Réinitialiser",
"filter.short_bio_toggle": "Inclure les profils incomplets",
"filter.wants_kids.any_preference": "Toutes préférences",
"filter.wants_kids.any_preference": "N'importe",
"filter.wants_kids.doesnt_want_kids": "Ne veut pas d'enfants",
"filter.wants_kids.either": "Tout désir d'enfants",
"filter.wants_kids.either": "N'importe",
"filter.wants_kids.wants_kids": "Veut des enfants",
"font.atkinson": "Atkinson Hyperlegible",
"font.classic-serif": "Serif classique",
@@ -679,12 +680,12 @@
"profile.gender.plural.male": "Hommes",
"profile.gender.plural.other": "Autres",
"profile.has_kids": "A des enfants",
"profile.has_kids.-1": "Tout enfant",
"profile.has_kids.-1": "N'importe",
"profile.has_kids.0": "N'a pas d'enfants",
"profile.has_kids.1": "A des enfants",
"profile.has_kids.doesnt_have_kids": "N'a pas d'enfants",
"profile.has_kids.has_kids": "A des enfants",
"profile.has_kids.no_preference": "Tout enfant",
"profile.has_kids.no_preference": "N'importe",
"profile.has_kids_many": "A {count} enfants",
"profile.has_kids_one": "A {count} enfant",
"profile.header.age": "{age} ans",

View File

@@ -10,7 +10,7 @@ export interface HasKidsLabelsMap {
export const hasKidsLabels: HasKidsLabelsMap = {
no_preference: {
name: 'Any kids',
name: 'Either way',
shortName: 'Either',
value: -1,
},

View File

@@ -31,3 +31,8 @@ export function groupConsecutive<T, U>(xs: T[], key: (x: T) => U) {
result.push(curr)
return result
}
export function nullifyEmpty<T>(array: T[]) {
if (!Array.isArray(array)) return null
return array.length > 0 ? array : null
}

View File

@@ -10,7 +10,7 @@ export type KidsLabelsMap = Record<string, KidLabel>
export const wantsKidsLabels: KidsLabelsMap = {
no_preference: {
name: 'Any preference',
name: 'Either',
shortName: 'Either',
strength: -1,
},

View File

@@ -11,7 +11,7 @@ import {buildArray} from 'common/util/array'
import {keyBy, partition, sortBy} from 'lodash'
import {useEffect, useState} from 'react'
import toast from 'react-hot-toast'
import DropdownMenu from 'web/components/comments/dropdown-menu'
import DropdownMenu, {DropdownButton} from 'web/components/comments/dropdown-menu'
import {Col} from 'web/components/layout/col'
import {Modal, MODAL_CLASS, SCROLLABLE_MODAL_CLASS} from 'web/components/layout/modal'
import {Row} from 'web/components/layout/row'
@@ -32,7 +32,6 @@ import {useUser} from 'web/hooks/use-user'
import {useT} from 'web/lib/locale'
import {db} from 'web/lib/supabase/db'
import {DropdownButton} from '../filters/desktop-filters'
import {Subtitle} from '../widgets/profile-subtitle'
import {AddCompatibilityQuestionButton} from './add-compatibility-question-button'
import {

View File

@@ -1,10 +1,11 @@
import {Popover, Transition} from '@headlessui/react'
import {DotsHorizontalIcon} from '@heroicons/react/solid'
import {ChevronDownIcon, ChevronUpIcon, DotsHorizontalIcon} from '@heroicons/react/solid'
import clsx from 'clsx'
import {toKey} from 'common/parsing'
import {Fragment, ReactNode, useState} from 'react'
import {usePopper} from 'react-popper'
import {Col} from 'web/components/layout/col'
import {Row} from 'web/components/layout/row'
import {useT} from 'web/lib/locale'
export type DropdownItem = {
@@ -165,3 +166,15 @@ export function DropdownOptions(props: {
</Col>
)
}
export function DropdownButton(props: {open: boolean; content: ReactNode}) {
const {open, content} = props
return (
<Row className="hover:text-ink-700 items-center gap-0.5 transition-all">
{content}
<span className="text-ink-400">
{open ? <ChevronUpIcon className="h-4 w-4" /> : <ChevronDownIcon className="h-4 w-4" />}
</span>
</Row>
)
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -51,12 +51,14 @@ function countActiveFilters(
locationFilterProps: LocationFilterProps,
raisedInLocationFilterProps: LocationFilterProps,
) {
let count = Object.keys(removeNullOrUndefinedProps({...filters, orderBy: undefined})).length
let parsedFilters = Object.keys(removeNullOrUndefinedProps({...filters, orderBy: undefined}))
parsedFilters = parsedFilters.filter((key) => !key.startsWith('big5_'))
let count = parsedFilters.length
if (locationFilterProps.location) count = count - 2
if (raisedInLocationFilterProps.location) count = count - 2
if (filters.pref_age_min && filters.pref_age_max) count--
const big5Count = countBig5Filters(filters)
if (big5Count > 1) count = count - (big5Count - 1)
if (big5Count > 0) count++
return count
}
@@ -344,26 +346,6 @@ function Filters(props: {
<RomanticFilter filters={filters} updateFilter={updateFilter} />
</FilterSection>
{/* Wants Kids */}
<FilterSection
title={t('filter.wants_kids.wants_kids', 'Wants kids')}
openFilter={openFilter}
setOpenFilter={setOpenFilter}
isActive={filters.wants_kids_strength != null && filters.wants_kids_strength !== -1}
selection={
<KidsLabel
strength={filters.wants_kids_strength ?? -1}
highlightedClass={
filters.wants_kids_strength != null && filters.wants_kids_strength !== -1
? 'text-primary-600'
: 'text-ink-900'
}
/>
}
>
<WantsKidsFilter filters={filters} updateFilter={updateFilter} />
</FilterSection>
{/* Has Kids */}
<FilterSection
title={t('profile.optional.has_kids', 'Has kids')}
@@ -383,6 +365,26 @@ function Filters(props: {
>
<HasKidsFilter filters={filters} updateFilter={updateFilter} />
</FilterSection>
{/* Wants Kids */}
<FilterSection
title={t('filter.wants_kids.wants_kids', 'Wants kids')}
openFilter={openFilter}
setOpenFilter={setOpenFilter}
isActive={filters.wants_kids_strength != null && filters.wants_kids_strength !== -1}
selection={
<KidsLabel
strength={filters.wants_kids_strength ?? -1}
highlightedClass={
filters.wants_kids_strength != null && filters.wants_kids_strength !== -1
? 'text-primary-600'
: 'text-ink-900'
}
/>
}
>
<WantsKidsFilter filters={filters} updateFilter={updateFilter} />
</FilterSection>
</FilterGroup>
)}
@@ -726,7 +728,7 @@ export function FilterSection(props: {
<Col className={clsx(className)}>
<button
className={clsx(
'text-ink-600 flex w-full flex-row justify-between px-4 pt-4 relative',
'text-ink-600 flex w-full flex-row justify-between px-4 pt-4 relative hover-bold',
isOpen ? 'pb-2' : 'pb-4',
)}
onClick={() => (isOpen ? setOpenFilter(undefined) : setOpenFilter(title))}

View File

@@ -1,19 +1,23 @@
import clsx from 'clsx'
import {FilterFields} from 'common/filters'
import {generateChoicesMap, hasKidsLabels} from 'common/has-kids'
import {FaChild} from 'react-icons/fa6'
import {invert} from 'lodash'
import {DropdownOptions} from 'web/components/comments/dropdown-menu'
import {Row} from 'web/components/layout/row'
import {ChoicesToggleGroup} from 'web/components/widgets/choices-toggle-group'
import {useT} from 'web/lib/locale'
const DEFAULT_KEY = -1
export function HasKidsLabel(props: {
has_kids: number
highlightedClass?: string
mobile?: boolean
}) {
const {has_kids, highlightedClass} = props
const {highlightedClass} = props
const t = useT()
const has_kids = Number(props.has_kids)
// Get the appropriate label based on has_kids value
let labelKey = 'no_preference'
let labelValue = hasKidsLabels.no_preference.name
@@ -27,8 +31,9 @@ export function HasKidsLabel(props: {
}
return (
<Row className="items-center gap-0.5">
<FaChild className="h-4 w-4" />
<span className={clsx(highlightedClass, has_kids !== -1 && 'font-semibold')}>
{/*<FaChild className="h-4 w-4" />*/}
<span className={clsx(highlightedClass, has_kids !== DEFAULT_KEY && 'font-semibold')}>
{has_kids === DEFAULT_KEY && t('filter.label.has_kids', 'Kids') + ': '}
{t(`profile.has_kids.${labelKey}`, labelValue)}
</span>
</Row>
@@ -41,12 +46,13 @@ export function HasKidsFilter(props: {
}) {
const {filters, updateFilter} = props
return (
<ChoicesToggleGroup
currentChoice={filters.has_kids ?? 0}
choicesMap={generateChoicesMap(hasKidsLabels)}
<DropdownOptions
items={invert(generateChoicesMap(hasKidsLabels))}
activeKey={String(filters.has_kids ?? DEFAULT_KEY)}
translationPrefix="profile.has_kids"
setChoice={(c) => updateFilter({has_kids: Number(c) >= 0 ? Number(c) : undefined})}
toggleClassName="w-1/3 justify-center"
onClick={(key) => {
updateFilter({has_kids: Number(key) === DEFAULT_KEY ? undefined : key})
}}
/>
)
}

View File

@@ -1,6 +1,7 @@
import clsx from 'clsx'
import {RELATIONSHIP_CHOICES} from 'common/choices'
import {FilterFields} from 'common/filters'
import {nullifyEmpty} from 'common/util/array'
import {MultiCheckbox} from 'web/components/multi-checkbox'
import {useT} from 'web/lib/locale'
import {convertRelationshipType, RelationshipType} from 'web/lib/util/convert-types'
@@ -59,7 +60,7 @@ export function RelationshipFilter(props: {
choices={RELATIONSHIP_CHOICES as any}
translationPrefix={'profile.relationship'}
onChange={(c) => {
updateFilter({pref_relation_styles: c})
updateFilter({pref_relation_styles: nullifyEmpty(c)})
}}
/>
)

View File

@@ -1,10 +1,11 @@
import clsx from 'clsx'
import {FilterFields} from 'common/filters'
import {generateChoicesMap, KidLabel, wantsKidsLabels} from 'common/wants-kids'
import {invert} from 'lodash'
import {ReactNode} from 'react'
import {MdNoStroller, MdOutlineStroller, MdStroller} from 'react-icons/md'
import {DropdownOptions} from 'web/components/comments/dropdown-menu'
import {Row} from 'web/components/layout/row'
import {ChoicesToggleGroup} from 'web/components/widgets/choices-toggle-group'
import {useT} from 'web/lib/locale'
interface KidLabelWithIcon extends KidLabel {
@@ -15,12 +16,14 @@ interface KidsLabelsMapWithIcon {
[key: string]: KidLabelWithIcon
}
const DEFAULT_KEY = -1
export const useWantsKidsLabelsWithIcon = () => {
const t = useT()
return {
no_preference: {
...wantsKidsLabels.no_preference,
name: t('filter.wants_kids.any_preference', 'Any preference'),
name: t('filter.wants_kids.any_preference', 'Either'),
shortName: t('filter.wants_kids.either', 'Either'),
icon: <MdOutlineStroller className="h-4 w-4" />,
},
@@ -55,18 +58,22 @@ export function WantsKidsIcon(props: {strength: number; className?: string}) {
}
export function KidsLabel(props: {strength: number; highlightedClass?: string; mobile?: boolean}) {
const {strength, highlightedClass} = props
const {highlightedClass} = props
const wantsKidsLabelsWithIcon = useWantsKidsLabelsWithIcon()
const t = useT()
const strength = props.strength ? Number(props.strength) : DEFAULT_KEY
return (
<Row className="items-center gap-0.5">
<WantsKidsIcon strength={strength} className={clsx('')} />
{/*<WantsKidsIcon strength={strength} className={clsx('')} />*/}
<span
className={clsx(
strength != wantsKidsLabelsWithIcon.no_preference.strength && 'font-semibold',
highlightedClass,
)}
>
{strength === DEFAULT_KEY && t('filter.label.wants_kids_strength', 'Wants Kids') + ': '}
{strength == wantsKidsLabelsWithIcon.no_preference.strength
? wantsKidsLabelsWithIcon.no_preference.name
: strength == wantsKidsLabelsWithIcon.wants_kids.strength
@@ -85,11 +92,13 @@ export function WantsKidsFilter(props: {
const wantsKidsLabelsWithIcon = useWantsKidsLabelsWithIcon()
return (
<ChoicesToggleGroup
currentChoice={filters.wants_kids_strength ?? 0}
choicesMap={generateChoicesMap(wantsKidsLabelsWithIcon)}
setChoice={(c) => updateFilter({wants_kids_strength: Number(c) >= 0 ? Number(c) : undefined})}
toggleClassName="w-1/3 justify-center"
<DropdownOptions
items={invert(generateChoicesMap(wantsKidsLabelsWithIcon))}
activeKey={String(filters.wants_kids_strength ?? DEFAULT_KEY)}
translationPrefix="profile.wants_kids"
onClick={(key) => {
updateFilter({wants_kids_strength: Number(key) === DEFAULT_KEY ? undefined : key})
}}
/>
)
}

View File

@@ -1,5 +1,6 @@
import clsx from 'clsx'
import {toKey} from 'common/parsing'
import {nullifyEmpty} from 'common/util/array'
import {useEffect, useMemo, useState} from 'react'
import {Button} from 'web/components/buttons/button'
import {Row} from 'web/components/layout/row'
@@ -12,7 +13,7 @@ export const MultiCheckbox = (props: {
choices: {[key: string]: string}
// Selected values (should match the "value" side of choices)
selected: string[]
onChange: (selected: string[]) => void
onChange: (selected: string[] | null) => void
className?: string
optionsClassName?: string
// If provided, enables adding a new option and should persist it (e.g. to DB)
@@ -146,7 +147,7 @@ export const MultiCheckbox = (props: {
if (checked) {
onChange([...selected, value])
} else {
onChange(selected.filter((s) => s !== value))
onChange(nullifyEmpty(selected.filter((s) => s !== value)))
}
}}
/>