From 4f9d9269f5ede4706c49b110661cbf7d9e2baa64 Mon Sep 17 00:00:00 2001 From: jhynsoo Date: Mon, 13 Nov 2023 19:52:04 +0900 Subject: [PATCH] feat: Add image upload feature --- src/API.js | 1 + src/App.jsx | 8 ++- src/assets/phone.svg | 2 +- src/components/CheckBox/index.jsx | 5 +- src/components/ItemIssues/index.jsx | 76 ++++++++++++++++++++ src/hooks/network/post.js | 16 ++++- src/pages/PostDetail/index.jsx | 7 ++ src/pages/WriteSteps/ItemIssuesStep.jsx | 32 +++++++++ src/pages/WriteSteps/PhotoStep.jsx | 8 ++- src/pages/WriteSteps/PostCreateStep.jsx | 50 ++++++++++++++ src/pages/WriteSteps/PriceStep.jsx | 4 +- src/pages/WriteSteps/StatusStep.jsx | 88 ------------------------ src/pages/WriteSteps/WaitingCreation.jsx | 14 ++++ src/pages/WriteSteps/index.jsx | 49 +++++++++---- src/styles/ItemIssues.styles.js | 11 +++ 15 files changed, 260 insertions(+), 111 deletions(-) create mode 100644 src/components/ItemIssues/index.jsx create mode 100644 src/pages/WriteSteps/ItemIssuesStep.jsx create mode 100644 src/pages/WriteSteps/PostCreateStep.jsx delete mode 100644 src/pages/WriteSteps/StatusStep.jsx create mode 100644 src/pages/WriteSteps/WaitingCreation.jsx create mode 100644 src/styles/ItemIssues.styles.js diff --git a/src/API.js b/src/API.js index 6c1e3f2..6fd7b63 100644 --- a/src/API.js +++ b/src/API.js @@ -31,6 +31,7 @@ export const URL = { myPage: '/user/mypage/', myPost: '/mypost/', password: '/user/password/', + deleteAccount: '/user/deleteMessage/', user: '/user/', brand: '/brand/', product: '/product/', diff --git a/src/App.jsx b/src/App.jsx index d0f62b9..a573d8a 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -5,7 +5,13 @@ import { ThemeProvider } from 'styled-components'; import theme from './styles/Themes.styles'; import { OverlayProvider } from '@toss/use-overlay'; -const queryClient = new QueryClient(); +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + suspense: true, + }, + }, +}); const App = () => { return ( diff --git a/src/assets/phone.svg b/src/assets/phone.svg index b6a4b8a..cbd3230 100644 --- a/src/assets/phone.svg +++ b/src/assets/phone.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/src/components/CheckBox/index.jsx b/src/components/CheckBox/index.jsx index c02e10f..2096095 100644 --- a/src/components/CheckBox/index.jsx +++ b/src/components/CheckBox/index.jsx @@ -1,6 +1,6 @@ import * as S from '../../styles/CheckBox.styles'; -function CheckBox({ name, id, text, checked, setChecked }) { +function CheckBox({ name, id, text, checked, setChecked, readOnly = false }) { return ( setChecked(!checked)} + disabled={readOnly} + onChange={(e) => setChecked(e.target.checked)} /> {name} diff --git a/src/components/ItemIssues/index.jsx b/src/components/ItemIssues/index.jsx new file mode 100644 index 0000000..8415f7f --- /dev/null +++ b/src/components/ItemIssues/index.jsx @@ -0,0 +1,76 @@ +import { useCallback } from 'react'; +import * as S from '../../styles/ItemIssues.styles'; +import CheckBox from '../CheckBox'; + +function ItemIssues({ readOnly, itemIssues, setItemIssues }) { + const setChecked = useCallback( + (key) => + readOnly + ? () => {} + : (value) => setItemIssues((prev) => ({ ...prev, [key]: value })), + [readOnly, setItemIssues] + ); + + return ( + + + + + + + + + + ); +} + +export default ItemIssues; diff --git a/src/hooks/network/post.js b/src/hooks/network/post.js index 935bd4c..b9d3c68 100644 --- a/src/hooks/network/post.js +++ b/src/hooks/network/post.js @@ -1,5 +1,5 @@ import { API, URL } from '../../API'; -import { useQuery } from 'react-query'; +import { useMutation, useQuery } from 'react-query'; const getPosts = async ({ my, page }) => { const pageString = page ? `?page=${page}` : ''; @@ -30,3 +30,17 @@ export const usePost = (id) => { return data; }; + +const addPost = async (data) => { + const { data: res } = await API.post(URL.post, data, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); + return res; +}; + +export const useAddPost = (formData) => { + const data = useMutation(() => addPost(formData)); + return data; +}; diff --git a/src/pages/PostDetail/index.jsx b/src/pages/PostDetail/index.jsx index a98a805..007f794 100644 --- a/src/pages/PostDetail/index.jsx +++ b/src/pages/PostDetail/index.jsx @@ -14,6 +14,8 @@ import Button from '../../components/Button'; import { useOverlay } from '@toss/use-overlay'; import { Suspense } from 'react'; import CustomDialog from '../../components/CustomDialog'; +import ItemIssues from '../../components/ItemIssues'; +import Spacer from '../../components/Spacer'; const PostDetail = () => { const overlay = useOverlay(); @@ -45,12 +47,17 @@ const PostDetail = () => { {data.text} + 제품 사진 image.image)} /> + 가격 {data.price.toLocaleString()}원 diff --git a/src/pages/WriteSteps/ItemIssuesStep.jsx b/src/pages/WriteSteps/ItemIssuesStep.jsx new file mode 100644 index 0000000..2db9ab3 --- /dev/null +++ b/src/pages/WriteSteps/ItemIssuesStep.jsx @@ -0,0 +1,32 @@ +import Button from '../../components/Button'; +import ButtonArea from '../../components/ButtonArea'; +import ItemIssues from '../../components/ItemIssues'; +import * as S from '../../styles/WriteSteps.styles'; + +function ItemIssuesStep({ + gotoNextStep, + gotoPrevStep, + itemIssues, + setItemIssues, +}) { + return ( + + 휴대폰 상태에 대해 알려주세요 + + + + + + + ); +} + +export default ItemIssuesStep; diff --git a/src/pages/WriteSteps/PhotoStep.jsx b/src/pages/WriteSteps/PhotoStep.jsx index ed773e0..e647bdb 100644 --- a/src/pages/WriteSteps/PhotoStep.jsx +++ b/src/pages/WriteSteps/PhotoStep.jsx @@ -1,3 +1,4 @@ +import { useState } from 'react'; import Button from '../../components/Button'; import ButtonArea from '../../components/ButtonArea'; import ImageInput from '../../components/ImageInput'; @@ -12,8 +13,11 @@ function PhotoStep({ gotoNextStep, gotoPrevStep, photos, setPhotos }) { Array.from(files).forEach((file) => newPhotos.push(URL.createObjectURL(file)) ); - setPhotos(newPhotos); + setPhotos(files); + setPreviewPhotos(newPhotos); }; + const [previewPhotos, setPreviewPhotos] = useState([]); + return ( 휴대폰 사진을 올려주세요 @@ -23,7 +27,7 @@ function PhotoStep({ gotoNextStep, gotoPrevStep, photos, setPhotos }) { onChange={handleChange} /> - {photos.length > 0 && } + {photos.length > 0 && } - - - - ); -} - -export default StatusStep; diff --git a/src/pages/WriteSteps/WaitingCreation.jsx b/src/pages/WriteSteps/WaitingCreation.jsx new file mode 100644 index 0000000..cba1d66 --- /dev/null +++ b/src/pages/WriteSteps/WaitingCreation.jsx @@ -0,0 +1,14 @@ +import { Center } from '../../styles/Center.styles'; +import * as S from '../../styles/WriteSteps.styles'; + +function WaitingCreation() { + return ( + +
+ 작성한 판매글을 올리는 중이에요 +
+
+ ); +} + +export default WaitingCreation; diff --git a/src/pages/WriteSteps/index.jsx b/src/pages/WriteSteps/index.jsx index 8223d88..ab584f6 100644 --- a/src/pages/WriteSteps/index.jsx +++ b/src/pages/WriteSteps/index.jsx @@ -1,18 +1,20 @@ -import { useState } from 'react'; +import { Suspense, useState } from 'react'; import ProductSelectStep from './ProductSelectStep'; -import StatusStep from './StatusStep'; +import ItemIssuesStep from './ItemIssuesStep'; import PhotoStep from './PhotoStep'; import PriceStep from './PriceStep'; import TextStep from './TextStep'; +import PostCreateStep from './PostCreateStep'; +import WaitingCreation from './WaitingCreation'; function WriteSteps() { const [formData, setFormData] = useState({ product: '', - status: { + item_issues: { display: false, frame: false, button: false, - biometrics: false, + biometric: false, camera: false, speaker: false, others: false, @@ -21,12 +23,26 @@ function WriteSteps() { photos: [], text: '', }); - const STEP_INFO = ['product', 'status', 'price', 'photo', 'text', 'step6']; + const STEP_INFO = [ + 'product', + 'itemIssues', + 'price', + 'photo', + 'text', + 'postCreate', + ]; const setProduct = (product) => { setFormData((prev) => ({ ...prev, product })); }; - const setStatus = (status) => { - setFormData((prev) => ({ ...prev, status })); + const setItemIssues = (itemIssues) => { + if (typeof itemIssues === 'function') { + setFormData((prev) => ({ + ...prev, + item_issues: itemIssues(prev.item_issues), + })); + return; + } + setFormData((prev) => ({ ...prev, itemIssues })); }; const setPrice = (price) => { setFormData((prev) => ({ ...prev, price })); @@ -43,15 +59,15 @@ function WriteSteps() { <> {step === 'product' && ( setStep('status')} + gotoNextStep={() => setStep('itemIssues')} product={formData.product} setProduct={setProduct} /> )} - {step === 'status' && ( - setStep('price')} gotoPrevStep={() => setStep('product')} /> @@ -62,7 +78,7 @@ function WriteSteps() { price={formData.price} setPrice={setPrice} gotoNextStep={() => setStep('photo')} - gotoPrevStep={() => setStep('status')} + gotoPrevStep={() => setStep('itemIssues')} /> )} {step === 'photo' && ( @@ -77,10 +93,15 @@ function WriteSteps() { setStep('step6')} + gotoNextStep={() => setStep('postCreate')} gotoPrevStep={() => setStep('photo')} /> )} + {step === 'postCreate' && ( + }> + + + )} ); } diff --git a/src/styles/ItemIssues.styles.js b/src/styles/ItemIssues.styles.js new file mode 100644 index 0000000..514e5b6 --- /dev/null +++ b/src/styles/ItemIssues.styles.js @@ -0,0 +1,11 @@ +import styled from 'styled-components'; + +export const Grid = styled.div` + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 20px; + + @media screen and (max-width: 768px) { + grid-template-columns: repeat(1, 1fr); + } +`;