diff --git a/backend/api/src/get-profiles.ts b/backend/api/src/get-profiles.ts index 0e687f1b..3b2a7a12 100644 --- a/backend/api/src/get-profiles.ts +++ b/backend/api/src/get-profiles.ts @@ -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}), diff --git a/common/src/api/schema.ts b/common/src/api/schema.ts index f4048711..3c18cc87 100644 --- a/common/src/api/schema.ts +++ b/common/src/api/schema.ts @@ -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(), diff --git a/common/src/api/zod-types.ts b/common/src/api/zod-types.ts index dab562de..79ee611f 100644 --- a/common/src/api/zod-types.ts +++ b/common/src/api/zod-types.ts @@ -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(), diff --git a/common/src/envs/constants.ts b/common/src/envs/constants.ts index 96883bd2..d38765df 100644 --- a/common/src/envs/constants.ts +++ b/common/src/envs/constants.ts @@ -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' } diff --git a/web/components/filters/desktop-filters.tsx b/web/components/filters/desktop-filters.tsx index 817ac256..e559939e 100644 --- a/web/components/filters/desktop-filters.tsx +++ b/web/components/filters/desktop-filters.tsx @@ -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 @@ -379,6 +381,31 @@ export function DesktopFilters(props: { menuWidth="w-50" /> + {/* SMOKER */} + ( + + + + + } + /> + )} + dropdownMenuContent={ + + + + } + popoverClassName="bg-canvas-50" + menuWidth="w-50" + /> + {/* EDUCATION */} ( diff --git a/web/components/filters/mobile-filters.tsx b/web/components/filters/mobile-filters.tsx index a02bc7c3..4b12f665 100644 --- a/web/components/filters/mobile-filters.tsx +++ b/web/components/filters/mobile-filters.tsx @@ -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 @@ -277,6 +278,23 @@ function MobileFilters(props: { + {/* SMOKER */} + + } + > + + + {/* POLITICS */} + + {is_smoker == null + ? mobile ? 'Either' : 'Either' + : is_smoker + ? mobile ? 'Yes' : 'Smoker' + : mobile ? 'No' : "Non-smoker"} + + + ) +} + +export function SmokerFilter(props: { + filters: Partial + updateFilter: (newState: Partial) => 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 ( + { + 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" + /> + ) +}