-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
FE-34 ✨ 마이페이지 프로필 수정 반응형 구현 및 토스트 메세지 출력 (#33)
* .nvmrc 버전 수정 * 폰트 및 공용컬러 추가 (#6) * font-family 추가 * tailwind common color 추가 * color 명 변경 * lang 수정 --------- Co-authored-by: 전유민 <[email protected]> * 💄 공용 컴포넌트 shadcn ui 추가 (#7) * 💄 Feat: shadcn-ui init * 💄 Feat: add toast ui * Feat: add textarea ui * Feat: add switch ui * Feat: add radio-group ui * Feat: add label ui * Feat: add input ui * Feat: add form ui * Feat: add button ui * Feat: add dropdown-menu ui * Feat: add card ui * Feat: add badge ui * Feat: add avatar ui * Feat: add alert dialog ui * Chore: add eslint rules * Chore: add shadcn ui * FE-48 📰 공용 컴포넌트 face emoji svg 파일 생성 * FE-48 🎨 감정 이모티콘 폴더 구조 변경 * FE-48 ✨ 감정 이모티콘 카드 컴포넌트 ui 생성 * FE-48 ✨ 감정 이모티콘 상태에 따른 클래스 설정 * FE-48 💄 감정 이모티콘 카드 컴포넌트 ui 수정 * FE-48 ✨ 감정 이모티콘 카드 클릭 이벤트 구현 - EmotionIconCardContainer를 사용해 상태관리와 이벤트 처리 (Clicked<->UnClicked) * FE-48 📝 컴포넌트 이름 변경 명확한 의미 전달을 위해 컴포넌트 이름 변경 * FE-48 ✨ 감정 이모티콘 상태 변화 동기화 구 감정 카드를 클릭할 때 상태가 올바르게 전환되고, 다른 카드의 상태도 동기화되는 기능 구현 * FE-48 ✨ EmotionSelector 컴포넌트 동적 크기 변경 구현 useMediaQuery 훅 생성: 화면의 크기가 변경될 때마다 리스너 추가 및 제거 * FE-48 🔥 출력 확인을 위한 테스트 컴포넌트 삭제 * FE-48 🔨 EmotionTypes 인터페이스 정의 emotion 관련 컴포넌트에서 해당 인터페이스를 import하여 사용하게 구현 * FE-59 ✨ 에피그램 카드 ui 구현 tailwind css를 확장해 줄무늬 배경 이미지 구현 * FE-59 ✨ 에피그램 카드 반응현 디자인 구현 * FE-59 💄 에피그램 카드 글씨체 적용 * FE-59 🔥 에피그램 카드 테스트 코드 삭제 * FE-59 🔥 테스트 흔적 삭제 * FE-58 ✨ 공용 컴포넌트 댓글 카드 기본 ui 구현 * FE-34 ✨ 유저 프로필 컴포넌트 분리 - Profile.tsx 파일로 유저 프로필 부분 분리 - 파일(이미지) 선택 기능 구현(api 연동x) - 등록 된 이미지가 없다면 샘플이미지 출력 * FE-34 ✨ 이미지 업로드 presignedUrl 생성 api 연동 - 현재 로그인 인증 토큰이 없어 401 에러가 뜸 * FE-58 💄 공용 컴포넌트 반응형 디자인 적용 * FE-58 🔥 댓글 카드 테스트 코드 삭제 * FE-58 👄 댓글 카드 관련 인터페이스, 스타일 분리 * FE-50 ✨공용컴포넌트 헤더 구현 (#19) * FE-5050✨ feat: 헤더 부분 기능 초안 * FE-50 ✨styles: 주석 추가 * FE-50 ✨styles: 주석 추추가 * FE-5050 ✨test: 테스트 코드 * FE-50 ✨fix: 테스트 코드 삭제 * FE-50 ✨feat: 공유 이미지 추가 및 현재 URL 복사 기능 추가 * FE-50 ✨styles: U셋 중 하나가 빠지더라도 안무너지게 UI 수정 * FE-50 ✨comment: 주석 수정 및 추가 * FE-50 ✨fix: 테스트 코드 삭제 * FE-50 ✨fix: 함수명 컨벤션에 맞게 변경 * FE-50 ✨fix: types 폴더에 interface 정의 * FE-50 fix: build 오류 수정 * FE-34 ✨ 프로필 수정 모달 ui * FE-34 🔀 main branch merge * FE-34✨ 이미지 파일 미리보기 기능 구현 * FE-34✨ 텍스트 입력 함수 추가 * FE-34✨ formik으로 회원정보 변경 로직 수정 * FE-34✨ presigned url 생성 api 연동 * FE-34 🔨 next.config 파일 s3 url 추가 * FE-34 ✨ 프로필 수정 유효성 검사 추가 - 파일 이름 'profile' + 파일업로드 날짜로 변경 - 닉네임 1~30자 일때만 입력 가능하도록 설정 * FE-34 ✨ 프로필 수정 api 완료 * FE-34 🎨 pr 리뷰 수정 * FE-34 🔥 confilct 삭제 * FE-34 💄 shadcn/ui dialog 추 * FE-34 🎨 shadcn/ui dialog로 프로필 수정 기능 변경 * FE-34 ✨ 프로필 수정 완료 시 toast 메세지 구현 * FE-34 🐛 build test 오류 수정 --------- Co-authored-by: 전유민 <[email protected]> Co-authored-by: MOON <[email protected]> Co-authored-by: NEWJIN <[email protected]> Co-authored-by: NEWJIN <[email protected]> Co-authored-by: imsoohyeok <[email protected]>
- Loading branch information
1 parent
8ceec83
commit 555cc5f
Showing
6 changed files
with
128 additions
and
51 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import * as React from 'react'; | ||
import * as DialogPrimitive from '@radix-ui/react-dialog'; | ||
import { X } from 'lucide-react'; | ||
|
||
import cn from '@/lib/utils'; | ||
|
||
const Dialog = DialogPrimitive.Root; | ||
|
||
const DialogTrigger = DialogPrimitive.Trigger; | ||
|
||
const DialogPortal = DialogPrimitive.Portal; | ||
|
||
const DialogClose = DialogPrimitive.Close; | ||
|
||
const DialogOverlay = React.forwardRef<React.ElementRef<typeof DialogPrimitive.Overlay>, React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>>(({ className, ...props }, ref) => ( | ||
<DialogPrimitive.Overlay | ||
ref={ref} | ||
className={cn('fixed inset-0 z-50 bg-background-100 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0', className)} | ||
{...props} | ||
/> | ||
)); | ||
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; | ||
|
||
const DialogContent = React.forwardRef<React.ElementRef<typeof DialogPrimitive.Content>, React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>>(({ className, children, ...props }, ref) => ( | ||
<DialogPortal> | ||
<DialogOverlay /> | ||
<DialogPrimitive.Content | ||
ref={ref} | ||
className={cn( | ||
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg', | ||
className, | ||
)} | ||
{...props} | ||
> | ||
{children} | ||
<DialogPrimitive.Close className='absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground'> | ||
<X className='h-4 w-4' /> | ||
<span className='sr-only'>Close</span> | ||
</DialogPrimitive.Close> | ||
</DialogPrimitive.Content> | ||
</DialogPortal> | ||
)); | ||
DialogContent.displayName = DialogPrimitive.Content.displayName; | ||
|
||
function DialogHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) { | ||
return <div className={cn('flex flex-col space-y-1.5 text-center sm:text-left', className)} {...props} />; | ||
} | ||
DialogHeader.displayName = 'DialogHeader'; | ||
|
||
function DialogFooter({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) { | ||
return <div className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)} {...props} />; | ||
} | ||
DialogFooter.displayName = 'DialogFooter'; | ||
|
||
const DialogTitle = React.forwardRef<React.ElementRef<typeof DialogPrimitive.Title>, React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>>(({ className, ...props }, ref) => ( | ||
<DialogPrimitive.Title ref={ref} className={cn('text-lg font-semibold leading-none tracking-tight', className)} {...props} /> | ||
)); | ||
DialogTitle.displayName = DialogPrimitive.Title.displayName; | ||
|
||
const DialogDescription = React.forwardRef<React.ElementRef<typeof DialogPrimitive.Description>, React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>>(({ className, ...props }, ref) => ( | ||
<DialogPrimitive.Description ref={ref} className={cn('text-sm text-muted-foreground', className)} {...props} /> | ||
)); | ||
DialogDescription.displayName = DialogPrimitive.Description.displayName; | ||
|
||
export { Dialog, DialogPortal, DialogOverlay, DialogClose, DialogTrigger, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,41 +1,41 @@ | ||
import Image from 'next/image'; | ||
import { UserProfileProps } from '@/types/user'; | ||
import { useState } from 'react'; | ||
|
||
import { Button } from '@/components/ui/button'; | ||
import { Dialog, DialogTrigger, DialogContent } from '@/components/ui/dialog'; | ||
import { sampleImage } from '../util/constants'; | ||
import ProfileEdit from './ProfileEdit'; | ||
|
||
export default function Profile({ image, nickname }: UserProfileProps) { | ||
const [isModalOpen, setIsModalOpen] = useState(false); | ||
|
||
// TODO: 여러개의 샘플 이미지 랜덤하게 뜨도록 추가 할 예정 | ||
const profileImage = image || sampleImage[1]; | ||
|
||
const handleProfileEditOpen = () => { | ||
setIsModalOpen(true); | ||
}; | ||
|
||
const handleProfileEditClose = () => { | ||
setIsModalOpen(false); | ||
}; | ||
|
||
// TODO: 여러개의 샘플 이미지 랜덤하게 뜨도록 추가 할 예정 | ||
const profileImage = image || sampleImage[1]; | ||
|
||
return ( | ||
<> | ||
<div className='w-[130px] h-[240px] flex flex-col justify-center items-center absolute top-[-50px]'> | ||
<div> | ||
<div role='button' tabIndex={0} className='w-[120px] h-[120px] rounded-full overflow-hidden cursor-pointer'> | ||
<Image src={profileImage} alt='유저 프로필' className='w-full h-full object-cover' width={120} height={120} priority /> | ||
</div> | ||
</div> | ||
<p className='mt-4 mb-6'>{nickname}</p> | ||
<div className='w-[130px] h-12 pl-4 pr-3.5 py-1.5 bg-zinc-100 rounded-[100px] justify-center items-center gap-1.5 inline-flex'> | ||
<button type='button' className="text-neutral-400 text-xl font-medium font-['Pretendard'] leading-loose" onClick={handleProfileEditOpen}> | ||
프로필 수정 | ||
</button> | ||
<div className='w-[130px] h-[240px] flex flex-col justify-center items-center absolute top-[-50px]'> | ||
<div> | ||
<div role='button' tabIndex={0} className='w-[120px] h-[120px] rounded-full overflow-hidden cursor-pointer'> | ||
<Image src={profileImage} alt='유저 프로필' className='w-full h-full object-cover' width={120} height={120} priority /> | ||
</div> | ||
</div> | ||
|
||
{isModalOpen && <ProfileEdit initialValues={{ image: profileImage, nickname }} onModalClose={handleProfileEditClose} />} | ||
</> | ||
<p className='mt-4 mb-6'>{nickname}</p> | ||
<div className='w-[130px] h-12 pl-4 pr-3.5 py-1.5 bg-zinc-100 rounded-[100px] justify-center items-center gap-1.5 inline-flex'> | ||
<Dialog open={isModalOpen} onOpenChange={setIsModalOpen}> | ||
<DialogTrigger asChild> | ||
<Button variant='outline' className='border-none'> | ||
프로필 수정 | ||
</Button> | ||
</DialogTrigger> | ||
<DialogContent className='sm:max-w-[425px] md:max-w-[1200px] bg-white' aria-describedby={undefined}> | ||
<ProfileEdit initialValues={{ image: profileImage, nickname }} onModalClose={handleProfileEditClose} /> | ||
</DialogContent> | ||
</Dialog> | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters