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

경북대 FE_김강민 6주차 과제 재제출 #124

Open
wants to merge 55 commits into
base: dobbymin
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
9e57736
init: 프로젝트 생성
Dobbymin Jul 30, 2024
66af9a4
init: 기존 코드 추가 및 기타 파일 정리
Dobbymin Jul 30, 2024
9e10a29
chore(deploy): 배포 자동화를 위한 폴더 생성
Dobbymin Jul 30, 2024
88fb7f8
chore(deploy): 배포 자동화를 위한 폴더 생성
Dobbymin Jul 30, 2024
1fdc68b
chore(deploy): 배포 자동화를 위한 yaml 파일 생성
Dobbymin Jul 30, 2024
e78e08d
refactor(api): api 폴더구조 변경
Dobbymin Jul 30, 2024
7a585f2
refactor(api): type 코드 분리
Dobbymin Jul 30, 2024
ba6d375
refactor(type): 폴더구조 변경
Dobbymin Jul 30, 2024
770d16c
refactor: 전체 경로 수정
Dobbymin Jul 30, 2024
a787583
fix(eslint): mockServiceWorker.js 파일 eslint 무시
Dobbymin Jul 30, 2024
f50b959
refactor(mock): mock data 위치 및 폴더구조 변경
Dobbymin Jul 30, 2024
ea9a6a8
Fix(login): 메인페이지 접속 시 '내 계정' 부터 나타나는 버그 수정
Dobbymin Jul 30, 2024
70cc6d3
Fix(login): 메인페이지 접속 시 '내 계정' 부터 나타나는 버그 수정
Dobbymin Jul 30, 2024
59d0aac
fix(my-account): 위시리스트 삭제 기능 수정
Dobbymin Jul 30, 2024
7ab0d72
feat(mock): wish-list mock data 구현
Dobbymin Jul 30, 2024
3ac996d
feat(mock): mock data handler 추가
Dobbymin Jul 30, 2024
2b31ec9
feat(wish-list): response data 타입 추가
Dobbymin Jul 30, 2024
98a0ab1
feat(mock): wish-list mock data 구현
Dobbymin Jul 30, 2024
958f640
refactor(api): props 네이밍 변경 및 경로 수정
Dobbymin Jul 30, 2024
e6fa534
refactor: 의미없는 주석 제거
Dobbymin Jul 30, 2024
7069639
fix(api): fetchInstance tokenInstance 코드 통합 및 경로 수정
Dobbymin Jul 30, 2024
b822b57
feat: env 파일 커밋 무시
Dobbymin Jul 30, 2024
583097f
feat(env): 환경변수 상수화 구현
Dobbymin Jul 30, 2024
74f2008
fix(api): Base URL 수정
Dobbymin Jul 30, 2024
952fe3b
feat(header): api 선택 select ui 구현
Dobbymin Jul 30, 2024
c398ce1
feat(api): Api provider 구현
Dobbymin Jul 31, 2024
37a8b8e
feat(api): Api provider 구현
Dobbymin Jul 31, 2024
96c9734
feat(api): product 상세 요청 기능 구현
Dobbymin Aug 1, 2024
a4dc50b
chore(api): productId 타입관련 주석 추가
Dobbymin Aug 1, 2024
971a2ab
chore(api): TODO 항목 주석 추가
Dobbymin Aug 1, 2024
26530e8
fix(URL): BASE_URL 경로 수정
Dobbymin Aug 1, 2024
16a20a1
feat(api): instance uri 주소수정
Dobbymin Aug 1, 2024
2567067
feat(header): api 주소 변경 기능 구현
Dobbymin Aug 1, 2024
10bd945
feat(api-provider): 백엔드 url 경로 case 별로 수정
Dobbymin Aug 1, 2024
aeb4864
deploy: yaml 파일 변경 및 env 파일 생성 기능 추가
Dobbymin Aug 1, 2024
cfb0d79
chore(package): react-icons 설치
Dobbymin Aug 1, 2024
ca01a8c
chore(api): TODO 주석 추가 및 BASE_URL 주소 변경
Dobbymin Aug 1, 2024
cab67f4
fix(url): base url 수정
Dobbymin Aug 1, 2024
3c4756e
feat(order): point 관련 타입 추가
Dobbymin Aug 1, 2024
f6378dc
feat(order): 최종결제금액 계산 부분 수정 및 포인트 사용 반영 기능 구현
Dobbymin Aug 1, 2024
5a5c2ff
feat(order): point 관련 ui 구현 및 point 사용 기능 구현
Dobbymin Aug 1, 2024
7dc34d7
feat(wish): 위시리스트 추가 관련 ui 및 기능 구현
Dobbymin Aug 1, 2024
9fae126
fix(url): base url 수정
Dobbymin Aug 1, 2024
7c59052
chore(gitignore): yaml 파일 github 업로드
Dobbymin Aug 2, 2024
0e0e0c6
fix(URL): URL 경로 수정 및 가장 처음 실행될 baseURL 설정
Dobbymin Aug 2, 2024
b368808
fix(URL): URL 경로 수정 및 가장 처음 실행될 baseURL 설정
Dobbymin Aug 2, 2024
3ebe691
fix(provider): api 변경 기능 및 undefined 오류 수정
Dobbymin Aug 2, 2024
40160a9
fix(header): api 변경 기능 오류 수정
Dobbymin Aug 2, 2024
3840ffe
docs(README): 6주차 질문 작성
Dobbymin Aug 4, 2024
508568a
feat(login): 카카오 로그인 기능 구현
Dobbymin Aug 4, 2024
676ca35
feat(router): kakao 로그인 router 설정
Dobbymin Aug 4, 2024
82ff7c1
feat(api): point 조회 api 구현
Dobbymin Aug 6, 2024
b4d997d
feat(api): point 조회 api response type 지정
Dobbymin Aug 6, 2024
6d562e5
feat(point): 포인트 조회 구현
Dobbymin Aug 6, 2024
45d9bae
Merge branch 'dobbymin' into kimgangmin
Dobbymin Aug 6, 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
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,21 @@
# react-deploy

# 📝 Requirements

## 6주차 질문

### 질문 1. SPA 페이지를 정적 배포를 하려고 할 때 Vercel을 사용하지 않고 한다면 어떻게 할 수 있을까요?

github page를 이용하거나 Vercel 과 비슷한 Netlify 또한 사용가능합니다. 이경우 workflow에 yaml 파일을 작성하여 CI/CD를 구현할 수 있습니다. 또한 과거에 ec2에 nginx를 이용해 배포를 해본 경험도 있습니다.

### 질문 2. CSRF나 XSS 공격을 막는 방법은 무엇일까요?

1. CSRF 공격을 막는 방법
HttpOnly 속성을 쿠키에 설정하여 클라이언트측 JavaScript에서 쿠키에 접근하지 못하도록 하는 방법이 있습니다.

2. XXS 공격을 막는 방법
쿠키에 SameSite 속성을 설정하는 방법이 있습니다. 또한 CSRF 토큰을 사용하는 방법도 있습니다.

### 질문 3. 브라우저 렌더링 원리에대해 설명해주세요.

브라우저의 렌더링 원리는 HTML을 파싱하여 DOM 트리를 만들고, CSS를 파싱하여 CSSOM 트리를 만든 다음 이를 결합하여 렌더트리를 생성합니다. 이 렌더 트리를 기반으로 요소들의 레이아웃을 계산한 후, 계산된 레이아웃 정보를 사용해 요소들을 픽셀 단위로 화면에 그리는 페인팅을 거쳐 최종적으로 여러 레이어를 합쳐 화면에 표시하는 과정입니다.
32 changes: 32 additions & 0 deletions src/api/hooks/auth/kakao-login.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useMutation } from '@tanstack/react-query';

import { BASE_URL, fetchInstance } from '@/api/instance';

import type { KakaoResponseData } from './type';

export const getKakaoLoginpath = () => `${BASE_URL}/oauth/kakao/login`;

export const kakaoLoginUser = async (): Promise<KakaoResponseData> => {
const response = await fetchInstance.get<KakaoResponseData>(getKakaoLoginpath());
console.log(response);
return response.data;
};

export const useKakaoCallback = async (code: string): Promise<KakaoResponseData> => {
const response = await fetchInstance.get<KakaoResponseData>(
`${BASE_URL}/oauth/kakao/callback?code=${code}`,
);
return response.data;
};

export const useKakaoLogin = () => {
return useMutation({
mutationFn: kakaoLoginUser,
});
};

export const useKakaoCallbackMutation = () => {
return useMutation({
mutationFn: useKakaoCallback,
});
};
6 changes: 6 additions & 0 deletions src/api/hooks/auth/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,9 @@ export type UserResponseData = {
email: string;
token: string;
};

export type KakaoResponseData = {
tokenType: string;
token: string;
};

18 changes: 18 additions & 0 deletions src/api/hooks/point/point.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useMutation } from '@tanstack/react-query';

import { BASE_URL, fetchInstance } from '@/api/instance';

import type { PointResponseData } from './type';

export const getPointsPath = () => `${BASE_URL}/api/Points`;

export const getPoints = async () => {
const response = await fetchInstance.post<PointResponseData>(getPointsPath());
return response.data;
};

export const useGetPoints = () => {
return useMutation<PointResponseData, Error>({
mutationFn: getPoints,
});
};
3 changes: 3 additions & 0 deletions src/api/hooks/point/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export type PointResponseData = {
point: number;
};
4 changes: 4 additions & 0 deletions src/assets/kakao_symbol.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 41 additions & 0 deletions src/components/features/Login/KakaoCallbackPage/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useEffect } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';

import { useKakaoCallbackMutation } from '@/api/hooks/auth/kakao-login.api';
import { useAuth } from '@/provider/Auth';
import { RouterPath } from '@/routes/path';
import { authSessionStorage } from '@/utils/storage';

const KakaoCallbackPage = () => {
const location = useLocation();
const navigate = useNavigate();
const { setAuthInfo } = useAuth();
const mutation = useKakaoCallbackMutation();

useEffect(() => {
const queryParams = new URLSearchParams(location.search);
const code = queryParams.get('code');

if (code) {
mutation.mutate(code, {
onSuccess: (data) => {
if (data.token) {
setAuthInfo({ token: data.token });
authSessionStorage.set(data.token);
navigate(RouterPath.home);
} else {
alert('로그인 실패');
}
},
onError: (error) => {
console.error('Error during Kakao login:', error);
alert('로그인 중 오류가 발생했습니다.');
},
});
}
}, [location.search, mutation, navigate, setAuthInfo]);

return <div>로그인 중...</div>;
};

export default KakaoCallbackPage;
100 changes: 34 additions & 66 deletions src/pages/Login/index.tsx
Original file line number Diff line number Diff line change
@@ -1,80 +1,23 @@
import { Box, Img, Text } from '@chakra-ui/react';
import styled from '@emotion/styled';
import { useState } from 'react';
import { useSearchParams } from 'react-router-dom';

import { useLogin } from '@/api/hooks/auth/login.api';
import { useRegister } from '@/api/hooks/auth/register.api';
import KAKAO_LOGO from '@/assets/kakao_logo.svg';
import { Button } from '@/components/common/Button';
import { UnderlineTextField } from '@/components/common/Form/Input/UnderlineTextField';
import { Spacing } from '@/components/common/layouts/Spacing';
import { useAuth } from '@/provider/Auth';
import Symbol from '@/assets/kakao_symbol.svg';
import { breakpoints } from '@/styles/variants';

export const LoginPage = () => {
const [id, setId] = useState('');
const [password, setPassword] = useState('');
const [queryParams] = useSearchParams();
const { setAuthInfo } = useAuth();
const loginMutation = useLogin();
const registerMutation = useRegister();

const handleConfirm = async () => {
if (!id || !password) {
alert('아이디와 비밀번호를 입력해주세요.');
return;
}
try {
const data = await loginMutation.mutateAsync({ email: id, password });
setAuthInfo({ id: data.email, name: data.email, token: data.token });

const redirectUrl = queryParams.get('redirect') ?? `${window.location.origin}/`;
window.location.replace(redirectUrl);
} catch (error: unknown) {
alert('로그인 실패: ' + (error as Error).message);
}
};

const handleSignup = async () => {
if (!id || !password) {
alert('아이디와 비밀번호를 입력해주세요.');
return;
}

try {
const data = await registerMutation.mutateAsync({ email: id, password });
setAuthInfo({ id: data.email, name: data.email, token: data.token });

alert('회원가입이 완료되었습니다.');
const redirectUrl = queryParams.get('redirect') ?? `${window.location.origin}/`;
window.location.replace(redirectUrl);
} catch (error: unknown) {
alert('회원가입 실패: ' + (error as Error).message);
}
const handleKakaoLogin = () => {
window.location.href = '/oauth/kakao/login';
};

return (
<Wrapper>
<Logo src={KAKAO_LOGO} alt="카카고 CI" />
<Logo src={KAKAO_LOGO} alt="카카오 CI" />
<FormWrapper>
<UnderlineTextField placeholder="이름" value={id} onChange={(e) => setId(e.target.value)} />
<Spacing />
<UnderlineTextField
type="password"
placeholder="비밀번호"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>

<Spacing
height={{
initial: 40,
sm: 60,
}}
/>
<Button onClick={handleConfirm}>로그인</Button>
<Spacing />
<Button onClick={handleSignup}>회원가입</Button>
<KakaoLoginButton onClick={handleKakaoLogin}>
<StyledImg src={Symbol} alt="Kakao Symbol" />
<LoginText>카카오 로그인</LoginText>
</KakaoLoginButton>
</FormWrapper>
</Wrapper>
);
Expand Down Expand Up @@ -104,3 +47,28 @@ const FormWrapper = styled.article`
padding: 60px 52px;
}
`;

const KakaoLoginButton = styled(Box)`
display: flex;
background-color: #fee500;
width: 100%;
height: 2.5rem;
border-radius: 5px;
text-align: center;
align-items: center;
justify-content: start;
cursor: pointer;
`;

const LoginText = styled(Text)`
display: flex;
text-align: center;
margin-left: 1rem;
`;

const StyledImg = styled(Img)`
width: 1.5rem;
text-align: center;
margin-right: 2rem;
margin-left: 1rem;
`;
23 changes: 20 additions & 3 deletions src/pages/MyAccount/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import { Box, Text } from '@chakra-ui/react';
import styled from '@emotion/styled';
import { useEffect } from 'react';

import { useGetPoints } from '@/api/hooks/point/point.api';
import { Button } from '@/components/common/Button';
import { Spacing } from '@/components/common/layouts/Spacing';
import { WishList } from '@/components/features/MyAccount/WishList';
import { useAuth } from '@/provider/Auth';
import { RouterPath } from '@/routes/path';
import { authSessionStorage } from '@/utils/storage';

export const MyAccountPage = () => {
const { authInfo } = useAuth();
const { mutate, data, status, error } = useGetPoints();

useEffect(() => {
mutate();
}, [mutate]);

const handleLogout = () => {
authSessionStorage.set(undefined);
Expand All @@ -19,7 +25,18 @@ export const MyAccountPage = () => {

return (
<Wrapper>
{authInfo?.name}님 안녕하세요! <Spacing height={64} />
<Text>개발자님 안녕하세요!</Text>
<Spacing height={64} />
<Box>
<Text>포인트</Text>
{status === 'pending' ? (
<Text>Loading...</Text>
) : status === 'error' ? (
<Text>Error fetching points: {error.message}</Text>
) : (
<Text>{data?.point ?? 0}</Text>
)}
</Box>
<Button
size="small"
theme="darkGray"
Expand Down
95 changes: 1 addition & 94 deletions src/pages/Register/index.tsx
Original file line number Diff line number Diff line change
@@ -1,96 +1,3 @@
import { Box, Text } from '@chakra-ui/react';
import styled from '@emotion/styled';
import { useState } from 'react';
import { Link, useSearchParams } from 'react-router-dom';

import { useRegister } from '@/api/hooks/auth/register.api';
import KAKAO_LOGO from '@/assets/kakao_logo.svg';
import { Button } from '@/components/common/Button';
import { UnderlineTextField } from '@/components/common/Form/Input/UnderlineTextField';
import { Spacing } from '@/components/common/layouts/Spacing';
import { useAuth } from '@/provider/Auth';
import { RouterPath } from '@/routes/path';
import { breakpoints } from '@/styles/variants';
export {};

export const RegisterPage = () => {
const [id, setId] = useState('');
const [password, setPassword] = useState('');
const [queryParams] = useSearchParams();
const { setAuthInfo } = useAuth();
const registerMutation = useRegister();

const handleConfirm = async () => {
if (!id || !password) {
alert('아이디와 비밀번호를 입력해주세요.');
return;
}

try {
const data = await registerMutation.mutateAsync({ email: id, password });
setAuthInfo({ id: data.email, name: data.email, token: data.token });

alert('회원가입이 완료되었습니다.');
const redirectUrl = queryParams.get('redirect') ?? `${window.location.origin}/`;
window.location.replace(redirectUrl);
} catch (error: unknown) {
alert('회원가입 실패: ' + (error as Error).message);
}
};

return (
<Wrapper>
<Logo src={KAKAO_LOGO} alt="카카오 CI" />
<FormWrapper>
<UnderlineTextField placeholder="이름" value={id} onChange={(e) => setId(e.target.value)} />
<Spacing />
<UnderlineTextField
type="password"
placeholder="비밀번호"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>

<Spacing
height={{
initial: 40,
sm: 60,
}}
/>
<Button onClick={handleConfirm}>회원가입</Button>
</FormWrapper>
<Box display="flex" textAlign="center" alignItems="center" margin="10px">
<Text fontSize="12px">이미 가입하셨을까요? &nbsp; </Text>
<Link to={RouterPath.login}>
<Text fontWeight="700" color="blue">
로그인
</Text>
</Link>
</Box>
</Wrapper>
);
};

const Wrapper = styled.div`
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
`;

const Logo = styled.img`
width: 88px;
color: #333;
`;

const FormWrapper = styled.article`
width: 100%;
max-width: 580px;
padding: 16px;

@media screen and (min-width: ${breakpoints.sm}) {
border: 1px solid rgba(0, 0, 0, 0.12);
padding: 60px 52px;
}
`;
Loading