From 080aa82db8a7118a8c1515f7a4feb12368a1c684 Mon Sep 17 00:00:00 2001 From: suminhan123 Date: Mon, 10 Feb 2025 19:40:28 +0900 Subject: [PATCH 1/7] =?UTF-8?q?feat:=20Toast=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/layout.tsx | 2 + src/components/Toast/Toast.module.scss | 71 ++++++++++++++++++++++++++ src/components/Toast/Toast.tsx | 20 ++++++++ src/components/Toast/index.ts | 1 + 4 files changed, 94 insertions(+) create mode 100644 src/components/Toast/Toast.module.scss create mode 100644 src/components/Toast/Toast.tsx create mode 100644 src/components/Toast/index.ts diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 091e95b..b229c16 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -3,6 +3,7 @@ import { Geist, Geist_Mono } from 'next/font/google'; import { AppleScript } from '@/components/appleScript'; import { KakaoScript } from '@/components/KakaoScript'; +import { Toast } from '@/components/Toast'; import ReactQueryProviders from '../providers/ReactQueryProvider'; @@ -39,6 +40,7 @@ export default function RootLayout({
{children}
+ diff --git a/src/components/Toast/Toast.module.scss b/src/components/Toast/Toast.module.scss new file mode 100644 index 0000000..d4a3972 --- /dev/null +++ b/src/components/Toast/Toast.module.scss @@ -0,0 +1,71 @@ +.viewport { + list-style: none; + z-index: 100; + outline: none; +} + +.root { + background: rgba(68, 73, 83, 0.95); + border-radius: 0.75rem; + z-index: 100; + position: absolute; + bottom: 4.25rem; + width: calc(100% - 2.5rem); + padding-top: 25.5px; + padding-bottom: 25.5px; + left: 50%; + transform: translateX(-50%); + @include flex-center; + + &[data-state='open'] { + animation: slideIn 300ms ease-out; + } + + &[data-state='closed'] { + animation: slideOut 300ms ease-in; + } + + &[data-swipe='move'] { + transform: translateY(var(--radix-toast-swipe-move-y)); + } + + &[data-swipe='cancel'] { + transform: translateY(0); + transition: transform 200ms ease-out; + } + + &[data-swipe='end'] { + animation: swipeOut 100ms ease-out; + } +} + +@keyframes slideIn { + from { + transform: translateX(-50%) translateY(5vh); + } + to { + transform: translateX(-50%) translateY(0); + } +} + +@keyframes slideOut { + from { + transform: translateX(-50%) translateY(0); + } + to { + transform: translateX(-50%) translateY(5vh); + } +} + +@keyframes swipeOut { + from { + transform: translateX(-50%) translateY(var(--radix-toast-swipe-end-y)); + } + to { + transform: translateX(-50%) translateY(100%); + } +} +.title { + color: $white; + @include subTitle4; +} diff --git a/src/components/Toast/Toast.tsx b/src/components/Toast/Toast.tsx new file mode 100644 index 0000000..ccd0769 --- /dev/null +++ b/src/components/Toast/Toast.tsx @@ -0,0 +1,20 @@ +'use client'; + +import { Toast as RadixToast } from 'radix-ui'; + +import { useToast } from '@/hooks'; + +import styles from './Toast.module.scss'; + +export default function Toast() { + const { toast, updateToast } = useToast(); + + return ( + + + {toast.message} + + + + ); +} diff --git a/src/components/Toast/index.ts b/src/components/Toast/index.ts new file mode 100644 index 0000000..116863c --- /dev/null +++ b/src/components/Toast/index.ts @@ -0,0 +1 @@ +export { default as Toast } from './Toast'; From 73149f47897ca1eff6ca935ad9b4eba38da9d97c Mon Sep 17 00:00:00 2001 From: suminhan123 Date: Mon, 10 Feb 2025 19:41:09 +0900 Subject: [PATCH 2/7] =?UTF-8?q?feat:=20useToast=20=ED=9B=85=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EB=B0=8F=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=ED=98=B8?= =?UTF-8?q?=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(home)/components/Page/Content.tsx | 13 ++++++++--- src/hooks/index.ts | 1 + src/hooks/useToast.ts | 22 +++++++++++++++++++ 3 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 src/hooks/useToast.ts diff --git a/src/app/(route)/(home)/components/Page/Content.tsx b/src/app/(route)/(home)/components/Page/Content.tsx index 004d71d..a693572 100644 --- a/src/app/(route)/(home)/components/Page/Content.tsx +++ b/src/app/(route)/(home)/components/Page/Content.tsx @@ -9,6 +9,7 @@ import { Capsule } from '@/components/Capsule'; import { LockBtn } from '@/components/LockBtn'; import type { LockBtnVariant } from '@/components/LockBtn/LockBtn'; import { ROUTES } from '@/constants'; +import { useToast } from '@/hooks'; import { useGetSurvey } from '@/query-hooks/useSurvey'; import { Drawer } from '../Drawer'; @@ -19,7 +20,7 @@ export default function HomeContent() { const routes = useRouter(); const [isOpen, setIsOpen] = useState(true); const { data } = useGetSurvey(); - + const { showToast } = useToast(); const daysArr = Array.from({ length: 15 }, (_, i) => i + 1); const getLockState = (day: number): LockBtnVariant => { @@ -39,11 +40,17 @@ export default function HomeContent() { return 'default'; }; - + // disabled 일 경우 토스트 띄우기 + // completed 일 경우 나의 답변 모아보기 focus 되어서 보여지게게 const onClickBtn = (day: number): void => { if (getLockState(day) === 'default') { routes.push(`${ROUTES.game}/${data?.bundleId}`); } + if (getLockState(day) === 'disabled' && day > (data?.nextSurveyIndex ?? 0)) { + showToast('하루에 한 회차씩 답변할 수 있어요'); + } + if (getLockState(day) === 'completed') { + } }; return ( @@ -54,7 +61,7 @@ export default function HomeContent() { - + diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 0a31300..8e8f6a3 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -1 +1,2 @@ export * from './usePreviousValue'; +export * from './useToast'; diff --git a/src/hooks/useToast.ts b/src/hooks/useToast.ts new file mode 100644 index 0000000..614a881 --- /dev/null +++ b/src/hooks/useToast.ts @@ -0,0 +1,22 @@ +import { useState } from 'react'; + +export function useToast() { + // TODO: 전역 상태 적용 + const [toast, setToast] = useState<{ open: boolean; message: string }>({ + open: false, + message: '', + }); + + const showToast = (message: string, duration = 500) => { + setToast({ open: true, message }); + setTimeout(() => { + setToast((prev) => ({ ...prev, open: false })); + }, duration); + }; + + const updateToast = (open: boolean) => { + setToast((prev) => ({ ...prev, open })); + }; + + return { toast, updateToast, showToast }; +} From d197fd7f10796af833c900750eb92fcc0644eae5 Mon Sep 17 00:00:00 2001 From: suminhan123 Date: Mon, 10 Feb 2025 19:43:30 +0900 Subject: [PATCH 3/7] =?UTF-8?q?style:=20Toast=20layout=20=EC=97=90=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=ED=95=B4=20=EC=8A=A4=ED=83=80=EC=9D=BC=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/layout.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/app/layout.tsx b/src/app/layout.tsx index b229c16..67e92e2 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -38,9 +38,11 @@ export default function RootLayout({
-
{children}
+
+ {children} + +
-
From d2cfac2a1a8a3fb074c3f7c280955f33ea7b842b Mon Sep 17 00:00:00 2001 From: suminhan123 Date: Mon, 10 Feb 2025 23:41:10 +0900 Subject: [PATCH 4/7] =?UTF-8?q?feat:=20=EC=A0=84=EC=97=AD=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=20toast=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useToast.ts | 12 ++++-------- src/stores/index.ts | 1 + src/stores/useToast.ts | 18 ++++++++++++++++++ 3 files changed, 23 insertions(+), 8 deletions(-) create mode 100644 src/stores/useToast.ts diff --git a/src/hooks/useToast.ts b/src/hooks/useToast.ts index 614a881..3f4ba6b 100644 --- a/src/hooks/useToast.ts +++ b/src/hooks/useToast.ts @@ -1,21 +1,17 @@ -import { useState } from 'react'; +import { useToastStore } from '@/stores'; export function useToast() { - // TODO: 전역 상태 적용 - const [toast, setToast] = useState<{ open: boolean; message: string }>({ - open: false, - message: '', - }); + const { toast, setToast } = useToastStore(); const showToast = (message: string, duration = 500) => { setToast({ open: true, message }); setTimeout(() => { - setToast((prev) => ({ ...prev, open: false })); + setToast({ open: false, message }); }, duration); }; const updateToast = (open: boolean) => { - setToast((prev) => ({ ...prev, open })); + setToast({ ...toast, open }); }; return { toast, updateToast, showToast }; diff --git a/src/stores/index.ts b/src/stores/index.ts index 8285f5e..3c5a335 100644 --- a/src/stores/index.ts +++ b/src/stores/index.ts @@ -1 +1,2 @@ export * from './useMember'; +export * from './useToast'; diff --git a/src/stores/useToast.ts b/src/stores/useToast.ts new file mode 100644 index 0000000..d0dd432 --- /dev/null +++ b/src/stores/useToast.ts @@ -0,0 +1,18 @@ +import { create } from 'zustand'; + +interface ToastState { + open: boolean; + message: string; +} +interface ToastStoreState { + toast: ToastState; + setToast: (toast: ToastState) => void; +} + +export const useToastStore = create((set, _) => ({ + toast: { + message: '', + open: false, + }, + setToast: (toast) => set({ toast }), +})); From 47e4449bc17550fa4c7f4a0d3c53c811c5d434e4 Mon Sep 17 00:00:00 2001 From: suminhan123 Date: Tue, 11 Feb 2025 20:51:19 +0900 Subject: [PATCH 5/7] =?UTF-8?q?feat:=20=EC=9E=91=EC=84=B1=ED=95=9C=20?= =?UTF-8?q?=ED=8A=B9=EC=A0=95=20=ED=9A=8C=EC=B0=A8=20=ED=9A=8C=EA=B3=A0=20?= =?UTF-8?q?focus=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(home)/components/Page/Content.tsx | 4 +- src/app/(route)/report/questions/page.tsx | 17 ++++- src/components/Card/Card.tsx | 74 +++++++++---------- src/components/Toast/Toast.module.scss | 6 +- 4 files changed, 58 insertions(+), 43 deletions(-) diff --git a/src/app/(route)/(home)/components/Page/Content.tsx b/src/app/(route)/(home)/components/Page/Content.tsx index a693572..2340411 100644 --- a/src/app/(route)/(home)/components/Page/Content.tsx +++ b/src/app/(route)/(home)/components/Page/Content.tsx @@ -40,8 +40,7 @@ export default function HomeContent() { return 'default'; }; - // disabled 일 경우 토스트 띄우기 - // completed 일 경우 나의 답변 모아보기 focus 되어서 보여지게게 + const onClickBtn = (day: number): void => { if (getLockState(day) === 'default') { routes.push(`${ROUTES.game}/${data?.bundleId}`); @@ -50,6 +49,7 @@ export default function HomeContent() { showToast('하루에 한 회차씩 답변할 수 있어요'); } if (getLockState(day) === 'completed') { + routes.push(`${ROUTES.reportQuestions}?focus=${day}`); } }; diff --git a/src/app/(route)/report/questions/page.tsx b/src/app/(route)/report/questions/page.tsx index 5c8473b..c1605f6 100644 --- a/src/app/(route)/report/questions/page.tsx +++ b/src/app/(route)/report/questions/page.tsx @@ -1,6 +1,7 @@ 'use client'; -import { useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; +import { useSearchParams } from 'next/navigation'; import dayjs from 'dayjs'; import CharacterSelectBottomSheet from '@/app/(route)/report/components/CharacterSelectBottomSheet'; @@ -35,6 +36,16 @@ export default function ReportQuestions() { const [characters, setCharacters] = useState(MOCK_CHARACTERS); const [selectedCharacter, setSelectedCharacter] = useState(MOCK_CHARACTER1); const [questions, setQuestions] = useState(mockQuestions); + const searchParams = useSearchParams(); + + const focusIndex = searchParams.get('focus') ? Number(searchParams.get('focus')) - 1 : null; + const cardRefs = useRef<(HTMLDivElement | null)[]>([]); + + useEffect(() => { + if (focusIndex !== null && cardRefs.current[focusIndex]) { + cardRefs.current[focusIndex]?.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } + }, [focusIndex]); const handleCharacter = (character: Character) => { setSelectedCharacter(character); @@ -53,12 +64,16 @@ export default function ReportQuestions() {
{questions.map((question, index) => ( { + cardRefs.current[index] = el; + }} key={index} count={index + 1} date={formatDate(question.date)} question={question.question} answer={question.answer} retrospective={question.retrospective} + isOpen={focusIndex === index} className={styles.card} /> ))} diff --git a/src/components/Card/Card.tsx b/src/components/Card/Card.tsx index 084aa8a..374a596 100644 --- a/src/components/Card/Card.tsx +++ b/src/components/Card/Card.tsx @@ -1,3 +1,4 @@ +import { forwardRef } from 'react'; import { Card as CardComponent } from '@radix-ui/themes'; import cn from 'classnames'; import { Accordion, Separator } from 'radix-ui'; @@ -56,42 +57,39 @@ const BasicCard = ({ count, date, question, answer, retrospective }: CardProps)
); -export default function Card({ - count, - date, - question, - answer, - hiddenCollapse = false, - isOpen = false, - retrospective, - className, -}: CardProps) { - if (hiddenCollapse) { - return ; - } +const Card = forwardRef( + ({ count, date, question, answer, hiddenCollapse = false, isOpen = false, retrospective, className }, ref) => { + if (hiddenCollapse) { + return ; + } - return ( -
- - - - - -
-

{count}회차

- -
-
- - - {date} - - - -
-
-
-
-
- ); -} + return ( +
+ + + + + +
+

{count}회차

+ +
+
+ + + {date} + + + +
+
+
+
+
+ ); + }, +); + +Card.displayName = 'Card'; + +export default Card; diff --git a/src/components/Toast/Toast.module.scss b/src/components/Toast/Toast.module.scss index d4a3972..7d6a4a7 100644 --- a/src/components/Toast/Toast.module.scss +++ b/src/components/Toast/Toast.module.scss @@ -11,8 +11,10 @@ position: absolute; bottom: 4.25rem; width: calc(100% - 2.5rem); - padding-top: 25.5px; - padding-bottom: 25.5px; + padding-left: 1.7188rem; + padding-right: 1.7188rem; + text-align: center; + height: 4.5rem; left: 50%; transform: translateX(-50%); @include flex-center; From b71d4f8b124c7ade271480ef4daa3441068a3e68 Mon Sep 17 00:00:00 2001 From: suminhan123 Date: Tue, 11 Feb 2025 20:57:32 +0900 Subject: [PATCH 6/7] =?UTF-8?q?feat:=20useSearchParams=20=EC=9C=84?= =?UTF-8?q?=ED=95=B4=20suspense=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(route)/report/questions/layout.tsx | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 src/app/(route)/report/questions/layout.tsx diff --git a/src/app/(route)/report/questions/layout.tsx b/src/app/(route)/report/questions/layout.tsx new file mode 100644 index 0000000..587dc48 --- /dev/null +++ b/src/app/(route)/report/questions/layout.tsx @@ -0,0 +1,6 @@ +import type { PropsWithChildren } from 'react'; +import { Suspense } from 'react'; + +export default function ReportQuestionsLayout({ children }: PropsWithChildren) { + return {children}; +} From a92fff70acf64b9e0af815c016a9806c5edcb4db Mon Sep 17 00:00:00 2001 From: suminhan123 Date: Thu, 13 Feb 2025 21:46:13 +0900 Subject: [PATCH 7/7] =?UTF-8?q?feat:=20=EB=A6=AC=EB=B7=B0=20=EB=B0=98?= =?UTF-8?q?=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(route)/report/questions/page.tsx | 3 --- src/components/Toast/Toast.module.scss | 8 ++++---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/app/(route)/report/questions/page.tsx b/src/app/(route)/report/questions/page.tsx index bd29793..29e3c87 100644 --- a/src/app/(route)/report/questions/page.tsx +++ b/src/app/(route)/report/questions/page.tsx @@ -16,9 +16,6 @@ import styles from './page.module.scss'; export default function ReportQuestions() { const [open, setOpen] = useState(false); - // const [characters, setCharacters] = useState(MOCK_CHARACTERS); - // const [selectedCharacter, setSelectedCharacter] = useState(MOCK_CHARACTER1); - // const [questions, setQuestions] = useState(mockQuestions); const searchParams = useSearchParams(); const focusIndex = searchParams.get('focus') ? Number(searchParams.get('focus')) - 1 : null; diff --git a/src/components/Toast/Toast.module.scss b/src/components/Toast/Toast.module.scss index 7d6a4a7..aa3575b 100644 --- a/src/components/Toast/Toast.module.scss +++ b/src/components/Toast/Toast.module.scss @@ -24,7 +24,7 @@ } &[data-state='closed'] { - animation: slideOut 300ms ease-in; + animation: slideOut 600ms ease-in; } &[data-swipe='move'] { @@ -33,11 +33,11 @@ &[data-swipe='cancel'] { transform: translateY(0); - transition: transform 200ms ease-out; + transition: transform 300ms ease-out; } &[data-swipe='end'] { - animation: swipeOut 100ms ease-out; + animation: swipeOut 300ms ease-out; } } @@ -55,7 +55,7 @@ transform: translateX(-50%) translateY(0); } to { - transform: translateX(-50%) translateY(5vh); + transform: translateX(-50%) translateY(9.5vh); } }