Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions next.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
// TODO: 백엔드 프로필 이미지 호스트가 확정되면 images.remotePatterns에 허용 도메인 추가 필요
turbopack: {
rules: {
'*.svg': [
Expand Down
2 changes: 2 additions & 0 deletions src/entities/user/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { Profile } from './ui/Profile';
export type { UserProfile } from './model/types';
4 changes: 4 additions & 0 deletions src/entities/user/model/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface UserProfile {
name: string;
profileImageUrl: string;
}
26 changes: 26 additions & 0 deletions src/entities/user/ui/Profile.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { DefaultProfileImage } from '@/shared/assets/images';

import { Profile } from './Profile';

import type { Meta, StoryObj } from '@storybook/nextjs';

const meta = {
title: 'Entities/User/Profile',
component: Profile,
args: {
name: '이서정',
profileImageUrl: DefaultProfileImage.src,
},
} satisfies Meta<typeof Profile>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Default: Story = {};

export const Fallback: Story = {
args: {
profileImageUrl: '/invalid-profile-image.png',
},
};
37 changes: 37 additions & 0 deletions src/entities/user/ui/Profile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { ImageProps } from 'next/image';

import { DefaultProfileImage } from '@/shared/assets/images';

import { ProfileImage } from './ProfileImage';

import type { UserProfile } from '../model/types';

interface ProfileProps extends UserProfile {
fallbackImageSrc?: ImageProps['src'];
imageAlt?: string;
}

/**
* ## Profile
*
* @description
* 로그인 사용자 프로필을 이름과 원형 프로필 이미지로 표시하는 도메인 UI입니다.
*
* @param name - 사용자 이름
* @param profileImageUrl - 백엔드에서 전달받은 프로필 이미지 URL
* @param fallbackImageSrc - 프로필 이미지 로딩 실패 시 표시할 대체 이미지
* @param imageAlt - 이미지 대체 텍스트. 전달하지 않으면 사용자 이름 기반으로 생성합니다.
*/
export function Profile({
name,
profileImageUrl,
fallbackImageSrc = DefaultProfileImage,
imageAlt = `${name} 프로필 이미지`,
}: ProfileProps) {
return (
<div className="flex items-center gap-3">
<ProfileImage alt={imageAlt} fallbackSrc={fallbackImageSrc} src={profileImageUrl} />
<span className="body-16 truncate font-medium text-white">{name}</span>
</div>
);
}
38 changes: 38 additions & 0 deletions src/entities/user/ui/ProfileImage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use client';

import { useState } from 'react';

import Image from 'next/image';
import type { ImageProps } from 'next/image';

interface ProfileImageProps {
alt: string;
fallbackSrc: ImageProps['src'];
src: string;
}

export function ProfileImage({ alt, fallbackSrc, src }: ProfileImageProps) {
const [failedImageSrc, setFailedImageSrc] = useState<string | null>(null);
const imageSrc = failedImageSrc === src ? fallbackSrc : src;

const handleError = () => {
if (imageSrc === fallbackSrc) {
return;
}

setFailedImageSrc(src);
};

return (
<Image
alt={alt}
className="size-8 shrink-0 rounded-full border border-white object-cover"
fetchPriority="high"
height={32}
loading="eager"
onError={handleError}
src={imageSrc}
width={32}
/>
);
}
Binary file added src/shared/assets/images/img-default-profile.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/shared/assets/images/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import DefaultProfileImage from './img-default-profile.png';

export { DefaultProfileImage };
Comment thread
coderabbitai[bot] marked this conversation as resolved.
5 changes: 5 additions & 0 deletions src/shared/types/images.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
declare module '*.png' {
const content: import('next/image').StaticImageData;

export default content;
}
Loading