diff --git a/backend/api/src/get-profiles.ts b/backend/api/src/get-profiles.ts index 5dd1c8b0..a6866523 100644 --- a/backend/api/src/get-profiles.ts +++ b/backend/api/src/get-profiles.ts @@ -12,6 +12,7 @@ export type profileQueryType = { // Search and filter parameters name?: string | undefined, genders?: String[] | undefined, + education_levels?: String[] | undefined, pref_gender?: String[] | undefined, pref_age_min?: number | undefined, pref_age_max?: number | undefined, @@ -44,6 +45,7 @@ export const loadProfiles = async (props: profileQueryType) => { after, name, genders, + education_levels, pref_gender, pref_age_min, pref_age_max, @@ -82,6 +84,7 @@ export const loadProfiles = async (props: profileQueryType) => { (l) => (!name || l.user.name.toLowerCase().includes(name.toLowerCase())) && (!genders || genders.includes(l.gender)) && + (!education_levels || education_levels.includes(l.education_level ?? '')) && (!pref_gender || intersection(pref_gender, l.pref_gender).length) && (!pref_age_min || (l.age ?? MAX_INT) >= pref_age_min) && (!pref_age_max || (l.age ?? MIN_INT) <= pref_age_max) && @@ -147,7 +150,9 @@ export const loadProfiles = async (props: profileQueryType) => { {word} )), - genders?.length && where(`gender = ANY($(gender))`, {gender: genders}), + genders?.length && where(`gender = ANY($(genders))`, {genders}), + + education_levels?.length && where(`education_level = ANY($(education_levels))`, {education_levels}), pref_gender?.length && where(`pref_gender is NULL or pref_gender = '{}' OR pref_gender && $(pref_gender)`, {pref_gender}), diff --git a/common/src/api/schema.ts b/common/src/api/schema.ts index f0a47d2a..2c877236 100644 --- a/common/src/api/schema.ts +++ b/common/src/api/schema.ts @@ -345,6 +345,7 @@ export const API = (_apiTypeCheck = { // Search and filter parameters name: z.string().optional(), genders: arraybeSchema.optional(), + education_levels: arraybeSchema.optional(), pref_gender: arraybeSchema.optional(), pref_age_min: z.coerce.number().optional(), pref_age_max: z.coerce.number().optional(), diff --git a/common/src/constants.ts b/common/src/constants.ts index ee3fe8a0..ab2b65fc 100644 --- a/common/src/constants.ts +++ b/common/src/constants.ts @@ -1,5 +1,5 @@ -export const MAX_INT = 99999 -export const MIN_INT = -MAX_INT +export const MIN_INT = Number.MIN_SAFE_INTEGER +export const MAX_INT = Number.MAX_SAFE_INTEGER export const supportEmail = 'hello@compassmeet.com'; // export const marketingEmail = 'hello@compassmeet.com'; diff --git a/common/src/filters.ts b/common/src/filters.ts index 3fa0bfd7..74dccef0 100644 --- a/common/src/filters.ts +++ b/common/src/filters.ts @@ -15,6 +15,7 @@ export type FilterFields = { lon: number | null radius: number | null genders: string[] + education_levels: string[] name: string | undefined shortBio: boolean | undefined } & Pick< @@ -57,6 +58,7 @@ export const initialFilters: Partial = { radius: undefined, name: undefined, genders: undefined, + education_levels: undefined, pref_age_max: undefined, pref_age_min: undefined, has_kids: undefined, diff --git a/common/src/searches.ts b/common/src/searches.ts index e96187a5..65977648 100644 --- a/common/src/searches.ts +++ b/common/src/searches.ts @@ -8,6 +8,7 @@ const filterLabels: Record = { location: "", name: "Searching", genders: "", + education_levels: "Education", pref_age_max: "Max age", pref_age_min: "Min age", has_kids: "", diff --git a/web/components/filters/choices.tsx b/web/components/filters/choices.tsx index e68df45b..d3d67699 100644 --- a/web/components/filters/choices.tsx +++ b/web/components/filters/choices.tsx @@ -37,6 +37,15 @@ export const DIET_CHOICES = { Other: 'other', } +export const EDUCATION_CHOICES = { + None: 'none', + 'High school': 'high-school', + 'Some college': 'some-college', + Bachelors: 'bachelors', + Masters: 'masters', + PhD: 'doctorate', +} + export const REVERTED_RELATIONSHIP_CHOICES = Object.fromEntries( Object.entries(RELATIONSHIP_CHOICES).map(([key, value]) => [value, key]) ); @@ -51,4 +60,8 @@ export const REVERTED_POLITICAL_CHOICES = Object.fromEntries( export const REVERTED_DIET_CHOICES = Object.fromEntries( Object.entries(DIET_CHOICES).map(([key, value]) => [value, key]) +); + +export const REVERTED_EDUCATION_CHOICES = Object.fromEntries( + Object.entries(EDUCATION_CHOICES).map(([key, value]) => [value, key]) ); \ No newline at end of file diff --git a/web/components/filters/desktop-filters.tsx b/web/components/filters/desktop-filters.tsx index 68a927bb..6a6a855b 100644 --- a/web/components/filters/desktop-filters.tsx +++ b/web/components/filters/desktop-filters.tsx @@ -25,6 +25,8 @@ import {DietFilter, DietFilterText} from "web/components/filters/diet-filter"; import {PoliticalFilter, PoliticalFilterText} from "web/components/filters/political-filter"; import {GiFruitBowl} from "react-icons/gi"; import {RiScales3Line} from "react-icons/ri"; +import {EducationFilter, EducationFilterText} from "web/components/filters/education-filter"; +import {LuGraduationCap} from "react-icons/lu"; export function DesktopFilters(props: { filters: Partial @@ -349,6 +351,30 @@ export function DesktopFilters(props: { menuWidth="w-50" /> + {/* EDUCATION */} + ( + + + + + } + open={open} + /> + )} + dropdownMenuContent={ + + + + } + popoverClassName="bg-canvas-50" + /> + {/* Short Bios */} Any education + ) + } + + const order = Object.values(EDUCATION_CHOICES) + const sortedOptions = options + .slice() + .sort((a, b) => { + const ia = order.indexOf(a as any) + const ib = order.indexOf(b as any) + const sa = ia === -1 ? MAX_INT : ia + const sb = ib === -1 ? MAX_INT : ib + if (sa !== sb) return sa - sb + return String(a).localeCompare(String(b)) + }) + + const convertedTypes = sortedOptions.map((r) => convertEducationTypes(r as any)) + + return ( +
+ + {stringOrStringArrayToText({ + text: convertedTypes, + capitalizeFirstLetterOption: true, + })}{' '} + +
+ ) +} + +export function EducationFilter(props: { + filters: Partial + updateFilter: (newState: Partial) => void +}) { + const {filters, updateFilter} = props + return ( + <> + { + updateFilter({education_levels: c}) + }} + /> + + ) +} diff --git a/web/components/filters/mobile-filters.tsx b/web/components/filters/mobile-filters.tsx index d1c21ea8..3ba2d88a 100644 --- a/web/components/filters/mobile-filters.tsx +++ b/web/components/filters/mobile-filters.tsx @@ -14,14 +14,14 @@ import {DietType, PoliticalType, RelationshipType, RomanticType} from 'web/lib/u import {FilterFields} from "common/filters"; import {ShortBioToggle} from "web/components/filters/short-bio-toggle"; import {PrefGenderFilter, PrefGenderFilterText} from "./pref-gender-filter" -import {KidsLabel, WantsKidsFilter, WantsKidsIcon} from "web/components/filters/wants-kids-filter"; +import {KidsLabel, WantsKidsFilter} from "web/components/filters/wants-kids-filter"; import {wantsKidsLabels} from "common/wants-kids"; -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"; import {PoliticalFilter, PoliticalFilterText} from "web/components/filters/political-filter"; +import {EducationFilter, EducationFilterText} from "web/components/filters/education-filter"; function MobileFilters(props: { filters: Partial @@ -198,7 +198,7 @@ function MobileFilters(props: { filters.wants_kids_strength != null && filters.wants_kids_strength !== -1 } - // icon={} + // icon={} selection={ } + // icon={} selection={ + {/* EDUCATION */} + + } + > + + + {/* Short Bios */} { } const yourFilters: Partial = { - genders: you?.pref_gender?.length ? you.pref_gender : undefined, pref_gender: you?.gender?.length ? [you.gender] : undefined, + genders: you?.pref_gender?.length ? you.pref_gender : undefined, + education_levels: you?.education_level ? [you.education_level] : undefined, pref_age_max: (you?.pref_age_max ?? MAX_INT) < 100 ? you?.pref_age_max : 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, @@ -99,6 +100,7 @@ export const useFilters = (you: Profile | undefined) => { !!you && (!location || location.id === you.geodb_city_id) && isEqual(filters.genders?.length ? filters.genders : undefined, yourFilters.genders?.length ? yourFilters.genders : undefined) + && isEqual(new Set(filters.education_levels), new Set([you.education_level])) && (!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)) diff --git a/web/components/optional-profile-form.tsx b/web/components/optional-profile-form.tsx index 6d3ccd8d..ae2a5456 100644 --- a/web/components/optional-profile-form.tsx +++ b/web/components/optional-profile-form.tsx @@ -28,7 +28,13 @@ 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 {DIET_CHOICES, POLITICAL_CHOICES, RELATIONSHIP_CHOICES, ROMANTIC_CHOICES} from "web/components/filters/choices"; +import { + DIET_CHOICES, + EDUCATION_CHOICES, + POLITICAL_CHOICES, + RELATIONSHIP_CHOICES, + ROMANTIC_CHOICES +} from "web/components/filters/choices"; import toast from "react-hot-toast"; export const OptionalProfileUserForm = (props: { @@ -537,14 +543,7 @@ export const OptionalProfileUserForm = (props: { setProfile('education_level', c)} /> diff --git a/web/components/profile-about.tsx b/web/components/profile-about.tsx index e9d4f6a2..1e489e62 100644 --- a/web/components/profile-about.tsx +++ b/web/components/profile-about.tsx @@ -3,7 +3,7 @@ import {convertRelationshipType, type RelationshipType,} from 'web/lib/util/conv import stringOrStringArrayToText from 'web/lib/util/string-or-string-array-to-text' import {ReactNode} from 'react' import { - REVERTED_DIET_CHOICES, + REVERTED_DIET_CHOICES, REVERTED_EDUCATION_CHOICES, REVERTED_POLITICAL_CHOICES, REVERTED_ROMANTIC_CHOICES } from 'web/components/filters/choices' @@ -170,21 +170,22 @@ function Education(props: { profile: Profile }) { const educationLevel = profile.education_level const university = profile.university - const noUniversity = - !educationLevel || - educationLevel == 'high-school' || - educationLevel == 'none' + let text = '' - if (!university || noUniversity) { + if (educationLevel) { + text += capitalizeAndRemoveUnderscores(REVERTED_EDUCATION_CHOICES[educationLevel]) + } + if (university) { + if (educationLevel) text += ' at ' + text += capitalizeAndRemoveUnderscores(university) + } + if (text.length === 0) { return <> } - const universityText = `${ - noUniversity ? '' : capitalizeAndRemoveUnderscores(educationLevel) + ' at ' - }${capitalizeAndRemoveUnderscores(university)}` return ( } - text={universityText} + text={text} /> ) } diff --git a/web/lib/util/convert-types.ts b/web/lib/util/convert-types.ts index 6be079ac..d6701078 100644 --- a/web/lib/util/convert-types.ts +++ b/web/lib/util/convert-types.ts @@ -1,14 +1,16 @@ import { REVERTED_DIET_CHOICES, + REVERTED_EDUCATION_CHOICES, + REVERTED_POLITICAL_CHOICES, REVERTED_RELATIONSHIP_CHOICES, - REVERTED_ROMANTIC_CHOICES, - REVERTED_POLITICAL_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 type PoliticalType = keyof typeof REVERTED_POLITICAL_CHOICES +export type EducationType = keyof typeof REVERTED_EDUCATION_CHOICES export function convertRelationshipType(relationshipType: RelationshipType) { return REVERTED_RELATIONSHIP_CHOICES[relationshipType] @@ -25,3 +27,7 @@ export function convertDietTypes(dietType: DietType) { export function convertPoliticalTypes(politicalType: PoliticalType) { return REVERTED_POLITICAL_CHOICES[politicalType] } + +export function convertEducationTypes(educationType: EducationType) { + return REVERTED_EDUCATION_CHOICES[educationType] +}