mirror of
https://github.com/CompassConnections/Compass.git
synced 2025-12-23 22:18:43 -05:00
Make all complete-profile fields optional and Pre-Save lover
This commit is contained in:
@@ -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 })
|
||||
)
|
||||
|
||||
@@ -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)`
|
||||
),
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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}>
|
||||
|
||||
@@ -68,6 +68,7 @@ export default function SignupPage() {
|
||||
: undefined
|
||||
|
||||
setIsSubmitting(true)
|
||||
console.log('loverForm', loverForm)
|
||||
const lover = await api(
|
||||
'create-lover',
|
||||
removeUndefinedProps({
|
||||
|
||||
Reference in New Issue
Block a user