mirror of
https://github.com/CompassConnections/Compass.git
synced 2026-08-02 03:11:45 -04:00
Translate filters
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
export const toKey = (str: string) => {
|
||||
return str.replace(/ /g, '_').toLowerCase()
|
||||
export const toKey = (str: string | number | boolean) => {
|
||||
return String(str).replace(/ /g, '_').toLowerCase()
|
||||
}
|
||||
@@ -146,7 +146,7 @@ const LoadedAboutSettings = (props: {
|
||||
const handleCopy = async () => {
|
||||
if (!diagnostics) return
|
||||
await navigator.clipboard.writeText(diagnosticsToText(diagnostics))
|
||||
setCopyFeedback(t('aboutsettings.copied', 'Copied!'))
|
||||
setCopyFeedback(t('about.settings.copied', 'Copied!'))
|
||||
setTimeout(() => {
|
||||
setCopyFeedback('')
|
||||
}, 2000)
|
||||
@@ -161,7 +161,7 @@ const LoadedAboutSettings = (props: {
|
||||
onClick={handleCopy}
|
||||
className="w-fit mt-4"
|
||||
>
|
||||
{copyFeedback || t('aboutsettings.copy_info', 'Copy Info')}
|
||||
{copyFeedback || t('about.settings.copy_info', 'Copy Info')}
|
||||
</Button>
|
||||
</Col>
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import clsx from 'clsx'
|
||||
import { RangeSlider } from 'web/components/widgets/slider'
|
||||
import {FilterFields} from "common/filters";
|
||||
import {RangeSlider} from 'web/components/widgets/slider'
|
||||
import {FilterFields} from "common/filters"
|
||||
import {useT} from 'web/lib/locale'
|
||||
|
||||
export const PREF_AGE_MIN = 18
|
||||
export const PREF_AGE_MAX = 100
|
||||
@@ -22,11 +23,12 @@ export function AgeFilterText(props: {
|
||||
const { pref_age_min, pref_age_max, highlightedClass } = props
|
||||
const [noMinAge, noMaxAge] = getNoMinMaxAge(pref_age_min, pref_age_max)
|
||||
|
||||
const t = useT()
|
||||
if (noMinAge && noMaxAge) {
|
||||
return (
|
||||
<span>
|
||||
<span className={clsx('text-semibold', highlightedClass)}>Any</span>{' '}
|
||||
<span className="hidden sm:inline">age</span>
|
||||
<span className={clsx('text-semibold', highlightedClass)}>{t('filter.age.any', 'Any')}</span>{' '}
|
||||
<span className="hidden sm:inline">{t('filter.age.age', 'age')}</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@@ -37,7 +39,7 @@ export function AgeFilterText(props: {
|
||||
{'<'}
|
||||
{pref_age_max}
|
||||
</span>{' '}
|
||||
years
|
||||
{t('filter.age.years', 'years')}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@@ -48,7 +50,7 @@ export function AgeFilterText(props: {
|
||||
{'>'}
|
||||
{pref_age_min}
|
||||
</span>{' '}
|
||||
years
|
||||
{t('filter.age.years', 'years')}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@@ -59,7 +61,7 @@ export function AgeFilterText(props: {
|
||||
{' - '}
|
||||
{pref_age_max}
|
||||
</span>{' '}
|
||||
years
|
||||
{t('filter.age.years', 'years')}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -225,6 +225,12 @@ export const GENDERS = {
|
||||
Other: 'other',
|
||||
}
|
||||
|
||||
export const GENDERS_PLURAL = {
|
||||
Women: 'female',
|
||||
Men: 'male',
|
||||
Other: 'other',
|
||||
}
|
||||
|
||||
export const INVERTED_RELATIONSHIP_CHOICES = invert(RELATIONSHIP_CHOICES)
|
||||
export const INVERTED_RELATIONSHIP_STATUS_CHOICES = invert(RELATIONSHIP_STATUS_CHOICES)
|
||||
export const INVERTED_ROMANTIC_CHOICES = invert(ROMANTIC_CHOICES)
|
||||
|
||||
@@ -16,7 +16,7 @@ import {FilterFields} from "common/filters";
|
||||
import {ShortBioToggle} from "web/components/filters/short-bio-toggle";
|
||||
import {PrefGenderFilter, PrefGenderFilterText} from "web/components/filters/pref-gender-filter";
|
||||
import DropdownMenu from "web/components/comments/dropdown-menu";
|
||||
import {KidsLabel, wantsKidsLabelsWithIcon} from "web/components/filters/wants-kids-filter";
|
||||
import {KidsLabel, useWantsKidsLabelsWithIcon} from "web/components/filters/wants-kids-filter";
|
||||
import {hasKidsLabels} from "common/has-kids";
|
||||
import {HasKidsLabel} from "web/components/filters/has-kids-filter";
|
||||
import {RomanticFilter, RomanticFilterText} from "web/components/filters/romantic-filter";
|
||||
@@ -27,7 +27,7 @@ import {GiFruitBowl} from "react-icons/gi";
|
||||
import {RiScales3Line} from "react-icons/ri";
|
||||
import {EducationFilter, EducationFilterText} from "web/components/filters/education-filter";
|
||||
import {MbtiFilter, MbtiFilterText} from "web/components/filters/mbti-filter";
|
||||
import {BsPersonVcard} from "react-icons/bs";
|
||||
import {BsPersonHeart, BsPersonVcard} from "react-icons/bs";
|
||||
import {LuCigarette, LuGraduationCap} from "react-icons/lu";
|
||||
import {DrinksFilter, DrinksFilterText} from "web/components/filters/drinks-filter";
|
||||
import {MdLanguage, MdLocalBar} from 'react-icons/md'
|
||||
@@ -39,9 +39,9 @@ import {
|
||||
RelationshipStatusFilter,
|
||||
RelationshipStatusFilterText
|
||||
} from "web/components/filters/relationship-status-filter";
|
||||
import {BsPersonHeart} from "react-icons/bs";
|
||||
import {InterestFilter, InterestFilterText} from "web/components/filters/interest-filter";
|
||||
import {OptionTableKey} from "common/profiles/constants";
|
||||
import {useT} from "web/lib/locale";
|
||||
|
||||
export function DesktopFilters(props: {
|
||||
filters: Partial<FilterFields>
|
||||
@@ -66,6 +66,9 @@ export function DesktopFilters(props: {
|
||||
choices,
|
||||
} = props
|
||||
|
||||
const t = useT()
|
||||
const wantsKidsLabelsWithIcon = useWantsKidsLabelsWithIcon()
|
||||
|
||||
return (
|
||||
<>
|
||||
<MyMatchesToggle
|
||||
@@ -116,7 +119,7 @@ export function DesktopFilters(props: {
|
||||
| string[]
|
||||
| undefined
|
||||
}
|
||||
defaultLabel={'Any relationship status'}
|
||||
defaultLabel={t('filter.relationship_status.any', 'Any relationship status')}
|
||||
highlightedClass={open ? 'text-primary-500' : undefined}
|
||||
/>
|
||||
</Row>
|
||||
@@ -439,7 +442,7 @@ export function DesktopFilters(props: {
|
||||
| undefined
|
||||
}
|
||||
highlightedClass={open ? 'text-primary-500' : undefined}
|
||||
label={'interest'}
|
||||
label={'interests'}
|
||||
/>
|
||||
</Row>
|
||||
}
|
||||
@@ -474,7 +477,7 @@ export function DesktopFilters(props: {
|
||||
| undefined
|
||||
}
|
||||
highlightedClass={open ? 'text-primary-500' : undefined}
|
||||
label={'cause'}
|
||||
label={'causes'}
|
||||
/>
|
||||
</Row>
|
||||
}
|
||||
@@ -596,7 +599,7 @@ export function DesktopFilters(props: {
|
||||
<MbtiFilterText
|
||||
options={filters.mbti as string[] | undefined}
|
||||
highlightedClass={open ? 'text-primary-500' : undefined}
|
||||
defaultLabel={'Any MBTI'}
|
||||
defaultLabel={t('filter.any_mbti', 'Any MBTI')}
|
||||
/>
|
||||
</Row>
|
||||
}
|
||||
@@ -656,6 +659,7 @@ export function DesktopFilters(props: {
|
||||
</Col>
|
||||
}
|
||||
popoverClassName="bg-canvas-50"
|
||||
menuWidth="w-50"
|
||||
/>
|
||||
|
||||
{/* Short Bios */}
|
||||
@@ -669,7 +673,7 @@ export function DesktopFilters(props: {
|
||||
className="text-ink-900 hover:text-primary-500 underline"
|
||||
onClick={clearFilters}
|
||||
>
|
||||
Reset filters
|
||||
{t('filter.reset', 'Reset filters')}
|
||||
</button>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -3,8 +3,9 @@ import {convertDietTypes, DietType,} from 'web/lib/util/convert-types'
|
||||
import stringOrStringArrayToText from 'web/lib/util/string-or-string-array-to-text'
|
||||
import {MultiCheckbox} from 'web/components/multi-checkbox'
|
||||
|
||||
import {DIET_CHOICES} from "web/components/filters/choices";
|
||||
import {FilterFields} from "common/filters";
|
||||
import {DIET_CHOICES} from 'web/components/filters/choices'
|
||||
import {FilterFields} from 'common/filters'
|
||||
import {useT} from 'web/lib/locale'
|
||||
|
||||
export function DietFilterText(props: {
|
||||
options: DietType[] | undefined
|
||||
@@ -13,9 +14,13 @@ export function DietFilterText(props: {
|
||||
const {options, highlightedClass} = props
|
||||
const length = (options ?? []).length
|
||||
|
||||
const t = useT()
|
||||
|
||||
if (!options || length < 1) {
|
||||
return (
|
||||
<span className={clsx('text-semibold', highlightedClass)}>Any diet</span>
|
||||
<span className={clsx('text-semibold', highlightedClass)}>
|
||||
{t('filter.any_diet', 'Any diet')}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -27,7 +32,7 @@ export function DietFilterText(props: {
|
||||
return (
|
||||
<span>
|
||||
<span className={clsx('font-semibold', highlightedClass)}>
|
||||
Multiple
|
||||
{t('filter.multiple', 'Multiple')}
|
||||
</span>
|
||||
</span>
|
||||
)
|
||||
@@ -53,6 +58,7 @@ export function DietFilter(props: {
|
||||
<MultiCheckbox
|
||||
selected={filters.diet ?? []}
|
||||
choices={DIET_CHOICES as any}
|
||||
translationPrefix={'profile.diet'}
|
||||
onChange={(c) => {
|
||||
updateFilter({diet: c})
|
||||
}}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import clsx from 'clsx'
|
||||
import {RangeSlider} from 'web/components/widgets/slider'
|
||||
import {FilterFields} from 'common/filters'
|
||||
import {useT} from 'web/lib/locale'
|
||||
|
||||
export const DRINKS_MIN = 0
|
||||
export const DRINKS_MAX = 20
|
||||
@@ -22,12 +23,13 @@ export function DrinksFilterText(props: {
|
||||
}) {
|
||||
const {drinks_min, drinks_max, highlightedClass} = props
|
||||
const [noMin, noMax] = getNoMinMaxDrinks(drinks_min, drinks_max)
|
||||
const t = useT()
|
||||
|
||||
|
||||
if (drinks_max === DRINKS_MIN) {
|
||||
return (
|
||||
<span className="font-semibold">
|
||||
<span className={clsx(highlightedClass)}>{drinks_max}</span> / mo
|
||||
<span className={clsx(highlightedClass)}>{drinks_max}</span> {t('common.per_month', '/ mo')}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@@ -35,8 +37,7 @@ export function DrinksFilterText(props: {
|
||||
if (noMin && noMax) {
|
||||
return (
|
||||
<span>
|
||||
<span className={clsx('text-semibold', highlightedClass)}>Any</span>{' '}
|
||||
<span className="hidden sm:inline">drinks</span>
|
||||
<span className="">{t('filter.any_drinks', 'Any drinks')}</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@@ -47,7 +48,7 @@ export function DrinksFilterText(props: {
|
||||
{' < '}
|
||||
{drinks_max}
|
||||
</span>{' '}
|
||||
/ mo
|
||||
{t('common.per_month', '/ mo')}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@@ -58,14 +59,14 @@ export function DrinksFilterText(props: {
|
||||
{' > '}
|
||||
{drinks_min}
|
||||
</span>{' '}
|
||||
/ mo
|
||||
{t('common.per_month', '/ mo')}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
if (drinks_min === drinks_max) {
|
||||
return (
|
||||
<span className="font-semibold">
|
||||
<span className={clsx(highlightedClass)}>{drinks_min}</span> / mo
|
||||
<span className={clsx(highlightedClass)}>{drinks_min}</span> {t('common.per_month', '/ mo')}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@@ -76,7 +77,7 @@ export function DrinksFilterText(props: {
|
||||
{' - '}
|
||||
{drinks_max}
|
||||
</span>{' '}
|
||||
/mo
|
||||
{t('common.per_month', '/ mo')}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ import {FilterFields} from "common/filters";
|
||||
import {EDUCATION_CHOICES} from "web/components/filters/choices";
|
||||
import {convertEducationTypes} from "web/lib/util/convert-types";
|
||||
import stringOrStringArrayToText from "web/lib/util/string-or-string-array-to-text";
|
||||
import {getSortedOptions} from "common/util/sorting";
|
||||
import {getSortedOptions} from 'common/util/sorting'
|
||||
import {useT} from 'web/lib/locale'
|
||||
import {toKey} from 'common/parsing'
|
||||
|
||||
export function EducationFilterText(props: {
|
||||
options: string[] | undefined
|
||||
@@ -14,14 +16,20 @@ export function EducationFilterText(props: {
|
||||
const {options, highlightedClass} = props
|
||||
const length = (options ?? []).length
|
||||
|
||||
const t = useT()
|
||||
|
||||
if (!options || length < 1) {
|
||||
return (
|
||||
<span className={clsx('text-semibold', highlightedClass)}>Any education</span>
|
||||
<span className={clsx('text-semibold', highlightedClass)}>
|
||||
{t('filter.any_education', 'Any education')}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
const sortedOptions = getSortedOptions(options, EDUCATION_CHOICES)
|
||||
const convertedTypes = sortedOptions.map((r) => convertEducationTypes(r as any))
|
||||
const convertedTypes = sortedOptions.map((r) =>
|
||||
t(`profile.education.${toKey(r)}`, convertEducationTypes(r as any))
|
||||
)
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -45,6 +53,7 @@ export function EducationFilter(props: {
|
||||
<MultiCheckbox
|
||||
selected={filters.education_levels ?? []}
|
||||
choices={EDUCATION_CHOICES}
|
||||
translationPrefix={'profile.education'}
|
||||
onChange={(c) => {
|
||||
updateFilter({education_levels: c})
|
||||
}}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import clsx from 'clsx'
|
||||
import GenderIcon from '../gender-icon'
|
||||
import { Gender } from 'common/gender'
|
||||
import { Row } from 'web/components/layout/row'
|
||||
import { MultiCheckbox } from 'web/components/multi-checkbox'
|
||||
|
||||
import {Gender} from 'common/gender'
|
||||
import {Row} from 'web/components/layout/row'
|
||||
import {MultiCheckbox} from 'web/components/multi-checkbox'
|
||||
import {useT} from 'web/lib/locale'
|
||||
import {FilterFields} from "common/filters";
|
||||
import {GENDERS_PLURAL} from "web/components/filters/choices";
|
||||
|
||||
export function GenderFilterText(props: {
|
||||
gender: Gender[] | undefined
|
||||
@@ -12,11 +13,12 @@ export function GenderFilterText(props: {
|
||||
}) {
|
||||
const { gender, highlightedClass } = props
|
||||
|
||||
const t = useT()
|
||||
if (!gender || gender.length < 1) {
|
||||
return (
|
||||
<span>
|
||||
<span className={clsx('text-semibold', highlightedClass)}>Any</span>{' '}
|
||||
<span className="hidden sm:inline">gender</span>
|
||||
<span className={clsx('text-semibold', highlightedClass)}>{t('filter.gender.any', 'Any')}</span>{' '}
|
||||
<span className="hidden sm:inline">{t('filter.gender.gender', 'gender')}</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@@ -28,7 +30,7 @@ export function GenderFilterText(props: {
|
||||
)
|
||||
})}{' '}
|
||||
<span className="hidden sm:inline">
|
||||
gender{gender.length > 1 ? 's' : ''}
|
||||
{gender.length > 1 ? t('filter.gender.genders', 'genders') : t('filter.gender.gender', 'gender')}
|
||||
</span>
|
||||
</Row>
|
||||
)
|
||||
@@ -43,11 +45,8 @@ export function GenderFilter(props: {
|
||||
<>
|
||||
<MultiCheckbox
|
||||
selected={filters.genders ?? []}
|
||||
choices={{
|
||||
Women: 'female',
|
||||
Men: 'male',
|
||||
'Other': 'other',
|
||||
}}
|
||||
choices={GENDERS_PLURAL}
|
||||
translationPrefix={'profile.gender.plural'}
|
||||
onChange={(c) => {
|
||||
updateFilter({ genders: c })
|
||||
}}
|
||||
|
||||
@@ -2,8 +2,9 @@ import clsx from 'clsx'
|
||||
import {Row} from 'web/components/layout/row'
|
||||
import {ChoicesToggleGroup} from 'web/components/widgets/choices-toggle-group'
|
||||
import {FaChild} from 'react-icons/fa6'
|
||||
import {FilterFields} from "common/filters";
|
||||
import {generateChoicesMap, hasKidsLabels} from "common/has-kids";
|
||||
import {FilterFields} from 'common/filters'
|
||||
import {generateChoicesMap, hasKidsLabels} from 'common/has-kids'
|
||||
import {useT} from 'web/lib/locale'
|
||||
|
||||
|
||||
export function HasKidsLabel(props: {
|
||||
@@ -12,23 +13,26 @@ export function HasKidsLabel(props: {
|
||||
mobile?: boolean
|
||||
}) {
|
||||
const {has_kids, highlightedClass, mobile} = props
|
||||
const t = useT()
|
||||
|
||||
// Get the appropriate label based on has_kids value
|
||||
let labelKey = 'no_preference'
|
||||
let labelValue = hasKidsLabels.no_preference.name
|
||||
|
||||
if (has_kids === hasKidsLabels.has_kids.value) {
|
||||
labelKey = 'has_kids'
|
||||
labelValue = mobile ? hasKidsLabels.has_kids.shortName : hasKidsLabels.has_kids.name
|
||||
} else if (has_kids === hasKidsLabels.doesnt_have_kids.value) {
|
||||
labelKey = 'doesnt_have_kids'
|
||||
labelValue = mobile ? hasKidsLabels.doesnt_have_kids.shortName : hasKidsLabels.doesnt_have_kids.name
|
||||
}
|
||||
return (
|
||||
<Row className="items-center gap-0.5">
|
||||
<FaChild className="hidden h-4 w-4 sm:inline"/>
|
||||
<span
|
||||
className={clsx(highlightedClass, has_kids !== -1 && 'font-semibold')}
|
||||
>
|
||||
{has_kids == hasKidsLabels.has_kids.value
|
||||
? mobile
|
||||
? hasKidsLabels.has_kids.shortName
|
||||
: hasKidsLabels.has_kids.name
|
||||
: has_kids == hasKidsLabels.doesnt_have_kids.value
|
||||
? mobile
|
||||
? hasKidsLabels.doesnt_have_kids.shortName
|
||||
: hasKidsLabels.doesnt_have_kids.name
|
||||
: mobile
|
||||
? hasKidsLabels.no_preference.shortName
|
||||
: hasKidsLabels.no_preference.name}
|
||||
{t(`profile.has_kids.${labelKey}`, labelValue)}
|
||||
</span>
|
||||
</Row>
|
||||
)
|
||||
@@ -43,6 +47,7 @@ export function HasKidsFilter(props: {
|
||||
<ChoicesToggleGroup
|
||||
currentChoice={filters.has_kids ?? 0}
|
||||
choicesMap={generateChoicesMap(hasKidsLabels)}
|
||||
translationPrefix="profile.has_kids"
|
||||
setChoice={(c) => updateFilter({has_kids: Number(c)})}
|
||||
toggleClassName="w-1/3 justify-center"
|
||||
/>
|
||||
|
||||
@@ -2,19 +2,24 @@ import clsx from 'clsx'
|
||||
import stringOrStringArrayToText from 'web/lib/util/string-or-string-array-to-text'
|
||||
import {MultiCheckbox} from 'web/components/multi-checkbox'
|
||||
import {FilterFields} from "common/filters";
|
||||
import {OptionTableKey} from "common/profiles/constants";
|
||||
import {OptionTableKey} from 'common/profiles/constants'
|
||||
import {useT} from 'web/lib/locale'
|
||||
import {toKey} from "common/parsing";
|
||||
|
||||
export function InterestFilterText(props: {
|
||||
options: string[] | undefined
|
||||
highlightedClass?: string
|
||||
label: string
|
||||
label: OptionTableKey
|
||||
}) {
|
||||
const {options, highlightedClass, label} = props
|
||||
const t = useT()
|
||||
const length = (options ?? []).length
|
||||
|
||||
if (!options || length < 1) {
|
||||
return (
|
||||
<span className={clsx('text-semibold', highlightedClass)}>Any {label}</span>
|
||||
<span className={clsx('text-semibold', highlightedClass)}>
|
||||
{t(`filter.any_${label}`, `Any ${label}`)}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -22,7 +27,7 @@ export function InterestFilterText(props: {
|
||||
return (
|
||||
<span>
|
||||
<span className={clsx('font-semibold', highlightedClass)}>
|
||||
Multiple
|
||||
{t('filter.multiple', 'Multiple')}
|
||||
</span>
|
||||
</span>
|
||||
)
|
||||
@@ -32,7 +37,7 @@ export function InterestFilterText(props: {
|
||||
<div>
|
||||
<span className={clsx('font-semibold', highlightedClass)}>
|
||||
{stringOrStringArrayToText({
|
||||
text: options,
|
||||
text: options.map((o) => t(`profile.${label}.${toKey(o)}`, o)),
|
||||
capitalizeFirstLetterOption: true,
|
||||
})}{' '}
|
||||
</span>
|
||||
@@ -51,6 +56,7 @@ export function InterestFilter(props: {
|
||||
<MultiCheckbox
|
||||
selected={filters[label] ?? []}
|
||||
choices={choices as any}
|
||||
translationPrefix={`profile.${label}`}
|
||||
onChange={(c) => {
|
||||
updateFilter({[label]: c})
|
||||
}}
|
||||
|
||||
@@ -3,9 +3,10 @@ import {convertLanguageTypes,} from 'web/lib/util/convert-types'
|
||||
import stringOrStringArrayToText from 'web/lib/util/string-or-string-array-to-text'
|
||||
import {MultiCheckbox} from 'web/components/multi-checkbox'
|
||||
|
||||
import {LANGUAGE_CHOICES} from "web/components/filters/choices";
|
||||
import {FilterFields} from "common/filters";
|
||||
import {getSortedOptions} from "common/util/sorting";
|
||||
import {LANGUAGE_CHOICES} from 'web/components/filters/choices'
|
||||
import {FilterFields} from 'common/filters'
|
||||
import {getSortedOptions} from 'common/util/sorting'
|
||||
import {useT} from 'web/lib/locale'
|
||||
|
||||
export function LanguageFilterText(props: {
|
||||
options: string[] | undefined
|
||||
@@ -14,9 +15,13 @@ export function LanguageFilterText(props: {
|
||||
const {options, highlightedClass} = props
|
||||
const length = (options ?? []).length
|
||||
|
||||
const t = useT()
|
||||
|
||||
if (!options || length < 1) {
|
||||
return (
|
||||
<span className={clsx('text-semibold', highlightedClass)}>Any language</span>
|
||||
<span className={clsx('text-semibold', highlightedClass)}>
|
||||
{t('filter.any_language', 'Any language')}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -24,14 +29,16 @@ export function LanguageFilterText(props: {
|
||||
return (
|
||||
<span>
|
||||
<span className={clsx('font-semibold', highlightedClass)}>
|
||||
Multiple
|
||||
{t('filter.multiple', 'Multiple')}
|
||||
</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
const sortedOptions = getSortedOptions(options, LANGUAGE_CHOICES)
|
||||
const convertedTypes = sortedOptions.map((r) => convertLanguageTypes(r as any))
|
||||
const convertedTypes = sortedOptions.map((r) =>
|
||||
t(`profile.language.${r}`, convertLanguageTypes(r as any))
|
||||
)
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -54,6 +61,7 @@ export function LanguageFilter(props: {
|
||||
<MultiCheckbox
|
||||
selected={filters.languages ?? []}
|
||||
choices={LANGUAGE_CHOICES as any}
|
||||
translationPrefix={'profile.language'}
|
||||
onChange={(c) => {
|
||||
updateFilter({languages: c})
|
||||
}}
|
||||
|
||||
@@ -3,10 +3,11 @@ import {Col} from 'web/components/layout/col'
|
||||
import {Slider} from 'web/components/widgets/slider'
|
||||
import {usePersistentInMemoryState} from 'web/hooks/use-persistent-in-memory-state'
|
||||
import {Row} from 'web/components/layout/row'
|
||||
import {City, CityRow, profileToCity, originToCity, useCitySearch,} from '../search-location'
|
||||
import {City, CityRow, originToCity, profileToCity, useCitySearch,} from '../search-location'
|
||||
import {Profile} from 'common/profiles/profile'
|
||||
import {useEffect, useState} from 'react'
|
||||
import {Input} from 'web/components/widgets/input'
|
||||
import {useT} from 'web/lib/locale'
|
||||
import {XIcon} from '@heroicons/react/solid'
|
||||
import {uniqBy} from 'lodash'
|
||||
import {buildArray} from 'common/util/array'
|
||||
@@ -20,11 +21,12 @@ export function LocationFilterText(props: {
|
||||
}) {
|
||||
const { location, radius, highlightedClass } = props
|
||||
|
||||
const t = useT()
|
||||
if (!location) {
|
||||
return (
|
||||
<span>
|
||||
<span className={clsx('text-semibold', highlightedClass)}>Any</span>
|
||||
<span className="hidden sm:inline"> location</span>
|
||||
<span className={clsx('text-semibold', highlightedClass)}>{t('filter.location.any', 'Any')}</span>
|
||||
<span className="hidden sm:inline"> {t('filter.location', 'location')}</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@@ -66,6 +68,8 @@ export function LocationFilter(props: {
|
||||
|
||||
const youCity = youProfile && profileToCity(youProfile)
|
||||
|
||||
const t = useT()
|
||||
|
||||
const [lastCity, setLastCity] = usePersistentInMemoryState<City>(
|
||||
location ? originToCity(location) : youCity || DEFAULT_LAST_CITY,
|
||||
'last-used-city'
|
||||
@@ -94,7 +98,7 @@ export function LocationFilter(props: {
|
||||
<Input
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder={'Search city...'}
|
||||
placeholder={t('filter.location.search_city', 'Search city...')}
|
||||
className="h-8 w-full rounded-none border-0 bg-transparent px-1 focus:border-b focus:ring-0 focus:ring-transparent"
|
||||
autoFocus
|
||||
// onBlur // TODO
|
||||
@@ -169,6 +173,7 @@ function LocationResults(props: {
|
||||
}
|
||||
}, [loading])
|
||||
|
||||
const t = useT()
|
||||
return (
|
||||
<Col className={className}>
|
||||
{showAny && (
|
||||
@@ -177,8 +182,8 @@ function LocationResults(props: {
|
||||
className="hover:bg-primary-200 hover:text-ink-950 cursor-pointer px-4 py-2 transition-colors"
|
||||
>
|
||||
<Row className="items-center gap-2">
|
||||
<XIcon className="h-4 w-4" />
|
||||
<span>Set to Any City</span>
|
||||
<XIcon className="h-4 w-4 text-ink-400" aria-label={t('common.close', 'Close')}/>
|
||||
<span>{t('filter.location.set_any_city', 'Set to Any City')}</span>
|
||||
</Row>
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -4,6 +4,7 @@ import {FilterFields} from 'common/filters'
|
||||
import {getSortedOptions} from 'common/util/sorting'
|
||||
import {MultiCheckbox} from 'web/components/multi-checkbox'
|
||||
import stringOrStringArrayToText from 'web/lib/util/string-or-string-array-to-text'
|
||||
import {useT} from 'web/lib/locale'
|
||||
|
||||
export function MbtiFilterText(props: {
|
||||
options: string[] | undefined
|
||||
@@ -11,9 +12,10 @@ export function MbtiFilterText(props: {
|
||||
defaultLabel?: string
|
||||
}) {
|
||||
const {options, highlightedClass, defaultLabel} = props
|
||||
const t = useT()
|
||||
const length = (options ?? []).length
|
||||
|
||||
const label = defaultLabel || 'Any'
|
||||
const label = defaultLabel || t('filter.any', 'Any')
|
||||
|
||||
if (!options || length < 1) {
|
||||
return (
|
||||
@@ -25,7 +27,7 @@ export function MbtiFilterText(props: {
|
||||
return (
|
||||
<span>
|
||||
<span className={clsx('font-semibold', highlightedClass)}>
|
||||
Multiple
|
||||
{t('filter.multiple', 'Multiple')}
|
||||
</span>
|
||||
</span>
|
||||
)
|
||||
@@ -57,6 +59,7 @@ export function MbtiFilter(props: {
|
||||
className={'grid grid-cols-2 xs:grid-cols-4'}
|
||||
selected={filters.mbti ?? []}
|
||||
choices={MBTI_CHOICES as any}
|
||||
translationPrefix={'profile.mbti'}
|
||||
onChange={(c) => {
|
||||
updateFilter({mbti: c})
|
||||
}}
|
||||
|
||||
@@ -34,6 +34,7 @@ import {MbtiFilter, MbtiFilterText} from "web/components/filters/mbti-filter";
|
||||
import {InterestFilter, InterestFilterText} from "web/components/filters/interest-filter";
|
||||
import {OptionTableKey} from "common/profiles/constants";
|
||||
import {NewBadge} from "web/components/new-badge";
|
||||
import {useT} from "web/lib/locale";
|
||||
|
||||
function MobileFilters(props: {
|
||||
filters: Partial<FilterFields>
|
||||
@@ -46,6 +47,7 @@ function MobileFilters(props: {
|
||||
includeRelationshipFilters: boolean | undefined
|
||||
choices: Record<OptionTableKey, Record<string, string[]>>
|
||||
}) {
|
||||
const t = useT()
|
||||
const {
|
||||
filters,
|
||||
youProfile,
|
||||
@@ -88,16 +90,16 @@ function MobileFilters(props: {
|
||||
/>
|
||||
</Col>
|
||||
<button
|
||||
className="text-ink-500 hover:text-primary-500 underline mt-4 mb-8 right absolute right-4"
|
||||
className="text-ink-900 hover:text-primary-500 underline"
|
||||
onClick={clearFilters}
|
||||
>
|
||||
Reset filters
|
||||
{t('filter.reset', 'Reset filters')}
|
||||
</button>
|
||||
</Row>
|
||||
|
||||
{/* RELATIONSHIP STYLE */}
|
||||
<MobileFilterSection
|
||||
title="Seeking"
|
||||
title={t('profile.seeking', "Seeking")}
|
||||
openFilter={openFilter}
|
||||
setOpenFilter={setOpenFilter}
|
||||
isActive={hasAny(filters.pref_relation_styles)}
|
||||
@@ -117,7 +119,7 @@ function MobileFilters(props: {
|
||||
|
||||
{/* Relationship Status */}
|
||||
<MobileFilterSection
|
||||
title="Relationship Status"
|
||||
title={t('profile.optional.relationship_status', 'Relationship status')}
|
||||
openFilter={openFilter}
|
||||
setOpenFilter={setOpenFilter}
|
||||
isActive={hasAny(filters.relationship_status || undefined)}
|
||||
@@ -137,7 +139,7 @@ function MobileFilters(props: {
|
||||
|
||||
{/* LOCATION */}
|
||||
<MobileFilterSection
|
||||
title="Location"
|
||||
title={t("profile.optional.location", "Location")}
|
||||
openFilter={openFilter}
|
||||
setOpenFilter={setOpenFilter}
|
||||
isActive={!!locationFilterProps.location}
|
||||
@@ -162,7 +164,7 @@ function MobileFilters(props: {
|
||||
|
||||
{/* AGE RANGE */}
|
||||
<MobileFilterSection
|
||||
title="Age"
|
||||
title={t("profile.optional.age", "Age")}
|
||||
openFilter={openFilter}
|
||||
setOpenFilter={setOpenFilter}
|
||||
childrenClassName={'pb-6'}
|
||||
@@ -182,7 +184,7 @@ function MobileFilters(props: {
|
||||
|
||||
{/* GENDER */}
|
||||
<MobileFilterSection
|
||||
title="Gender"
|
||||
title={t("profile.optional.gender", "Gender")}
|
||||
openFilter={openFilter}
|
||||
setOpenFilter={setOpenFilter}
|
||||
isActive={hasAny(filters.genders)}
|
||||
@@ -200,7 +202,7 @@ function MobileFilters(props: {
|
||||
|
||||
{/* PREFERRED GENDER */}
|
||||
<MobileFilterSection
|
||||
title="Gender they seek"
|
||||
title={t('filter.gender.they_seek', 'Gender they seek')}
|
||||
openFilter={openFilter}
|
||||
setOpenFilter={setOpenFilter}
|
||||
isActive={hasAny(filters.pref_gender)}
|
||||
@@ -220,7 +222,7 @@ function MobileFilters(props: {
|
||||
|
||||
{/* ROMANTIC STYLE */}
|
||||
<MobileFilterSection
|
||||
title="Style"
|
||||
title={t("profile.romantic.style", "Style")}
|
||||
openFilter={openFilter}
|
||||
setOpenFilter={setOpenFilter}
|
||||
isActive={hasAny(filters.pref_romantic_styles || undefined)}
|
||||
@@ -240,7 +242,7 @@ function MobileFilters(props: {
|
||||
|
||||
{/* WANTS KIDS */}
|
||||
<MobileFilterSection
|
||||
title="Wants kids"
|
||||
title={t('filter.wants_kids.wants_kids', 'Wants kids')}
|
||||
openFilter={openFilter}
|
||||
setOpenFilter={setOpenFilter}
|
||||
isActive={
|
||||
@@ -266,7 +268,7 @@ function MobileFilters(props: {
|
||||
|
||||
{/* HAS KIDS */}
|
||||
<MobileFilterSection
|
||||
title="Has kids"
|
||||
title={t("profile.has_kids", "Has kids")}
|
||||
openFilter={openFilter}
|
||||
setOpenFilter={setOpenFilter}
|
||||
isActive={filters.has_kids != null && filters.has_kids !== -1}
|
||||
@@ -290,7 +292,7 @@ function MobileFilters(props: {
|
||||
|
||||
{/* DIET */}
|
||||
<MobileFilterSection
|
||||
title="Diet"
|
||||
title={t("profile.optional.diet", "Diet")}
|
||||
openFilter={openFilter}
|
||||
setOpenFilter={setOpenFilter}
|
||||
isActive={hasAny(filters.diet || undefined)}
|
||||
@@ -310,7 +312,7 @@ function MobileFilters(props: {
|
||||
|
||||
{/* DRINKS PER MONTH */}
|
||||
<MobileFilterSection
|
||||
title="Drinks"
|
||||
title={t("profile.drinks", "Drinks")}
|
||||
openFilter={openFilter}
|
||||
setOpenFilter={setOpenFilter}
|
||||
isActive={(() => {
|
||||
@@ -333,7 +335,7 @@ function MobileFilters(props: {
|
||||
|
||||
{/* SMOKER */}
|
||||
<MobileFilterSection
|
||||
title="Smoker"
|
||||
title={t("profile.smokes", "Smoker")}
|
||||
openFilter={openFilter}
|
||||
setOpenFilter={setOpenFilter}
|
||||
isActive={filters.is_smoker != null}
|
||||
@@ -350,7 +352,7 @@ function MobileFilters(props: {
|
||||
|
||||
{/* LANGUAGES */}
|
||||
<MobileFilterSection
|
||||
title="Languages"
|
||||
title={t("profile.optional.languages", "Languages")}
|
||||
// className="col-span-full max-h-80 overflow-y-auto"
|
||||
openFilter={openFilter}
|
||||
setOpenFilter={setOpenFilter}
|
||||
@@ -373,7 +375,7 @@ function MobileFilters(props: {
|
||||
<MobileFilterSection
|
||||
showNewBadge
|
||||
newBadgeClassName={"-top-0 -left-0"}
|
||||
title="Interests"
|
||||
title={t("profile.optional.interests", "Interests")}
|
||||
openFilter={openFilter}
|
||||
setOpenFilter={setOpenFilter}
|
||||
isActive={hasAny(filters.interests || undefined)}
|
||||
@@ -401,7 +403,7 @@ function MobileFilters(props: {
|
||||
<MobileFilterSection
|
||||
showNewBadge
|
||||
newBadgeClassName={"-top-0 -left-0"}
|
||||
title="Causes"
|
||||
title={t("profile.optional.causes", "Causes")}
|
||||
openFilter={openFilter}
|
||||
setOpenFilter={setOpenFilter}
|
||||
isActive={hasAny(filters.causes || undefined)}
|
||||
@@ -429,7 +431,7 @@ function MobileFilters(props: {
|
||||
<MobileFilterSection
|
||||
showNewBadge
|
||||
newBadgeClassName={"-top-0 -left-0"}
|
||||
title="Work"
|
||||
title={t("profile.optional.work", "Work")}
|
||||
openFilter={openFilter}
|
||||
setOpenFilter={setOpenFilter}
|
||||
isActive={hasAny(filters.work || undefined)}
|
||||
@@ -455,7 +457,7 @@ function MobileFilters(props: {
|
||||
|
||||
{/* POLITICS */}
|
||||
<MobileFilterSection
|
||||
title="Politics"
|
||||
title={t("profile.optional.political_beliefs", "Politics")}
|
||||
openFilter={openFilter}
|
||||
setOpenFilter={setOpenFilter}
|
||||
isActive={hasAny(filters.political_beliefs || undefined)}
|
||||
@@ -475,7 +477,7 @@ function MobileFilters(props: {
|
||||
|
||||
{/* RELIGION */}
|
||||
<MobileFilterSection
|
||||
title="Religion"
|
||||
title={t("profile.optional.religious_beliefs", "Religion")}
|
||||
openFilter={openFilter}
|
||||
setOpenFilter={setOpenFilter}
|
||||
isActive={hasAny(filters.religion || undefined)}
|
||||
@@ -511,7 +513,7 @@ function MobileFilters(props: {
|
||||
|
||||
{/* EDUCATION */}
|
||||
<MobileFilterSection
|
||||
title="Education"
|
||||
title={t("profile.education.short_name", "Education")}
|
||||
openFilter={openFilter}
|
||||
setOpenFilter={setOpenFilter}
|
||||
isActive={hasAny(filters.education_levels)}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Row } from 'web/components/layout/row'
|
||||
import {Row} from 'web/components/layout/row'
|
||||
import clsx from 'clsx'
|
||||
import { Profile } from 'common/profiles/profile'
|
||||
import {Profile} from 'common/profiles/profile'
|
||||
import {useT} from "web/lib/locale";
|
||||
|
||||
export function MyMatchesToggle(props: {
|
||||
setYourFilters: (checked: boolean) => void
|
||||
@@ -13,7 +14,8 @@ export function MyMatchesToggle(props: {
|
||||
return <></>
|
||||
}
|
||||
|
||||
const label = 'Your filters'
|
||||
const t = useT()
|
||||
const label = t('filter.mine_toggle', 'Your filters')
|
||||
|
||||
return (
|
||||
<Row className={clsx('mr-2 items-center', on && 'font-semibold')}>
|
||||
|
||||
@@ -3,9 +3,11 @@ import {convertPoliticalTypes,} from 'web/lib/util/convert-types'
|
||||
import stringOrStringArrayToText from 'web/lib/util/string-or-string-array-to-text'
|
||||
import {MultiCheckbox} from 'web/components/multi-checkbox'
|
||||
|
||||
import {POLITICAL_CHOICES} from "web/components/filters/choices";
|
||||
import {FilterFields} from "common/filters";
|
||||
import {getSortedOptions} from "common/util/sorting";
|
||||
import {POLITICAL_CHOICES} from 'web/components/filters/choices'
|
||||
import {FilterFields} from 'common/filters'
|
||||
import {getSortedOptions} from 'common/util/sorting'
|
||||
import {useT} from 'web/lib/locale'
|
||||
import {toKey} from 'common/parsing'
|
||||
|
||||
export function PoliticalFilterText(props: {
|
||||
options: string[] | undefined
|
||||
@@ -14,9 +16,13 @@ export function PoliticalFilterText(props: {
|
||||
const {options, highlightedClass} = props
|
||||
const length = (options ?? []).length
|
||||
|
||||
const t = useT()
|
||||
|
||||
if (!options || length < 1) {
|
||||
return (
|
||||
<span className={clsx('text-semibold', highlightedClass)}>Any politics</span>
|
||||
<span className={clsx('text-semibold', highlightedClass)}>
|
||||
{t('filter.any_politics', 'Any politics')}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -24,14 +30,16 @@ export function PoliticalFilterText(props: {
|
||||
return (
|
||||
<span>
|
||||
<span className={clsx('font-semibold', highlightedClass)}>
|
||||
Multiple
|
||||
{t('filter.multiple', 'Multiple')}
|
||||
</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
const sortedOptions = getSortedOptions(options, POLITICAL_CHOICES)
|
||||
const convertedTypes = sortedOptions.map((r) => convertPoliticalTypes(r as any))
|
||||
const convertedTypes = sortedOptions.map((r) =>
|
||||
t(`profile.political.${toKey(r)}`, convertPoliticalTypes(r as any))
|
||||
)
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -54,6 +62,7 @@ export function PoliticalFilter(props: {
|
||||
<MultiCheckbox
|
||||
selected={filters.political_beliefs ?? []}
|
||||
choices={POLITICAL_CHOICES as any}
|
||||
translationPrefix={'profile.political'}
|
||||
onChange={(c) => {
|
||||
updateFilter({political_beliefs: c})
|
||||
}}
|
||||
|
||||
@@ -1,35 +1,37 @@
|
||||
import clsx from 'clsx'
|
||||
import GenderIcon from '../gender-icon'
|
||||
import { Gender } from 'common/gender'
|
||||
import { Row } from 'web/components/layout/row'
|
||||
import { MultiCheckbox } from 'web/components/multi-checkbox'
|
||||
|
||||
import {FilterFields} from "common/filters";
|
||||
import {Gender} from 'common/gender'
|
||||
import {Row} from 'web/components/layout/row'
|
||||
import {MultiCheckbox} from 'web/components/multi-checkbox'
|
||||
import {FilterFields} from 'common/filters'
|
||||
import {useT} from 'web/lib/locale'
|
||||
import {GENDERS_PLURAL} from "web/components/filters/choices";
|
||||
|
||||
export function PrefGenderFilterText(props: {
|
||||
pref_gender: Gender[] | undefined
|
||||
highlightedClass?: string
|
||||
}) {
|
||||
const { pref_gender, highlightedClass } = props
|
||||
const {pref_gender, highlightedClass} = props
|
||||
const t = useT()
|
||||
|
||||
if (!pref_gender || pref_gender.length < 1) {
|
||||
return (
|
||||
<span>
|
||||
<span className="hidden sm:inline">Gender they seek: </span>
|
||||
<span className="hidden sm:inline">{t('filter.gender.they_seek', 'Gender they seek')}: </span>
|
||||
<span
|
||||
className={clsx(
|
||||
'text-semibold capitalize sm:normal-case',
|
||||
highlightedClass
|
||||
)}
|
||||
>
|
||||
any
|
||||
{t('filter.any', 'any')}
|
||||
</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Row className="items-center gap-0.5 font-semibold">
|
||||
<span className="hidden sm:inline">Gender they seek: </span>
|
||||
<span className="hidden sm:inline">{t('filter.gender.they_seek', 'Gender they seek')}: </span>
|
||||
{pref_gender.map((gender) => {
|
||||
return (
|
||||
<GenderIcon
|
||||
@@ -48,17 +50,14 @@ export function PrefGenderFilter(props: {
|
||||
filters: Partial<FilterFields>
|
||||
updateFilter: (newState: Partial<FilterFields>) => void
|
||||
}) {
|
||||
const { filters, updateFilter } = props
|
||||
const {filters, updateFilter} = props
|
||||
return (
|
||||
<MultiCheckbox
|
||||
selected={filters.pref_gender ?? []}
|
||||
choices={{
|
||||
Women: 'female',
|
||||
Men: 'male',
|
||||
Other: 'other',
|
||||
}}
|
||||
choices={GENDERS_PLURAL}
|
||||
translationPrefix={'profile.gender.plural'}
|
||||
onChange={(c) => {
|
||||
updateFilter({ pref_gender: c })
|
||||
updateFilter({pref_gender: c})
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@ import clsx from 'clsx'
|
||||
import {convertRelationshipType, RelationshipType,} from 'web/lib/util/convert-types'
|
||||
import stringOrStringArrayToText from 'web/lib/util/string-or-string-array-to-text'
|
||||
import {MultiCheckbox} from 'web/components/multi-checkbox'
|
||||
import {useT} from 'web/lib/locale'
|
||||
|
||||
import {RELATIONSHIP_CHOICES} from "web/components/filters/choices";
|
||||
import {FilterFields} from "common/filters";
|
||||
@@ -13,21 +14,24 @@ export function RelationshipFilterText(props: {
|
||||
const {relationship, highlightedClass} = props
|
||||
const relationshipLength = (relationship ?? []).length
|
||||
|
||||
const t = useT()
|
||||
if (!relationship || relationshipLength < 1) {
|
||||
return (
|
||||
<span className={clsx('text-semibold', highlightedClass)}>Any connection</span>
|
||||
<span className={clsx('text-semibold', highlightedClass)}>
|
||||
{t('filter.relationship.any_connection', 'Any connection')}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
const convertedRelationships = relationship.map((r) =>
|
||||
convertRelationshipType(r)
|
||||
t(`profile.relationship.${r}`, convertRelationshipType(r))
|
||||
)
|
||||
|
||||
if (relationshipLength > 1) {
|
||||
return (
|
||||
<span>
|
||||
<span className={clsx('font-semibold', highlightedClass)}>
|
||||
Multiple
|
||||
{t('filter.multiple', 'Multiple')}
|
||||
</span>
|
||||
</span>
|
||||
)
|
||||
@@ -53,6 +57,7 @@ export function RelationshipFilter(props: {
|
||||
<MultiCheckbox
|
||||
selected={filters.pref_relation_styles ?? []}
|
||||
choices={RELATIONSHIP_CHOICES as any}
|
||||
translationPrefix={'profile.relationship'}
|
||||
onChange={(c) => {
|
||||
updateFilter({pref_relation_styles: c})
|
||||
}}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {MultiCheckbox} from 'web/components/multi-checkbox'
|
||||
import {RELATIONSHIP_STATUS_CHOICES} from "web/components/filters/choices"
|
||||
import {FilterFields} from "common/filters"
|
||||
import {getSortedOptions} from "common/util/sorting"
|
||||
import {useT} from 'web/lib/locale'
|
||||
|
||||
export function RelationshipStatusFilterText(props: {
|
||||
options: string[] | undefined
|
||||
@@ -13,9 +14,10 @@ export function RelationshipStatusFilterText(props: {
|
||||
defaultLabel?: string
|
||||
}) {
|
||||
const {options, highlightedClass, defaultLabel} = props
|
||||
const t = useT()
|
||||
const length = (options ?? []).length
|
||||
|
||||
const label = defaultLabel || 'Any'
|
||||
const label = defaultLabel || t('filter.any', 'Any')
|
||||
|
||||
if (!options || length < 1) {
|
||||
return (
|
||||
@@ -27,14 +29,16 @@ export function RelationshipStatusFilterText(props: {
|
||||
return (
|
||||
<span>
|
||||
<span className={clsx('font-semibold', highlightedClass)}>
|
||||
Multiple
|
||||
{t('filter.multiple', 'Multiple')}
|
||||
</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
const sortedOptions = getSortedOptions(options, RELATIONSHIP_STATUS_CHOICES)
|
||||
const convertedTypes = sortedOptions.map((r) => convertRelationshipStatusTypes(r as any))
|
||||
const convertedTypes = sortedOptions.map((r) =>
|
||||
t(`profile.relationship_status.${r}`, convertRelationshipStatusTypes(r))
|
||||
)
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -57,6 +61,7 @@ export function RelationshipStatusFilter(props: {
|
||||
<MultiCheckbox
|
||||
selected={filters.relationship_status ?? []}
|
||||
choices={RELATIONSHIP_STATUS_CHOICES as any}
|
||||
translationPrefix={'profile.relationship_status'}
|
||||
onChange={(c) => {
|
||||
updateFilter({relationship_status: c})
|
||||
}}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import clsx from 'clsx'
|
||||
import {MultiCheckbox} from 'web/components/multi-checkbox'
|
||||
|
||||
import {FilterFields} from "common/filters";
|
||||
import {RELIGION_CHOICES} from "web/components/filters/choices";
|
||||
import {convertReligionTypes} from "web/lib/util/convert-types";
|
||||
import stringOrStringArrayToText from "web/lib/util/string-or-string-array-to-text";
|
||||
import {getSortedOptions} from "common/util/sorting";
|
||||
import {FilterFields} from 'common/filters'
|
||||
import {RELIGION_CHOICES} from 'web/components/filters/choices'
|
||||
import {convertReligionTypes} from 'web/lib/util/convert-types'
|
||||
import stringOrStringArrayToText from 'web/lib/util/string-or-string-array-to-text'
|
||||
import {getSortedOptions} from 'common/util/sorting'
|
||||
import {useT} from 'web/lib/locale'
|
||||
import {toKey} from 'common/parsing'
|
||||
|
||||
|
||||
export function ReligionFilterText(props: {
|
||||
@@ -15,9 +17,13 @@ export function ReligionFilterText(props: {
|
||||
const {options, highlightedClass} = props
|
||||
const length = (options ?? []).length
|
||||
|
||||
const t = useT()
|
||||
|
||||
if (!options || length < 1) {
|
||||
return (
|
||||
<span className={clsx('text-semibold', highlightedClass)}>Any religion</span>
|
||||
<span className={clsx('text-semibold', highlightedClass)}>
|
||||
{t('filter.any_religion', 'Any religion')}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -25,14 +31,16 @@ export function ReligionFilterText(props: {
|
||||
return (
|
||||
<span>
|
||||
<span className={clsx('font-semibold', highlightedClass)}>
|
||||
Multiple
|
||||
{t('filter.multiple', 'Multiple')}
|
||||
</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
const sortedOptions = getSortedOptions(options, RELIGION_CHOICES)
|
||||
const convertedTypes = sortedOptions.map((r) => convertReligionTypes(r as any))
|
||||
const convertedTypes = sortedOptions.map((r) =>
|
||||
t(`profile.religion.${toKey(r)}`, convertReligionTypes(r as any))
|
||||
)
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -57,6 +65,7 @@ export function ReligionFilter(props: {
|
||||
<MultiCheckbox
|
||||
selected={filters.religion ?? []}
|
||||
choices={RELIGION_CHOICES}
|
||||
translationPrefix={'profile.religion'}
|
||||
onChange={(c) => {
|
||||
updateFilter({religion: c})
|
||||
}}
|
||||
|
||||
@@ -3,31 +3,36 @@ import {convertRomanticTypes, RomanticType,} from 'web/lib/util/convert-types'
|
||||
import stringOrStringArrayToText from 'web/lib/util/string-or-string-array-to-text'
|
||||
import {MultiCheckbox} from 'web/components/multi-checkbox'
|
||||
|
||||
import {ROMANTIC_CHOICES} from "web/components/filters/choices";
|
||||
import {FilterFields} from "common/filters";
|
||||
import {ROMANTIC_CHOICES} from 'web/components/filters/choices'
|
||||
import {FilterFields} from 'common/filters'
|
||||
import {useT} from 'web/lib/locale'
|
||||
import {toKey} from "common/parsing";
|
||||
|
||||
export function RomanticFilterText(props: {
|
||||
relationship: RomanticType[] | undefined
|
||||
highlightedClass?: string
|
||||
}) {
|
||||
const {relationship, highlightedClass} = props
|
||||
const t = useT()
|
||||
const length = (relationship ?? []).length
|
||||
|
||||
if (!relationship || length < 1) {
|
||||
return (
|
||||
<span className={clsx('text-semibold', highlightedClass)}>Any relationship</span>
|
||||
<span className={clsx('text-semibold', highlightedClass)}>
|
||||
{t('filter.any_relationship', 'Any relationship')}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
const convertedTypes = relationship.map((r) =>
|
||||
convertRomanticTypes(r)
|
||||
t(`profile.romantic.${toKey(r)}`, convertRomanticTypes(r))
|
||||
)
|
||||
|
||||
if (length > 1) {
|
||||
return (
|
||||
<span>
|
||||
<span className={clsx('font-semibold', highlightedClass)}>
|
||||
Multiple
|
||||
{t('filter.multiple', 'Multiple')}
|
||||
</span>
|
||||
</span>
|
||||
)
|
||||
@@ -53,6 +58,7 @@ export function RomanticFilter(props: {
|
||||
<MultiCheckbox
|
||||
selected={filters.pref_romantic_styles ?? []}
|
||||
choices={ROMANTIC_CHOICES as any}
|
||||
translationPrefix={'profile.romantic'}
|
||||
onChange={(c) => {
|
||||
updateFilter({pref_romantic_styles: c})
|
||||
}}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {Row} from 'web/components/layout/row'
|
||||
import clsx from 'clsx'
|
||||
import {FilterFields} from "common/filters";
|
||||
import {useT} from "web/lib/locale";
|
||||
|
||||
export function ShortBioToggle(props: {
|
||||
filters: Partial<FilterFields>
|
||||
@@ -12,7 +13,8 @@ export function ShortBioToggle(props: {
|
||||
return <></>
|
||||
}
|
||||
|
||||
const label = 'Include Short Bios'
|
||||
const t = useT()
|
||||
const label = t('filter.short_bio_toggle', 'Include Short Bios')
|
||||
|
||||
const on = filters.shortBio || false
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ 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'
|
||||
import {useT} from 'web/lib/locale'
|
||||
|
||||
export function SmokerFilterText(props: {
|
||||
is_smoker: boolean | null | undefined
|
||||
@@ -9,14 +10,15 @@ export function SmokerFilterText(props: {
|
||||
mobile?: boolean
|
||||
}) {
|
||||
const {is_smoker, highlightedClass, mobile} = props
|
||||
const t = useT()
|
||||
return (
|
||||
<Row className="items-center gap-0.5">
|
||||
<span className={clsx(highlightedClass, is_smoker != null && 'font-semibold')}>
|
||||
{is_smoker == null
|
||||
? mobile ? 'Either' : 'Either'
|
||||
? t('common.either', 'Either')
|
||||
: is_smoker
|
||||
? mobile ? 'Yes' : 'Smoker'
|
||||
: mobile ? 'No' : "Non-smoker"}
|
||||
? mobile ? t('common.yes', 'Yes') : t('profile.smoker.yes', 'Smoker')
|
||||
: mobile ? t('common.no', 'No') : t('profile.smoker.no', 'Non-smoker')}
|
||||
</span>
|
||||
</Row>
|
||||
)
|
||||
@@ -34,16 +36,16 @@ export function SmokerFilter(props: {
|
||||
No: 'no',
|
||||
} as const
|
||||
|
||||
const currentChoice =
|
||||
filters.is_smoker == null ? choicesMap.Either : filters.is_smoker ? choicesMap.Yes : choicesMap.No
|
||||
const currentChoice = filters.is_smoker == null ? 'either' : filters.is_smoker ? 'yes' : 'no'
|
||||
|
||||
return (
|
||||
<ChoicesToggleGroup
|
||||
currentChoice={currentChoice}
|
||||
choicesMap={choicesMap}
|
||||
translationPrefix={'profile.smoker'}
|
||||
setChoice={(c) => {
|
||||
if (c === choicesMap.Either) updateFilter({is_smoker: undefined})
|
||||
else if (c === choicesMap.Yes) updateFilter({is_smoker: true})
|
||||
if (c === 'either') updateFilter({is_smoker: undefined})
|
||||
else if (c === 'yes') updateFilter({is_smoker: true})
|
||||
else updateFilter({is_smoker: false})
|
||||
}}
|
||||
toggleClassName="w-1/3 justify-center"
|
||||
|
||||
@@ -6,6 +6,7 @@ import clsx from 'clsx'
|
||||
import {FilterFields} from "common/filters";
|
||||
|
||||
import {generateChoicesMap, KidLabel, wantsKidsLabels} from "common/wants-kids"
|
||||
import {useT} from "web/lib/locale";
|
||||
|
||||
interface KidLabelWithIcon extends KidLabel {
|
||||
icon: ReactNode
|
||||
@@ -15,25 +16,35 @@ interface KidsLabelsMapWithIcon {
|
||||
[key: string]: KidLabelWithIcon
|
||||
}
|
||||
|
||||
export const wantsKidsLabelsWithIcon: KidsLabelsMapWithIcon = {
|
||||
...wantsKidsLabels,
|
||||
no_preference: {
|
||||
...wantsKidsLabels.no_preference,
|
||||
icon: <MdOutlineStroller className="h-4 w-4"/>,
|
||||
},
|
||||
wants_kids: {
|
||||
...wantsKidsLabels.wants_kids,
|
||||
icon: <MdStroller className="h-4 w-4"/>,
|
||||
},
|
||||
doesnt_want_kids: {
|
||||
...wantsKidsLabels.doesnt_want_kids,
|
||||
icon: <MdNoStroller className="h-4 w-4"/>,
|
||||
},
|
||||
export const useWantsKidsLabelsWithIcon = () => {
|
||||
const t = useT()
|
||||
return {
|
||||
no_preference: {
|
||||
...wantsKidsLabels.no_preference,
|
||||
name: t('filter.wants_kids.any_preference', 'Any preference'),
|
||||
shortName: t('filter.wants_kids.either', 'Either'),
|
||||
icon: <MdOutlineStroller className="h-4 w-4"/>,
|
||||
},
|
||||
wants_kids: {
|
||||
...wantsKidsLabels.wants_kids,
|
||||
name: t('filter.wants_kids.wants_kids', 'Wants kids'),
|
||||
shortName: t('common.yes', 'Yes'),
|
||||
icon: <MdStroller className="h-4 w-4"/>,
|
||||
},
|
||||
doesnt_want_kids: {
|
||||
...wantsKidsLabels.doesnt_want_kids,
|
||||
name: t('filter.wants_kids.doesnt_want_kids', "Doesn't want kids"),
|
||||
shortName: t('common.no', 'No'),
|
||||
icon: <MdNoStroller className="h-4 w-4"/>,
|
||||
},
|
||||
} as KidsLabelsMapWithIcon
|
||||
}
|
||||
|
||||
|
||||
export function WantsKidsIcon(props: { strength: number; className?: string }) {
|
||||
const {strength, className} = props
|
||||
const wantsKidsLabelsWithIcon = useWantsKidsLabelsWithIcon()
|
||||
|
||||
return (
|
||||
<span className={className}>
|
||||
{strength == wantsKidsLabelsWithIcon.no_preference.strength
|
||||
@@ -51,6 +62,7 @@ export function KidsLabel(props: {
|
||||
mobile?: boolean
|
||||
}) {
|
||||
const {strength, highlightedClass, mobile} = props
|
||||
const wantsKidsLabelsWithIcon = useWantsKidsLabelsWithIcon()
|
||||
|
||||
return (
|
||||
<Row className="items-center gap-0.5">
|
||||
@@ -82,6 +94,7 @@ export function WantsKidsFilter(props: {
|
||||
updateFilter: (newState: Partial<FilterFields>) => void
|
||||
}) {
|
||||
const {filters, updateFilter} = props
|
||||
const wantsKidsLabelsWithIcon = useWantsKidsLabelsWithIcon()
|
||||
|
||||
return (
|
||||
<ChoicesToggleGroup
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Row } from 'web/components/layout/row'
|
||||
import { Checkbox } from 'web/components/widgets/checkbox'
|
||||
import { Input } from 'web/components/widgets/input'
|
||||
import { Button } from 'web/components/buttons/button'
|
||||
import {Row} from 'web/components/layout/row'
|
||||
import {Checkbox} from 'web/components/widgets/checkbox'
|
||||
import {Input} from 'web/components/widgets/input'
|
||||
import {Button} from 'web/components/buttons/button'
|
||||
import clsx from 'clsx'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import {useEffect, useMemo, useState} from 'react'
|
||||
import {useT} from "web/lib/locale";
|
||||
import {toKey} from "common/parsing";
|
||||
|
||||
|
||||
export const MultiCheckbox = (props: {
|
||||
// Map of label -> value
|
||||
choices: { [key: string]: string }
|
||||
@@ -119,7 +119,7 @@ export const MultiCheckbox = (props: {
|
||||
{filteredEntries.map(([key, value]) => (
|
||||
<Checkbox
|
||||
key={key}
|
||||
label={t(`${translationPrefix}.${toKey(value)}`, key)}
|
||||
label={translationPrefix ? t(`${translationPrefix}.${toKey(value)}`, key) : key}
|
||||
checked={selected.includes(value)}
|
||||
toggle={(checked: boolean) => {
|
||||
if (checked) {
|
||||
|
||||
@@ -12,18 +12,18 @@ import {useT} from 'web/lib/locale'
|
||||
export function DeleteYourselfButton() {
|
||||
const [deleteAccountConfirmation, setDeleteAccountConfirmation] = useState('')
|
||||
const t = useT()
|
||||
const confirmPhrase = t('deleteyourself.confirm_phrase', 'delete my account')
|
||||
const confirmPhrase = t('delete_yourself.confirm_phrase', 'delete my account')
|
||||
|
||||
return (
|
||||
<ConfirmationButton
|
||||
openModalBtn={{
|
||||
className: 'p-2',
|
||||
label: t('deleteyourself.open_label', 'Permanently delete this account'),
|
||||
label: t('delete_yourself.open_label', 'Permanently delete this account'),
|
||||
icon: <TrashIcon className="mr-1 h-5 w-5"/>,
|
||||
color: 'red',
|
||||
}}
|
||||
submitBtn={{
|
||||
label: t('deleteyourself.submit', 'Delete account'),
|
||||
label: t('delete_yourself.submit', 'Delete account'),
|
||||
color:
|
||||
deleteAccountConfirmation == confirmPhrase ? 'red' : 'gray',
|
||||
}}
|
||||
@@ -31,13 +31,13 @@ export function DeleteYourselfButton() {
|
||||
if (deleteAccountConfirmation == confirmPhrase) {
|
||||
toast
|
||||
.promise(deleteAccount(), {
|
||||
loading: t('deleteyourself.toast.loading', 'Deleting account...'),
|
||||
loading: t('delete_yourself.toast.loading', 'Deleting account...'),
|
||||
success: () => {
|
||||
router.push('/')
|
||||
return t('deleteyourself.toast.success', 'Your account has been deleted.')
|
||||
return t('delete_yourself.toast.success', 'Your account has been deleted.')
|
||||
},
|
||||
error: () => {
|
||||
return t('deleteyourself.toast.error', 'Failed to delete account.')
|
||||
return t('delete_yourself.toast.error', 'Failed to delete account.')
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
@@ -51,16 +51,16 @@ export function DeleteYourselfButton() {
|
||||
}}
|
||||
>
|
||||
<Col>
|
||||
<Title>{t('deleteyourself.title', 'Are you sure?')}</Title>
|
||||
<Title>{t('delete_yourself.title', 'Are you sure?')}</Title>
|
||||
<div>
|
||||
{t(
|
||||
'deleteyourself.description',
|
||||
'delete_yourself.description',
|
||||
'Deleting your account means you will no longer be able to use your account. You will lose access to all of your data.'
|
||||
)}
|
||||
</div>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder={t('deleteyourself.input_placeholder', "Type 'delete my account' to confirm")}
|
||||
placeholder={t('delete_yourself.input_placeholder', "Type 'delete my account' to confirm")}
|
||||
className="w-full"
|
||||
value={deleteAccountConfirmation}
|
||||
onChange={(e) => setDeleteAccountConfirmation(e.target.value)}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { RadioGroup } from '@headlessui/react'
|
||||
import {RadioGroup} from '@headlessui/react'
|
||||
import clsx from 'clsx'
|
||||
import {toKey} from "common/parsing";
|
||||
import {useT} from "web/lib/locale";
|
||||
|
||||
const colorClasses = {
|
||||
'indigo-dark':
|
||||
@@ -15,16 +17,17 @@ export type ColorType = keyof typeof colorClasses
|
||||
|
||||
export function ChoicesToggleGroup<T extends Record<string, string | number | boolean>>(
|
||||
props: {
|
||||
currentChoice: T[keyof T] | undefined | null
|
||||
choicesMap: T
|
||||
disabled?: boolean
|
||||
disabledOptions?: Array<T[keyof T]>
|
||||
setChoice: (val: T[keyof T]) => void
|
||||
color?: ColorType
|
||||
className?: string
|
||||
toggleClassName?: string
|
||||
children?: React.ReactNode
|
||||
}) {
|
||||
currentChoice: T[keyof T] | undefined | null
|
||||
choicesMap: T
|
||||
disabled?: boolean
|
||||
disabledOptions?: Array<T[keyof T]>
|
||||
setChoice: (val: T[keyof T]) => void
|
||||
color?: ColorType
|
||||
className?: string
|
||||
toggleClassName?: string
|
||||
children?: React.ReactNode
|
||||
translationPrefix?: string
|
||||
}) {
|
||||
const {
|
||||
currentChoice,
|
||||
setChoice,
|
||||
@@ -35,7 +38,12 @@ export function ChoicesToggleGroup<T extends Record<string, string | number | bo
|
||||
className,
|
||||
children,
|
||||
toggleClassName,
|
||||
translationPrefix
|
||||
} = props
|
||||
const t = useT()
|
||||
// Object.entries(choicesMap).map(([choiceKey, choice]) => (
|
||||
// console.log(`${translationPrefix}.${toKey(String(choice))}`)
|
||||
// ))
|
||||
return (
|
||||
<RadioGroup
|
||||
className={clsx(
|
||||
@@ -52,7 +60,7 @@ export function ChoicesToggleGroup<T extends Record<string, string | number | bo
|
||||
key={choiceKey}
|
||||
value={choice}
|
||||
disabled={disabledOptions?.includes(choice as any)}
|
||||
className={({ disabled }) =>
|
||||
className={({disabled}) =>
|
||||
clsx(
|
||||
disabled
|
||||
? 'text-ink-300 aria-checked:bg-ink-300 aria-checked:text-ink-0 cursor-not-allowed'
|
||||
@@ -62,7 +70,8 @@ export function ChoicesToggleGroup<T extends Record<string, string | number | bo
|
||||
)
|
||||
}
|
||||
>
|
||||
<RadioGroup.Label as="span">{choiceKey}</RadioGroup.Label>
|
||||
<RadioGroup.Label
|
||||
as="span">{translationPrefix ? t(`${translationPrefix}.${toKey(String(choice))}`, choiceKey) : choiceKey}</RadioGroup.Label>
|
||||
</RadioGroup.Option>
|
||||
))}
|
||||
{children}
|
||||
|
||||
1511
web/messages/fr.json
1511
web/messages/fr.json
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user