Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
c2a39fd
refactor(mentor): PropsWithChildren 교체
cksrlcks Jan 14, 2025
76d0ec2
refactor(mentor): 상품 이미지 업로드 서비스 함수 개선
cksrlcks Jan 14, 2025
a22e42c
refactor(mentor): util 함수들 인풋에대한 검증 추가
cksrlcks Jan 14, 2025
55172c7
feat: article type, schema, service 작성
cksrlcks Jan 15, 2025
75e9bfc
feat: Article action 훅 작성
cksrlcks Jan 15, 2025
a9f61a0
feat: 이미지업로드 컴포넌트 개선
cksrlcks Jan 15, 2025
d4dc45c
fix: 아바타 컴포넌트 수정 (Next Image width 설정)
cksrlcks Jan 15, 2025
b754f36
feat: Comment 관련 컴포넌트 개선
cksrlcks Jan 15, 2025
ee13623
feat: 중고마켓 작성, 수정시 해당 페이지로 이동하도록 수정
cksrlcks Jan 15, 2025
5dcc2f1
feat: 자유게시판 상세페이지 작업
cksrlcks Jan 15, 2025
1a73a67
feat: 자유게시판 작성페이지 작업
cksrlcks Jan 15, 2025
22032fb
design: 플레이스홀더 메세지 줄바꿈 css 추가
cksrlcks Jan 15, 2025
f3607ed
fix: 로딩메세지 수정
cksrlcks Jan 15, 2025
d7f0cd3
feat: 목록으로 돌아가기 버튼 작업
cksrlcks Jan 15, 2025
f82f654
feat: 게시물 작성 페이지 작업
cksrlcks Jan 15, 2025
099a884
fix: 네비게이션 활성화 수정
cksrlcks Jan 15, 2025
fea5f65
fix: placeholder 수정
cksrlcks Jan 15, 2025
ce97f3e
fix: middleware match 경로 수정
cksrlcks Jan 15, 2025
89fa285
refactor: 페이지 에러핸들링 수정(최상위 error로 가도록)
cksrlcks Jan 15, 2025
3def569
refactor: 자동으로 static되는 페이지에 dynamic 키워트 제거
cksrlcks Jan 16, 2025
1c14751
refactor: 공용 코멘트 컴포넌트 위치 변경
cksrlcks Jan 16, 2025
871c1cd
fix: 메세지 상수 파일 수정
cksrlcks Jan 16, 2025
42b5ffe
refactor: ssr페이지 에러 처리 개선
cksrlcks Jan 16, 2025
6d2b65d
refactor: try문안에서 redirect를 사용하도록 수정
cksrlcks Jan 16, 2025
4721252
chore: readme 수정
cksrlcks Jan 16, 2025
c1aa081
refactor(mentor): axios config params에 쿼리파람 옮기기
cksrlcks Jan 16, 2025
e346262
refactor: 게시물 작성완료시 페이지이동 개선
cksrlcks Jan 16, 2025
b3d1b3c
fix: axios 에러 타입체크 변경
cksrlcks Jan 16, 2025
9adef1f
feat: 코멘트에 리액트쿼리 사용해보기, 코멘트 리팩토링
cksrlcks Jan 16, 2025
81f8663
chore: console.log 지우기
cksrlcks Jan 17, 2025
d55cbf5
refactor: 회원가입, 로그인 서버액션 사용해보기
cksrlcks Jan 17, 2025
9438b13
refactor: force-static 지우기
cksrlcks Jan 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,8 @@ state 관리하고 있는 params과 usePageSize로 관리하는 pageSize의 변
- 직접 값을 업데이트해줄때는 (특수한 필드의 경우), useForm의 반환값중 setValue를 이용하면 값 업데이트와 벨리데이션이 작동한다.
- react hook form에 컴포넌트를 연결할때에는 한겹의 어뎁터 레이어를 설정하여 컴포넌트 내부에서 react hook form의 의존성이 없도록 작성하는 방법을 사용하자.
- watch를 통해 각 필드에 value값을 전달하면 하나의 필드가 업데이트 될때마다 전체가 리랜더링이 되어버린다. (Controller, useController 등을 통해서 전달해야함)

### nextjs server component에서 try/catch문 안에서 redirect 사용하기

- 서버컴포넌트에서 try,catch문안에서 redirect사용시 문제가 됬던점에 대해서 정리했습니다.
- https://heavy-bear.tistory.com/16
27 changes: 27 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
},
"dependencies": {
"@hookform/resolvers": "^3.9.1",
"@tanstack/react-query": "^5.64.1",
"axios": "^1.7.8",
"clsx": "^2.1.1",
"dayjs": "^1.11.13",
Expand Down
5 changes: 2 additions & 3 deletions src/app/(auth)/_components/AuthContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { ReactNode } from "react";
import { PropsWithChildren } from "react";
import Link from "next/link";
import Image from "next/image";
import logo from "@assets/img/common/logo_full.svg";
import Oauth from "./Oauth";
import styles from "./AuthContainer.module.scss";

interface AuthContainerProps {
children: ReactNode;
interface AuthContainerProps extends PropsWithChildren {
mode?: "login" | "signup";
}

Expand Down
44 changes: 13 additions & 31 deletions src/app/(auth)/_components/LoginForm.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
"use client";

import { useRouter } from "next/navigation";
import { Form, FieldItem, Input } from "@components/Field";
import { FieldItem, Input } from "@components/Field";
import { Button } from "@components/ui";
import useFormWithError from "@hooks/useFormWithError";
import { zodResolver } from "@hookform/resolvers/zod";
import { signinFormSchmea, SigninFormType } from "@schemas/auth";
import { FieldAdapter } from "@components/adaptor/rhf";
import { signIn } from "next-auth/react";
import { ServerForm } from "@/components/Field/ServerForm";
import { useActionState } from "react";
import action from "../login/action";

export default function LoginForm() {
const router = useRouter();

const [formStatus, formAction, isPending] = useActionState(action, {
message: "",
});
const {
control,
formError,
handleSubmit,
formState: { isSubmitting, isValid },
formState: { isValid },
} = useFormWithError<SigninFormType>({
mode: "onBlur",
resolver: zodResolver(signinFormSchmea),
Expand All @@ -26,29 +26,11 @@ export default function LoginForm() {
},
});

async function onSubmit(data: SigninFormType) {
try {
const respone = await signIn("credentials", {
redirect: false,
...data,
});

if (respone?.error) {
throw new Error(respone?.code);
}

alert("로그인에 성공했습니다.");
router.replace("/items");
} catch (err) {
throw err;
}
}

return (
<Form
isLoading={isSubmitting}
error={formError}
onSubmit={handleSubmit(onSubmit)}
<ServerForm
action={formAction}
isLoading={isPending}
error={formStatus?.message}
>
<FieldItem>
<FieldItem.Label htmlFor="email">이메일</FieldItem.Label>
Expand Down Expand Up @@ -81,6 +63,6 @@ export default function LoginForm() {
<Button type="submit" size="xl" disabled={!isValid}>
로그인
</Button>
</Form>
</ServerForm>
);
}
35 changes: 13 additions & 22 deletions src/app/(auth)/_components/SignupForm.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
"use client";

import { useRouter } from "next/navigation";
import { Form, FieldItem, Input } from "@components/Field";
import { FieldItem, Input } from "@components/Field";
import { Button } from "@components/ui";
import useFormWithError from "@hooks/useFormWithError";
import { zodResolver } from "@hookform/resolvers/zod";
import { signupFormSchema, SignupFormType } from "@schemas/auth";
import { FieldAdapter } from "@components/adaptor/rhf";
import { signUp } from "@/service/auth";
import action from "../signup/action";
import { useActionState } from "react";
import { ServerForm } from "@/components/Field/ServerForm";

export default function SignupForm() {
const router = useRouter();
const [formStatus, formAction, isPending] = useActionState(action, {
message: "",
});
Comment on lines +14 to +16
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 실무에서 써본적이 없긴 한데, 보니까 써볼만 한것같긴 하네여...!ㅎㅎ
👍


const {
control,
formError,
handleSubmit,
formState: { isSubmitting, isValid },
formState: { isValid },
} = useFormWithError<SignupFormType>({
mode: "onBlur",
resolver: zodResolver(signupFormSchema),
Expand All @@ -28,21 +29,11 @@ export default function SignupForm() {
},
});

async function onSubmit(data: SignupFormType) {
try {
await signUp(data);
alert("회원가입에 성공했습니다. \n로그인 페이지로 이동합니다.");
router.replace("/login");
} catch (err) {
throw err;
}
}

return (
<Form
isLoading={isSubmitting}
error={formError}
onSubmit={handleSubmit(onSubmit)}
<ServerForm
action={formAction}
isLoading={isPending}
error={formStatus?.message}
>
<FieldItem>
<FieldItem.Label htmlFor="email">이메일</FieldItem.Label>
Expand Down Expand Up @@ -101,6 +92,6 @@ export default function SignupForm() {
<Button type="submit" size="xl" disabled={!isValid}>
회원가입
</Button>
</Form>
</ServerForm>
);
}
42 changes: 42 additions & 0 deletions src/app/(auth)/login/action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"use server";

import { signIn } from "@/auth";
import { signinFormSchmea } from "@/schemas/auth";
import { CredentialsSignin } from "next-auth";
import { isRedirectError } from "next/dist/client/components/redirect-error";
import { redirect } from "next/navigation";
export default async function action(
prevState: { message: string },
formData: FormData
) {
const parsed = signinFormSchmea.safeParse(Object.fromEntries(formData));

if (!parsed.success) {
return {
message: "제출양식에 문제가 있습니다. 확인해주세요",
};
}

try {
await signIn("credentials", {
email: parsed.data.email,
password: parsed.data.password,
redirect: false,
});
redirect("/");
} catch (error) {
if (isRedirectError(error)) {
throw error;
}

if (error instanceof CredentialsSignin) {
return {
message: `로그인 실패 : ${error.code}`,
};
}
}

return {
message: "로그인 성공",
};
}
47 changes: 47 additions & 0 deletions src/app/(auth)/signup/action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"use server";

import { signupFormSchema } from "@/schemas/auth";
import { signUp } from "@/service/auth";
import { isAxiosError } from "axios";
import { isRedirectError } from "next/dist/client/components/redirect-error";
import { redirect } from "next/navigation";

export default async function action(
prevState: { message: string },
formData: FormData
) {
const parsed = signupFormSchema.safeParse(Object.fromEntries(formData));

if (!parsed.success) {
return {
message: "제출양식에 문제가 있습니다. 확인해주세요",
};
}

try {
await signUp({
email: parsed.data.email,
nickname: parsed.data.nickname,
password: parsed.data.password,
passwordConfirmation: parsed.data.passwordConfirmation,
});

redirect("/login");
} catch (error) {
if (isRedirectError(error)) {
throw error;
}

if (isAxiosError(error)) {
const message =
error.response?.data.message || "알 수 없는 에러가 발생했어요.";
return {
message: `회원가입 실패 : ${message}`,
};
}
}

return {
message: "회원가입 성공",
};
}
Loading
Loading