mirror of
https://github.com/CompassConnections/Compass.git
synced 2026-02-19 23:37:25 -05:00
Add Big 5 profile field
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@compass/api",
|
||||
"description": "Backend API endpoints",
|
||||
"version": "1.5.1",
|
||||
"version": "1.6.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"watch:serve": "tsx watch src/serve.ts",
|
||||
|
||||
@@ -17,6 +17,16 @@ export type profileQueryType = {
|
||||
pref_age_max?: number | undefined,
|
||||
drinks_min?: number | undefined,
|
||||
drinks_max?: number | undefined,
|
||||
big5_openness_min?: number | undefined,
|
||||
big5_openness_max?: number | undefined,
|
||||
big5_conscientiousness_min?: number | undefined,
|
||||
big5_conscientiousness_max?: number | undefined,
|
||||
big5_extraversion_min?: number | undefined,
|
||||
big5_extraversion_max?: number | undefined,
|
||||
big5_agreeableness_min?: number | undefined,
|
||||
big5_agreeableness_max?: number | undefined,
|
||||
big5_neuroticism_min?: number | undefined,
|
||||
big5_neuroticism_max?: number | undefined,
|
||||
pref_relation_styles?: string[] | undefined,
|
||||
pref_romantic_styles?: string[] | undefined,
|
||||
diet?: string[] | undefined,
|
||||
@@ -60,6 +70,16 @@ export const loadProfiles = async (props: profileQueryType) => {
|
||||
pref_age_max,
|
||||
drinks_min,
|
||||
drinks_max,
|
||||
big5_openness_min,
|
||||
big5_openness_max,
|
||||
big5_conscientiousness_min,
|
||||
big5_conscientiousness_max,
|
||||
big5_extraversion_min,
|
||||
big5_extraversion_max,
|
||||
big5_agreeableness_min,
|
||||
big5_agreeableness_max,
|
||||
big5_neuroticism_min,
|
||||
big5_neuroticism_max,
|
||||
pref_relation_styles,
|
||||
pref_romantic_styles,
|
||||
diet,
|
||||
@@ -211,6 +231,42 @@ export const loadProfiles = async (props: profileQueryType) => {
|
||||
drinks_max &&
|
||||
where(`drinks_per_month <= $(drinks_max) or drinks_per_month is null`, {drinks_max}),
|
||||
|
||||
big5_openness_min &&
|
||||
where(`big5_openness >= $(big5_openness_min) or big5_openness is null`, {big5_openness_min}),
|
||||
|
||||
big5_openness_max &&
|
||||
where(`big5_openness <= $(big5_openness_max) or big5_openness is null`, {big5_openness_max}),
|
||||
|
||||
big5_conscientiousness_min &&
|
||||
where(
|
||||
`big5_conscientiousness >= $(big5_conscientiousness_min) or big5_conscientiousness is null`,
|
||||
{big5_conscientiousness_min}
|
||||
),
|
||||
|
||||
big5_conscientiousness_max &&
|
||||
where(
|
||||
`big5_conscientiousness <= $(big5_conscientiousness_max) or big5_conscientiousness is null`,
|
||||
{big5_conscientiousness_max}
|
||||
),
|
||||
|
||||
big5_extraversion_min &&
|
||||
where(`big5_extraversion >= $(big5_extraversion_min) or big5_extraversion is null`, {big5_extraversion_min}),
|
||||
|
||||
big5_extraversion_max &&
|
||||
where(`big5_extraversion <= $(big5_extraversion_max) or big5_extraversion is null`, {big5_extraversion_max}),
|
||||
|
||||
big5_agreeableness_min &&
|
||||
where(`big5_agreeableness >= $(big5_agreeableness_min) or big5_agreeableness is null`, {big5_agreeableness_min}),
|
||||
|
||||
big5_agreeableness_max &&
|
||||
where(`big5_agreeableness <= $(big5_agreeableness_max) or big5_agreeableness is null`, {big5_agreeableness_max}),
|
||||
|
||||
big5_neuroticism_min &&
|
||||
where(`big5_neuroticism >= $(big5_neuroticism_min) or big5_neuroticism is null`, {big5_neuroticism_min}),
|
||||
|
||||
big5_neuroticism_max &&
|
||||
where(`big5_neuroticism <= $(big5_neuroticism_max) or big5_neuroticism is null`, {big5_neuroticism_max}),
|
||||
|
||||
pref_relation_styles?.length &&
|
||||
where(
|
||||
`pref_relation_styles IS NULL OR pref_relation_styles = '{}' OR pref_relation_styles && $(pref_relation_styles)`,
|
||||
|
||||
@@ -113,6 +113,11 @@ export const sinclairProfile: ProfileRow = {
|
||||
search_text: 'the futa in futarchy',
|
||||
search_tsv: 'the futa in futarchy',
|
||||
age: 25,
|
||||
big5_agreeableness: 10,
|
||||
big5_openness: 10,
|
||||
big5_conscientiousness: 10,
|
||||
big5_extraversion: 10,
|
||||
big5_neuroticism: 10,
|
||||
}
|
||||
|
||||
export const jamesUser: User = {
|
||||
@@ -226,4 +231,9 @@ export const jamesProfile: ProfileRow = {
|
||||
search_text: 'the futa in futarchy',
|
||||
search_tsv: 'the futa in futarchy',
|
||||
age: 32,
|
||||
big5_agreeableness: 10,
|
||||
big5_openness: 10,
|
||||
big5_conscientiousness: 10,
|
||||
big5_extraversion: 10,
|
||||
big5_neuroticism: 10,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
ALTER TABLE profiles
|
||||
ADD COLUMN big5_openness SMALLINT,
|
||||
ADD COLUMN big5_conscientiousness SMALLINT,
|
||||
ADD COLUMN big5_extraversion SMALLINT,
|
||||
ADD COLUMN big5_agreeableness SMALLINT,
|
||||
ADD COLUMN big5_neuroticism SMALLINT;
|
||||
|
||||
|
||||
ALTER TABLE profiles
|
||||
ADD CONSTRAINT big5_range_check
|
||||
CHECK (
|
||||
big5_openness BETWEEN 0 AND 100 AND
|
||||
big5_conscientiousness BETWEEN 0 AND 100 AND
|
||||
big5_extraversion BETWEEN 0 AND 100 AND
|
||||
big5_agreeableness BETWEEN 0 AND 100 AND
|
||||
big5_neuroticism BETWEEN 0 AND 100
|
||||
);
|
||||
|
||||
CREATE INDEX profiles_big5_open_idx ON profiles (big5_openness);
|
||||
CREATE INDEX profiles_big5_cons_idx ON profiles (big5_conscientiousness);
|
||||
CREATE INDEX profiles_big5_extra_idx ON profiles (big5_extraversion);
|
||||
CREATE INDEX profiles_big5_agree_idx ON profiles (big5_agreeableness);
|
||||
CREATE INDEX profiles_big5_neur_idx ON profiles (big5_neuroticism);
|
||||
@@ -509,6 +509,16 @@ export const API = (_apiTypeCheck = {
|
||||
pref_age_max: z.coerce.number().optional(),
|
||||
drinks_min: z.coerce.number().optional(),
|
||||
drinks_max: z.coerce.number().optional(),
|
||||
big5_openness_min: z.coerce.number().optional(),
|
||||
big5_openness_max: z.coerce.number().optional(),
|
||||
big5_conscientiousness_min: z.coerce.number().optional(),
|
||||
big5_conscientiousness_max: z.coerce.number().optional(),
|
||||
big5_extraversion_min: z.coerce.number().optional(),
|
||||
big5_extraversion_max: z.coerce.number().optional(),
|
||||
big5_agreeableness_min: z.coerce.number().optional(),
|
||||
big5_agreeableness_max: z.coerce.number().optional(),
|
||||
big5_neuroticism_min: z.coerce.number().optional(),
|
||||
big5_neuroticism_max: z.coerce.number().optional(),
|
||||
religion: arraybeSchema.optional(),
|
||||
pref_relation_styles: arraybeSchema.optional(),
|
||||
pref_romantic_styles: arraybeSchema.optional(),
|
||||
|
||||
@@ -56,6 +56,7 @@ export const baseProfilesSchema = z.object({
|
||||
country: z.string().optional().nullable(),
|
||||
gender: genderType,
|
||||
geodb_city_id: z.string().optional().nullable(),
|
||||
languages: z.array(z.string()).optional().nullable(),
|
||||
looking_for_matches: zBoolean,
|
||||
photo_urls: z.array(z.string()).nullable(),
|
||||
pinned_url: z.string(),
|
||||
@@ -67,13 +68,18 @@ export const baseProfilesSchema = z.object({
|
||||
region_code: z.string().optional().nullable(),
|
||||
visibility: z.union([z.literal('public'), z.literal('member')]),
|
||||
wants_kids_strength: z.number().nullable(),
|
||||
languages: z.array(z.string()).optional().nullable(),
|
||||
})
|
||||
|
||||
const optionalProfilesSchema = z.object({
|
||||
avatar_url: z.string().optional().nullable(),
|
||||
bio: contentSchema.optional().nullable(),
|
||||
big5_openness: z.number().min(0).max(100).optional().nullable(),
|
||||
big5_conscientiousness: z.number().min(0).max(100).optional().nullable(),
|
||||
big5_extraversion: z.number().min(0).max(100).optional().nullable(),
|
||||
big5_agreeableness: z.number().min(0).max(100).optional().nullable(),
|
||||
big5_neuroticism: z.number().min(0).max(100).optional().nullable(),
|
||||
born_in_location: z.string().optional().nullable(),
|
||||
causes: z.array(z.string()).optional().nullable(),
|
||||
comments_enabled: zBoolean.optional(),
|
||||
company: z.string().optional().nullable(),
|
||||
diet: z.array(z.string()).optional().nullable(),
|
||||
@@ -82,29 +88,27 @@ const optionalProfilesSchema = z.object({
|
||||
drinks_min: z.number().min(0).optional().nullable(),
|
||||
drinks_per_month: z.number().min(0).optional().nullable(),
|
||||
education_level: z.string().optional().nullable(),
|
||||
mbti: z.string().optional().nullable(),
|
||||
ethnicity: z.array(z.string()).optional().nullable(),
|
||||
has_kids: z.number().min(0).optional().nullable(),
|
||||
has_pets: zBoolean.optional().nullable(),
|
||||
height_in_inches: z.number().optional().nullable(),
|
||||
image_descriptions: z.any().optional().nullable(),
|
||||
interests: z.array(z.string()).optional().nullable(),
|
||||
is_smoker: zBoolean.optional().nullable(),
|
||||
mbti: z.string().optional().nullable(),
|
||||
occupation: z.string().optional().nullable(),
|
||||
occupation_title: z.string().optional().nullable(),
|
||||
political_beliefs: z.array(z.string()).optional().nullable(),
|
||||
interests: z.array(z.string()).optional().nullable(),
|
||||
work: z.array(z.string()).optional().nullable(),
|
||||
causes: z.array(z.string()).optional().nullable(),
|
||||
relationship_status: z.array(z.string()).optional().nullable(),
|
||||
political_details: z.string().optional().nullable(),
|
||||
pref_romantic_styles: z.array(z.string()).nullable(),
|
||||
relationship_status: z.array(z.string()).optional().nullable(),
|
||||
religion: z.array(z.string()).optional().nullable(),
|
||||
religious_belief_strength: z.number().optional().nullable(),
|
||||
religious_beliefs: z.string().optional().nullable(),
|
||||
twitter: z.string().optional().nullable(),
|
||||
university: z.string().optional().nullable(),
|
||||
website: z.string().optional().nullable(),
|
||||
image_descriptions: z.any().optional().nullable(),
|
||||
work: z.array(z.string()).optional().nullable(),
|
||||
})
|
||||
|
||||
export const combinedProfileSchema =
|
||||
baseProfilesSchema.merge(optionalProfilesSchema)
|
||||
export const combinedProfileSchema = baseProfilesSchema.merge(optionalProfilesSchema)
|
||||
|
||||
@@ -22,6 +22,17 @@ export type FilterFields = {
|
||||
shortBio: boolean | undefined
|
||||
drinks_min: number | undefined
|
||||
drinks_max: number | undefined
|
||||
// Big Five personality filters (0-100 range)
|
||||
big5_openness_min: number | undefined
|
||||
big5_openness_max: number | undefined
|
||||
big5_conscientiousness_min: number | undefined
|
||||
big5_conscientiousness_max: number | undefined
|
||||
big5_extraversion_min: number | undefined
|
||||
big5_extraversion_max: number | undefined
|
||||
big5_agreeableness_min: number | undefined
|
||||
big5_agreeableness_max: number | undefined
|
||||
big5_neuroticism_min: number | undefined
|
||||
big5_neuroticism_max: number | undefined
|
||||
} & {
|
||||
[K in OptionTableKey]: string[]
|
||||
}
|
||||
@@ -89,6 +100,16 @@ export const initialFilters: Partial<FilterFields> = {
|
||||
shortBio: undefined,
|
||||
drinks_min: undefined,
|
||||
drinks_max: undefined,
|
||||
big5_openness_min: undefined,
|
||||
big5_openness_max: undefined,
|
||||
big5_conscientiousness_min: undefined,
|
||||
big5_conscientiousness_max: undefined,
|
||||
big5_extraversion_min: undefined,
|
||||
big5_extraversion_max: undefined,
|
||||
big5_agreeableness_min: undefined,
|
||||
big5_agreeableness_max: undefined,
|
||||
big5_neuroticism_min: undefined,
|
||||
big5_neuroticism_max: undefined,
|
||||
orderBy: 'created_time',
|
||||
}
|
||||
|
||||
|
||||
@@ -856,6 +856,11 @@ export type Database = {
|
||||
profiles: {
|
||||
Row: {
|
||||
age: number | null
|
||||
big5_agreeableness: number | null
|
||||
big5_conscientiousness: number | null
|
||||
big5_extraversion: number | null
|
||||
big5_neuroticism: number | null
|
||||
big5_openness: number | null
|
||||
bio: Json | null
|
||||
bio_length: number | null
|
||||
bio_text: string | null
|
||||
@@ -913,6 +918,11 @@ export type Database = {
|
||||
}
|
||||
Insert: {
|
||||
age?: number | null
|
||||
big5_agreeableness?: number | null
|
||||
big5_conscientiousness?: number | null
|
||||
big5_extraversion?: number | null
|
||||
big5_neuroticism?: number | null
|
||||
big5_openness?: number | null
|
||||
bio?: Json | null
|
||||
bio_length?: number | null
|
||||
bio_text?: string | null
|
||||
@@ -970,6 +980,11 @@ export type Database = {
|
||||
}
|
||||
Update: {
|
||||
age?: number | null
|
||||
big5_agreeableness?: number | null
|
||||
big5_conscientiousness?: number | null
|
||||
big5_extraversion?: number | null
|
||||
big5_neuroticism?: number | null
|
||||
big5_openness?: number | null
|
||||
bio?: Json | null
|
||||
bio_length?: number | null
|
||||
bio_text?: string | null
|
||||
@@ -1204,7 +1219,15 @@ export type Database = {
|
||||
ts?: string
|
||||
user_id?: string | null
|
||||
}
|
||||
Relationships: []
|
||||
Relationships: [
|
||||
{
|
||||
foreignKeyName: 'user_events_user_id_fkey'
|
||||
columns: ['user_id']
|
||||
isOneToOne: false
|
||||
referencedRelation: 'users'
|
||||
referencedColumns: ['id']
|
||||
},
|
||||
]
|
||||
}
|
||||
user_notifications: {
|
||||
Row: {
|
||||
|
||||
181
web/components/filters/big5-filter.tsx
Normal file
181
web/components/filters/big5-filter.tsx
Normal file
@@ -0,0 +1,181 @@
|
||||
import clsx from 'clsx'
|
||||
import {RangeSlider} from 'web/components/widgets/slider'
|
||||
import {FilterFields} from 'common/filters'
|
||||
import {useT} from 'web/lib/locale'
|
||||
|
||||
export const BIG5_MIN = 0
|
||||
export const BIG5_MAX = 100
|
||||
|
||||
export type Big5Key =
|
||||
| 'big5_openness'
|
||||
| 'big5_conscientiousness'
|
||||
| 'big5_extraversion'
|
||||
| 'big5_agreeableness'
|
||||
| 'big5_neuroticism'
|
||||
|
||||
export type Big5MinMaxKey =
|
||||
| 'big5_openness_min'
|
||||
| 'big5_openness_max'
|
||||
| 'big5_conscientiousness_min'
|
||||
| 'big5_conscientiousness_max'
|
||||
| 'big5_extraversion_min'
|
||||
| 'big5_extraversion_max'
|
||||
| 'big5_agreeableness_min'
|
||||
| 'big5_agreeableness_max'
|
||||
| 'big5_neuroticism_min'
|
||||
| 'big5_neuroticism_max'
|
||||
|
||||
export function hasAnyBig5Filter(filters: Partial<FilterFields>) {
|
||||
return (
|
||||
filters.big5_openness_min != null ||
|
||||
filters.big5_openness_max != null ||
|
||||
filters.big5_conscientiousness_min != null ||
|
||||
filters.big5_conscientiousness_max != null ||
|
||||
filters.big5_extraversion_min != null ||
|
||||
filters.big5_extraversion_max != null ||
|
||||
filters.big5_agreeableness_min != null ||
|
||||
filters.big5_agreeableness_max != null ||
|
||||
filters.big5_neuroticism_min != null ||
|
||||
filters.big5_neuroticism_max != null
|
||||
)
|
||||
}
|
||||
|
||||
export function Big5FilterText(props: {
|
||||
filters: Partial<FilterFields>
|
||||
highlightedClass?: string
|
||||
}) {
|
||||
const {filters, highlightedClass} = props
|
||||
const t = useT()
|
||||
const hasAny = hasAnyBig5Filter(filters)
|
||||
|
||||
if (!hasAny) {
|
||||
return (
|
||||
<span className={clsx(!hasAny && 'text-ink-600')}>
|
||||
{t('filter.big5.any', 'Any Big 5')}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<span className={clsx('font-semibold', highlightedClass)}>
|
||||
{t('filter.big5.custom', 'Custom Big 5')}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export function Big5SliderRow(props: {
|
||||
label: string
|
||||
minValue: number | null | undefined
|
||||
maxValue: number | null | undefined
|
||||
onChange: (min: number | undefined, max: number | undefined) => void
|
||||
}) {
|
||||
const {label, minValue, maxValue, onChange} = props
|
||||
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<div className="mb-1 flex items-center justify-between text-sm text-ink-600">
|
||||
<span>{label}</span>
|
||||
<span className="font-semibold text-ink-700">
|
||||
{minValue == null && maxValue == null
|
||||
? '0 – 100'
|
||||
: `${minValue ?? BIG5_MIN} – ${maxValue ?? BIG5_MAX}`}
|
||||
</span>
|
||||
</div>
|
||||
<RangeSlider
|
||||
lowValue={minValue ?? BIG5_MIN}
|
||||
highValue={maxValue ?? BIG5_MAX}
|
||||
min={BIG5_MIN}
|
||||
max={BIG5_MAX}
|
||||
setValues={(low, high) => {
|
||||
onChange(
|
||||
low > BIG5_MIN ? Math.round(low) : undefined,
|
||||
high < BIG5_MAX ? Math.round(high) : undefined
|
||||
)
|
||||
}}
|
||||
marks={[
|
||||
{value: 0, label: '0'},
|
||||
{value: 25, label: '25'},
|
||||
{value: 50, label: '50'},
|
||||
{value: 75, label: '75'},
|
||||
{value: 100, label: '100'},
|
||||
].map((m) => ({
|
||||
value: ((m.value - BIG5_MIN) / (BIG5_MAX - BIG5_MIN)) * 100,
|
||||
label: m.label,
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function Big5Filters(props: {
|
||||
filters: Partial<FilterFields>
|
||||
updateFilter: (newState: Partial<FilterFields>) => void
|
||||
}) {
|
||||
const {filters, updateFilter} = props
|
||||
const t = useT()
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-md py-2">
|
||||
<Big5SliderRow
|
||||
label={t('profile.big5_openness', 'Openness')}
|
||||
minValue={filters.big5_openness_min}
|
||||
maxValue={filters.big5_openness_max}
|
||||
onChange={(min, max) =>
|
||||
updateFilter({
|
||||
big5_openness_min: min,
|
||||
big5_openness_max: max,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Big5SliderRow
|
||||
label={t(
|
||||
'profile.big5_conscientiousness',
|
||||
'Conscientiousness'
|
||||
)}
|
||||
minValue={filters.big5_conscientiousness_min}
|
||||
maxValue={filters.big5_conscientiousness_max}
|
||||
onChange={(min, max) =>
|
||||
updateFilter({
|
||||
big5_conscientiousness_min: min,
|
||||
big5_conscientiousness_max: max,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Big5SliderRow
|
||||
label={t('profile.big5_extraversion', 'Extraversion')}
|
||||
minValue={filters.big5_extraversion_min}
|
||||
maxValue={filters.big5_extraversion_max}
|
||||
onChange={(min, max) =>
|
||||
updateFilter({
|
||||
big5_extraversion_min: min,
|
||||
big5_extraversion_max: max,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Big5SliderRow
|
||||
label={t('profile.big5_agreeableness', 'Agreeableness')}
|
||||
minValue={filters.big5_agreeableness_min}
|
||||
maxValue={filters.big5_agreeableness_max}
|
||||
onChange={(min, max) =>
|
||||
updateFilter({
|
||||
big5_agreeableness_min: min,
|
||||
big5_agreeableness_max: max,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Big5SliderRow
|
||||
label={t('profile.big5_neuroticism', 'Neuroticism')}
|
||||
minValue={filters.big5_neuroticism_min}
|
||||
maxValue={filters.big5_neuroticism_max}
|
||||
onChange={(min, max) =>
|
||||
updateFilter({
|
||||
big5_neuroticism_min: min,
|
||||
big5_neuroticism_max: max,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ import {InterestFilter, InterestFilterText} from "web/components/filters/interes
|
||||
import {OptionTableKey} from "common/profiles/constants";
|
||||
import {useT} from "web/lib/locale";
|
||||
import {ResetFiltersButton} from "web/components/searches/button";
|
||||
import {Big5Filters, Big5FilterText, hasAnyBig5Filter} from "web/components/filters/big5-filter";
|
||||
|
||||
export function DesktopFilters(props: {
|
||||
filters: Partial<FilterFields>
|
||||
@@ -646,6 +647,31 @@ export function DesktopFilters(props: {
|
||||
menuWidth="w-[400px]"
|
||||
/>
|
||||
|
||||
{/* BIG FIVE PERSONALITY */}
|
||||
<CustomizeableDropdown
|
||||
buttonContent={(open) => (
|
||||
<DropdownButton
|
||||
open={open}
|
||||
content={
|
||||
<Row className="items-center gap-1">
|
||||
<BsPersonVcard className="h-4 w-4"/>
|
||||
<Big5FilterText
|
||||
filters={filters}
|
||||
highlightedClass={open || hasAnyBig5Filter(filters) ? 'text-primary-500' : undefined}
|
||||
/>
|
||||
</Row>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
dropdownMenuContent={
|
||||
<Col className="mx-2 mb-4">
|
||||
<Big5Filters filters={filters} updateFilter={updateFilter}/>
|
||||
</Col>
|
||||
}
|
||||
popoverClassName="bg-canvas-50"
|
||||
menuWidth="w-[420px]"
|
||||
/>
|
||||
|
||||
{/* EDUCATION */}
|
||||
<CustomizeableDropdown
|
||||
buttonContent={(open: boolean) => (
|
||||
|
||||
@@ -43,6 +43,7 @@ import {LuCigarette, LuGraduationCap} from "react-icons/lu";
|
||||
import {RiScales3Line} from "react-icons/ri";
|
||||
import {PiHandsPrayingBold} from "react-icons/pi";
|
||||
import {ResetFiltersButton} from "web/components/searches/button";
|
||||
import {Big5Filters, Big5FilterText, hasAnyBig5Filter} from "web/components/filters/big5-filter";
|
||||
import {FilterGuide} from "web/components/guidance";
|
||||
|
||||
function MobileFilters(props: {
|
||||
@@ -534,6 +535,25 @@ function MobileFilters(props: {
|
||||
<MbtiFilter filters={filters} updateFilter={updateFilter}/>
|
||||
</MobileFilterSection>
|
||||
|
||||
{/* BIG FIVE PERSONALITY */}
|
||||
<MobileFilterSection
|
||||
title={t('profile.big5', 'Personality (Big Five)')}
|
||||
openFilter={openFilter}
|
||||
setOpenFilter={setOpenFilter}
|
||||
isActive={hasAnyBig5Filter(filters)}
|
||||
icon={<BsPersonVcard className="h-4 w-4"/>}
|
||||
selection={
|
||||
<Big5FilterText
|
||||
filters={filters}
|
||||
highlightedClass={
|
||||
hasAnyBig5Filter(filters) ? 'text-primary-600' : 'text-ink-900'
|
||||
}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Big5Filters filters={filters} updateFilter={updateFilter}/>
|
||||
</MobileFilterSection>
|
||||
|
||||
{/* EDUCATION */}
|
||||
<MobileFilterSection
|
||||
title={t("profile.education.short_name", "Education")}
|
||||
|
||||
@@ -47,6 +47,7 @@ import {AddOptionEntry} from "web/components/add-option-entry";
|
||||
import {sleep} from "common/util/time"
|
||||
import {SignupBio} from "web/components/bio/editable-bio";
|
||||
import {Editor} from "@tiptap/core";
|
||||
import {Slider} from "web/components/widgets/slider";
|
||||
|
||||
|
||||
export const OptionalProfileUserForm = (props: {
|
||||
@@ -644,6 +645,55 @@ export const OptionalProfileUserForm = (props: {
|
||||
/>
|
||||
</Col>
|
||||
|
||||
{/* Big Five personality traits (0–100) */}
|
||||
<Col className={clsx(colClassName, 'max-w-[550px]')}>
|
||||
<label className={clsx(labelClassName)}>
|
||||
{t('profile.big5', 'Big Five Personality Traits')}
|
||||
</label>
|
||||
<div className="space-y-4">
|
||||
<Big5Slider
|
||||
label={t('profile.big5_openness', 'Openness')}
|
||||
value={profile.big5_openness ?? 50}
|
||||
onChange={(v) => setProfile('big5_openness', v)}
|
||||
/>
|
||||
<Big5Slider
|
||||
label={t(
|
||||
'profile.big5_conscientiousness',
|
||||
'Conscientiousness'
|
||||
)}
|
||||
value={profile.big5_conscientiousness ?? 50}
|
||||
onChange={(v) => setProfile('big5_conscientiousness', v)}
|
||||
/>
|
||||
<Big5Slider
|
||||
label={t('profile.big5_extraversion', 'Extraversion')}
|
||||
value={profile.big5_extraversion ?? 50}
|
||||
onChange={(v) => setProfile('big5_extraversion', v)}
|
||||
/>
|
||||
<Big5Slider
|
||||
label={t(
|
||||
'profile.big5_agreeableness',
|
||||
'Agreeableness'
|
||||
)}
|
||||
value={profile.big5_agreeableness ?? 50}
|
||||
onChange={(v) => setProfile('big5_agreeableness', v)}
|
||||
/>
|
||||
<Big5Slider
|
||||
label={t(
|
||||
'profile.big5_neuroticism',
|
||||
'Neuroticism'
|
||||
)}
|
||||
value={profile.big5_neuroticism ?? 50}
|
||||
onChange={(v) => setProfile('big5_neuroticism', v)}
|
||||
/>
|
||||
<p className="text-sm text-ink-500">
|
||||
{t(
|
||||
'profile.big5_hint',
|
||||
'Drag each slider to set where you see yourself on these traits (0 = low, 100 = high).'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</Col>
|
||||
|
||||
<Category title={t('profile.optional.diet', 'Diet')}/>
|
||||
|
||||
<Col className={clsx(colClassName)}>
|
||||
@@ -900,3 +950,33 @@ function Category({title, className}: { title: string, className?: string }) {
|
||||
return <h3 className={clsx("text-xl font-semibold mb-[-8px]", className)}>{title}</h3>;
|
||||
}
|
||||
|
||||
const Big5Slider = (props: {
|
||||
label: string
|
||||
value: number
|
||||
onChange: (v: number) => void
|
||||
}) => {
|
||||
const {label, value, onChange} = props
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-1 flex items-center justify-between text-sm text-ink-600">
|
||||
<span>{label}</span>
|
||||
<span className="font-semibold text-ink-700">{Math.round(value)}</span>
|
||||
</div>
|
||||
<Slider
|
||||
amount={value}
|
||||
min={0}
|
||||
max={100}
|
||||
onChange={(v) => onChange(Math.round(v))}
|
||||
marks={[
|
||||
{value: 0, label: '0'},
|
||||
{value: 25, label: '25'},
|
||||
{value: 50, label: '50'},
|
||||
{value: 75, label: '75'},
|
||||
{value: 100, label: '100'},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ import {FaBriefcase, FaHandsHelping, FaHeart, FaStar} from "react-icons/fa";
|
||||
import {useLocale, useT} from "web/lib/locale";
|
||||
import {useChoices} from "web/hooks/use-choices";
|
||||
import {getSeekingGenderText} from "web/lib/profile/seeking";
|
||||
import {TbBulb, TbCheck, TbMoodSad, TbUsers} from "react-icons/tb";
|
||||
|
||||
export function AboutRow(props: {
|
||||
icon: ReactNode
|
||||
@@ -119,6 +120,7 @@ export default function ProfileAbout(props: {
|
||||
icon={<BsPersonVcard className="h-5 w-5"/>}
|
||||
text={profile.mbti ? INVERTED_MBTI_CHOICES[profile.mbti] : null}
|
||||
/>
|
||||
<Big5Traits profile={profile}/>
|
||||
<AboutRow
|
||||
icon={<HiOutlineGlobe className="h-5 w-5"/>}
|
||||
text={profile.ethnicity
|
||||
@@ -340,6 +342,85 @@ function LastOnline(props: { lastOnlineTime?: string }) {
|
||||
)
|
||||
}
|
||||
|
||||
function Big5Traits(props: { profile: Profile }) {
|
||||
const t = useT()
|
||||
const {profile} = props
|
||||
|
||||
const traits = [
|
||||
{
|
||||
key: 'big5_openness',
|
||||
icon: <TbBulb className="h-5 w-5"/>,
|
||||
label: t('profile.big5_openness', 'Openness'),
|
||||
value: profile.big5_openness
|
||||
},
|
||||
{
|
||||
key: 'big5_conscientiousness',
|
||||
icon: <TbCheck className="h-5 w-5"/>,
|
||||
label: t('profile.big5_conscientiousness', 'Conscientiousness'),
|
||||
value: profile.big5_conscientiousness
|
||||
},
|
||||
{
|
||||
key: 'big5_extraversion',
|
||||
icon: <TbUsers className="h-5 w-5"/>,
|
||||
label: t('profile.big5_extraversion', 'Extraversion'),
|
||||
value: profile.big5_extraversion
|
||||
},
|
||||
{
|
||||
key: 'big5_agreeableness',
|
||||
icon: <FaHeart className="h-5 w-5"/>,
|
||||
label: t('profile.big5_agreeableness', 'Agreeableness'),
|
||||
value: profile.big5_agreeableness
|
||||
},
|
||||
{
|
||||
key: 'big5_neuroticism',
|
||||
icon: <TbMoodSad className="h-5 w-5"/>,
|
||||
label: t('profile.big5_neuroticism', 'Neuroticism'),
|
||||
value: profile.big5_neuroticism
|
||||
}
|
||||
]
|
||||
|
||||
const hasAnyTraits = traits.some(trait => trait.value !== null && trait.value !== undefined)
|
||||
|
||||
if (!hasAnyTraits) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
return (
|
||||
<Col className="gap-2">
|
||||
<div className="text-ink-600 font-medium">
|
||||
{t('profile.big5', 'Big Five personality traits')}:
|
||||
</div>
|
||||
<div className="ml-6">
|
||||
{traits.map((trait) => {
|
||||
if (trait.value === null || trait.value === undefined) return null
|
||||
|
||||
let levelText: string
|
||||
if (trait.value <= 20) {
|
||||
levelText = t('profile.big5_very_low', 'Very low')
|
||||
} else if (trait.value <= 40) {
|
||||
levelText = t('profile.big5_low', 'Low')
|
||||
} else if (trait.value <= 60) {
|
||||
levelText = t('profile.big5_average', 'Average')
|
||||
} else if (trait.value <= 80) {
|
||||
levelText = t('profile.big5_high', 'High')
|
||||
} else {
|
||||
levelText = t('profile.big5_very_high', 'Very high')
|
||||
}
|
||||
|
||||
return (
|
||||
<Row key={trait.key} className="items-center gap-2">
|
||||
<div className="text-ink-600 w-5">{trait.icon}</div>
|
||||
<div>
|
||||
{trait.label}: {levelText} ({trait.value})
|
||||
</div>
|
||||
</Row>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</Col>
|
||||
)
|
||||
}
|
||||
|
||||
function HasKids(props: { profile: Profile }) {
|
||||
const t = useT()
|
||||
const {profile} = props
|
||||
|
||||
@@ -212,6 +212,8 @@
|
||||
"filter.any_relationship": "Jede romantische Beziehung",
|
||||
"filter.any_religion": "Jede Religion",
|
||||
"filter.any_work": "Jede Arbeit",
|
||||
"filter.big5.any": "Beliebige Big 5",
|
||||
"filter.big5.custom": "Benutzerdefinierte Big 5",
|
||||
"filter.gender.any": "Alle",
|
||||
"filter.gender.gender": "Geschlecht",
|
||||
"filter.gender.genders": "Geschlechter",
|
||||
@@ -647,6 +649,19 @@
|
||||
"profile.optional.username_or_url": "Benutzername oder URL",
|
||||
"profile.optional.want_kids": "Ich möchte Kinder haben",
|
||||
"profile.optional.work": "Arbeit",
|
||||
"profile.big5_openness": "Offenheit",
|
||||
"profile.big5_conscientiousness": "Gewissenhaftigkeit",
|
||||
"profile.big5_extraversion": "Extraversion",
|
||||
"profile.big5_agreeableness": "Verträglichkeit",
|
||||
"profile.big5_neuroticism": "Neurotizismus",
|
||||
"profile.big5": "Persönlichkeit (Big Five)",
|
||||
"profile.big5_hint": "Ziehen Sie jeden Schieber, um festzulegen, wo Sie sich bei diesen Merkmalen sehen (0 = niedrig, 100 = hoch).",
|
||||
"profile.big5_very_low": "Sehr niedrig",
|
||||
"profile.big5_low": "Niedrig",
|
||||
"profile.big5_average": "Durchschnittlich",
|
||||
"profile.big5_high": "Hoch",
|
||||
"profile.big5_very_high": "Sehr hoch",
|
||||
"profile.optional.big5_hint": "Ziehen Sie jeden Schieber, um festzulegen, wo Sie sich bei diesen Merkmalen sehen (0 = niedrig, 100 = hoch).",
|
||||
"profile.political.conservative": "Konservativ",
|
||||
"profile.political.e/acc": "Effektiver Akzelerationismus",
|
||||
"profile.political.green": "Grün / Ökosozialismus",
|
||||
|
||||
@@ -212,6 +212,8 @@
|
||||
"filter.any_relationship": "Toute relation amoureuse",
|
||||
"filter.any_religion": "Toute religion",
|
||||
"filter.any_work": "Tout travail",
|
||||
"filter.big5.any": "Tout Big 5",
|
||||
"filter.big5.custom": "Certains Big 5",
|
||||
"filter.gender.any": "Tout",
|
||||
"filter.gender.gender": "genre",
|
||||
"filter.gender.genders": "genres",
|
||||
@@ -647,6 +649,19 @@
|
||||
"profile.optional.username_or_url": "Nom d'utilisateur ou URL",
|
||||
"profile.optional.want_kids": "Je souhaite avoir des enfants",
|
||||
"profile.optional.work": "Domaine de travail",
|
||||
"profile.big5_openness": "Ouverture",
|
||||
"profile.big5_conscientiousness": "Conscienciosité",
|
||||
"profile.big5_extraversion": "Extraversion",
|
||||
"profile.big5_agreeableness": "Agréabilité",
|
||||
"profile.big5_neuroticism": "Névrosisme",
|
||||
"profile.big5": "Big Five",
|
||||
"profile.big5_hint": "Faites glisser chaque curseur pour définir où vous vous situez sur ces traits (0 = faible, 100 = élevé).",
|
||||
"profile.big5_very_low": "Très faible",
|
||||
"profile.big5_low": "Faible",
|
||||
"profile.big5_average": "Moyen",
|
||||
"profile.big5_high": "Élevé",
|
||||
"profile.big5_very_high": "Très élevé",
|
||||
"profile.optional.big5_hint": "Faites glisser chaque curseur pour définir où vous vous situez sur ces traits (0 = faible, 100 = élevé).",
|
||||
"profile.political.conservative": "Conservateur·trice",
|
||||
"profile.political.e/acc": "Accélérationnisme efficace",
|
||||
"profile.political.green": "Vert·e / Éco-socialiste",
|
||||
|
||||
Reference in New Issue
Block a user