Skip to content

Commit

Permalink
🎉 닉네임 수정 컴포넌트 및 로직
Browse files Browse the repository at this point in the history
  • Loading branch information
oaoong committed Nov 8, 2023
1 parent e8fef2e commit a26a4a4
Show file tree
Hide file tree
Showing 16 changed files with 207 additions and 18 deletions.
11 changes: 11 additions & 0 deletions public/images/icon-edit-memo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 23 additions & 2 deletions src/app/(root)/(routes)/mypage/section/UserInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

import React, { useState } from 'react'
import AvatarEditable from '@/components/domain/AvatarEditable'
import { putUserProfile } from '@/services/user/user'
import TextEditable from '@/components/domain/TextEditable'
import { useAuth } from '@/contexts/AuthProvider'
import { putUserNickname, putUserProfile } from '@/services/user/user'

const UserInfo = () => {
const { currentUser } = useAuth()
const [isProfileChanged, setIsProfileChanged] = useState(true)
const [isNicknameChanged, setIsNicknameChanged] = useState(true)

Expand All @@ -19,11 +22,29 @@ const UserInfo = () => {
}
}

const nicknameChangeHandler = async (nickname: string) => {
setIsNicknameChanged(true)
try {
const _data = await putUserNickname(nickname)
} catch (error) {
setIsNicknameChanged(false)
console.log(error)
//TODO: toast error message 추가
}
}

return (
<div>
//TODO: 제출 하고 응답 받는 동안 로딩 처리 또는 비활성화 처리
<div className="flex flex-col items-center gap-2">
<AvatarEditable
fileChangeHandler={fileChangeHandler}
changedSuccessfully={isProfileChanged}
defaultImage={currentUser?.profileImg}
/>
<TextEditable
onChangeHandler={nicknameChangeHandler}
changedSuccessfully={isNicknameChanged}
defaultText={currentUser?.nickname}
/>
</div>
)
Expand Down
8 changes: 5 additions & 3 deletions src/components/domain/AvatarEditable/AvatarEditable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,21 @@ interface EditButtonProps extends React.HTMLAttributes<HTMLInputElement> {
type AvatarEditablePropsType = {
fileChangeHandler: (_file: File) => void
changedSuccessfully: boolean
defaultImage?: string | null
}

const AvatarEditable = ({
fileChangeHandler,
changedSuccessfully,
defaultImage = null,
}: AvatarEditablePropsType) => {
const [profileImage, setProfileImage] = useState<string | null>(null)
const [profileImage, setProfileImage] = useState<string | null>(defaultImage)

useEffect(() => {
if (!changedSuccessfully) {
setProfileImage(() => null)
setProfileImage(() => defaultImage)
}
}, [changedSuccessfully])
}, [changedSuccessfully, defaultImage])

const onClickEdit = (event: ChangeEvent<HTMLInputElement>) => {
event.preventDefault()
Expand Down
22 changes: 22 additions & 0 deletions src/components/domain/TextEditable/TextEditable.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { Meta, StoryObj } from '@storybook/react'
import TextEditable from './TextEditable'

const meta = {
title: 'DOMAIN/TextEditable',
component: TextEditable,
tags: ['autodocs'],
argTypes: {},
} satisfies Meta<typeof TextEditable>

export default meta
type Story = StoryObj<typeof meta>

export const Normal: Story = {
args: {
defaultText: 'text',
changedSuccessfully: true,
onChangeHandler: () => {
alert('changed')
},
},
}
69 changes: 69 additions & 0 deletions src/components/domain/TextEditable/TextEditable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
'use client'

import React, { useEffect, useState } from 'react'
import Image from 'next/image'
import Button from '@/components/ui/Button'
import Input from '@/components/ui/Input'
import Assets from '@/config/assets'
import { cn } from '@/utils'

type TextEditablePropsType = {
onChangeHandler: (_text: string) => void
changedSuccessfully: boolean
defaultText?: string
}

const TextEditable = ({
onChangeHandler,
changedSuccessfully,
defaultText = '닉네임',
}: TextEditablePropsType) => {
const [isEditing, setIsEditing] = useState(false)
const [value, setValue] = useState(defaultText)

useEffect(() => {
if (!changedSuccessfully) {
setValue(() => defaultText)
}
}, [changedSuccessfully, defaultText])

const onChangeInput = (event: React.ChangeEvent<HTMLInputElement>) => {
setValue(event.target.value)
}

return (
<div className="relative flex flex-row items-center justify-center gap-1">
<Input
border={isEditing ? 'bottom' : 'none'}
disabled={!isEditing}
value={value}
className="overflow-hidden text-center cursor-default w-fit line-clamp-1"
onChange={onChangeInput}
/>
<Button
onClick={() => {
if (isEditing) {
onChangeHandler(value)
}
setIsEditing((prev) => !prev)
}}
variant={isEditing ? 'primary' : null}
size={isEditing ? 'sm' : 'icon_sm'}
className={cn(!isEditing && 'absolute right-0 cursor-pointer')}
asChild
>
{isEditing ? (
<span className="break-keep">완료</span>
) : (
<Image
src={Assets.editMemoIcon}
className="bg-background-color"
alt={'edit-text'}
/>
)}
</Button>
</div>
)
}

export default TextEditable
3 changes: 3 additions & 0 deletions src/components/domain/TextEditable/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import TextEditable from './TextEditable'

export default TextEditable
2 changes: 1 addition & 1 deletion src/components/ui/Input/Input.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default meta
type Story = StoryObj<typeof meta>

export const Normal: Story = {
args: {},
args: { border: 'default' },
}

export const InputWithLabel: Story = {
Expand Down
29 changes: 23 additions & 6 deletions src/components/ui/Input/Input.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,35 @@
import * as React from 'react'
import { VariantProps, cva } from 'class-variance-authority'
import { cn } from '@/utils'

const inputVariants = cva(
'flex h-9 w-full rounded-md bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50',
{
variants: {
border: {
default:
'border border-input focus-visible:outline-primary-color focus-visible:outline-1 focus-visible:ring-1 focus-visible:ring-ring',
none: 'border-none shadow-none focus-visible:ring-none focus-visible:outline-none',
bottom:
'border-t-0 border-x-0 border-b border-b-input shadow-none focus-visible:ring-none focus-visible:outline-none rounded-none focus-visible:border-primary-color',
},
},
defaultVariants: {
border: 'default',
},
},
)

export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
extends React.InputHTMLAttributes<HTMLInputElement>,
VariantProps<typeof inputVariants> {}

const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
({ className, type, border, ...props }, ref) => {
return (
<input
type={type}
className={cn(
'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-primary-color focus-visible:outline-1 focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
className={cn(inputVariants({ border, className }))}
ref={ref}
{...props}
/>
Expand Down
1 change: 1 addition & 0 deletions src/config/apiEndPoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const ApiEndPoint = {
dibs: (itemId: number) => `/dib/${itemId}`,
suggestions: (itemId: string) => `/${itemId}/available-cards`,
putUserProfile: () => '/users/profile-image',
putUserNickname: () => '/users/nickname',
} as const

export default ApiEndPoint
2 changes: 2 additions & 0 deletions src/config/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import ArrowLeftIcon from '/public/images/arrow-left.svg'
import AlarmIcon from '/public/images/bell.svg'
import FilterIcon from '/public/images/filter.svg'
import GoogleIcon from '/public/images/google.png'
import EditMemoIcon from '/public/images/icon-edit-memo.svg'
import editIcon from '/public/images/icon-edit.svg'
import ActiveHeartIcon from '/public/images/icon-heart-active.svg'
import InActiveHeartIcon from '/public/images/icon-heart-inactive.svg'
Expand Down Expand Up @@ -36,6 +37,7 @@ const Assets = {
markerIcon: MarkerIcon,
moneyIcon: MoneyIcon,
editIcon: editIcon,
editMemoIcon: EditMemoIcon,
} as const

export default Assets
3 changes: 2 additions & 1 deletion src/contexts/AuthProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

import React, { createContext, useMemo, useContext } from 'react'
import useValidate from '@/hooks/useValidate'
import type { User } from '@/types'

const AuthContext = createContext<{
currentUser: any
currentUser: User | null
isLoggedIn: boolean
}>({
currentUser: null,
Expand Down
7 changes: 4 additions & 3 deletions src/hooks/useValidate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import Cookies from 'js-cookie'
import { usePathname } from 'next/navigation'
import { Environment } from '@/config/environment'
import { getValidateUser } from '@/services/auth/auth'
import type { User } from '@/types'

const useValidate = () => {
const token = Cookies.get(Environment.tokenName())
const pathname = usePathname()

const [isLoggedIn, setIsLoggedIn] = useState(!!token)
const [currentUser, setCurrentUser] = useState(undefined)
const [isLoggedIn, setIsLoggedIn] = useState<boolean>(!!token)
const [currentUser, setCurrentUser] = useState<User | null>(null)

const { data, isError } = useQuery({
queryKey: ['validate', token],
Expand All @@ -27,7 +28,7 @@ const useValidate = () => {
if (isError) {
Cookies.remove(Environment.tokenName())
setIsLoggedIn(() => false)
setCurrentUser(() => undefined)
setCurrentUser(() => null)
}
if (data) {
setIsLoggedIn(() => true)
Expand Down
17 changes: 17 additions & 0 deletions src/lib/msw/mocks/userHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,23 @@ const userHandlers = [
)
},
),
rest.put(
`${baseUrl}${ApiEndPoint.putUserNickname()}`,
async (_req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({
data: {
userInfo: {
userId: 1,
nickname: '귀염둥이파김치',
profileUrl: 'xxx',
},
},
}),
)
},
),
]

export { userHandlers }
12 changes: 10 additions & 2 deletions src/services/user/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,16 @@ const putUserProfile = async ({ file }: putUserProfileReq) => {
'Content-Type': 'multipart/form-data',
},
)
console.log(response)

return response
}

const putUserNickname = async (nickname: string) => {
const response = await apiClient.put(ApiEndPoint.putUserNickname(), {
nickname,
})

return response
}

export { putUserProfile }
export { putUserProfile, putUserNickname }
3 changes: 3 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Category, Status } from './item'
import { User } from './user'

export interface Item {
_id: number
Expand Down Expand Up @@ -55,3 +56,5 @@ export interface ItemSuggestion {
priceRange: Category
suggestionType: 'poke' | 'offer'
}

export type { User }
11 changes: 11 additions & 0 deletions src/types/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
interface User {
userId: number
accountId: string
nickname: string
role: 'USER' | 'ADMIN'
createdDate: string
modifiedDate: string
profileImg?: string
}

export type { User }

0 comments on commit a26a4a4

Please sign in to comment.