Files
Compass/web/components/widgets/ships-display.tsx
Martin Braquet ba9b3cfb06 Add pretty formatting (#29)
* Test

* Add pretty formatting

* Fix Tests

* Fix Tests

* Fix Tests

* Fix

* Add pretty formatting fix

* Fix

* Test

* Fix tests

* Clean typeckech

* Add prettier check

* Fix api tsconfig

* Fix api tsconfig

* Fix tsconfig

* Fix

* Fix

* Prettier
2026-02-20 17:32:27 +01:00

170 lines
5.6 KiB
TypeScript

import clsx from 'clsx'
import {ShipData} from 'common/api/profile-types'
import {Profile} from 'common/profiles/profile'
import {User} from 'common/user'
import {groupBy, max, orderBy} from 'lodash'
import {useState} from 'react'
import {Col} from 'web/components/layout/col'
import {Modal, MODAL_CLASS} from 'web/components/layout/modal'
import {Row} from 'web/components/layout/row'
import {Avatar, EmptyAvatar} from 'web/components/widgets/avatar'
import {Carousel} from 'web/components/widgets/carousel'
import {UserLink} from 'web/components/widgets/user-link'
import {useProfileByUserId} from 'web/hooks/use-profile'
import {useUser} from 'web/hooks/use-user'
import {useUserById} from 'web/hooks/use-user-supabase'
import {hasShipped} from 'web/lib/util/ship-util'
import {MatchAvatars} from '../matches/match-avatars'
import {Subtitle} from './profile-subtitle'
import {ShipButton} from './ship-button'
export const ShipsList = (props: {
label: string
ships: ShipData[]
profileProfile: Profile
refreshShips: () => Promise<void>
}) => {
const {label, ships, profileProfile, refreshShips} = props
const shipsWithTargetId = ships.map(({target1_id, target2_id, ...other}) => ({
...other,
target1_id,
target2_id,
targetId: target1_id === profileProfile.user_id ? target2_id : target1_id,
}))
const shipsByTargetId = groupBy(shipsWithTargetId, (s) => s.targetId)
const sortedTargetIds = orderBy(
Object.keys(shipsByTargetId),
(targetId) => max(shipsByTargetId[targetId].map((s) => s.created_time)),
'desc',
)
return (
<Col className="gap-1">
<Subtitle>{label}</Subtitle>
{sortedTargetIds.length > 0 ? (
<Carousel className="w-full" labelsParentClassName="gap-0">
{sortedTargetIds.map((targetId) => {
return (
<ShipsTargetDisplay
key={targetId}
ships={shipsByTargetId[targetId]}
profileProfile={profileProfile}
refreshShips={refreshShips}
/>
)
})}
</Carousel>
) : (
<div className="text-ink-500">None</div>
)}
</Col>
)
}
const ShipsTargetDisplay = (props: {
ships: (ShipData & {targetId: string})[]
refreshShips: () => Promise<void>
profileProfile: Profile
className?: string
}) => {
const {ships, refreshShips, profileProfile, className} = props
const {targetId} = ships[0]
const targetProfile = useProfileByUserId(targetId)
const targetUser = useUserById(targetId) as User | null | undefined
const [open, setOpen] = useState(false)
const currentUser = useUser()
const shipped = hasShipped(currentUser, profileProfile.user_id, targetId, ships)
return (
<>
<button
className={clsx(className, 'group flex flex-col items-center gap-1')}
onClick={() => setOpen(!open)}
>
<UserAvatar className="-ml-1 first:ml-0" userId={targetId} />
<div className="text-ink-500 group-hover:underline group-active:underline">
x {ships.length}
</div>
</button>
{open && (
<Modal open={open} setOpen={setOpen}>
<Col className={clsx(MODAL_CLASS, 'relative')}>
{targetProfile && targetUser && (
<>
<MatchAvatars
profileProfile={profileProfile}
matchedProfile={{...targetProfile, user: targetUser}}
/>
<Row className="w-full items-baseline justify-stretch gap-2 text-lg font-semibold">
<Row className="flex-1 justify-end">
<UserLink hideBadge user={profileProfile.user} noLink />
</Row>
&
<Row className="flex-1 justify-start">
<UserLink hideBadge user={targetUser} />
</Row>
</Row>
</>
)}
<Col className="gap-2 self-start">
<div className="text-ink-600 text-lg font-semibold">
Shipping them ({ships.length})
</div>
<Col className="gap-2">
{ships.map((ship) => (
<UserInfoRow key={ship.creator_id} userId={ship.creator_id} />
))}
</Col>
</Col>
{currentUser &&
profileProfile.user_id !== currentUser?.id &&
targetId !== currentUser?.id && (
<Row className="sticky bottom-[70px] right-0 mr-1 self-end lg:bottom-6">
<ShipButton
shipped={shipped}
targetId1={profileProfile.user_id}
targetId2={targetId}
refresh={refreshShips}
/>
</Row>
)}
</Col>
</Modal>
)}
</>
)
}
const UserAvatar = (props: {userId: string; className?: string}) => {
const {userId, className} = props
const profile = useProfileByUserId(userId)
const user = useUserById(userId)
if (!profile || !profile.pinned_url) return <EmptyAvatar className={className} size={10} />
return (
<Avatar className={className} avatarUrl={profile.pinned_url} username={user?.username} noLink />
)
}
const UserInfoRow = (props: {userId: string; className?: string}) => {
const {userId, className} = props
const user = useUserById(userId)
const profile = useProfileByUserId(userId)
return (
<Row className={clsx(className, 'items-center gap-2')}>
{!profile || !profile.pinned_url ? (
<EmptyAvatar size={10} />
) : (
<Avatar avatarUrl={profile.pinned_url} username={user?.username} />
)}
{user && <UserLink user={user} hideBadge />}
</Row>
)
}