diff --git a/components/common/Header.tsx b/components/common/Header.tsx index b8e578c..4c7a41f 100644 --- a/components/common/Header.tsx +++ b/components/common/Header.tsx @@ -43,7 +43,9 @@ export default function Header() { onChange={handleInputValue} /> - + + + diff --git a/components/common/WriteHeader.tsx b/components/common/WriteHeader.tsx index bf750b6..d1064cb 100644 --- a/components/common/WriteHeader.tsx +++ b/components/common/WriteHeader.tsx @@ -2,14 +2,17 @@ import styled from '@emotion/styled'; import { IcWriteHeaderLogo } from '../../public/assets/icons'; import Link from 'next/link'; import { useRecoilState } from 'recoil'; -import { newPostInfoState } from '../../core/atom'; -import { postCommunity } from '../../core/api/community'; +import { isChangeInfoState, newPostInfoState } from '../../core/atom'; +import { postCommunity, putCommunity } from '../../core/api/community'; import { useRouter } from 'next/router'; +import { PutCommunityBody } from '../../types/community'; export default function WriteHeader() { const [newPostInfo, setNewPostInfo] = useRecoilState(newPostInfoState); + const [isChangeCommunity, setIsChangeCommunity] = + useRecoilState(isChangeInfoState); const router = useRouter(); - const { pathname } = useRouter(); + const { pathname, query } = useRouter(); const handleRegister = async () => { const { title, content } = newPostInfo; @@ -27,6 +30,51 @@ export default function WriteHeader() { router.push(`/community/${data.id}`); }; + const handleCancel = () => { + const val = confirm( + '수정을 취소하시겠습니까? 작성하던 내용은 모두 삭제됩니다.', + ); + + if (val) { + router.push(`/community/${query.cid}`); + } + }; + + const handleUpdate = async () => { + const { category, title, content, imageList } = newPostInfo; + const { + isChangeCategory, + isChangeTitle, + isChangeContent, + isChangeImageList, + } = isChangeCommunity; + const updatePostInfo: PutCommunityBody = {}; + + if (isChangeCategory) updatePostInfo.category = category; + if (isChangeTitle) updatePostInfo.title = title; + if (isChangeContent) updatePostInfo.content = content; + if (isChangeImageList) updatePostInfo.imageList = imageList; + + if (updatePostInfo.title === '' || updatePostInfo.content === '') { + alert('내용을 입력해주세요.'); + return; + } + + const data = await putCommunity(String(query.cid), updatePostInfo); + setNewPostInfo({ + category: '후기', + title: '', + content: '', + }); + setIsChangeCommunity({ + isChangeCategory: false, + isChangeTitle: false, + isChangeContent: false, + isChangeImageList: false, + }); + router.push(`/community/${data.id}`); + }; + return ( @@ -34,13 +82,19 @@ export default function WriteHeader() { - {pathname === '/community' ? ( + {pathname === '/write/[cid]' ? ( - 취소 - 수정완료 + + 취소 + + + 수정완료 + ) : ( - 등록하기 + + 등록하기 + )} ); @@ -53,7 +107,7 @@ const StWriteHeaderWrapper = styled.section` position: sticky; top: -3.2em; - width: 192rem; + width: 100%; height: 11.4rem; padding-top: 4.2rem; @@ -63,12 +117,12 @@ const StWriteHeaderWrapper = styled.section` box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.08); `; -const StWriteBtn = styled.a` +const StWriteBtn = styled.a<{ isMargin: boolean }>` display: flex; justify-content: center; align-items: center; - margin-left: 11.4rem; + margin-left: ${({ isMargin }) => (isMargin ? '11.4rem' : '0rem')}; width: 10rem; height: 4.2rem; diff --git a/components/community/CommunityFloatingBtn.tsx b/components/community/CommunityFloatingBtn.tsx index 2f8d9c8..b880cbe 100644 --- a/components/community/CommunityFloatingBtn.tsx +++ b/components/community/CommunityFloatingBtn.tsx @@ -1,4 +1,3 @@ -import { css } from '@emotion/react'; import styled from '@emotion/styled'; import Link from 'next/link'; import { IcWriteBtn, IcTopBtn } from '../../public/assets/icons'; diff --git a/components/main/ToyPreview.tsx b/components/main/ToyPreview.tsx index b69b391..f4888cf 100644 --- a/components/main/ToyPreview.tsx +++ b/components/main/ToyPreview.tsx @@ -10,7 +10,6 @@ import { MainToyData } from '../../types/toy'; export default function ToyPreview(props: MainToyData) { const { image, month, price, siteName, siteUrl, title } = props; const [isMark, setIsMark] = useState(false); - console.log(image); const handleToySite = (e: React.MouseEvent) => { if (!(e.target instanceof SVGElement)) window.open(siteUrl); }; diff --git a/components/viewProduct/FilterDropdown.tsx b/components/viewProduct/FilterDropdown.tsx index 3e20528..228b63a 100644 --- a/components/viewProduct/FilterDropdown.tsx +++ b/components/viewProduct/FilterDropdown.tsx @@ -64,7 +64,7 @@ export default function FilterDropdown(props: FilterDropdownProps) { isDrop ? 'slide-fade-in-dropdown' : 'slide-fade-out-dropdown' }`} > - {categoryKey === '장난감 종류' && toyKindList.length !== 0 + {categoryKey === '종류' && toyKindList.length !== 0 ? toyKindList.map((tagText: string, elementIdx: number) => { return ( { @@ -7,6 +11,7 @@ export const useGetCollectionProduct = (key: string) => { console.log(data); return data; }; + export const getCollectionProduct = (key: string) => { return baseInstance.get(`/collection?sort=${key}`); }; @@ -27,6 +32,7 @@ export const postCommunity = async (body: PostCommunityBody) => { console.log(e); } }; + export const getCommunityDetail = async (id: string) => { try { // const { data } = await axios.get( @@ -38,6 +44,7 @@ export const getCommunityDetail = async (id: string) => { console.log(e); } }; + export const deleteCommunity = async (id: string) => { try { const { status } = await baseInstance.delete(`/board/${id}`); @@ -47,6 +54,15 @@ export const deleteCommunity = async (id: string) => { } }; +export const putCommunity = async (id: string, body: PutCommunityBody) => { + try { + const { data } = await baseInstance.put(`/board/${id}`, body); + return data; + } catch (e) { + console.log(e); + } +}; + export const postReply = async (body: PostCommentBody) => { try { const { data } = await baseInstance.post('/board/comment', body); diff --git a/core/api/user.ts b/core/api/user.ts index f7b8c82..c2ef1f1 100644 --- a/core/api/user.ts +++ b/core/api/user.ts @@ -1,10 +1,6 @@ import LocalStorage from '../localStorage'; import { baseInstance } from '../axios'; -import { - PostLoginBody, - PostSignUpBody, - ResponseLoginDto, -} from '../../types/user'; +import { PostLoginBody, SignUpBody, ResponseLoginDto } from '../../types/user'; export const loginUser = async (userLoginData: PostLoginBody) => { const data = (await baseInstance.post( @@ -12,15 +8,20 @@ export const loginUser = async (userLoginData: PostLoginBody) => { userLoginData, )) as ResponseLoginDto; - console.log(userLoginData); - if (data) { - LocalStorage.setUserSession(data.data.accessToken, data.data.refreshToken); + if (data.status === 200) { + LocalStorage.setUserSession( + data.data.data.accessToken, + data.data.data.refreshToken, + ); + return data.data.data.isSignup; } - return data.data.isSignup; }; export const getRefreshToken = () => { return baseInstance.get('/auth/refresh'); }; -export const putSignup = (signUpBody: PostSignUpBody) => { +export const postNickname = (nicknameBody: SignUpBody) => { + return baseInstance.post('/auth/nickname', nicknameBody); +}; +export const putSignup = (signUpBody: SignUpBody) => { return baseInstance.put('/auth/signup', signUpBody); }; diff --git a/core/atom.ts b/core/atom.ts index 6bb3cfd..62d5990 100644 --- a/core/atom.ts +++ b/core/atom.ts @@ -1,9 +1,8 @@ -import { atom, selector, selectorFamily } from 'recoil'; +import { atom } from 'recoil'; import { recoilPersist } from 'recoil-persist'; //페이지가 변경되더라도 상태관리를 유지 -import { PostCommunityBody } from '../types/community'; -import { PostLoginBody, UserData } from '../types/user'; -import { FilterDropdownProps, FilterTagProps } from '../types/viewProduct'; - +import { PostCommunityBody, IsChangeCommunity } from '../types/community'; +import { PostLoginBody } from '../types/user'; +import { FilterTagProps } from '../types/viewProduct'; const { persistAtom } = recoilPersist(); @@ -18,7 +17,6 @@ export const userInfoState = atom({ effects_UNSTABLE: [persistAtom], }); - export const newPostInfoState = atom({ key: 'newPostInfo', default: { @@ -29,34 +27,22 @@ export const newPostInfoState = atom({ effects_UNSTABLE: [persistAtom], }); +export const isChangeInfoState = atom({ + key: 'isChangeInfo', + default: { + isChangeCategory: false, + isChangeTitle: false, + isChangeContent: false, + isChangeImageList: false, + }, + effects_UNSTABLE: [persistAtom], +}); + export const filterListState = atom({ key: 'filterListState', default: { filterList: { - 스토어: [ - '국민장난감', - '그린키드', - '러브로', - '리틀베이비', - '빌리바바', - '어텐션홈이벤트', - '장난감점빵', - '젤리바운스', - '해피장난감', - ], - '사용 연령': [ - '0~5개월', - '6~11개월', - '12~17개월', - '18~23개월', - '24~35개월', - '36~47개월', - '48~60개월', - '기타', - ], - 가격: ['1만원 미만', '1-3만원', '3-5만원', '5-8만원', '8만원이상'], - 특성: ['타고 노는', '만지고 노는', '기타'], - '장난감 종류': [ + 종류: [ '아기체육관', '모빌', '바운서', @@ -78,9 +64,33 @@ export const filterListState = atom({ '역할놀이', '기타', ], + '사용 연령': [ + '0~5개월', + '6~11개월', + '12~17개월', + '18~23개월', + '24~35개월', + '36~47개월', + '48~60개월', + '기타', + ], + 가격: ['1만원 미만', '1-3만원', '3-5만원', '5-8만원', '8만원이상'], + 특성: ['타고 노는', '만지고 노는', '기타'], + 스토어: [ + '국민장난감', + '그린키드', + '러브로', + '리틀베이비', + '빌리바바', + '어텐션홈이벤트', + '장난감점빵', + '젤리바운스', + '해피장난감', + ], }, }, }); + export const filterTagState = atom({ key: 'filterTagState', default: [], @@ -96,6 +106,7 @@ export const checkedItemsState = atom[]>({ new Set(), ], }); + export const toyKindState = atom({ key: 'toyKindState', default: [], diff --git a/core/axios.ts b/core/axios.ts index 7258cc1..562175f 100644 --- a/core/axios.ts +++ b/core/axios.ts @@ -12,8 +12,10 @@ const baseInstance = axios.create({ baseInstance.interceptors.request.use((config) => { const headers = { ...config.headers, - accessToken: LocalStorage.getItem('accessToken'), - refreshToken: LocalStorage.getItem('refreshToken'), + accessToken: + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MzAsImlhdCI6MTY1ODM4NDEwMCwiZXhwIjoxNjU4MzkxMzAwLCJpc3MiOiJub3JpIn0.ZNRxtGPFJpVCHJzlK0HdcgPcZWvkkCt3FZ_VHu8sz7M', + refreshToken: + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MzAsImlhdCI6MTY1ODM4NDEwMCwiZXhwIjoxNjU5NTkzNzAwLCJpc3MiOiJub3JpIn0.AeuhSmM1ZqItojeM3O0SwrELog-Qfq91r_ii0EMgPig', }; return { ...config, headers }; diff --git a/core/localStorage.ts b/core/localStorage.ts index 9d6d098..10287ef 100644 --- a/core/localStorage.ts +++ b/core/localStorage.ts @@ -2,23 +2,15 @@ class LocalStorage { constructor() {} static setItem(key: string, item: string) { - if (typeof window !== 'undefined') { - localStorage.setItem(key, item); - } + localStorage.setItem(key, item); } static getItem(key: string) { - if (typeof window !== 'undefined') { - return localStorage.getItem(key) as string; - } - - return ''; + return localStorage.getItem(key) as string; } static removeItem(key: string) { - if (typeof window !== 'undefined') { - localStorage.removeItem(key); - } + localStorage.removeItem(key); } static setUserSession(accessToken: string, refreshToken: string) { diff --git a/mocks/handlers/community.ts b/mocks/handlers/community.ts index db40615..3f6588d 100644 --- a/mocks/handlers/community.ts +++ b/mocks/handlers/community.ts @@ -4,6 +4,7 @@ import { CommunityData, PostCommentBody, PostCommunityBody, + PutCommunityBody, } from '../../types/community'; // 커뮤니티 게시글 작성 export const postCommunity = rest.post('/board', (req, res, ctx) => { @@ -38,9 +39,19 @@ export const postCommunity = rest.post('/board', (req, res, ctx) => { }), ); }); + // 커뮤니티 게시글 수정 export const putCommunity = rest.put('/board/:boardId', (req, res, ctx) => { + const { category, title, content, imageList } = req.body as PutCommunityBody; const { boardId } = req.params; + + return res( + ctx.status(200), + ctx.delay(500), + ctx.json({ + id: boardId, + }), + ); }); // 커뮤니티 게시글 상세조회 export const getCommunityDetail = rest.get( diff --git a/pages/_app.tsx b/pages/_app.tsx index c9af2a9..e9f492e 100755 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -18,7 +18,7 @@ export default function App({ - {pathname === '/write' ? ( + {pathname.includes('/write') ? ( ) : ( pathname !== '/login' && pathname !== '/signup' &&
diff --git a/pages/community/index.tsx b/pages/community/index.tsx index aee5f4d..11d12b5 100644 --- a/pages/community/index.tsx +++ b/pages/community/index.tsx @@ -38,31 +38,6 @@ export default function community({ setCategory(value); }; - // const data = useGetCommunityList(); - // console.log(data); - - // const handleCurrentPage = (nextPage: number) => { - // setCurrentPage(nextPage); - // }; - // let { communityList, isLoading, isError } = - // useGetCommunityList() as GetCommunityList; - - // useEffect(() => { - // if (communityList) { - // let data = communityList as CommunityData[]; - // data = data.filter( - // (_, idx) => (currentPage - 1) * 10 <= idx && idx < currentPage * 10, - // ); - // setContentList(data); - // console.log(data); - // window.scrollTo({ - // top: 0, - // behavior: 'smooth', - // }); - // } - // }, [contentList, currentPage]); - // console.log(contentList); - useEffect(() => { if (data) { data = data.filter( diff --git a/pages/login.tsx b/pages/login.tsx index 468cd1b..44630a0 100644 --- a/pages/login.tsx +++ b/pages/login.tsx @@ -3,10 +3,10 @@ import { signIn, getSession } from 'next-auth/react'; import Link from 'next/link'; import { loginUser } from '../core/api/user'; import { PostLoginBody } from '../types/user'; -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import LocalStorage from '../core/localStorage'; import Router from 'next/router'; -import { useRecoilState } from 'recoil'; +import { useRecoilState, useResetRecoilState } from 'recoil'; import { GetServerSideProps, InferGetServerSidePropsType } from 'next'; import { IcLoginNori, @@ -20,20 +20,26 @@ export default function login({ data, }: InferGetServerSidePropsType) { const [userInfo, setUserInfo] = useRecoilState(userInfoState); + useResetRecoilState(userInfoState); + useEffect(() => { + const handleLogin = async () => { + if (data.session?.user) { + const userLoginData = { + snsId: data.session?.user.email, + provider: userInfo.provider, + email: data.session?.user.email, + } as PostLoginBody; - const handleLogin = async (social: string) => { - if (data.session.user) { - const userLoginData = { - snsId: data.session?.user.email, - provider: social, - email: data.session?.user.email, - } as PostLoginBody; - const login = await loginUser(userLoginData); - if (login) { - setUserInfo(userLoginData); + const login = await loginUser(userLoginData); + console.log(login); + if (!login) { + setUserInfo(userLoginData); + } } - } - }; + }; + if (userInfo.provider !== '') handleLogin(); + }, [data.session]); + useEffect(() => { if (!userInfo.isSignup && LocalStorage.getItem('accessToken')) Router.push('/signup'); @@ -59,8 +65,8 @@ export default function login({ { - handleLogin('kakao'); signIn('kakao'); + setUserInfo({ provider: 'kakao' }); }} /> @@ -68,14 +74,14 @@ export default function login({ style={{ marginTop: '1.1rem' }} onClick={() => { signIn('google'); - handleLogin('google'); + setUserInfo({ provider: 'google' }); }} /> { signIn('naver'); - handleLogin('naver'); + setUserInfo({ provider: 'naver' }); }} /> diff --git a/pages/signup.tsx b/pages/signup.tsx index f2b0b78..abca3fd 100644 --- a/pages/signup.tsx +++ b/pages/signup.tsx @@ -2,7 +2,9 @@ import styled from '@emotion/styled'; import Router from 'next/router'; import { useState, useEffect, useRef } from 'react'; -import { putSignup } from '../core/api/user'; +import { useRecoilState } from 'recoil'; +import { postNickname, putSignup } from '../core/api/user'; +import { userInfoState } from '../core/atom'; import { IcSignupCheckboxSelected, IcSignupCheckboxUnselected, @@ -18,6 +20,7 @@ export default function signup() { ); const [isNickname, setIsNickname] = useState(true); const signupBtnRef = useRef(null); + const [userInfo, setUserInfo] = useRecoilState(userInfoState); useEffect(() => { if (signupBtnRef.current) @@ -46,9 +49,9 @@ export default function signup() { '한, 영, 숫자 조합만 가능합니다. 2글자 이상 10글자 이하로 입력해주세요.', ); } else { - const data = await putSignup({ nickname: nickName }); + const data = await postNickname({ nickname: nickName }); if (data.status === 409) setNotice('사용중인 닉네임입니다'); - else if (data.status === 200) { + else if (data.status === 201) { setNotice('사용가능한 닉네임입니다'); setIsNickname(true); } @@ -56,6 +59,7 @@ export default function signup() { }; const handleSignupBtn = () => { if (isNickname && isChecked) { + putSignup({ nickname: nickName }); Router.push('/'); } }; @@ -148,7 +152,9 @@ const StNoticeSpan = styled.span<{ isNickname: boolean; notice: string }>` color: ${({ isNickname, notice, theme: { colors } }) => !isNickname && notice !== '사용중인 닉네임입니다' && - notice !== '사용가능한 닉네임입니다' + notice !== '사용가능한 닉네임입니다' && + notice === + '한, 영, 숫자 조합만 가능합니다. 2글자 이상 10글자 이하로 입력해주세요.' ? 'red' : notice === '사용중인 닉네임입니다' ? colors.orange @@ -187,6 +193,7 @@ const StSignupBtn = styled.button` border: 0.1rem solid ${({ theme }) => theme.colors.gray005}; border-radius: 0.5rem; + background: ${({ theme }) => theme.colors.gray004}; color: ${({ theme }) => theme.colors.white}; ${({ theme }) => theme.fonts.b2_18_medium_130}; diff --git a/pages/write/[cid].tsx b/pages/write/[cid].tsx index 77caf6c..97a56b7 100644 --- a/pages/write/[cid].tsx +++ b/pages/write/[cid].tsx @@ -4,7 +4,7 @@ import { ParsedUrlQuery } from 'querystring'; import React, { useEffect, useRef, useState } from 'react'; import { useRecoilState } from 'recoil'; import { getCommunityDetail } from '../../core/api/community'; -import { newPostInfoState } from '../../core/atom'; +import { isChangeInfoState, newPostInfoState } from '../../core/atom'; import { IcDefaultImg, IcDelete } from '../../public/assets/icons'; import { CommunityData, ImgData } from '../../types/community'; @@ -12,6 +12,8 @@ export default function UpdateForm({ data, }: InferGetServerSidePropsType) { const [newPostInfo, setNewPostInfo] = useRecoilState(newPostInfoState); + const [isChangeCommunity, setIsChangeCommunity] = + useRecoilState(isChangeInfoState); const [isCategory, setIsCategory] = useState(false); const [images, setImages] = useState([]); const [imagesSize, setImagesSize] = useState(0); @@ -55,6 +57,10 @@ export default function UpdateForm({ const handleCategory = (value: string) => { setCategory(value); + setIsChangeCommunity({ + ...isChangeCommunity, + isChangeCategory: true, + }); setNewPostInfo({ ...newPostInfo, category: value }); }; @@ -89,6 +95,10 @@ export default function UpdateForm({ images.map((image) => formData.append(image.id + '', image.file)); imageList.map((image) => formData.append(image.id + '', image.file)); + setIsChangeCommunity({ + ...isChangeCommunity, + isChangeImageList: true, + }); setNewPostInfo({ ...newPostInfo, imageList: formData, @@ -104,6 +114,10 @@ export default function UpdateForm({ const formData = new FormData(); imgDelData.map((image) => formData.append(image.id + '', image.file)); + setIsChangeCommunity({ + ...isChangeCommunity, + isChangeImageList: true, + }); setNewPostInfo({ ...newPostInfo, imageList: formData, @@ -114,11 +128,19 @@ export default function UpdateForm({ const handleTitle = (e: React.ChangeEvent) => { if (e.target.value.length > 30) return; setTitle(e.target.value); + setIsChangeCommunity({ + ...isChangeCommunity, + isChangeTitle: true, + }); setNewPostInfo({ ...newPostInfo, title: e.target.value }); }; const handleContent = (e: React.ChangeEvent) => { setContent(e.target.value); + setIsChangeCommunity({ + ...isChangeCommunity, + isChangeContent: true, + }); setNewPostInfo({ ...newPostInfo, content: e.target.value }); }; diff --git a/types/community.ts b/types/community.ts index 147231d..9be6327 100644 --- a/types/community.ts +++ b/types/community.ts @@ -26,12 +26,25 @@ export interface PostCommunityBody { content: string; imageList?: FormData; } +// 커뮤니티 수정 put body +export interface PutCommunityBody { + category?: string; + title?: string; + content?: string; + imageList?: FormData; +} +// 커뮤니티 변경된 state 판단 +export interface IsChangeCommunity { + isChangeCategory: boolean; + isChangeTitle: boolean; + isChangeContent: boolean; + isChangeImageList: boolean; +} // 커뮤니티 댓글 export interface PostCommentBody { boardId?: string; content: string; } - export interface GetCommunityList { communityList: CommunityData[]; isLoading: boolean; diff --git a/types/user.ts b/types/user.ts index b5fa2d5..e85c383 100644 --- a/types/user.ts +++ b/types/user.ts @@ -6,20 +6,23 @@ export interface UserData { email: string; } -export interface PostSignUpBody { +export interface SignUpBody { nickname: string; } export interface PostLoginBody { - snsId: string; + snsId?: string; provider: string; - email: string; + email?: string; isSignup?: boolean; } export interface ResponseLoginDto { + status: number; data: { - accessToken: string; - refreshToken: string; - isSignup: boolean; + data: { + accessToken: string; + refreshToken: string; + isSignup: boolean; + }; }; } diff --git a/utils/check.ts b/utils/check.ts index 5cf9dbf..3d90fcc 100644 --- a/utils/check.ts +++ b/utils/check.ts @@ -1,4 +1,4 @@ export function checkNickname(value: string) { - const regex = /^[ㄱ-ㅎ|가-힣|a-z|A-Z|0-9|]{2,10}$/; + const regex = /^[가-힣|a-z|A-Z|0-9|]{2,10}$/; return regex.test(value); }