Skip to content
Open
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
2 changes: 2 additions & 0 deletions week5/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
VITE_SERVER_API_URL=http://localhost:8000
Copy link
Contributor

Choose a reason for hiding this comment

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

env .gitignore에 추가해야해용!!


24 changes: 24 additions & 0 deletions week5/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# 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?
54 changes: 54 additions & 0 deletions week5/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# 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:

```js
export default tseslint.config({
extends: [
// Remove ...tseslint.configs.recommended and replace with this
...tseslint.configs.recommendedTypeChecked,
// Alternatively, use this for stricter rules
...tseslint.configs.strictTypeChecked,
// Optionally, add this for stylistic rules
...tseslint.configs.stylisticTypeChecked,
],
languageOptions: {
// other options...
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
},
})
```

You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:

```js
// eslint.config.js
import reactX from 'eslint-plugin-react-x'
import reactDom from 'eslint-plugin-react-dom'

export default tseslint.config({
plugins: {
// Add the react-x and react-dom plugins
'react-x': reactX,
'react-dom': reactDom,
},
rules: {
// other rules...
// Enable its recommended typescript rules
...reactX.configs['recommended-typescript'].rules,
...reactDom.configs.recommended.rules,
},
})
```
28 changes: 28 additions & 0 deletions week5/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 week5/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<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>Vite + React + TS</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
36 changes: 36 additions & 0 deletions week5/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"name": "login-fe",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@hookform/resolvers": "^5.0.1",
"@tailwindcss/vite": "^4.1.3",
"axios": "^1.9.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-hook-form": "^7.56.1",
"react-router-dom": "^7.5.0",
"tailwindcss": "^4.1.3",
"zod": "^3.24.3"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react-swc": "^3.8.0",
"eslint": "^9.21.0",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^15.15.0",
"typescript": "~5.7.2",
"typescript-eslint": "^8.24.1",
"vite": "^6.2.0"
}
}
1 change: 1 addition & 0 deletions week5/public/vite.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file added week5/src/App.css
Empty file.
51 changes: 51 additions & 0 deletions week5/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@

import "./App.css"
import { createBrowserRouter, RouterProvider } from 'react-router'

import NotFoundPage from './pages/NotFoundPage'
import LoginPage from './pages/LoginPage'
import HomeLayout from './layouts/HomeLayout'
import HomePage from './pages/HomePage'
import SignupPage from './pages/SignupPage'
import MyPage from './pages/MyPage'
import { AuthProvider } from './context/AuthContext'
import ProtectedLayout from './layouts/ProtectedLayout'


//publicRoutes : 인증없이 접근 가능한 라우트
const publicRoutes = [
{
path: "/",
element: <HomeLayout />,
errorElement: <NotFoundPage />,
children: [
{index: true, element: <HomePage />},
{path: 'login', element: <LoginPage />},
{path: 'signup', element: <SignupPage />},
],
}
]
//protectedRoutes : 인증이 필요한 라우트
const protectedRoutes = [
{
path: "/",
element: <ProtectedLayout/>,
children: [
{
path: 'my',
element: <MyPage />,
}
]
}
]
const router = createBrowserRouter([...publicRoutes])
Copy link
Contributor

Choose a reason for hiding this comment

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

왜 protectedRoutes가 안들어가 있죠...???


function App() {

return (
<AuthProvider>
<RouterProvider router={router} />
</AuthProvider>
)
}
export default App
25 changes: 25 additions & 0 deletions week5/src/apis/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import axios from 'axios';
import { RequestSigninDto, RequestSignupDto, ResponseMyInfoDto, ResponseSigninDto, ResponseSignupDto } from '../types/auth';
import { axiosInstance } from './axios';

export const postSignup = async(body: RequestSignupDto): Promise<ResponseSignupDto> => {

const {data} = await axiosInstance.post("/v1/auth/signup", body, )
Copy link
Contributor

Choose a reason for hiding this comment

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

, 랑 공백 빼도 될 것 같아여

return data;
}

export const postSignin = async(body: RequestSigninDto): Promise<ResponseSigninDto> => {
const {data} = await axiosInstance.post( "/v1/auth/signin", body, )
return data;
}

export const getMyInfo = async(): Promise<ResponseMyInfoDto> => {

const {data} = await axiosInstance.get("/v1/users/me")
return data;
}

export const postLogout = async() => {
const {data} = await axiosInstance.post("/v1/auth/signout")
return data;
}
23 changes: 23 additions & 0 deletions week5/src/apis/axios.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import axios from 'axios';
import { LOCAL_STORAGE_KEY } from '../constants/key';
import { useLocalStorage } from '../hooks/useLocalStorage';




export const axiosInstance = axios.create({
baseURL: import.meta.env.VITE_SERVER_API_URL,
// headers: {
// Authorization: `Bearer ${localStorage.getItem(LOCAL_STORAGE_KEY.ACCESS_TOKEN)}`,
// }
});

axiosInstance.interceptors.request.use((config) => {
const{getItem} = useLocalStorage(LOCAL_STORAGE_KEY.ACCESS_TOKEN)
const token = getItem()

if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
})
1 change: 1 addition & 0 deletions week5/src/assets/react.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions week5/src/constants/key.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const LOCAL_STORAGE_KEY = {
ACCESS_TOKEN: 'accessToken',
REFRESH_TOKEN: 'refreshToken'
}
99 changes: 99 additions & 0 deletions week5/src/context/AuthContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { createContext, PropsWithChildren, use, useContext } from 'react';
import { RequestSigninDto } from '../types/auth';
import { useLocalStorage } from '../hooks/useLocalStorage';
import { LOCAL_STORAGE_KEY } from '../constants/key';
import { useState } from 'react';
import { postSignin, postLogout } from '../apis/auth';



interface AuthContextType {
accessToken: string | null;
refreshToken: string | null;
login: (signInData: RequestSigninDto) => Promise<void>;
logout: () => Promise<void>;
}

export const AuthContext = createContext<AuthContextType>({
accessToken: null,
refreshToken: null,
login: async () => {},
logout: async () => {},
})

export const AuthProvider = ({ children }:PropsWithChildren) => {

const {
getItem: getAccessTockenFromStorage,
setItem: setAccessTockenFromStorage,
removeItem: removeAccessTockenFromStorage,
} = useLocalStorage(LOCAL_STORAGE_KEY.ACCESS_TOKEN)
const {
getItem: getRefreshTockenFromStorage,
setItem: setRefreshTockenFromStorage,
removeItem: removeRefreshTockenFromStorage,
} = useLocalStorage(LOCAL_STORAGE_KEY.REFRESH_TOKEN)

const[accessToken, setAccessToken] = useState<string | null>( //lazy initialization
getAccessTockenFromStorage(),
);
const[refreshToken, setRefreshToken] = useState<string | null>(
getRefreshTockenFromStorage(),
);

const login = async (signinData:RequestSigninDto) => {

try{
const{data}=await postSignin(signinData)

if(data){
const newaccessToken = data.accessToken
const newrefreshToken = data.refreshToken

setAccessTockenFromStorage(newaccessToken)
setRefreshTockenFromStorage(newrefreshToken)

setAccessToken(newaccessToken)
setRefreshToken(newrefreshToken)
alert("로그인 성공")
window.location.href = "/my"
}
}catch(error){
console.error("로그인 실패", error) //toast UI로 바꾸기
alert("로그인 실패")
}
};
const logout = async () => {
try{
await postLogout()
removeAccessTockenFromStorage()
removeRefreshTockenFromStorage()
setAccessToken(null)
setRefreshToken(null)
alert("로그아웃 성공")
}catch(error){
console.error("로그아웃 에러", error) //toast UI로 바꾸기
alert("로그아웃 실패")
}
};
return (
<AuthContext.Provider
value={{
accessToken,
refreshToken,
login,
logout,
}}
>
{children}
</AuthContext.Provider>
);
}

export const useAuth = () => {
const context: AuthContextType = useContext(AuthContext);
if (!context) {
throw new Error('AuthContext를 찾을 수 없음');
}
return context;
}
4 changes: 4 additions & 0 deletions week5/src/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
declare module '*.css' {
const content: { [className: string]: string };
export default content;
}
Loading