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

[김미소] week14 #445

Merged
merged 21 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
d6de20f
Merge branch 'part3-김미소' into part3-김미소-week13
nightowlzz May 13, 2024
569075d
Feat: 메인페이지 React에서 Next.js로의 전환과 관련된 작업 추가 및 수정
nightowlzz May 13, 2024
97ad057
Feat: 특정페이지 header, footer 노출 유무 작업
nightowlzz May 14, 2024
c28130d
Feat: 로그인, 회원가입 JS파일 Next.js로 이동 및 오류수정
nightowlzz May 14, 2024
2259ce3
Feat: 회원가입 react-hook-form 적용 및 중복확인 작업
nightowlzz May 16, 2024
3a2d697
Feat: 로그인 페이지 react-hook-form 적용 및 가입확인 api 연동
nightowlzz May 16, 2024
9268e71
Move: pages폴더 안에있던 style 위치이동 및 로그인,회원가입 로그인지 접속제한
nightowlzz May 16, 2024
eba660c
Feat: 로그인 유무에 따라 header의 유저정보 보이기
nightowlzz May 16, 2024
bcc432c
Rename: login => signin으로 이름변경 및 input eorror 클래스 추가
nightowlzz May 16, 2024
248039b
Fix: 페이지확인 하면 오류 수정
nightowlzz May 16, 2024
8c0d0a9
Fix: prettier, eslint 설치하고 코드라인 정리
nightowlzz May 16, 2024
0ab9e90
Fix: 코드리뷰 받은 것들 수정
nightowlzz May 16, 2024
ff1514b
Refactor: 로그인시 접근제한 cookie인증으로 수정, redirect로 페이지 이동
nightowlzz May 18, 2024
59de712
Refactor: header, footer 특정페이지 접근히 노출 제한
nightowlzz May 18, 2024
ba0dc40
Fix: 이미지 주소 변수명 변경
nightowlzz May 18, 2024
bd9b2e6
Fix: boolean 변수 이름 변경
nightowlzz May 18, 2024
184bce6
Fix: getServerSideProps let을 const로 변경하기 위한 구조변경
nightowlzz May 18, 2024
0453cae
Fix: 로그인시 url입력 이동하면 유저정보가 로그인버튼이 노출되는 에러 수정
nightowlzz May 18, 2024
61fd9d4
Refactor: <img/> 태그를 nextjs <Image/>로 변경
nightowlzz May 18, 2024
73d070d
Refactor: Modal전체 구조 변경
nightowlzz May 18, 2024
87bd07a
Refactor: 로딩 화면 추가
nightowlzz May 19, 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
7 changes: 6 additions & 1 deletion .babelrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
"next/babel"
],
"plugins": [
"babel-plugin-styled-components"
[
"styled-components",
{
"ssr": true
}
]
]
}
92 changes: 54 additions & 38 deletions components/common/Footer.tsx
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,58 +1,74 @@
import Link from "next/link";
import SocialLinkButton from "./SocialLinkButton";
import {
FootInner,
FootNav,
FootSign,
FootSocial,
FootWrap,
} from "./footerStyle";
export interface IImageSnsArr {
id: string;
src: string;
link: string;
}
const imageSnsArr: IImageSnsArr[] = [
import { useEffect, useState } from 'react';
import { FootInner, FootNav, FootSign, FootSocial, FootWrap } from './footerStyle';
import LinkButton from './atoms/LinkButton';
import Link from 'next/link';
import Image from 'next/image';
import { useRouter } from 'next/router';

export const snsIconSrc = [
{
id: "Facebook",
src: "/assets/icon/icons_face.svg",
link: "https://www.facebook.com/?locale=ko_KR",
id: 'Facebook',
src: '/assets/icon/icons_face.svg',
link: 'https://www.facebook.com/?locale=ko_KR',
},
{
id: "Twitter",
src: "/assets/icon/icons_twt.svg",
link: "https://twitter.com/?lang=ko",
id: 'Twitter',
src: '/assets/icon/icons_twt.svg',
link: 'https://twitter.com/?lang=ko',
},
{
id: "YouTube",
src: "/assets/icon/icons_you.svg",
link: "https://www.youtube.com/",
id: 'YouTube',
src: '/assets/icon/icons_you.svg',
link: 'https://www.youtube.com/',
},
{
id: "Instagram",
src: "/assets/icon/icons_ins.svg",
link: "https://www.instagram.com/",
id: 'Instagram',
src: '/assets/icon/icons_ins.svg',
link: 'https://www.instagram.com/',
},
];

const hidePages = ['/signin', '/signup'];

function Footer() {
const { pathname } = useRouter();
const [isHideFooter, setIsHideFooter] = useState(true);

useEffect(() => {
setIsHideFooter(hidePages.includes(pathname));
}, [pathname]);

if (isHideFooter) return null;

return (
<FootWrap className="foot__main">
<FootInner className="foot__inner">
<FootSign className="foot__sign">
<Link href="https://www.codeit.kr/" target="_blank">
©codeit - 2023
<FootWrap className='foot__main'>
<FootInner className='foot__inner'>
<FootSign className='foot__sign'>
<Link
href='https://www.codeit.kr/'
target='_blank'>
©codeit - 2024
</Link>
</FootSign>

<FootNav className="d__flex foot__nav">
<Link href="/privacy">Privacy Policy</Link>
<Link href="/faq">FAQ</Link>
<FootNav className='d__flex foot__nav'>
<Link href='/privacy'>Privacy Policy</Link>
<Link href='/faq'>FAQ</Link>
</FootNav>

<FootSocial className="d__flex foot__btn__sns">
{imageSnsArr.map((sns) => (
<SocialLinkButton key={sns.id} {...sns} />
<FootSocial className='d__flex foot__btn__sns'>
{snsIconSrc.map((sns) => (
<LinkButton
key={sns.id}
$link={sns.link}
$linkClass={`link--social-emoji`}>
<Image
src={sns.src}
alt={sns.id}
width={20}
height={20}
/>
</LinkButton>
))}
</FootSocial>
</FootInner>
Expand Down
100 changes: 54 additions & 46 deletions components/common/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,66 +1,74 @@
import { useEffect, useState } from "react";
import axios from "@/lib/axios";
import { Email, HeaderControl, HeaderInner, HeaderLogo, HeaderUserInfo, HeaderWrap } from "./headerStyle";
import { useRouter } from "next/router";
import { Profile } from "@/styles/commonStyle";
import LinkButton from "./atoms/LinkButton";
import Link from "next/link";
import Image from "next/image";
import { useContext, useEffect, useState } from 'react';
import { Email, HeaderControl, HeaderInner, HeaderLogo, HeaderWrap } from './headerStyle';
import { Profile } from '@/styles/commonStyle';
import { AuthContext } from '@/lib/auto.context';
import { joinInstance } from '@/lib/axios';
import LinkButton from './atoms/LinkButton';
import Button from './atoms/Button';
import Link from 'next/link';
import Image from 'next/image';
import { useRouter } from 'next/router';

const logo = '/assets/logo/logo.svg';
const LOGO_IMAGE = '/assets/logo/logo.svg';

export interface IHeaderUser {
id:number,
email:string,
name?:string,
image_source?:string,
created_at?:string,
auth_id:string
id: number;
email: string;
name?: string;
image_source?: string;
created_at?: string;
auth_id: string;
}

export interface IHeaderUserLoginInfoApi {
userInfo?: {
data: IHeaderUser[];
};
}

export async function getStaticProps() {
const res = await axios.get(``);
const userInfo = res.data;
const hidePages = ['/signin', '/signup'];
const noHeaderFixed = ['/folder'];

return {
props:{
userInfo,
}
}
}
function Header() {
const router = useRouter();
const { pathname } = router;
const { isLoggedIn, handleLogout } = useContext(AuthContext);
const [isfixed, setIsFixed] = useState(true);
const [isHideHeader, setIsHideHeader] = useState(true);
const [userInfo, setUserInfo] = useState<IHeaderUser | null>();

function Header({userInfo}:IHeaderUserLoginInfoApi) {
const {pathname} = useRouter();
const [fixed, setFixed] = useState(true);
const handleUserInfo = async () => {
const res = await joinInstance.get(`/sample/user`);
setUserInfo(JSON.parse(JSON.stringify(res.data)));
Copy link
Collaborator

Choose a reason for hiding this comment

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

다음과 같이 구조분해할당을 할 수 있습니다 !

Suggested change
const res = await joinInstance.get(`/sample/user`);
const { data } = await joinInstance.get(`/sample/user`);

Copy link
Collaborator

Choose a reason for hiding this comment

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

보통 api 함수들은 따로 빼놓고 사용하는게 일반적입니다 ! 😊

Next.js 프로젝트에서는 API 호출 로직을 별도의 파일로 분리하여 관리하는 것이 일반적이예요. 이렇게 하면 코드의 재사용성을 높이고, 유지보수가 쉬워질 수 있습니다 ! API 함수를 모듈화하여 사용하면 코드가 더 깔끔하고 읽기 쉬워집니다. 다음은 Next.js 프로젝트의 디렉토리 구조와 API 함수 예제입니다:

// src/services/apis/user.api.ts (예시입니다 !)
export const getUserInfo = async () => {
  const response = await axios.get('/sample/user');
  return response.data;
};

};
Copy link
Collaborator

Choose a reason for hiding this comment

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

혹시 JSON 임베드 모듈을 사용하게 된 이유가 있으실까요? 🤔


useEffect(() => {
if (pathname === '/folder') {
setFixed(false);
}
handleUserInfo();
setIsHideHeader(hidePages.includes(pathname));
setIsFixed(noHeaderFixed.includes(pathname));
Copy link
Collaborator

Choose a reason for hiding this comment

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

pathname의 특정 경로 체크는 startsWith로 정확하게 할 수 있어요.

Suggested change
setIsHideHeader(hidePages.includes(pathname));
setIsHideHeader(hidePages.some(page => pathname.startsWith(page)));

경로의 포함 여부를 체크할 때 includes 메서드는 문자열의 부분 일치를 검사하므로, 정확한 경로 매칭이 필요하다면 startsWith를 사용하는 것이 더 좋습니다. startsWith는 문자열이 특정 문자열로 시작하는지 확인할 수 있어서 의도한 대로 정확한 경로를 확인할 수 있습니다.

includes란?: 문자열이 특정 문자열을 포함하고 있는지를 검사합니다. 부분 일치를 확인할 때 사용됩니다.

const pathname = '/signin/extra';
console.log(pathname.includes('/signin'));
// true

startsWith란?: 문자열이 특정 문자열로 시작하는지를 검사합니다. 정확한 시작을 확인할 때 사용됩니다.

const pathname = '/signin/extra';
console.log(pathname.startsWith('/signin'));
// true

}, [pathname]);
Comment on lines +40 to +42
Copy link
Collaborator

Choose a reason for hiding this comment

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

오호. 조건부 렌더링을 통해서 레이아웃을 감추고 있군요?


if (isHideHeader) return null;

Copy link
Collaborator

Choose a reason for hiding this comment

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

굳굳 ! Guard Clause 패턴을 사용하셨군요 👍

return (
<HeaderWrap className="head__wrap" $position={fixed}>
<HeaderWrap
className='head__wrap'
$position={isfixed}>
<HeaderInner>
<HeaderLogo className="head__logo">
<Link href="/">
<img src={logo} alt="linkbrary" />
<HeaderLogo className='head__logo'>
<Link href='/'>
<Image
src={LOGO_IMAGE}
alt='linkbrary'
width={133}
height={25}
/>
</Link>
</HeaderLogo>
<HeaderControl className="head__login__box">
{userInfo ? (
<HeaderUserInfo>
<HeaderControl className='head__login__box'>
{isLoggedIn ? (
<Button onclick={handleLogout}>
<Profile></Profile>
<Email>{userInfo?.data[0].email}</Email>
</HeaderUserInfo>
<Email>{userInfo?.email}</Email>
</Button>
) : (
<LinkButton $link={'/signin'} $linkClass={'link--gradient link--login large'}>
<LinkButton
$link={'/login'}
$linkClass={'link--gradient link--login large'}>
로그인
</LinkButton>
)}
Expand Down
9 changes: 0 additions & 9 deletions components/common/SocialLinkButton.tsx

This file was deleted.

14 changes: 3 additions & 11 deletions components/common/atoms/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,22 @@ import { ButtonHTMLAttributes } from 'react';
import { ButtonModule } from './buttonStyle';
interface IButtonModule {
children: React.ReactNode;
$btnClass: string;
$btnClass?: string;
$BeforButtonIcon?: string;
$id?: string;
$afterButtonIcon?: string;
$type?: ButtonHTMLAttributes<HTMLButtonElement>['type'];
onclick?: () => void;
}

export default function Button({
children,
$btnClass,
$type = 'button',
$BeforButtonIcon = '',
$afterButtonIcon = '',
onclick,
}: IButtonModule) {
export default function Button({ children, $btnClass, $type = 'button', $BeforButtonIcon = '', $afterButtonIcon = '', onclick }: IButtonModule) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

React에서 제공하는 HTML 타입을 사용해보시는건 어떨까요?

다음과 같이 리액트에서 제공하는 Attributes를 사용할 수도 있습니다:

import cn from 'classnames';
import { ButtonHTMLAttributes } from 'react';

interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: 'primary' | 'secondary' | 'none';
}

export default function MelonButton({ className, variant, ...rest }: Props) {

Copy link
Collaborator

Choose a reason for hiding this comment

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

미소님 같은 경우 다음과 같은 타입이 정의 될 수 있겠네요 ! 😊:

Suggested change
export default function Button({ children, $btnClass, $type = 'button', $BeforButtonIcon = '', $afterButtonIcon = '', onclick }: IButtonModule) {
interface IButtonModule {
children: React.ReactNode;
$btnClass?: string;
$BeforButtonIcon?: string;
$id?: string;
$afterButtonIcon?: string;
$type?: ButtonHTMLAttributes<HTMLButtonElement>['type'];
// onclick?: () => void;
} extends ButtonHTMLAttributes<HTMLButtonElement>;

return (
<ButtonModule
className={$btnClass}
type={$type}
$BeforButtonIcon={$BeforButtonIcon}
$afterButtonIcon={$afterButtonIcon}
onClick={onclick}
>
onClick={onclick}>
{children}
</ButtonModule>
);
Expand Down
50 changes: 21 additions & 29 deletions components/common/atoms/CheckBox.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,25 @@
import { IModal } from '../../modal/interface';
import { IModal } from '@/src/constant/modal';
import { CheckBoxWrap } from './checkBoxStyle';

interface ICheckBoxData {
$data: IModal['$modalData'];
}

function CheckBox({ $data }: ICheckBoxData) {
if ($data?.data) {
return (
<CheckBoxWrap className="chk--list-type1">
{$data &&
$data.data.map((list: any) => (
<div className="inner" key={list.id}>
<input type="checkbox" id={list.id} />
<label htmlFor={list.id}>
<strong>{list.name}</strong>
<span>{list.link.count}개 링크</span>
</label>
</div>
))}
</CheckBoxWrap>
);
} else {
return (
<div>
<input type="text" />
<label htmlFor=""></label>
</div>
);
}
function CheckBox({ $data }: IModal<any>['modalData']) {
return (
<CheckBoxWrap className='chk--list-type1'>
{$data &&
$data?.map((list: any) => (
<div
className='inner'
key={list.id}>
<input
type='checkbox'
id={list.id}
/>
<label htmlFor={list.id}>
<strong>{list.name}</strong>
<span>{list.link.count}개 링크</span>
</label>
</div>
))}
</CheckBoxWrap>
);
}
export default CheckBox;
Loading
Loading