feat(tarot): TodayCard.jsx — 원카드 페이지 (T14)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
98
src/pages/tarot/TodayCard.jsx
Normal file
98
src/pages/tarot/TodayCard.jsx
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import './Tarot.css';
|
||||||
|
import { TAROT_DECK, CATEGORIES } from './data/cards';
|
||||||
|
import { useTarotReading } from './hooks/useTarotReading';
|
||||||
|
import TarotCard from './components/TarotCard';
|
||||||
|
import InterpretationPanel from './components/InterpretationPanel';
|
||||||
|
|
||||||
|
export default function TodayCard() {
|
||||||
|
const [category, setCategory] = useState('일반');
|
||||||
|
const [question, setQuestion] = useState('');
|
||||||
|
const [pick, setPick] = useState(null);
|
||||||
|
const { status, interpretation, runInterpretAndSave, error } = useTarotReading();
|
||||||
|
|
||||||
|
const drawCard = () => {
|
||||||
|
const idx = Math.floor(Math.random() * TAROT_DECK.length);
|
||||||
|
const reversed = Math.random() < 0.5;
|
||||||
|
const card = TAROT_DECK[idx];
|
||||||
|
setPick({ card, position: '오늘', reversed });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleStart = () => {
|
||||||
|
drawCard();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInterpret = async () => {
|
||||||
|
if (!pick) return;
|
||||||
|
try {
|
||||||
|
await runInterpretAndSave({
|
||||||
|
spread_type: 'one_card',
|
||||||
|
category,
|
||||||
|
question: question.trim() || null,
|
||||||
|
picks: [pick],
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// error는 hook의 state로 전달됨
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectedCardMeaning = pick?.card || null;
|
||||||
|
const focusCardId = pick?.card?.slug;
|
||||||
|
|
||||||
|
const busy = status === 'interpreting' || status === 'saving';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="tarot tarot-reading">
|
||||||
|
<aside className="tarot-reading__col">
|
||||||
|
<div className="tarot-reading__step-label">오늘의 카드</div>
|
||||||
|
<label className="tarot-reading__step-label">질문 (선택)</label>
|
||||||
|
<textarea
|
||||||
|
className="tarot-reading__textarea"
|
||||||
|
value={question}
|
||||||
|
onChange={(e) => setQuestion(e.target.value)}
|
||||||
|
placeholder="오늘 무엇이 궁금한가요?"
|
||||||
|
/>
|
||||||
|
<div className="tarot-reading__step-label" style={{ marginTop: 16 }}>카테고리</div>
|
||||||
|
<div className="tarot-reading__chips">
|
||||||
|
{CATEGORIES.map((c) => (
|
||||||
|
<button
|
||||||
|
key={c}
|
||||||
|
className={`tarot-chip ${category === c ? 'is-active' : ''}`}
|
||||||
|
onClick={() => setCategory(c)}
|
||||||
|
>{c}</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{!pick && (
|
||||||
|
<button className="tarot-reading__primary" onClick={handleStart}>
|
||||||
|
카드 뽑기
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{pick && !interpretation && (
|
||||||
|
<button className="tarot-reading__primary" onClick={handleInterpret} disabled={busy}>
|
||||||
|
{busy ? '해석 중…' : 'AI 해석 시작'}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{pick && interpretation && (
|
||||||
|
<button className="tarot-reading__primary" onClick={() => { setPick(null); }}>
|
||||||
|
다시 뽑기
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{error && <p style={{ color: '#f43f5e', marginTop: 12, fontSize: 13 }}>오류: {error}</p>}
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<div className="tarot-reading__col" style={{ display: 'grid', placeItems: 'center', minHeight: 320 }}>
|
||||||
|
{pick ? (
|
||||||
|
<TarotCard card={pick.card} reversed={pick.reversed} size="lg" label={pick.position} />
|
||||||
|
) : (
|
||||||
|
<p style={{ color: 'var(--tarot-text-dim)' }}>좌측에서 "카드 뽑기"를 눌러보세요.</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<InterpretationPanel
|
||||||
|
interpretation={interpretation}
|
||||||
|
selectedCard={selectedCardMeaning}
|
||||||
|
focusCardId={focusCardId}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user