From 367195ee21f875c1db1dc463e83baf4a47281cdd Mon Sep 17 00:00:00 2001 From: Yunkyu Jung <39851220+asdf99245@users.noreply.github.com> Date: Sun, 13 Aug 2023 01:23:05 +0900 Subject: [PATCH] =?UTF-8?q?feature:=20=EC=9C=A0=EC=A0=80=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EB=AA=A8=EB=8B=AC=20=EC=97=B0=EA=B2=B0=20?= =?UTF-8?q?=EB=B0=8F=20=ED=88=B4=ED=8C=81=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EA=B5=AC=ED=98=84=20(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: blue color 추가 * design: Dimmed z-index 부여 * feat: AddMenu 컴포넌트 구현 * design: Modal z-index 부여 * refactor: hook export * feat: useModals hook 추가 여러 모달들의 state를 관리 * feat: FilmAddModal 추가 * feat: 필름제목수정 모달 추가 * feat: useSafeContext hook 추가 * feat: arrow down svg 추가 * feat: Select 컴포넌트 구현 * design: Select item 커서 포인터 추가 * feat: FilmSelectModal 추가 * feat: 유저 페이지 모달 연결 * feat: pencil 아이콘 추가 * refactor: hook 이름 수정 * chore: eslint rule 수정 * feat: ModalProvider 생성 * feat: 유저페이지 ModalProvider wrapping * feat: Drawer 회원가입, 로그인 링킹 * feat: 필름 제목 edit 모달 연결 * design: modal min height 설정 * design: input full width 추가 * fix: camera svg size를 조절할 수 없던 문제 해결 * feat: 유저페이지 프로필모달 연결 * feat: isString type util 추가 * feat: Close 아이콘 추가 * feat: tooltip 컴포넌트 추가 * feat: 유저페이지 tooltip 컴포넌트 적용 * feat: add페이지 title query 적용 --- .eslintrc.json | 3 +- src/assets/icons/arrow-down.svg | 3 + src/assets/icons/camera.svg | 2 +- src/assets/icons/index.ts | 3 + src/assets/icons/pencil.svg | 4 + src/components/shared/Avatar/Avatar.tsx | 33 +++---- src/components/shared/Dimmed/Dimmed.tsx | 5 +- src/components/shared/Drawer/Drawer.tsx | 6 +- src/components/shared/Input/Input.tsx | 2 +- src/components/shared/Modal/Modal.tsx | 2 +- src/components/shared/Select/Select.tsx | 82 +++++++++++++++++ src/components/shared/Select/index.ts | 1 + src/components/shared/Tooltip/Tooltip.tsx | 27 ++++++ src/components/shared/Tooltip/index.ts | 1 + src/components/shared/index.ts | 2 + src/components/user/AddMenu.tsx | 31 +++++++ src/components/user/CameraRoll.tsx | 18 ++-- src/components/user/FilmAddModal.tsx | 23 +++++ src/components/user/FilmSelectModal.tsx | 34 +++++++ src/components/user/FilmTitleModal.tsx | 29 ++++++ src/components/user/ProfileModal.tsx | 76 ++++++++++++++++ src/components/user/hooks/index.ts | 1 + src/components/user/hooks/useModals.ts | 105 ++++++++++++++++++++++ src/components/user/index.ts | 5 ++ src/hooks/index.ts | 3 + src/hooks/useSafeContext.ts | 11 +++ src/pages/_app.tsx | 16 +++- src/pages/user/[id]/index.tsx | 103 ++++++++++++++++----- src/pages/user/[id]/item/add/index.tsx | 13 +-- src/providers/ModalProvider.tsx | 16 ++++ src/providers/index.ts | 1 + src/utils/type-util.ts | 3 + tailwind.config.js | 3 +- 33 files changed, 599 insertions(+), 68 deletions(-) create mode 100644 src/assets/icons/arrow-down.svg create mode 100644 src/assets/icons/pencil.svg create mode 100644 src/components/shared/Select/Select.tsx create mode 100644 src/components/shared/Select/index.ts create mode 100644 src/components/shared/Tooltip/Tooltip.tsx create mode 100644 src/components/shared/Tooltip/index.ts create mode 100644 src/components/user/AddMenu.tsx create mode 100644 src/components/user/FilmAddModal.tsx create mode 100644 src/components/user/FilmSelectModal.tsx create mode 100644 src/components/user/FilmTitleModal.tsx create mode 100644 src/components/user/ProfileModal.tsx create mode 100644 src/components/user/hooks/index.ts create mode 100644 src/components/user/hooks/useModals.ts create mode 100644 src/hooks/useSafeContext.ts create mode 100644 src/providers/ModalProvider.tsx create mode 100644 src/utils/type-util.ts diff --git a/.eslintrc.json b/.eslintrc.json index 2f2a570..be3f126 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -17,6 +17,7 @@ "no-unused-vars": "off", "@typescript-eslint/no-unused-vars": "warn", "@typescript-eslint/no-empty-interface": "off", - "@typescript-eslint/no-explicit-any": "off" + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/ban-types": "off" } } diff --git a/src/assets/icons/arrow-down.svg b/src/assets/icons/arrow-down.svg new file mode 100644 index 0000000..675d1a1 --- /dev/null +++ b/src/assets/icons/arrow-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/camera.svg b/src/assets/icons/camera.svg index eb3fcb0..57a03e4 100644 --- a/src/assets/icons/camera.svg +++ b/src/assets/icons/camera.svg @@ -1,4 +1,4 @@ - + diff --git a/src/assets/icons/index.ts b/src/assets/icons/index.ts index 3002639..e04036c 100644 --- a/src/assets/icons/index.ts +++ b/src/assets/icons/index.ts @@ -9,3 +9,6 @@ export { default as LeftChevron } from './left-chevron.svg'; export { default as RightChevronBG } from './right-chevron-bg.svg'; export { default as LeftChevronBG } from './left-chevron-bg.svg'; export { default as Camera } from './camera.svg'; +export { default as ArrowDown } from './arrow-down.svg'; +export { default as Pencil } from './pencil.svg'; +export { default as Close } from './close.svg'; diff --git a/src/assets/icons/pencil.svg b/src/assets/icons/pencil.svg new file mode 100644 index 0000000..0d3a0fc --- /dev/null +++ b/src/assets/icons/pencil.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/components/shared/Avatar/Avatar.tsx b/src/components/shared/Avatar/Avatar.tsx index e6d8114..8f998ba 100644 --- a/src/components/shared/Avatar/Avatar.tsx +++ b/src/components/shared/Avatar/Avatar.tsx @@ -1,11 +1,11 @@ import Image from 'next/image'; import type { ComponentProps, MouseEventHandler } from 'react'; +import { Profile } from '@/pages/user/[id]'; import clsx from 'clsx'; import { cn } from '@/utils/cn'; const PLACEHOLDER_SRC = '/images/avatar-placeholder.png'; const HINT_TEXT = '한줄소개를 작성해주세요'; -const SIZE = 80; interface AvatarProps { nickname: string; @@ -13,7 +13,7 @@ interface AvatarProps { displayMeta?: boolean; description?: string; viewCount?: number; - onClick?: MouseEventHandler; + onEditProfile?: (info: Profile) => void; } type Props = AvatarProps & @@ -23,12 +23,16 @@ export function Avatar({ src = PLACEHOLDER_SRC, nickname, displayMeta = false, - description, + description = '', viewCount = 0, className, - onClick, + onEditProfile, ...restProps }: Props) { + const handleEditProfile: MouseEventHandler = () => { + if (onEditProfile) onEditProfile({ profileImage: src, nickname, description }); + }; + return (
- {nickname} + {nickname}
{displayMeta && (
-
+
{nickname} {`Total ${viewCount}`}

- {description ?? HINT_TEXT} + {description.length === 0 && HINT_TEXT}

)} diff --git a/src/components/shared/Dimmed/Dimmed.tsx b/src/components/shared/Dimmed/Dimmed.tsx index fd29151..41c08fe 100644 --- a/src/components/shared/Dimmed/Dimmed.tsx +++ b/src/components/shared/Dimmed/Dimmed.tsx @@ -4,9 +4,6 @@ type Props = HTMLAttributes; export function Dimmed(props: Props) { return ( -
+
); } diff --git a/src/components/shared/Drawer/Drawer.tsx b/src/components/shared/Drawer/Drawer.tsx index 6a578a5..c04217f 100644 --- a/src/components/shared/Drawer/Drawer.tsx +++ b/src/components/shared/Drawer/Drawer.tsx @@ -1,5 +1,6 @@ import Image from 'next/image'; import Link from 'next/link'; +import { useRouter } from 'next/router'; import { Fragment, type MouseEventHandler, useEffect, useState } from 'react'; import { Button, Dimmed, Icon } from '@/components/shared'; import { cn } from '@/utils/cn'; @@ -13,6 +14,7 @@ interface Props { export function Drawer({ isOpen, onClose }: Props) { const [mounted, setMounted] = useState(false); + const router = useRouter(); useEffect(() => { if (isOpen) { @@ -48,12 +50,12 @@ export function Drawer({ isOpen, onClose }: Props) { 나의 취향을 전시할 수 있는
바이오그래피, 그라피입니다

-
  • - 로그인하기 + 로그인하기
  • 의견 보내기 diff --git a/src/components/shared/Input/Input.tsx b/src/components/shared/Input/Input.tsx index 6e70fd5..b5d3866 100644 --- a/src/components/shared/Input/Input.tsx +++ b/src/components/shared/Input/Input.tsx @@ -63,7 +63,7 @@ export const Input = forwardRef( }; return ( -
    +
    {label && (
  • + {children} +
  • + ); +} + +Select.Item = SelectItem; diff --git a/src/components/shared/Select/index.ts b/src/components/shared/Select/index.ts new file mode 100644 index 0000000..7868ecb --- /dev/null +++ b/src/components/shared/Select/index.ts @@ -0,0 +1 @@ +export * from './Select'; diff --git a/src/components/shared/Tooltip/Tooltip.tsx b/src/components/shared/Tooltip/Tooltip.tsx new file mode 100644 index 0000000..89a0418 --- /dev/null +++ b/src/components/shared/Tooltip/Tooltip.tsx @@ -0,0 +1,27 @@ +import { type HTMLAttributes, type MouseEventHandler, type PropsWithChildren, useState } from 'react'; +import { Icon } from '@/components/shared/Icon'; + +interface Props extends HTMLAttributes { + isOpen?: boolean; + text: string; +} + +export function Tooltip({ children, isOpen: isOpenFromProps = true, text, ...restProps }: PropsWithChildren) { + const [isOpen, setIsOpen] = useState(isOpenFromProps); + + const handleClickClose: MouseEventHandler = () => { + setIsOpen(false); + }; + + return ( +
    + {children} + {isOpen && ( +
    +

    {text}

    + +
    + )} +
    + ); +} diff --git a/src/components/shared/Tooltip/index.ts b/src/components/shared/Tooltip/index.ts new file mode 100644 index 0000000..f2febce --- /dev/null +++ b/src/components/shared/Tooltip/index.ts @@ -0,0 +1 @@ +export * from './Tooltip' \ No newline at end of file diff --git a/src/components/shared/index.ts b/src/components/shared/index.ts index f820339..4599353 100644 --- a/src/components/shared/index.ts +++ b/src/components/shared/index.ts @@ -9,3 +9,5 @@ export * from './Portal'; export * from './Textarea'; export * from './ImageFrame'; export * from './TextButton'; +export * from './Select'; +export * from './Tooltip' diff --git a/src/components/user/AddMenu.tsx b/src/components/user/AddMenu.tsx new file mode 100644 index 0000000..66399c4 --- /dev/null +++ b/src/components/user/AddMenu.tsx @@ -0,0 +1,31 @@ +import { MouseEventHandler } from 'react'; +import { Dimmed, Portal } from '@/components/shared'; +import { cn } from '@/utils/cn'; + +interface Props { + isOpen: boolean; + onClose: MouseEventHandler; + onAddFilm: MouseEventHandler; + onUploadPhoto: MouseEventHandler; +} + +const menuStyle = + 'tw-w-full tw-cursor-pointer tw-py-[22px] tw-text-blue tw-flex tw-justify-center tw-items-center hover:tw-bg-grayscale-100'; + +export function AddMenu({ isOpen, onClose, onAddFilm, onUploadPhoto }: Props) { + if (!isOpen) return null; + + return ( + + +
    +
    + 새로운 필름 추가 +
    +
    + 포토컷 올리기 +
    +
    +
    + ); +} diff --git a/src/components/user/CameraRoll.tsx b/src/components/user/CameraRoll.tsx index 92d8d28..81d01f2 100644 --- a/src/components/user/CameraRoll.tsx +++ b/src/components/user/CameraRoll.tsx @@ -1,4 +1,5 @@ import { type HTMLAttributes } from 'react'; +import { Icon } from '@/components/shared'; import { cn } from '@/utils/cn'; const FILM_HOLE_COUNT = 11; @@ -6,14 +7,10 @@ const FILM_HOLE_COUNT = 11; interface Props extends HTMLAttributes { title: string; photos?: string[]; + onEditTitle: (title: string) => void; } -export function CameraRoll({ - title, - photos = [], - className, - ...restProps -}: Props) { +export function CameraRoll({ title, photos = [], onEditTitle, className, ...restProps }: Props) { const srcs = Array.from({ length: 10 }, (_, i) => photos[i] ?? ''); return ( @@ -22,7 +19,14 @@ export function CameraRoll({ {...restProps} >
    -

    {title}

    +
    +

    {title}

    + onEditTitle(title)} + /> +
    {`${photos.length} Cuts`}
    diff --git a/src/components/user/FilmAddModal.tsx b/src/components/user/FilmAddModal.tsx new file mode 100644 index 0000000..63b9b23 --- /dev/null +++ b/src/components/user/FilmAddModal.tsx @@ -0,0 +1,23 @@ +import { type ComponentProps, type MouseEventHandler, useState } from 'react'; +import { Input, Modal } from '@/components/shared'; + +type Props = Omit, 'title' | 'children'>; + +export function FilmAddModal({ onCancel, ...restProps }: Props) { + const [input, setInput] = useState(''); + + const handleValueChange = (value: string) => { + setInput(value); + }; + + const handleSave: MouseEventHandler = (e) => { + // TODO: 필름 추가 API 연결 + if (onCancel) onCancel(e); + }; + + return ( + + + + ); +} diff --git a/src/components/user/FilmSelectModal.tsx b/src/components/user/FilmSelectModal.tsx new file mode 100644 index 0000000..a8974ac --- /dev/null +++ b/src/components/user/FilmSelectModal.tsx @@ -0,0 +1,34 @@ +import { useRouter } from 'next/router'; +import { type ComponentProps, type MouseEventHandler, useState } from 'react'; +import { Modal, Select } from '@/components/shared'; + +type Props = Omit, 'title' | 'children'>; + +export function FilmSelectModal({ onCancel, ...restProps }: Props) { + const [selected, setSelected] = useState('좋아하는 디자이너'); + const router = useRouter(); + + const handleSelect = (value: string) => { + setSelected(value); + }; + + const handleSave: MouseEventHandler = () => { + // TODO: 실제 유저아이디로 변경 + router.push({ + pathname: '/user/1/item/add', + query: { + title: selected, + }, + }); + }; + + return ( + + + + ); +} diff --git a/src/components/user/FilmTitleModal.tsx b/src/components/user/FilmTitleModal.tsx new file mode 100644 index 0000000..f1e3106 --- /dev/null +++ b/src/components/user/FilmTitleModal.tsx @@ -0,0 +1,29 @@ +import { type ComponentProps, MouseEventHandler, useEffect, useState } from 'react'; +import { Input, Modal } from '@/components/shared'; + +type Props = Omit, 'children'>; + +export function FilmTitleModal({ isOpen, title, onCancel, ...restProps }: Props) { + const [input, setInput] = useState(title); + + useEffect(() => { + if (!isOpen) return; + + setInput(title); + }, [isOpen, title]); + + const handleValueChange = (value: string) => { + setInput(value); + }; + + const handleSave: MouseEventHandler = (e) => { + // TODO: 필름 제목 수정 API 연결 + if (onCancel) onCancel(e); + }; + + return ( + + + + ); +} diff --git a/src/components/user/ProfileModal.tsx b/src/components/user/ProfileModal.tsx new file mode 100644 index 0000000..e573e9a --- /dev/null +++ b/src/components/user/ProfileModal.tsx @@ -0,0 +1,76 @@ +import { type ChangeEventHandler, type ComponentProps, type MouseEventHandler, useRef, useState } from 'react'; +import { convertImageToBase64 } from '@/utils'; +import { Avatar, Icon, Input, Modal } from '@/components/shared'; + +interface Props extends Omit, 'title' | 'children'> { + profileImage: string; + nickname: string; + description: string; +} + +export function ProfileModal({ + onCancel, + profileImage, + nickname: nicknameFromProps, + description: descriptionFromProps, + ...restProps +}: Props) { + const fileInputRef = useRef(null); + const [image, setImage] = useState(profileImage); + const [nickname, setNickname] = useState(nicknameFromProps); + const [description, setDescription] = useState(descriptionFromProps); + + const handleSave: MouseEventHandler = (e) => { + // TODO: API 연결 + if (onCancel) onCancel(e); + }; + + const handleNicknameChange = (value: string) => { + setNickname(value); + }; + + const handleDescriptionChange = (value: string) => { + setDescription(value); + }; + + const handleEditPhoto: MouseEventHandler = () => { + fileInputRef.current?.click(); + }; + + const onChangeFile: ChangeEventHandler = async (e) => { + if (!e.target.files) return; + const file = e.target.files[0]; + try { + const converted = await convertImageToBase64(file); + if (typeof converted === 'string') setImage(converted); + } catch (err) { + console.error(err); + } + + // TODO: API 연결 + }; + + return ( + +
    +
    + +
    + +
    +
    +
    + + + +
    +
    +
    + ); +} diff --git a/src/components/user/hooks/index.ts b/src/components/user/hooks/index.ts new file mode 100644 index 0000000..ab2a2f7 --- /dev/null +++ b/src/components/user/hooks/index.ts @@ -0,0 +1 @@ +export * from './useModals'; diff --git a/src/components/user/hooks/useModals.ts b/src/components/user/hooks/useModals.ts new file mode 100644 index 0000000..08dcd97 --- /dev/null +++ b/src/components/user/hooks/useModals.ts @@ -0,0 +1,105 @@ +import { useReducer } from 'react'; + +export type Action = { + type: + | 'OPEN_DRAWER' + | 'CLOSE_DRAWER' + | 'OPEN_FILM_TITLE_MODAL' + | 'CLOSE_FILM_TITLE_MODAL' + | 'OPEN_FILM_ADD_MODAL' + | 'CLOSE_FILM_ADD_MODAL' + | 'OPEN_FILM_SELECT_MODAL' + | 'CLOSE_FILM_SELECT_MODAL' + | 'OPEN_PROFILE_MODAL' + | 'CLOSE_PROFILE_MODAL' + | 'OPEN_ADD_MENU' + | 'CLOSE_ADD_MENU'; +}; + +export type State = { + isDrawerOpen: boolean; + isFilmTitleModalOpen: boolean; + isFilmAddModalOpen: boolean; + isFilmSelectModalOpen: boolean; + isProfileModalOpen: boolean; + isAddMenuOpen: boolean; +}; + +const initialState: State = { + isDrawerOpen: false, + isFilmTitleModalOpen: false, + isFilmAddModalOpen: false, + isFilmSelectModalOpen: false, + isProfileModalOpen: false, + isAddMenuOpen: false, +}; + +const reducer = (state: State, action: Action): State => { + switch (action.type) { + case 'OPEN_DRAWER': + return { ...state, isDrawerOpen: true }; + case 'CLOSE_DRAWER': + return { ...state, isDrawerOpen: false }; + case 'OPEN_FILM_TITLE_MODAL': + return { ...state, isFilmTitleModalOpen: true }; + case 'CLOSE_FILM_TITLE_MODAL': + return { ...state, isFilmTitleModalOpen: false }; + case 'OPEN_FILM_ADD_MODAL': + return { ...state, isFilmAddModalOpen: true, isAddMenuOpen: false }; + case 'CLOSE_FILM_ADD_MODAL': + return { ...state, isFilmAddModalOpen: false }; + case 'OPEN_FILM_SELECT_MODAL': + return { ...state, isFilmSelectModalOpen: true, isAddMenuOpen: false }; + case 'CLOSE_FILM_SELECT_MODAL': + return { ...state, isFilmSelectModalOpen: false }; + case 'OPEN_PROFILE_MODAL': + return { ...state, isProfileModalOpen: true }; + case 'CLOSE_PROFILE_MODAL': + return { ...state, isProfileModalOpen: false }; + case 'OPEN_ADD_MENU': + return { ...state, isAddMenuOpen: true }; + case 'CLOSE_ADD_MENU': + return { ...state, isAddMenuOpen: false }; + default: + throw new Error('Invalid action type'); + } +}; + +export function useModals() { + const [state, dispatch] = useReducer(reducer, initialState); + + return { status: state, dispatch } as const; +} + +// 맵 자료구조로 key,value로 모달들을 관리하고 key에 따라 특정한 모달만 활성화하는 훅을 짜볼려했으나 ... 실패 +// 시간있을떄 다시도전.. + +// export type Key = string; + +// export type ModalMap = Map>; + +// interface Props { +// modalMap: ModalMap; +// } + +// export function useModalManager({ modalMap }: Props) { +// const [activatedModal, setActivatedModal] = useState | null>(null); + +// const enableModal = useCallback( +// (key: Key) => { +// const modal = modalMap.get(key); +// if (modal === undefined) { +// throw new Error(`Modal with key ${key} does not exist`); +// } + +// setActivatedModal(modal); +// }, +// [modalMap], +// ); + +// const disableModal = useCallback(() => { +// setActivatedModal(null); +// }, []); + +// return { ActivatedModal: activatedModal, enableModal, disableModal } as const; +// } diff --git a/src/components/user/index.ts b/src/components/user/index.ts index 577d94d..f77a97a 100644 --- a/src/components/user/index.ts +++ b/src/components/user/index.ts @@ -1 +1,6 @@ export * from './CameraRoll'; +export * from './AddMenu'; +export * from './FilmAddModal'; +export * from './FilmSelectModal'; +export * from './FilmTitleModal'; +export * from './ProfileModal'; diff --git a/src/hooks/index.ts b/src/hooks/index.ts index bb33101..e63cf87 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -1,2 +1,5 @@ export { useControllableState } from './useControllableState'; export { useMounted } from './useMounted'; +export { useToggle } from './useToggle'; +export { useModal } from './useModal'; +export { useSafeContext } from './useSafeContext'; diff --git a/src/hooks/useSafeContext.ts b/src/hooks/useSafeContext.ts new file mode 100644 index 0000000..a3e6e4b --- /dev/null +++ b/src/hooks/useSafeContext.ts @@ -0,0 +1,11 @@ +import { type Context, useContext } from 'react'; + +export function useSafeContext(context: Context) { + const _context = useContext(context); + + if (!_context) { + throw new Error('Context는 반드시 Provider로 감싸져야 합니다.'); + } + + return _context; +} diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 9af623c..034a1ae 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,15 +1,27 @@ +import type { NextPage } from 'next'; import type { AppProps } from 'next/app'; +import type { ReactElement, ReactNode } from 'react'; import { Hydrate, QueryProvider } from '@/providers'; import 'bootstrap/dist/css/bootstrap.min.css'; import { montserrat, pretendard } from '@/styles/fonts'; import '@/styles/globals.css'; -export default function App({ Component, pageProps }: AppProps) { +export type NextPageWithLayout

    = NextPage & { + getLayout?: (page: ReactElement) => ReactNode; +}; + +type AppPropsWithLayout = AppProps & { + Component: NextPageWithLayout; +}; + +export default function App({ Component, pageProps }: AppPropsWithLayout) { + const getLayout = Component.getLayout || ((page) => page); + return (

    - + {getLayout()}
    diff --git a/src/pages/user/[id]/index.tsx b/src/pages/user/[id]/index.tsx index b277996..fc9b16f 100644 --- a/src/pages/user/[id]/index.tsx +++ b/src/pages/user/[id]/index.tsx @@ -1,14 +1,44 @@ -import { Avatar, Button, Icon } from '@/components/shared'; +import { type ReactElement, useState } from 'react'; +import { useSafeContext } from '@/hooks'; +import type { NextPageWithLayout } from '@/pages/_app'; +import { ModalContext, ModalProvider } from '@/providers'; +import { Avatar, Button, Icon, Tooltip } from '@/components/shared'; import { Drawer } from '@/components/shared/Drawer'; -import { CameraRoll } from '@/components/user/CameraRoll'; -import { useToggle } from '@/hooks/useToggle'; +import { AddMenu } from '@/components/user'; +import { CameraRoll, FilmAddModal, FilmSelectModal, FilmTitleModal, ProfileModal } from '@/components/user'; -export default function User() { +export interface Profile { + profileImage: string; + nickname: string; + description: string; +} + +const User: NextPageWithLayout = () => { + const { status, dispatch } = useSafeContext(ModalContext); const { - status: isDrawerOpen, - setOn: openDrawer, - setOff: closeDrawer, - } = useToggle(); + isDrawerOpen, + isAddMenuOpen, + isProfileModalOpen, + isFilmAddModalOpen, + isFilmSelectModalOpen, + isFilmTitleModalOpen, + } = status; + const [editingTitle, setEditingTitle] = useState(''); + const [userInfo, setUserInfo] = useState({ + profileImage: '/images/profile.png', + nickname: '', + description: '', + }); + + const handleEditTitle = (title: string) => { + setEditingTitle(title); + dispatch({ type: 'OPEN_FILM_TITLE_MODAL' }); + }; + + const handleEditProfile = (info: Profile) => { + setUserInfo(info); + dispatch({ type: 'OPEN_PROFILE_MODAL' }); + }; return (
    @@ -17,27 +47,60 @@ export default function User() { nickname='Jichoi' displayMeta className='tw-mx-5' + onEditProfile={handleEditProfile} /> {/* {TODO: 방명록 기능 추가할 때 변경} */}
    방명록 기능이 추가될 공간입니다 ㅎ
    - - - + + +
    - - + dispatch({ type: 'OPEN_DRAWER' })} + className='tw-cursor-pointer' + width={32} + height={32} + /> + + dispatch({ type: 'CLOSE_PROFILE_MODAL' })} + /> + dispatch({ type: 'CLOSE_FILM_TITLE_MODAL' })} /> - + dispatch({ type: 'CLOSE_FILM_ADD_MODAL' })} /> + dispatch({ type: 'CLOSE_FILM_SELECT_MODAL' })} /> + dispatch({ type: 'CLOSE_ADD_MENU' })} + onAddFilm={() => dispatch({ type: 'OPEN_FILM_ADD_MODAL' })} + onUploadPhoto={() => dispatch({ type: 'OPEN_FILM_SELECT_MODAL' })} + /> + dispatch({ type: 'CLOSE_DRAWER' })} />
    ); -} +}; + +User.getLayout = function getLayout(page: ReactElement) { + return {page}; +}; + +export default User; diff --git a/src/pages/user/[id]/item/add/index.tsx b/src/pages/user/[id]/item/add/index.tsx index 542c7dd..b947377 100644 --- a/src/pages/user/[id]/item/add/index.tsx +++ b/src/pages/user/[id]/item/add/index.tsx @@ -1,17 +1,12 @@ import { useRouter } from 'next/router'; import { useRef, useState } from 'react'; import { convertImageToBase64 } from '@/utils'; -import { - Icon, - ImageFrame, - Input, - TextButton, - Textarea, -} from '@/components/shared'; +import { Icon, ImageFrame, Input, TextButton, Textarea } from '@/components/shared'; +import { isString } from '@/utils/type-util'; export default function AddPage() { const router = useRouter(); - const { id } = router.query; + const { id, title } = router.query; const inputRef = useRef(null); const [image, setImage] = useState(null); @@ -70,7 +65,7 @@ export default function AddPage() { {/** 본문 입력 영역 */}
    - +