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

[정인재] Sprint11 #322

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 4 additions & 3 deletions components/bestboarditemlist.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import favorite from "@/images/favorite.png";
import { BoardItemProps, BoardItemListProps } from "@/interfaces/boardItem";
import Link from "next/link";
import { FormatDate } from "@/pages/util/formatDate";
import defaultImage from "@/images/img_default.png";

export default function BestBoardItemList({ boards }: BoardItemListProps) {
return (
Expand All @@ -24,12 +25,12 @@ function BestBoardItem({ board }: BoardItemProps) {
<BoardWrapper>
<TitleWrapper>
<BoardTitle>{board.title}</BoardTitle>
{/* <Image
src={board.image}
<Image
src={board.image || defaultImage}
Copy link
Collaborator

Choose a reason for hiding this comment

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

board에 image가 없는 경우 defaultImage를 잘 넣어주셨네요!
추가로 board에 image가 있더라도 이미지 로딩 시 에러가 발생했을때 defaultImage를 넣어주는 것도 반영해주시면 좋을 것 같습니다.

alt="BestBoardImage"
width={72}
height={72}
/> */}
/>
</TitleWrapper>
<WriterWrapper>
<WriterLeftElement>
Expand Down
3 changes: 2 additions & 1 deletion components/boarditemlist.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Image from "next/image";
import { BoardItemProps, BoardItemListProps } from "@/interfaces/boardItem";
import Link from "next/link";
import { FormatDate } from "@/pages/util/formatDate";
import defaultImage from "@/images/img_default.png";

export default function BoardItemList({ boards }: BoardItemListProps) {
return (
Expand All @@ -22,7 +23,7 @@ function BoardItem({ board }: BoardItemProps) {
<TitleWrapper>
<BoardTitle>{board.title}</BoardTitle>
<Image
src={board.image}
src={board.image || defaultImage}
alt="BestBoardImage"
width={72}
height={72}
Expand Down
10 changes: 6 additions & 4 deletions components/fileinput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import styled from "styled-components";
import Image from "next/image";
import plusIcon from "@/images/ic_plus.svg";
import { FileInputType } from "@/interfaces/article";
import { postImage } from "@/pages/util/api";

function FileInput({ value, onChange }: FileInputType) {
function FileInput({ value, fileChange, previewChange }: FileInputType) {
const inputRef = useRef<HTMLInputElement | null>(null);
const [preview, setPreview] = useState<string | null>(null);

const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
const nextValue = e.target.files ? e.target.files[0] : null;
onChange(nextValue);
fileChange(nextValue);
};

const handleClearClick = () => {
Expand All @@ -22,7 +23,7 @@ function FileInput({ value, onChange }: FileInputType) {
inputNode.value = "";

setPreview(null);
onChange(null);
fileChange(null);
};

useEffect(() => {
Expand All @@ -31,7 +32,8 @@ function FileInput({ value, onChange }: FileInputType) {
const nextpreview = URL.createObjectURL(value);

setPreview(nextpreview);
console.log(nextpreview);
previewChange(nextpreview);
// console.log(nextpreview);
Copy link
Collaborator

Choose a reason for hiding this comment

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

불필요한 주석은 지워주시면 좋을 것 같습니다.


// 메모리 누수를 방지하기 위해 URL 객체를 해제
return () => {
Expand Down
6 changes: 5 additions & 1 deletion components/primarybutton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ export default function PrimaryButton({
disabled: disabled = false,
Copy link
Collaborator

Choose a reason for hiding this comment

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

여기는 disabled = false 이런식으로 작성해주셔도 똑같이 동작합니다!

onClick,
}: ButtonProps) {
return <Button disabled={disabled}>{children}</Button>;
return (
<Button disabled={disabled} onClick={onClick}>
{children}
</Button>
);
}

const Button = styled.button<{ disabled: boolean }>`
Expand Down
45 changes: 45 additions & 0 deletions components/primaryinput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { ChangeEventHandler, FocusEventHandler } from "react";
import styled from "styled-components";

interface InputType {
id: string;
name: string;
type: string;
placeholder: string;
onChange?: ChangeEventHandler<HTMLInputElement>;
onBlur?: FocusEventHandler<HTMLInputElement>;
hasError?: boolean;
}

export default function PrimaryInput({
id,
name,
type,
placeholder,
onChange,
onBlur,
hasError,
}: InputType) {
return (
<Input
id={id}
name={name}
type={type}
placeholder={placeholder}
onChange={onChange}
onBlur={onBlur}
hasError={hasError}
/>
);
}

const Input = styled.input<{ hasError?: boolean }>`
width: 100%;
border-radius: 12px;
padding: 16px 24px;
background-color: #f3f4f6;
color: #9ca3af;
Copy link
Collaborator

Choose a reason for hiding this comment

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

컬러 변수가 누락된 곳이 종종 있는데, 혹시 사용하지 않고 계시다면 사용해보셔도 좋을 것 같습니다. 컬러를 변수로 관리 하신다면 색상 수정할때 한번에 수정할 수 있거든요.

font-size: 16px;
font-weight: 400;
border: ${({ hasError }) => (hasError ? "1px solid red" : "none")};
Copy link
Collaborator

Choose a reason for hiding this comment

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

hasError가 undefined가 올 수도 있기 때문에 !!hasError 이런식으로 작성해주시면 확실하게 boolean으로 판단이 가능해집니다.

`;
66 changes: 62 additions & 4 deletions components/topbar.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,48 @@
import PrimaryButton from "./primarybutton";
import pandaLogo from "@/images/logo.png";
import mobilePandaLogo from "@/images/mobilelogo.png";
import userIcon from "@/images/user.png";
import Link from "next/link";
import Image from "next/image";
import styled from "styled-components";
import { isLoggedIn } from "@/pages/util/api";
import { useEffect, useState } from "react";
import PrimaryButton from "./primarybutton";

export default function Topbar() {
const [isLogin, setIsLogin] = useState(false);
const [isOpen, setIsOpen] = useState(false);

useEffect(() => {
if (isLoggedIn() == true) {
setIsLogin(true);
} else {
setIsLogin(false);
}
}, [isLogin]);

const handleLogClick = () => {
setIsOpen((prev) => !prev);
};

const handleLogoutClick = () => {
setIsLogin(false);
localStorage.removeItem("access_token");
localStorage.removeItem("refresh_token");
window.location.href = "/login"; // 로그아웃 후 리다이렉션
};

return (
<TopbarHeader>
<LeftElement>
<Link href="/">
<LogoWrapper>
<Image src={pandaLogo} alt="Panda Logo" className="desktop" />
<Image
src={pandaLogo}
alt="Panda Logo"
className="desktop"
width={153}
height={51}
/>
<Image
src={mobilePandaLogo}
alt="Mobile Panda Logo"
Expand All @@ -29,8 +59,18 @@ export default function Topbar() {
</Link>
</ButtonWrapper>
</LeftElement>
<Image src={userIcon} alt="userIcon" />
{/* <PrimaryButton>로그인</PrimaryButton> */}
{isLogin ? (
<LogOutWrapper onClick={handleLogClick}>
<Image src={userIcon} alt="userIcon" />
{isOpen && (
<LogOutButton onClick={handleLogoutClick}>로그아웃</LogOutButton>
)}
</LogOutWrapper>
) : (
<Link href="/login">
<PrimaryButton>로그인</PrimaryButton>
</Link>
)}
</TopbarHeader>
);
}
Expand Down Expand Up @@ -99,3 +139,21 @@ const LogoWrapper = styled.div`
}
}
`;

const LogOutWrapper = styled.div`
position: relative;
`;

const LogOutButton = styled.button`
position: absolute;
width: 139px;
height: 51px;
border: solid 1px #d1d5db;
border-radius: 8px;
padding: 16px 0;
margin-top: 50px;
margin-left: -130px;
font-size: 16px;
Comment on lines +154 to +155
Copy link
Collaborator

Choose a reason for hiding this comment

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

position이 absolute이기 때문에 margin 대신 left나 top 속성을 써보시는 것도 추천드립니다.

font-weight: 400;
background-color: #ffffff;
`;
Binary file added images/Img_home_01.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 images/Img_home_02.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 images/Img_home_03.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 images/Img_home_top.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions images/ic_eyeclose.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions images/ic_eyeopen.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 images/ic_google.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 images/ic_kakao.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 images/img_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 modified images/logo.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 images/logo2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions interfaces/article.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
export interface articleType {
title: string;
content: string;
image: string;
image: string | null;
}

export interface FileInputType {
value: File | null;
onChange: (file: File | null) => void;
fileChange: (file: File | null) => void;
previewChange: (preview: string | null) => void;
}
6 changes: 6 additions & 0 deletions interfaces/user.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface UserInfo {
email: string;
nickname?: string;
password: string;
passwordConfirmation?: string;
}
47 changes: 46 additions & 1 deletion lib/axios.ts
Copy link
Collaborator

Choose a reason for hiding this comment

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

인터셉터를 활용해서 요청과 응답 시 공통되는 로직을 합쳐주신거 좋네요!

Original file line number Diff line number Diff line change
@@ -1,7 +1,52 @@
import axios from "axios";
import axios, { AxiosError } from "axios";

const instance = axios.create({
baseURL: " https://panda-market-api.vercel.app/",
});

// 요청 인터셉터
instance.interceptors.request.use(
(config) => {
// 요청이 가기 전에 할 작업
// 예: 인증 토큰 추가
const token = localStorage.getItem("access_token");
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
// 요청 에러 처리
return Promise.reject(error);
}
);

// 응답 인터셉터
instance.interceptors.response.use(
(response) => {
// 응답 데이터 처리
return response;
},
(error: AxiosError) => {
// 응답 에러 처리
if (axios.isAxiosError(error)) {
if (error.response) {
// 서버 응답이 있는 경우
console.error("서버 에러:", error.response.data);
alert("유효하지 않은 아이디 입니다.");
} else if (error.request) {
// 요청이 보내졌으나 응답이 없는 경우
console.error("요청 에러:", error.request);
} else {
// 에러를 발생시킨 요청 구성 문제
console.error("에러 구성:", error.message);
}
} else {
// Axios가 아닌 다른 에러
console.error("기타 에러:", error);
}
return Promise.reject(error);
}
);

export default instance;
9 changes: 8 additions & 1 deletion next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ const nextConfig = {
protocol: 'https',
hostname: 'sprint-fe-project.s3.ap-northeast-2.amazonaws.com',
},
{
protocol: 'http',
hostname: 'via.placeholder.com',
},
{
protocol: 'https',
hostname: 'flexible.img.hani.co.kr',
},
],
},
reactStrictMode: true,
Expand All @@ -22,7 +30,6 @@ const nextConfig = {
]
},


}

module.exports = nextConfig
11 changes: 9 additions & 2 deletions pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,22 @@ import type { AppProps } from "next/app";
import GlobalStyle from "@/styles/globalstyle";
import Topbar from "@/components/topbar";
import Head from "next/head";
import { useRouter } from "next/router";

export default function App({ Component, pageProps }: AppProps) {
const router = useRouter();

const isLoginPage =
router.pathname === "/login" || router.pathname === "/signup";

const isLandingPage = router.pathname === "/";
return (
<>
<Head>
<title>판다마켓</title>
</Head>
<GlobalStyle />
<Topbar />
<GlobalStyle isLoginPage={isLoginPage} isLandingPage={isLandingPage} />
{!isLoginPage && <Topbar />}
<Component {...pageProps} />
</>
);
Expand Down
Loading
Loading