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

[4주차 기본/심화/공유 과제] 회원가입 & 로그인 #5

Open
wants to merge 29 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f1c3615
chore: 4주차 과제 초기세팅
m2na7 Nov 5, 2024
b1d4626
chore: paths alias 설정
m2na7 Nov 6, 2024
70f4c83
chore: 파일구조 및 라우터 설정
m2na7 Nov 6, 2024
a606f53
feat: Theme 설정
m2na7 Nov 6, 2024
f2d64a7
style: 로그인 페이지 퍼블리싱
m2na7 Nov 6, 2024
0b0f2eb
style: 회원가입 페이지 퍼블리싱
m2na7 Nov 6, 2024
69aea39
style: 헤더 퍼블리싱
m2na7 Nov 6, 2024
8b7f80f
feat: url 경로에 따른 헤더 레이아웃 설정
m2na7 Nov 6, 2024
93638ba
feat: `Button` 컴포넌트 추가
m2na7 Nov 6, 2024
f68808a
feat: `Input` 컴포넌트 추가
m2na7 Nov 6, 2024
9b850d0
refactor: 공통 컴포넌트 불러오도록 수정
m2na7 Nov 6, 2024
ed8cbf2
style: 취미 페이지 퍼블리싱
m2na7 Nov 6, 2024
8c3a8db
style: 내 정보 페이지 퍼블리싱
m2na7 Nov 6, 2024
8119c70
feat: api 기본 설정
m2na7 Nov 9, 2024
c1687e5
feat: 회원가입 기능 구현
m2na7 Nov 9, 2024
0ccbdc1
feat: 로그인 및 로그아웃 기능 구현
m2na7 Nov 9, 2024
06af2c0
feat: Protected Route 기능 구현
m2na7 Nov 9, 2024
8acce09
feat: 나의 취미 조회 기능 구현
m2na7 Nov 9, 2024
be09712
feat: 취미 검색 기능 구현
m2na7 Nov 9, 2024
75363fb
feat: 정보 수정 기능 구현
m2na7 Nov 9, 2024
efdd288
feat: 에러 핸들링
m2na7 Nov 11, 2024
49ea26f
feat: `Button` disabled 기능 추가
m2na7 Nov 11, 2024
22f8ce3
feat: 폼제출 관련 에러 핸들링
m2na7 Nov 11, 2024
4ad67db
refactor: 불필요한 타입 제거 및 import 변경
m2na7 Nov 11, 2024
0aad4f6
feat: 비밀번호 표시 기능 추가
m2na7 Nov 11, 2024
572ad58
refactor: 라우터 구조 변경
m2na7 Nov 12, 2024
39ce6fc
refactor: `SignupForm` 내부 컴포넌트 분리
m2na7 Nov 12, 2024
e3ee82d
refactor: Layout 구조 간소화
m2na7 Dec 9, 2024
c59c19e
refactor: placeholder 타입 제거
m2na7 Dec 9, 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
Binary file added .DS_Store
Binary file not shown.
27 changes: 27 additions & 0 deletions week4/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

.yarn
.env
50 changes: 50 additions & 0 deletions week4/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# React + TypeScript + Vite

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.

Currently, two official plugins are available:

- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh

## Expanding the ESLint configuration

If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:

- Configure the top-level `parserOptions` property like this:

```js
export default tseslint.config({
languageOptions: {
// other options...
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
},
})
```

- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked`
- Optionally add `...tseslint.configs.stylisticTypeChecked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config:

```js
// eslint.config.js
import react from 'eslint-plugin-react'

export default tseslint.config({
// Set the react version
settings: { react: { version: '18.3' } },
plugins: {
// Add the react plugin
react,
},
rules: {
// other rules...
// Enable its recommended rules
...react.configs.recommended.rules,
...react.configs['jsx-runtime'].rules,
},
})
```
28 changes: 28 additions & 0 deletions week4/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'

export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
)
13 changes: 13 additions & 0 deletions week4/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>4주차 과제</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
34 changes: 34 additions & 0 deletions week4/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "week4",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@vanilla-extract/css": "^1.16.0",
"@vanilla-extract/vite-plugin": "^4.0.17",
"axios": "^1.7.7",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.27.0",
"vite-tsconfig-paths": "^5.1.0"
},
"devDependencies": {
"@eslint/js": "^9.13.0",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react-swc": "^3.5.0",
"eslint": "^9.13.0",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.14",
"globals": "^15.11.0",
"typescript": "~5.6.2",
"typescript-eslint": "^8.11.0",
"vite": "^5.4.10"
}
}
8 changes: 8 additions & 0 deletions week4/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Router from '@router/Router';
import '@styles/global.css';

function App() {
return <Router />;
}

export default App;
9 changes: 9 additions & 0 deletions week4/src/api/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import axios from 'axios';
import { BASE_URL } from '@constants/api';
import { onErrorResponse } from './error';

export const client = axios.create({
baseURL: BASE_URL,
});

client.interceptors.response.use((response) => response, onErrorResponse);
39 changes: 39 additions & 0 deletions week4/src/api/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { ERROR_MESSAGE } from '@constants/messages';
import axios, { AxiosError, AxiosResponse } from 'axios';

export const onErrorResponse = (error: AxiosError) => {
if (axios.isAxiosError(error)) {
const response = error.response as AxiosResponse;
const code = response.data.code;
let errorMessage;

// http 상태 코드에 따른 에러 메시지 처리
switch (response.status) {
case 400:
errorMessage = ERROR_MESSAGE.INVALID_REQUEST;
break;
case 401:
errorMessage = ERROR_MESSAGE.NO_TOKEN;
break;
case 403:
errorMessage =
code === '00'
? ERROR_MESSAGE.INVALID_TOKEN
: ERROR_MESSAGE.INVALID_PASSWORD;
break;
case 404:
errorMessage =
code === '00' ? ERROR_MESSAGE.INVALID_PATH : ERROR_MESSAGE.INVALID_NO;
break;
case 409:
errorMessage = ERROR_MESSAGE.DUPLICATED_USERNAME;
break;
default:
errorMessage = ERROR_MESSAGE.DEFAULT;
}

return Promise.reject(new Error(errorMessage));
}

return Promise.reject(new Error(ERROR_MESSAGE.DEFAULT));
};
35 changes: 35 additions & 0 deletions week4/src/api/hobby.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { ACCESS_TOKEN_KEY } from '@constants/config';
import { client } from './client';
import { END_POINT } from '@constants/api';

export const getMyHobby = async () => {
try {
const token = sessionStorage.getItem(ACCESS_TOKEN_KEY);
const response = await client.get(END_POINT.MY_HOBBY, {
headers: {
token: token,
},
});
return response.data.result.hobby;
} catch (error) {
if (error instanceof Error) {
throw new Error(error.message);
}
}
};

export const getUserHobby = async (userId: number) => {
try {
const token = sessionStorage.getItem(ACCESS_TOKEN_KEY);
const response = await client.get(END_POINT.USER_HOBBY(userId), {
headers: {
token: token,
},
});
return response.data.result.hobby;
} catch (error) {
if (error instanceof Error) {
throw new Error(error.message);
}
}
};
44 changes: 44 additions & 0 deletions week4/src/api/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { client } from './client';
import { END_POINT } from '@constants/api';
import { ACCESS_TOKEN_KEY } from '@constants/config';
import { LoginData, User, UserInfoData } from '@type/user';

export const postLogin = async (data: LoginData) => {
try {
const response = await client.post(END_POINT.LOGIN, data);

return response.data.result.token;
} catch (error) {
if (error instanceof Error) {
throw new Error(error.message);
}
}
};

export const postSignUp = async (data: User) => {
try {
const response = await client.post(END_POINT.USER, data);
return response.data.result;
} catch (error) {
if (error instanceof Error) {
throw new Error(error.message);
}
}
};

export const putUserInfo = async (data: UserInfoData) => {
try {
const token = sessionStorage.getItem(ACCESS_TOKEN_KEY);
const response = await client.put(END_POINT.USER, data, {
headers: {
token: token,
},
});

return response.data;
} catch (error) {
if (error instanceof Error) {
throw new Error(error.message);
}
}
};
1 change: 1 addition & 0 deletions week4/src/assets/visibility.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions week4/src/assets/visibility_off.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions week4/src/components/button/Button.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { vars } from '@styles/theme.css';
import { style } from '@vanilla-extract/css';

export const button = style({
marginBottom: 14,
padding: 12,
borderRadius: 8,
Comment on lines +5 to +7
Copy link
Member

Choose a reason for hiding this comment

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

이렇게 단위를 설정하지않으면 기본적으로 px단위로 지정되지 않나요?

font등에서는 rem단위를 사용하고 여백 등 레이아웃에서는 px단위를 사용하시는 것으로 보이는데,
폰트는 접근성을 고려하고 레이아웃은 깨지지 않게 하려고 하신걸까요?

단위를 섞어쓰면 유지보수나 일관성 측면에서 단점이 좀 생기지 않나 해서 여쭤봅니다~

Copy link
Member Author

@m2na7 m2na7 Dec 9, 2024

Choose a reason for hiding this comment

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

4주차 과제진행할 때 vanilla extract에 익숙치 않았어서 단위를 많이 생략(ve에서는 생략시 px로 변환)했었네요 ... 빠르게 진행하다보니 ㅎㅎ.
border를 제외하고는 rem으로 바꾸는게 적절하다고 저도 생각합니다 ~

backgroundColor: vars.color.primary,
color: vars.color.white,
textAlign: 'center',
fontSize: vars.fontSize.md,

':hover': {
backgroundColor: vars.color.blue600,
transition: 'background-color 0.3s',
},

':disabled': {
backgroundColor: vars.color.gray300,
color: vars.color.gray100,
cursor: 'not-allowed',
},
});
15 changes: 15 additions & 0 deletions week4/src/components/button/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as styles from './Button.css';

interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
children: React.ReactNode;
}

const Button = ({ children, ...props }: ButtonProps) => {
return (
<button className={styles.button} {...props}>
{children}
</button>
);
};

export default Button;
46 changes: 46 additions & 0 deletions week4/src/components/header/Header.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { style } from '@vanilla-extract/css';
import { vars } from '@styles/theme.css';

export const header = style({
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: '20px',
minHeight: vars.spacing.headerHeight,
gap: 20,

backgroundColor: vars.color.primary,
color: vars.color.white,
});

export const leftSection = style({
display: 'flex',
alignItems: 'center',
gap: 60,
});

export const title = style({
fontSize: vars.fontSize['2xl'],
fontWeight: 'bold',
});

export const nav = style({
display: 'flex',
gap: 40,
});

export const link = style({
fontSize: vars.fontSize.lg,

':hover': {
color: vars.color.secondary,
},
});

export const logoutButton = style({
fontSize: vars.fontSize.lg,

':hover': {
color: vars.color.secondary,
},
});
Loading