This commit is contained in:
MartinBraquet
2025-10-23 14:58:57 +02:00
parent 725261335c
commit b94cdba5af
18 changed files with 185 additions and 22 deletions

View File

@@ -17,6 +17,7 @@ export type profileQueryType = {
pref_age_max?: number | undefined,
pref_relation_styles?: String[] | undefined,
pref_romantic_styles?: String[] | undefined,
diet?: String[] | undefined,
wants_kids_strength?: number | undefined,
has_kids?: number | undefined,
is_smoker?: boolean | undefined,
@@ -47,6 +48,7 @@ export const loadProfiles = async (props: profileQueryType) => {
pref_age_max,
pref_relation_styles,
pref_romantic_styles,
diet,
wants_kids_strength,
has_kids,
is_smoker,
@@ -85,6 +87,8 @@ export const loadProfiles = async (props: profileQueryType) => {
intersection(pref_relation_styles, l.pref_relation_styles).length) &&
(!pref_romantic_styles ||
intersection(pref_romantic_styles, l.pref_romantic_styles).length) &&
(!diet ||
intersection(diet, l.diet).length) &&
(!wants_kids_strength ||
wants_kids_strength == -1 ||
!l.wants_kids_strength ||
@@ -162,6 +166,12 @@ export const loadProfiles = async (props: profileQueryType) => {
{pref_romantic_styles}
),
diet?.length &&
where(
`diet IS NULL OR diet = '{}' OR diet && $(diet)`,
{diet}
),
!!wants_kids_strength &&
wants_kids_strength !== -1 &&
where(

View File

@@ -46,7 +46,7 @@ export const sinclairProfile: ProfileRow = {
has_kids: 0,
is_smoker: false,
drinks_per_month: 0,
is_vegetarian_or_vegan: null,
diet: null,
political_beliefs: ['e/acc', 'libertarian'],
religious_belief_strength: null,
religious_beliefs: null,
@@ -147,7 +147,7 @@ export const jamesProfile: ProfileRow = {
has_kids: 0,
is_smoker: false,
drinks_per_month: 5,
is_vegetarian_or_vegan: null,
diet: null,
political_beliefs: ['libertarian'],
religious_belief_strength: null,
religious_beliefs: '',

View File

@@ -27,7 +27,7 @@ CREATE TABLE IF NOT EXISTS profiles (
height_in_inches INTEGER,
id BIGINT GENERATED ALWAYS AS IDENTITY NOT NULL,
is_smoker BOOLEAN,
is_vegetarian_or_vegan BOOLEAN,
diet TEXT[],
last_modification_time TIMESTAMPTZ DEFAULT now() NOT NULL,
looking_for_matches BOOLEAN DEFAULT TRUE NOT NULL,
messaging_status TEXT DEFAULT 'open'::TEXT NOT NULL,

View File

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

View File

@@ -80,7 +80,7 @@ const optionalProfilesSchema = z.object({
education_level: z.string().optional(),
is_smoker: z.boolean().optional(),
drinks_per_month: z.number().min(0).optional(),
is_vegetarian_or_vegan: z.boolean().optional(),
diet: z.array(z.string()).optional(),
has_kids: z.number().min(0).optional(),
university: z.string().optional(),
occupation_title: z.string().optional(),

View File

@@ -22,6 +22,7 @@ export type FilterFields = {
| 'wants_kids_strength'
| 'pref_relation_styles'
| 'pref_romantic_styles'
| 'diet'
| 'is_smoker'
| 'has_kids'
| 'pref_gender'
@@ -62,6 +63,7 @@ export const initialFilters: Partial<FilterFields> = {
is_smoker: undefined,
pref_relation_styles: undefined,
pref_romantic_styles: undefined,
diet: undefined,
pref_gender: undefined,
shortBio: undefined,
orderBy: 'created_time',

View File

@@ -454,7 +454,7 @@ export type Database = {
height_in_inches: number | null
id: number
is_smoker: boolean | null
is_vegetarian_or_vegan: boolean | null
diet: string[] | null
last_modification_time: string
looking_for_matches: boolean
messaging_status: string
@@ -502,7 +502,7 @@ export type Database = {
height_in_inches?: number | null
id?: number
is_smoker?: boolean | null
is_vegetarian_or_vegan?: boolean | null
diet?: string[] | null
last_modification_time?: string
looking_for_matches?: boolean
messaging_status?: string
@@ -550,7 +550,7 @@ export type Database = {
height_in_inches?: number | null
id?: number
is_smoker?: boolean | null
is_vegetarian_or_vegan?: boolean | null
diet?: string[] | null
last_modification_time?: string
looking_for_matches?: boolean
messaging_status?: string

View File

@@ -27,6 +27,16 @@ export const POLITICAL_CHOICES = {
'Independent / Other': 'other',
}
export const DIET_CHOICES = {
Omnivore: 'omnivore',
Vegetarian: 'veg',
Vegan: 'vegan',
Keto: 'keto',
Paleo: 'paleo',
Pescetarian: 'pescetarian',
Other: 'other',
}
export const REVERTED_RELATIONSHIP_CHOICES = Object.fromEntries(
Object.entries(RELATIONSHIP_CHOICES).map(([key, value]) => [value, key])
);
@@ -37,4 +47,8 @@ export const REVERTED_ROMANTIC_CHOICES = Object.fromEntries(
export const REVERTED_POLITICAL_CHOICES = Object.fromEntries(
Object.entries(POLITICAL_CHOICES).map(([key, value]) => [value, key])
);
export const REVERTED_DIET_CHOICES = Object.fromEntries(
Object.entries(DIET_CHOICES).map(([key, value]) => [value, key])
);

View File

@@ -1,5 +1,5 @@
import {ChevronDownIcon, ChevronUpIcon} from '@heroicons/react/outline'
import {RelationshipType, RomanticType} from 'web/lib/util/convert-relationship-type'
import {DietType, 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'
@@ -21,6 +21,7 @@ import {hasKidsLabels} from "common/has-kids";
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";
export function DesktopFilters(props: {
filters: Partial<FilterFields>
@@ -171,7 +172,7 @@ export function DesktopFilters(props: {
{includeRelationshipFilters && <>
{/* CONNECTION */}
{/* RELATIONSHIP STYLE */}
<CustomizeableDropdown
buttonContent={(open) => (
<DropdownButton
@@ -291,6 +292,32 @@ export function DesktopFilters(props: {
</>
}
{/* DIET */}
<CustomizeableDropdown
buttonContent={(open) => (
<DropdownButton
open={open}
content={
<Row className="items-center gap-1">
<DietFilterText
options={
filters.diet as
| DietType[]
| undefined
}
highlightedClass={open ? 'text-primary-500' : undefined}
/>
</Row>
}
/>
)}
dropdownMenuContent={
<DietFilter filters={filters} updateFilter={updateFilter}/>
}
popoverClassName="bg-canvas-50"
menuWidth="w-50"
/>
{/* Short Bios */}
<ShortBioToggle
updateFilter={updateFilter}

View File

@@ -0,0 +1,61 @@
import clsx from 'clsx'
import {convertDietTypes, DietType,} 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 {DIET_CHOICES} from "web/components/filters/choices";
import {FilterFields} from "common/filters";
export function DietFilterText(props: {
options: DietType[] | undefined
highlightedClass?: string
}) {
const {options, highlightedClass} = props
const length = (options ?? []).length
if (!options || length < 1) {
return (
<span className={clsx('text-semibold', highlightedClass)}>Any diet</span>
)
}
const convertedTypes = options.map((r) =>
convertDietTypes(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 DietFilter(props: {
filters: Partial<FilterFields>
updateFilter: (newState: Partial<FilterFields>) => void
}) {
const {filters, updateFilter} = props
return (
<MultiCheckbox
selected={filters.diet ?? []}
choices={DIET_CHOICES as any}
onChange={(c) => {
updateFilter({diet: c})
}}
/>
)
}

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 {RelationshipType, RomanticType} from 'web/lib/util/convert-relationship-type'
import {DietType, 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"
@@ -20,6 +20,7 @@ import {FaChild} from "react-icons/fa"
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";
function MobileFilters(props: {
filters: Partial<FilterFields>
@@ -237,6 +238,26 @@ function MobileFilters(props: {
</>}
{/* DIET */}
<MobileFilterSection
title="Diet"
openFilter={openFilter}
setOpenFilter={setOpenFilter}
isActive={hasAny(filters.diet || undefined)}
selection={
<DietFilterText
options={filters.diet as DietType[]}
highlightedClass={
hasAny(filters.diet || undefined)
? 'text-primary-600'
: 'text-ink-900'
}
/>
}
>
<DietFilter filters={filters} updateFilter={updateFilter}/>
</MobileFilterSection>
{/* Short Bios */}
<Col className="p-4 pb-2">
<ShortBioToggle

View File

@@ -1,5 +1,5 @@
import clsx from 'clsx'
import {convertRelationshipType, RelationshipType,} from 'web/lib/util/convert-relationship-type'
import {convertRelationshipType, RelationshipType,} 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'

View File

@@ -1,5 +1,5 @@
import clsx from 'clsx'
import {convertRomanticTypes, RomanticType,} from 'web/lib/util/convert-relationship-type'
import {convertRomanticTypes, RomanticType,} 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'

View File

@@ -84,6 +84,7 @@ export const useFilters = (you: Profile | undefined) => {
pref_age_min: (you?.pref_age_min ?? MIN_INT) > 18 ? you?.pref_age_min : 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,
wants_kids_strength: wantsKidsDatabaseToWantsKidsFilter(
(you?.wants_kids_strength ?? 2) as wantsKidsDatabase
),
@@ -100,6 +101,7 @@ export const useFilters = (you: Profile | 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

View File

@@ -28,7 +28,7 @@ import {City, CityRow, profileToCity, useCitySearch} from "web/components/search
import {AddPhotosWidget} from './widgets/add-photos'
import {RadioToggleGroup} from "web/components/widgets/radio-toggle-group";
import {MultipleChoiceOptions} from "common/profiles/multiple-choice";
import {POLITICAL_CHOICES, RELATIONSHIP_CHOICES, ROMANTIC_CHOICES} from "web/components/filters/choices";
import {DIET_CHOICES, POLITICAL_CHOICES, RELATIONSHIP_CHOICES, ROMANTIC_CHOICES} from "web/components/filters/choices";
import toast from "react-hot-toast";
export const OptionalProfileUserForm = (props: {
@@ -409,6 +409,15 @@ export const OptionalProfileUserForm = (props: {
/>
</Col>
<Col className={clsx(colClassName)}>
<label className={clsx(labelClassName)}>Diet</label>
<MultiCheckbox
choices={DIET_CHOICES}
selected={profile['diet'] ?? []}
onChange={(selected) => setProfile('diet', selected)}
/>
</Col>
<Col className={clsx(colClassName)}>
<label className={clsx(labelClassName)}>Do you smoke?</label>
<ChoicesToggleGroup

View File

@@ -1,14 +1,18 @@
import clsx from 'clsx'
import {convertRelationshipType, type RelationshipType,} from 'web/lib/util/convert-relationship-type'
import {convertRelationshipType, type RelationshipType,} from 'web/lib/util/convert-types'
import stringOrStringArrayToText from 'web/lib/util/string-or-string-array-to-text'
import {ReactNode} from 'react'
import {REVERTED_POLITICAL_CHOICES, REVERTED_ROMANTIC_CHOICES} from 'web/components/filters/choices'
import {
REVERTED_DIET_CHOICES,
REVERTED_POLITICAL_CHOICES,
REVERTED_ROMANTIC_CHOICES
} from 'web/components/filters/choices'
import {BiSolidDrink} from 'react-icons/bi'
import {BsPersonHeart} from 'react-icons/bs'
import {FaChild} from 'react-icons/fa6'
import {LuBriefcase, LuCigarette, LuCigaretteOff, LuGraduationCap,} from 'react-icons/lu'
import {MdNoDrinks, MdOutlineChildFriendly} from 'react-icons/md'
import {PiHandsPrayingBold, PiMagnifyingGlassBold, PiPlantBold,} from 'react-icons/pi'
import {PiHandsPrayingBold, PiMagnifyingGlassBold,} from 'react-icons/pi'
import {RiScales3Line} from 'react-icons/ri'
import {Col} from 'web/components/layout/col'
import {Row} from 'web/components/layout/row'
@@ -21,6 +25,7 @@ import {Profile} from 'common/profiles/profile'
import {UserActivity} from "common/user";
import {ClockIcon} from "@heroicons/react/solid";
import {MAX_INT, MIN_INT} from "common/constants";
import {GiFruitBowl} from "react-icons/gi";
export function AboutRow(props: {
icon: ReactNode
@@ -77,8 +82,8 @@ export default function ProfileAbout(props: {
<Smoker profile={profile}/>
<Drinks profile={profile}/>
<AboutRow
icon={<PiPlantBold className="h-5 w-5"/>}
text={profile.is_vegetarian_or_vegan ? 'Vegetarian/Vegan' : null}
icon={<GiFruitBowl className="h-5 w-5"/>}
text={profile.diet?.map(e => REVERTED_DIET_CHOICES[e])}
/>
<HasKids profile={profile}/>
<WantsKids profile={profile}/>
@@ -296,7 +301,7 @@ export const formatProfileValue = (key: string, value: any) => {
case 'last_online_time':
return fromNow(new Date(value).valueOf())
case 'is_smoker':
case 'is_vegetarian_or_vegan':
case 'diet':
case 'has_pets':
return value ? 'Yes' : 'No'
case 'height_in_inches':

View File

@@ -50,11 +50,13 @@ export function ProfilesHome() {
if (!user) return;
setIsReloading(true);
const current = ++id.current;
api('get-profiles', removeNullOrUndefinedProps({
const args = removeNullOrUndefinedProps({
limit: 20,
compatibleWithUserId: user?.id,
...filters
}) as any)
});
console.debug('Refreshing profiles, filters:', args);
api('get-profiles', args as any)
.then(({profiles}) => {
if (current === id.current) setProfiles(profiles);
})

View File

@@ -1,7 +1,12 @@
import {REVERTED_RELATIONSHIP_CHOICES, REVERTED_ROMANTIC_CHOICES} from "web/components/filters/choices";
import {
REVERTED_DIET_CHOICES,
REVERTED_RELATIONSHIP_CHOICES,
REVERTED_ROMANTIC_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 function convertRelationshipType(relationshipType: RelationshipType) {
return REVERTED_RELATIONSHIP_CHOICES[relationshipType]
@@ -10,3 +15,7 @@ export function convertRelationshipType(relationshipType: RelationshipType) {
export function convertRomanticTypes(romanticType: RomanticType) {
return REVERTED_ROMANTIC_CHOICES[romanticType]
}
export function convertDietTypes(dietType: DietType) {
return REVERTED_DIET_CHOICES[dietType]
}