feat(saju-ui-v2): match.mobile.jsx — 두 사람 입력 폼 (PersonForm + IconHeart)

This commit is contained in:
2026-05-27 07:47:07 +09:00
parent e1804ad181
commit 29f37a1642

View File

@@ -0,0 +1,108 @@
import React from 'react';
import TopRibbon from '../_shell/TopRibbon';
import TitleBlock from '../_shell/TitleBlock';
import Mascot from '../_shell/Mascot';
import MascotBubble from '../_shell/MascotBubble';
import OrnateFrame from '../_shell/OrnateFrame';
import PrimaryButton from '../_shell/PrimaryButton';
import InputRow from '../_shell/InputRow';
import { IconHeart, IconSparkle } from '../_shell/Icons';
const inputStyle = {
flex: 1, padding: '8px 10px', border: '1px solid rgba(31,42,68,0.12)',
borderRadius: 8, background: '#FBF7EF', fontSize: 13, color: '#1F2A44',
fontFamily: 'inherit',
};
function pad(n) { return String(n).padStart(2, '0'); }
function dateValue(p) {
if (!p.year || !p.month || !p.day) return '';
return `${p.year}-${pad(p.month)}-${pad(p.day)}`;
}
function timeValue(p) {
if (p.hour === '' || p.hour == null) return '';
return `${pad(p.hour)}:00`;
}
function onDate(p, set, e) {
const v = e.target.value;
if (!v) return set({ ...p, year: '', month: '', day: '' });
const [y, m, d] = v.split('-');
set({ ...p, year: parseInt(y, 10), month: parseInt(m, 10), day: parseInt(d, 10) });
}
function onTime(p, set, e) {
const v = e.target.value;
if (!v) return set({ ...p, hour: null });
const [h] = v.split(':');
set({ ...p, hour: parseInt(h, 10) });
}
function PersonForm({ label, person, onChange }) {
return (
<OrnateFrame color="#4E6B5C" bg="#FBF7EF" radius={14} padding="14px 16px">
<div className="font-title" style={{
fontSize: 14, color: '#4E6B5C', textAlign: 'center', marginBottom: 8,
}}>{label}</div>
<InputRow label="이름">
<input value={person.name || ''}
onChange={(e) => onChange({ ...person, name: e.target.value })}
placeholder="홍길동" style={inputStyle} />
</InputRow>
<InputRow label="생년월일">
<input type="date" value={dateValue(person)}
onChange={(e) => onDate(person, onChange, e)} style={inputStyle} />
</InputRow>
<InputRow label="시간">
<input type="time" value={timeValue(person)}
onChange={(e) => onTime(person, onChange, e)} style={inputStyle} />
</InputRow>
<InputRow label="성별">
<select value={person.gender}
onChange={(e) => onChange({ ...person, gender: e.target.value })}
style={inputStyle}>
<option value="male"></option>
<option value="female"></option>
</select>
</InputRow>
<InputRow label="달력">
<select value={person.calendar_type}
onChange={(e) => onChange({ ...person, calendar_type: e.target.value })}
style={inputStyle}>
<option value="solar">양력</option>
<option value="lunar">음력</option>
</select>
</InputRow>
</OrnateFrame>
);
}
export default function MatchMobile({ personA, personB, onChangeA, onChangeB, onSubmit, loading, error }) {
return (
<main className="page paper-bg screen-in">
<TopRibbon color="#4E6B5C" opacity={0.6} />
<div style={{ padding: '8px 24px 0', textAlign: 'center' }}>
<TitleBlock title="궁합 보기" gold="#4E6B5C"
subtitle="두 사람의 사주를 입력하면 만남의 흐름을 알려드려요." />
</div>
<div style={{ padding: '14px 20px 0', display: 'flex', gap: 8, alignItems: 'flex-end' }}>
<MascotBubble tone="green"
text={'두 사주를 비교해\n어울리는 결을\n읽어드릴게요.'}
style={{ flex: 1, marginBottom: 8 }} />
<Mascot variant="upper" size={120} style={{ marginRight: -8 }} />
</div>
<form onSubmit={onSubmit} style={{ padding: '24px 20px 40px', display: 'grid', gap: 14 }}>
<PersonForm label="사람 A" person={personA} onChange={onChangeA} />
<div style={{ display: 'flex', justifyContent: 'center' }}>
<IconHeart size={28} stroke="#4E6B5C" strokeWidth={2} />
</div>
<PersonForm label="사람 B" person={personB} onChange={onChangeB} />
{error && (
<div style={{ color: '#C04A4A', fontSize: 12, textAlign: 'center' }}>{error}</div>
)}
<PrimaryButton color="#4E6B5C" type="submit">
{loading ? '호령이 두 사주를 비교 중...' : '궁합 보기'}
{!loading && <IconSparkle size={12} color="#E8C76B" />}
</PrimaryButton>
</form>
</main>
);
}