-
Notifications
You must be signed in to change notification settings - Fork 38
[이상달] sprint10 #238
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
addiescode-sj
merged 45 commits into
codeit-bootcamp-frontend:Next-이상달
from
asksa1256:Next-이상달-sprint10
Aug 13, 2025
The head ref may contain hidden characters: "Next-\uC774\uC0C1\uB2EC-sprint10"
Merged
[이상달] sprint10 #238
Changes from 36 commits
Commits
Show all changes
45 commits
Select commit
Hold shift + click to select a range
5722f75
fix: 리스트 아이템 왼쪽 버튼 클릭 시 todo ↔ done 동작하도록 수정
asksa1256 4a68509
refactor: bullet onClick, list onClick 이벤트 분리 및 버블링 방지
asksa1256 cbb201c
fix: button onClick event type 지정 (HTMLButtonElement)
asksa1256 e053f66
feat: 아이템 클릭 시 상세 페이지로 이동
asksa1256 d8e4efc
chore: svgr 패키지 컴포넌트 추가, svg 이미지 추가
asksa1256 bf9060e
chore: svgr 패키지 설치
asksa1256 0aee11d
refactor: Bullet 버튼 - 컴포넌트로 분리
asksa1256 3b904b7
feat: ItemDetailPage - 서버 컴포넌트(페이지)와 클라이언트 컴포넌트(수정폼)로 분리
asksa1256 2039fb7
fix: ItemDetailPage params await 추가 (Next 15 경고)
asksa1256 cfda7e7
style: 이미지 업로드 버튼 퍼블리싱
asksa1256 90f6048
chore: 필요없는 pages 파일 제거
asksa1256 11eae3f
feat: 이미지 업로드 커스텀 훅 생성
asksa1256 f1d9716
feat: 로딩 스피너 추가
asksa1256 6491412
chore: TodoLoading 경로 이동 (Loader)
asksa1256 213ab9a
chore: TodoLoading 경로 이동
asksa1256 1644195
feat: ImageUploader - 이미지 업로드 및 미리보기 기능 추가
asksa1256 361deae
style: Memo 컴포넌트 퍼블리싱
asksa1256 a8d4622
style: 수정 폼 - 수정,삭제 버튼 퍼블리싱
asksa1256 4bc71cf
feat: debounce 함수 추가
asksa1256 56a4da3
feat: textarea handleHeight 디바운스 적용 및 언마운트 시 클리어
asksa1256 53489f0
feat: 폼 수정 payload 세팅
asksa1256 27e60b5
style: 버튼 클릭 스타일 추가
asksa1256 f8e2779
style: memo width 변경
asksa1256 8a06d83
feat: 수정 폼 - 수정 상태에 따라 '수정하기' 버튼 활성화/비활성화 처리
asksa1256 548af10
chore: 리액트 쿼리 개발자 도구 추가
asksa1256 fc5407b
refactor: 초기 데이터 요청 구조 prefetchQuery + dehydrate 패턴으로 변경
asksa1256 a06c884
feat: 수정 폼 - patch api 연동
asksa1256 cc8378e
feat: ImageUploader onUploaded 전달값 변경 (patch api payload 형식 맞추기)
asksa1256 084134e
feat: 투두 삭제 api 연동
asksa1256 e853d43
feat: 수정 폼 - todo/done 상태 업데이트 api 연동
asksa1256 015b021
style: BulletButton hover시 체크 이미지 표시
asksa1256 8ca45e0
feat: 할 일 수정 성공 - 목록 페이지로 이동
asksa1256 064bd0f
style: ImageUploader edit 버튼 hover 스타일 추가
asksa1256 7de98d8
feat: 이미지 파일 첨부 제약조건 추가
asksa1256 f474bf5
chore: 불필요한 주석 제거
asksa1256 d778495
fix: 빌드 에러 수정
asksa1256 931e1e9
chore: 코드 사이 공백 추가 (변수, 기능 사이)
asksa1256 c7d195b
chore: next 설정 - 이미지 최적화 옵션 추가
asksa1256 389e26b
chore: sharp 패키지 추가
asksa1256 ddacbbb
feat: image blur placeholder 유틸 함수 생성
asksa1256 798f62a
feat: image blur placeholder 유틸 함수 적용
asksa1256 d3eda3e
refactor: ImageUploader preview src 삼항 연산 -> 유틸 함수로 분리
asksa1256 787e540
feat: ImageUploader Image - unoptimized 옵션 추가 (gif 이미지는 최적화 제외)
asksa1256 a7364d0
refactor: PrefetchHydration wrapper 컴포넌트 생성 및 적용
asksa1256 4446101
refactor: TodoUpdateForm - 상태 관리 로직, api 요청 로직 커스텀 훅으로 분리
asksa1256 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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,19 @@ | ||
| import TodoUpdateForm from "@/components/Todos/TodoUpdateForm"; | ||
| import { getTodo } from "@/lib/api"; | ||
|
|
||
| interface ItemDetailPageProps { | ||
| params: Promise<{ itemId: string }>; | ||
| } | ||
|
|
||
| const ItemDetailPage = async ({ params }: ItemDetailPageProps) => { | ||
| const { itemId } = await params; | ||
| const data = await getTodo(itemId); | ||
|
|
||
| return ( | ||
| <section className="mt-10"> | ||
| <TodoUpdateForm initialData={data} /> | ||
| </section> | ||
| ); | ||
| }; | ||
|
|
||
| export default ItemDetailPage; |
This file contains hidden or 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 hidden or 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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes
This file contains hidden or 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,44 @@ | ||
| "use client"; | ||
|
|
||
| import clsx from "clsx"; | ||
| import Image from "next/image"; | ||
| import { MouseEvent } from "react"; | ||
|
|
||
| interface BulletButtonProps { | ||
| variant?: string; | ||
| type?: "submit" | "button"; | ||
| onClick?: (e: MouseEvent<HTMLButtonElement>) => void; | ||
| } | ||
asksa1256 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| const BulletButton = ({ | ||
| variant, | ||
| type = "button", | ||
| onClick, | ||
| }: BulletButtonProps) => { | ||
| return ( | ||
| <button | ||
| className={clsx("group relative bullet-base", { | ||
| "bullet-todo": variant === "todo", | ||
| "bullet-done": variant === "done", | ||
| })} | ||
| onClick={onClick} | ||
| type={type} | ||
| > | ||
| <Image | ||
| src="/images/ico-check-wt.svg" | ||
| alt="완료된 할 일" | ||
| width="20" | ||
| height="20" | ||
| className={clsx( | ||
| "absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 transition-opacity", | ||
| { | ||
| "opacity-100": variant === "done", | ||
| "opacity-0 group-hover:opacity-100": variant === "todo", | ||
| } | ||
| )} | ||
| /> | ||
| </button> | ||
| ); | ||
| }; | ||
|
|
||
| export default BulletButton; | ||
This file contains hidden or 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 hidden or 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,120 @@ | ||
| "use client"; | ||
|
|
||
| import { ChangeEvent, useRef, useState } from "react"; | ||
| import ImgIcon from "@/assets/images/ico-img.svg"; | ||
| import PlusIcon from "@/assets/images/ico-plus.svg"; | ||
| import EditIcon from "@/assets/images/ico-edit.svg"; | ||
| import Image from "next/image"; | ||
| import useImageUpload from "@/hooks/useImageUpload"; | ||
| import LoadingSpinner from "@/components/Loader/LoadingSpinner"; | ||
| import clsx from "clsx"; | ||
|
|
||
| interface ImageUploaderProps { | ||
| initialData?: string; | ||
| className: string; | ||
| onUploaded: (v: string) => void; | ||
| } | ||
|
|
||
| const ImageUploader = ({ | ||
| initialData, | ||
| className, | ||
| onUploaded, | ||
| }: ImageUploaderProps) => { | ||
| const fileRef = useRef<HTMLInputElement>(null); | ||
| const [preview, setPreview] = useState<File | string | null | undefined>( | ||
| initialData | ||
| ); | ||
| const { mutate: uploadImage, isPending } = useImageUpload(); | ||
|
|
||
| const handleClick = () => { | ||
| if (!fileRef.current) return; | ||
| fileRef.current.click(); | ||
| }; | ||
|
|
||
| const handleFileChange = async (e: ChangeEvent<HTMLInputElement>) => { | ||
| const files = e.target.files; | ||
| if (!files) return; | ||
|
|
||
| const file = files[0]; | ||
|
|
||
| const filename = file.name.split(".").slice(0, -1).join("."); | ||
| const engOnlyRegex = /^[a-zA-Z0-9_\-]+$/; | ||
| if (!engOnlyRegex.test(filename)) { | ||
asksa1256 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| alert("파일 이름은 영어로만 이루어져야 합니다."); | ||
| e.target.value = ""; | ||
| return; | ||
| } | ||
|
|
||
| const MAX_SIZE = 5 * 1024 * 1024; | ||
| if (file.size > MAX_SIZE) { | ||
| alert("파일 크기는 5MB 이하여야 합니다."); | ||
| e.target.value = ""; | ||
| return; | ||
| } | ||
|
|
||
| setPreview(file); | ||
|
|
||
| uploadImage(file, { | ||
| onSuccess: (image) => { | ||
| onUploaded(image.url); | ||
| }, | ||
| onError: () => { | ||
| alert("이미지 업로드에 실패했습니다."); | ||
| }, | ||
| }); | ||
| }; | ||
|
|
||
| return ( | ||
| <div | ||
| className={`relative flex items-center justify-center w-[384px] h-[310px] rounded-3xl border-2 border-dashed border-gray-300 bg-gray-50 hover:bg-primary-100 hover:border-primary transition-colors overflow-hidden ${className}`} | ||
| > | ||
| <input | ||
| ref={fileRef} | ||
| type="file" | ||
| className="hidden" | ||
| onChange={handleFileChange} | ||
| /> | ||
|
|
||
| {preview && ( | ||
| <Image | ||
| src={ | ||
| typeof preview === "string" | ||
| ? preview | ||
| : preview | ||
| ? URL.createObjectURL(preview) | ||
| : "" | ||
asksa1256 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| alt="이미지 미리보기" | ||
| width={384} | ||
| height={310} | ||
| className="absolute object-cover z-[1] w-full h-full pointer-events-none" | ||
| /> | ||
| )} | ||
|
|
||
| <button | ||
| type="button" | ||
| onClick={handleClick} | ||
| disabled={isPending} | ||
| className="group relative flex items-center justify-center w-full h-full" | ||
| > | ||
| <ImgIcon className="w-16 h-16 text-gray-200 group-hover:text-white z-0" /> | ||
| <span | ||
| className={clsx("btn-upload-base", { | ||
| "btn-upload": !preview, | ||
| "btn-edit": preview, | ||
| })} | ||
| > | ||
| {isPending ? ( | ||
asksa1256 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| <LoadingSpinner /> | ||
| ) : !preview ? ( | ||
| <PlusIcon className="group-hover:text-white" /> | ||
| ) : ( | ||
| <EditIcon className="group-hover:text-primary text-white" /> | ||
| )} | ||
| </span> | ||
| </button> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default ImageUploader; | ||
This file contains hidden or 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 hidden or 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 hidden or 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,7 @@ | ||
| const LoadingSpinner = () => { | ||
| return ( | ||
| <div className="w-8 h-8 border-4 border-gray-300 border-t-primary rounded-full animate-spin" /> | ||
| ); | ||
| }; | ||
|
|
||
| export default LoadingSpinner; |
File renamed without changes.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.