chore: clean up stale task files and update ManualDivinationPage
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import Icon from './Icon';
|
||||
|
||||
interface Props {
|
||||
@@ -7,166 +7,347 @@ interface Props {
|
||||
divination: { questionTitle: string; questionPlaceholder: string; categoryLabel: string; categories: string; timeTitle: string; timeHint: string; guideTitle: string; guideManual: string; yaoTitle: string; coinLabel: string; confirmBtn: string; summaryTitle: string; checkCategory: string; checkMethod: string; checkCost: string; submitBtn: string; progressLabel: string };
|
||||
}
|
||||
|
||||
type CoinFace = '字' | '花';
|
||||
type CoinFace = 'zi' | 'hua';
|
||||
type YaoType = 'youngYang' | 'youngYin' | 'oldYang' | 'oldYin';
|
||||
|
||||
function CoinImage({ face, size = 'w-16 h-16' }: { face: CoinFace; size?: string }) {
|
||||
const TOTAL_YAO_COUNT = 6;
|
||||
|
||||
function formatDateTimeInput(value: Date): string {
|
||||
const pad = (n: number) => String(n).padStart(2, '0');
|
||||
return `${value.getFullYear()}-${pad(value.getMonth() + 1)}-${pad(value.getDate())}T${pad(value.getHours())}:${pad(value.getMinutes())}`;
|
||||
}
|
||||
|
||||
function fromHuaCount(huaCount: number): YaoType {
|
||||
switch (huaCount) {
|
||||
case 0:
|
||||
return 'oldYin';
|
||||
case 1:
|
||||
return 'youngYang';
|
||||
case 2:
|
||||
return 'youngYin';
|
||||
case 3:
|
||||
return 'oldYang';
|
||||
default:
|
||||
throw new RangeError('huaCount must be 0..3');
|
||||
}
|
||||
}
|
||||
|
||||
function fromCoins(coins: CoinFace[]): YaoType {
|
||||
return fromHuaCount(coins.filter((coin) => coin === 'hua').length);
|
||||
}
|
||||
|
||||
function CoinImage({ face, selected }: { face: CoinFace; selected?: boolean }) {
|
||||
return (
|
||||
<img
|
||||
src={face === '字' ? '/images/qigua/zi.jpg' : '/images/qigua/hua.jpg'}
|
||||
alt={face}
|
||||
className={`${size} rounded-full object-cover border border-amber-300 shadow-sm`}
|
||||
src={face === 'zi' ? '/images/qigua/zi.jpg' : '/images/qigua/hua.jpg'}
|
||||
alt={face === 'zi' ? '字' : '花'}
|
||||
className={`h-20 w-20 rounded-full object-cover shadow-sm transition-transform ${selected ? 'ring-2 ring-violet-600 ring-offset-2 ring-offset-slate-50' : ''}`}
|
||||
draggable={false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function YaoGlyph({ type, active }: { type?: YaoType; active?: boolean }) {
|
||||
const color = active || type ? 'bg-violet-700' : 'bg-slate-200';
|
||||
|
||||
if (!type || type === 'youngYang' || type === 'oldYang') {
|
||||
return <div className={`h-2.5 w-full rounded-full ${color}`} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-2.5 w-full gap-4">
|
||||
<div className={`h-2.5 flex-1 rounded-full ${color}`} />
|
||||
<div className={`h-2.5 flex-1 rounded-full ${color}`} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const copy = {
|
||||
zh: {
|
||||
title: '手动起卦',
|
||||
subtitle: '准备三枚相同的钱币,从初爻到上爻依次录入六次结果。',
|
||||
balance: '可用 120 积分 · 本次 20 积分',
|
||||
defaultQuestion: '我接下来三个月的事业发展需要注意什么?',
|
||||
modify: '修改',
|
||||
guideLines: ['从初爻开始,按从下往上的顺序记录。', '每一爻由三枚钱币的字面/花面组合决定。', '六爻完成后才可开始解卦。'],
|
||||
openGuide: '查看手动起卦教程',
|
||||
guideSteps: [
|
||||
['手动起卦', '准备三枚相同的钱币。每次记录一爻,按从下往上的顺序共记录六爻。'],
|
||||
['确认时间', '先确认起卦时间。如需调整,点击右侧「修改」。'],
|
||||
['依次录入六爻', '从初爻开始逐条选择,未完成前下一爻不可点。每条会弹出三枚钱币选择面板。'],
|
||||
['开始分析', '六爻都填完后,「开始解卦」按钮会高亮提示,点击即可解卦。'],
|
||||
],
|
||||
closeGuide: '结束教程',
|
||||
nextGuide: '下一步',
|
||||
prevGuide: '上一步',
|
||||
lineNames: ['初爻', '二爻', '三爻', '四爻', '五爻', '上爻'],
|
||||
pending: '待录入',
|
||||
zi: '字',
|
||||
hua: '花',
|
||||
yaoTypeNames: { youngYang: '少阳', youngYin: '少阴', oldYang: '老阳', oldYin: '老阴' },
|
||||
questionTypePrefix: '问题类型',
|
||||
method: '起卦方式:手动起卦',
|
||||
submit: '开始解卦',
|
||||
},
|
||||
zh_Hant: {
|
||||
title: '手動起卦',
|
||||
subtitle: '準備三枚相同的錢幣,從初爻到上爻依序錄入六次結果。',
|
||||
balance: '可用 120 積分 · 本次 20 積分',
|
||||
defaultQuestion: '我接下來三個月的事業發展需要注意什麼?',
|
||||
modify: '修改',
|
||||
guideLines: ['從初爻開始,按從下往上的順序記錄。', '每一爻由三枚錢幣的字面/花面組合決定。', '六爻完成後才可開始解卦。'],
|
||||
openGuide: '查看手動起卦教程',
|
||||
guideSteps: [
|
||||
['手動起卦', '準備三枚相同的錢幣。每次記錄一爻,按從下往上的順序共記錄六爻。'],
|
||||
['確認時間', '先確認起卦時間。如需調整,點擊右側「修改」。'],
|
||||
['依序錄入六爻', '從初爻開始逐條選擇,未完成前下一爻不可點擊。每條會彈出三枚錢幣選擇面板。'],
|
||||
['開始分析', '六爻都填完後,「開始解卦」按鈕會高亮提示,點擊即可解卦。'],
|
||||
],
|
||||
closeGuide: '結束教程',
|
||||
nextGuide: '下一步',
|
||||
prevGuide: '上一步',
|
||||
lineNames: ['初爻', '二爻', '三爻', '四爻', '五爻', '上爻'],
|
||||
pending: '待錄入',
|
||||
zi: '字',
|
||||
hua: '花',
|
||||
yaoTypeNames: { youngYang: '少陽', youngYin: '少陰', oldYang: '老陽', oldYin: '老陰' },
|
||||
questionTypePrefix: '問題類型',
|
||||
method: '起卦方式:手動起卦',
|
||||
submit: '開始解卦',
|
||||
},
|
||||
en: {
|
||||
title: 'Manual Casting',
|
||||
subtitle: 'Prepare three identical coins and record six results from the first yao at the bottom to the top yao.',
|
||||
balance: 'Available 120 credits · This reading 20 credits',
|
||||
defaultQuestion: 'What should I pay attention to in my career development over the next three months?',
|
||||
modify: 'Modify',
|
||||
guideLines: ['Record from the first yao upward.', 'Each yao is determined by the text-side and flower-side combination of three coins.', 'Start interpretation after all six yao are complete.'],
|
||||
openGuide: 'View Manual Casting Guide',
|
||||
guideSteps: [
|
||||
['Manual Casting', 'Prepare three identical coins. Record one yao at a time, from bottom to top, until all six yao are complete.'],
|
||||
['Confirm Time', 'Check the casting time first. Use Modify on the right if you need to adjust it.'],
|
||||
['Fill Six Yao in Order', 'Start from the first yao and complete one row at a time. The next row stays locked until the current row is confirmed.'],
|
||||
['Start Interpretation', 'After all six yao are filled, Start Interpretation becomes active. Select it to continue.'],
|
||||
],
|
||||
closeGuide: 'Finish',
|
||||
nextGuide: 'Next',
|
||||
prevGuide: 'Back',
|
||||
lineNames: ['First Yao', 'Second Yao', 'Third Yao', 'Fourth Yao', 'Fifth Yao', 'Top Yao'],
|
||||
pending: 'Pending',
|
||||
zi: 'Text',
|
||||
hua: 'Flower',
|
||||
yaoTypeNames: { youngYang: 'Young Yang', youngYin: 'Young Yin', oldYang: 'Old Yang', oldYin: 'Old Yin' },
|
||||
questionTypePrefix: 'Category',
|
||||
method: 'Method: Manual Casting',
|
||||
submit: 'Start Interpretation',
|
||||
},
|
||||
} as const;
|
||||
|
||||
export default function ManualDivinationPage({ locale, divination: d }: Props) {
|
||||
const cats = d.categories.split(',');
|
||||
const text = copy[locale as keyof typeof copy] ?? copy.zh;
|
||||
const cats = useMemo(() => d.categories.split(','), [d.categories]);
|
||||
const [category, setCategory] = useState(cats[0]);
|
||||
const [question, setQuestion] = useState('');
|
||||
const [yaoIndex, setYaoIndex] = useState(0);
|
||||
const [coins, setCoins] = useState<[CoinFace, CoinFace, CoinFace]>(['字', '字', '字']);
|
||||
const [yaoResults, setYaoResults] = useState<CoinFace[][]>([]);
|
||||
const [question, setQuestion] = useState(text.defaultQuestion);
|
||||
const [selectedTime, setSelectedTime] = useState(() => formatDateTimeInput(new Date()));
|
||||
const [coins, setCoins] = useState<[CoinFace, CoinFace, CoinFace]>(['zi', 'zi', 'zi']);
|
||||
const [yaoResults, setYaoResults] = useState<YaoType[]>([]);
|
||||
const [guideStep, setGuideStep] = useState<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setCategory(cats[0]);
|
||||
}, [cats]);
|
||||
|
||||
const progress = yaoResults.length;
|
||||
const currentYaoType = fromCoins(coins);
|
||||
const guideOpen = guideStep !== null;
|
||||
const guide = guideOpen ? text.guideSteps[guideStep] : null;
|
||||
|
||||
const flipCoin = (idx: number) => {
|
||||
const next: [CoinFace, CoinFace, CoinFace] = [...coins];
|
||||
next[idx] = next[idx] === '字' ? '花' : '字';
|
||||
setCoins(next);
|
||||
setCoins((current) => {
|
||||
const next = [...current] as [CoinFace, CoinFace, CoinFace];
|
||||
next[idx] = next[idx] === 'zi' ? 'hua' : 'zi';
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
const confirmYao = () => {
|
||||
const newResults = [...yaoResults, [...coins]];
|
||||
setYaoResults(newResults);
|
||||
if (newResults.length < 6) {
|
||||
setYaoIndex(newResults.length);
|
||||
setCoins(['字', '字', '字']);
|
||||
}
|
||||
if (progress >= TOTAL_YAO_COUNT) return;
|
||||
setYaoResults((current) => [...current, currentYaoType]);
|
||||
setCoins(['zi', 'zi', 'zi']);
|
||||
};
|
||||
|
||||
const progress = yaoResults.length;
|
||||
const isYang = (c: CoinFace[]) => c.filter(x => x === '字').length % 2 === 1;
|
||||
const yaoNames = ['初爻', '二爻', '三爻', '四爻', '五爻', '上爻'];
|
||||
const showPreviousGuide = () => setGuideStep((step) => (step === null ? 0 : Math.max(step - 1, 0)));
|
||||
const showNextGuide = () => setGuideStep((step) => (step === null ? 0 : Math.min(step + 1, text.guideSteps.length - 1)));
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-[22px] min-h-full">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-[#333333] text-[28px] font-bold leading-tight">{locale === 'en' ? 'Manual Cast' : d.checkMethod.replace(/^.*:|^.*: /, '')}</h1>
|
||||
<p className="text-[#666666] text-sm mt-1">{locale === 'en' ? 'Prepare three matching coins and enter six results from bottom line to top line.' : '准备三枚相同的钱币,从初爻到上爻依次录入六次结果。'}</p>
|
||||
</div>
|
||||
<div className="hidden md:flex items-center gap-2 h-10 px-3.5 rounded-full bg-white border border-slate-200 text-[#333333] text-[13px] font-semibold">
|
||||
<Icon name="paid" className="w-[18px] h-[18px] text-violet-700" />
|
||||
{locale === 'en' ? 'Available 120 credits · This time 20 credits' : '可用 120 积分 · 本次 20 积分'}
|
||||
</div>
|
||||
<div className="relative flex min-h-full flex-col gap-[22px]">
|
||||
<div className="flex items-center justify-between gap-5">
|
||||
<div className="min-w-0">
|
||||
<h1 className="text-[28px] font-bold leading-tight text-[#333333]">{text.title}</h1>
|
||||
<p className="mt-1 text-sm text-[#666666]">{text.subtitle}</p>
|
||||
</div>
|
||||
<div className="flex flex-col xl:flex-row gap-[22px] min-h-0 flex-1">
|
||||
{/* Left: Question + Time + Guide */}
|
||||
<div className="w-full xl:w-[360px] flex flex-col gap-4 shrink-0">
|
||||
{/* Question panel */}
|
||||
<div className="bg-white rounded-2xl p-[22px] border border-slate-200 flex flex-col gap-4" style={{ height: '300px' }}>
|
||||
<h3 className="text-slate-900 text-lg font-bold">{d.questionTitle}</h3>
|
||||
<div className="flex items-center justify-between h-[42px] px-3 rounded-[10px] bg-slate-50 border border-slate-300">
|
||||
<span className="text-slate-600 text-sm">{category}</span>
|
||||
<select value={category} onChange={e => setCategory(e.target.value)} className="bg-transparent text-sm outline-none cursor-pointer">
|
||||
{cats.map(c => <option key={c} value={c}>{c}</option>)}
|
||||
</select>
|
||||
<div className="hidden h-10 items-center gap-2 rounded-full border border-slate-200 bg-white px-3.5 text-[13px] font-semibold text-[#333333] md:flex">
|
||||
<Icon name="paid" className="h-[18px] w-[18px] text-violet-700" />
|
||||
{text.balance}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex min-h-0 flex-1 flex-col gap-[22px] xl:flex-row">
|
||||
<div className="flex w-full shrink-0 flex-col gap-4 xl:w-[360px]">
|
||||
<section className="flex h-[300px] flex-col gap-4 rounded-2xl border border-slate-200 bg-white p-[22px]">
|
||||
<h2 className="text-lg font-bold text-slate-900">{d.questionTitle}</h2>
|
||||
<label className="sr-only" htmlFor="manual-category">{d.categoryLabel}</label>
|
||||
<select
|
||||
id="manual-category"
|
||||
value={category}
|
||||
onChange={(event) => setCategory(event.target.value)}
|
||||
className="h-[42px] rounded-[10px] border border-slate-300 bg-slate-50 px-3 text-sm font-bold text-[#333333] outline-none focus:border-violet-500"
|
||||
>
|
||||
{cats.map((cat) => <option key={cat} value={cat}>{cat}</option>)}
|
||||
</select>
|
||||
<textarea
|
||||
value={question}
|
||||
onChange={(event) => setQuestion(event.target.value)}
|
||||
placeholder={d.questionPlaceholder}
|
||||
className="min-h-0 flex-1 resize-none rounded-[10px] border border-slate-300 bg-white px-3.5 py-3 text-sm text-[#333333] outline-none focus:border-violet-500"
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section className={`flex h-[132px] flex-col gap-3 rounded-2xl border bg-white p-5 ${guideStep === 1 ? 'border-violet-500 ring-4 ring-violet-100' : 'border-slate-200'}`}>
|
||||
<h2 className="text-base font-bold text-slate-900">{d.timeTitle}</h2>
|
||||
<div className="flex h-[42px] items-center justify-between gap-3 rounded-[10px] bg-slate-50 px-3">
|
||||
<input
|
||||
type="datetime-local"
|
||||
value={selectedTime}
|
||||
onChange={(event) => setSelectedTime(event.target.value)}
|
||||
className="w-full bg-transparent text-sm font-semibold text-[#333333] outline-none"
|
||||
/>
|
||||
<span className="shrink-0 text-[13px] font-bold text-violet-700">{text.modify}</span>
|
||||
</div>
|
||||
<textarea value={question} onChange={e => setQuestion(e.target.value)} placeholder={d.questionPlaceholder}
|
||||
className="flex-1 w-full px-3.5 py-3 rounded-[10px] bg-white border border-slate-300 text-sm focus:outline-none focus:border-violet-400 resize-none" />
|
||||
</div>
|
||||
{/* Time panel */}
|
||||
<div className="bg-white rounded-2xl p-5 border border-slate-200 flex flex-col gap-3" style={{ height: '132px' }}>
|
||||
<h3 className="text-slate-900 text-base font-bold">{d.timeTitle}</h3>
|
||||
<div className="flex items-center justify-between h-[42px] px-3 rounded-[10px] bg-slate-50">
|
||||
<input type="datetime-local" className="bg-transparent text-sm outline-none w-full" />
|
||||
<Icon name="calendar_today" className="w-[18px] h-[18px] text-slate-400" />
|
||||
</div>
|
||||
</div>
|
||||
{/* Guide panel */}
|
||||
<div className="bg-white rounded-2xl p-5 border border-slate-200 flex flex-col gap-3 flex-1 overflow-y-auto" style={{ height: '214px' }}>
|
||||
<h3 className="text-slate-900 text-base font-bold">{d.guideTitle}</h3>
|
||||
<p className="text-slate-500 text-[13px] whitespace-pre-line">{d.guideManual}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className={`flex h-[214px] flex-col gap-3 rounded-2xl border bg-white p-5 ${guideStep === 0 ? 'border-violet-500 ring-4 ring-violet-100' : 'border-slate-200'}`}>
|
||||
<h2 className="text-base font-bold text-slate-900">{d.guideTitle}</h2>
|
||||
{text.guideLines.map((line) => <p key={line} className="text-[13px] leading-relaxed text-[#666666]">{line}</p>)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setGuideStep(0)}
|
||||
className="mt-auto flex h-8 w-fit items-center gap-2 rounded-full bg-violet-50 px-3 text-[13px] font-bold text-violet-700"
|
||||
>
|
||||
<Icon name="notifications" className="h-[18px] w-[18px]" />
|
||||
{text.openGuide}
|
||||
</button>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{/* Center: Yao panel */}
|
||||
<div className="flex-1 bg-white rounded-2xl p-6 border border-slate-200 flex flex-col gap-4 overflow-y-auto">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-slate-900 text-lg font-bold">{d.yaoTitle}</h3>
|
||||
<span className="text-violet-600 text-[13px] font-bold">{progress} / 6</span>
|
||||
<section className={`flex min-w-0 flex-1 flex-col gap-4 rounded-2xl border bg-white p-6 ${guideStep === 2 ? 'border-violet-500 ring-4 ring-violet-100' : 'border-slate-200'}`}>
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<h2 className="text-lg font-bold text-slate-900">{d.yaoTitle}</h2>
|
||||
<span className="text-[13px] font-bold text-violet-700">{progress} / {TOTAL_YAO_COUNT}</span>
|
||||
</div>
|
||||
|
||||
{/* 6 Yao rows - from bottom to top (上爻 at top) */}
|
||||
<div className="flex flex-col gap-2.5">
|
||||
{[5, 4, 3, 2, 1, 0].map((i) => {
|
||||
const result = yaoResults[i];
|
||||
const isCurrent = i === yaoIndex && progress < 6;
|
||||
const isDone = result !== undefined;
|
||||
{[5, 4, 3, 2, 1, 0].map((index) => {
|
||||
const result = yaoResults[index];
|
||||
const active = index === progress && progress < TOTAL_YAO_COUNT;
|
||||
return (
|
||||
<div key={i}
|
||||
className={`flex items-center gap-4 h-[62px] px-3.5 rounded-[10px] ${isCurrent ? 'bg-violet-50 border border-violet-400' : isDone ? 'bg-slate-50' : 'bg-slate-50 border border-slate-200'}`}>
|
||||
<span className="text-slate-400 text-xs font-medium w-8">{yaoNames[i]}</span>
|
||||
{isDone ? (
|
||||
<div className="flex gap-2">
|
||||
{result.map((face, ci) => (
|
||||
<CoinImage key={ci} face={face} size="w-8 h-8" />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-slate-300 text-xs">待录入</span>
|
||||
)}
|
||||
{isDone && (
|
||||
<div className="ml-auto">
|
||||
{isYang(result) ? (
|
||||
<div className="w-12 h-2 bg-violet-600 rounded" />
|
||||
) : (
|
||||
<div className="flex gap-1.5"><div className="w-4 h-2 bg-violet-400 rounded" /><div className="w-4 h-2 bg-violet-400 rounded" /></div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
key={index}
|
||||
className={`flex h-[62px] items-center gap-4 rounded-[10px] px-3.5 ${active ? 'border border-violet-600 bg-violet-50' : result ? 'border border-slate-200 bg-white' : 'bg-slate-50'}`}
|
||||
>
|
||||
<span className={`w-16 text-sm font-bold ${active || result ? 'text-violet-700' : 'text-slate-400'}`}>{text.lineNames[index]}</span>
|
||||
<div className="min-w-0 flex-1">
|
||||
<YaoGlyph type={result} active={active} />
|
||||
</div>
|
||||
<span className={`w-20 text-right text-[13px] font-bold ${result ? 'text-violet-700' : 'text-slate-400'}`}>
|
||||
{result ? text.yaoTypeNames[result] : text.pending}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Coin selector */}
|
||||
{progress < 6 && (
|
||||
<div className="bg-slate-50 rounded-xl p-4 flex flex-col items-center gap-4" style={{ minHeight: '142px' }}>
|
||||
<div className="flex items-center gap-6">
|
||||
{coins.map((face, ci) => (
|
||||
<div key={ci} className="flex flex-col items-center gap-2" onClick={() => flipCoin(ci)} style={{ cursor: 'pointer', width: '86px' }}>
|
||||
<div className={`w-16 h-16 rounded-full border-2 flex items-center justify-center text-lg font-bold transition-all cursor-pointer select-none ${face === '字' ? 'bg-amber-100 border-amber-400 text-amber-700' : 'bg-slate-200 border-slate-400 text-slate-600'}`}>
|
||||
<CoinImage face={face} />
|
||||
</div>
|
||||
<span className="text-slate-400 text-xs">{face === '字' ? '正面' : '反面'}</span>
|
||||
</div>
|
||||
))}
|
||||
{progress < TOTAL_YAO_COUNT && (
|
||||
<>
|
||||
<div className="flex min-h-[142px] items-center justify-center rounded-xl bg-slate-50 p-4">
|
||||
<div className="flex items-center justify-center gap-6">
|
||||
{coins.map((face, index) => (
|
||||
<button
|
||||
key={index}
|
||||
type="button"
|
||||
onClick={() => flipCoin(index)}
|
||||
className="flex w-20 flex-col items-center gap-2 text-[13px] font-bold text-slate-600"
|
||||
>
|
||||
<CoinImage face={face} selected={face === 'hua'} />
|
||||
<span>{face === 'zi' ? text.zi : text.hua}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{progress < 6 && (
|
||||
<button onClick={confirmYao} className="w-full h-10 rounded-xl bg-violet-600 text-white text-[13px] font-bold hover:bg-violet-700 transition-colors">{d.confirmBtn}</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="rounded-xl border border-violet-100 bg-violet-50 px-4 py-3 text-center text-[13px] font-semibold text-violet-800">
|
||||
{text.yaoTypeNames[currentYaoType]}
|
||||
</div>
|
||||
|
||||
{/* Right: Summary */}
|
||||
<div className="w-full xl:w-[300px] bg-white rounded-2xl p-[22px] border border-slate-200 flex flex-col gap-[18px] shrink-0">
|
||||
<h3 className="text-slate-900 text-lg font-bold">{d.summaryTitle}</h3>
|
||||
{/* Progress */}
|
||||
<div className="bg-slate-50 rounded-xl p-4 flex flex-col gap-2">
|
||||
<p className="text-slate-500 text-[13px]">{d.progressLabel}</p>
|
||||
<p className="text-violet-600 text-[28px] font-bold">{progress} / 6</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={confirmYao}
|
||||
className="h-10 w-full rounded-full bg-violet-700 text-[13px] font-bold text-white transition-colors hover:bg-violet-800"
|
||||
>
|
||||
{d.confirmBtn}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<aside className="flex w-full shrink-0 flex-col gap-[18px] rounded-2xl border border-slate-200 bg-white p-[22px] xl:w-[300px]">
|
||||
<h2 className="text-lg font-bold text-slate-900">{d.summaryTitle}</h2>
|
||||
<div className="flex h-[94px] flex-col gap-2 rounded-xl bg-slate-50 p-4">
|
||||
<p className="text-[13px] text-[#666666]">{d.progressLabel}</p>
|
||||
<p className="text-[28px] font-bold text-violet-700">{progress} / {TOTAL_YAO_COUNT}</p>
|
||||
</div>
|
||||
<p className="text-slate-500 text-sm">{d.checkCategory}</p>
|
||||
<p className="text-slate-500 text-sm">{d.checkMethod}</p>
|
||||
<p className="text-slate-500 text-sm">{d.checkCost}</p>
|
||||
<p className="text-sm text-[#666666]">{text.questionTypePrefix}{locale === 'en' ? ': ' : ':'}{category}</p>
|
||||
<p className="text-sm text-[#666666]">{text.method}</p>
|
||||
<p className="text-sm text-[#666666]">{d.checkCost}</p>
|
||||
<div className="flex-1" />
|
||||
<button disabled={progress < 6}
|
||||
className={`w-full h-[46px] rounded-full text-sm font-bold transition-colors ${progress >= 6 ? 'bg-violet-600 text-white hover:bg-violet-700' : 'bg-slate-300 text-slate-400 cursor-not-allowed'}`}>
|
||||
{d.submitBtn}
|
||||
<button
|
||||
type="button"
|
||||
disabled={progress < TOTAL_YAO_COUNT}
|
||||
className={`h-[46px] w-full rounded-full text-sm font-bold transition-colors ${progress >= TOTAL_YAO_COUNT ? 'bg-violet-700 text-white hover:bg-violet-800' : 'cursor-not-allowed bg-slate-300 text-slate-400'} ${guideStep === 3 ? 'ring-4 ring-violet-100' : ''}`}
|
||||
>
|
||||
{text.submit}
|
||||
</button>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
{guideOpen && guide && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 p-5">
|
||||
<div className="w-full max-w-md rounded-2xl border border-white/10 bg-slate-950 p-6 text-white shadow-2xl">
|
||||
<div className="mb-4 flex items-center justify-between gap-4">
|
||||
<span className="text-sm font-bold text-violet-200">{guideStep + 1} / {text.guideSteps.length}</span>
|
||||
<button type="button" onClick={() => setGuideStep(null)} className="rounded-full p-1 text-white/70 hover:bg-white/10 hover:text-white">
|
||||
<Icon name="close" className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
<h2 className="text-xl font-bold">{guide[0]}</h2>
|
||||
<p className="mt-3 text-sm leading-6 text-white/80">{guide[1]}</p>
|
||||
<div className="mt-6 flex items-center justify-between gap-3">
|
||||
<button type="button" onClick={showPreviousGuide} disabled={guideStep === 0} className="h-10 rounded-full px-4 text-sm font-bold text-white/70 disabled:opacity-40">
|
||||
{text.prevGuide}
|
||||
</button>
|
||||
{guideStep === text.guideSteps.length - 1 ? (
|
||||
<button type="button" onClick={() => setGuideStep(null)} className="h-10 rounded-full bg-white px-5 text-sm font-bold text-slate-950">
|
||||
{text.closeGuide}
|
||||
</button>
|
||||
) : (
|
||||
<button type="button" onClick={showNextGuide} className="h-10 rounded-full bg-white px-5 text-sm font-bold text-slate-950">
|
||||
{text.nextGuide}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user