Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
106 lines
2.9 KiB
TypeScript
106 lines
2.9 KiB
TypeScript
'use client'
|
|
|
|
/**
|
|
* UserIdentity — avatar + display name for any user (host or guest).
|
|
*
|
|
* Purpose
|
|
* - Shared “who is this person?” row: wraps `UserAvatar` and a formatted name.
|
|
* - Optional `userId` turns the block into a link to `/u/:id`.
|
|
* - Works for hosts and guests; pass `nameFallback` for role-specific empty names.
|
|
*
|
|
* Related
|
|
* - Avatar only: `UserAvatar`
|
|
* - Name formatting: `formatPersonName`
|
|
*/
|
|
|
|
import type { ReactNode } from 'react'
|
|
|
|
import Link from 'next/link'
|
|
|
|
import UserAvatar, { type UserAvatarVariant } from '@/components/consumer/UserAvatar'
|
|
import { SEO_ROUTES } from '@/constants/routes'
|
|
import { formatPersonName } from '@/helpers'
|
|
import { cn } from '@/lib/cn'
|
|
import { texts, format } from '@/texts'
|
|
|
|
export type { UserAvatarVariant }
|
|
|
|
export interface UserIdentityProps {
|
|
/** Public profile id — when set, avatar + name link to `/u/:id`. */
|
|
userId?: string | null
|
|
firstName?: string | null
|
|
lastName?: string | null
|
|
/** Pre-formatted name; wins over first/last when provided. */
|
|
name?: string | null
|
|
/** Fallback when name parts are empty (e.g. guest vs host label). */
|
|
nameFallback?: string
|
|
src?: string | null
|
|
variant?: UserAvatarVariant
|
|
/** Pixel size for the avatar (width and height). */
|
|
size?: number
|
|
avatarClassName?: string
|
|
iconClassName?: string
|
|
/** Optional line under the name (e.g. relative time). */
|
|
subtitle?: ReactNode
|
|
/** Extra content beside the name (e.g. verified badge). */
|
|
nameExtra?: ReactNode
|
|
nameClassName?: string
|
|
subtitleClassName?: string
|
|
className?: string
|
|
}
|
|
|
|
const UserIdentity = ({
|
|
userId,
|
|
firstName,
|
|
lastName,
|
|
name,
|
|
nameFallback = texts.events.guest,
|
|
src,
|
|
variant = 'circle',
|
|
size = 36,
|
|
avatarClassName,
|
|
iconClassName,
|
|
subtitle,
|
|
nameExtra,
|
|
nameClassName,
|
|
subtitleClassName,
|
|
className,
|
|
}: UserIdentityProps) => {
|
|
const displayName = name?.trim() || formatPersonName(firstName, lastName, nameFallback)
|
|
const content = (
|
|
<>
|
|
<UserAvatar
|
|
alt={displayName}
|
|
className={avatarClassName}
|
|
iconClassName={iconClassName}
|
|
size={size}
|
|
src={src}
|
|
variant={variant}
|
|
/>
|
|
<div className="min-w-0 flex-1">
|
|
<div className="flex min-w-0 items-center gap-1">
|
|
<p className={cn('truncate text-sm font-medium text-secondary-30', nameClassName)}>{displayName}</p>
|
|
{nameExtra}
|
|
</div>
|
|
{subtitle ? <div className={cn(subtitleClassName)}>{subtitle}</div> : null}
|
|
</div>
|
|
</>
|
|
)
|
|
|
|
if (userId) {
|
|
return (
|
|
<Link
|
|
aria-label={format(texts.events.hostProfileAria, { name: displayName })}
|
|
className={cn('consumer-focus-ring flex min-w-0 items-center gap-2 rounded-lg', className)}
|
|
href={SEO_ROUTES.USER(userId)}
|
|
>
|
|
{content}
|
|
</Link>
|
|
)
|
|
}
|
|
|
|
return <div className={cn('flex min-w-0 items-center gap-2', className)}>{content}</div>
|
|
}
|
|
|
|
export default UserIdentity
|