Skip to content

Commit

Permalink
Merge pull request #693 from PixeIDark/Next.js-김민재-sprint11
Browse files Browse the repository at this point in the history
[김민재] Sprint11
  • Loading branch information
kiJu2 authored Jun 17, 2024
2 parents 9155dcb + 8d728c0 commit 31b8e54
Show file tree
Hide file tree
Showing 25 changed files with 447 additions and 208 deletions.
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
BASE_URL = https://panda-market-api.vercel.app
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
/.next/
/out/

/.env

# production
/build

Expand Down
3 changes: 3 additions & 0 deletions next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ const nextConfig = {
"example.com",
],
},
env: {
BASE_URL: process.env.BASE_URL,
},
};

export default nextConfig;
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"next": "14.2.3",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hook-form": "^7.51.5",
"react-responsive": "^10.0.0"
},
"devDependencies": {
Expand Down
1 change: 1 addition & 0 deletions panda.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export default defineConfig({
blueBanner: { value: "#CFE5FF" },
textBasic: { value: "#374151" },
disabledBasic: { value: "#9CA3AF" },
errorRed: { value: "#F74747" },
},
fonts: {
ROKAF: { value: "ROKAF Sans" },
Expand Down
13 changes: 13 additions & 0 deletions pnpm-lock.yaml

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

12 changes: 3 additions & 9 deletions src/apis/article/postArticles.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,19 @@
import { Token } from "@/types/api";
import axiosInstance from "../axiosInstance";
import { PostData } from "@/components/addBoardComponents/Form";
import { PostData } from "@/components/addBoardComponents/ArticleForm";

const postArticles = async (
option: PostData = {
content: "",
title: "",
image: null,
},
token?: string | null
}
) => {
const { image, ...restOption } = option;
const payload = image ? option : restOption;

try {
const { data } = await axiosInstance.post<any>("/articles", payload, {
headers: {
Authorization: `Bearer ${token}`,
},
});
return data;
await axiosInstance.post<any>("/articles", payload);
} catch (error) {
console.error(`Failed to fetch data: ${error}`);
throw error;
Expand Down
8 changes: 3 additions & 5 deletions src/apis/auth/PostRefreshToken.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { Token } from "@/types/api";
import axiosInstance from "../axiosInstance";
import { Token } from "@/types/api";

const PostRefreshToken = async (
refreshToken: string | null
): Promise<string> => {
const PostRefreshToken = async (refreshToken?: string): Promise<Token> => {
try {
const { data } = await axiosInstance.post<string>("/auth/refresh-token", {
const { data } = await axiosInstance.post<Token>("/auth/refresh-token", {
refreshToken: refreshToken,
});
return data;
Expand Down
56 changes: 53 additions & 3 deletions src/apis/axiosInstance.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import axios, { AxiosRequestConfig } from "axios";
// git push origin Next.js-김민재-sprint10 실수하지않을려고 push config설정 안했는데 브랜치이름 너무 치기어려워서 넣었습니다.
import axios, { AxiosRequestConfig, AxiosError } from "axios";
import {
getToken,
loadTokenFromLocalStorage,
saveTokenToLocalStorage,
} from "@/utils/localStorageToken";
import PostRefreshToken from "@/apis/auth/PostRefreshToken";

interface InternalAxiosRequestConfig extends AxiosRequestConfig {
_retry?: boolean;
}

const axiosConfig: AxiosRequestConfig = {
baseURL: "https://panda-market-api.vercel.app/",
baseURL: `${process.env.BASE_URL}`,
headers: {
"Content-Type": "application/json",
Accept: "application/json",
Expand All @@ -10,4 +20,44 @@ const axiosConfig: AxiosRequestConfig = {

const axiosInstance = axios.create(axiosConfig);

axiosInstance.interceptors.request.use(
async (config) => {
const accessToken = await getToken();
if (accessToken) {
config.headers.Authorization = `Bearer ${accessToken}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);

axiosInstance.interceptors.response.use(
(response) => response,
async (error: AxiosError) => {
const originalRequest = error.config as InternalAxiosRequestConfig;
if (!originalRequest.headers) {
return Promise.reject(error);
}
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
const token = loadTokenFromLocalStorage();
if (token?.refreshToken) {
try {
const newToken = await PostRefreshToken(token.refreshToken);
saveTokenToLocalStorage(newToken);
originalRequest.headers.Authorization = `Bearer ${newToken.accessToken}`;
return axiosInstance(originalRequest);
} catch (refreshError) {
console.error(`Failed to refresh token: ${refreshError}`);
return Promise.reject(refreshError);
}
}
}

return Promise.reject(error);
}
);

export default axiosInstance;
5 changes: 3 additions & 2 deletions src/apis/comment/getArticlesIdComment.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { ArticleId } from "@/types/articles";
import axiosInstance from "../axiosInstance";

interface GetArticlesIdCommentResponse {
list: [];
list: ArticleId[];
nextCursor: null | string;
}

const getArticlesIdComment = async (
articleId: string | string[] | undefined
articleId?: string | string[]
): Promise<GetArticlesIdCommentResponse> => {
try {
const { data } = await axiosInstance.get<GetArticlesIdCommentResponse>(
Expand Down
16 changes: 2 additions & 14 deletions src/apis/comment/postArticlesIdComment.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,8 @@
import axiosInstance from "../axiosInstance";

const postArticlesComment = async (
content: string,
id: string,
token?: string | null
) => {
const postArticlesComment = async (content: string, id: string) => {
try {
await axiosInstance.post<any>(
`/articles/${id}/comments`,
{ content },
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
await axiosInstance.post<any>(`/articles/${id}/comments`, { content });
} catch (error) {
console.error(`Failed to fetch data: ${error}`);
throw error;
Expand Down
5 changes: 2 additions & 3 deletions src/apis/postImage.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import axiosInstance from "@/apis/axiosInstance";

interface PostImageResponse {
url?: null | Blob | MediaSource;
image?: null | Blob | MediaSource;
}

const postImage = async (token: string | null, image?: any) => {
const postImage = async (image?: any) => {
try {
const formData = new FormData();
if (image) {
Expand All @@ -15,7 +15,6 @@ const postImage = async (token: string | null, image?: any) => {
formData,
{
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "multipart/form-data",
},
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ interface FormProps {
onChangeImage: (e: React.ChangeEvent<HTMLInputElement>) => void;
}

function Form({ postData, file, onChangeInput, onChangeImage }: FormProps) {
function ArticleForm({
postData,
file,
onChangeInput,
onChangeImage,
}: FormProps) {
const [imageUrl, setImageUrl] = useState<string | StaticImageData>(imageAdd);

useEffect(() => {
Expand All @@ -35,36 +40,42 @@ function Form({ postData, file, onChangeInput, onChangeImage }: FormProps) {
return (
<div className={cx(formStyle, css({ marginTop: "24px" }))}>
<div className={labelInput}>
<label className={labelBasicStyle}>*제목</label>
<input
name="title"
type="text"
value={postData.title}
onChange={onChangeInput}
className={inputRecipe()}
placeholder="제목을 입력해주세요"
/>
<label className={labelBasicStyle}>
*제목
<input
name="title"
type="text"
value={postData.title}
onChange={onChangeInput}
className={inputRecipe()}
placeholder="제목을 입력해주세요"
/>
</label>
</div>
<div className={labelInput}>
<label className={labelBasicStyle}>*내용</label>
<textarea
name="content"
value={postData.content}
onChange={onChangeInput}
className={inputRecipe({ visual: "xLarge" })}
placeholder="내용을 입력해주세요"
/>
<label className={labelBasicStyle}>
*내용
<textarea
name="content"
value={postData.content}
onChange={onChangeInput}
className={inputRecipe({ visual: "xLarge" })}
placeholder="내용을 입력해주세요"
/>
</label>
</div>
<div className={labelInput}>
<label className={labelBasicStyle}>이미지</label>
<input
type="file"
name="image"
accept="image/*"
onChange={onChangeImage}
className={css({ display: "none" })}
id="image-upload"
/>
<label className={labelBasicStyle}>
이미지
<input
type="file"
name="image"
accept="image/*"
onChange={onChangeImage}
className={css({ display: "none" })}
id="image-upload"
/>
</label>
<label htmlFor="image-upload" className={css({ cursor: "pointer" })}>
<Image
src={imageUrl}
Expand All @@ -83,4 +94,4 @@ function Form({ postData, file, onChangeInput, onChangeImage }: FormProps) {
);
}

export default Form;
export default ArticleForm;
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ interface FormTitleProps {
handleSubmit: MouseEventHandler<HTMLButtonElement>;
}

function FormTitle({ isValid, handleSubmit }: FormTitleProps) {
function ArticleFormTitle({ isValid, handleSubmit }: FormTitleProps) {
return (
<div className={hstack({ justifyContent: "space-between" })}>
<h2 className={subTitle}>게시글 쓰기</h2>
Expand All @@ -24,4 +24,4 @@ function FormTitle({ isValid, handleSubmit }: FormTitleProps) {
);
}

export default FormTitle;
export default ArticleFormTitle;
4 changes: 2 additions & 2 deletions src/components/boardsComponents/NormalPost.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ function NormalPost() {
useEffect(() => {
const loadArticles = async () => {
const receive = await getArticles({
orderBy: `${orderBy}`,
keyword: `${searchValue}`,
orderBy: orderBy,
keyword: searchValue,
});
setArticles(receive.list);
};
Expand Down
8 changes: 7 additions & 1 deletion src/components/shared/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,15 @@ import { useRouter } from "next/router";
import Image from "next/image";
import { buttonRecipe } from "@/css/recipe/buttonRecipe.styled";
import userPassiveIcon from "@/assets/icons/user_passive_ic.svg";
import { useEffect, useState } from "react";

function Header() {
const router = useRouter();
const [isToken, setIsToken] = useState<string | undefined>("");

useEffect(() => {
setIsToken(localStorage.getItem("accessToken")?.replace(/"/gi, ""));
});

return (
<div className={headerContainer}>
Expand All @@ -40,7 +46,7 @@ function Header() {
중고마켓
</Link>
</div>
{router.pathname !== "/" ? (
{isToken ? (
<Link href="/signin">
<Image src={userPassiveIcon} alt="userPassive" />
</Link>
Expand Down
Loading

0 comments on commit 31b8e54

Please sign in to comment.