From 66be5105a85b7e3dda3cbe64937a6ddd4851cdf0 Mon Sep 17 00:00:00 2001 From: gahusb Date: Tue, 26 May 2026 08:27:12 +0900 Subject: [PATCH] feat(saju): useSajuForm + SajuInputForm + ActionCard --- src/pages/saju/components/ActionCard.jsx | 28 +++++++++ src/pages/saju/components/SajuInputForm.jsx | 42 ++++++++++++++ src/pages/saju/hooks/useSajuForm.js | 64 +++++++++++++++++++++ 3 files changed, 134 insertions(+) create mode 100644 src/pages/saju/components/ActionCard.jsx create mode 100644 src/pages/saju/components/SajuInputForm.jsx create mode 100644 src/pages/saju/hooks/useSajuForm.js diff --git a/src/pages/saju/components/ActionCard.jsx b/src/pages/saju/components/ActionCard.jsx new file mode 100644 index 0000000..74965ae --- /dev/null +++ b/src/pages/saju/components/ActionCard.jsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; + +const ICON = { + today: 'β˜€', + heart: 'β™₯', + book: 'πŸ“–', +}; + +export default function ActionCard({ to, icon, title, desc, variant = 'saju', disabled = false }) { + const cls = `saju-action-card saju-action-card--${variant}`; + if (disabled) { + return ( + + {ICON[icon] || '✦'} + {title} + {desc || 'μ€€λΉ„ 쀑'} + + ); + } + return ( + + {ICON[icon] || '✦'} + {title} + {desc} + + ); +} diff --git a/src/pages/saju/components/SajuInputForm.jsx b/src/pages/saju/components/SajuInputForm.jsx new file mode 100644 index 0000000..a3bf0ec --- /dev/null +++ b/src/pages/saju/components/SajuInputForm.jsx @@ -0,0 +1,42 @@ +import React from 'react'; + +export default function SajuInputForm({ form, onChange, onSubmit, loading, error }) { + return ( +
+

+ 사주풀이 μ‹œμž‘ν•˜κΈ° +

+ onChange('name', e.target.value)} + disabled={loading} + /> +
+ onChange('year', e.target.value)} disabled={loading} min="1900" max="2100" /> + onChange('month', e.target.value)} disabled={loading} min="1" max="12" /> + onChange('day', e.target.value)} disabled={loading} min="1" max="31" /> +
+
+ onChange('hour', e.target.value)} disabled={loading} min="0" max="23" /> + + +
+ {error &&
{error}
} + +
+ ); +} diff --git a/src/pages/saju/hooks/useSajuForm.js b/src/pages/saju/hooks/useSajuForm.js new file mode 100644 index 0000000..86f276b --- /dev/null +++ b/src/pages/saju/hooks/useSajuForm.js @@ -0,0 +1,64 @@ +import { useState, useCallback } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { sajuInterpret } from '../../../api'; + +const INITIAL_FORM = { + name: '', + year: '', + month: '', + day: '', + hour: '', + gender: 'male', + calendar_type: 'solar', +}; + +export default function useSajuForm() { + const [form, setForm] = useState(INITIAL_FORM); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const navigate = useNavigate(); + + const handleChange = useCallback((field, value) => { + setForm((prev) => ({ ...prev, [field]: value })); + }, []); + + const handleSubmit = useCallback(async (e) => { + if (e?.preventDefault) e.preventDefault(); + setError(null); + + if (!form.year || !form.month || !form.day) { + setError('생년월일을 λͺ¨λ‘ μž…λ ₯ν•΄μ£Όμ„Έμš”.'); + return; + } + const year = parseInt(form.year, 10); + const month = parseInt(form.month, 10); + const day = parseInt(form.day, 10); + if (year < 1900 || year > 2100 || month < 1 || month > 12 || day < 1 || day > 31) { + setError('μ˜¬λ°”λ₯Έ 생년월일을 μž…λ ₯ν•΄μ£Όμ„Έμš”.'); + return; + } + + setLoading(true); + try { + const body = { + year, + month, + day, + gender: form.gender, + calendar_type: form.calendar_type, + }; + if (form.hour !== '') { + body.hour = parseInt(form.hour, 10); + } + const result = await sajuInterpret(body); + navigate(`/saju/result?rid=${result.reading_id}`); + } catch (err) { + console.error('사주 뢄석 μ‹€νŒ¨', err); + setError(err.message || 'μž μ‹œ ν›„ λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”.'); + } finally { + setLoading(false); + } + }, [form, navigate]); + + return { form, handleChange, handleSubmit, loading, error }; +}