diff --git a/README.md b/README.md index 1e5a46b..52b957f 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,132 @@ -# React + TypeScript + Vite +# [๐Ÿ’Œ WOOGYEOL: ์šฐ๋ฆฌ ๊ฒฐํ˜ผํ•ด์š”](https://woogyeol.site/) -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. +> ๊ฐœ๋ฐœ ๊ธฐ๊ฐ„: 2025.01 ~ 2025.07 -Currently, two official plugins are available: +"์šฐ๋ฆฌ ๊ฒฐํ˜ผํ•ด์š”"๋Š” ๐Ÿ’Œ ์‹ ๋ž‘ ์‹ ๋ถ€๊ฐ€ ์ง์ ‘ ๊พธ๋ฏธ๋Š” ๋ชจ๋ฐ”์ผ ์ฒญ์ฒฉ์žฅ์„ ์†์‰ฝ๊ฒŒ ์ œ์ž‘ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋•๋Š” ํ”Œ๋žซํผ์ž…๋‹ˆ๋‹ค.
+๐Ÿ“† ์ผ์ •๊ณผ ๐Ÿ“ ์œ„์น˜ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜๊ณ , ํ•˜๊ฐ์€ ๐Ÿ“Š ์ฐธ์„ ์—ฌ๋ถ€๋ฅผ ๋“ฑ๋กํ•˜๋ฉฐ, ๐Ÿ“ธ ์‹ค์‹œ๊ฐ„ ํฌํ† ์›”์— ์ถ•ํ•˜ ์‚ฌ์ง„๊ณผ ๋ฉ”์‹œ์ง€๋ฅผ ๋‚จ๊ธธ ์ˆ˜ ์žˆ์–ด ๋ชจ๋‘๊ฐ€ ํ•จ๊ป˜ ์ถ”์–ต์„ ๋งŒ๋“ค์–ด ๊ฐ‘๋‹ˆ๋‹ค.
+ํ•˜๊ฐ๊ณผ์˜ ์†Œํ†ต์„ ์ž์—ฐ์Šค๋Ÿฝ๊ณ  ๋”ฐ๋œปํ•˜๊ฒŒ ์ด์–ด์ฃผ๋Š”, ์šฐ๋ฆฌ๋งŒ์˜ ํŠน๋ณ„ํ•œ ์ดˆ๋Œ€์žฅ ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค. -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh +
-## Expanding the ESLint configuration +- [๐Ÿ”— ์„œ๋น„์Šค ๋งํฌ](https://woogyeol.site/) +- [๐Ÿ“š ๋…ธ์…˜](https://www.notion.so/19e9673ec79780a3b17bed3825f5fa8c?pvs=21) +- [๐ŸŽจ ํ”ผ๊ทธ๋งˆ](https://www.figma.com/design/Amij7OxsmnsATHkYM5PO52/Woo-Gyeol?node-id=3-62788&t=WoMAwjY7bvNK1etC-1) -If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: +
-- Configure the top-level `parserOptions` property like this: +## ๐Ÿ”‘ ์ฃผ์š” ๊ธฐ๋Šฅ + +### **์ฒญ์ฒฉ์žฅ ๊ด€๋ฆฌ** + +> ๋‚˜๋งŒ์˜ ์ฒญ์ฒฉ์žฅ์„ ์ƒ์„ฑ, ์ˆ˜์ •, ์กฐํšŒ, ์‚ญ์ œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +- ์ƒ์„ฑ / ์ˆ˜์ • / ์‚ญ์ œ / ์กฐํšŒ ๊ธฐ๋Šฅ +- ๋‚ด ์ฒญ์ฒฉ์žฅ ๋ชฉ๋ก ํ™•์ธ + +### **์ฒญ์ฒฉ์žฅ ๋งŒ๋“ค๊ธฐ: 3๋‹จ๊ณ„ ์ž…๋ ฅ๊ณผ 8๊ฐ€์ง€ ์„ ํƒ ๊ธฐ๋Šฅ** + +> 3๋‹จ๊ณ„์˜ ์ž…๋ ฅ ๊ณผ์ •์„ ๊ฑฐ์ณ ์ฒญ์ฒฉ์žฅ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (๊ธฐ๋ณธ ์ •๋ณด ์ž…๋ ฅ -> ๊ธฐ๋Šฅ ์„ ํƒ -> ํ…Œ๋งˆ ์„ ํƒ)
+ +- ์บ˜๋ฆฐ๋” +- ์ง€๋„/๊ตํ†ต์ˆ˜๋‹จ +- ๊ฐค๋Ÿฌ๋ฆฌ +- ์ถ•์˜๊ธˆ +- ์—ฐ๋ฝํ•˜๊ธฐ +- ๊ณต์ง€์‚ฌํ•ญ +- ๊ธ€๊ผด +- ๋ฐฐ๊ฒฝ์Œ์•… + +### **์ฒญ์ฒฉ์žฅ ๊ณต์œ ** + +> ์ œ์ž‘ํ•œ ์ฒญ์ฒฉ์žฅ์„ ํ•˜๊ฐ๋“ค์—๊ฒŒ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +- URL ๋ณต์‚ฌ +- ์นด์นด์˜คํ†ก ๊ณต์œ  +- QR ์ฝ”๋“œ ์ €์žฅ + +### **RSVP: ์ฐธ์„์—ฌ๋ถ€ ํ†ต๊ณ„** + +> ํ•˜๊ฐ๋“ค๋กœ๋ถ€ํ„ฐ ์ฐธ์„์—ฌ๋ถ€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›๊ณ  ์ด๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
+ +- ์ฐธ์„/๋ถˆ์ฐธ ํ†ต๊ณ„ ์‹œ๊ฐํ™” +- ์ƒ์„ธ ์ž…๋ ฅ ๋‚ด์—ญ ์กฐํšŒ +- ์—‘์…€ ๋‹ค์šด๋กœ๋“œ (.xlsx) + +### **ํฌํ† ํ†ก: ์‹ค์‹œ๊ฐ„ ํฌํ† ์›”** + +> ํ•˜๊ฐ๋“ค๋กœ๋ถ€ํ„ฐ ์‹ค์‹œ๊ฐ„ ํฌํ† ์›” ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +- ์‚ฌ์ง„ + ์ถ•ํ•˜ ๋ฉ”์‹œ์ง€ ์—…๋กœ๋“œ +- ํ•˜๊ฐ ์ด๋ฏธ์ง€ ๋‹ค์šด๋กœ๋“œ / ์‚ญ์ œ ๊ธฐ๋Šฅ ์ง€์› +- ๊ด€๋ฆฌ์ž ๊ถŒํ•œ ์ฒ˜๋ฆฌ + +### **๋‹คํฌ ๋ชจ๋“œ ์ง€์›** + +> ๋‹คํฌ ๋ชจ๋“œ๋ฅผ ํ†ตํ•ด ์ฒญ์ฒฉ์žฅ์˜ ์ƒ‰๊ฐ ๋ฐ˜์ „์„ ๊ฒฝํ—˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +
+ +## ๐Ÿ› ๏ธ ๊ธฐ์ˆ  ์Šคํƒ + +| ๊ตฌ๋ถ„ | ๊ธฐ์ˆ  | +| ----------------- | ---------------------- | +| **Frontend** | React, TypeScript | +| **์Šคํƒ€์ผ** | TailwindCSS, Storybook | +| **์ƒํƒœ๊ด€๋ฆฌ** | React Query, Zustand | +| **๋ฒˆ๋“ค๋Ÿฌ** | Vite | +| **ํ…Œ์ŠคํŠธ** | Jest | +| **๋ฐฐํฌ** | Vercel | +| **ํŒจํ‚ค์ง€ ๋งค๋‹ˆ์ €** | npm | + +
+ +## ๐Ÿ“ ํด๋” ๊ตฌ์กฐ -```js -export default tseslint.config({ - languageOptions: { - // other options... - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - }, -}); ``` +src + โ”ฃ components # ๊ณตํ†ต/๊ธฐ๋Šฅ๋ณ„ UI ์ปดํฌ๋„ŒํŠธ + โ”ฃ common # ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ปดํฌ๋„ŒํŠธ + โ”ฃ form # ์ฒญ์ฒฉ์žฅ ์ •๋ณด ์ž…๋ ฅ ๊ด€๋ จ UI + โ”ฃ display # ์™„์„ฑ๋œ ์ฒญ์ฒฉ์žฅ ๊ด€๋ จ UI + โ”ฃ phototalk # ํฌํ† ํ†ก ๊ด€๋ จ ์ปดํฌ๋„ŒํŠธ + โ”ฃ mypage # ๋‚ด์ •๋ณด ๊ด€๋ จ ์ปดํฌ๋„ŒํŠธ + โ”ฃ assets + โ”ฃ constants # ์ •์  ๋ฐ์ดํ„ฐ + โ”ฃ hooks # ์ปค์Šคํ…€ ํ›… + โ”ฃ pages # ๋ผ์šฐํŒ… ๊ธฐ๋ฐ˜ ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ + โ”ฃ services # api ์„œ๋น„์Šค ๋ชจ๋“ˆ + โ”ฃ store # Zustand ์ „์—ญ ์ƒํƒœ ์ €์žฅ์†Œ + โ”ฃ types # ์ „์—ญ ํƒ€์ž… ์ •์˜ + โ”ฃ utils # ์œ ํ‹ธ ํ•จ์ˆ˜ + โ”ฃ styles # ์ „์—ญ ์Šคํƒ€์ผ + โ”ฃ App.tsx # ๋ผ์šฐํ„ฐ ๋ฐ ์ „์ฒด ๋ ˆ์ด์•„์›ƒ ๊ตฌ์„ฑ -- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked` -- Optionally add `...tseslint.configs.stylisticTypeChecked` -- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config: - -```js -// eslint.config.js -import react from 'eslint-plugin-react'; - -export default tseslint.config({ - // Set the react version - settings: { react: { version: '18.3' } }, - plugins: { - // Add the react plugin - react, - }, - rules: { - // other rules... - // Enable its recommended rules - ...react.configs.recommended.rules, - ...react.configs['jsx-runtime'].rules, - }, -}); ``` + +
+ +## ๐Ÿค™ ์ปค๋ฐ‹ ์ปจ๋ฒค์…˜ + +| ํƒœ๊ทธ | ์„ค๋ช… | +| ---------- | ------------------------------------- | +| `feat` | ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ ์ถ”๊ฐ€ | +| `fix` | ๋ฒ„๊ทธ ์ˆ˜์ • | +| `style` | ์ฝ”๋“œ ํฌ๋งท, ์„ธ๋ฏธ์ฝœ๋ก  ๋“ฑ ๋ณ€๊ฒฝ | +| `refactor` | ์ฝ”๋“œ ๋ฆฌํŒฉํ† ๋ง | +| `test` | ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ | +| `docs` | ๋ฌธ์„œ ์ˆ˜์ • | +| `chore` | ๋นŒ๋“œ ์—…๋ฌด ์ˆ˜์ •, ํŒจํ‚ค์ง€ ๋งค๋‹ˆ์ € ์„ค์ • ๋“ฑ | +| `ci` | CI ๊ด€๋ จ ์„ค์ • | +| `build` | ๋นŒ๋“œ ํŒŒ์ผ ๊ด€๋ จ | +| `revert` | ์ปค๋ฐ‹ ๋˜๋Œ๋ฆฌ๊ธฐ | + +
+ +## ๐Ÿ‘ฅ ๋ฉค๋ฒ„ ์†Œ๊ฐœ + +| FE | FE | FE | FE | +| :------------------------------------------------------------------: | :-------------------------------------------------------------------: | :-----------------------------------------------------------------: | :------------------------------------------------------------------: | +| | | | | +| [์ด์†Œ์—ฐ](https://github.com/eesoyeon) | [๋‚จ์œ ์„ฑ](https://github.com/meteorqz6) | [ํ™ฉ์ฑ„์—ฐ](https://github.com/chaeon1) | [ํ•œ์ •์šฑ](https://github.com/nowrobin) | + +
diff --git a/src/App.tsx b/src/App.tsx index 6169dbc..4e3bc39 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -20,69 +20,65 @@ import PreviewPhotoTalkPage from '@/pages/PhotoTalk/PreviewPhotoTalkPage'; import PreviewInvitationPage from '@/pages/PreviewInvitationPage'; import ResultPage from '@/pages/ResultPage'; import GuestPhotoTalkPage from '@/pages/PhotoTalk/GuestPhotoTalkPage'; -import { useUserStore } from './store/useUserStore'; -import useAuthStore from './store/useAuthStore'; -import { useEffect } from 'react'; +import Authorized from '@/components/common/Authorized/Authorized'; +import BasicInformationPage from './pages/BasicInformationPage'; function App() { const queryClient = new QueryClient(); - const fetchUserInfo = useUserStore((state) => state.fetchUserInfo); - const token = useAuthStore((state) => state.accessToken); - - useEffect(() => { - if (token) { - fetchUserInfo(); - } - }, [token]); - return ( - - - {/* } /> */} - } /> - } /> - } /> - } /> + + + + {/* } /> */} + } /> + } /> + } /> + } /> - {/* ์ฒญ์ฒฉ์žฅ ๋งŒ๋“ค๊ธฐ */} - } /> - } /> - } /> - } - /> - } - /> + {/* ์ฒญ์ฒฉ์žฅ ๋งŒ๋“ค๊ธฐ */} + } /> + } /> + } /> + } + /> + } + /> - {/* ์ฒญ์ฒฉ์žฅ ์™„์„ฑ๋ณธ */} - } - /> - } - /> + {/* ์ฒญ์ฒฉ์žฅ ์™„์„ฑ๋ณธ */} + } + /> + } + /> - {/* ๋งˆ์ดํŽ˜์ด์ง€ */} - } /> - } /> - } /> - } - /> - } /> - } /> - } /> - - + {/* ๋งˆ์ดํŽ˜์ด์ง€ */} + } /> + } /> + } /> + } + /> + } + /> + } /> + } /> + } /> + + + ); diff --git a/src/actions/invitationAction.ts b/src/actions/invitationAction.ts index 09e03a0..d125fd4 100644 --- a/src/actions/invitationAction.ts +++ b/src/actions/invitationAction.ts @@ -29,8 +29,10 @@ import fonts from '@/constants/fonts'; const defaultCoord = { lat: 37.5086, lng: 127.0397 }; // const today = new Date(); -export const defaultInvitationValues: Omit = { - createdAt: '', +export const defaultInvitationValues: Omit< + InvitationDetail, + 'title' | 'createdAt' +> = { groomName: '', brideName: '', date: [null, null, null], @@ -157,7 +159,7 @@ export const defaultInvitationValues: Omit = { export const getInvitationAction = (): Omit< InvitationDetail, - 'imgUrl' | 'galleries' | 'notices' | 'title' + 'imgUrl' | 'galleries' | 'notices' | 'title' | 'createdAt' > => { //์›จ๋”ฉ ์ •๋ณด const { @@ -168,7 +170,7 @@ export const getInvitationAction = (): Omit< weddingHallDetail, coords, } = useAddressStore(); - const { optionalItems } = useAccordionStore(); + const { optionalItems } = useAccordionStore.getState(); const findOrder = (feature: string) => { if (!feature) return undefined; // feature๊ฐ€ ์—†์œผ๋ฉด undefined ๋ฐ˜ํ™˜ const result = optionalItems.find((value) => value.feature === feature); @@ -283,7 +285,7 @@ export const getInvitationAction = (): Omit< }; }; -export const useUpdateInvitationStore = (details: InvitationDetail) => { +export const updateInvitationStore = (details: InvitationDetail) => { const { setAddress, setJibunAddress, @@ -311,8 +313,9 @@ export const useUpdateInvitationStore = (details: InvitationDetail) => { useRSVPStore.getState(); const { toggleSubFeature: calendarToggle } = useCalendarFeatureStore.getState(); + const { setOrderItems, setOptionalItems } = useAccordionStore.getState(); - if (details) { + try { updateBrideGroom(0, 'name', details.groomName); updateBrideGroom(1, 'name', details.brideName); updateFamily(1, 'father', 'name', details.brideFatherName); @@ -580,6 +583,23 @@ export const useUpdateInvitationStore = (details: InvitationDetail) => { { key: 'contact', list: contactInfo }, { key: 'notice', list: noticesData }, ]; + + const newOptionalItems = useAccordionStore + .getState() + .optionalItems.map((item) => { + const feature = features.find((f) => f.key === item.feature); + //feature.list[0].order๋Š” details Props ์—์„œ ๋ฐ›์•„์˜จ ๊ฐ’ + if (feature && Array.isArray(feature.list) && feature.list.length > 0) { + //ํ•ด๋‹น props์˜ ์ˆœ์„œ๋ฅผ ๋ฐ›์•„์™€์„œ + const order = feature.list[0].order; + //๋ฐ˜ํ™˜ + return order !== undefined ? { ...item, order } : item; + } + return item; + }); + setOptionalItems(newOptionalItems); + setOrderItems(); + // isActive ๋™๊ธฐํ™” features.forEach(({ key, list }) => { const isActive = Array.isArray(list) && list.length > 0 && list[0].isActive; @@ -590,5 +610,7 @@ export const useUpdateInvitationStore = (details: InvitationDetail) => { selectMusic(details.audio); } musicToggle('music', !!details.audio); //์ˆ˜์ • ํ•„์š” + } catch (err) { + throw new Error(`์ˆ˜์ •์ค‘ ์ฒญ์ฒฉ์žฅ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์—๋Ÿฌ ๋ฐœ์ƒ : ${err}`); } }; diff --git a/src/components/common/Authorized/Authorized.tsx b/src/components/common/Authorized/Authorized.tsx new file mode 100644 index 0000000..2b1b105 --- /dev/null +++ b/src/components/common/Authorized/Authorized.tsx @@ -0,0 +1,31 @@ +import { useUserStore } from '@/store/useUserStore'; +import useAuthStore from '@/store/useAuthStore'; +import { useEffect } from 'react'; +import { useNavigate, useLocation } from 'react-router'; + +export default function Authorized({ + children, +}: { + children: React.ReactNode; +}) { + const EXCLUDED_PATHS = ['/', '/login', '/signup', '/result']; + const fetchUserInfo = useUserStore((state) => state.fetchUserInfo); + const token = useAuthStore((state) => state.accessToken); + const navigate = useNavigate(); + const location = useLocation(); + + useEffect(() => { + const currentPath = location.pathname; + const isExcluded = EXCLUDED_PATHS.some((path) => + currentPath.startsWith(path), + ); + + if (token) { + fetchUserInfo(); + } else if (!isExcluded) { + alert('๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•œ ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค.'); + navigate('/login', { replace: true }); + } + }, []); + return <>{children}; +} diff --git a/src/components/common/Card/Card.tsx b/src/components/common/Card/Card.tsx index 289dca1..223e1b9 100644 --- a/src/components/common/Card/Card.tsx +++ b/src/components/common/Card/Card.tsx @@ -12,9 +12,10 @@ interface CardProps { image: string; id: number; title: string; + createdAt: string; } -const Card = ({ image, id: invitationId, title }: CardProps) => { +const Card = ({ image, id: invitationId, title, createdAt }: CardProps) => { const navigate = useNavigate(); const { id: userId } = useUserStore(); const [modal, setModal] = useState(false); @@ -55,6 +56,7 @@ const Card = ({ image, id: invitationId, title }: CardProps) => { id={invitationId} image={image} title={title} + createdAt={createdAt} setModal={setModal} /> diff --git a/src/components/common/Card/CardFooter.tsx b/src/components/common/Card/CardFooter.tsx index a4a97ed..1a7836a 100644 --- a/src/components/common/Card/CardFooter.tsx +++ b/src/components/common/Card/CardFooter.tsx @@ -2,22 +2,20 @@ import ShareIcon from '@icons/ShareIcon'; import { Dispatch, SetStateAction, useRef, useState } from 'react'; import ShareInvitation from '../Share/ShareInvitation'; import { useUserStore } from '@/store/useUserStore'; -import { useGetInvitation } from '@/hooks/useInvitation'; interface CardFooterProps { title: string; image: string; id: number; + createdAt: string; setModal: Dispatch>; } -const CardFooter = ({ title, image, id }: CardFooterProps) => { +const CardFooter = ({ title, image, id, createdAt }: CardFooterProps) => { const [isFocused, setIsFocused] = useState(false); const parentRef = useRef(null); const { id: userId } = useUserStore(); - const { invitations } = useGetInvitation(id); - const createdAt = invitations?.createdAt?.split('T')[0]; const shareUrl = `result/${userId}/${id}`; const handleBlur = (event: React.FocusEvent) => { @@ -39,7 +37,7 @@ const CardFooter = ({ title, image, id }: CardFooterProps) => {

{title}

- {createdAt} + {createdAt.split('T')[0]}
diff --git a/src/components/common/InvitationLoader/InvitaionLoader.tsx b/src/components/common/InvitationLoader/InvitaionLoader.tsx new file mode 100644 index 0000000..c9642df --- /dev/null +++ b/src/components/common/InvitationLoader/InvitaionLoader.tsx @@ -0,0 +1,29 @@ +import logo from '@assets/woogyeol/logo_dark.png'; + +export default function InvitationLoader() { + return ( +
+
+
+ ๋กœ๊ณ  +
+ +

+ ์ฒญ์ฒฉ์žฅ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ด์—์š” +

+ +
+
+
+
+
+
+
+ ); +} diff --git a/src/components/display/PreviewDisplay.tsx b/src/components/display/PreviewDisplay.tsx index a3c70d4..326ee1c 100644 --- a/src/components/display/PreviewDisplay.tsx +++ b/src/components/display/PreviewDisplay.tsx @@ -4,17 +4,17 @@ import { useAccordionStore } from '@store/useAccordionStore'; const PreviewDisplay = () => { const { getSections } = useAccordionStore(); const { font } = useThemeStore(); - + const sections = getSections(); return (
- {getSections().map((section, index) => { + {sections.map((section, index) => { if (index == 2 || index == 3) { return; } return
{section}
; })} - {getSections()[2]} - {getSections()[3]} + {sections[2]} + {sections[3]}
); }; diff --git a/src/components/display/ResultDisplay.tsx b/src/components/display/ResultDisplay.tsx index 936a5aa..d3c4d92 100644 --- a/src/components/display/ResultDisplay.tsx +++ b/src/components/display/ResultDisplay.tsx @@ -4,17 +4,18 @@ import { useAccordionStore } from '@store/useAccordionStore'; const ResultDisplay = () => { const { getSections } = useAccordionStore(); const { font } = useThemeStore(); + const sections = getSections(); return (
- {getSections().map((section, index) => { + {sections.map((section, index) => { if (index == 2 || index == 3) { return; } return
{section}
; })} - {getSections()[2]} - {getSections()[3]} + {sections[2]} + {sections[3]}
); }; diff --git a/src/hooks/useInvitation.tsx b/src/hooks/useInvitation.tsx index 93ab2be..4e281e3 100644 --- a/src/hooks/useInvitation.tsx +++ b/src/hooks/useInvitation.tsx @@ -2,23 +2,30 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { deleteInvitation, getInvitation, + getInvitationCredential, getInvitations, postInvitation, updateInvitation, } from '../services/invitationService'; import { InvitationDetail } from '../types/invitationTypes'; import resetAllStores from '@/store/resetStore'; -import { useNavigate } from 'react-router'; +import { useLocation, useNavigate } from 'react-router'; export const useGetInvitation = (id: number) => { - let { data, isError } = useQuery({ + const navigate = useNavigate(); + const { pathname } = useLocation(); + const isResultPage = pathname.startsWith('/result'); + let { data, isError, isLoading, isFetching } = useQuery({ queryKey: ['invitations', id], - queryFn: () => getInvitation(id), + queryFn: () => + isResultPage ? getInvitation(id) : getInvitationCredential(id), }); if (isError) { + navigate('/login', { replace: true }); + console.error('์ฒญ์ฒฉ์žฅ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ์‹คํŒจ:', isError); throw new Error(`์ฒญ์ฒฉ์žฅ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ์‹คํŒจ`); } - return { invitations: data, error: isError }; + return { invitations: data, error: isError, isLoading, isFetching }; }; export const useGetInvitations = () => { @@ -32,7 +39,7 @@ export const usePostInvitation = () => { const navigate = useNavigate(); const queryClient = useQueryClient(); return useMutation({ - mutationFn: (details: InvitationDetail) => { + mutationFn: (details: Omit) => { return postInvitation(details); }, onSuccess: (data) => { @@ -49,11 +56,15 @@ export const usePostInvitation = () => { export const useUpdateInvitation = (id: number) => { const queryClient = useQueryClient(); + const navigate = useNavigate(); return useMutation({ - mutationFn: (details: Omit) => + mutationFn: (details: Omit) => updateInvitation({ id, details }), onSuccess: () => { + navigate(`/dashboard`); + resetAllStores(); queryClient.invalidateQueries({ queryKey: ['invitations'] }); + // navigate(`/dashboard`); }, onError: (error) => { throw new Error(`์ฒญ์ฒฉ์žฅ ์ˆ˜์ • ์‹คํŒจ:${error}`); diff --git a/src/pages/BasicInformationPage.tsx b/src/pages/BasicInformationPage.tsx new file mode 100644 index 0000000..e4afb08 --- /dev/null +++ b/src/pages/BasicInformationPage.tsx @@ -0,0 +1,123 @@ +import { useState } from 'react'; +import { useNavigate } from 'react-router'; +import PageLayout from '@/components/layout/PageLayout'; +import BackIcon from '@/components/icons/BackIcon'; +import NameInput from '@/components/form/BasicInformation/NameInput/NameInput'; +import WeddingDateInput from '@/components/form/BasicInformation/WeddingDateInput/WeddingDateInput'; +import AddressInput from '@/components/form/BasicInformation/AddressInput/AddressInput'; +import { Check, ChevronRight } from 'lucide-react'; + +const BasicInformationPage = () => { + const navigate = useNavigate(); + const [expandedIds, setExpandedIds] = useState([1, 2, 3]); + + const toggleExpand = (id: number) => { + setExpandedIds((prev) => + prev.includes(id) ? prev.filter((i) => i !== id) : [...prev, id], + ); + }; + + const sections = [ + { + id: 1, + title: '์‹ ๋ž‘ / ์‹ ๋ถ€ ์ด๋ฆ„', + isRequired: true, + isCompleted: true, + content: , + }, + { + id: 2, + title: '์˜ˆ์‹ ์ผ์‹œ', + isRequired: true, + isCompleted: true, + content: , + }, + { + id: 3, + title: '์˜ˆ์‹ ์žฅ์†Œ', + isRequired: true, + isCompleted: true, + content: , + }, + ]; + + return ( +
+ navigate(-1)} aria-label="๋’ค๋กœ๊ฐ€๊ธฐ"> + + + } + rightButton={ + + } + > +
+
+ {sections.map((section) => { + const isExpanded = expandedIds.includes(section.id); + return ( +
+
toggleExpand(section.id)} + role="button" + aria-expanded={isExpanded} + aria-controls={`accordion-content-${section.id}`} + > +
+
+ {section.isRequired && ( +
+ {section.isCompleted ? ( + + ) : ( +
+ )} +
+ )} +

+ {section.title} +

+
+ +
+
+ + {isExpanded && ( +
+
+ {section.content} +
+
+ )} +
+ ); + })} +
+
+ +
+ ); +}; + +export default BasicInformationPage; diff --git a/src/pages/CreateInvitationPage.tsx b/src/pages/CreateInvitationPage.tsx index 949fab6..8966a09 100644 --- a/src/pages/CreateInvitationPage.tsx +++ b/src/pages/CreateInvitationPage.tsx @@ -11,12 +11,12 @@ import useImageStore from '@/store/useImageStore'; import { useS3Image } from '@/hooks/useS3Image'; import { getInvitationAction, - useUpdateInvitationStore, + updateInvitationStore, } from '@/actions/invitationAction'; import useGalleryStore from '@/store/OptionalFeature/useGalleryFeatureStore'; import { useOptionalFeatureStore } from '@/store/OptionalFeature/useOptionalFeatureStore'; import useNoticeStore from '@/store/OptionalFeature/useNoticeFeatureStore'; -import { InvitationDetail, NoticeDetail } from '@/types/invitationTypes'; +import { NoticeDetail } from '@/types/invitationTypes'; import PreviewButton from '@/components/common/CreateInvitation/PreviewButton'; import { useDebouncedInputStore } from '@/store/useDebouncedInputStore'; import { useInvitationStore } from '@/store/useInvitaionStore'; @@ -44,8 +44,9 @@ const CreateInvitationPage = () => { const [previewModal, setPreviewModal] = useState(false); const { message, showToast } = useToast(); - const details = getInvitationAction(); const { invitations } = useGetInvitation(parseInt(id!)); + const details = getInvitationAction(); + const { mutateAsync: updateMutate } = useUpdateInvitation(parseInt(id!)); const { mutateAsync: s3Mutate } = useS3Image(); @@ -117,7 +118,12 @@ const CreateInvitationPage = () => { ...details, imgUrl: thumbnail.length > 0 ? thumbnail[0] : uploadedImageUrl, galleries: [ - { images: gallery, grid, isActive: selectedOptionalFeatures.gallery }, + { + images: gallery, + grid, + isActive: selectedOptionalFeatures.gallery, + order: findOrder('gallery'), + }, ], notices: noticeList, }); @@ -130,7 +136,9 @@ const CreateInvitationPage = () => { }, [invitations?.title]); useEffect(() => { - useUpdateInvitationStore(invitations as InvitationDetail); + if (invitations) { + updateInvitationStore(invitations); + } }, [invitations]); useEffect(() => { @@ -138,33 +146,6 @@ const CreateInvitationPage = () => { initializeItems(start, end); }, [currentStep, initializeItems]); - // useEffect(() => { - // const intervalId = setInterval(async () => { - // await flushAll() - // setAutoSaveModal(true); - // await saveInvitationData() - // // useUpdateInvitationStore(invitations as InvitationDetail); - // setTimeout(() => { - // setAutoSaveModal(false); - // setIsModalOpen(false) - // }, AUTO_SAVE_MODAL_DURATION_MS); // ๋ชจ๋‹ฌ ์ธํ„ฐ๋ฒŒ - // }, AUTO_SAVE_INTERVAL_MS); // ์ž„์‹œ์ €์žฅ ์ธํ„ฐ๋ฒŒ - // return () => { - // clearInterval(intervalId); // ์ปดํฌ๋„ŒํŠธ unmount ์‹œ cleanup - // }; - // }, [updateMutate]); - - // const handleNextStep = () => { - // if (currentStep < STEP_RANGES.length) { - // handleStepClick(currentStep + 1); - // } - // }; - // const handlePrevStep = () => { - // if (currentStep > 0) { - // handleStepClick(currentStep - 1); - // } - // }; - const toggleExpand = (id: number) => { setExpandedIds((prev) => prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id], @@ -187,7 +168,8 @@ const CreateInvitationPage = () => { navigate('/dashboard'); }; - const handleSave = async () => { + const handleSave = async (e: React.MouseEvent) => { + e.preventDefault(); saveInvitationData(); showToast('์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค'); }; diff --git a/src/pages/DashBoardPage.tsx b/src/pages/DashBoardPage.tsx index a767423..cd24b2a 100644 --- a/src/pages/DashBoardPage.tsx +++ b/src/pages/DashBoardPage.tsx @@ -57,6 +57,7 @@ const DashBoardPage = () => { image={card.imgUrl} data-testid="invitation-card" id={card.id as number} + createdAt={card.createdAt} title={card.title} /> ))} diff --git a/src/pages/EditProfilePage.tsx b/src/pages/EditProfilePage.tsx index 1850a43..0fb49d2 100644 --- a/src/pages/EditProfilePage.tsx +++ b/src/pages/EditProfilePage.tsx @@ -99,6 +99,23 @@ const EditProfilePage = () => {
)} +
+

+ ์›จ๋”ฉ ์ •๋ณด +

+
+

+ ๊ธฐ๋ณธ ์ •๋ณด ๊ด€๋ฆฌ +

+ +
+
+ {/*

SNS ์—ฐ๋™

์—ฐ๋™๋œ ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธํ•  ์ˆ˜ ์žˆ์–ด์š”.

diff --git a/src/pages/PreviewInvitationPage.tsx b/src/pages/PreviewInvitationPage.tsx index 73b0545..ab079b3 100644 --- a/src/pages/PreviewInvitationPage.tsx +++ b/src/pages/PreviewInvitationPage.tsx @@ -2,25 +2,25 @@ import BackIcon from '@icons/BackIcon'; import { useNavigate, useParams } from 'react-router'; import PreviewDisplay from '@/components/display/PreviewDisplay'; import { useGetInvitation } from '@/hooks/useInvitation'; -import { useAccordionStore } from '@/store/useAccordionStore'; import { useEffect } from 'react'; -import { useUpdateInvitationStore } from '@/actions/invitationAction'; -import { InvitationDetail } from '@/types/invitationTypes'; +import { updateInvitationStore } from '@/actions/invitationAction'; import CloseIcon from '@/components/icons/CloseIcon'; import Logo from '@/components/common/Logo'; +import InvitationLoader from '@/components/common/InvitationLoader/InvitaionLoader'; const PreviewInvitationPage = () => { const navigate = useNavigate(); const { invitationId } = useParams(); - const { invitations } = useGetInvitation(parseInt(invitationId!)); - const { setOrderItems } = useAccordionStore(); + const { invitations, isLoading, isFetching } = useGetInvitation( + parseInt(invitationId!), + ); useEffect(() => { - setOrderItems(); - }, []); - - useUpdateInvitationStore(invitations as InvitationDetail); + if (invitations) { + updateInvitationStore(invitations); + } + }, [invitations]); const handleBack = () => { navigate(-1); @@ -73,7 +73,7 @@ const PreviewInvitationPage = () => {

- + {isLoading || isFetching ? : }