Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
592a3d9
feat: 베스트 게시물 기능 추가
Jan 17, 2025
c94f749
feat: 게시글 SSR로 작업
Jan 17, 2025
7c3cfc5
feat: 게시글 orderBy 기능 작업
Jan 17, 2025
498ba56
feat: 게시글 검색 기능 구현
Jan 17, 2025
32ae842
feat: 반응형 작업 완료
Jan 18, 2025
8d3017f
feat: 반응형 pagesize 작업 및 로딩중, 비어있음 적용
Jan 18, 2025
2f83b8f
feat: pagesize 무한스크롤 기능 및 useOutsideClick hook 추가
Jan 18, 2025
87d6588
fix: 네트리파이 배포 오류 수정
Jan 18, 2025
e8cb077
style: css파일 삭제 후 인라인속성으로 변경
Jan 18, 2025
ecc2dbb
fix: 반응형 버그 수정
Jan 22, 2025
0011b09
refactor: 날짜포맷 dayjs 라이브러리로 변경
Jan 22, 2025
863aca2
pref: boards 데이터 불러오기 성능 개선
Jan 22, 2025
406d403
pref: useCallback으로 boards 데이터 불러오기 함수 메모이제이션
Jan 22, 2025
3871bf7
style: board 검색 form태그 추가 시멘틱하게 수정
Jan 22, 2025
fe6b0f8
feat: 게시글 작성, 게시글 상세 작업완료
Jan 22, 2025
ae625b6
refactor: 컴포넌트 분리
Jan 22, 2025
a63a913
feat: 입력값 입력 시 버튼 활성화
Jan 22, 2025
9fe291d
feat: 반응형 작업완료
Jan 23, 2025
5d3c518
fix: 반응형 스타일 수정
Jan 23, 2025
59e5603
feat: 회원가입, 로그인 html, css 작성
Jan 25, 2025
2777c37
feat: 로그인, 회원가입 기능 작업 완료
Jan 27, 2025
eb288f0
Merge branch 'Next-김승석' into Next-김승석-sprint11
kss761036 Jan 27, 2025
9ba593a
Merge branch 'Next-김승석' into Next-김승석-sprint11
kss761036 Feb 5, 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
468 changes: 454 additions & 14 deletions package-lock.json

Large diffs are not rendered by default.

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": {
"@svgr/webpack": "^8.1.0",
"axios": "^1.7.9",
"dayjs": "^1.11.13",
"next": "14.2.23",
"react": "^18",
Expand Down
10 changes: 10 additions & 0 deletions public/assets/img/icon_google.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions public/assets/img/icon_kakao.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion src/components/form/form-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@ interface Props {
type: string;
placeholder: string;
value?: string;
name?: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
}

const FormInput = ({ type, placeholder, value, onChange }: Props) => {
const FormInput = ({ type, placeholder, value, name, onChange }: Props) => {
return (
<input
className={styles.input}
type={type}
placeholder={placeholder}
value={value}
name={name}
onChange={onChange}
/>
);
Expand Down
1 change: 1 addition & 0 deletions src/components/header-layout.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
}
.header .my {
margin-left: auto;
cursor: pointer;
}

@media (max-width: 744px) {
Expand Down
35 changes: 32 additions & 3 deletions src/components/header-layout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,30 @@
import Link from "next/link";
import styles from "./header-layout.module.css";
import { useEffect, useState } from "react";
import { useRouter } from "next/router";

export default function HeaderLayout() {
const [isLoggedIn, setIsLoggedIn] = useState(false);
const router = useRouter();

useEffect(() => {
const accessToken = localStorage.getItem("accessToken");
if (accessToken) {
setIsLoggedIn(true);
}
}, [router.pathname]);

const onLogout: React.MouseEventHandler<HTMLDivElement> = () => {
const result = confirm("로그아웃 하시겠습니까?");
if (result) {
localStorage.removeItem("accessToken");
localStorage.removeItem("refreshToken");
localStorage.removeItem("user");
setIsLoggedIn(false);
} else {
return;
}
Comment on lines +19 to +26
Copy link
Collaborator

Choose a reason for hiding this comment

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

if(!result) return;
 localStorage.removeItem("accessToken");
      localStorage.removeItem("refreshToken");
      localStorage.removeItem("user");
      setIsLoggedIn(false);

이런식으로 early return기법을 사용해보는건 어떨까요

};
return (
<header className={styles.header}>
<div className="inner">
Expand All @@ -27,9 +50,15 @@ export default function HeaderLayout() {
<Link href="">중고마켓</Link>
</li>
</ul>
<Link className={styles.my} href="">
<img src="/assets/img/icon_my.svg" alt="마이페이지" />
</Link>
{isLoggedIn ? (
<div className={styles.my} onClick={onLogout}>
<img src="/assets/img/icon_my.svg" alt="마이페이지" />
</div>
) : (
<Link href="/login" className={`btn ${styles.my}`}>
로그인
</Link>
)}
</div>
</header>
);
Expand Down
8 changes: 8 additions & 0 deletions src/lib/axios.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import axios from "axios";

const Instance = axios.create({
baseURL: "https://panda-market-api.vercel.app",
Copy link
Collaborator

Choose a reason for hiding this comment

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

baseURL같은건 env로 관리하는게 더 좋아보이네요

});

export default Instance;
export { axios };
1 change: 0 additions & 1 deletion src/lib/format-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,5 @@ import dayjs from "dayjs";
export default function formatDate(dateString: string) {
const isoDate = new Date(dateString);
const formattedDate = dayjs(isoDate).format("YYYY. MM. DD");

return formattedDate;
}
5 changes: 0 additions & 5 deletions src/pages/boards/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ export default function Page() {
setBestPageSize(3);
}
}, [isMo, isTa]);


const onSortToggle = () => {
setSortState(!sortState);
};
Expand Down Expand Up @@ -93,13 +91,11 @@ export default function Page() {
setTimeout(() => {
setSearch("");
}, 100);

};

const ref = useOutsideClick(() => {
setSortState(false);
});

const handleScroll = () => {
const bottom =
window.innerHeight + document.documentElement.scrollTop ===
Expand Down Expand Up @@ -132,7 +128,6 @@ export default function Page() {
<div className={styles.boart_common_title}>
<div className="common_title">게시글</div>
<Link className="btn" href="/addboard">

글쓰기
</Link>
</div>
Expand Down
153 changes: 153 additions & 0 deletions src/pages/login/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import FormInput from "@/components/form/form-input";
import styles from "./../signup/signup.module.css";
import { useEffect, useState } from "react";
import Link from "next/link";
import Instance, { axios } from "@/lib/axios";
import { isAxiosError } from "axios";
import { useRouter } from "next/router";

export default function Page() {
const router = useRouter();
const [loginValue, setLoginValue] = useState({
email: "",
password: "",
});

useEffect(() => {
const accessToken = localStorage.getItem("accessToken");
if (accessToken) {
router.push("/");
}
}, [router]);

const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setLoginValue((prev) => ({
...prev,
[name]: value,
}));
};

const onSignInSubmit: React.FormEventHandler<HTMLFormElement> = async (e) => {
e.preventDefault();

const { email, password } = loginValue;

if (!email || !password) {
alert("모든 필드를 채워주세요.");
return;
}
if (!/\S+@\S+\.\S+/.test(email)) {
alert("유효한 이메일 주소를 입력해주세요.");
return;
}
if (password.length < 8) {
alert("비밀번호는 최소 8자 이상이어야 합니다.");
return;
}
Comment on lines +36 to +47
Copy link
Collaborator

Choose a reason for hiding this comment

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

validation로직은 submit함수에서 따로 추출하는게 더 좋아보이네요


try {
const response = await Instance.post("/auth/signIn", {
email,
password,
});
alert("로그인이 성공적으로 완료되었습니다!");

// 서버로부터 받은 데이터 추출
const { user, accessToken, refreshToken } = response.data;

// 로컬스토리지에 토큰과 사용자 데이터 저장
localStorage.setItem("accessToken", accessToken);
localStorage.setItem("refreshToken", refreshToken);
localStorage.setItem("user", JSON.stringify(user)); // 객체는 JSON 문자열로 변환

setLoginValue({
email: "",
password: "",
});
router.push("/");
} catch (error) {
if (isAxiosError(error) && error.response) {
console.error("서버 응답 상태 코드:", error.response.status);
console.error("서버 응답 데이터:", error.response.data);
alert(error.response.data.message || "로그인에 실패했습니다.");
} else {
console.error("알 수 없는 오류:", error);
alert("로그인 중 문제가 발생했습니다. 다시 시도해주세요.");
}
}
};

return (
<div className={styles.login_wrap}>
<div className={styles.logo}>
<Link href="/">
<img src="/assets/img/logo.svg" alt="로고" />
</Link>
</div>
<form onSubmit={onSignInSubmit}>
<ul className={styles.login_list}>
<li>
<div className={styles.title}>이메일</div>
<div className={styles.content}>
<FormInput
type="email"
name="email"
placeholder="이메일을 입력해주세요"
onChange={onChange}
value={loginValue.email}
/>
</div>
</li>
<li>
<div className={styles.title}>비밀번호</div>
<div className={styles.content}>
<FormInput
type="password"
name="password"
placeholder="비밀번호를 입력해주세요"
onChange={onChange}
value={loginValue.password}
/>
</div>
</li>
<li>
<button className="btn round block large" disabled={false}>
로그인
</button>
</li>
<li>
<div className={styles.sns}>
<h3>간편 로그인하기</h3>
<ul>
<li>
<Link href="">
<img
src="/assets/img/icon_google.svg"
alt="구글 간편로그인"
/>
</Link>
</li>
<li>
<Link href="">
<img
src="/assets/img/icon_kakao.svg"
alt="카카오 간편로그인"
/>
</Link>
</li>
</ul>
</div>
</li>
<li>
<div className={styles.link}>
<p>
판다마켓이 처음이신가요? <Link href="/signup">회원가입</Link>
</p>
</div>
</li>
</ul>
</form>
</div>
);
}
Loading
Loading