feat(saju-ui-v2): Today.jsx v2 진입 + 4 state branches
This commit is contained in:
@@ -1,138 +1,73 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useSearchParams, Link } from 'react-router-dom';
|
import { useSearchParams, Link } from 'react-router-dom';
|
||||||
import './Saju.css';
|
import './_shell/tokens.css';
|
||||||
import SajuNav from './components/SajuNav';
|
import './_shell/shell.css';
|
||||||
import HoryungMascot from './components/HoryungMascot';
|
import useViewportMode from './_shell/useViewportMode';
|
||||||
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';
|
import useSajuReading from './hooks/useSajuReading';
|
||||||
|
import BottomNav from './_shell/BottomNav';
|
||||||
|
import DesktopHeader from './_shell/DesktopHeader';
|
||||||
|
import Mascot from './_shell/Mascot';
|
||||||
|
import MascotBubble from './_shell/MascotBubble';
|
||||||
|
import PrimaryButton from './_shell/PrimaryButton';
|
||||||
|
import GhostButton from './_shell/GhostButton';
|
||||||
|
import TodayMobile from './views/today.mobile.jsx';
|
||||||
|
import TodayDesktop from './views/today.desktop.jsx';
|
||||||
|
|
||||||
export default function Today() {
|
export default function Today() {
|
||||||
|
const mode = useViewportMode();
|
||||||
const [params] = useSearchParams();
|
const [params] = useSearchParams();
|
||||||
const rid = params.get('rid');
|
const rid = params.get('rid');
|
||||||
const ridNum = rid ? parseInt(rid, 10) : null;
|
const ridNum = rid ? parseInt(rid, 10) : null;
|
||||||
const { data, loading, error } = useSajuReading(ridNum);
|
const { data, loading, error } = useSajuReading(ridNum);
|
||||||
|
|
||||||
if (!rid) {
|
|
||||||
return (
|
return (
|
||||||
<div className="saju-page">
|
<div className="saju-v2">
|
||||||
<SajuNav />
|
{mode === 'desktop' && <DesktopHeader />}
|
||||||
<div className="saju-stub">
|
{!rid && <EmptyState />}
|
||||||
<HoryungMascot pose="thinking" />
|
{rid && loading && <LoadingState />}
|
||||||
<h2 className="saju-h2">사주가 필요해요</h2>
|
{rid && error && <ErrorState />}
|
||||||
<p>오늘의 운세를 보려면 먼저 사주를 입력해주세요.</p>
|
{rid && data && (mode === 'desktop'
|
||||||
<Link to="/saju">사주 입력하러 가기</Link>
|
? <TodayDesktop reading={data} />
|
||||||
</div>
|
: <TodayMobile reading={data} />
|
||||||
</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>
|
|
||||||
)}
|
)}
|
||||||
|
{mode === 'mobile' && <BottomNav theme="ivory" />}
|
||||||
{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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function EmptyState() {
|
||||||
|
return (
|
||||||
|
<main className="page paper-bg screen-in" style={{ padding: '40px 24px', textAlign: 'center' }}>
|
||||||
|
<Mascot variant="greeting" size={160} style={{ margin: '0 auto 16px' }} />
|
||||||
|
<MascotBubble tone="ivory" tail={false}
|
||||||
|
text="사주를 먼저 입력해주세요."
|
||||||
|
style={{ margin: '0 auto 24px' }} />
|
||||||
|
<Link to="/saju" style={{ display: 'inline-block' }}>
|
||||||
|
<PrimaryButton color="#D4AF37" full={false}>사주 입력하러 가기</PrimaryButton>
|
||||||
|
</Link>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function LoadingState() {
|
||||||
|
return (
|
||||||
|
<main className="page paper-bg screen-in" style={{ padding: '60px 24px', textAlign: 'center' }}>
|
||||||
|
<Mascot variant="thinking" size={160} style={{ margin: '0 auto 16px' }} />
|
||||||
|
<MascotBubble tone="ivory" tail={false}
|
||||||
|
text="호령이 오늘 운세를 살펴보고 있어요..."
|
||||||
|
style={{ margin: '0 auto' }} />
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ErrorState() {
|
||||||
|
return (
|
||||||
|
<main className="page paper-bg screen-in" style={{ padding: '40px 24px', textAlign: 'center' }}>
|
||||||
|
<Mascot variant="thinking" size={140} style={{ margin: '0 auto 16px' }} />
|
||||||
|
<MascotBubble tone="ivory" tail={false}
|
||||||
|
text="오늘 운세를 가져오지 못했어요."
|
||||||
|
style={{ margin: '0 auto 20px' }} />
|
||||||
|
<GhostButton color="#D4AF37" full={false} onClick={() => window.location.reload()}>다시 시도</GhostButton>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user