EquippedBadges.tsx 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. 'use client';
  2. // D3 장착 지위 아이템 렌더 — 댓글/게시글 작성자·프로필 옆 배지 + 닉네임 이펙트.
  3. // EquippedItem[] 을 직접 받는 프레젠테이션 컴포넌트 (배치 조회는 상위에서 useEquippedItems 로).
  4. import '@/app/styles/reward.scss';
  5. import { EquipSlot, ItemKind, parseEffectPayload } from '@/types/reward';
  6. import type { EquippedItem } from '@/types/reward';
  7. interface BadgeProps {
  8. items: EquippedItem[]|undefined|null;
  9. }
  10. // 배지 슬롯만 렌더 (닉네임 옆 아이콘/라벨). 닉네임 이펙트는 NicknameText 로 분리.
  11. export function EquippedBadge({ items }: BadgeProps)
  12. {
  13. if (!items || items.length === 0) {
  14. return null;
  15. }
  16. const badge = items.find(i => i.slot === EquipSlot.Badge || i.kind === ItemKind.Badge);
  17. if (!badge) {
  18. return null;
  19. }
  20. const payload = parseEffectPayload(badge.effectPayload);
  21. const label = payload?.label ?? badge.productName;
  22. return (
  23. <span
  24. className='equipped-badge'
  25. title={badge.productName}
  26. style={payload?.background ? { background: payload.background, color: payload.textColor ?? payload.color } : undefined}
  27. >
  28. {badge.thumbnail ? (
  29. // eslint-disable-next-line @next/next/no-img-element
  30. <img src={badge.thumbnail} alt='' className='equipped-badge__icon' />
  31. ) : payload?.icon ? (
  32. <span className='equipped-badge__emoji' aria-hidden='true'>{payload.icon}</span>
  33. ) : null}
  34. <span className='equipped-badge__label'>{label}</span>
  35. </span>
  36. );
  37. }
  38. interface NicknameProps {
  39. name: string;
  40. items: EquippedItem[]|undefined|null;
  41. className?: string;
  42. }
  43. // 닉네임 이펙트 슬롯 적용 — 색상/강조. 이펙트 없으면 평범한 텍스트.
  44. export function NicknameText({ name, items, className }: NicknameProps)
  45. {
  46. const effect = items?.find(i => i.slot === EquipSlot.NicknameEffect || i.kind === ItemKind.NicknameEffect);
  47. const payload = parseEffectPayload(effect?.effectPayload);
  48. if (!effect || !payload) {
  49. return <span className={className}>{name}</span>;
  50. }
  51. const animCls = payload.animation ? ` nickname-effect--${payload.animation}` : '';
  52. return (
  53. <span
  54. className={`nickname-effect${animCls}${className ? ` ${className}` : ''}`}
  55. style={{ color: payload.color ?? payload.textColor }}
  56. >
  57. {name}
  58. </span>
  59. );
  60. }
  61. interface RowProps {
  62. name: string;
  63. items: EquippedItem[]|undefined|null;
  64. className?: string;
  65. }
  66. // 닉네임 이펙트 + 배지를 한 줄로 (작성자 표시 공용).
  67. export function EquippedNickname({ name, items, className }: RowProps)
  68. {
  69. return (
  70. <span className={`inline-flex items-center gap-1 align-middle${className ? ` ${className}` : ''}`}>
  71. <NicknameText name={name} items={items} />
  72. <EquippedBadge items={items} />
  73. </span>
  74. );
  75. }