feat(saju): 오늘운세 페이지 (FortuneRing + 4 ScoreCard + LuckyBox + good_signs/warnings)

This commit is contained in:
2026-05-26 08:35:41 +09:00
parent 435e6fb1bc
commit 69d17f787a
2 changed files with 143 additions and 0 deletions

138
src/pages/saju/Today.jsx Normal file
View File

@@ -0,0 +1,138 @@
import React from 'react';
import { useSearchParams, Link } from 'react-router-dom';
import './Saju.css';
import SajuNav from './components/SajuNav';
import HoryungMascot from './components/HoryungMascot';
import FortuneRing from './components/FortuneRing';
import ScoreCard from './components/ScoreCard';
import LuckyBox from './components/LuckyBox';
import HoryungQuote from './components/HoryungQuote';
import useSajuReading from './hooks/useSajuReading';
export default function Today() {
const [params] = useSearchParams();
const rid = params.get('rid');
const ridNum = rid ? parseInt(rid, 10) : null;
const { data, loading, error } = useSajuReading(ridNum);
if (!rid) {
return (
<div className="saju-page">
<SajuNav />
<div className="saju-stub">
<HoryungMascot pose="thinking" />
<h2 className="saju-h2">사주가 필요해요</h2>
<p>오늘의 운세를 보려면 먼저 사주를 입력해주세요.</p>
<Link to="/saju">사주 입력하러 가기</Link>
</div>
</div>
);
}
if (loading) {
return (
<div className="saju-page">
<SajuNav />
<div className="saju-stub">
<HoryungMascot pose="thinking" />
<p>오늘의 운세를 풀어보는 ...</p>
</div>
</div>
);
}
if (error || !data) {
return (
<div className="saju-page">
<SajuNav />
<div className="saju-stub">
<h2 className="saju-h2">결과를 찾을 없어요</h2>
<p>{error || '다시 입력해주세요.'}</p>
<Link to="/saju">메인으로 가기</Link>
</div>
</div>
);
}
const scores = data.fortune_scores;
const lucky = data.lucky;
return (
<div className="saju-page">
<SajuNav />
<section className="saju-hero">
<div className="saju-hero__left">
<HoryungMascot pose="pointing" size="lg" />
</div>
<div className="saju-hero__right">
<h1 className="saju-h1">오늘의 운세</h1>
<p className="saju-sub">
오늘 하루 어떤 흐름이 호령을 따라올지 확인해보세요.
</p>
</div>
</section>
<section style={{ padding: '0 2rem', maxWidth: 1400, margin: '0 auto', display: 'grid', gap: '2rem' }}>
{scores && (
<div style={{ display: 'grid', gridTemplateColumns: '1fr 2fr', gap: '2rem', alignItems: 'center' }}>
<div>
<h2 className="saju-h2" style={{ marginBottom: '1rem' }}>오늘의 종합점</h2>
<FortuneRing score={scores.overall} />
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '1rem' }}>
<ScoreCard category="wealth" score={scores.wealth} />
<ScoreCard category="romance" score={scores.romance} />
<ScoreCard category="social" score={scores.social} />
<ScoreCard category="career" score={scores.career} />
</div>
</div>
)}
{lucky && (
<div>
<h2 className="saju-h2" style={{ marginBottom: '1rem' }}>오늘의 럭키</h2>
<LuckyBox lucky={lucky} />
</div>
)}
{(lucky?.good_signs?.length || lucky?.warnings?.length) ? (
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem' }}>
{lucky.good_signs?.length > 0 && (
<div className="saju-quote-box" style={{ maxWidth: 'none', background: 'rgba(75, 112, 101, 0.15)' }}>
<strong style={{ color: 'var(--saju-jade)' }}> 행운 알림</strong>
<ul style={{ marginTop: '0.5rem', paddingLeft: '1.2rem' }}>
{lucky.good_signs.map((s, i) => <li key={i}>{s}</li>)}
</ul>
</div>
)}
{lucky.warnings?.length > 0 && (
<div className="saju-quote-box" style={{ maxWidth: 'none', background: 'rgba(197, 143, 118, 0.15)' }}>
<strong style={{ color: 'var(--saju-apricot)' }}> 주의사항</strong>
<ul style={{ marginTop: '0.5rem', paddingLeft: '1.2rem' }}>
{lucky.warnings.map((s, i) => <li key={i}>{s}</li>)}
</ul>
</div>
)}
</div>
) : null}
<HoryungQuote
pose="happy"
text="오늘 하루도 호령과 함께 평안하시길 바라요. 작은 신호에도 귀 기울이세요."
/>
</section>
<section style={{ padding: '3rem 2rem', display: 'flex', gap: '1rem', justifyContent: 'center' }}>
<Link to={`/saju/result?rid=${rid}`} className="saju-action-card saju-action-card--saju" style={{ maxWidth: 240 }}>
<span className="saju-action-card__icon">📖</span>
<span className="saju-action-card__title">사주풀이 보기</span>
</Link>
<Link to="/saju" className="saju-action-card saju-action-card--gunghab" style={{ maxWidth: 240 }} aria-disabled="true">
<span className="saju-action-card__icon"></span>
<span className="saju-action-card__title">궁합 (준비 )</span>
</Link>
</section>
</div>
);
}

View File

@@ -37,6 +37,7 @@ const TarotReading = lazy(() => import('./pages/tarot/Reading'));
const TarotHistory = lazy(() => import('./pages/tarot/History')); const TarotHistory = lazy(() => import('./pages/tarot/History'));
const Saju = lazy(() => import('./pages/saju/Saju')); const Saju = lazy(() => import('./pages/saju/Saju'));
const SajuResult = lazy(() => import('./pages/saju/SajuResult')); const SajuResult = lazy(() => import('./pages/saju/SajuResult'));
const SajuToday = lazy(() => import('./pages/saju/Today'));
const Compatibility = lazy(() => import('./pages/saju/Compatibility')); const Compatibility = lazy(() => import('./pages/saju/Compatibility'));
const CompatibilityResult = lazy(() => import('./pages/saju/CompatibilityResult')); const CompatibilityResult = lazy(() => import('./pages/saju/CompatibilityResult'));
@@ -262,6 +263,10 @@ export const appRoutes = [
path: 'saju/result', path: 'saju/result',
element: <SajuResult />, element: <SajuResult />,
}, },
{
path: 'saju/today',
element: <SajuToday />,
},
{ {
path: 'saju/compatibility', path: 'saju/compatibility',
element: <Compatibility />, element: <Compatibility />,