Skip to content

Commit

Permalink
Merge pull request #79 from 5wonju/feat/#76-user-ui
Browse files Browse the repository at this point in the history
Feat/#76 user UI
  • Loading branch information
leewooseong authored May 13, 2024
2 parents 16fd75f + f2100ff commit 55ec352
Show file tree
Hide file tree
Showing 9 changed files with 179 additions and 29 deletions.
2 changes: 1 addition & 1 deletion frontend/app/(page)/(needProtection)/lobby/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const Lobby = () => {
<WaitingRoomList />
</section>
<div className="col-span-3 grid grid-cols-3">
<section className="col-span-1">
<section className="col-span-1 flex items-center justify-center shadow-inner">
<UserInfo />
</section>
<section className="col-span-2 relative rounded overflow-hidden">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { useAuth } from '@/app/hooks/useAuth'
import React, { useState } from 'react'
import { NameMessageInfo, NameMessageType } from '../lib/type.d'
import { validateNickname } from '../lib/util'
import clsx from 'clsx'
import { patchNickname } from '../lib/api'

const ModifyNickname = () => {
const {
userInfo: { nickname },
refetch,
} = useAuth()

const [nicknameStatus, setNicknameStatus] = useState<NameMessageType>('initial')

const handleSubmitModification = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
const formData = new FormData(e.currentTarget)
const nickname = formData.get('nickname') as string

const currentStatus = validateNickname(nickname ?? '')
if (currentStatus !== 'valid') {
setNicknameStatus(currentStatus)
return
}

const patchNicknameResult = await patchNickname(nickname)
if (patchNicknameResult === 'valid') {
setNicknameStatus('valid')
refetch() // Todo: nicknameStatus state도 초기화 안되는지 확인 필요.
} else {
setNicknameStatus('duplicate')
}
}

return (
<form onSubmit={handleSubmitModification} className="flex flex-col justify-center gap-3">
<input
type="text"
defaultValue={nickname}
name="nickname"
placeholder="수정할 닉네임을 입력해주세요."
className="w-full px-10 py-3 text-2xl font-bold text-center text-black border-b-2 px- border-lightGray2 focus:outline-none"
/>
<p
className={clsx('font-medium', {
'text-red-500': nicknameStatus !== 'valid',
'text-green-500': nicknameStatus === 'valid',
})}
>
{NameMessageInfo[nicknameStatus]}
</p>
<button type="submit" className={clsx(`py-4 text-white rounded bg-darkGray1 `, {})}>
수정
</button>
<ul className="text-darkGray1">
<li>* 10자 이상으로 작성해주세요.</li>
<li>* 알파벳 소문자와 한글만 입력해주세요.</li>
<li>* 특수문자로 _와 .만 입력해주세요.</li>
</ul>
</form>
)
}

export default ModifyNickname
22 changes: 22 additions & 0 deletions frontend/app/(page)/(needProtection)/mypage/lib/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { tokenInstance } from '@/app/axios'
import { AxiosError } from 'axios'

interface IPatchNicknameRes {
msg: string
data: { nickname: string }
}

export const patchNickname = async (nickname: string): Promise<'valid' | 'duplicated'> => {
try {
await tokenInstance.patch('/users/my-page/nickname', { nickname })
return 'valid'
} catch (error) {
if (error instanceof AxiosError) {
console.error(error)
return error.response?.status === 409 ? 'duplicated' : 'valid'
} else {
console.error(error)
throw error // 예기치 않은 오류는 다시 던집니다.
}
}
}
16 changes: 16 additions & 0 deletions frontend/app/(page)/(needProtection)/mypage/lib/type.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export type NameMessageType =
| 'empty'
| 'tooLong'
| 'invalidChar'
| 'duplicate'
| 'valid'
| 'initial'

export enum NameMessageInfo {
'initial' = '',
'empty' = '닉네임이 비어있습니다.',
'tooLong' = '닉네임은 최대 10자를 넘을 수 없습니다.',
'invalidChar' = '유효하지 않은 문자 입력 입니다.',
'duplicate' = '이미 사용중인 닉네임 입니다.',
'valid' = '프로필이 수정되었습니다.',
}
18 changes: 18 additions & 0 deletions frontend/app/(page)/(needProtection)/mypage/lib/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { NameMessageType } from './type.d'

export const validateNickname = (nickname: string): NameMessageType => {
// :: 정규식
const tooLongPwReg = /^.{10,}$/ // 10자 이상
const validCharReg = /^[a-z0-9_.-|-|-]+$/g // 공백이나 유효하지 않은 문자가 포함된 경우

// :: name 유효성 검사
if (nickname.length === 0) {
return 'empty'
}
if (tooLongPwReg.test(nickname)) {
return 'tooLong'
} else if (!validCharReg.test(nickname)) {
return 'invalidChar'
}
return 'valid'
}
13 changes: 4 additions & 9 deletions frontend/app/(page)/(needProtection)/mypage/page.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
'use client'
import React, { useEffect } from 'react'
import UserInfo from '../../../component/UserInfo'
import { useAuth } from '@/app/hooks/useAuth'
import ModifyNickname from './component/ModifyNickname'

const Mypage = () => {
const { refetch } = useAuth()

useEffect(() => {
refetch()
}, [])

return (
<div>
<section className="h-[calc(100vh-3.5rem)] flex flex-col justify-center items-center gap-6">
<UserInfo />
</div>
<ModifyNickname />
</section>
)
}

Expand Down
16 changes: 16 additions & 0 deletions frontend/app/component/Navigation/GoBackButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useRouter } from 'next/navigation'
import React from 'react'

const GoBackButton = () => {
const router = useRouter()
const handleClickGoBack = () => {
router.back()
}
return (
<button onClick={handleClickGoBack} className="nav-btn-mono">
돌아가기
</button>
)
}

export default GoBackButton
14 changes: 14 additions & 0 deletions frontend/app/component/Navigation/HeaderNavigationBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Link from 'next/link'
import LogoutButton from './LogoutButton'
import ExitLobbyButton from './ExitLobbyButton'
import ExitGameButton from './ExitGameButton'
import GoBackButton from './GoBackButton'

// 1. 메인 페이지
// - 로고
Expand All @@ -26,6 +27,10 @@ import ExitGameButton from './ExitGameButton'
// - 로고
// - 게임 나가기 버튼

// 5. 마이페이지
// - 로고
// - 돌아가기 버튼

const renderChannelNavbarButtons = () => {
return (
<>
Expand All @@ -51,10 +56,19 @@ const renderGameNavbarButtons = () => {
)
}

const renderMyPageNavbarButtons = () => {
return (
<>
<GoBackButton />
</>
)
}

const conditionalNavbarInfo: Record<string, () => React.JSX.Element> = {
'/channel': renderChannelNavbarButtons,
'/lobby': renderLobbyNavbarButtons,
'/game': renderGameNavbarButtons,
'/mypage': renderMyPageNavbarButtons,
}

const HeaderNavigationBar = () => {
Expand Down
42 changes: 23 additions & 19 deletions frontend/app/component/UserInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,39 @@ interface userInfoType {
}
const UserInfo = () => {
const { userInfo } = useAuth()
// const [userData, setUserData] = React.useState<userInfoType | null>(null)
// useEffect(() => {
// userInfo && setUserData(userInfo.userData)
// }, [userInfo])

return (
<>
<p className="sr-only">유저 정보 컴포넌트</p>
{userInfo && (
<Link
href={'/mypage'}
className="bg-white p-4 shadow-md flex flex-col justify-center gap-1 h-full"
className="bg-white p-4 flex flex-col justify-center gap-1 items-center"
>
<div className="flex gap-4">
<Image
src={defaultProfileImg}
alt="Profile Image"
width={100}
height={100}
className="rounded-full"
/>
<div className="flex flex-col gap-1 grow">
<div className="font-bold text-lg">{userInfo.userNickname}</div>
<div>승리: {userInfo.winCount}</div>
<div>승률: {userInfo.winRate}%</div>
<div className="flex gap-8">
<div className="flex flex-col justify-center items-center gap-4">
<Image
src={defaultProfileImg}
alt="Profile Image"
width={100}
height={100}
className="rounded-full"
/>
<div className="font-bold text-2xl">{userInfo.nickname}</div>
</div>
<dl className="grid grid-cols-2 gap-1 grow text-lg">
<dt className="font-bold inline-block">승리 : </dt>
<dd className="inline-block">{userInfo.winCnt}</dd>
<dt className="font-bold inline-block">승률 : </dt>
<dd className="inline-block">{userInfo.winRate}%</dd>
<dt className="font-bold inline-block">포인트 : </dt>
<dd className="inline-block">{userInfo.point}P</dd>
<dt className="font-bold inline-block">탈주 이력 : </dt>
<dd className="inline-block">{userInfo.escapeHistory}</dd>
<dt className="font-bold inline-block">경험치 : </dt>
<dd className="inline-block">{userInfo.exp}</dd>
</dl>
</div>
<div className="">포인트: {userInfo.userPoint}</div>
<div className="">경험치: {userInfo.exp}</div>
</Link>
)}
</>
Expand Down

0 comments on commit 55ec352

Please sign in to comment.