|
| 1 | +'use client'; |
| 2 | + |
| 3 | +import { useState } from 'react'; |
| 4 | +import Image from 'next/image'; |
| 5 | +import cn from '@/lib/cn'; |
| 6 | +import { ProfileImageProps } from '@/types/mypageTypes'; |
| 7 | +import PenIcon from '@assets/svg/pen'; |
| 8 | +import ProfileDefaultIcon from '@assets/svg/profile-default'; |
| 9 | + |
| 10 | +/** |
| 11 | + * @component ProfileImage |
| 12 | + * @description |
| 13 | + * 마이페이지 전용 프로필 이미지 컴포넌트입니다. |
| 14 | + * |
| 15 | + * @param {ProfileImageProps} props - ProfileImage 컴포넌트의 props |
| 16 | + * @param {string} [props.src] - 프로필 이미지 URL |
| 17 | + * @param {string} [props.alt] - 이미지 alt 텍스트 |
| 18 | + * @param {string} [props.nickname='사용자'] - 사용자 닉네임 |
| 19 | + * @param {boolean} [props.showEditButton=false] - 편집 버튼 표시 여부 |
| 20 | + * @param {() => void} [props.onEdit] - 편집 버튼 클릭 핸들러 |
| 21 | + * @param {string} [props.className] - 추가 CSS 클래스 |
| 22 | + */ |
| 23 | + |
| 24 | +function isValidUrl(url: string): boolean { |
| 25 | + if (!url || url.trim() === '') return false; |
| 26 | + |
| 27 | + try { |
| 28 | + new URL(url); |
| 29 | + return true; |
| 30 | + } catch { |
| 31 | + return false; |
| 32 | + } |
| 33 | +} |
| 34 | + |
| 35 | +export default function ProfileImage({ |
| 36 | + src, |
| 37 | + alt, |
| 38 | + nickname = '사용자', |
| 39 | + showEditButton = false, |
| 40 | + onEdit, |
| 41 | + className, |
| 42 | +}: ProfileImageProps) { |
| 43 | + const [imageError, setImageError] = useState(false); |
| 44 | + |
| 45 | + // 이미지 로딩 에러 핸들러 |
| 46 | + const handleImageError = () => { |
| 47 | + setImageError(true); |
| 48 | + }; |
| 49 | + |
| 50 | + // URL 유효성 검사 |
| 51 | + const hasValidImage = src && isValidUrl(src) && !imageError; |
| 52 | + |
| 53 | + return ( |
| 54 | + <div className={cn('relative inline-block', className)}> |
| 55 | + {/* 프로필 이미지 컨테이너 */} |
| 56 | + <div className='relative h-160 w-160 overflow-hidden rounded-full bg-gray-200 shadow-lg'> |
| 57 | + {hasValidImage ? ( |
| 58 | + <Image |
| 59 | + src={src} |
| 60 | + alt={alt || `${nickname}의 프로필 이미지`} |
| 61 | + fill |
| 62 | + className='object-cover' |
| 63 | + onError={handleImageError} |
| 64 | + sizes='160px' |
| 65 | + /> |
| 66 | + ) : ( |
| 67 | + // 기본 프로필 아이콘 |
| 68 | + <div className='flex h-full w-full items-center justify-center'> |
| 69 | + <ProfileDefaultIcon size={160} /> |
| 70 | + </div> |
| 71 | + )} |
| 72 | + </div> |
| 73 | + |
| 74 | + {/* 편집 버튼 */} |
| 75 | + {showEditButton && ( |
| 76 | + <button |
| 77 | + onClick={onEdit} |
| 78 | + className='absolute right-0 bottom-0 flex h-32 w-32 items-center justify-center rounded-full bg-green-300 shadow-lg transition-colors hover:bg-green-200 focus:ring-2 focus:ring-green-300 focus:ring-offset-2 focus:outline-none' |
| 79 | + aria-label='프로필 이미지 편집' |
| 80 | + type='button' |
| 81 | + > |
| 82 | + <PenIcon size={20} /> |
| 83 | + </button> |
| 84 | + )} |
| 85 | + </div> |
| 86 | + ); |
| 87 | +} |
0 commit comments