Add filter for smoking

This commit is contained in:
MartinBraquet
2025-10-25 00:58:53 +02:00
parent f1f9970407
commit 342a0c612a
7 changed files with 116 additions and 10 deletions

View File

@@ -113,7 +113,7 @@ export const loadProfiles = async (props: profileQueryType) => {
has_kids == -1 ||
(has_kids == 0 && !l.has_kids) ||
(l.has_kids && l.has_kids > 0)) &&
(!is_smoker || l.is_smoker === is_smoker) &&
(is_smoker === undefined || l.is_smoker === is_smoker) &&
(l.id.toString() != skipId) &&
(!geodbCityIds ||
(l.geodb_city_id && geodbCityIds.includes(l.geodb_city_id))) &&
@@ -209,7 +209,12 @@ export const loadProfiles = async (props: profileQueryType) => {
has_kids === 0 && where(`has_kids IS NULL OR has_kids = 0`),
has_kids && has_kids > 0 && where(`has_kids > 0`),
is_smoker !== undefined && where(`is_smoker = $(is_smoker)`, {is_smoker}),
is_smoker !== undefined && (
where(
(is_smoker ? '' : 'is_smoker IS NULL OR ') + // smokers are rare, so we don't include the people who didn't answer if we're looking for smokers
`is_smoker = $(is_smoker)`, {is_smoker}
)
),
geodbCityIds?.length &&
where(`geodb_city_id = ANY($(geodbCityIds))`, {geodbCityIds}),

View File

@@ -2,7 +2,7 @@ import {
contentSchema,
combinedProfileSchema,
baseProfilesSchema,
arraybeSchema,
arraybeSchema, zBoolean,
} from 'common/api/zod-types'
import {PrivateChatMessage} from 'common/chat-message'
import {CompatibilityScore} from 'common/profiles/compatibility-score'
@@ -357,8 +357,8 @@ export const API = (_apiTypeCheck = {
political_beliefs: arraybeSchema.optional(),
wants_kids_strength: z.coerce.number().optional(),
has_kids: z.coerce.number().optional(),
is_smoker: z.coerce.boolean().optional(),
shortBio: z.coerce.boolean().optional(),
is_smoker: zBoolean.optional().optional(),
shortBio: zBoolean.optional().optional(),
geodbCityIds: arraybeSchema.optional(),
lat: z.coerce.number().optional(),
lon: z.coerce.number().optional(),

View File

@@ -42,6 +42,10 @@ const genderType = z.string()
// ])
const genderTypes = z.array(genderType)
export const zBoolean = z
.union([z.boolean(), z.string()])
.transform((val) => val === true || val === "true");
export const baseProfilesSchema = z.object({
// Required fields
age: z.number().min(18).max(100).optional(),
@@ -51,7 +55,7 @@ export const baseProfilesSchema = z.object({
pref_age_max: z.number().min(18).max(100).optional(),
pref_relation_styles: z.array(z.string()),
wants_kids_strength: z.number(),
looking_for_matches: z.boolean(),
looking_for_matches: zBoolean,
photo_urls: z.array(z.string()),
visibility: z.union([z.literal('public'), z.literal('member')]),
@@ -76,9 +80,9 @@ const optionalProfilesSchema = z.object({
ethnicity: z.array(z.string()).optional(),
born_in_location: z.string().optional(),
height_in_inches: z.number().optional(),
has_pets: z.boolean().optional(),
has_pets: zBoolean.optional().optional(),
education_level: z.string().optional(),
is_smoker: z.boolean().optional(),
is_smoker: zBoolean.optional().optional(),
drinks_per_month: z.number().min(0).optional(),
diet: z.array(z.string()).optional(),
has_kids: z.number().min(0).optional(),
@@ -86,7 +90,7 @@ const optionalProfilesSchema = z.object({
occupation_title: z.string().optional(),
occupation: z.string().optional(),
company: z.string().optional(),
comments_enabled: z.boolean().optional(),
comments_enabled: zBoolean.optional().optional(),
website: z.string().optional(),
bio: contentSchema.optional().nullable(),
twitter: z.string().optional(),

View File

@@ -14,7 +14,7 @@ export const IS_DEPLOYED = IS_GOOGLE_CLOUD || IS_VERCEL
export const IS_LOCAL = !IS_DEPLOYED
export const HOSTING_ENV = IS_GOOGLE_CLOUD ? 'Google Cloud' : IS_VERCEL ? 'Vercel' : IS_LOCAL ? 'local' : 'unknown'
if (IS_LOCAL && !process.env.ENVIRONMENT) {
if (IS_LOCAL && !process.env.ENVIRONMENT && !process.env.NEXT_PUBLIC_FIREBASE_ENV) {
console.warn("No ENVIRONMENT set, defaulting to DEV")
process.env.ENVIRONMENT = 'DEV'
}

View File

@@ -29,6 +29,8 @@ import {EducationFilter, EducationFilterText} from "web/components/filters/educa
import {LuGraduationCap} from "react-icons/lu";
import {DrinksFilter, DrinksFilterText} from "web/components/filters/drinks-filter";
import {MdLocalBar} from 'react-icons/md'
import {SmokerFilter, SmokerFilterText} from 'web/components/filters/smoker-filter'
import {LuCigarette} from 'react-icons/lu'
export function DesktopFilters(props: {
filters: Partial<FilterFields>
@@ -379,6 +381,31 @@ export function DesktopFilters(props: {
menuWidth="w-50"
/>
{/* SMOKER */}
<CustomizeableDropdown
buttonContent={(open) => (
<DropdownButton
open={open}
content={
<Row className="items-center gap-1">
<LuCigarette className="h-4 w-4"/>
<SmokerFilterText
is_smoker={filters.is_smoker}
highlightedClass={open ? 'text-primary-500' : undefined}
/>
</Row>
}
/>
)}
dropdownMenuContent={
<Col className="mx-2 mb-4">
<SmokerFilter filters={filters} updateFilter={updateFilter} />
</Col>
}
popoverClassName="bg-canvas-50"
menuWidth="w-50"
/>
{/* EDUCATION */}
<CustomizeableDropdown
buttonContent={(open: boolean) => (

View File

@@ -23,6 +23,7 @@ import {DietFilter, DietFilterText} from "web/components/filters/diet-filter";
import {PoliticalFilter, PoliticalFilterText} from "web/components/filters/political-filter";
import {EducationFilter, EducationFilterText} from "web/components/filters/education-filter";
import {DrinksFilter, DrinksFilterText, getNoMinMaxDrinks} from "./drinks-filter";
import {SmokerFilter, SmokerFilterText} from "./smoker-filter";
function MobileFilters(props: {
filters: Partial<FilterFields>
@@ -277,6 +278,23 @@ function MobileFilters(props: {
<DrinksFilter filters={filters} updateFilter={updateFilter} />
</MobileFilterSection>
{/* SMOKER */}
<MobileFilterSection
title="Smoker"
openFilter={openFilter}
setOpenFilter={setOpenFilter}
isActive={filters.is_smoker != null}
selection={
<SmokerFilterText
is_smoker={filters.is_smoker}
highlightedClass={filters.is_smoker == null ? 'text-ink-900' : 'text-primary-600'}
mobile
/>
}
>
<SmokerFilter filters={filters} updateFilter={updateFilter} />
</MobileFilterSection>
{/* POLITICS */}
<MobileFilterSection
title="Politics"

View File

@@ -0,0 +1,52 @@
import clsx from 'clsx'
import {Row} from 'web/components/layout/row'
import {ChoicesToggleGroup} from 'web/components/widgets/choices-toggle-group'
import {FilterFields} from 'common/filters'
export function SmokerFilterText(props: {
is_smoker: boolean | null | undefined
highlightedClass?: string
mobile?: boolean
}) {
const {is_smoker, highlightedClass, mobile} = props
return (
<Row className="items-center gap-0.5">
<span className={clsx(highlightedClass, is_smoker != null && 'font-semibold')}>
{is_smoker == null
? mobile ? 'Either' : 'Either'
: is_smoker
? mobile ? 'Yes' : 'Smoker'
: mobile ? 'No' : "Non-smoker"}
</span>
</Row>
)
}
export function SmokerFilter(props: {
filters: Partial<FilterFields>
updateFilter: (newState: Partial<FilterFields>) => void
}) {
const {filters, updateFilter} = props
const choicesMap = {
Either: 'either',
Yes: 'yes',
No: 'no',
} as const
const currentChoice =
filters.is_smoker == null ? choicesMap.Either : filters.is_smoker ? choicesMap.Yes : choicesMap.No
return (
<ChoicesToggleGroup
currentChoice={currentChoice}
choicesMap={choicesMap}
setChoice={(c) => {
if (c === choicesMap.Either) updateFilter({is_smoker: undefined})
else if (c === choicesMap.Yes) updateFilter({is_smoker: true})
else updateFilter({is_smoker: false})
}}
toggleClassName="w-1/3 justify-center"
/>
)
}