feat(saju-ui-v2): SajuResult.jsx v2 진입 + Empty/Loading/Error state
This commit is contained in:
@@ -1,142 +1,73 @@
|
||||
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 SajuPillars from './components/SajuPillars';
|
||||
import ElementBarChart from './components/ElementBarChart';
|
||||
import InterpretAccordion from './components/InterpretAccordion';
|
||||
import HoryungQuote from './components/HoryungQuote';
|
||||
import MonthlyFlow from './components/MonthlyFlow';
|
||||
import './_shell/tokens.css';
|
||||
import './_shell/shell.css';
|
||||
import useViewportMode from './_shell/useViewportMode';
|
||||
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 SajuMobile from './views/saju.mobile.jsx';
|
||||
import SajuDesktop from './views/saju.desktop.jsx';
|
||||
|
||||
export default function SajuResult() {
|
||||
const mode = useViewportMode();
|
||||
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 className="saju-v2">
|
||||
{mode === 'desktop' && <DesktopHeader />}
|
||||
{!rid && <EmptyState />}
|
||||
{rid && loading && <LoadingState />}
|
||||
{rid && error && <ErrorState />}
|
||||
{rid && data && (mode === 'desktop'
|
||||
? <SajuDesktop reading={data} />
|
||||
: <SajuMobile reading={data} />
|
||||
)}
|
||||
{mode === 'mobile' && <BottomNav theme="ivory" />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
function EmptyState() {
|
||||
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 saju = data.saju_data;
|
||||
const analysis = data.analysis_data;
|
||||
const interp = data.interpretation_json;
|
||||
const monthlyFlow = data.monthly_flow;
|
||||
|
||||
return (
|
||||
<div className="saju-page">
|
||||
<SajuNav />
|
||||
|
||||
<section className="saju-hero">
|
||||
<div className="saju-hero__left">
|
||||
<HoryungMascot pose="thinking" size="lg" />
|
||||
</div>
|
||||
<div className="saju-hero__right">
|
||||
<h1 className="saju-h1">사주풀이</h1>
|
||||
<p className="saju-sub">
|
||||
{data.birth_year}년 {data.birth_month}월 {data.birth_day}일
|
||||
{data.birth_hour !== null ? ` ${data.birth_hour}시` : ' (시간 미상)'} ·{' '}
|
||||
{data.gender === 'male' ? '남' : '여'} ·{' '}
|
||||
{data.calendar_type === 'lunar' ? '음력' : '양력'}
|
||||
</p>
|
||||
{interp?.summary && (
|
||||
<HoryungQuote pose="thinking" text={interp.summary} />
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section style={{ padding: '0 2rem', maxWidth: 1400, margin: '0 auto', display: 'grid', gap: '2rem' }}>
|
||||
<div>
|
||||
<h2 className="saju-h2" style={{ marginBottom: '1rem' }}>사주 4기둥</h2>
|
||||
<SajuPillars saju={saju} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 className="saju-h2" style={{ marginBottom: '1rem' }}>오행 분석</h2>
|
||||
<ElementBarChart scores={analysis?.element_scores} />
|
||||
</div>
|
||||
|
||||
{analysis?.day_master_strength && (
|
||||
<div>
|
||||
<h2 className="saju-h2" style={{ marginBottom: '1rem' }}>일간 강도</h2>
|
||||
<div className="saju-quote-box" style={{ maxWidth: 'none' }}>
|
||||
<p style={{ margin: 0 }}>
|
||||
<strong>{analysis.day_master_strength.result}</strong> · 점수 {analysis.day_master_strength.score}<br />
|
||||
{(analysis.day_master_strength.reasons || []).join(' · ')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{monthlyFlow && (
|
||||
<div>
|
||||
<h2 className="saju-h2" style={{ marginBottom: '1rem' }}>12개월 운세 흐름</h2>
|
||||
<MonthlyFlow flow={monthlyFlow} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{interp?.items && (
|
||||
<div>
|
||||
<h2 className="saju-h2" style={{ marginBottom: '1rem' }}>AI 12항목 해석</h2>
|
||||
<InterpretAccordion items={interp.items} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{interp?.advice && (
|
||||
<div>
|
||||
<h2 className="saju-h2" style={{ marginBottom: '1rem' }}>호령의 조언</h2>
|
||||
<HoryungQuote pose="happy" text={interp.advice} />
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section style={{ padding: '3rem 2rem', display: 'flex', gap: '1rem', justifyContent: 'center' }}>
|
||||
<Link to={`/saju/today?rid=${rid}`} className="saju-action-card saju-action-card--today" style={{ maxWidth: 240 }}>
|
||||
<span className="saju-action-card__icon">☀</span>
|
||||
<span className="saju-action-card__title">오늘의 운세</span>
|
||||
<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="#6A4C7C" full={false}>사주 입력하러 가기</PrimaryButton>
|
||||
</Link>
|
||||
<Link to="/saju" 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>
|
||||
</section>
|
||||
</div>
|
||||
</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="purple" tail={false}
|
||||
text={'호령이 풀이 중이에요...\n(최대 1분 정도 걸려요)'}
|
||||
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="purple" tail={false}
|
||||
text="아이고, 풀이를 가져오지 못했어요. 다시 시도해주세요."
|
||||
style={{ margin: '0 auto 20px' }} />
|
||||
<GhostButton color="#6A4C7C" full={false} onClick={() => window.location.reload()}>다시 시도</GhostButton>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user