diff --git a/frontend/src/components/common/LoadingScreen.tsx b/frontend/src/components/common/LoadingScreen.tsx new file mode 100644 index 0000000..86fe71c --- /dev/null +++ b/frontend/src/components/common/LoadingScreen.tsx @@ -0,0 +1,192 @@ +/* eslint-disable react-hooks/purity */ +'use client'; + +import React, { useEffect, useState, useMemo } from 'react'; +import { motion } from 'framer-motion'; +import Image from 'next/image'; + +interface LoadingScreenProps { + message?: string; +} + +const MBTI_GROUPS = [ + { + name: 'Analysts', + color: '#E0D7FF', // Purple-ish + textColor: '#5D2FB7', + characters: ['INTJ', 'INTP', 'ENTJ', 'ENTP'], + }, + { + name: 'Diplomats', + color: '#D7FFD7', // Green-ish + textColor: '#2D812D', + characters: ['INFJ', 'INFP', 'ENFJ', 'ENFP'], + }, + { + name: 'Sentinels', + color: '#D7F3FF', // Blue-ish + textColor: '#2B6DA1', + characters: ['ISTJ', 'ISFJ', 'ESTJ', 'ESFJ'], + }, + { + name: 'Explorers', + color: '#FFF7D7', // Yellow-ish + textColor: '#A17D1F', + characters: ['ISTP', 'ISFP', 'ESTP', 'ESFP'], + }, +]; + +export default function LoadingScreen({ + message = 'Loading...', +}: LoadingScreenProps) { + const [activeStep, setActiveStep] = useState(0); + + // Pick one random character from each group + const selectedCharacters = useMemo(() => { + return MBTI_GROUPS.map((group) => { + const randomIndex = Math.floor(Math.random() * group.characters.length); + return { + id: group.characters[randomIndex], + groupColor: group.color, + textColor: group.textColor, + }; + }); + }, []); + + useEffect(() => { + const timer = setInterval(() => { + setActiveStep((prev) => (prev + 1) % selectedCharacters.length); + }, 1500); // Wait for jump animation to mostly complete + + return () => clearInterval(timer); + }, [selectedCharacters.length]); + + return ( + + {/* Retro-pop dot pattern background */} +
+ +
+ {/* Characters Row */} +
+ {selectedCharacters.map((char, index) => { + const isActive = index === activeStep; + + return ( +
+ + {char.id} + + + {/* Visual indicator / shadow under active char */} + +
+ ); + })} +
+ + {/* Loading Text */} +
+ + {message} + + + {/* Pulsing dots indicator */} +
+ {[0, 1, 2].map((i) => ( + + ))} +
+
+
+ + {/* Group Name display (Subtle) */} +
+ + {MBTI_GROUPS[activeStep].name} + +
+ + ); +} diff --git a/frontend/src/features/diagnosis/components/BaselineSurvey.tsx b/frontend/src/features/diagnosis/components/BaselineSurvey.tsx index 0e8a1a4..12e7825 100644 --- a/frontend/src/features/diagnosis/components/BaselineSurvey.tsx +++ b/frontend/src/features/diagnosis/components/BaselineSurvey.tsx @@ -11,7 +11,7 @@ import { type BaselineAnswers, type AnswerOption, } from '@/features/diagnosis/types'; -import Spinner from '@/components/ui/Spinner'; +import LoadingScreen from '@/components/common/LoadingScreen'; import { postRegister, submitGame } from '@/lib/api'; type Status = 'answering' | 'loading' | 'error' | 'success'; @@ -79,11 +79,7 @@ export default function BaselineSurvey() { }; if (status === 'loading') { - return ( -
- -
- ); + return ; } if (status === 'error') { @@ -101,19 +97,7 @@ export default function BaselineSurvey() { } if (status === 'success') { - return ( -
-

診断完了!

-

- これからゲームが始まります。 -
- ゲームでのあなたの行動から、本当の性格を分析します。 -

-

- まもなくゲーム画面に移動します... -

-
- ); + return ; } return ( diff --git a/frontend/src/features/games/terms/components/TermsGameFlow.tsx b/frontend/src/features/games/terms/components/TermsGameFlow.tsx index 4a029bc..b163dab 100644 --- a/frontend/src/features/games/terms/components/TermsGameFlow.tsx +++ b/frontend/src/features/games/terms/components/TermsGameFlow.tsx @@ -7,6 +7,7 @@ import type { Game1Data, ScrollEvent } from '@/features/games/types'; import { game1DataAtom } from '@/stores/games'; import PopupAd from './PopupAd'; import PopupTerms from './PopupTerms'; +import LoadingScreen from '@/components/common/LoadingScreen'; // TODO: バックエンド接続時にコメントアウトを解除する // import { submitGame } from '@/lib/api'; @@ -183,16 +184,7 @@ export default function TermsGameFlow() { ); if (isCompleted) { - return ( -
-
-

完了しました

-

- 次のゲームに移動します... -

-
-
- ); + return ; } return ( diff --git a/frontend/src/features/result/components/ResultPage.tsx b/frontend/src/features/result/components/ResultPage.tsx index 96c708a..2179b0b 100644 --- a/frontend/src/features/result/components/ResultPage.tsx +++ b/frontend/src/features/result/components/ResultPage.tsx @@ -5,13 +5,14 @@ import { resultAtom } from '@/stores/result'; import { useResult } from '../hooks/useResult'; import AnalyzingView from './AnalyzingView'; import ResultReport from './ResultReport'; +import LoadingScreen from '@/components/common/LoadingScreen'; export default function ResultPage() { const { status, errorMessage, retry } = useResult(); const result = useAtomValue(resultAtom); if (status === 'loading') { - return ; + return ; } if (status === 'error') { @@ -28,5 +29,5 @@ export default function ResultPage() { return ; } - return ; + return ; }