[양재영] sprint9#143
Hidden character warning
Conversation
|
스프리트 미션 하시느라 수고 많으셨어요. |
| @@ -0,0 +1,56 @@ | |||
| const BASE_URL = `${process.env.NEXT_PUBLIC_BASE_URL}/items`; | |||
There was a problem hiding this comment.
items를 지워도 될 것 같아요 !
현재 itmes 외에도 이미지 업로드에 필요한 images API도 있는 것으로 보이는군요 😉
| const BASE_URL = `${process.env.NEXT_PUBLIC_BASE_URL}/items`; | |
| const BASE_URL = `${process.env.NEXT_PUBLIC_BASE_URL}`; |
위와 같이 사용해볼 수 있습니다 😆
There was a problem hiding this comment.
해당 파일은 index.tsx보다 index.ts 확장자가 더 낫겠군요 !
해당 파일은 jsx 문법을 사용하고 있지 않기에 .ts로 변경하셔도 무방합니다 😉
그럼 확장자만 봐도 JSX 파일인지 아닌지 구분할 수 있겠죠? 😆
| export async function patchData( | ||
| itemId: number, | ||
| updateData: Partial<{ name: string; isCompleted: boolean }> | ||
| ) { | ||
| try { | ||
| const response = await fetch(`${BASE_URL}/${itemId}`, { | ||
| method: "PATCH", | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| }, | ||
| body: JSON.stringify(updateData), | ||
| }); | ||
| if (!response.ok) { | ||
| throw new Error("서버 요청 실패" + response.status); | ||
| } | ||
| const data = response.json(); | ||
| return data; | ||
| } catch (error) { | ||
| console.log("에러 발생", error); | ||
| } | ||
| } |
There was a problem hiding this comment.
Swagger 문서를 보면 다음과 같이 스키마의 타입을 알 수 있습니다:
해당 타입으로 다음과 같은 타입을 정의해볼 수 있겠군요 !
interface Item {
id: number;
tenantId: string
name: string;
memo: string;
imageUrl: string;
isCompleted?: boolean;
}그리고 요청 타입과 응답 타입을 정의해볼 수 있습니다 !
interface PatchTodoRequest extends Partial<Omit<TodoItem, 'id'>> {}
interface PatchTodoResponse extends TodoItem {}There was a problem hiding this comment.
(이어서) 그리고 API 함수에 다음에 요청과 응답 타입을 정의해볼 수 있습니다 😉
| export async function patchData( | |
| itemId: number, | |
| updateData: Partial<{ name: string; isCompleted: boolean }> | |
| ) { | |
| try { | |
| const response = await fetch(`${BASE_URL}/${itemId}`, { | |
| method: "PATCH", | |
| headers: { | |
| "Content-Type": "application/json", | |
| }, | |
| body: JSON.stringify(updateData), | |
| }); | |
| if (!response.ok) { | |
| throw new Error("서버 요청 실패" + response.status); | |
| } | |
| const data = response.json(); | |
| return data; | |
| } catch (error) { | |
| console.log("에러 발생", error); | |
| } | |
| } | |
| export async function patchData( | |
| itemId: number, | |
| updateData: PatchTodoRequest | |
| ): Promise<ApiResponse<PatchTodoResponse>> { | |
| try { | |
| const response = await fetch(`${BASE_URL}/${itemId}`, { | |
| method: "PATCH", | |
| headers: { | |
| "Content-Type": "application/json", | |
| }, | |
| body: JSON.stringify(updateData), | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`서버 요청 실패: ${response.status}`); | |
| } | |
| const data: PatchTodoResponse = await response.json(); | |
| return { data }; | |
| } catch (error) { | |
| console.log("에러 발생", error); | |
| return { | |
| error: error instanceof Error ? error.message : "알 수 없는 오류가 발생했습니다" | |
| }; | |
| } | |
| } | |
이렇게 정의해두면 요청 타입과 응답 타입이 필요할 때 export 해볼 수 있으며 타입에 별칭이 있어 가독성도 향상될 수 있겠군요 !
| import plusIc from "@/assets/plus.svg"; | ||
| import Image from "next/image"; | ||
|
|
||
| export const Button = ({ handleSubmit }) => { | ||
| return ( | ||
| <button | ||
| onClick={handleSubmit} | ||
| className="w-[168px] border-2 border-slate-900 bg-slate-100 rounded-3xl px-5 py-2 shadow-md shadow-slate-800 items-center font-bold flex justify-center gap-2 text-slate-900" | ||
| > | ||
| <Image src={plusIc} width={16} height={16} alt="추가 아이콘" /> 추가하기 | ||
| </button> | ||
| ); | ||
| }; |
There was a problem hiding this comment.
(응용 / 더 나아가기) 해당 컴포넌트는 자칫 공통컴포넌트로 오해할 수 있겠군요.
src/components/ui/button.tsx 이름과 경로를 보더라도 해당 앱 어디서나 사용될 수 있는 공통컴포넌트로 오해될 수 있을 것 같아요.
두 가지 방법을 제안드리고 싶습니다 !
공통 컴포넌트를 진짜 만들어버리기
// components/common/Button.tsx
import { ButtonHTMLAttributes } from "react";
import clsx from "clsx";
interface CommonButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: "primary" | "secondary" | "ghost"; // 예시로 작성했습니다 😉
}
export function Button({
children,
variant = "primary",
className,
...props
}: CommonButtonProps) {
const baseStyle =
"rounded-3xl font-bold flex items-center justify-center gap-2 transition-colors duration-150";
const variants = {
primary:
"bg-slate-100 text-slate-900 border-2 border-slate-900 shadow-md shadow-slate-800 hover:bg-slate-200",
secondary:
"bg-white text-slate-800 border border-slate-400 hover:bg-slate-100", // 예시로 작성했습니다 😉
ghost: "text-slate-600 hover:text-slate-800 hover:bg-slate-100", // 예시로 작성했습니다 😉
};
return (
<button
{...props}
className={clsx(baseStyle, variants[variant], className)}
>
{children}
</button>
);
}(이어서)
There was a problem hiding this comment.
두번째 방법: 컴포넌트 이름 바꾸기
정말 간단합니다 😉 그냥 이름만 바꿔줘도 될 것 같아요 !
| import plusIc from "@/assets/plus.svg"; | |
| import Image from "next/image"; | |
| export const Button = ({ handleSubmit }) => { | |
| return ( | |
| <button | |
| onClick={handleSubmit} | |
| className="w-[168px] border-2 border-slate-900 bg-slate-100 rounded-3xl px-5 py-2 shadow-md shadow-slate-800 items-center font-bold flex justify-center gap-2 text-slate-900" | |
| > | |
| <Image src={plusIc} width={16} height={16} alt="추가 아이콘" /> 추가하기 | |
| </button> | |
| ); | |
| }; | |
| import plusIc from "@/assets/plus.svg"; | |
| import Image from "next/image"; | |
| export const AddItemButton = ({ handleSubmit }) => { | |
| return ( | |
| <button | |
| onClick={handleSubmit} | |
| className="w-[168px] border-2 border-slate-900 bg-slate-100 rounded-3xl px-5 py-2 shadow-md shadow-slate-800 items-center font-bold flex justify-center gap-2 text-slate-900" | |
| > | |
| <Image src={plusIc} width={16} height={16} alt="추가 아이콘" /> 추가하기 | |
| </button> | |
| ); | |
| }; |
해당 컴포넌트는 특수 목적이 있는 버튼임을 명시하는 방법이 있습니다 !
| onChange={handleChange} | ||
| handleKeyDown={handleKeyDown} | ||
| /> | ||
| <Button handleSubmit={handleSubmit} /> |
There was a problem hiding this comment.
(이어서) 해당 코드를 다음과 같이 변경합니다:
| <Button handleSubmit={handleSubmit} /> | |
| <Button onClick={onClick} /> |
React.ButtonHTMLAttributes<HTMLButtonElement>에 이미 onClick 속성이 존재하므로 따로 props에 명시할 필요 없이 사용할 수 있습니다 😉
| extend: { | ||
| colors: { | ||
| slate: { | ||
| 100: "#F1F5F9", | ||
| 200: "#E2E8F0", | ||
| 300: "#CBD5E1", | ||
| 400: "#94A3B8", | ||
| 500: "#64748B", | ||
| 800: "#1E293B", | ||
| 900: "#0F172A", | ||
| }, | ||
|
|
||
| "accent-violet-600": "#7C3AED", | ||
| "accent-violet-100": "#DED9FE", | ||
| "accent-rose": "#4F3F5E", | ||
| "accent-lime": "#BEF264", | ||
| "accent-amber": "#92400E", | ||
| }, | ||
| fontFamily: { | ||
| sans: ["NanumSquare", "system-ui", "sans-serif"], | ||
| }, | ||
| }, | ||
| }, |
There was a problem hiding this comment.
크으 ~ 테며드셨군요 👍
테일윈드 참 편리하고 좋지요 👍 테일윈드 설정을 통해서 팔레트를 정의하셨군요. 훌륭합니다 👍
|
수고하셨습니다 재영님 ! 이번 심화 프로젝트에서도 재영님의 역량이 팀에 큰 힘이 될 것이라 확신합니다 😉 미션 수행하시느라 수고 많으셨습니다 ! |
요구사항
기본
스크린샷
멘토에게