mirror of
https://github.com/CompassConnections/Compass.git
synced 2025-12-23 22:18:43 -05:00
769 lines
26 KiB
TypeScript
769 lines
26 KiB
TypeScript
import {Fragment, useEffect, useRef, useState} from 'react'
|
|
import {Title} from 'web/components/widgets/title'
|
|
import {Col} from 'web/components/layout/col'
|
|
import clsx from 'clsx'
|
|
import {MultiCheckbox} from 'web/components/multi-checkbox'
|
|
import {Row} from 'web/components/layout/row'
|
|
import {Input} from 'web/components/widgets/input'
|
|
import {ChoicesToggleGroup} from 'web/components/widgets/choices-toggle-group'
|
|
import {Button, IconButton} from 'web/components/buttons/button'
|
|
import {colClassName, labelClassName} from 'web/pages/signup'
|
|
import {useRouter} from 'next/router'
|
|
import {api, updateProfile, updateUser} from 'web/lib/api'
|
|
import {User} from 'common/user'
|
|
import {track} from 'web/lib/service/analytics'
|
|
import {Carousel} from 'web/components/widgets/carousel'
|
|
import {tryCatch} from 'common/util/try-catch'
|
|
import {ProfileWithoutUser} from 'common/profiles/profile'
|
|
import {removeUndefinedProps} from 'common/util/object'
|
|
import {isEqual, range} from 'lodash'
|
|
import {PlatformSelect} from 'web/components/widgets/platform-select'
|
|
import {PLATFORM_LABELS, type Site, SITE_ORDER} from 'common/socials'
|
|
import {PlusIcon, XIcon} from '@heroicons/react/solid'
|
|
import {SocialIcon} from './user/social'
|
|
import {Select} from 'web/components/widgets/select'
|
|
import {City, CityRow, profileToCity, useCitySearch} from "web/components/search-location";
|
|
import {AddPhotosWidget} from './widgets/add-photos'
|
|
import {RadioToggleGroup} from "web/components/widgets/radio-toggle-group";
|
|
import {MultipleChoiceOptions} from "common/profiles/multiple-choice";
|
|
import {
|
|
DIET_CHOICES,
|
|
EDUCATION_CHOICES,
|
|
LANGUAGE_CHOICES,
|
|
MBTI_CHOICES,
|
|
POLITICAL_CHOICES,
|
|
RACE_CHOICES,
|
|
RELATIONSHIP_CHOICES,
|
|
RELATIONSHIP_STATUS_CHOICES,
|
|
RELIGION_CHOICES,
|
|
ROMANTIC_CHOICES
|
|
} from "web/components/filters/choices";
|
|
import toast from "react-hot-toast";
|
|
import {db} from "web/lib/supabase/db";
|
|
import {fetchChoices} from "web/hooks/use-choices";
|
|
import {AddOptionEntry} from "web/components/add-option-entry";
|
|
|
|
|
|
export const OptionalProfileUserForm = (props: {
|
|
profile: ProfileWithoutUser
|
|
setProfile: <K extends keyof ProfileWithoutUser>(key: K, value: ProfileWithoutUser[K]) => void
|
|
user: User
|
|
buttonLabel?: string
|
|
fromSignup?: boolean
|
|
onSubmit?: () => Promise<void>
|
|
}) => {
|
|
const {profile, user, buttonLabel, setProfile, fromSignup, onSubmit} = props
|
|
|
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
|
const [lookingRelationship, setLookingRelationship] = useState((profile.pref_relation_styles || []).includes('relationship'))
|
|
const router = useRouter()
|
|
const [heightFeet, setHeightFeet] = useState<number | undefined>(
|
|
profile.height_in_inches
|
|
? Math.floor((profile['height_in_inches'] ?? 0) / 12)
|
|
: undefined
|
|
)
|
|
const [heightInches, setHeightInches] = useState<number | undefined>(
|
|
profile.height_in_inches
|
|
? Math.floor((profile['height_in_inches'] ?? 0) % 12)
|
|
: undefined
|
|
)
|
|
|
|
const [newLinks, setNewLinks] = useState<Record<string, string | null>>(
|
|
user.link
|
|
)
|
|
|
|
const [newLinkPlatform, setNewLinkPlatform] = useState('')
|
|
const [newLinkValue, setNewLinkValue] = useState('')
|
|
const [interestChoices, setInterestChoices] = useState({})
|
|
const [causeChoices, setCauseChoices] = useState({})
|
|
const [workChoices, setWorkChoices] = useState({})
|
|
|
|
useEffect(() => {
|
|
fetchChoices('interests').then(setInterestChoices)
|
|
fetchChoices('causes').then(setCauseChoices)
|
|
fetchChoices('work').then(setWorkChoices)
|
|
}, [db])
|
|
|
|
const handleSubmit = async () => {
|
|
setIsSubmitting(true)
|
|
const {
|
|
bio: _bio,
|
|
bio_text: _bio_text,
|
|
bio_tsv: _bio_tsv,
|
|
bio_length: _bio_length,
|
|
interests,
|
|
causes,
|
|
work,
|
|
...otherProfileProps
|
|
} = profile
|
|
console.debug('otherProfileProps', removeUndefinedProps(otherProfileProps))
|
|
const promises: Promise<any>[] = [
|
|
tryCatch(updateProfile(removeUndefinedProps(otherProfileProps) as any))
|
|
]
|
|
if (interests?.length) {
|
|
promises.push(api('update-options', {table: 'interests', names: interests}))
|
|
}
|
|
if (causes?.length) {
|
|
promises.push(api('update-options', {table: 'causes', names: causes}))
|
|
}
|
|
if (work?.length) {
|
|
promises.push(api('update-options', {table: 'work', names: work}))
|
|
}
|
|
try {
|
|
await Promise.all(promises)
|
|
} catch (error) {
|
|
console.error(error)
|
|
toast.error(
|
|
`We ran into an issue saving your profile. Please try again or contact us if the issue persists.`
|
|
)
|
|
setIsSubmitting(false)
|
|
return
|
|
}
|
|
if (!isEqual(newLinks, user.link)) {
|
|
const {error} = await tryCatch(updateUser({link: newLinks}))
|
|
if (error) {
|
|
console.error(error)
|
|
return
|
|
}
|
|
}
|
|
onSubmit && (await onSubmit())
|
|
setIsSubmitting(false)
|
|
track('submit optional profile')
|
|
if (user)
|
|
router.push(`/${user.username}${fromSignup ? '?fromSignup=true' : ''}`)
|
|
else router.push('/')
|
|
}
|
|
|
|
const updateUserLink = (platform: string, value: string | null) => {
|
|
setNewLinks((links) => ({...links, [platform]: value}))
|
|
}
|
|
|
|
const addNewLink = () => {
|
|
if (newLinkPlatform && newLinkValue) {
|
|
updateUserLink(newLinkPlatform.toLowerCase().trim(), newLinkValue.trim())
|
|
setNewLinkPlatform('')
|
|
setNewLinkValue('')
|
|
}
|
|
}
|
|
|
|
function setProfileCity(inputCity: City | undefined) {
|
|
if (!inputCity) {
|
|
setProfile('geodb_city_id', null)
|
|
setProfile('city', '')
|
|
setProfile('region_code', null)
|
|
setProfile('country', null)
|
|
setProfile('city_latitude', null)
|
|
setProfile('city_longitude', null)
|
|
} else {
|
|
const {
|
|
geodb_city_id,
|
|
city,
|
|
region_code,
|
|
country,
|
|
latitude: city_latitude,
|
|
longitude: city_longitude,
|
|
} = inputCity
|
|
setProfile('geodb_city_id', geodb_city_id)
|
|
setProfile('city', city)
|
|
setProfile('region_code', region_code)
|
|
setProfile('country', country)
|
|
setProfile('city_latitude', city_latitude)
|
|
setProfile('city_longitude', city_longitude)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<>
|
|
{/*<Row className={'justify-end'}>*/}
|
|
{/* <Button*/}
|
|
{/* disabled={isSubmitting}*/}
|
|
{/* loading={isSubmitting}*/}
|
|
{/* onClick={handleSubmit}*/}
|
|
{/* >*/}
|
|
{/* {buttonLabel ?? 'Next / Skip'}*/}
|
|
{/* </Button>*/}
|
|
{/*</Row>*/}
|
|
|
|
<Title>More about me</Title>
|
|
<div className="text-ink-500 mb-6 text-lg">Optional information</div>
|
|
|
|
<Col className={'gap-8'}>
|
|
|
|
<Col className={clsx(colClassName)}>
|
|
<label className={clsx(labelClassName)}>Location</label>
|
|
{profile.city ? (
|
|
<Row className="border-primary-500 w-full justify-between rounded border px-4 py-2">
|
|
<CityRow
|
|
city={profileToCity(profile)}
|
|
onSelect={() => {
|
|
}}
|
|
className="pointer-events-none"
|
|
/>
|
|
<button
|
|
className="text-ink-700 hover:text-primary-700 text-sm underline"
|
|
onClick={() => {
|
|
setProfileCity(undefined)
|
|
}}
|
|
>
|
|
Change
|
|
</button>
|
|
</Row>
|
|
) : (
|
|
<CitySearchBox
|
|
onCitySelected={(city: City | undefined) => {
|
|
setProfileCity(city)
|
|
}}
|
|
/>
|
|
)}
|
|
</Col>
|
|
|
|
<Col className={clsx(colClassName)}>
|
|
<label className={clsx(labelClassName)}>Age</label>
|
|
<Input
|
|
type="number"
|
|
placeholder="Age"
|
|
value={profile['age'] ?? undefined}
|
|
min={18}
|
|
max={100}
|
|
onChange={(e) => setProfile('age', e.target.value ? Number(e.target.value) : null)}
|
|
/>
|
|
</Col>
|
|
|
|
<Row className={'items-center gap-2'}>
|
|
<Col className={'gap-1'}>
|
|
<label className={clsx(labelClassName)}>Gender</label>
|
|
<ChoicesToggleGroup
|
|
currentChoice={profile['gender']}
|
|
choicesMap={{
|
|
Woman: 'female',
|
|
Man: 'male',
|
|
Other: 'other',
|
|
}}
|
|
setChoice={(c) => setProfile('gender', c)}
|
|
/>
|
|
</Col>
|
|
</Row>
|
|
|
|
<Col className={clsx(colClassName)}>
|
|
<label className={clsx(labelClassName)}>Interested in connecting with</label>
|
|
<MultiCheckbox
|
|
choices={{
|
|
Women: 'female',
|
|
Men: 'male',
|
|
Other: 'other',
|
|
}}
|
|
selected={profile['pref_gender'] || []}
|
|
onChange={(selected) => setProfile('pref_gender', selected)}
|
|
/>
|
|
</Col>
|
|
|
|
<Col className={clsx(colClassName)}>
|
|
<label className={clsx(labelClassName)}>Who are aged between</label>
|
|
<Row className={'gap-2'}>
|
|
<Col>
|
|
<span>Min</span>
|
|
<Select
|
|
value={profile['pref_age_min'] ?? ''}
|
|
onChange={(e) =>
|
|
setProfile('pref_age_min', e.target.value ? Number(e.target.value) : 18)
|
|
}
|
|
className={'w-18 border-ink-300 rounded-md'}
|
|
>
|
|
<option key={""} value={""}></option>
|
|
{range(18, 100).map((m) => (
|
|
<option key={m} value={m}>
|
|
{m}
|
|
</option>
|
|
))}
|
|
</Select>
|
|
</Col>
|
|
<Col>
|
|
<span>Max</span>
|
|
<Select
|
|
value={profile['pref_age_max'] ?? ''}
|
|
onChange={(e) =>
|
|
setProfile('pref_age_max', e.target.value ? Number(e.target.value) : 100)
|
|
}
|
|
className={'w-18 border-ink-300 rounded-md'}
|
|
>
|
|
<option key={""} value={""}></option>
|
|
{range(18, 100).map((m) => (
|
|
<option key={m} value={m}>
|
|
{m}
|
|
</option>
|
|
))}
|
|
</Select>
|
|
</Col>
|
|
</Row>
|
|
</Col>
|
|
|
|
<Col className={clsx(colClassName)}>
|
|
<label className={clsx(labelClassName)}>Connection type</label>
|
|
<MultiCheckbox
|
|
choices={RELATIONSHIP_CHOICES}
|
|
selected={profile['pref_relation_styles'] || []}
|
|
onChange={(selected) => {
|
|
setProfile('pref_relation_styles', selected)
|
|
setLookingRelationship((selected || []).includes('relationship'))
|
|
}}
|
|
/>
|
|
</Col>
|
|
|
|
<Col className={clsx(colClassName)}>
|
|
<label className={clsx(labelClassName)}>Relationship status</label>
|
|
<MultiCheckbox
|
|
choices={RELATIONSHIP_STATUS_CHOICES}
|
|
selected={profile['relationship_status'] ?? []}
|
|
onChange={(selected) => setProfile('relationship_status', selected)}
|
|
/>
|
|
</Col>
|
|
|
|
{lookingRelationship && <>
|
|
<Col className={clsx(colClassName)}>
|
|
<label className={clsx(labelClassName)}>Relationship style</label>
|
|
<MultiCheckbox
|
|
choices={ROMANTIC_CHOICES}
|
|
selected={profile['pref_romantic_styles'] || []}
|
|
onChange={(selected) => {
|
|
setProfile('pref_romantic_styles', selected)
|
|
}}
|
|
/>
|
|
</Col>
|
|
|
|
<Col className={clsx(colClassName)}>
|
|
<label className={clsx(labelClassName)}>
|
|
I would like to have kids
|
|
</label>
|
|
<RadioToggleGroup
|
|
className={'w-44'}
|
|
choicesMap={MultipleChoiceOptions}
|
|
setChoice={(choice) => {
|
|
setProfile('wants_kids_strength', choice)
|
|
}}
|
|
currentChoice={profile.wants_kids_strength ?? -1}
|
|
/>
|
|
</Col>
|
|
|
|
<Col className={clsx(colClassName)}>
|
|
<label className={clsx(labelClassName)}>Current number of kids</label>
|
|
<Input
|
|
type="number"
|
|
onChange={(e) => {
|
|
const value =
|
|
e.target.value === '' ? null : Number(e.target.value)
|
|
setProfile('has_kids', value)
|
|
}}
|
|
className={'w-20'}
|
|
min={0}
|
|
value={profile['has_kids'] ?? undefined}
|
|
/>
|
|
</Col>
|
|
</>}
|
|
|
|
<Col className={clsx(colClassName, 'pb-4')}>
|
|
<label className={clsx(labelClassName)}>Socials</label>
|
|
|
|
<div className="grid w-full grid-cols-[8rem_1fr_auto] gap-2">
|
|
{Object.entries(newLinks)
|
|
.filter(([_, value]) => value != null)
|
|
.map(([platform, value]) => (
|
|
<Fragment key={platform}>
|
|
<div className="col-span-3 mt-2 flex items-center gap-2 self-center sm:col-span-1">
|
|
<SocialIcon
|
|
site={platform as any}
|
|
className="text-primary-700 h-4 w-4"
|
|
/>
|
|
{PLATFORM_LABELS[platform as Site] ?? platform}
|
|
</div>
|
|
<Input
|
|
type="text"
|
|
value={value!}
|
|
onChange={(e) => updateUserLink(platform, e.target.value)}
|
|
className="col-span-2 sm:col-span-1"
|
|
/>
|
|
<IconButton onClick={() => updateUserLink(platform, null)}>
|
|
<XIcon className="h-6 w-6"/>
|
|
<div className="sr-only">Remove</div>
|
|
</IconButton>
|
|
</Fragment>
|
|
))}
|
|
|
|
{/* Spacer */}
|
|
<div className="col-span-3 h-4"/>
|
|
|
|
<PlatformSelect
|
|
value={newLinkPlatform}
|
|
onChange={setNewLinkPlatform}
|
|
className="h-full !w-full"
|
|
/>
|
|
<Input
|
|
type="text"
|
|
placeholder={
|
|
SITE_ORDER.includes(newLinkPlatform as any) &&
|
|
newLinkPlatform != 'site'
|
|
? 'Username or URL'
|
|
: 'Site URL'
|
|
}
|
|
value={newLinkValue}
|
|
onChange={(e) => setNewLinkValue(e.target.value)}
|
|
// disable password managers
|
|
autoComplete="off"
|
|
data-1p-ignore
|
|
data-lpignore="true"
|
|
data-bwignore="true"
|
|
data-protonpass-ignore="true"
|
|
className="w-full"
|
|
/>
|
|
<Button
|
|
color="gray-outline"
|
|
onClick={addNewLink}
|
|
disabled={!newLinkPlatform || !newLinkValue}
|
|
>
|
|
<PlusIcon className="h-6 w-6"/>
|
|
<div className="sr-only">Add</div>
|
|
</Button>
|
|
</div>
|
|
</Col>
|
|
|
|
<Col className={clsx(colClassName)}>
|
|
<label className={clsx(labelClassName)}>Languages</label>
|
|
<div className="grid grid-cols-1 gap-4">
|
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
|
<div className="col-span-full max-h-60 overflow-y-auto w-full">
|
|
<MultiCheckbox
|
|
choices={LANGUAGE_CHOICES}
|
|
selected={profile.languages || []}
|
|
onChange={(selected) => setProfile('languages', selected)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Col>
|
|
|
|
<Col className={clsx(colClassName)}>
|
|
<label className={clsx(labelClassName)}>Political beliefs</label>
|
|
<MultiCheckbox
|
|
choices={POLITICAL_CHOICES}
|
|
selected={profile['political_beliefs'] ?? []}
|
|
onChange={(selected) => setProfile('political_beliefs', selected)}
|
|
/>
|
|
<p>Details:</p>
|
|
<Input
|
|
type="text"
|
|
onChange={(e) => setProfile('political_details', e.target.value)}
|
|
className={'w-full sm:w-96'}
|
|
value={profile['political_details'] ?? undefined}
|
|
/>
|
|
</Col>
|
|
|
|
<Col className={clsx(colClassName)}>
|
|
<label className={clsx(labelClassName)}>Religious beliefs</label>
|
|
<MultiCheckbox
|
|
choices={RELIGION_CHOICES}
|
|
selected={profile['religion'] ?? []}
|
|
onChange={(selected) => setProfile('religion', selected)}
|
|
/>
|
|
<p>Details:</p>
|
|
<Input
|
|
type="text"
|
|
onChange={(e) => setProfile('religious_beliefs', e.target.value)}
|
|
className={'w-full sm:w-96'}
|
|
value={profile['religious_beliefs'] ?? undefined}
|
|
/>
|
|
</Col>
|
|
|
|
<AddOptionEntry
|
|
choices={interestChoices}
|
|
setChoices={setInterestChoices}
|
|
profile={profile}
|
|
setProfile={setProfile}
|
|
label={'interests'}
|
|
/>
|
|
|
|
<AddOptionEntry
|
|
choices={causeChoices}
|
|
setChoices={setCauseChoices}
|
|
profile={profile}
|
|
setProfile={setProfile}
|
|
label={'causes'}
|
|
/>
|
|
|
|
<Col className={clsx(colClassName, 'max-w-[550px]')}>
|
|
<label className={clsx(labelClassName)}>MBTI Personality Type</label>
|
|
<ChoicesToggleGroup
|
|
currentChoice={profile['mbti'] ?? ''}
|
|
choicesMap={MBTI_CHOICES}
|
|
setChoice={(c) => setProfile('mbti', c)}
|
|
className="grid grid-cols-4 xs:grid-cols-8"
|
|
/>
|
|
</Col>
|
|
|
|
<Col className={clsx(colClassName)}>
|
|
<label className={clsx(labelClassName)}>Diet</label>
|
|
<MultiCheckbox
|
|
choices={DIET_CHOICES}
|
|
selected={profile['diet'] ?? []}
|
|
onChange={(selected) => setProfile('diet', selected)}
|
|
/>
|
|
</Col>
|
|
|
|
<AddOptionEntry
|
|
choices={workChoices}
|
|
setChoices={setWorkChoices}
|
|
profile={profile}
|
|
setProfile={setProfile}
|
|
label={'work'}
|
|
/>
|
|
|
|
<Col className={clsx(colClassName)}>
|
|
<label className={clsx(labelClassName)}>Company</label>
|
|
<Input
|
|
type="text"
|
|
onChange={(e) => setProfile('company', e.target.value)}
|
|
className={'w-52'}
|
|
value={profile['company'] ?? undefined}
|
|
/>
|
|
</Col>
|
|
|
|
<Col className={clsx(colClassName)}>
|
|
<label className={clsx(labelClassName)}>
|
|
Job title {profile['company'] ? 'at ' + profile['company'] : ''}
|
|
</label>
|
|
<Input
|
|
type="text"
|
|
onChange={(e) => setProfile('occupation_title', e.target.value)}
|
|
className={'w-52'}
|
|
value={profile['occupation_title'] ?? undefined}
|
|
/>
|
|
</Col>
|
|
|
|
<Col className={clsx(colClassName)}>
|
|
<label className={clsx(labelClassName)}>
|
|
Highest completed education level
|
|
</label>
|
|
<Carousel className="max-w-full">
|
|
<ChoicesToggleGroup
|
|
currentChoice={profile['education_level'] ?? ''}
|
|
choicesMap={EDUCATION_CHOICES}
|
|
setChoice={(c) => setProfile('education_level', c)}
|
|
/>
|
|
</Carousel>
|
|
</Col>
|
|
|
|
<Col className={clsx(colClassName)}>
|
|
<label className={clsx(labelClassName)}>University</label>
|
|
<Input
|
|
type="text"
|
|
onChange={(e) => setProfile('university', e.target.value)}
|
|
className={'w-52'}
|
|
value={profile['university'] ?? undefined}
|
|
/>
|
|
</Col>
|
|
|
|
<Col className={clsx(colClassName)}>
|
|
<label className={clsx(labelClassName)}>Do you smoke?</label>
|
|
<ChoicesToggleGroup
|
|
currentChoice={profile['is_smoker'] ?? undefined}
|
|
choicesMap={{
|
|
Yes: true,
|
|
No: false,
|
|
}}
|
|
setChoice={(c) => setProfile('is_smoker', c)}
|
|
/>
|
|
</Col>
|
|
|
|
<Col className={clsx(colClassName)}>
|
|
<label className={clsx(labelClassName)}>
|
|
Alcoholic beverages consumed per month
|
|
</label>
|
|
<Input
|
|
type="number"
|
|
onChange={(e) => {
|
|
const value =
|
|
e.target.value === '' ? null : Number(e.target.value)
|
|
setProfile('drinks_per_month', value)
|
|
}}
|
|
className={'w-20'}
|
|
min={0}
|
|
value={profile['drinks_per_month'] ?? undefined}
|
|
/>
|
|
</Col>
|
|
|
|
<Col className={clsx(colClassName)}>
|
|
<label className={clsx(labelClassName)}>Height</label>
|
|
<Row className={'gap-2'}>
|
|
<Col>
|
|
<span>Feet</span>
|
|
<Input
|
|
type="number"
|
|
onChange={(e) => {
|
|
if (e.target.value === '') {
|
|
setHeightFeet(undefined)
|
|
} else {
|
|
setHeightFeet(Number(e.target.value))
|
|
const heightInInches = Number(e.target.value) * 12 + (heightInches ?? 0)
|
|
setProfile('height_in_inches', heightInInches)
|
|
}
|
|
}}
|
|
className={'w-16'}
|
|
value={typeof heightFeet === 'number' ? Math.floor(heightFeet) : ''}
|
|
/>
|
|
</Col>
|
|
<Col>
|
|
<span>Inches</span>
|
|
<Input
|
|
type="number"
|
|
onChange={(e) => {
|
|
if (e.target.value === '') {
|
|
setHeightInches(undefined)
|
|
} else {
|
|
setHeightInches(Number(e.target.value))
|
|
const heightInInches = Number(e.target.value) + 12 * (heightFeet ?? 0)
|
|
setProfile('height_in_inches', heightInInches)
|
|
}
|
|
}}
|
|
className={'w-16'}
|
|
value={typeof heightInches === 'number' ? Math.floor(heightInches) : ''}
|
|
/>
|
|
</Col>
|
|
<div className="self-end mb-2 text-ink-700 mx-2">OR</div>
|
|
<Col>
|
|
<span>Centimeters</span>
|
|
<Input
|
|
type="number"
|
|
onChange={(e) => {
|
|
if (e.target.value === '') {
|
|
setHeightFeet(undefined)
|
|
setHeightInches(undefined)
|
|
setProfile('height_in_inches', null)
|
|
} else {
|
|
// Convert cm to inches
|
|
const totalInches = Number(e.target.value) / 2.54
|
|
setHeightFeet(Math.floor(totalInches / 12))
|
|
setHeightInches(totalInches % 12)
|
|
setProfile('height_in_inches', totalInches)
|
|
}
|
|
}}
|
|
className={'w-20'}
|
|
value={heightFeet !== undefined
|
|
? Math.round((heightFeet * 12 + (heightInches ?? 0)) * 2.54)
|
|
: ''}
|
|
/>
|
|
</Col>
|
|
</Row>
|
|
</Col>
|
|
|
|
{/* <Col className={clsx(colClassName)}>
|
|
<label className={clsx(labelClassName)}>Birthplace</label>
|
|
<Input
|
|
type="text"
|
|
onChange={(e) => setProfileState('born_in_location', e.target.value)}
|
|
className={'w-52'}
|
|
value={profile['born_in_location'] ?? undefined}
|
|
/>
|
|
</Col> */}
|
|
|
|
<Col className={clsx(colClassName)}>
|
|
<label className={clsx(labelClassName)}>Ethnicity/origin</label>
|
|
<MultiCheckbox
|
|
choices={RACE_CHOICES}
|
|
selected={profile['ethnicity'] ?? []}
|
|
onChange={(selected) => setProfile('ethnicity', selected)}
|
|
/>
|
|
</Col>
|
|
|
|
{/*<Col className={clsx(colClassName)}>*/}
|
|
{/* <label className={clsx(labelClassName)}>Looking for a relationship?</label>*/}
|
|
{/* <ChoicesToggleGroup*/}
|
|
{/* currentChoice={lookingRelationship}*/}
|
|
{/* choicesMap={{Yes: true, No: false}}*/}
|
|
{/* setChoice={(c) => setLookingRelationship(c)}*/}
|
|
{/* />*/}
|
|
{/*</Col>*/}
|
|
|
|
<Col className={clsx(colClassName)}>
|
|
<label className={clsx(labelClassName)}>Photos</label>
|
|
|
|
{/*<div className="mb-1">*/}
|
|
{/* A real or stylized photo of you is required.*/}
|
|
{/*</div>*/}
|
|
|
|
<AddPhotosWidget
|
|
user={user}
|
|
photo_urls={profile.photo_urls}
|
|
pinned_url={profile.pinned_url}
|
|
setPhotoUrls={(urls) => setProfile('photo_urls', urls)}
|
|
setPinnedUrl={(url) => setProfile('pinned_url', url)}
|
|
setDescription={(url, description) =>
|
|
setProfile("image_descriptions", {
|
|
...(profile?.image_descriptions as Record<string, string> ?? {}),
|
|
[url]: description,
|
|
})
|
|
}
|
|
image_descriptions={profile.image_descriptions as Record<string, string>}
|
|
/>
|
|
</Col>
|
|
|
|
|
|
<Row className={'justify-end'}>
|
|
<Button
|
|
className="fixed bottom-[calc(90px+var(--bnh))] lg:bottom-6 right-4 z-50 text-xl"
|
|
disabled={isSubmitting}
|
|
loading={isSubmitting}
|
|
onClick={handleSubmit}
|
|
>
|
|
{buttonLabel ?? 'Next'}
|
|
</Button>
|
|
</Row>
|
|
</Col>
|
|
</>
|
|
)
|
|
}
|
|
|
|
const CitySearchBox = (props: {
|
|
onCitySelected: (city: City | undefined) => void
|
|
}) => {
|
|
// search results
|
|
const {cities, query, setQuery} = useCitySearch()
|
|
const [focused, setFocused] = useState(false)
|
|
|
|
const dropdownRef = useRef<HTMLDivElement>(null)
|
|
|
|
return (
|
|
<>
|
|
<Input
|
|
value={query}
|
|
onChange={(e) => setQuery(e.target.value)}
|
|
placeholder={'Search city...'}
|
|
onFocus={() => setFocused(true)}
|
|
onBlur={(e) => {
|
|
// Do not hide the dropdown if clicking inside the dropdown
|
|
if (
|
|
dropdownRef.current &&
|
|
!dropdownRef.current.contains(e.relatedTarget)
|
|
) {
|
|
setFocused(false)
|
|
}
|
|
}}
|
|
/>
|
|
<div className="relative w-full" ref={dropdownRef}>
|
|
<Col className="bg-canvas-50 absolute left-0 right-0 top-1 z-10 w-full overflow-hidden rounded-md">
|
|
{focused &&
|
|
cities.map((c) => (
|
|
<CityRow
|
|
key={c.geodb_city_id}
|
|
city={c}
|
|
onSelect={() => {
|
|
props.onCitySelected(c)
|
|
setQuery('')
|
|
}}
|
|
className="hover:bg-primary-200 justify-between gap-1 px-4 py-2 transition-colors"
|
|
/>
|
|
))}
|
|
</Col>
|
|
</div>
|
|
</>
|
|
)
|
|
}
|
|
|