Skip to content

Commit

Permalink
Feat/api (#16)
Browse files Browse the repository at this point in the history
- Admin login 추가
- 404 & 500 페이지 추가
- Home page history section css 수정
  • Loading branch information
Sunny-jinn authored Nov 12, 2024
2 parents fef1a25 + 54e4db2 commit 11c0266
Show file tree
Hide file tree
Showing 15 changed files with 172 additions and 39 deletions.
2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { checkToken, getUserInfo } from './api/auth.ts'
import userStore from './store/User.ts'
import Home from './pages/Home'
import Admin from './pages/Admin.tsx'
import { NotFound } from './pages/error/NotFound.tsx'

function App() {
const [location] = useLocation()
Expand Down Expand Up @@ -46,6 +47,7 @@ function App() {
<Route path='/post/:post_id' component={PostDetail} />
<Route path='/profile' component={Profile} />
<Route path='/admin' component={Admin} />
<Route path='*' component={NotFound} />
</Switch>
)
}
Expand Down
14 changes: 8 additions & 6 deletions src/Auth/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import verticalBar from '../assets/verticalBar.png'
import { Link } from 'wouter'
import { z } from 'zod'
import { useLogin } from './auth.tsx'
import { StudentId } from './User.ts'

interface Props {
onClose: () => void
Expand Down Expand Up @@ -79,13 +78,16 @@ export const LoginModal: FC<Props> = ({ context, ...props }) => {
try {
const loginForm = new FormData(event.currentTarget)

const studentId = StudentId.parse(loginForm.get('schoolId'))
const password = z.string().parse(loginForm.get('password'))
const studentId = loginForm.get('schoolId')?.toString()
const password = loginForm.get('password')?.toString()

await login(studentId, password)
const parsedPassword = z.string().parse(password)

console.log('login try')

await login(studentId!, parsedPassword)
props.onClose()
} catch {
//@TODO inform user that login has failed
console.log('Failed to login')
}
}}
Expand All @@ -97,7 +99,7 @@ export const LoginModal: FC<Props> = ({ context, ...props }) => {
name='schoolId'
label='학번'
placeholder='학번을 입력해주세요'
pattern={/\d{10}/}
pattern={/^admin$|^\d{10}$/}
errorMessage='학번은 숫자 열 자리여야 합니다'
required
css={css`
Expand Down
50 changes: 33 additions & 17 deletions src/Auth/auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createContext, useContext, FC, ReactNode, useState } from 'react'
import { StudentId } from './User.ts'

import { addAccessTokenToServer } from '../api/index.ts'
import { useLocation } from 'wouter'

//@TODO: Refactor this when React 19 release.
const Auth = createContext<
Expand All @@ -28,35 +29,50 @@ export function useCredential() {

//@TODO: Discriminate login failures.
export function useLogin(): (
studentId: StudentId,
studentId: StudentId | string,
password: string
) => Promise<boolean> {
const [, setCredential] = useContext(Auth)
const [, navigate] = useLocation()

return async (studentId, password) => {
const loginInfo = new FormData()

loginInfo.set('username', studentId)
loginInfo.set('password', password)

const response = await fetch('/api/login', {
method: 'POST',
body: loginInfo,
})
try {
const response = await fetch('/api/login', {
method: 'POST',
body: loginInfo,
})

if (response.ok) {
const token = response.headers.get('Authorization')?.split(' ')[1]
if (token) {
addAccessTokenToServer(token)
}
const credential = await response.text()
const data = await response.json()

setCredential(credential)
window.location.reload()
if (data) {
// isAdmin 값을 sessionStorage에 저장
sessionStorage.setItem('isAdmin', data.isAdmin.toString())
console.log('어드민인가요?', data.isAdmin)

return true
} else {
return false
// isAdmin이 true일 경우 /admin 페이지로 이동
if (data.isAdmin) {
navigate('/admin')
}
}

if (response.ok) {
const token = response.headers.get('Authorization')?.split(' ')[1]
if (token) {
addAccessTokenToServer(token)
}
setCredential(data.credential)
window.location.reload()
return true
} else {
return false
}
} catch (error) {
console.error('Login request failed:', error)
throw error
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/api/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const getUserInfo = async (): Promise<TUser> => {
export const logout = async () => {
const res = await Server.post('logout')
localStorage.removeItem('accessToken')
sessionStorage.removeItem('isAdmin')

return res
}
Expand Down
4 changes: 3 additions & 1 deletion src/common/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@ export const Header: FC<HeaderProps> = ({ num }) => {
<>
<header css={HeaderStyle.header}>
<div css={HeaderStyle.logoBox}>
<img src={logo} css={HeaderStyle.logo} />
<Link href='/'>
<img src={logo} css={HeaderStyle.logo} />
</Link>
</div>

<nav css={HeaderStyle.navBox(num)}>
Expand Down
3 changes: 2 additions & 1 deletion src/common/InputBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,11 @@ const InputBoxStyle = {
}
`,
errorMessage: (inputId: string) => css`
display: none;
opacity: 0;
#${inputId}:invalid:not(:focus)[data-is-touched='true'] ~ &:not(:empty) {
display: inline;
opacity: 1;
color: ${Color.Red};
}
`,
Expand Down
18 changes: 17 additions & 1 deletion src/pages/Admin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { TUser } from '../types'
import { Color } from '../palette'
import icon_search from '../assets/icon_search.svg'
import { Pagination } from '../components/Pagination'
import { ServerError } from './error/ServerError'

const Style = {
wrapper: css`
Expand Down Expand Up @@ -88,8 +89,19 @@ const Admin: React.FC = () => {
const [keyword, setKeyword] = useState('')
const [debouncedKeyword, setDebouncedKeyword] = useState('')
const [page, setPage] = useState(0)
const [isAuthorized, setIsAuthorized] = useState(true) // 접근 허용 여부를 위한 상태

const size = 5

useEffect(() => {
// sessionStorage에서 isAdmin 값을 가져와 Boolean으로 변환
const isAdmin = sessionStorage.getItem('isAdmin') === 'true'

if (!isAdmin) {
setIsAuthorized(false) // 접근이 허용되지 않음을 설정
}
}, [])

useEffect(() => {
const timer = setTimeout(() => {
setDebouncedKeyword(keyword)
Expand Down Expand Up @@ -131,7 +143,11 @@ const Admin: React.FC = () => {
))
}

if (studentRequestError || studentListError) return <div>Error</div>
if (studentRequestError || studentListError) return <ServerError />

if (!isAuthorized) {
return <div>관리자만 접근 가능한 페이지입니다.</div>
}

return (
<div css={Style.wrapper}>
Expand Down
7 changes: 4 additions & 3 deletions src/pages/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ const HistorySection = css`

const HistoryContainer = css`
display: flex;
/* height: 100%; */
flex-direction: column;
z-index: 999;
`
Expand All @@ -169,7 +170,7 @@ const HistoryTitle = css`
line-height: 38.4px;
font-weight: 900;
color: ${Color.Gray100};
margin-bottom: 136px;
margin-bottom: 11vh;
`

const HistorySubtitle = css`
Expand All @@ -194,7 +195,7 @@ const HistoryBold = css`

const HistoryContentContainer = css`
position: relative;
padding-top: 167px;
padding-top: 14vh;
margin-left: 70px;
`

Expand All @@ -214,7 +215,7 @@ const HistoryContents = css`
display: flex;
flex-direction: column;
margin-left: 30px;
gap: 200px;
gap: 16vh;
`

const HistoryContent = css`
Expand Down
5 changes: 3 additions & 2 deletions src/pages/PostDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
import icon_default_profile from '../assets/icon_default_profile.svg'
import userStore from '../store/User'
import { mutate } from 'swr'
import { ServerError } from './error/ServerError'

const PostStyle = {
wrapper: css`
Expand Down Expand Up @@ -202,8 +203,8 @@ const Post: React.FC = () => {
mutate: commentMutate,
} = useGetComments(post_id!)

if (isLoading || isCommentsLoading) return <>Loading..</>
if (error || commentsError) return <>Error occured!</>
if (isLoading || isCommentsLoading) return <></>
if (error || commentsError) return <ServerError />

const comments: TComment[] = commentsData.content

Expand Down
5 changes: 3 additions & 2 deletions src/pages/Profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { CustomPlusButton } from '../components/CustomPlusButton'
import { useLocation } from 'wouter'

import { CustomInput } from '../components/CustomInput'
import { ServerError } from './error/ServerError'

const Style = {
wrapper: css`
Expand Down Expand Up @@ -248,8 +249,8 @@ const Profile: React.FC = () => {
}
}, [data])

if (error || postsError) return <div>로그인 후 이용해주세요!</div>
if (isLoading || postLoading || !profileData) return <div>Loading...</div>
if (isLoading || postLoading || !profileData) return <div></div>
if (error || postsError) return <ServerError />

const profiles: TUser = profileData

Expand Down
7 changes: 4 additions & 3 deletions src/pages/Project.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ProjectCard } from '../components/ProjectCard'
import { TProject } from '../types'
import userStore from '../store/User'
import { Pagination } from '../components/Pagination'
import { ServerError } from './error/ServerError'

const containerStyle = css`
display: flex;
Expand Down Expand Up @@ -102,16 +103,16 @@ const Project: FC = () => {
size: '7',
}).toString()

const { data, error } = useSWR(`project?${params}`, fetcher)
const { data, error, isLoading } = useSWR(`project?${params}`, fetcher)

useEffect(() => {
if (data) {
setTotalPages(Math.ceil(data.data.totalElements / 7))
}
}, [data])

if (error) return <div>Failed to load profile</div>
if (!data) return <div>Loading...</div>
if (isLoading) return <div></div>
if (error) return <ServerError />

const projectList: TProject[] = data.data.content

Expand Down
4 changes: 3 additions & 1 deletion src/pages/ProjectDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'swiper/css/effect-fade'
import { SwiperBullets } from '../components/SwiperBullets'
import { Swiper as SwiperClass } from 'swiper/types' // Swiper 인스턴스 타입
import { Header } from '../common/Header'
import { ServerError } from './error/ServerError'

const Style = {
wrapper: css`
Expand Down Expand Up @@ -197,7 +198,8 @@ const ProjectDetail: React.FC = () => {
const [, navigate] = useLocation()

const { data, isLoading, error } = useSWR(`project/${project_id!}`, fetcher)
if (isLoading || error) return <div>Error</div>
if (isLoading) return <div></div>
if (error) return <ServerError />

const projectInfo: TProject = data.data

Expand Down
5 changes: 3 additions & 2 deletions src/pages/TechBlog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Header } from '../common/Header'
import { TPost } from '../types'
import { useGetPostList } from '../hooks/query/post.api'
import { Pagination } from '../components/Pagination'
import { ServerError } from './error/ServerError'

const TechBlog: React.FC = () => {
const [currentPage, setCurrentPage] = useState<number>(0)
Expand All @@ -31,8 +32,8 @@ const TechBlog: React.FC = () => {
}
}, [data])

if (isLoading) return <div>Failed to load profiles</div>
if (error) return <div>Error!</div>
if (isLoading) return <div></div>
if (error) return <ServerError />

const postList: TPost[] = data.content

Expand Down
43 changes: 43 additions & 0 deletions src/pages/error/NotFound.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { css } from '@emotion/react'
import main_image from '../../assets/main_image.svg'
import { Link } from 'wouter'
import { Color } from '../../palette'

export const NotFound = () => {
return (
<div css={Wrapper}>
<img src={main_image} alt='404' />
<div css={TextContainer}>
<h1>404</h1>
<p>페이지를 찾을 수 없습니다.</p>
<Link to='/'>홈으로 돌아가기</Link>
</div>
</div>
)
}

const Wrapper = css`
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
`

const TextContainer = css`
display: flex;
flex-direction: column;
align-items: center;
font-size: 1.5rem;
a {
font-weight: 600;
color: ${Color.Primary};
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
`
Loading

0 comments on commit 11c0266

Please sign in to comment.