-
Notifications
You must be signed in to change notification settings - Fork 31
[송미진] sprint 9,10 #160
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
The head ref may contain hidden characters: "Next-\uC1A1\uBBF8\uC9C4"
[송미진] sprint 9,10 #160
Changes from all commits
140be26
db231fa
b1c72be
7b30661
476318f
1ca9a35
f70a8b3
20f0882
60ee308
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| 'use client'; | ||
| import Container from '@/components/layout/Container'; | ||
| import Button from '@/components/ui/Button'; | ||
| import ConfirmModal from '@/components/ui/ConfirmModal'; | ||
| import FormField from '@/components/ui/form/FormField'; | ||
| import ImageFileBox from '@/components/ui/form/ImageFileBox'; | ||
| import { TextAreaField } from '@/components/ui/form/InputBox'; | ||
| import Title from '@/components/ui/Title'; | ||
| import { ArticleCreateRequest, usePostArticles } from '@/hooks/useArticles'; | ||
| import { useConfirmModal } from '@/hooks/useModal'; | ||
| import { validationRules } from '@/utils/validate'; | ||
| import { useRouter } from 'next/navigation'; | ||
| import React, { useState } from 'react'; | ||
| import { SubmitHandler, useForm } from 'react-hook-form'; | ||
|
|
||
|
|
||
| const INITIAL_Article: ArticleCreateRequest = { | ||
| image: "", | ||
| content: "", | ||
| title: "" | ||
| } | ||
| type FormValues = { | ||
| title: string; | ||
| content: string; | ||
| image: string; | ||
| }; | ||
| function PostArticles() { | ||
| const router = useRouter(); | ||
| const { isConfirmOpen, confirmMessage, openConfirmModal, closeConfirmModal } = useConfirmModal(); | ||
|
|
||
| const { | ||
| register, | ||
| handleSubmit, | ||
| getValues, | ||
| formState: { errors, isValid, isDirty }, | ||
| } = useForm<FormValues>({ | ||
| mode: 'onBlur', | ||
| }); | ||
|
|
||
| const [addArticles, setAddArticles] = useState<ArticleCreateRequest>(INITIAL_Article); | ||
|
|
||
| const { mutate: postArticles} = usePostArticles(openConfirmModal,router); | ||
|
|
||
| const handleFieldBlur = () => { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 위와 마찬가지로 불필요한 함수입니다! |
||
| const values = getValues(); // 모든 필드 값 가져오기 | ||
| setAddArticles((prev) => { | ||
| const updated = { ...prev, ...values }; | ||
| return updated; | ||
| }); | ||
| }; | ||
|
|
||
| const onSubmit: SubmitHandler<FormValues> = (data) => { | ||
| setAddArticles((prev) => ({ | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 애초에 불필요한 코드이기도 하지만, 다시 생각해 보시면 좋은 것은, setState는 비동기적으로 동작하죠! 아래에서 실행되는 |
||
| ...prev, | ||
| ...addArticles, // form에서 온 title, content 등 | ||
| })); | ||
| postArticles(addArticles); | ||
| }; | ||
|
|
||
| function handleInputBlur(e: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>){ | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 부분도 지워도 될 거 같네요 :) |
||
| const value = e.target.value; | ||
| setAddArticles((prev) => ({ | ||
| ...prev, | ||
| [e.target.id]: value | ||
| })); | ||
| } | ||
|
|
||
| return ( | ||
| <Container className="relative mb-[130px]"> | ||
| <Title titleTag='h1' text='게시물 쓰기'> | ||
| </Title> | ||
| <form className='flex flex-col gap-6' onSubmit={handleSubmit(onSubmit)}> | ||
| <Button | ||
| type="submit" | ||
| variant="roundedSS" | ||
| className="!absolute top-0 right-0" | ||
| disabled = { !isValid || !isDirty || !addArticles.content || !addArticles.image || !addArticles.title } | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. required 옵션, validation을 잘 넣어주셨다면 !isValid 로도 충분할 거 같네요! :) |
||
| >등록</Button> | ||
| <FormField | ||
| id="title" | ||
| label="*제목" | ||
| type="text" | ||
| placeholder="제목을 입력해주세요" | ||
| error={errors.title?.message} | ||
| {...register('title', { | ||
| ...validationRules.title, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 유효성 검사를 하실때 react-hook-form/tpyescript 와 zod 궁합이 좋습니다~! 한 번 써보셔도 좋을 거 같아요 :) |
||
| onBlur: handleFieldBlur, | ||
| })} | ||
| /> | ||
| <TextAreaField id='content' label='내용' height='282px' placeholder='내용를 입력해주세요' onBlur={handleInputBlur} /> | ||
| <ImageFileBox<ArticleCreateRequest> setForm={setAddArticles} /> | ||
| </form> | ||
| <ConfirmModal isOpen={isConfirmOpen} onClose={closeConfirmModal} errorMessage={confirmMessage} /> | ||
| </Container> | ||
| ); | ||
| } | ||
|
|
||
| export default PostArticles; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| 'use client'; | ||
|
|
||
| import CommentSection from '@/components/Article/comment/CommentSection'; | ||
| import Container from '@/components/layout/Container'; | ||
| import ConfirmModal from '@/components/ui/ConfirmModal'; | ||
| import LikeButton from '@/components/ui/LikeButton'; | ||
| import UserInfo from '@/components/ui/UserInfo'; | ||
| import { useArticleDetails, useToggleArticlesFavorite } from '@/hooks/useArticles'; | ||
| import { useConfirmModal } from '@/hooks/useModal'; | ||
| import { formatDate } from '@/utils/date'; | ||
| import { useParams } from 'next/navigation'; | ||
| import React from 'react'; | ||
|
|
||
| function PostDetail() { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. next를 좀 더 적극적으로 써보시면 좋을 거 같아요! 특히나 상세 페이지의 경우 정적 페이지로 만들기가 좋죠!! |
||
|
|
||
| const { id } = useParams(); // URL에서 [id] 추출 | ||
| const articleId = Number(id); | ||
|
|
||
| const { isConfirmOpen, confirmMessage, openConfirmModal, closeConfirmModal } = useConfirmModal(); | ||
| const { mutate: toggleFavorite } = useToggleArticlesFavorite(openConfirmModal, { | ||
| onSuccess: (data) => { | ||
| openConfirmModal(data.isFavorited ? "관심상품 등록되었습니다" : "관심상품 취소되었습니다"); | ||
| }, | ||
| }); | ||
| const { data } = useArticleDetails(articleId); | ||
| if ( data === undefined) return; | ||
|
|
||
| const createdAtString = formatDate( data.createdAt ); | ||
|
|
||
| return ( | ||
| <div> | ||
| <div className='flex flex-col mt-[32px]'> | ||
| <Container className='flex flex-col w-full gap-6 '> | ||
| { data && ( | ||
| <> | ||
| <div className="flex flex-col gap-4 border-b border-b-secondary-200 pb-4"> | ||
| <strong className="text-xl font-bold">{data.title}</strong> | ||
| <div className="flex flex-row gap-6 items-center"> | ||
| <UserInfo ownerNickname={data.writer.nickname} createdAtString={createdAtString} width={40} className="!text-[18px]" childrenClassName=" !flex-row"/> | ||
| <span className='w-[1px] h-[34px] bg-secondary-200'></span> | ||
| <LikeButton | ||
| id={data.id} | ||
| favoriteCount={data.likeCount} | ||
| toggleFavorite={toggleFavorite} | ||
| isFavorite={false} | ||
| className="border rounded-full border-secondary-200 py-[7px] px-[12px]" | ||
| childrenClassName='gap-0 text-[16px]' width="24" height="24"/> | ||
| </div> | ||
| </div> | ||
| <div> | ||
| <span>{data.content}</span> | ||
| </div> | ||
| </> | ||
| )} | ||
| </Container> | ||
| <Container className="mb-[151px] mt-8"> | ||
| <CommentSection articleId={articleId} /> | ||
| </Container> | ||
| <ConfirmModal isOpen={isConfirmOpen} onClose={closeConfirmModal} errorMessage={confirmMessage} /> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| export default PostDetail; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,5 @@ | ||
| import BoardsClient from "@/components/Article/BoardsClient"; | ||
|
|
||
|
|
||
|
|
||
| import React from 'react'; | ||
|
|
||
| function Boards() { | ||
| return ( | ||
| <> | ||
| <h2>Boards</h2> | ||
| </> | ||
| ); | ||
| } | ||
|
|
||
| export default Boards; | ||
| export default function BoardsPage() { | ||
| return <BoardsClient />; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| 'use client'; | ||
| import React from 'react'; | ||
|
|
||
| function Faq() { | ||
|
|
||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,23 +1,5 @@ | ||
| 'use client'; | ||
| import ProductClient from "@/components/Product/ProductClient"; | ||
|
|
||
| import React from 'react'; | ||
| import Container from 'components/layout/Container'; | ||
| import Title from 'components/ui/Title'; | ||
| import { BestItems } from '@/components/Product/BestItems'; | ||
| import { AllItems } from '@/components/Product/AllItems'; | ||
|
|
||
|
|
||
| function ItemsBox() { | ||
|
|
||
| return ( | ||
| <> | ||
| <Container> | ||
| <Title titleTag='h1' text='베스트 상품' /> | ||
| </Container> | ||
| <BestItems /> | ||
| <AllItems /> | ||
| </> | ||
| ); | ||
| } | ||
|
|
||
| export default ItemsBox; | ||
| export default function BoardsPage() { | ||
| return <ProductClient />; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이미 react-hook-form 을 사용중이라 중복해서 상태를 다시 정의할 필요 없습니다! 🤔
우리가 react-hook-form 사용하는 이유중 하나는 uncontrolled로 form을 관리하기 때문입니다. 직접 상태를 관리하실 필요 없는거죠!
controlled/uncontrolled 컴포넌트 개념을 좀 더 알아보셔도 좋습니다 :)