diff --git a/backend/api/package.json b/backend/api/package.json index 5fd90c47..0f4eee41 100644 --- a/backend/api/package.json +++ b/backend/api/package.json @@ -1,6 +1,6 @@ { "name": "@compass/api", - "version": "1.48.0", + "version": "1.49.0", "private": true, "description": "Backend API endpoints", "main": "src/serve.ts", diff --git a/backend/api/src/get-profiles.ts b/backend/api/src/get-profiles.ts index 896f9230..ecad2b79 100644 --- a/backend/api/src/get-profiles.ts +++ b/backend/api/src/get-profiles.ts @@ -6,6 +6,7 @@ import { GENDERS, LANGUAGE_CHOICES, MBTI_CHOICES, + NEUROTYPE_CHOICES, ORIENTATION_CHOICES, POLITICAL_CHOICES, RACE_CHOICES, @@ -81,6 +82,7 @@ export type profileQueryType = { languages?: string[] | undefined religion?: string[] | undefined orientation?: string[] | undefined + neurotype?: string[] | undefined wants_kids_strength?: number | undefined has_kids?: number | undefined is_smoker?: boolean | undefined @@ -131,6 +133,9 @@ const textFields = [ 'religious_beliefs', 'orientation_details', 'gender_details', + 'neurotype_details', + // Keyword-searchable but deliberately has no filter of its own — see the migration for why. + 'accessibility_notes', ] // Define choice fields to search @@ -147,6 +152,7 @@ const arrayChoiceFields = [ {field: 'relationship_status', choices: RELATIONSHIP_STATUS_CHOICES}, {field: 'religion', choices: RELIGION_CHOICES}, {field: 'orientation', choices: ORIENTATION_CHOICES}, + {field: 'neurotype', choices: NEUROTYPE_CHOICES}, {field: 'pref_relation_styles', choices: RELATIONSHIP_CHOICES}, {field: 'pref_romantic_styles', choices: ROMANTIC_CHOICES}, {field: 'languages', choices: LANGUAGE_CHOICES}, @@ -211,6 +217,7 @@ export const loadProfiles = async (props: profileQueryType, db?: SupabaseDirectC languages, religion, orientation, + neurotype, wants_kids_strength, has_kids, interests, @@ -508,6 +515,17 @@ export const loadProfiles = async (props: profileQueryType, db?: SupabaseDirectC }, ), + neurotype?.length && + where( + // most are neurotypical, so we don't include the people who didn't answer if we're looking for + // anything else — a rare value is only meaningful when it was actually stated + (!neurotype.includes('neurotypical') ? '' : "neurotype IS NULL OR neurotype = '{}' OR ") + + `neurotype && $(neurotype)`, + { + neurotype, + }, + ), + interests?.length && where(getManyToManyClause('interests'), {values: interests.map(Number)}), causes?.length && where(getManyToManyClause('causes'), {values: causes.map(Number)}), diff --git a/backend/supabase/migration.sql b/backend/supabase/migration.sql index 9d5716bb..ef7536bb 100644 --- a/backend/supabase/migration.sql +++ b/backend/supabase/migration.sql @@ -58,4 +58,6 @@ BEGIN; \i backend/supabase/email_unsubscribe_tokens.sql \i backend/supabase/migrations/20260524_add_orientation_to_profiles.sql \i backend/supabase/migrations/20260709_add_last_checked_at_to_bookmarked_searches.sql +\i backend/supabase/migrations/20260724_add_neurotype_to_profiles.sql +\i backend/supabase/migrations/20260724_add_accessibility_notes_to_profiles.sql COMMIT; diff --git a/backend/supabase/migrations/20260724_add_accessibility_notes_to_profiles.sql b/backend/supabase/migrations/20260724_add_accessibility_notes_to_profiles.sql new file mode 100644 index 00000000..fc55c69c --- /dev/null +++ b/backend/supabase/migrations/20260724_add_accessibility_notes_to_profiles.sql @@ -0,0 +1,10 @@ +-- Add accessibility notes to profiles table +-- Created: 2026-07-24 +-- +-- Deliberately free text rather than a checkbox taxonomy of conditions: a taxonomy exists mainly to be +-- filtered on, and filtering people out by disability is exactly what this field must not enable. Free +-- text keeps framing and depth of disclosure with the member. It is keyword-searchable (see `textFields` +-- in backend/api/src/get-profiles.ts) but has no filter of its own. + +ALTER TABLE profiles + ADD COLUMN IF NOT EXISTS accessibility_notes TEXT; diff --git a/backend/supabase/migrations/20260724_add_neurotype_to_profiles.sql b/backend/supabase/migrations/20260724_add_neurotype_to_profiles.sql new file mode 100644 index 00000000..0f31ced9 --- /dev/null +++ b/backend/supabase/migrations/20260724_add_neurotype_to_profiles.sql @@ -0,0 +1,9 @@ +-- Add neurotype (neurodivergence identity) fields to profiles table +-- Created: 2026-07-24 + +ALTER TABLE profiles + ADD COLUMN IF NOT EXISTS neurotype TEXT[], + ADD COLUMN IF NOT EXISTS neurotype_details TEXT; + +-- Create GIN index for array field +CREATE INDEX IF NOT EXISTS profiles_neurotype_gin ON profiles USING GIN (neurotype); diff --git a/common/messages/de.json b/common/messages/de.json index 65cea332..0023d322 100644 --- a/common/messages/de.json +++ b/common/messages/de.json @@ -387,6 +387,7 @@ "filter.any_interests": "Jedes Interesse", "filter.any_language": "Alle Sprachen", "filter.any_mbti": "Jeder MBTI", + "filter.any_neurotype": "Beliebiger Neurotyp", "filter.any_new_users": "Jegliche neue Nutzer", "filter.any_orientation": "Jede Orientierung", "filter.any_politics": "Jede politische Ausrichtung", @@ -451,6 +452,7 @@ "filter.mine_toggle": "Meine Filter", "filter.multiple": "Mehrere", "filter.near": "in der Nähe von", + "filter.neurotype.show_more": "Mehr Neurotypen anzeigen", "filter.orientation.show_more": "Mehr Orientierungen anzeigen", "filter.raised_in": "Aufgewachsen", "filter.relationship.any_connection": "Jede Beziehung", @@ -752,6 +754,7 @@ "privacy.use.text": "Ihre Daten werden nur verwendet, um die Plattform zu betreiben, zu personalisieren und zu verbessern. Wir verkaufen Ihre persönlichen Informationen nicht an Dritte.", "privacy.use.title": "2. Wie wir Ihre Daten verwenden", "profile.about.raised_in": "Aufgewachsen in {location}", + "profile.accessibility_notes": "Barrierefreiheit", "profile.age_any": "jeden Alters", "profile.age_between": "zwischen {min} und {max} Jahren", "profile.age_exact": "genau {min} Jahre", @@ -1057,7 +1060,19 @@ "profile.mbti.ESFP": "Unterhalter", "profile.mutual": "Gegenseitig", "profile.mutual_interests": "Sie haben beide gemeinsame Interessen: {matches}", + "profile.neurotype": "Neurotyp", + "profile.neurotype.adhd": "ADHS", + "profile.neurotype.autistic": "Autistisch", + "profile.neurotype.details_placeholder": "Was hilfreich zu wissen ist über dein Denken oder Kommunizieren…", + "profile.neurotype.dyscalculic": "Dyskalkulie", + "profile.neurotype.dyslexic": "Legasthenie", + "profile.neurotype.hsp": "Hochsensibel (HSP)", + "profile.neurotype.neurotypical": "Neurotypisch", + "profile.neurotype.show_more": "Weitere Optionen anzeigen", "profile.not_both_open_to_type": "Sie sind nicht beide für eine(n) {type} offen", + "profile.optional.accessibility_notes": "Barrierefreiheit", + "profile.optional.accessibility_notes_hint": "Optional und öffentlich wie der Rest deines Profils. Alles Praktische, das ein gutes Treffen erleichtert — Zugangsbedürfnisse, Energielevel, sensorische Vorlieben, ruhigere Orte. Sag so viel oder so wenig, wie du möchtest.", + "profile.optional.accessibility_notes_placeholder": "z. B. Ich nutze einen Rollstuhl, stufenlose Orte passen am besten. Fragen sind willkommen.", "profile.optional.age": "Alter", "profile.optional.age.error_max": "Bitte gib ein gültiges Alter ein", "profile.optional.age.error_min": "Du musst mindestens 18 Jahre alt sein", @@ -1101,6 +1116,7 @@ "profile.optional.languages": "Gesprochene Sprachen", "profile.optional.location": "Standort", "profile.optional.mbti": "MBTI-Persönlichkeitstyp", + "profile.optional.neurotype": "Neurotyp", "profile.optional.num_kids": "Aktuelle Anzahl von Kindern", "profile.optional.og_card": "Profilkarte", "profile.optional.orientation": "Sexuelle Orientierung", diff --git a/common/messages/fr.json b/common/messages/fr.json index 0be6385b..ee031ceb 100644 --- a/common/messages/fr.json +++ b/common/messages/fr.json @@ -387,6 +387,7 @@ "filter.any_interests": "Tout intérêt", "filter.any_language": "Toutes les langues", "filter.any_mbti": "Tout MBTI", + "filter.any_neurotype": "Tout neurotype", "filter.any_new_users": "Tout nouvel utilisateur", "filter.any_orientation": "Toute orientation", "filter.any_politics": "Toute orientation politique", @@ -451,6 +452,7 @@ "filter.mine_toggle": "Mes filtres", "filter.multiple": "Multiple", "filter.near": "près de", + "filter.neurotype.show_more": "Afficher plus de neurotypes", "filter.orientation.show_more": "Afficher plus d'orientations", "filter.raised_in": "A grandi", "filter.relationship.any_connection": "Toute relation", @@ -751,6 +753,7 @@ "privacy.use.text": "Vos données sont utilisées uniquement pour faire fonctionner, personnaliser et améliorer la plateforme. Nous ne vendons pas vos informations personnelles à des tiers.", "privacy.use.title": "2. Comment nous utilisons vos données", "profile.about.raised_in": "A grandi à {location}", + "profile.accessibility_notes": "Accessibilité", "profile.age_any": "de tout âge", "profile.age_between": "entre {min} et {max} ans", "profile.age_exact": "exactement {min} ans", @@ -1056,7 +1059,19 @@ "profile.mbti.ESFP": "Animateur", "profile.mutual": "Mutuel", "profile.mutual_interests": "Vous avez tous les deux des intérêts communs : {matches}. Contactez-les ci-dessus!", + "profile.neurotype": "Neurotype", + "profile.neurotype.adhd": "TDAH", + "profile.neurotype.autistic": "Autiste", + "profile.neurotype.details_placeholder": "Ce qu'il est utile de savoir sur votre façon de penser ou de communiquer…", + "profile.neurotype.dyscalculic": "Dyscalculique", + "profile.neurotype.dyslexic": "Dyslexique", + "profile.neurotype.hsp": "Hypersensible (HSP)", + "profile.neurotype.neurotypical": "Neurotypique", + "profile.neurotype.show_more": "Afficher plus d'options", "profile.not_both_open_to_type": "Vous n'êtes pas tous les deux ouverts à un(e) {type}", + "profile.optional.accessibility_notes": "Accessibilité", + "profile.optional.accessibility_notes_hint": "Facultatif, et public comme le reste de votre profil. Tout ce qui est utile en pratique pour bien vous rencontrer — besoins d'accès, niveau d'énergie, préférences sensorielles, lieux plus calmes. Dites-en autant ou aussi peu que vous voulez.", + "profile.optional.accessibility_notes_placeholder": "ex. Je me déplace en fauteuil roulant, les lieux sans marches sont donc plus simples. N'hésitez pas à poser des questions.", "profile.optional.age": "Âge", "profile.optional.age.error_max": "Veuillez entrer un âge valide", "profile.optional.age.error_min": "Vous devez avoir au moins 18 ans", @@ -1100,6 +1115,7 @@ "profile.optional.languages": "Langues parlées", "profile.optional.location": "Localisation", "profile.optional.mbti": "Type de personnalité MBTI", + "profile.optional.neurotype": "Neurotype", "profile.optional.num_kids": "Nombre actuel d'enfants", "profile.optional.og_card": "Carte de profil", "profile.optional.orientation": "Orientation sexuelle", diff --git a/common/src/api/schema.ts b/common/src/api/schema.ts index 4c6b8354..f6172888 100644 --- a/common/src/api/schema.ts +++ b/common/src/api/schema.ts @@ -762,6 +762,7 @@ export const API = (_apiTypeCheck = { big5_neuroticism_max: z.coerce.number().optional(), religion: arraybeSchema.optional(), orientation: arraybeSchema.optional(), + neurotype: arraybeSchema.optional(), pref_relation_styles: arraybeSchema.optional(), pref_romantic_styles: arraybeSchema.optional(), diet: arraybeSchema.optional(), diff --git a/common/src/api/zod-types.ts b/common/src/api/zod-types.ts index 721ce466..e901e073 100644 --- a/common/src/api/zod-types.ts +++ b/common/src/api/zod-types.ts @@ -123,6 +123,9 @@ const optionalProfilesSchema = z.object({ gender_details: z.string().optional().nullable(), orientation: z.array(z.string()).optional().nullable(), orientation_details: z.string().optional().nullable(), + neurotype: z.array(z.string()).optional().nullable(), + neurotype_details: z.string().optional().nullable(), + accessibility_notes: z.string().optional().nullable(), religion: z.array(z.string()).optional().nullable(), religious_belief_strength: z.number().optional().nullable(), religious_beliefs: z.string().optional().nullable(), diff --git a/common/src/choices.ts b/common/src/choices.ts index e86fd42d..e6d41567 100644 --- a/common/src/choices.ts +++ b/common/src/choices.ts @@ -314,6 +314,27 @@ export const EXTRA_ORIENTATIONS = Object.values(ORIENTATION_CHOICES).filter( (v) => !DEFAULT_ORIENTATIONS.includes(v as any), ) +// Neurotype is framed as an identity, not a diagnosis: no "undiagnosed" or "prefer not to say" options, +// and no psychiatric conditions. Anything a member doesn't want structured goes in `neurotype_details`. +export const NEUROTYPE_CHOICES = { + Autistic: 'autistic', + ADHD: 'adhd', + Neurotypical: 'neurotypical', + // No combined "AuDHD" option on purpose — that is ticking Autistic and ADHD together. + Dyslexic: 'dyslexic', + Dyscalculic: 'dyscalculic', + 'Highly sensitive (HSP)': 'hsp', +} as const + +export const DEFAULT_NEUROTYPES = [ + NEUROTYPE_CHOICES.Autistic, + NEUROTYPE_CHOICES.ADHD, + NEUROTYPE_CHOICES.Neurotypical, +] +export const EXTRA_NEUROTYPES = Object.values(NEUROTYPE_CHOICES).filter( + (v) => !DEFAULT_NEUROTYPES.includes(v as any), +) + export const LAST_ONLINE_CHOICES = { now: 'Currently online', today: 'Today', @@ -375,6 +396,7 @@ export const INVERTED_CANNABIS_CHOICES = invert(CANNABIS_CHOICES) export const INVERTED_SUBSTANCE_INTENTION_CHOICES = invert(SUBSTANCE_INTENTION_CHOICES) export const INVERTED_SUBSTANCE_PREFERENCE_CHOICES = invert(SUBSTANCE_PREFERENCE_CHOICES) export const INVERTED_ORIENTATION_CHOICES = invert(ORIENTATION_CHOICES) +export const INVERTED_NEUROTYPE_CHOICES = invert(NEUROTYPE_CHOICES) //Exported types for test files to use when referencing the keys of the choices objects export type PersonalityKey = keyof typeof MBTI_CHOICES diff --git a/common/src/filters-format.ts b/common/src/filters-format.ts index 88d21f21..06028269 100644 --- a/common/src/filters-format.ts +++ b/common/src/filters-format.ts @@ -5,6 +5,7 @@ import { INVERTED_GENDERS, INVERTED_LANGUAGE_CHOICES, INVERTED_MBTI_CHOICES, + INVERTED_NEUROTYPE_CHOICES, INVERTED_ORIENTATION_CHOICES, INVERTED_POLITICAL_CHOICES, INVERTED_PSYCHEDELICS_CHOICES, @@ -41,6 +42,7 @@ const filterLabels: Record = { work: '', religion: '', orientation: '', + neurotype: '', orderBy: '', hasPhoto: '', diet: 'Diet', @@ -186,6 +188,10 @@ export function formatFilters( value = value.map((s) => translate(`profile.orientation.${s}`, INVERTED_ORIENTATION_CHOICES[s] ?? s), ) + } else if (key === 'neurotype') { + value = value.map((s) => + translate(`profile.neurotype.${s}`, INVERTED_NEUROTYPE_CHOICES[s] ?? s), + ) } else if (key === 'languages') { value = value.map((s) => translate(`profile.language.${s}`, INVERTED_LANGUAGE_CHOICES[s])) } else if (key === 'pref_gender') { diff --git a/common/src/filters.ts b/common/src/filters.ts index 479ca6f2..f12e9cb3 100644 --- a/common/src/filters.ts +++ b/common/src/filters.ts @@ -56,6 +56,7 @@ export type FilterFields = { | 'pref_age_max' | 'religion' | 'orientation' + | 'neurotype' > export const orderProfiles = (profiles: Profile[], starredUserIds: string[] | undefined) => { @@ -107,6 +108,7 @@ export const initialFilters: Partial = { languages: undefined, religion: undefined, orientation: undefined, + neurotype: undefined, mbti: undefined, pref_gender: undefined, shortBio: undefined, diff --git a/common/src/supabase/schema.ts b/common/src/supabase/schema.ts index 1508db8e..dbb911cf 100644 --- a/common/src/supabase/schema.ts +++ b/common/src/supabase/schema.ts @@ -1114,6 +1114,7 @@ export type Database = { } profiles: { Row: { + accessibility_notes: string | null age: number | null allow_direct_messaging: boolean | null allow_interest_indicating: boolean | null @@ -1158,6 +1159,8 @@ export type Database = { looking_for_matches: boolean mbti: string | null messaging_status: string + neurotype: string[] | null + neurotype_details: string | null occupation: string | null occupation_title: string | null orientation: string[] | null @@ -1195,6 +1198,7 @@ export type Database = { wants_kids_strength: number | null } Insert: { + accessibility_notes?: string | null age?: number | null allow_direct_messaging?: boolean | null allow_interest_indicating?: boolean | null @@ -1239,6 +1243,8 @@ export type Database = { looking_for_matches?: boolean mbti?: string | null messaging_status?: string + neurotype?: string[] | null + neurotype_details?: string | null occupation?: string | null occupation_title?: string | null orientation?: string[] | null @@ -1276,6 +1282,7 @@ export type Database = { wants_kids_strength?: number | null } Update: { + accessibility_notes?: string | null age?: number | null allow_direct_messaging?: boolean | null allow_interest_indicating?: boolean | null @@ -1320,6 +1327,8 @@ export type Database = { looking_for_matches?: boolean mbti?: string | null messaging_status?: string + neurotype?: string[] | null + neurotype_details?: string | null occupation?: string | null occupation_title?: string | null orientation?: string[] | null diff --git a/docs/filters.md b/docs/filters.md index 9394305e..8ac80c16 100644 --- a/docs/filters.md +++ b/docs/filters.md @@ -18,6 +18,7 @@ - Languages — by languages spoken. - Politics — political beliefs or ideological alignment. - Religion — religious belief or affiliation. +- Neurotype — neurodivergence identity (multi-select with extended list hidden behind "Show more"). - MBTI — Myers–Briggs personality type. - Big Five — ranges of the Big Five personality traits. - Last Active — how recently they were active on the platform. diff --git a/tests/e2e/backend/utils/userInformation.ts b/tests/e2e/backend/utils/userInformation.ts index 56be485a..d0697072 100644 --- a/tests/e2e/backend/utils/userInformation.ts +++ b/tests/e2e/backend/utils/userInformation.ts @@ -6,6 +6,7 @@ import { GENDERS, LANGUAGE_CHOICES, MBTI_CHOICES, + NEUROTYPE_CHOICES, ORIENTATION_CHOICES, POLITICAL_CHOICES, PSYCHEDELICS_CHOICES, @@ -37,6 +38,14 @@ class UserAccountInformationForSeeding { faker.number.int({min: 0, max: Object.values(ORIENTATION_CHOICES).length - 1}) ], ) + neurotype = Array.from( + {length: faker.number.int({min: 1, max: 2})}, + () => + Object.values(NEUROTYPE_CHOICES)[ + faker.number.int({min: 0, max: Object.values(NEUROTYPE_CHOICES).length - 1}) + ], + ) + accessibility_notes = faker.lorem.sentence() pref_age = { min: faker.number.int({min: 18, max: 27}), max: faker.number.int({min: 36, max: 68}), diff --git a/tests/e2e/utils/seed-showcase.ts b/tests/e2e/utils/seed-showcase.ts index defd843e..328eae2d 100644 --- a/tests/e2e/utils/seed-showcase.ts +++ b/tests/e2e/utils/seed-showcase.ts @@ -154,6 +154,7 @@ async function seedShowcaseProfile(profile: ShowcaseProfile, userId: string) { age: profile.age, gender: profile.gender, orientation: profile.orientation, + neurotype: profile.neurotype ?? null, headline: profile.headline, bio, bio_length: bioText.length, diff --git a/tests/e2e/utils/seedDatabase.ts b/tests/e2e/utils/seedDatabase.ts index 2e1be56e..b82dac64 100644 --- a/tests/e2e/utils/seedDatabase.ts +++ b/tests/e2e/utils/seedDatabase.ts @@ -69,6 +69,7 @@ export async function seedDbUser( age: userInfo.age, gender: userInfo.randomElement(userInfo.gender), orientation: userInfo.orientation, + neurotype: userInfo.neurotype, headline: userInfo.headline, keywords: profileKeywords, ethnicity: [userInfo.randomElement(userInfo.ethnicity)], @@ -96,6 +97,8 @@ export async function seedDbUser( education_level: userInfo.randomElement(userInfo.education_level), languages: languagesKnown, orientation_details: 'Some orientation details', + neurotype_details: 'Some neurotype details', + accessibility_notes: userInfo.accessibility_notes, pinned_url: '/images/default-avatar.png', } diff --git a/tests/e2e/utils/showcase-profiles.ts b/tests/e2e/utils/showcase-profiles.ts index 3ead8418..25f910eb 100644 --- a/tests/e2e/utils/showcase-profiles.ts +++ b/tests/e2e/utils/showcase-profiles.ts @@ -25,6 +25,8 @@ export interface ShowcaseProfile { age: number gender: string orientation: string[] + /** Optional — only some showcase profiles state a neurotype, as in real life. */ + neurotype?: string[] headline: string /** Paragraphs of the bio, in order. */ bio: string[] @@ -256,6 +258,7 @@ export const SHOWCASE_PROFILES: ShowcaseProfile[] = [ age: 26, gender: 'female', orientation: ['queer'], + neurotype: ['autistic'], headline: 'Climate modeller. Meditation twice a day. Will talk about soil for hours.', bio: [ 'I model regional climate for a living, which means I spend most days being carefully precise about uncertainty and most evenings being asked whether we are doomed. (Short answer: the question is badly posed.)', @@ -310,6 +313,7 @@ export const SHOWCASE_PROFILES: ShowcaseProfile[] = [ age: 61, gender: 'male', orientation: ['straight'], + neurotype: ['neurotypical'], headline: 'Retired GP, second act as a choir conductor, still learning Bach', bio: [ 'Forty years of general practice taught me that most of what people need is someone who will actually listen for more than four minutes. I retired two years ago and I am still adjusting to having time.', @@ -418,6 +422,7 @@ export const SHOWCASE_PROFILES: ShowcaseProfile[] = [ age: 29, gender: 'non-binary', orientation: ['pansexual'], + neurotype: ['adhd', 'hsp'], headline: 'Game designer, amateur mycologist, host of a very small dinner party', bio: [ 'I design systems for a mid-size indie studio — mostly economies and progression, the invisible plumbing that makes a game feel fair. Before that I studied economics, which turns out to be the same job with worse graphics.', @@ -641,6 +646,7 @@ export const SHOWCASE_PROFILES: ShowcaseProfile[] = [ age: 36, gender: 'male', orientation: ['straight', 'demisexual'], + neurotype: ['autistic', 'adhd'], headline: 'Open-source maintainer looking for collaborators, not users', bio: [ 'I maintain a mid-sized open-source database tool that about four thousand companies depend on and roughly nine people contribute to. The maths of that is the most interesting problem in my life right now.', diff --git a/web/components/filters/filters.tsx b/web/components/filters/filters.tsx index f51d627a..68a79b84 100644 --- a/web/components/filters/filters.tsx +++ b/web/components/filters/filters.tsx @@ -24,6 +24,7 @@ import {IncompleteProfilesToggle} from 'web/components/filters/incomplete-profil import {InterestFilter, InterestFilterText} from 'web/components/filters/interest-filter' import {LanguageFilter, LanguageFilterText} from 'web/components/filters/language-filter' import {MbtiFilter, MbtiFilterText} from 'web/components/filters/mbti-filter' +import {NeurotypeFilter, NeurotypeFilterText} from 'web/components/filters/neurotype-filter' import {OrientationFilter, OrientationFilterText} from 'web/components/filters/orientation-filter' import {PoliticalFilter, PoliticalFilterText} from 'web/components/filters/political-filter' import { @@ -727,6 +728,25 @@ function Filters(props: { setOpenGroup={setOpenGroup} // icon={} > + {/* Grouped with MBTI/Big Five rather than under Relationship — it describes how someone's mind + works, and matters just as much for friendship and collaboration as for dating. */} + + } + > + + + + {t('filter.any_neurotype', 'Any neurotype')} + + ) + } + + if (length > 2) { + return ( + + + {t('filter.multiple', 'Multiple')} + + + ) + } + + const convertedTypes = options.map((o) => + t(`profile.neurotype.${toKey(o)}`, INVERTED_NEUROTYPE_CHOICES[o] ?? o), + ) + + return ( +
+ + {stringOrStringArrayToText({ + text: convertedTypes, + capitalizeFirstLetterOption: true, + t: t, + })}{' '} + +
+ ) +} + +export function NeurotypeFilter(props: { + filters: Partial + updateFilter: (newState: Partial) => void + className?: string + profile?: Profile | null +}) { + const {filters, updateFilter, className, profile} = props + const t = useT() + const selected = ((filters as any).neurotype as string[] | undefined) ?? [] + const hasExtendedSelected = + selected.some((v) => EXTRA_NEUROTYPES.includes(v as any)) || + (profile?.neurotype && profile?.neurotype.some((v) => EXTRA_NEUROTYPES.includes(v as any))) + const [showAll, setShowAll] = useState(hasExtendedSelected) + + const visibleChoices = Object.fromEntries( + Object.entries(NEUROTYPE_CHOICES).filter( + ([, v]) => showAll || DEFAULT_NEUROTYPES.includes(v as any) || selected.includes(v), + ), + ) + + return ( + <> + { + updateFilter({neurotype: c} as any) + }} + optionsClassName={className} + /> + {!showAll && ( + + )} + + ) +} diff --git a/web/components/optional-profile-form.tsx b/web/components/optional-profile-form.tsx index 24002640..89bfc8c9 100644 --- a/web/components/optional-profile-form.tsx +++ b/web/components/optional-profile-form.tsx @@ -4,13 +4,16 @@ import {Editor} from '@tiptap/react' import clsx from 'clsx' import { CANNABIS_CHOICES, + DEFAULT_NEUROTYPES, DEFAULT_ORIENTATIONS, DIET_CHOICES, EDUCATION_CHOICES, + EXTRA_NEUROTYPES, GENDERS, GENDERS_PLURAL, LANGUAGE_CHOICES, MBTI_CHOICES, + NEUROTYPE_CHOICES, ORIENTATION_CHOICES, POLITICAL_CHOICES, PSYCHEDELICS_CHOICES, @@ -123,6 +126,11 @@ export const OptionalProfileUserForm = (props: { () => !!profile.gender && EXTRA_GENDERS.includes(profile.gender as any), ) const [showAllOrientations, setShowAllOrientations] = useState(false) + const [showAllNeurotypes, setShowAllNeurotypes] = useState( + () => + !!(profile as any).neurotype && + ((profile as any).neurotype as string[]).some((v) => EXTRA_NEUROTYPES.includes(v as any)), + ) const t = useT() const {locale} = useLocale() @@ -1049,6 +1057,56 @@ export const OptionalProfileUserForm = (props: { + + {/* Sits with MBTI/Big Five rather than under Relationships: it describes how someone's mind + works, and it is just as relevant to friendship and collaboration as to dating. */} + + + + showAllNeurotypes || + DEFAULT_NEUROTYPES.includes(v as any) || + ((profile as any)['neurotype'] ?? []).includes(v), + ), + ) as any + } + selected={(profile as any)['neurotype'] ?? []} + translationPrefix={'profile.neurotype'} + onChange={(selected) => setProfile('neurotype' as any, selected)} + /> + {!showAllNeurotypes && ( + + )} + {showAllNeurotypes && ( + <> +

{t('profile.optional.details', 'Details')}

+ ) => + setProfile('neurotype_details' as any, e.target.value) + } + className={'w-full sm:w-[700px]'} + value={(profile as any)['neurotype_details'] ?? undefined} + placeholder={t( + 'profile.neurotype.details_placeholder', + 'Anything helpful to know about how you think or communicate…', + )} + /> + + )} + +