admin/components/consumer/UserIdentity.tsx
alisaza e1eaf5eff5 feat: initial ghabilee-admin backoffice app
Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js
app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
2026-09-05 13:12:59 +03:30

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