-
Notifications
You must be signed in to change notification settings - Fork 8
[5주차/챈니] 워크북 제출합니다. #49
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
Merged
Merged
Changes from 3 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
46360ef
keyword: 5주차 키워드 제출
12234538 65a95ac
mission: chapter05 mission_1 제출
12234538 dbd3711
mission: chapter05 mission_2 제출
12234538 b2b5d94
mission: chapter05 mission_3 제출
12234538 7034d3a
mission: chapter05 mission_3 피어 리뷰 후 수정
12234538 1c9074e
mission: chapter05 mission_3 피어리뷰 후 수정
12234538 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| import './App.css'; | ||
| import { createBrowserRouter, RouterProvider } from 'react-router-dom'; | ||
| import HomeLayout from './layouts/HomeLayout'; | ||
| import NotFound from './pages/NotFoundPage'; | ||
| import LoginPage from './pages/LoginPage'; | ||
| import HomePage from './pages/HomePage'; | ||
| import SignupPage from './pages/SignupPage'; | ||
| import MyPage from './pages/MyPage'; | ||
| import ProtectedRoute from './components/ProtectedRoute'; | ||
| import { AuthProvider } from './context/AuthContext'; | ||
|
|
||
| const router = createBrowserRouter([ | ||
| { | ||
| path: '/', | ||
| element: <HomeLayout />, | ||
| errorElement: <NotFound />, | ||
| children: [ | ||
| { index: true, element: <HomePage /> }, | ||
| { | ||
| path: 'mypage', | ||
| element: ( | ||
| <ProtectedRoute> | ||
| <MyPage /> | ||
| </ProtectedRoute> | ||
| ), | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| path: '/login', | ||
| element: <LoginPage />, | ||
| errorElement: <NotFound />, | ||
| }, | ||
| { | ||
| path: '/signup', | ||
| element: <SignupPage />, | ||
| errorElement: <NotFound />, | ||
| }, | ||
| ]); | ||
| function App() { | ||
| return ( | ||
| <AuthProvider> | ||
| <RouterProvider router={router} /> | ||
| </AuthProvider> | ||
| ); | ||
| } | ||
|
|
||
| export default App; |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| import { useNavigate } from 'react-router-dom'; | ||
| import { useAuth } from '../context/AuthContext'; | ||
| const Navbar = () => { | ||
| const navigate = useNavigate(); | ||
| const { user, logout } = useAuth(); | ||
|
|
||
| return ( | ||
| <nav className="flex items-center justify-between px-6 py-4 border-b border-gray-200"> | ||
| {/* 로고 */} | ||
| <div | ||
| className="font-bold text-lg cursor-pointer" | ||
| onClick={() => navigate('/')} | ||
| > | ||
| 홈 | ||
| </div> | ||
|
|
||
| {/* 버튼 영역 */} | ||
| <div className="flex gap-2"> | ||
| {user ? ( | ||
| //로그인 된 상태 | ||
| <div className="flex items-center gap-3"> | ||
| <span | ||
| className="text-gray-600 cursor-pointer hover:text-blue-400 transition-colors" | ||
| onClick={() => navigate('/mypage')} | ||
| > | ||
| 마이페이지 | ||
| </span> | ||
| <button | ||
| onClick={() => { | ||
| logout(); | ||
| navigate('/'); | ||
| }} | ||
| className="px-4 py-2 text-sm border border-gray-300 rounded-md hover:bg-gray-100 transition-colors" | ||
| > | ||
| 로그아웃 | ||
| </button> | ||
| </div> | ||
| ) : ( | ||
| // 로그인 안 된 상태 | ||
| <> | ||
| <button | ||
| onClick={() => navigate('/login')} | ||
| className="px-4 py-2 text-sm border border-gray-300 rounded-md hover:bg-gray-100 transition-colors" | ||
| > | ||
| 로그인 | ||
| </button> | ||
| <button | ||
| onClick={() => navigate('/signup')} | ||
| className="px-4 py-2 text-sm bg-blue-300 text-white rounded-md hover:bg-blue-500 transition-colors" | ||
| > | ||
| 회원가입 | ||
| </button> | ||
| </> | ||
| )} | ||
| </div> | ||
| </nav> | ||
| ); | ||
| }; | ||
|
|
||
| export default Navbar; | ||
11 changes: 11 additions & 0 deletions
11
mission/chapter05/mission_1/src/components/ProtectedRoute.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| import { Navigate } from 'react-router-dom'; | ||
| import { useAuth } from '../context/AuthContext'; | ||
|
|
||
| const ProtectedRoute = ({ children }: { children: React.ReactNode }) => { | ||
| const { user } = useAuth(); | ||
|
|
||
| if (!user) return <Navigate to="/login" replace />; | ||
| return <>{children}</>; | ||
| }; | ||
|
|
||
| export default ProtectedRoute; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| import { createContext, useContext, useState } from 'react'; | ||
|
|
||
| interface User { | ||
| email: string; | ||
| nickname: string; | ||
| } | ||
|
|
||
| interface AuthContextType { | ||
| user: User | null; | ||
| login: (user: User) => void; | ||
| logout: () => void; | ||
| } | ||
|
|
||
| const AuthContext = createContext<AuthContextType | null>(null); | ||
|
|
||
| export const AuthProvider = ({ children }: { children: React.ReactNode }) => { | ||
| const [user, setUser] = useState<User | null>(() => { | ||
| // 로컬스토리지에서 초기값 불러오기 | ||
| const stored = localStorage.getItem('user'); | ||
| return stored ? JSON.parse(stored) : null; | ||
| }); | ||
|
|
||
| const login = (userData: User) => { | ||
| setUser(userData); | ||
| localStorage.setItem('user', JSON.stringify(userData)); | ||
| }; | ||
|
|
||
| const logout = () => { | ||
| setUser(null); | ||
| localStorage.removeItem('user'); | ||
| }; | ||
|
|
||
| return ( | ||
| <AuthContext.Provider value={{ user, login, logout }}> | ||
| {children} | ||
| </AuthContext.Provider> | ||
| ); | ||
| }; | ||
|
|
||
| export const useAuth = () => { | ||
| const context = useContext(AuthContext); | ||
| if (!context) throw new Error('AuthProvider 밖에서 사용 불가!'); | ||
| return context; | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| import { useEffect, useState, type ChangeEvent } from 'react'; | ||
|
|
||
| interface UseFormProps<T> { | ||
| initialValue: T; | ||
| //값이 올바른지 검증하는 함수 | ||
| validate: (values: T) => Record<keyof T, string>; | ||
| } | ||
|
|
||
| function useForm<T>({ initialValue, validate }: UseFormProps<T>) { | ||
| const [values, setValues] = useState(initialValue); | ||
|
|
||
| //"email": true -> touch 됨 | ||
| //"password": false -> touch 안 됨 | ||
| const [touched, setTouched] = useState<Record<string, boolean>>(); | ||
|
|
||
| //"email": 이메일은 반드시 @를 포함해야 합니다. | ||
| const [errors, setErrors] = useState<Record<string, string>>(); | ||
|
|
||
| //사용자가 입력값 바꿀 때 실행되는 함수 | ||
| const handleChange = (name: keyof T, text: string) => { | ||
| setValues({ | ||
| ...values, //기존 입력값 유지 | ||
| [name]: text, | ||
| }); | ||
| }; | ||
|
|
||
| const handleBlur = (name: keyof T) => { | ||
| setTouched({ | ||
| ...touched, | ||
| [name]: true, | ||
| }); | ||
| }; | ||
|
|
||
| //이메일과 비밀번호 인풋, 속성들을 가져오는 것 | ||
| const getInputProps = (name: keyof T) => { | ||
| const value = values[name]; | ||
| const onChange = (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => | ||
| handleChange(name, e.target.value); | ||
| const onBlur = () => handleBlur(name); | ||
|
|
||
| return { value, onChange, onBlur }; | ||
| }; | ||
|
|
||
| //values가 변경될 때 에러검증 로직 | ||
| //변경될 때마다 로직이 변경되어야 하니 useEffect 사용 | ||
| useEffect(() => { | ||
| const newErrors = validate(values); | ||
| setErrors(newErrors); //오류 메시지 업데이트 | ||
| }, [validate, values]); | ||
|
|
||
| return {values, errors, touched, getInputProps}; | ||
| } | ||
|
|
||
| export default useForm; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| import { useState } from 'react'; | ||
|
|
||
| function useLocalStorage<T>(key: string, initialValue: T) { | ||
| // 로컬스토리지에서 값을 가져오는 함수 | ||
| const [storedValue, setStoredValue] = useState<T>(() => { | ||
| try { | ||
| const item = window.localStorage.getItem(key); | ||
| // 저장된 값이 있으면 파싱해서 반환, 없으면 초기값 반환 | ||
| return item ? JSON.parse(item) : initialValue; | ||
| } catch (error) { | ||
| console.error(error); | ||
| return initialValue; | ||
| } | ||
| }); | ||
|
|
||
| // 값을 저장하는 함수 | ||
| const setValue = (value: T) => { | ||
| try { | ||
| setStoredValue(value); | ||
| window.localStorage.setItem(key, JSON.stringify(value)); | ||
| } catch (error) { | ||
| console.error(error); | ||
| } | ||
| }; | ||
|
|
||
| return [storedValue, setValue] as const; | ||
| } | ||
|
|
||
| export default useLocalStorage; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| @import 'tailwindcss'; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.