Add politics filter

This commit is contained in:
MartinBraquet
2025-10-23 15:41:46 +02:00
parent 2ad87a5ec5
commit fdd96507b8
8 changed files with 189 additions and 59 deletions

View File

@@ -18,6 +18,7 @@ export type profileQueryType = {
pref_relation_styles?: String[] | undefined,
pref_romantic_styles?: String[] | undefined,
diet?: String[] | undefined,
political_beliefs?: String[] | undefined,
wants_kids_strength?: number | undefined,
has_kids?: number | undefined,
is_smoker?: boolean | undefined,
@@ -49,6 +50,7 @@ export const loadProfiles = async (props: profileQueryType) => {
pref_relation_styles,
pref_romantic_styles,
diet,
political_beliefs,
wants_kids_strength,
has_kids,
is_smoker,
@@ -89,6 +91,8 @@ export const loadProfiles = async (props: profileQueryType) => {
intersection(pref_romantic_styles, l.pref_romantic_styles).length) &&
(!diet ||
intersection(diet, l.diet).length) &&
(!political_beliefs ||
intersection(political_beliefs, l.political_beliefs).length) &&
(!wants_kids_strength ||
wants_kids_strength == -1 ||
!l.wants_kids_strength ||
@@ -172,6 +176,12 @@ export const loadProfiles = async (props: profileQueryType) => {
{diet}
),
political_beliefs?.length &&
where(
`political_beliefs IS NULL OR political_beliefs = '{}' OR political_beliefs && $(political_beliefs)`,
{political_beliefs}
),
!!wants_kids_strength &&
wants_kids_strength !== -1 &&
where(

View File

@@ -351,6 +351,7 @@ export const API = (_apiTypeCheck = {
pref_relation_styles: arraybeSchema.optional(),
pref_romantic_styles: arraybeSchema.optional(),
diet: arraybeSchema.optional(),
political_beliefs: arraybeSchema.optional(),
wants_kids_strength: z.coerce.number().optional(),
has_kids: z.coerce.number().optional(),
is_smoker: z.coerce.boolean().optional(),

View File

@@ -23,6 +23,7 @@ export type FilterFields = {
| 'pref_relation_styles'
| 'pref_romantic_styles'
| 'diet'
| 'political_beliefs'
| 'is_smoker'
| 'has_kids'
| 'pref_gender'
@@ -64,6 +65,7 @@ export const initialFilters: Partial<FilterFields> = {
pref_relation_styles: undefined,
pref_romantic_styles: undefined,
diet: undefined,
political_beliefs: undefined,
pref_gender: undefined,
shortBio: undefined,
orderBy: 'created_time',

View File

@@ -1,5 +1,5 @@
import {ChevronDownIcon, ChevronUpIcon} from '@heroicons/react/outline'
import {DietType, RelationshipType, RomanticType} from 'web/lib/util/convert-types'
import {DietType, PoliticalType, RelationshipType, RomanticType} from 'web/lib/util/convert-types'
import {ReactNode} from 'react'
import {FaUserGroup} from 'react-icons/fa6'
import {Col} from 'web/components/layout/col'
@@ -22,6 +22,7 @@ import {HasKidsLabel} from "web/components/filters/has-kids-filter";
import {RomanticFilter, RomanticFilterText} from "web/components/filters/romantic-filter";
import {FaHeart} from "react-icons/fa";
import {DietFilter, DietFilterText} from "web/components/filters/diet-filter";
import {PoliticalFilter, PoliticalFilterText} from "web/components/filters/political-filter";
export function DesktopFilters(props: {
filters: Partial<FilterFields>
@@ -318,6 +319,32 @@ export function DesktopFilters(props: {
menuWidth="w-50"
/>
{/* POLITICS */}
<CustomizeableDropdown
buttonContent={(open) => (
<DropdownButton
open={open}
content={
<Row className="items-center gap-1">
<PoliticalFilterText
options={
filters.political_beliefs as
| PoliticalType[]
| undefined
}
highlightedClass={open ? 'text-primary-500' : undefined}
/>
</Row>
}
/>
)}
dropdownMenuContent={
<PoliticalFilter filters={filters} updateFilter={updateFilter}/>
}
popoverClassName="bg-canvas-50"
menuWidth="w-50"
/>
{/* Short Bios */}
<ShortBioToggle
updateFilter={updateFilter}

View File

@@ -10,7 +10,7 @@ import {RelationshipFilter, RelationshipFilterText,} from './relationship-filter
import {MyMatchesToggle} from './my-matches-toggle'
import {Profile} from 'common/profiles/profile'
import {Gender} from 'common/gender'
import {DietType, RelationshipType, RomanticType} from 'web/lib/util/convert-types'
import {DietType, PoliticalType, RelationshipType, RomanticType} from 'web/lib/util/convert-types'
import {FilterFields} from "common/filters";
import {ShortBioToggle} from "web/components/filters/short-bio-toggle";
import {PrefGenderFilter, PrefGenderFilterText} from "./pref-gender-filter"
@@ -21,6 +21,7 @@ import {HasKidsFilter, HasKidsLabel} from "./has-kids-filter"
import {hasKidsLabels} from "common/has-kids";
import {RomanticFilter, RomanticFilterText} from "web/components/filters/romantic-filter";
import {DietFilter, DietFilterText} from "web/components/filters/diet-filter";
import {PoliticalFilter, PoliticalFilterText} from "web/components/filters/political-filter";
function MobileFilters(props: {
filters: Partial<FilterFields>
@@ -188,53 +189,53 @@ function MobileFilters(props: {
<RomanticFilter filters={filters} updateFilter={updateFilter}/>
</MobileFilterSection>
{/* WANTS KIDS */}
<MobileFilterSection
title="Wants kids"
openFilter={openFilter}
setOpenFilter={setOpenFilter}
isActive={
filters.wants_kids_strength != null &&
filters.wants_kids_strength !== -1
}
icon={<WantsKidsIcon strength={filters.wants_kids_strength ?? -1}/>}
selection={
<KidsLabel
strength={filters.wants_kids_strength ?? -1}
highlightedClass={
(filters.wants_kids_strength ?? -1) ==
wantsKidsLabels.no_preference.strength
? 'text-ink-900'
: 'text-primary-600'
}
mobile
/>
}
>
<WantsKidsFilter filters={filters} updateFilter={updateFilter}/>
</MobileFilterSection>
{/* WANTS KIDS */}
<MobileFilterSection
title="Wants kids"
openFilter={openFilter}
setOpenFilter={setOpenFilter}
isActive={
filters.wants_kids_strength != null &&
filters.wants_kids_strength !== -1
}
icon={<WantsKidsIcon strength={filters.wants_kids_strength ?? -1}/>}
selection={
<KidsLabel
strength={filters.wants_kids_strength ?? -1}
highlightedClass={
(filters.wants_kids_strength ?? -1) ==
wantsKidsLabels.no_preference.strength
? 'text-ink-900'
: 'text-primary-600'
}
mobile
/>
}
>
<WantsKidsFilter filters={filters} updateFilter={updateFilter}/>
</MobileFilterSection>
{/* HAS KIDS */}
<MobileFilterSection
title="Has kids"
openFilter={openFilter}
setOpenFilter={setOpenFilter}
isActive={filters.has_kids != null && filters.has_kids !== -1}
icon={<FaChild className="text-ink-900 h-4 w-4"/>}
selection={
<HasKidsLabel
has_kids={filters.has_kids ?? -1}
highlightedClass={
(filters.has_kids ?? -1) == hasKidsLabels.no_preference.value
? 'text-ink-900'
: 'text-primary-600'
}
mobile
/>
}
>
<HasKidsFilter filters={filters} updateFilter={updateFilter}/>
</MobileFilterSection>
{/* HAS KIDS */}
<MobileFilterSection
title="Has kids"
openFilter={openFilter}
setOpenFilter={setOpenFilter}
isActive={filters.has_kids != null && filters.has_kids !== -1}
icon={<FaChild className="text-ink-900 h-4 w-4"/>}
selection={
<HasKidsLabel
has_kids={filters.has_kids ?? -1}
highlightedClass={
(filters.has_kids ?? -1) == hasKidsLabels.no_preference.value
? 'text-ink-900'
: 'text-primary-600'
}
mobile
/>
}
>
<HasKidsFilter filters={filters} updateFilter={updateFilter}/>
</MobileFilterSection>
</>}
@@ -258,6 +259,26 @@ function MobileFilters(props: {
<DietFilter filters={filters} updateFilter={updateFilter}/>
</MobileFilterSection>
{/* POLITICS */}
<MobileFilterSection
title="Politics"
openFilter={openFilter}
setOpenFilter={setOpenFilter}
isActive={hasAny(filters.political_beliefs || undefined)}
selection={
<PoliticalFilterText
options={filters.political_beliefs as PoliticalType[]}
highlightedClass={
hasAny(filters.political_beliefs || undefined)
? 'text-primary-600'
: 'text-ink-900'
}
/>
}
>
<PoliticalFilter filters={filters} updateFilter={updateFilter}/>
</MobileFilterSection>
{/* Short Bios */}
<Col className="p-4 pb-2">
<ShortBioToggle

View File

@@ -0,0 +1,61 @@
import clsx from 'clsx'
import {convertPoliticalTypes, PoliticalType,} from 'web/lib/util/convert-types'
import stringOrStringArrayToText from 'web/lib/util/string-or-string-array-to-text'
import {MultiCheckbox} from 'web/components/multi-checkbox'
import {POLITICAL_CHOICES} from "web/components/filters/choices";
import {FilterFields} from "common/filters";
export function PoliticalFilterText(props: {
options: PoliticalType[] | undefined
highlightedClass?: string
}) {
const {options, highlightedClass} = props
const length = (options ?? []).length
if (!options || length < 1) {
return (
<span className={clsx('text-semibold', highlightedClass)}>Any politics</span>
)
}
const convertedTypes = options.map((r) =>
convertPoliticalTypes(r)
)
if (length > 1) {
return (
<span>
<span className={clsx('font-semibold', highlightedClass)}>
Multiple
</span>
</span>
)
}
return (
<div>
<span className={clsx('font-semibold', highlightedClass)}>
{stringOrStringArrayToText({
text: convertedTypes,
capitalizeFirstLetterOption: true,
})}{' '}
</span>
</div>
)
}
export function PoliticalFilter(props: {
filters: Partial<FilterFields>
updateFilter: (newState: Partial<FilterFields>) => void
}) {
const {filters, updateFilter} = props
return (
<MultiCheckbox
selected={filters.political_beliefs ?? []}
choices={POLITICAL_CHOICES as any}
onChange={(c) => {
updateFilter({political_beliefs: c})
}}
/>
)
}

View File

@@ -85,6 +85,7 @@ export const useFilters = (you: Profile | undefined) => {
pref_relation_styles: you?.pref_relation_styles.length ? you.pref_relation_styles : undefined,
pref_romantic_styles: you?.pref_romantic_styles?.length ? you.pref_romantic_styles : undefined,
diet: you?.diet?.length ? you.diet : undefined,
political_beliefs: you?.political_beliefs?.length ? you.political_beliefs : undefined,
wants_kids_strength: wantsKidsDatabaseToWantsKidsFilter(
(you?.wants_kids_strength ?? 2) as wantsKidsDatabase
),
@@ -95,16 +96,17 @@ export const useFilters = (you: Profile | undefined) => {
console.debug(you, yourFilters)
const isYourFilters =
!!you &&
(!location || location.id === you.geodb_city_id) &&
isEqual(filters.genders?.length ? filters.genders : undefined, yourFilters.genders?.length ? yourFilters.genders : undefined) &&
filters.pref_gender?.length == 1 && isEqual(filters.pref_gender?.length ? filters.pref_gender[0] : undefined, you.gender) &&
isEqual(new Set(filters.pref_romantic_styles), new Set(you.pref_romantic_styles)) &&
isEqual(new Set(filters.pref_relation_styles), new Set(you.pref_relation_styles)) &&
isEqual(new Set(filters.diet), new Set(you.diet)) &&
filters.pref_age_max == yourFilters.pref_age_max &&
filters.pref_age_min == yourFilters.pref_age_min &&
filters.wants_kids_strength == yourFilters.wants_kids_strength
!!you
&& (!location || location.id === you.geodb_city_id)
&& isEqual(filters.genders?.length ? filters.genders : undefined, yourFilters.genders?.length ? yourFilters.genders : undefined)
&& (!you.gender || filters.pref_gender?.length == 1 && isEqual(filters.pref_gender?.length ? filters.pref_gender[0] : undefined, you.gender))
&& isEqual(new Set(filters.pref_romantic_styles), new Set(you.pref_romantic_styles))
&& isEqual(new Set(filters.pref_relation_styles), new Set(you.pref_relation_styles))
&& isEqual(new Set(filters.diet), new Set(you.diet))
&& isEqual(new Set(filters.political_beliefs), new Set(you.political_beliefs))
&& filters.pref_age_max == yourFilters.pref_age_max
&& filters.pref_age_min == yourFilters.pref_age_min
&& filters.wants_kids_strength == yourFilters.wants_kids_strength
const setYourFilters = (checked: boolean) => {
if (checked) {

View File

@@ -1,12 +1,14 @@
import {
REVERTED_DIET_CHOICES,
REVERTED_RELATIONSHIP_CHOICES,
REVERTED_ROMANTIC_CHOICES
REVERTED_ROMANTIC_CHOICES,
REVERTED_POLITICAL_CHOICES
} from "web/components/filters/choices";
export type RelationshipType = keyof typeof REVERTED_RELATIONSHIP_CHOICES
export type RomanticType = keyof typeof REVERTED_ROMANTIC_CHOICES
export type DietType = keyof typeof REVERTED_DIET_CHOICES
export type PoliticalType = keyof typeof REVERTED_POLITICAL_CHOICES
export function convertRelationshipType(relationshipType: RelationshipType) {
return REVERTED_RELATIONSHIP_CHOICES[relationshipType]
@@ -19,3 +21,7 @@ export function convertRomanticTypes(romanticType: RomanticType) {
export function convertDietTypes(dietType: DietType) {
return REVERTED_DIET_CHOICES[dietType]
}
export function convertPoliticalTypes(politicalType: PoliticalType) {
return REVERTED_POLITICAL_CHOICES[politicalType]
}