Skip to content
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

[이형준]sprint10 #295

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
a6d9f28
fix: 폰트 적용 안되던 것 수정
leehj322 Aug 14, 2024
1e122bd
refactor(shared): GlobalNavBar isMain 프롭 제거
leehj322 Aug 14, 2024
f6ef68e
feat(BoardsPage): Link 컴포넌트 추가
leehj322 Aug 14, 2024
e761f42
refactor(axios): axios이름 변경 및 폴더 변경
leehj322 Aug 14, 2024
db2c5cf
feat(auth): 임시 로그인 기능 구현
leehj322 Aug 16, 2024
ce26be3
refactor(shared): GlobalNavBar isLogin prop 제거
leehj322 Aug 16, 2024
0ea5e73
feat(apis): 기존 getArticles api 수정 및 추가 api 구현
leehj322 Aug 16, 2024
bc8ae5b
fix(shared): GlobalNavBar isLogin 관련 오류 수정
leehj322 Aug 16, 2024
4e47af7
refactor(shared): BlueButton 컴포넌트 props 전달 추가
leehj322 Aug 16, 2024
092f3b6
refactor(BoardsPage): Image 컴포넌트 src null 처리
leehj322 Aug 16, 2024
ad525e2
refactor(apis): api image null 관련 로직 추가 및 인터셉터 로직 변경
leehj322 Aug 16, 2024
60e9572
feat(apis): 이미지 파일을 url로 변경해주는 uploadImage api 추가
leehj322 Aug 16, 2024
79fa8e1
feat(shared): input 관련 공통 컴포넌트 추가
leehj322 Aug 16, 2024
ef844a5
refactor(style): textarea global.css 값 추가
leehj322 Aug 16, 2024
77a126c
feat(ArticleAddPage): 게시글 추가 페이지 완성
leehj322 Aug 16, 2024
c23bdbb
fix(BoardsPage): type 오타 수정
leehj322 Aug 16, 2024
058ec01
refactor(게시글추가페이지): 게시글 추가 페이지 리팩토링
leehj322 Aug 17, 2024
3db9927
feat(utils): elapsedTimeCalc.ts 추가
leehj322 Aug 17, 2024
fbff985
refactor(utils): elapsedTimeCalc jsdoc 추가
leehj322 Aug 17, 2024
2c99d20
feat(제품 상세 페이지): 제품상세페이지 UI 구현
leehj322 Aug 17, 2024
69a4d98
refactor(shared): GrayTextArea 스타일 수정
leehj322 Aug 17, 2024
eb8ebdc
feat(게시글 질문 페이지): 댓글달기 api 요청 적용
leehj322 Aug 17, 2024
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
4 changes: 4 additions & 0 deletions public/images/ic_back_arrow.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/ic_kebab_btn.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/ic_register_img_file.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/img_del_btn_default.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/img_no_comments.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 changes: 0 additions & 48 deletions src/apis/getArticles.ts

This file was deleted.

7 changes: 0 additions & 7 deletions src/apis/instance.ts

This file was deleted.

86 changes: 86 additions & 0 deletions src/axios/articles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import axiosInstance from "./instance";

interface Writer {
id: number;
nickname: string;
}

export interface Article {
id: number;
title: string;
content: string;
image: string | null;
likeCount: number;
writer: Writer;
createdAt: string;
updatedAt: string;
}

export type OrderOption = "like" | "recent";

interface GetArticlesProps {
Copy link
Collaborator

Choose a reason for hiding this comment

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

이 타입이 api 요청을 보낼때 매개변수 타입으로 쓰인다면, GetArticlesParams 같은 이름도 추천드립니다.

page?: number;
pageSize: number;
orderBy?: OrderOption;
keyword?: string;
}

interface ArticlesResponse {
list: Article[];
totalCount: number;
}

export async function getArticles({
page = 1,
pageSize,
orderBy = "recent",
keyword = "",
}: GetArticlesProps) {
let query = `page=${page}&pageSize=${pageSize}&orderBy=${orderBy}`;
if (keyword) {
query += `&keyword=${keyword}`;
}

try {
const res = await axiosInstance.get(`/articles?${query}`);
const { list, totalCount }: ArticlesResponse = res.data;
return { list, totalCount };
} catch (error) {
return { list: [], totalCount: 0 };
}
}

interface GetArticleByIDProps {
articleId: number;
}

export async function getArticleByID({ articleId }: GetArticleByIDProps) {
const res = await axiosInstance.get(`/articles/${articleId}`);
try {
const article: Article = res.data;
return article;
} catch {
throw new Error("게시글 응답이 올바르지 않습니다.");
}
}

export interface PostArticleProps {
image: string | null;
content: string;
title: string;
}

export async function postArticle({ image, content, title }: PostArticleProps) {
try {
let res;
if (image) {
res = await axiosInstance.post("/articles", { image, content, title });
} else {
res = await axiosInstance.post("/articles", { content, title });
}
const postedArticle: Article = res.data;
return postedArticle;
} catch (error) {
console.log(error);
}
}
47 changes: 47 additions & 0 deletions src/axios/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import axiosInstance from "./instance";

export interface LogInUserProps {
email: string;
password: string;
}

export interface SignUpUserProps extends LogInUserProps {
Copy link
Collaborator

Choose a reason for hiding this comment

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

extends 활용하신거 좋네요~

nickname: string;
passwordConfirmation: string;
}

export async function signUpUser({
email,
nickname,
password,
passwordConfirmation,
}: SignUpUserProps) {
try {
const res = await axiosInstance.post("/auth/signUp", {
email,
nickname,
password,
passwordConfirmation,
});
const { accessToken, refreshToken } = res.data;

localStorage.setItem("access_token", accessToken);
localStorage.setItem("refresh_token", refreshToken);
window.location.href = "/";
Comment on lines +28 to +30
Copy link
Collaborator

Choose a reason for hiding this comment

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

api 요청을 보내는 부분에서는 api 요청만 보내고 이런 로직들은 훅으로 만들어서 api 호출할때 추가해주시는 것을 추천드립니다. 각 함수의 역할을 분리해주는 것이죠!

} catch (error) {
console.log(error);
}
}

export async function logInUser({ email, password }: LogInUserProps) {
try {
const res = await axiosInstance.post("/auth/signIn", { email, password });
const { accessToken, refreshToken } = res.data;

localStorage.setItem("access_token", accessToken);
localStorage.setItem("refresh_token", refreshToken);
window.location.href = "/";
} catch (error) {
console.log(error);
}
}
46 changes: 46 additions & 0 deletions src/axios/comments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import axiosInstance from "./instance";

interface Writer {
id: number;
image: string;
nickname: string;
}

export interface Comment {
id: number;
content: string;
updatedAt: string;
createdAt: string;
writer: Writer;
}

interface GetArticleCommentsProps {
articleId: number;
limit: number;
cursor?: number;
}

export interface ArticleCommentsResponse {
nextCursor: number | null;
list: Comment[];
}

export async function getArticleComments({ articleId, limit, cursor }: GetArticleCommentsProps) {
let query = `limit=${limit}`;
if (cursor) {
query += `&cursor=${cursor}`;
}
const res = await axiosInstance.get(`/articles/${articleId}/comments?${query}`);
const { nextCursor, list }: ArticleCommentsResponse = res.data;
return { nextCursor, list };
}

interface PostArticleCommentProps {
articleId: number;
content: string;
}

export async function postArticleComment({ articleId, content }: PostArticleCommentProps) {
const res = await axiosInstance.post(`/articles/${articleId}/comments`, { content });
return res.data as Comment;
}
21 changes: 21 additions & 0 deletions src/axios/images.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import axiosInstance from "./instance";

interface UploadImageProps {
imageFile: File;
}

export default async function uploadImage({ imageFile }: UploadImageProps) {
// image file을 form data로 변경
const formDataForSubmit = new FormData();
formDataForSubmit.append("image", imageFile);

try {
const res = await axiosInstance.post("/images/upload", formDataForSubmit, {
headers: { "Content-Type": "multipart/form-data" },
});
const { url } = res.data;
return url;
} catch (error) {
console.log(error);
}
}
74 changes: 74 additions & 0 deletions src/axios/instance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import axios, { AxiosResponse, AxiosError, InternalAxiosRequestConfig } from "axios";

const axiosInstance = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL,
});

/**
* http request가 넘어가기 전에 call 되는 함수
*/
const onRequest = (config: InternalAxiosRequestConfig) => {
if (typeof window !== "undefined") {
const accessToken = localStorage.getItem("access_token");

if (accessToken && config.headers) {
config.headers["Authorization"] = `Bearer ${accessToken}`;
}
}
return config;
};

/**
* http response가 catch로 넘어가기 전에 call 되는 함수
*/
const onErrorResponse = async (error: AxiosError | Error) => {
if (axios.isAxiosError(error)) {
const { status, data, config } = error.response as AxiosResponse;

switch (status) {
case 400: {
alert("입력한 정보가 올바르지 않습니다.");
break;
}

case 401: {
const refreshToken = localStorage.getItem("refresh_token");

if (refreshToken) {
if (data.message === "jwt expired") {
const res = await axiosInstance.post("/auth/refresh-token", { refreshToken });
const { accessToken } = res.data;

localStorage.setItem("access_token", accessToken);

config.headers["Authorization"] = `Bearer ${accessToken}`;
return axiosInstance(config);
} else if (data.message === "jwt malformed") {
localStorage.removeItem("access_token");
localStorage.removeItem("refresh_token");
alert("세션이 올바르지 않습니다. 다시 로그인 해주세요.");
window.location.href = "/login";
} else {
console.log(data.message);
}
} else {
alert("로그인이 필요합니다.");
window.location.href = "/login";
}
break;
}

default: {
console.log(error.message);
break;
}
}
}

return Promise.reject(error);
};

axiosInstance.interceptors.request.use(onRequest);
axiosInstance.interceptors.response.use((response: AxiosResponse) => response, onErrorResponse);
Comment on lines +71 to +72
Copy link
Collaborator

Choose a reason for hiding this comment

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

interceptor 활용 잘해주셨네요!


export default axiosInstance;
3 changes: 2 additions & 1 deletion src/components/@shared/BlueButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
* @param shape default(0.5rem), pill(알약형) 중 한가지 선택
* @returns 공통 스타일 버튼 컴포넌트
*/
export default function BlueButton({ customStyle, shape, children }: ButtonProps) {
export default function BlueButton({ customStyle, shape, children, ...rest }: ButtonProps) {
return (
<button
{...rest}
className={`${customStyle} ${
shape === "pill" ? "rounded-full" : "rounded-lg"
} flex justify-center items-center gap-2 w-full h-full font-semibold bg-brand-blue text-gray-100 hover:bg-blue-hover active:bg-blue-active disabled:bg-gray-400 `}
Expand Down
Loading
Loading