-
Notifications
You must be signed in to change notification settings - Fork 2
refactor: #133/로딩 스피너 추가 #139
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,7 +3,7 @@ import { ChangeEvent, useState, useEffect } from "react"; | |
| import Image from "next/image"; | ||
| import { twMerge } from "tailwind-merge"; | ||
| import clsx from "clsx"; | ||
| import { postImage } from "@/lib/apis/imageApi"; | ||
| import { HashLoader } from "react-spinners"; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Spinner 컴포넌트는 지피티로도 간단하게 만들 수 있어서 굳이 라이브러리로 안해도 될 거 같아요.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오 라이브러리 사용보다 ai로 컴포넌트 만드는게 더 간단하게 할 수 있는 방법이군요! 참고하겠습니다🙏🏻 |
||
|
|
||
| interface BaseImageInputProps | ||
| extends React.InputHTMLAttributes<HTMLInputElement> { | ||
|
|
@@ -12,6 +12,7 @@ interface BaseImageInputProps | |
| initialImageUrl?: string | null; | ||
| onImageUrlChange?: (url: string) => void; | ||
| token?: string; | ||
| isLoading?: boolean; | ||
| } | ||
|
|
||
| interface TaskImageInputProps extends BaseImageInputProps { | ||
|
|
@@ -29,6 +30,7 @@ const ImageInput = ({ | |
| label, | ||
| variant, | ||
| initialImageUrl, | ||
| isLoading = false, | ||
| ...props | ||
| }: ImageInputProps) => { | ||
| const [uploadImgUrl, setUploadImgUrl] = useState<string>( | ||
|
|
@@ -40,7 +42,7 @@ const ImageInput = ({ | |
| setUploadImgUrl(initialImageUrl || ""); | ||
| }, [initialImageUrl]); | ||
|
|
||
| const handleFileChange = async (e: ChangeEvent<HTMLInputElement>) => { | ||
| const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => { | ||
| const file = e.target.files?.[0]; | ||
| if (!file) return; | ||
|
|
||
|
|
@@ -50,32 +52,9 @@ const ImageInput = ({ | |
| return; | ||
| } | ||
|
|
||
| const localUrl = URL.createObjectURL(file); | ||
| setUploadImgUrl(localUrl); | ||
| setError(null); | ||
|
|
||
| if (props.onChange) { | ||
| props.onChange(e); | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| const columnId = | ||
| variant === "task" | ||
| ? (props as TaskImageInputProps).columnId | ||
| : undefined; | ||
| const imageUrl = await postImage(variant, columnId, file, props.token); | ||
| setUploadImgUrl(imageUrl); | ||
|
|
||
| if (props.onImageUrlChange) { | ||
| props.onImageUrlChange(imageUrl); | ||
| } | ||
| } catch (error: unknown) { | ||
| if (error instanceof Error) { | ||
| setError(error.message || "이미지 업로드에 실패했습니다."); | ||
| } else { | ||
| setError("알 수 없는 오류가 발생했습니다."); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
|
|
@@ -105,7 +84,15 @@ const ImageInput = ({ | |
| </span> | ||
| )} | ||
| <label htmlFor={label || "file"} className={imageWrapperStyles}> | ||
| {uploadImgUrl ? ( | ||
| {isLoading ? ( | ||
| <div className="absolute inset-0 flex items-center justify-center bg-gray-100 bg-opacity-75 rounded-md"> | ||
| <HashLoader | ||
| color="#5534DA" | ||
| size={variant === "task" ? 30 : 50} | ||
| className="z-20" | ||
| /> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 중첩 3항연산자를 사용하면 코드 가독성이 내려가요.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 토스 ts-pattern |
||
| </div> | ||
| ) : uploadImgUrl ? ( | ||
| <Image | ||
| src={uploadImgUrl} | ||
| alt="업로드 이미지 미리보기" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,13 +7,15 @@ import DateInput from "@/components/common/input/DateInput"; | |
| import ImageInput from "@/components/common/input/ImageInput"; | ||
| import Textarea from "@/components/common/textarea/Textarea"; | ||
| import UserIcon from "@/components/common/user-icon/UserIcon"; | ||
| import dropdownIcon from "../../../../public/icon/dropdown_icon.svg"; | ||
| import dropdownIcon from "public/icon/dropdown_icon.svg"; | ||
| import { fetchDashboardMember } from "@/lib/apis/membersApi"; | ||
| import { DashboardMember } from "@/lib/types"; | ||
| import { useDashboardStore } from "@/lib/store/useDashboardStore"; | ||
| import { useColumnStore } from "@/lib/store/useColumnStore"; | ||
| import checkItem from "../../../../public/icon/check_icon.svg"; | ||
| import checkItem from "public/icon/check_icon.svg"; | ||
| import { createCard } from "@/lib/apis/cardsApi"; | ||
| import { postImage } from "@/lib/apis/imageApi"; | ||
| import Cookies from "js-cookie"; | ||
|
|
||
| export default function CreateDashboardModal() { | ||
| const { dashboardId } = useDashboardStore(); | ||
|
|
@@ -26,12 +28,13 @@ export default function CreateDashboardModal() { | |
| const [dueDate, setDueDate] = useState<string>(""); | ||
| const [tags, setTags] = useState<string[]>([]); | ||
| const [imageUrl, setImageUrl] = useState<string | null>(null); | ||
| const [isImageUploading, setIsImageUploading] = useState(false); | ||
| const [items, setItems] = useState<DashboardMember[]>([]); | ||
|
|
||
| const [isFormValid, setIsFormValid] = useState(false); | ||
| const [isDropdownOpen, setIsDropdownOpen] = useState(false); | ||
|
|
||
| const accessToken = localStorage.getItem("accessToken") ?? ""; | ||
| const accessToken = Cookies.get("accessToken") ?? ""; | ||
|
|
||
| const fetchMembers = async () => { | ||
| if (!dashboardId) return; | ||
|
|
@@ -112,6 +115,26 @@ export default function CreateDashboardModal() { | |
| window.location.reload(); | ||
| }; | ||
|
|
||
| const handleImageUpload = async (e: React.ChangeEvent<HTMLInputElement>) => { | ||
| const file = e.target.files?.[0]; | ||
| if (!file || !selectedColumnId) return; | ||
|
|
||
| setIsImageUploading(true); | ||
| try { | ||
| const imageUrl = await postImage( | ||
| "task", | ||
| selectedColumnId, | ||
| file, | ||
| accessToken | ||
| ); | ||
| setImageUrl(imageUrl); | ||
| } catch (error) { | ||
| console.error("이미지 업로드 에러:", error); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이런부분 console이 아니라 Toast로 에러메시지 띄어주면 유저가 더 이해하기 수월하겠죠? toast.open("이미지 업로드 에러")
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 토스트 알림을 사용하면 유저 경험이 확실히 좋아질것 같다고 느꼈습니다.🥲 우선 해당 페이지 일관성을 위해서 콘솔로 에러처리 해뒀는데 다시 리팩토링 해보겠습니다 의견주셔서 너무 감사드려요!👍🏻 |
||
| } finally { | ||
| setIsImageUploading(false); | ||
| } | ||
| }; | ||
|
|
||
| if (!selectedColumnId) return; | ||
|
|
||
| return ( | ||
|
|
@@ -211,7 +234,10 @@ export default function CreateDashboardModal() { | |
| variant="task" | ||
| columnId={selectedColumnId} | ||
| token={accessToken} | ||
| initialImageUrl={imageUrl} | ||
| onImageUrlChange={(url) => setImageUrl(url)} | ||
| onChange={handleImageUpload} | ||
| isLoading={isImageUploading} | ||
| /> | ||
| </div> | ||
| </Modal> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
안쓰는 코드는 삭제하는게 어떨까요?