Files
Compass/web/components/profile/profile-header.tsx
Okechi Jones-Williams e115df8e11 [Onboarding Flow] Answer compatibility questions in the test suite for onboarding (#35)
* Fixed Type errors

* Added Database checks to the onboarding flow

* Updated Onboarding flow
Changed type ChildrenExpectation so that it can be used for database verification

* Added compatibility page setup
Added more compatibility questions

* Finished up the onboarding flow suite
Added compatibility question tests and verifications
Updated tests to cover Keywords and Headline changes recently made
Updated tests to cover all of the big5 personality traits

* Apply suggestions from code review

---------

Co-authored-by: Martin Braquet <martin.braquet@gmail.com>
2026-03-04 11:05:56 +01:00

270 lines
9.7 KiB
TypeScript

import {
EllipsisHorizontalIcon,
EyeIcon,
LockClosedIcon,
PencilIcon,
} from '@heroicons/react/24/outline'
import clsx from 'clsx'
import {debug} from 'common/logger'
import {Profile} from 'common/profiles/profile'
import {User, UserActivity} from 'common/user'
import {capitalize} from 'lodash'
import Link from 'next/link'
import Router from 'next/router'
import React, {useState} from 'react'
import toast from 'react-hot-toast'
import {Button} from 'web/components/buttons/button'
import {MoreOptionsUserButton} from 'web/components/buttons/more-options-user-button'
import DropdownMenu from 'web/components/comments/dropdown-menu'
import {Col} from 'web/components/layout/col'
import {Row} from 'web/components/layout/row'
import {SendMessageButton} from 'web/components/messaging/send-message-button'
import HideProfileButton from 'web/components/widgets/hide-profile-button'
import {linkClass} from 'web/components/widgets/site-link'
import {StarButton} from 'web/components/widgets/star-button'
import {Tooltip} from 'web/components/widgets/tooltip'
import {useUser} from 'web/hooks/use-user'
import {updateProfile} from 'web/lib/api'
import {useT} from 'web/lib/locale'
import {track} from 'web/lib/service/analytics'
import {disableProfile} from 'web/lib/util/disable'
import {ShareProfileButton} from '../widgets/share-profile-button'
import ProfilePrimaryInfo from './profile-primary-info'
import {VisibilityConfirmationModal} from './visibility-confirmation-modal'
export default function ProfileHeader(props: {
user: User
userActivity?: UserActivity
profile: Profile
simpleView?: boolean
starredUserIds: string[]
refreshStars: () => Promise<void>
isHiddenFromMe: boolean | undefined
showMessageButton: boolean
refreshProfile: () => void
}) {
const {
user,
profile,
userActivity,
simpleView,
starredUserIds,
refreshStars,
showMessageButton,
refreshProfile,
isHiddenFromMe,
} = props
const currentUser = useUser()
const isCurrentUser = currentUser?.id === user.id
const [showVisibilityModal, setShowVisibilityModal] = useState(false)
const disabled = profile.disabled
const t = useT()
debug('ProfileProfileHeader', {
user,
profile,
userActivity,
currentUser,
})
let tooltipText = undefined
if (!profile.allow_direct_messaging) {
tooltipText = t(
'profile.header.tooltip.direct_messaging_off',
'{name} turned off direct messaging',
{
name: user.name,
},
)
}
if (!profile.allow_direct_messaging && profile.allow_interest_indicating) {
tooltipText =
tooltipText +
t(
'profile.header.tooltip.can_express_interest',
', but you can still express interest at the end of the profile',
)
}
return (
<Col className="w-full">
{currentUser && !isCurrentUser && isHiddenFromMe && (
<div className="guidance">
{t(
'profile_grid.hidden_notice',
"You hid this person, so they don't appear in your search results.",
)}
</div>
)}
{currentUser && isCurrentUser && disabled && (
<div className="text-red-500">
{t(
'profile.header.disabled_notice',
'You disabled your profile, so no one else can access it.',
)}
</div>
)}
<Row className={clsx('flex-wrap justify-between gap-2 py-1')}>
<Row className="items-center gap-1">
<Col className="gap-1">
<Row className="items-center gap-1 text-xl" data-testid="profile-display-name-age">
{/*{!isCurrentUser && <OnlineIcon last_online_time={userActivity?.last_online_time}/>}*/}
<span>
{simpleView ? (
<Link className={linkClass} href={`/${user.username}`}>
<span className="font-semibold">{user.name}</span>
</Link>
) : (
<span className="font-semibold">{user.name}</span>
)}
{profile.age && `, ${t('profile.header.age', '{age}', {age: profile.age})}`}
</span>
</Row>
<ProfilePrimaryInfo profile={profile} />
</Col>
</Row>
{currentUser && isCurrentUser ? (
<Row className={'items-center gap-4'}>
<ShareProfileButton className="sm:flex" username={user.username} />
<Tooltip text={t('more_options_user.edit_profile', 'Edit profile')} noTap>
<Button
data-testid="profile-edit"
color={'gray-outline'}
onClick={() => {
track('editprofile', {userId: user.id})
Router.push('profile')
}}
size="sm"
>
<PencilIcon className=" h-4 w-4" />
</Button>
</Tooltip>
<Tooltip
text={t('more_options_user.profile_options', 'Profile options')}
noTap
testId="profile-options"
>
<DropdownMenu
menuWidth={'w-52'}
icon={<EllipsisHorizontalIcon className="h-5 w-5" aria-hidden="true" />}
items={[
{
name:
profile.visibility === 'member'
? t('profile.header.menu.list_public', 'List Profile Publicly')
: t('profile.header.menu.limit_members', 'Limit to Members Only'),
icon:
profile.visibility === 'member' ? (
<EyeIcon className="h-4 w-4" />
) : (
<LockClosedIcon className="h-4 w-4" />
),
onClick: () => setShowVisibilityModal(true),
},
{
name: disabled
? t('profile.header.menu.enable_profile', 'Enable profile')
: t('profile.header.menu.disable_profile', 'Disable profile'),
icon: null,
onClick: async () => {
const confirmed = true // confirm(
// 'Are you sure you want to disable your profile? This will hide your profile from searches and listings..'
// )
if (confirmed) {
toast
.promise(disableProfile(!disabled), {
loading: disabled
? t('profile.header.toast.enabling', 'Enabling profile...')
: t('profile.header.toast.disabling', 'Disabling profile...'),
success: () => {
return disabled
? t('profile.header.toast.enabled', 'Profile enabled')
: t('profile.header.toast.disabled', 'Profile disabled')
},
error: () => {
return disabled
? t(
'profile.header.toast.failed_enable',
'Failed to enable profile',
)
: t(
'profile.header.toast.failed_disable',
'Failed to disable profile',
)
},
})
.then(() => {
refreshProfile()
})
.catch(() => {
// return false
})
}
},
},
]}
/>
</Tooltip>
</Row>
) : (
<Row className="items-center gap-1 sm:gap-2">
{currentUser && !isCurrentUser && <HideProfileButton hiddenUserId={user.id} />}
<ShareProfileButton className="sm:flex" username={user.username} />
{currentUser && (
<StarButton
targetProfile={profile}
isStarred={starredUserIds.includes(user.id)}
refresh={refreshStars}
/>
)}
{currentUser && showMessageButton && (
<SendMessageButton
toUser={user}
currentUser={currentUser}
tooltipText={tooltipText}
disabled={!profile.allow_direct_messaging}
/>
)}
<MoreOptionsUserButton user={user} />
</Row>
)}
</Row>
{/*<Row className="justify-end sm:hidden">*/}
{/* <ShareProfileButton username={user.username} />*/}
{/*</Row>*/}
<VisibilityConfirmationModal
open={showVisibilityModal}
setOpen={setShowVisibilityModal}
currentVisibility={profile.visibility}
onConfirm={async () => {
const newVisibility = profile.visibility === 'member' ? 'public' : 'member'
await updateProfile({visibility: newVisibility})
refreshProfile()
}}
/>
{profile.headline && (
<div className="italic max-w-3xl px-4 py-3" data-testid="profile-headline">
{profile.headline}
</div>
)}
<Row className={'px-4 gap-2 flex-wrap py-2'} data-testid="profile-keywords">
{profile.keywords?.map(capitalize)?.map((tag, i) => (
<span
key={i}
className={'bg-canvas-100'}
style={{
padding: '6px 16px',
borderRadius: '20px',
}}
>
{tag.trim()}
</span>
))}
</Row>
</Col>
)
}