Make all complete-profile fields optional and Pre-Save lover

This commit is contained in:
MartinBraquet
2025-09-07 23:54:37 +02:00
parent 7886d32933
commit 4815cc7682
12 changed files with 399 additions and 367 deletions

View File

@@ -28,6 +28,8 @@ export const createLover: APIHandler<'create-lover'> = async (body, auth) => {
updateUser(pg, auth.uid, { avatarUrl: body.pinned_url })
}
console.log('body', body)
const { data, error } = await tryCatch(
insert(pg, 'lovers', { user_id: auth.uid, ...body })
)

View File

@@ -76,7 +76,7 @@ export const getLovers: APIHandler<'get-lovers'> = async (props, _auth) => {
from('lovers'),
join('users on users.id = lovers.user_id'),
where('looking_for_matches = true'),
where(`pinned_url is not null and pinned_url != ''`),
// where(`pinned_url is not null and pinned_url != ''`),
where(
`(data->>'isBannedFromPosting' != 'true' or data->>'isBannedFromPosting' is null)`
),

View File

@@ -57,11 +57,13 @@ export const baseLoversSchema = z.object({
z.literal('other'),
])
),
wants_kids_strength: z.number().min(0),
wants_kids_strength: z.number(),
looking_for_matches: z.boolean(),
photo_urls: z.array(z.string()),
visibility: z.union([z.literal('public'), z.literal('member')]),
bio: contentSchema.optional().nullable(),
geodb_city_id: z.string().optional(),
city: z.string(),
region_code: z.string().optional(),

View File

@@ -60,3 +60,26 @@ export function EditableBio(props: {
</Col>
)
}
export function SignupBio(props: {
onChange: (e: JSONContent) => void
}) {
const { onChange } = props
const editor = useTextEditor({
max: MAX_DESCRIPTION_LENGTH,
defaultValue: '',
placeholder: "Tell us about yourself — and what you're looking for!",
})
return (
<Col className="relative w-full">
<TextEditor
editor={editor}
onBlur={() => {
// console.log('onchange', editor?.getText())
if (editor) onChange(editor.getJSON())
}}
/>
</Col>
)
}

View File

@@ -80,10 +80,10 @@ export const Search = (props: {
value={filters.orderBy || 'created_time'}
className={'w-18 border-ink-300 rounded-md'}
>
<option value="created_time">New</option>
{youLover && (
<option value="compatibility_score">Compatible</option>
)}
<option value="created_time">New</option>
<option value="last_online_time">Active</option>
</Select>
<Button

View File

@@ -50,7 +50,7 @@ const initialFilters: Partial<FilterFields> = {
is_smoker: undefined,
pref_relation_styles: undefined,
pref_gender: undefined,
orderBy: 'compatibility_score',
orderBy: 'created_time',
}
export const useFilters = (you: Lover | undefined) => {

View File

@@ -236,6 +236,7 @@ function Drinks(props: { lover: Lover }) {
function WantsKids(props: { lover: Lover }) {
const { lover } = props
const wantsKidsStrength = lover.wants_kids_strength
if (wantsKidsStrength == null || wantsKidsStrength < 0) return null
const wantsKidsText =
wantsKidsStrength == 0
? 'Does not want children'

View File

@@ -1,28 +1,31 @@
import { Fragment, useState } from 'react'
import { Title } from 'web/components/widgets/title'
import { Col } from 'web/components/layout/col'
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 { updateLover, updateUser } from 'web/lib/api'
import { Column } from 'common/supabase/utils'
import { User } from 'common/user'
import { track } from 'web/lib/service/analytics'
import { Races } from './race'
import { Carousel } from 'web/components/widgets/carousel'
import { tryCatch } from 'common/util/try-catch'
import { LoverRow } from 'common/love/lover'
import { removeNullOrUndefinedProps } from 'common/util/object'
import { isEqual } from 'lodash'
import { PlatformSelect } from 'web/components/widgets/platform-select'
import { type Site, PLATFORM_LABELS, SITE_ORDER } from 'common/socials'
import { PlusIcon, XIcon } from '@heroicons/react/solid'
import { SocialIcon } from './user/social'
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 {updateLover, updateUser} from 'web/lib/api'
import {Column} from 'common/supabase/utils'
import {User} from 'common/user'
import {track} from 'web/lib/service/analytics'
import {Races} from './race'
import {Carousel} from 'web/components/widgets/carousel'
import {tryCatch} from 'common/util/try-catch'
import {LoverRow} from 'common/love/lover'
import {removeNullOrUndefinedProps} 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, loverToCity, useCitySearch} from "web/components/search-location";
import {AddPhotosWidget} from './widgets/add-photos'
export const OptionalLoveUserForm = (props: {
lover: LoverRow
@@ -32,7 +35,7 @@ export const OptionalLoveUserForm = (props: {
fromSignup?: boolean
onSubmit?: () => Promise<void>
}) => {
const { lover, user, buttonLabel, setLover, fromSignup, onSubmit } = props
const {lover, user, buttonLabel, setLover, fromSignup, onSubmit} = props
const [isSubmitting, setIsSubmitting] = useState(false)
const router = useRouter()
@@ -56,8 +59,8 @@ export const OptionalLoveUserForm = (props: {
const handleSubmit = async () => {
setIsSubmitting(true)
const { bio: _, ...otherLoverProps } = lover
const { error } = await tryCatch(
const {bio: _, ...otherLoverProps} = lover
const {error} = await tryCatch(
updateLover(removeNullOrUndefinedProps(otherLoverProps) as any)
)
if (error) {
@@ -65,7 +68,7 @@ export const OptionalLoveUserForm = (props: {
return
}
if (!isEqual(newLinks, user.link)) {
const { error } = await tryCatch(updateUser({ link: newLinks }))
const {error} = await tryCatch(updateUser({link: newLinks}))
if (error) {
console.error(error)
return
@@ -81,7 +84,7 @@ export const OptionalLoveUserForm = (props: {
}
const updateUserLink = (platform: string, value: string | null) => {
setNewLinks((links) => ({ ...links, [platform]: value }))
setNewLinks((links) => ({...links, [platform]: value}))
}
const addNewLink = () => {
@@ -92,12 +95,227 @@ export const OptionalLoveUserForm = (props: {
}
}
const [trans, setTrans] = useState<boolean | undefined>(
lover['gender'].includes('trans')
)
function setLoverCity(inputCity: City | undefined) {
if (!inputCity) {
setLover('geodb_city_id', null)
setLover('city', '')
setLover('region_code', null)
setLover('country', null)
setLover('city_latitude', null)
setLover('city_longitude', null)
} else {
const {
geodb_city_id,
city,
region_code,
country,
latitude: city_latitude,
longitude: city_longitude,
} = inputCity
setLover('geodb_city_id', geodb_city_id)
setLover('city', city)
setLover('region_code', region_code)
setLover('country', country)
setLover('city_latitude', city_latitude)
setLover('city_longitude', city_longitude)
}
}
useEffect(() => {
const currentState = lover['gender']
if (currentState === 'non-binary') {
setTrans(undefined)
} else if (trans && !currentState.includes('trans-')) {
setLover('gender', 'trans-' + currentState.replace('trans-', ''))
} else if (!trans && currentState.includes('trans-')) {
setLover('gender', currentState.replace('trans-', ''))
}
}, [trans, lover['gender']])
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)}>Looking for matches</label>*/}
{/* <ChoicesToggleGroup*/}
{/* currentChoice={lover.looking_for_matches}*/}
{/* choicesMap={{*/}
{/* Yes: true,*/}
{/* No: false,*/}
{/* }}*/}
{/* setChoice={(c) => setLover('looking_for_matches', c)}*/}
{/* />*/}
{/*</Col>*/}
<Col className={clsx(colClassName)}>
<label className={clsx(labelClassName)}>Your location</label>
{lover.city ? (
<Row className="border-primary-500 w-full justify-between rounded border px-4 py-2">
<CityRow
city={loverToCity(lover)}
onSelect={() => {
}}
className="pointer-events-none"
/>
<button
className="text-ink-700 hover:text-primary-700 text-sm underline"
onClick={() => {
setLoverCity(undefined)
}}
>
Change
</button>
</Row>
) : (
<CitySearchBox
onCitySelected={(city: City | undefined) => {
setLoverCity(city)
}}
/>
)}
</Col>
<Col className={clsx(colClassName)}>
<label className={clsx(labelClassName)}>Age</label>
<Input
type="number"
placeholder="Age"
value={lover['age'] > 0 ? lover['age'] : undefined}
min={18}
max={100}
onChange={(e) => setLover('age', Number(e.target.value))}
/>
</Col>
<Row className={'items-center gap-2'}>
<Col className={'gap-1'}>
<label className={clsx(labelClassName)}>Gender</label>
<ChoicesToggleGroup
currentChoice={lover['gender'].replace('trans-', '')}
choicesMap={{
Woman: 'female',
Man: 'male',
Other: 'other',
}}
setChoice={(c) => setLover('gender', c)}
/>
</Col>
</Row>
<Col className={clsx(colClassName)}>
<label className={clsx(labelClassName)}>Interested in</label>
<MultiCheckbox
choices={{
Women: 'female',
Men: 'male',
Other: 'other',
}}
selected={lover['pref_gender']}
onChange={(selected) => setLover('pref_gender', selected)}
/>
</Col>
{/*<Col className={clsx(colClassName)}>*/}
{/* <label className={clsx(labelClassName)}>Relationship style</label>*/}
{/* <MultiCheckbox*/}
{/* choices={{*/}
{/* Monogamous: 'mono',*/}
{/* Polyamorous: 'poly',*/}
{/* 'Open Relationship': 'open',*/}
{/* Other: 'other',*/}
{/* }}*/}
{/* selected={lover['pref_relation_styles']}*/}
{/* onChange={(selected) =>*/}
{/* setLover('pref_relation_styles', selected)*/}
{/* }*/}
{/* />*/}
{/*</Col>*/}
<Col className={clsx(colClassName)}>
<label className={clsx(labelClassName)}>Partner age range</label>
<Row className={'gap-2'}>
<Col>
<span>Min</span>
<Select
value={lover['pref_age_min']}
onChange={(e) =>
setLover('pref_age_min', Number(e.target.value))
}
className={'w-18 border-ink-300 rounded-md'}
>
{range(18, 100).map((m) => (
<option key={m} value={m}>
{m}
</option>
))}
</Select>
</Col>
<Col>
<span>Max</span>
<Select
value={lover['pref_age_max']}
onChange={(e) =>
setLover('pref_age_max', Number(e.target.value))
}
className={'w-18 border-ink-300 rounded-md'}
>
{range(18, 100).map((m) => (
<option key={m} value={m}>
{m}
</option>
))}
</Select>
</Col>
</Row>
</Col>
{/*<Col className={clsx(colClassName)}>*/}
{/* <label className={clsx(labelClassName)}>*/}
{/* You want to have kids*/}
{/* </label>*/}
{/* <RadioToggleGroup*/}
{/* className={'w-44'}*/}
{/* choicesMap={MultipleChoiceOptions}*/}
{/* setChoice={(choice) => {*/}
{/* setLover('wants_kids_strength', choice)*/}
{/* }}*/}
{/* currentChoice={lover.wants_kids_strength ?? -1}*/}
{/* />*/}
{/*</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={lover.photo_urls}
pinned_url={lover.pinned_url}
setPhotoUrls={(urls) => setLover('photo_urls', urls)}
setPinnedUrl={(url) => setLover('pinned_url', url)}
/>
</Col>
<Col className={clsx(colClassName, 'pb-4')}>
<label className={clsx(labelClassName)}>Socials</label>
@@ -120,14 +338,14 @@ export const OptionalLoveUserForm = (props: {
className="col-span-2 sm:col-span-1"
/>
<IconButton onClick={() => updateUserLink(platform, null)}>
<XIcon className="h-6 w-6" />
<XIcon className="h-6 w-6"/>
<div className="sr-only">Remove</div>
</IconButton>
</Fragment>
))}
{/* Spacer */}
<div className="col-span-3 h-4" />
<div className="col-span-3 h-4"/>
<PlatformSelect
value={newLinkPlatform}
@@ -156,7 +374,7 @@ export const OptionalLoveUserForm = (props: {
onClick={addNewLink}
disabled={!newLinkPlatform || !newLinkValue}
>
<PlusIcon className="h-6 w-6" />
<PlusIcon className="h-6 w-6"/>
<div className="sr-only">Add</div>
</Button>
</div>
@@ -357,3 +575,50 @@ export const OptionalLoveUserForm = (props: {
</>
)
}
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>
</>
)
}

View File

@@ -1,22 +1,15 @@
import { Lover } from 'common/love/lover'
import { CompatibilityScore } from 'common/love/compatibility-score'
import { LoadingIndicator } from 'web/components/widgets/loading-indicator'
import { LoadMoreUntilNotVisible } from 'web/components/widgets/visibility-observer'
import { UserIcon } from '@heroicons/react/solid'
import { capitalize } from 'lodash'
import Link from 'next/link'
import { useUser } from 'web/hooks/use-user'
import { track } from 'web/lib/service/analytics'
import { convertGender, Gender } from 'common/gender'
import { Col } from './layout/col'
import { Row } from './layout/row'
import { CompatibleBadge } from './widgets/compatible-badge'
import { StarButton } from './widgets/star-button'
import {Lover} from 'common/love/lover'
import {CompatibilityScore} from 'common/love/compatibility-score'
import {LoadingIndicator} from 'web/components/widgets/loading-indicator'
import {LoadMoreUntilNotVisible} from 'web/components/widgets/visibility-observer'
import {useUser} from 'web/hooks/use-user'
import {track} from 'web/lib/service/analytics'
import {Col} from './layout/col'
import clsx from 'clsx'
import Image from 'next/image'
import {JSONContent} from "@tiptap/core";
import {Content} from "web/components/widgets/editor";
import React from "react";
import Router from "next/router";
export const ProfileGrid = (props: {
lovers: Lover[]
@@ -56,11 +49,11 @@ export const ProfileGrid = (props: {
))}
</div>
<LoadMoreUntilNotVisible loadMore={loadMore} />
<LoadMoreUntilNotVisible loadMore={loadMore}/>
{isLoadingMore && (
<div className="flex justify-center py-4">
<LoadingIndicator />
<LoadingIndicator/>
</div>
)}
@@ -77,19 +70,20 @@ function ProfilePreview(props: {
hasStar: boolean
refreshStars: () => Promise<void>
}) {
const { lover, compatibilityScore, hasStar, refreshStars } = props
const { user, gender, age, pinned_url, city, bio } = lover
const currentUser = useUser()
const {lover, compatibilityScore, hasStar, refreshStars} = props
const {user, gender, age, pinned_url, city, bio} = lover
// const currentUser = useUser()
return (
<Link
href={`/${user.username}`}
<div
onClick={() => {
track('click love profile preview')
Router.push(`/${user.username}`)
}}
className="group block dark:bg-gray-800 rounded-lg overflow-hidden shadow hover:shadow-md transition-shadow duration-200 h-full"
className="cursor-pointer group block dark:bg-gray-800 rounded-lg overflow-hidden shadow hover:shadow-md transition-shadow duration-200 h-full"
>
<Col className="relative h-40 w-full overflow-hidden rounded text-white transition-all hover:scale-y-105 hover:drop-shadow">
<Col
className="relative h-40 w-full overflow-hidden rounded text-white transition-all hover:scale-y-105 hover:drop-shadow">
{/*{pinned_url ? (*/}
{/* <Image*/}
{/* src={pinned_url}*/}
@@ -130,9 +124,9 @@ function ProfilePreview(props: {
<h3 className="text-lg font-medium text-gray-900 dark:text-white truncate">
{user.name}
</h3>
<p className="text-sm text-gray-500 dark:text-gray-400">
<Content className="w-full line-clamp-4" content={lover.bio as JSONContent} />
</p>
<div className="text-sm text-gray-500 dark:text-gray-400">
<Content className="w-full line-clamp-4" content={lover.bio as JSONContent}/>
</div>
{/*{age}*/}
</div>
</div>
@@ -141,6 +135,6 @@ function ProfilePreview(props: {
{/*</Row>*/}
</Col>
</Col>
</Link>
</div>
)
}

View File

@@ -1,40 +1,34 @@
import { useEffect, useRef, useState } from 'react'
import { Title } from 'web/components/widgets/title'
import { Col } from 'web/components/layout/col'
import {useEffect, useRef, useState} from 'react'
import {Title} from 'web/components/widgets/title'
import {Col} from 'web/components/layout/col'
import clsx from 'clsx'
import { ChoicesToggleGroup } from 'web/components/widgets/choices-toggle-group'
import { Input } from 'web/components/widgets/input'
import { Row } from 'web/components/layout/row'
import { Button } from 'web/components/buttons/button'
import { colClassName, labelClassName } from 'web/pages/signup'
import { MultiCheckbox } from 'web/components/multi-checkbox'
import { User } from 'common/user'
import { RadioToggleGroup } from 'web/components/widgets/radio-toggle-group'
import { MultipleChoiceOptions } from 'common/love/multiple-choice'
import { useEditableUserInfo } from 'web/hooks/use-editable-user-info'
import { LoadingIndicator } from 'web/components/widgets/loading-indicator'
import { Column } from 'common/supabase/utils'
import { Checkbox } from 'web/components/widgets/checkbox'
import { range } from 'lodash'
import { Select } from 'web/components/widgets/select'
import { City, loverToCity, CityRow, useCitySearch } from './search-location'
import { AddPhotosWidget } from './widgets/add-photos'
import { LoverRow } from 'common/love/lover'
import {Input} from 'web/components/widgets/input'
import {Row} from 'web/components/layout/row'
import {Button} from 'web/components/buttons/button'
import {labelClassName} from 'web/pages/signup'
import {User} from 'common/user'
import {useEditableUserInfo} from 'web/hooks/use-editable-user-info'
import {LoadingIndicator} from 'web/components/widgets/loading-indicator'
import {Column} from 'common/supabase/utils'
import {LoverRow} from 'common/love/lover'
import {SignupBio} from "web/components/bio/editable-bio";
import {JSONContent} from "@tiptap/core";
export const initialRequiredState = {
age: 0,
age: 30,
gender: '',
pref_gender: [],
pref_age_min: 18,
pref_age_max: 100,
pref_relation_styles: [],
wants_kids_strength: 2,
wants_kids_strength: -1,
looking_for_matches: true,
messaging_status: 'open',
visibility: 'member',
city: '',
pinned_url: '',
photo_urls: [],
bio: null,
}
const requiredKeys = Object.keys(
@@ -52,12 +46,9 @@ export const RequiredLoveUserForm = (props: {
onSubmit?: () => void
loverCreatedAlready?: boolean
}) => {
const { user, onSubmit, loverCreatedAlready, setLover, lover, isSubmitting } =
const {user, onSubmit, loverCreatedAlready, setLover, lover, isSubmitting} =
props
const [trans, setTrans] = useState<boolean | undefined>(
lover['gender'].includes('trans')
)
const { updateUsername, updateDisplayName, userInfo, updateUserState } =
const {updateUsername, updateDisplayName, userInfo, updateUserState} =
useEditableUserInfo(user)
const {
@@ -76,56 +67,20 @@ export const RequiredLoveUserForm = (props: {
props.setEditDisplayName && props.setEditDisplayName(name)
}, [name])
const canContinue =
(!lover.looking_for_matches ||
requiredKeys
.map((k) => lover[k])
.every((v) =>
typeof v == 'string'
? v !== ''
: Array.isArray(v)
? v.length > 0
: v !== undefined
)) &&
!loadingUsername &&
!loadingName
function setLoverCity(inputCity: City | undefined) {
if (!inputCity) {
setLover('geodb_city_id', undefined)
setLover('city', '')
setLover('region_code', undefined)
setLover('country', undefined)
setLover('city_latitude', undefined)
setLover('city_longitude', undefined)
} else {
const {
geodb_city_id,
city,
region_code,
country,
latitude: city_latitude,
longitude: city_longitude,
} = inputCity
setLover('geodb_city_id', geodb_city_id)
setLover('city', city)
setLover('region_code', region_code)
setLover('country', country)
setLover('city_latitude', city_latitude)
setLover('city_longitude', city_longitude)
}
}
useEffect(() => {
const currentState = lover['gender']
if (currentState === 'non-binary') {
setTrans(undefined)
} else if (trans && !currentState.includes('trans-')) {
setLover('gender', 'trans-' + currentState.replace('trans-', ''))
} else if (!trans && currentState.includes('trans-')) {
setLover('gender', currentState.replace('trans-', ''))
}
}, [trans, lover['gender']])
const canContinue = true
// const canContinue =
// (!lover.looking_for_matches ||
// requiredKeys
// .map((k) => lover[k])
// .every((v) =>
// typeof v == 'string'
// ? v !== ''
// : Array.isArray(v)
// ? v.length > 0
// : v !== undefined
// )) &&
// !loadingUsername &&
// !loadingName
return (
<>
@@ -140,11 +95,11 @@ export const RequiredLoveUserForm = (props: {
placeholder="Display name"
value={name}
onChange={(e) => {
updateUserState({ name: e.target.value || '' })
updateUserState({name: e.target.value || ''})
}}
onBlur={updateDisplayName}
/>
{loadingName && <LoadingIndicator className={'ml-2'} />}
{loadingName && <LoadingIndicator className={'ml-2'}/>}
</Row>
{errorName && <span className="text-error text-sm">{errorName}</span>}
</Col>
@@ -158,192 +113,26 @@ export const RequiredLoveUserForm = (props: {
placeholder="Username"
value={username}
onChange={(e) => {
updateUserState({ username: e.target.value || '' })
updateUserState({username: e.target.value || ''})
}}
onBlur={updateUsername}
/>
{loadingUsername && <LoadingIndicator className={'ml-2'} />}
{loadingUsername && <LoadingIndicator className={'ml-2'}/>}
</Row>
{errorUsername && (
<span className="text-error text-sm">{errorUsername}</span>
)}
</Col>
<Col className={clsx(colClassName)}>
<label className={clsx(labelClassName)}>Looking for matches</label>
<ChoicesToggleGroup
currentChoice={lover.looking_for_matches}
choicesMap={{
Yes: true,
No: false,
<Col>
<label className={clsx(labelClassName)}>Bio</label>
<SignupBio
onChange={(e: JSONContent) => {
console.log('bio changed', e, lover.bio)
setLover('bio', e)
}}
setChoice={(c) => setLover('looking_for_matches', c)}
/>
</Col>
{(lover.looking_for_matches || loverCreatedAlready) && (
<>
<Col className={clsx(colClassName)}>
<label className={clsx(labelClassName)}>Your location</label>
{lover.city ? (
<Row className="border-primary-500 w-full justify-between rounded border px-4 py-2">
<CityRow
city={loverToCity(lover)}
onSelect={() => {}}
className="pointer-events-none"
/>
<button
className="text-ink-700 hover:text-primary-700 text-sm underline"
onClick={() => {
setLoverCity(undefined)
}}
>
Change
</button>
</Row>
) : (
<CitySearchBox
onCitySelected={(city: City | undefined) => {
setLoverCity(city)
}}
/>
)}
</Col>
<Col className={clsx(colClassName)}>
<label className={clsx(labelClassName)}>Age</label>
<Input
type="number"
placeholder="Age"
value={lover['age'] > 0 ? lover['age'] : undefined}
min={18}
max={100}
onChange={(e) => setLover('age', Number(e.target.value))}
/>
</Col>
<Row className={'items-center gap-2'}>
<Col className={'gap-1'}>
<label className={clsx(labelClassName)}>Gender</label>
<ChoicesToggleGroup
currentChoice={lover['gender'].replace('trans-', '')}
choicesMap={{
Woman: 'female',
Man: 'male',
'Non-binary': 'non-binary',
}}
setChoice={(c) => setLover('gender', c)}
/>
</Col>
{lover.gender !== 'non-binary' && (
<Checkbox
className={'mt-7'}
label={'Trans'}
toggle={setTrans}
checked={trans ?? false}
/>
)}
</Row>
<Col className={clsx(colClassName)}>
<label className={clsx(labelClassName)}>Interested in</label>
<MultiCheckbox
choices={{
Women: 'female',
Men: 'male',
'Non-binary': 'non-binary',
'Trans women': 'trans-female',
'Trans men': 'trans-male',
}}
selected={lover['pref_gender']}
onChange={(selected) => setLover('pref_gender', selected)}
/>
</Col>
<Col className={clsx(colClassName)}>
<label className={clsx(labelClassName)}>Relationship style</label>
<MultiCheckbox
choices={{
Monogamous: 'mono',
Polyamorous: 'poly',
'Open Relationship': 'open',
Other: 'other',
}}
selected={lover['pref_relation_styles']}
onChange={(selected) =>
setLover('pref_relation_styles', selected)
}
/>
</Col>
<Col className={clsx(colClassName)}>
<label className={clsx(labelClassName)}>Partner age range</label>
<Row className={'gap-2'}>
<Col>
<span>Min</span>
<Select
value={lover['pref_age_min']}
onChange={(e) =>
setLover('pref_age_min', Number(e.target.value))
}
className={'w-18 border-ink-300 rounded-md'}
>
{range(18, 100).map((m) => (
<option key={m} value={m}>
{m}
</option>
))}
</Select>
</Col>
<Col>
<span>Max</span>
<Select
value={lover['pref_age_max']}
onChange={(e) =>
setLover('pref_age_max', Number(e.target.value))
}
className={'w-18 border-ink-300 rounded-md'}
>
{range(18, 100).map((m) => (
<option key={m} value={m}>
{m}
</option>
))}
</Select>
</Col>
</Row>
</Col>
<Col className={clsx(colClassName)}>
<label className={clsx(labelClassName)}>
You want to have kids
</label>
<RadioToggleGroup
className={'w-44'}
choicesMap={MultipleChoiceOptions}
setChoice={(choice) => {
setLover('wants_kids_strength', choice)
}}
currentChoice={lover.wants_kids_strength ?? -1}
/>
</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={lover.photo_urls}
pinned_url={lover.pinned_url}
setPhotoUrls={(urls) => setLover('photo_urls', urls)}
setPinnedUrl={(url) => setLover('pinned_url', url)}
/>
</Col>
</>
)}
{onSubmit && (
<Row className={'justify-end'}>
@@ -359,50 +148,4 @@ export const RequiredLoveUserForm = (props: {
</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>
</>
)
}
}

View File

@@ -184,8 +184,9 @@ export function TextEditor(props: {
children?: ReactNode // additional toolbar buttons
className?: string
onBlur?: () => void
onChange?: () => void
}) {
const { editor, simple, hideEmbed, children, className, onBlur } = props
const { editor, simple, hideEmbed, children, className, onBlur, onChange } = props
return (
// matches input styling
@@ -197,7 +198,7 @@ export function TextEditor(props: {
>
<FloatingFormatMenu editor={editor} advanced={!simple} />
<div className={clsx('max-h-[69vh] overflow-auto')}>
<EditorContent editor={editor} onBlur={onBlur} />
<EditorContent editor={editor} onBlur={onBlur} onChange={onChange} />
</div>
<StickyFormatMenu editor={editor} hideEmbed={hideEmbed}>

View File

@@ -68,6 +68,7 @@ export default function SignupPage() {
: undefined
setIsSubmitting(true)
console.log('loverForm', loverForm)
const lover = await api(
'create-lover',
removeUndefinedProps({