feat: add admin SettingsPage for configurable pricing
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
import { useState } from 'react';
|
||||
import { Save, DollarSign } from 'lucide-react';
|
||||
import { useSettings } from './useSettings';
|
||||
|
||||
const inputStyle = {
|
||||
width: '100%', padding: '10px 12px',
|
||||
background: 'var(--bg-lighter)',
|
||||
border: '1.5px solid var(--border-light)',
|
||||
borderRadius: '6px',
|
||||
color: 'var(--text-main)', fontSize: '0.92rem',
|
||||
outline: 'none', fontFamily: 'var(--font-body)',
|
||||
boxSizing: 'border-box',
|
||||
};
|
||||
|
||||
const labelStyle = {
|
||||
display: 'block', marginBottom: '5px',
|
||||
fontSize: '0.75rem', fontWeight: 700,
|
||||
color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.4px',
|
||||
};
|
||||
|
||||
const SettingsPage = () => {
|
||||
const { prices, loading, error: fetchError, updatePrice } = useSettings();
|
||||
const [form, setForm] = useState(null);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [savedMsg, setSavedMsg] = useState('');
|
||||
const [saveError, setSaveError] = useState('');
|
||||
|
||||
// form is null until user edits; fall back to loaded prices
|
||||
const formValues = form ?? prices;
|
||||
|
||||
const handleChange = (thickness, value) => {
|
||||
setForm((prev) => ({ ...(prev ?? prices), [thickness]: Number(value) }));
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
setSaving(true);
|
||||
setSavedMsg('');
|
||||
setSaveError('');
|
||||
const [r200, r300] = await Promise.all([
|
||||
updatePrice('price_200', formValues[200]),
|
||||
updatePrice('price_300', formValues[300]),
|
||||
]);
|
||||
setSaving(false);
|
||||
if (r200.error || r300.error) {
|
||||
setSaveError(r200.error?.message ?? r300.error?.message ?? 'Ошибка сохранения');
|
||||
} else {
|
||||
setSavedMsg('Цены сохранены');
|
||||
setTimeout(() => setSavedMsg(''), 3000);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return <div style={{ padding: '30px', color: 'var(--text-muted)' }}>Загрузка...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ padding: '30px', maxWidth: '480px' }}>
|
||||
<h1 style={{ fontFamily: 'var(--font-heading)', fontSize: '1.5rem', fontWeight: 800, marginBottom: '24px' }}>
|
||||
Настройки
|
||||
</h1>
|
||||
|
||||
<div className="glass-panel" style={{ padding: '28px' }}>
|
||||
<h2 style={{
|
||||
fontFamily: 'var(--font-heading)', fontSize: '1rem', fontWeight: 700,
|
||||
marginBottom: '20px', display: 'flex', alignItems: 'center', gap: '8px',
|
||||
}}>
|
||||
<DollarSign size={16} color="var(--accent-blue)" />
|
||||
Цены на бронирование (₽/м²)
|
||||
</h2>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px', marginBottom: '20px' }}>
|
||||
<div>
|
||||
<label style={labelStyle}>200 микрон</label>
|
||||
<input
|
||||
type="number"
|
||||
value={formValues[200]}
|
||||
onChange={(e) => handleChange(200, e.target.value)}
|
||||
style={inputStyle}
|
||||
min="0"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label style={labelStyle}>300 микрон</label>
|
||||
<input
|
||||
type="number"
|
||||
value={formValues[300]}
|
||||
onChange={(e) => handleChange(300, e.target.value)}
|
||||
style={inputStyle}
|
||||
min="0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{fetchError && (
|
||||
<p style={{ color: '#ef4444', fontSize: '0.85rem', marginBottom: '12px' }}>{fetchError}</p>
|
||||
)}
|
||||
{saveError && (
|
||||
<p style={{ color: '#ef4444', fontSize: '0.85rem', marginBottom: '12px' }}>{saveError}</p>
|
||||
)}
|
||||
{savedMsg && (
|
||||
<p style={{ color: '#22c55e', fontSize: '0.85rem', marginBottom: '12px' }}>{savedMsg}</p>
|
||||
)}
|
||||
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={saving}
|
||||
className="btn-primary"
|
||||
style={{
|
||||
display: 'flex', alignItems: 'center', gap: '8px',
|
||||
opacity: saving ? 0.7 : 1,
|
||||
cursor: saving ? 'not-allowed' : 'pointer',
|
||||
}}
|
||||
>
|
||||
<Save size={15} />
|
||||
{saving ? 'Сохранение...' : 'Сохранить цены'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsPage;
|
||||
Reference in New Issue
Block a user