diff --git a/public/images/network-error.png b/public/images/network-error.png new file mode 100644 index 00000000..c2ea4d28 Binary files /dev/null and b/public/images/network-error.png differ diff --git a/src/app/(routes)/quiz/[id]/page.tsx b/src/app/(routes)/quiz/[id]/page.tsx index 4249ecbf..4a2e7aa1 100644 --- a/src/app/(routes)/quiz/[id]/page.tsx +++ b/src/app/(routes)/quiz/[id]/page.tsx @@ -8,10 +8,8 @@ interface Props { id: string } searchParams: { - quizType: 'today' | 'document' | 'collection' + quizType: 'today' | 'document' | 'collection' | 'create' createdAt: string - // 문제 생성일 경우 - isFirst?: boolean // 문서 퀴즈일 경우 documentName?: string directoryEmoji?: string @@ -26,7 +24,6 @@ const QuizDetailPage = async ({ params, searchParams }: Props) => { const { quizType, createdAt, - isFirst, documentName, directoryEmoji, collectionId, @@ -59,7 +56,6 @@ const QuizDetailPage = async ({ params, searchParams }: Props) => { = ({ children }) => { - return
{children}
-} - -export default Layout diff --git a/src/app/(routes)/quiz/page.tsx b/src/app/(routes)/quiz/page.tsx deleted file mode 100644 index 5eafcd0c..00000000 --- a/src/app/(routes)/quiz/page.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import AiCreatingQuiz from '@/features/quiz/screen/ai-creating-quiz' - -interface Props { - searchParams: { - documentId: string - documentName: string - directoryEmoji: string - } -} - -// quiz-set-id 없이 /quiz로만 오면 document에서 퀴즈를 생성하는 것으로 판단 -const QuizPage = ({ searchParams }: Props) => { - const { documentId, documentName, directoryEmoji } = searchParams - - return ( - <> - - - ) -} - -export default QuizPage diff --git a/src/app/error.tsx b/src/app/error.tsx new file mode 100644 index 00000000..9252ff4f --- /dev/null +++ b/src/app/error.tsx @@ -0,0 +1,57 @@ +'use client' + +import Icon from '@/shared/components/custom/icon' +import { Button } from '@/shared/components/ui/button' +import Text from '@/shared/components/ui/text' +import { NextPageContext } from 'next' +import Image from 'next/image' +import React from 'react' + +interface Props { + statusCode: number +} + +function Error({ statusCode }: Props) { + if (statusCode === 500) + return ( +
+ +
+ + 네트워크 문제로
연결이 지연되고 있습니다 +
+ + 네트워크 연결 상태를 확인하신 후,
+ 새로고침 버튼을 눌러주세요 +
+
+
+
+ +
+
+
+ ) +} + +Error.getInitialProps = ({ res, err }: NextPageContext) => { + if ( + err && + (err.message?.includes('NetworkError') || + err.message?.includes('Failed to fetch') || + err.message?.includes('ECONNREFUSED')) + ) { + return { statusCode: 500 } + } + const statusCode = res?.statusCode || err?.statusCode || 404 + return { statusCode } +} + +export default Error diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx index a406c242..4bb56233 100644 --- a/src/app/not-found.tsx +++ b/src/app/not-found.tsx @@ -1,23 +1,27 @@ 'use client' +import Text from '@/shared/components/ui/text' import { useRouter } from 'next/navigation' export default function NotFound() { const router = useRouter() return ( -
- +
+
-
페이지를 찾을 수 없습니다.
-
- 존재하지 않는 주소를 입력했거나, 요청하신 페이지의 주소가 변경, 삭제되어 찾을 수 없습니다. -
+ 페이지를 찾을 수 없습니다. + + 존재하지 않는 주소를 입력했거나, 요청하신 페이지의
주소가 변경, 삭제되어 찾을 수 + 없습니다. +
-
Q
-
다음 중 이동을 원하는 페이지는?
+ + Q. + + 다음 중 이동을 원하는 페이지는?
-
+ {order} -
-
{label}
+ + {label} ) } diff --git a/src/features/quiz/screen/ai-creating-quiz/index.tsx b/src/features/quiz/screen/ai-creating-quiz/index.tsx index 7a2063df..bebc3625 100644 --- a/src/features/quiz/screen/ai-creating-quiz/index.tsx +++ b/src/features/quiz/screen/ai-creating-quiz/index.tsx @@ -66,9 +66,7 @@ const AiCreatingQuiz = ({ documentId, documentName, directoryEmoji }: Props) => router.push( '/quiz/' + data.quizSetId + - '?quizType=document' + - '&' + - 'isFirst=true' + + '?quizType=create' + '&' + `createdAt=${data.createdAt}` + '&' + @@ -82,10 +80,10 @@ const AiCreatingQuiz = ({ documentId, documentName, directoryEmoji }: Props) => return ( <> -
+
{quizIsReady ? ( -
+
diff --git a/src/features/quiz/screen/intro-and-quiz-view.tsx b/src/features/quiz/screen/intro-and-quiz-view.tsx index 0b1a7579..accfbc7a 100644 --- a/src/features/quiz/screen/intro-and-quiz-view.tsx +++ b/src/features/quiz/screen/intro-and-quiz-view.tsx @@ -5,10 +5,9 @@ import QuizView from './quiz-view' import QuizIntro from './intro' interface Props { - quizType: 'today' | 'document' | 'collection' + quizType: 'today' | 'document' | 'collection' | 'create' quizzes: QuizWithMetadata[] createdAt: string - isFirst: boolean | undefined documentInfo?: { name: string; directoryEmoji: string } collectionInfo?: { name: string; emoji: string } } @@ -17,7 +16,6 @@ const IntroAndQuizView = ({ quizType, quizzes, createdAt, - isFirst, documentInfo, collectionInfo, }: Props) => { @@ -34,7 +32,7 @@ const IntroAndQuizView = ({ if (!finishedIntro) { return ( + return } export default IntroAndQuizView diff --git a/src/features/quiz/screen/intro/index.tsx b/src/features/quiz/screen/intro/index.tsx index 8968a220..3896b7ca 100644 --- a/src/features/quiz/screen/intro/index.tsx +++ b/src/features/quiz/screen/intro/index.tsx @@ -7,7 +7,7 @@ import CollectionQuizIntro from './components/collection-quiz-intro' import { formatDateKorean } from '@/shared/utils/date' interface Props { - quizType: 'today' | 'document' | 'collection' + quizType: 'today' | 'document' | 'collection' | 'create' createdAt: string documentInfo?: { name: string; directoryEmoji: string } collectionInfo?: { name: string; emoji: string } diff --git a/src/features/quiz/utils/index.ts b/src/features/quiz/utils/index.ts index 4838128a..c014ff18 100644 --- a/src/features/quiz/utils/index.ts +++ b/src/features/quiz/utils/index.ts @@ -33,7 +33,7 @@ export const getAnswerText = (answer: string) => { return answer } -export const getQuizSetTypeEnum = (quizSetType: 'today' | 'document' | 'collection') => { +export const getQuizSetTypeEnum = (quizSetType: 'today' | 'document' | 'collection' | 'create') => { let enumQuizType: QuizSetType switch (quizSetType) { @@ -46,6 +46,9 @@ export const getQuizSetTypeEnum = (quizSetType: 'today' | 'document' | 'collecti case 'collection': enumQuizType = 'COLLECTION_QUIZ_SET' break + case 'create': + enumQuizType = 'FIRST_QUIZ_SET' + break } return enumQuizType diff --git a/src/features/write/pages/write-document-page.tsx b/src/features/write/pages/write-document-page.tsx index eba30306..c8976602 100644 --- a/src/features/write/pages/write-document-page.tsx +++ b/src/features/write/pages/write-document-page.tsx @@ -1,6 +1,6 @@ 'use client' -import { useState } from 'react' +import { useEffect, useState } from 'react' import TitleInput from '../components/title-input' import Icon from '@/shared/components/custom/icon' import Text from '@/shared/components/ui/text' @@ -10,6 +10,7 @@ import { useCreateDocument } from '@/requests/document/hooks' import { MAX_CHARACTERS, MIN_CHARACTERS } from '@/features/document/config' import { useDirectoryContext } from '@/features/directory/contexts/directory-context' import CreateQuizDrawer from '../components/create-quiz-drawer' +import AiCreatingQuiz from '@/features/quiz/screen/ai-creating-quiz' import { useRouter } from 'next/navigation' const Editor = dynamic(() => import('../components/editor'), { @@ -22,6 +23,8 @@ const WriteDocumentPage = () => { const { selectedDirectory, selectDirectoryId, globalDirectoryId } = useDirectoryContext() const [title, setTitle] = useState('') const [content, setContent] = useState('') + const [documentId, setDocumentId] = useState(null) + const [showCreatePopup, setShowCreatePopup] = useState(false) const { mutate: createDocumentMutate } = useCreateDocument() @@ -46,22 +49,52 @@ const WriteDocumentPage = () => { }, { onSuccess: ({ id }) => { - router.push( - '/quiz' + - '?documentId=' + - id + - '&' + - 'documentName=' + - title + - '&' + - 'directoryEmoji=' + - selectedDirectory.emoji - ) + setDocumentId(id) + setShowCreatePopup(true) }, } ) } + useEffect(() => { + const handlePopState = (event: PopStateEvent) => { + // ai 퀴즈 생성 팝업이 열려 있는 상태에서는 뒤로 가기 이벤트를 확인 + if (showCreatePopup) { + event.preventDefault() + window.history.pushState(null, '', window.location.href) + const userConfirm = window.confirm( + '현재 화면에서 나가시겠습니까? 지금 나가더라도 AI 퀴즈 생성이 중단되지는 않습니다.' + ) + if (userConfirm && documentId) { + router.push(`/document/${documentId}`) + } + } + } + + if (showCreatePopup) { + window.history.pushState(null, '', window.location.href) + window.addEventListener('popstate', handlePopState) + } + + return () => { + window.removeEventListener('popstate', handlePopState) + } + }, [showCreatePopup, router, documentId]) + + if (documentId !== null && showCreatePopup) { + return ( +
+
+ +
+
+ ) + } + return (
setTitle(value)} />