mirror of
https://github.com/CompassConnections/Compass.git
synced 2026-03-26 02:21:06 -04:00
206 lines
6.2 KiB
TypeScript
206 lines
6.2 KiB
TypeScript
import {Editor} from '@tiptap/core'
|
|
import {MIN_BIO_LENGTH} from 'common/constants'
|
|
import {MAX_DESCRIPTION_LENGTH} from 'common/envs/constants'
|
|
import {Profile, ProfileWithoutUser} from 'common/profiles/profile'
|
|
import {richTextToString} from 'common/util/parse'
|
|
import {tryCatch} from 'common/util/try-catch'
|
|
import {useRouter} from 'next/router'
|
|
import {useEffect, useState} from 'react'
|
|
import ReactMarkdown from 'react-markdown'
|
|
import {Button} from 'web/components/buttons/button'
|
|
import {Col} from 'web/components/layout/col'
|
|
import {Row} from 'web/components/layout/row'
|
|
import {TextEditor, useTextEditor} from 'web/components/widgets/editor'
|
|
import {NewTabLink} from 'web/components/widgets/new-tab-link'
|
|
import {ShowMore} from 'web/components/widgets/show-more'
|
|
import {updateProfile} from 'web/lib/api'
|
|
import {useT} from 'web/lib/locale'
|
|
import {track} from 'web/lib/service/analytics'
|
|
|
|
export function BioTips({onClick}: {onClick?: () => void}) {
|
|
const t = useT()
|
|
const tips = t(
|
|
'profile.bio.tips_list',
|
|
`
|
|
- Your core values, interests, and activities
|
|
- Personality traits, what makes you unique and what you care about
|
|
- Connection goals (collaborative, friendship, romantic)
|
|
- Expectations and boundaries
|
|
- Availability, how to contact you or start a conversation (email, social media, etc.)
|
|
- Optional: romantic preferences, lifestyle habits, and conversation starters
|
|
`,
|
|
)
|
|
|
|
let href = '/tips-bio'
|
|
|
|
const router = useRouter()
|
|
if (router.pathname === '/signup') {
|
|
href += '?fromSignup=true'
|
|
}
|
|
|
|
return (
|
|
<ShowMore
|
|
labelClosed={t('profile.bio.tips', 'Tips')}
|
|
labelOpen={t('profile.bio.hide_info', 'Hide info')}
|
|
className={'custom-link text-sm prose prose-neutral dark:prose-invert'}
|
|
>
|
|
<p>
|
|
{t(
|
|
'profile.bio.tips_intro',
|
|
'Write a clear and engaging bio to help others understand who you are and the connections you seek. Include:',
|
|
)}
|
|
</p>
|
|
<ReactMarkdown>{tips}</ReactMarkdown>
|
|
<NewTabLink href={href} onClick={onClick}>
|
|
{t('profile.bio.tips_link', 'Read full tips for writing a high-quality bio')}
|
|
</NewTabLink>
|
|
</ShowMore>
|
|
)
|
|
}
|
|
|
|
export function EditableBio(props: {profile: Profile; onSave: () => void; onCancel?: () => void}) {
|
|
const {profile, onCancel, onSave} = props
|
|
const [editor, setEditor] = useState<any>(null)
|
|
const [textLength, setTextLength] = useState(0)
|
|
const t = useT()
|
|
|
|
const hideButtons = textLength === 0 && !profile.bio
|
|
|
|
const saveBio = async () => {
|
|
if (!editor) return
|
|
// console.log(editor.getText().length)
|
|
const {error} = await tryCatch(
|
|
updateProfile({
|
|
bio: editor.getJSON(),
|
|
bio_length: editor.getText().length,
|
|
}),
|
|
)
|
|
|
|
if (error) {
|
|
console.error(error)
|
|
return
|
|
}
|
|
|
|
track('edited profile bio')
|
|
}
|
|
|
|
return (
|
|
<Col className="relative w-full">
|
|
<BaseBio
|
|
defaultValue={profile.bio}
|
|
onEditor={(e) => {
|
|
setEditor(e)
|
|
if (e) setTextLength(e.getText().length)
|
|
e?.on('update', () => {
|
|
setTextLength(e.getText().length)
|
|
})
|
|
}}
|
|
/>
|
|
{!hideButtons && (
|
|
<Row className="absolute bottom-1 right-1 justify-between gap-2">
|
|
{onCancel && (
|
|
<Button size="xs" color="gray-outline" onClick={onCancel}>
|
|
{t('profile.bio.cancel', 'Cancel')}
|
|
</Button>
|
|
)}
|
|
<Button
|
|
size="xs"
|
|
onClick={async () => {
|
|
await saveBio()
|
|
onSave()
|
|
}}
|
|
>
|
|
{t('profile.bio.save', 'Save')}
|
|
</Button>
|
|
</Row>
|
|
)}
|
|
</Col>
|
|
)
|
|
}
|
|
|
|
export function SignupBio(props: {
|
|
onChange: (e: Editor) => void
|
|
profile?: ProfileWithoutUser | undefined
|
|
onClickTips?: () => void
|
|
onEditor?: (editor: any) => void
|
|
}) {
|
|
const {onChange, profile, onClickTips, onEditor} = props
|
|
const [editor, setEditor] = useState<any>(null)
|
|
|
|
// Keep the editor content in sync with profile.bio when it becomes available
|
|
useEffect(() => {
|
|
if (!editor) return
|
|
const profileText = profile?.bio ? richTextToString(profile.bio as any) : ''
|
|
const currentText = editor?.getText?.() ?? ''
|
|
// Only update if the underlying text differs to avoid clobbering user input unnecessarily
|
|
if (profileText !== currentText) {
|
|
editor.commands.setContent(profile?.bio ?? '')
|
|
}
|
|
}, [profile?.bio, editor])
|
|
|
|
return (
|
|
<Col className="relative w-full">
|
|
<BaseBio
|
|
defaultValue={profile?.bio}
|
|
onBlur={(editor) => {
|
|
if (!editor) return
|
|
onChange(editor)
|
|
}}
|
|
onClickTips={onClickTips}
|
|
onEditor={(e) => {
|
|
setEditor(e)
|
|
onEditor?.(e)
|
|
}}
|
|
/>
|
|
</Col>
|
|
)
|
|
}
|
|
|
|
interface BaseBioProps {
|
|
defaultValue?: any
|
|
onBlur?: (editor: any) => void
|
|
onEditor?: (editor: any) => void
|
|
onClickTips?: () => void
|
|
}
|
|
|
|
export function BaseBio({defaultValue, onBlur, onEditor, onClickTips}: BaseBioProps) {
|
|
const t = useT()
|
|
const editor = useTextEditor({
|
|
// extensions: [StarterKit],
|
|
max: MAX_DESCRIPTION_LENGTH,
|
|
defaultValue: defaultValue,
|
|
placeholder: t(
|
|
'profile.bio.placeholder',
|
|
"Tell us all the details about yourself — and what you're looking for!",
|
|
),
|
|
})
|
|
const textLength = editor?.getText().length ?? 0
|
|
const remainingChars = MIN_BIO_LENGTH - textLength
|
|
|
|
useEffect(() => {
|
|
onEditor?.(editor)
|
|
}, [editor, onEditor])
|
|
|
|
return (
|
|
<div>
|
|
{textLength < MIN_BIO_LENGTH && (
|
|
<p className={'guidance hidden'}>
|
|
{remainingChars === 1
|
|
? t(
|
|
'profile.bio.add_characters_one',
|
|
'Add {count} more character so you can appear in search results—or take your time and start by exploring others.',
|
|
{count: remainingChars},
|
|
)
|
|
: t(
|
|
'profile.bio.add_characters_many',
|
|
'Add {count} more characters so you can appear in search results—or take your time and start by exploring others.',
|
|
{count: remainingChars},
|
|
)}
|
|
</p>
|
|
)}
|
|
<BioTips onClick={onClickTips} />
|
|
<TextEditor editor={editor} onBlur={() => onBlur?.(editor)} />
|
|
</div>
|
|
)
|
|
}
|