- π νλ‘μ νΈ μ€ν
- π₯ μ¬μ©ν κΈ°μ
- π νλ‘μ νΈ κ΅¬μ‘°
- β μ£Όμ κΈ°λ₯
- π§· API μ€ν
git clone https://github.com/GitHubGW/wanted-pre-onboarding-challenge-fe-1
λ₯Ό ν΅ν΄ 리ν¬μ§ν 리λ₯Ό ν΄λ‘ ν©λλ€.npm install
μ ν΅ν΄ νμν λΌμ΄λΈλ¬λ¦¬λ₯Ό μ€μΉν©λλ€.npm start
λ₯Ό ν΅ν΄ νλ‘μ νΈλ₯Ό μ€νν©λλ€.
git clone https://github.com/GitHubGW/wanted-pre-onboarding-challenge-fe-1
npm install
npm start
-
React
- Reactλ μ£Όλ‘ SPAλ₯Ό μν μ¬μ©μ μΈν°νμ΄μ€λ₯Ό λ§λλ λ° μ¬μ©λλ Javascript λΌμ΄λΈλ¬λ¦¬μ λλ€.
- λ¨λ°©ν₯ λ°μ΄ν° νλ¦μ ν΅ν΄ λ°μ΄ν° νλ¦ λ° λ³νλ₯Ό μμΈ‘νκΈ° μ½κ³ , μ»΄ν¬λνΈ κΈ°λ° κ΅¬μ‘°λ₯Ό ν΅ν΄ μ¬μ¬μ©μ±κ³Ό μ½λ κ΄λ¦¬ λ° μ μ§λ³΄μλ₯Ό μν΄ μ¬μ©νμμ΅λλ€.
-
NextJS
- Next.jsλ μλ²μ¬μ΄λ λ λλ§, μ μ νμ΄μ§ μμ± λ± λ¦¬μ‘νΈ κΈ°λ° μΉ μ ν리μΌμ΄μ κΈ°λ₯μ μν μΉ κ°λ° νλ μμν¬μ λλ€.
-
Typescript
- Typescriptλ Javascript μνΌμ μΈμ΄μ λλ€.
- νμ μ 미리 μ§μ ν΄μ λ°νμ μ΄μ λ¨κ³μμ 미리 μλ¬λ₯Ό νμΈν μ μκ³ , μ½λ μλ μμ± λ° λλ²κΉ μ μν΄ μ¬μ©νμμ΅λλ€.
-
React Query
- React Queryλ λ°μ΄ν° Fetching, μΊμ±, λκΈ°ν, μλ² μͺ½ λ°μ΄ν° μ λ°μ΄νΈ λ±μ μν λΌμ΄λΈλ¬λ¦¬μ λλ€.
- κ° νμ΄μ§μμ 곡ν΅μΌλ‘ μ¬μ©νλ μ μ μνκ° μ κ³ μλ² μͺ½ λ°μ΄ν°λ€μ κ°νΈνκ³ ν¨μ¨μ μΌλ‘ κ΄λ¦¬νκΈ° μν΄ μ¬μ©νμμ΅λλ€.
-
React Hook Form
- React Hook Formμ Reactμμ Formμ μ μ΄νκ³ μ ν¨μ± κ²μ¬ λ° μλ¬λ₯Ό μ²λ¦¬ν μ μλ λΌμ΄λΈλ¬λ¦¬μ λλ€.
- μ 체 μ½λ μμ μ€μ΄κ³ , μΌκ΄μ μΈ μ½λ μμ± λ° μ μ§λ³΄μ, λ λλ§ μ΄μ λ±μ ν΄κ²°νκΈ° μν΄ μ¬μ©νμμ΅λλ€.
-
Tailwind CSS
- Tailwind CSSλ μ νΈλ¦¬ν° νΌμ€νΈ(Utility-first)λ₯Ό μ§ν₯νλ μ€ν μμ€ CSS νλ μμν¬μ λλ€.
- μΌκ΄λ λμμΈκ³Ό μμ λ‘μ΄ μ»€μ€ν°λ§μ΄μ§ λ° μμ°μ±μ μν΄ μ¬μ©νμμ΅λλ€.
- api : API κ΄λ¦¬
- components : μ»΄ν¬λνΈ κ΄λ¦¬
- constants : μμ κ΄λ¦¬
- pages : λΌμ°ν κ΄λ¦¬
- queries : 리μ‘νΈ μΏΌλ¦¬ κ΄λ¦¬
- styles : μ€νμΌ κ΄λ¦¬
- types : κ³΅ν΅ νμ κ΄λ¦¬
- utils : μ νΈλ¦¬ν° ν¨μ κ΄λ¦¬
β£ api
β β£ auth.ts
β β todo.ts
β£ components
β β£ layouts
β β β MainLayout.tsx
β β£ AuthForm.tsx
β β£ FormError.tsx
β β£ TodoDetail.tsx
β β£ TodoDetailItem.tsx
β β£ TodoForm.tsx
β β£ TodoItem.tsx
β β TodoList.tsx
β£ constants
β β£ auth.ts
β β£ common.ts
β β queryKeys.ts
β£ pages
β β£ _app.tsx
β β£ _document.tsx
β β£ auth.tsx
β β index.tsx
β£ queries
β β£ useAuthMutate.ts
β β£ useTodoMutate.ts
β β useTodoQuery.ts
β£ styles
β β globals.css
β£ types
β β£ auth.ts
β β todo.ts
β£ utils
β β localStorage.ts
- /auth κ²½λ‘μ λ‘κ·ΈμΈ λ° νμκ°μ κΈ°λ₯μ ꡬννμμ΅λλ€.
- νμκ°μ λ° λ‘κ·ΈμΈ μ±κ³΅ μ, μ λ¬λ°μ ν ν°μ λ‘컬 μ€ν 리μ§μ μ μ₯νκ³ λ£¨νΈ κ²½λ‘λ‘ μ΄λμν΅λλ€.
- λ‘κ·Έμμ μ, λ‘컬 μ€ν 리μ§μ μ μ₯λ ν ν°μ μμ νκ³ /auth κ²½λ‘λ‘ μ΄λμν΅λλ€.
// components/AuthForm.tsx
const { useSignUpMutation, useLoginMutation } = useAuthMutation();
const { mutateAsync: signUpMutateAsync, data: signUpData } = useSignUpMutation();
const { mutateAsync: loginMutateAsync, data: loginData } = useLoginMutation();
const onValid = useCallback(
async (formData: FormData) => {
if (status === AUTH_STATE.SIGN_UP) {
const result = await signUpMutateAsync(formData);
if (result.token) {
reset();
setLocalStorageItem(result.token);
setStatus(AUTH_STATE.LOGIN);
}
} else {
const result = await loginMutateAsync(formData);
if (result.token) {
reset();
setLocalStorageItem(result.token);
router.push("/");
}
}
},
[status, reset, router, signUpMutateAsync, loginMutateAsync]
);
React Hook Form
μ μ¬μ©νμ¬ formμ μ μ΄νκ³ , μ΄λ©μΌκ³Ό λΉλ°λ²νΈμ μ ν¨μ± νμΈ λ° μλ¬λ₯Ό λμν©λλ€.
<input
{...register("email", {
required: "μ΄λ©μΌμ νμμ
λλ€.",
validate: (value) => {
const isValidText = value.includes("@") && value.includes(".");
return isValidText || "μ΄λ©μΌμλ @μ .μ΄ ν¬ν¨λμ΄μΌ ν©λλ€.";
},
})}
required
type="email"
id="emailInput"
/>
<FormError text={errors.email?.message} />
- ν¬λ μΆκ° λ° μμ λ²νΌμ ν΄λ¦ν΄ ν μΌμ μμ±νκ±°λ μμ ν μ μμ΅λλ€.
- ν¬λ μμ λ²νΌμ ν΄λ¦νλ©΄ μμ λͺ¨λκ° νμ±νλκ³ , μμ λ΄μ©μ μ μΆνκ±°λ μ·¨μν μ μμ΅λλ€.
// components/TodoForm.tsx
const { useCreateTodoMutation, useUpdateTodoMutation } = useTodoMutation();
const { mutate: createTodoMutate } = useCreateTodoMutation();
const { mutate: updateTodoMutate } = useUpdateTodoMutation();
const onValid = useCallback(
(formData: FormData) => {
if (isUpdating && id && handleToggleTodo) {
handleToggleTodo();
reset();
updateTodoMutate({ id, title: formData.title, content: formData.content });
} else {
reset();
createTodoMutate({ title: formData.title, content: formData.content });
}
},
[updateTodoMutate, createTodoMutate, handleToggleTodo, isUpdating, id, reset]
);
- μμ ν ν¬λμ λ΄μ©μ μ 체 ν¬λ λͺ©λ‘ λ° ν¬λ μμΈμ μ€μκ°μΌλ‘ λ°μλ©λλ€.
- μμ ν ν¬λμ λ΄μ©μ μ€μκ°μΌλ‘ λ°μνκΈ° μν΄
React Query
μ invalidateQueries λ©μλλ₯Ό ν΅ν΄ κΈ°μ‘΄μ μ‘°ννλ 쿼리λ₯Ό 무ν¨νμν€κ³ λ°μ΄ν°λ₯Ό μλ‘ μ‘°νν΄μ€λλ‘ ν©λλ€. - invalidateQueries λ©μλλ μ λ¬λ°μ 쿼리 ν€μ ν΄λΉνλ λͺ¨λ 쿼리λ₯Ό 무ν¨νν©λλ€.
// queries/useTodoMutation.ts
const useUpdateTodoMutation = () => {
return useMutation({
mutationFn: (params: UpdateTodoParams) => TodoApi.updateTodo({ id: params.id, title: params.title, content: params.content }),
onSuccess: (response) => {
queryClient.invalidateQueries(QUERY_KEYS.TODO.GET_TODOS());
queryClient.invalidateQueries(QUERY_KEYS.TODO.GET_TODO_BY_ID(response.data.id));
},
onError: (error) => {
console.log("useUpdateTodo onError", error);
},
});
};
// components/TodoList.tsx
const TodoList = () => {
const { useGetTodosQuery } = useTodoQuery();
const { data: todosData, isLoading } = useGetTodosQuery();
if (isLoading) {
return "Loading...";
}
return <div>{todosData?.data && [...todosData?.data]?.reverse().map((item) => <TodoItem key={item.id} id={item.id} title={item.title} createdAt={item.createdAt} />)}</div>;
};
// components/TodoDetail.tsx
const TodoDetail = () => {
const { useGetTodoByIdQuery } = useTodoQuery();
const { data: todoData } = useGetTodoByIdQuery({ id: String(router.query.id) });
return (
<div>
{todoData?.data && !isUpdating && (
<TodoDetailItem title={todoData.data.title} content={todoData.data.content} createdAt={todoData.data.createdAt} updatedAt={todoData.data.updatedAt} />
)}
</div>
);
};
- POST /users/login
- Parameter
- email: string
- password: string
- POST /users/create
- Parameter
- email: string
- password: string
- GET /todos
- Headers
- Authorization: login token
- GET /todos/:id
- Headers
- Authorization: login token
- POST /todos
- Parameter
- title: string
- content: string
- Headers
- Authorization: login token
- PUT /todos/:id
- Parameter
- title: string
- content: string
- Headers
- Authorization: login token
- DELETE /todos/:id
- Headers
- Authorization: login token