feat: add SettingsPage — kennel name, tagline, address, phone, website, email
This commit is contained in:
160
client/src/pages/SettingsPage.jsx
Normal file
160
client/src/pages/SettingsPage.jsx
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
import { Settings, Save, CheckCircle } from 'lucide-react'
|
||||||
|
import { useSettings } from '../hooks/useSettings'
|
||||||
|
|
||||||
|
const FIELDS = [
|
||||||
|
{ key: 'kennel_name', label: 'Kennel / App Name', placeholder: 'BREEDR', type: 'text', required: true },
|
||||||
|
{ key: 'kennel_tagline', label: 'Tagline', placeholder: 'Raising champions since...', type: 'text' },
|
||||||
|
{ key: 'kennel_address', label: 'Address', placeholder: '123 Main St, City, ST', type: 'text' },
|
||||||
|
{ key: 'kennel_phone', label: 'Phone', placeholder: '(555) 000-0000', type: 'tel' },
|
||||||
|
{ key: 'kennel_email', label: 'Email', placeholder: 'kennel@example.com', type: 'email'},
|
||||||
|
{ key: 'kennel_website', label: 'Website', placeholder: 'https://yourdomain.com', type: 'url' },
|
||||||
|
{ key: 'kennel_akc_id', label: 'AKC Kennel ID', placeholder: 'Optional', type: 'text' },
|
||||||
|
{ key: 'kennel_breed', label: 'Primary Breed', placeholder: 'e.g. Labrador Retriever', type: 'text' },
|
||||||
|
]
|
||||||
|
|
||||||
|
export default function SettingsPage() {
|
||||||
|
const { settings, saveSettings } = useSettings()
|
||||||
|
const [form, setForm] = useState({})
|
||||||
|
const [saving, setSaving] = useState(false)
|
||||||
|
const [saved, setSaved] = useState(false)
|
||||||
|
const [error, setError] = useState(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setForm({
|
||||||
|
kennel_name: settings.kennel_name || '',
|
||||||
|
kennel_tagline: settings.kennel_tagline || '',
|
||||||
|
kennel_address: settings.kennel_address || '',
|
||||||
|
kennel_phone: settings.kennel_phone || '',
|
||||||
|
kennel_email: settings.kennel_email || '',
|
||||||
|
kennel_website: settings.kennel_website || '',
|
||||||
|
kennel_akc_id: settings.kennel_akc_id || '',
|
||||||
|
kennel_breed: settings.kennel_breed || '',
|
||||||
|
})
|
||||||
|
}, [settings])
|
||||||
|
|
||||||
|
const handleChange = (key, value) => {
|
||||||
|
setForm(prev => ({ ...prev, [key]: value }))
|
||||||
|
setSaved(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
if (!form.kennel_name?.trim()) {
|
||||||
|
setError('Kennel name is required.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setSaving(true)
|
||||||
|
setError(null)
|
||||||
|
try {
|
||||||
|
await saveSettings(form)
|
||||||
|
setSaved(true)
|
||||||
|
setTimeout(() => setSaved(false), 3000)
|
||||||
|
} catch (err) {
|
||||||
|
setError('Failed to save settings. Please try again.')
|
||||||
|
} finally {
|
||||||
|
setSaving(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container" style={{ paddingTop: '2rem', paddingBottom: '3rem', maxWidth: '720px' }}>
|
||||||
|
{/* Header */}
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem', marginBottom: '0.5rem' }}>
|
||||||
|
<div style={{
|
||||||
|
width: '2.5rem', height: '2.5rem',
|
||||||
|
borderRadius: 'var(--radius)',
|
||||||
|
background: 'linear-gradient(135deg, var(--primary) 0%, var(--accent) 100%)',
|
||||||
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||||
|
flexShrink: 0,
|
||||||
|
boxShadow: '0 4px 12px rgba(194,134,42,0.3)'
|
||||||
|
}}>
|
||||||
|
<Settings size={18} color="#0e0f0c" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h1 style={{ marginBottom: 0 }}>Settings</h1>
|
||||||
|
<p style={{ color: 'var(--text-secondary)', fontSize: '0.875rem' }}>
|
||||||
|
Kennel profile & app configuration
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="divider" />
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className="card">
|
||||||
|
<h3 style={{ marginBottom: '1.5rem', color: 'var(--primary-light)' }}>Kennel Information</h3>
|
||||||
|
|
||||||
|
{error && <div className="error" style={{ marginBottom: '1rem' }}>{error}</div>}
|
||||||
|
|
||||||
|
<div className="form-grid">
|
||||||
|
{FIELDS.map(field => (
|
||||||
|
<div className="form-group" key={field.key}>
|
||||||
|
<label className="label">
|
||||||
|
{field.label}
|
||||||
|
{field.required && <span style={{ color: 'var(--danger)', marginLeft: '0.25rem' }}>*</span>}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type={field.type || 'text'}
|
||||||
|
className="input"
|
||||||
|
placeholder={field.placeholder}
|
||||||
|
value={form[field.key] || ''}
|
||||||
|
onChange={e => handleChange(field.key, e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="divider" />
|
||||||
|
|
||||||
|
{/* Preview */}
|
||||||
|
{form.kennel_name && (
|
||||||
|
<div style={{
|
||||||
|
marginBottom: '1.5rem',
|
||||||
|
padding: '1rem',
|
||||||
|
background: 'var(--bg-tertiary)',
|
||||||
|
borderRadius: 'var(--radius)',
|
||||||
|
border: '1px solid var(--border)'
|
||||||
|
}}>
|
||||||
|
<p className="label" style={{ marginBottom: '0.5rem' }}>Header Preview</p>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
|
||||||
|
<span style={{
|
||||||
|
fontSize: '1.75rem',
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '-0.025em',
|
||||||
|
background: 'linear-gradient(135deg, #c9940a 0%, #b5620a 50%, #8b2500 100%)',
|
||||||
|
WebkitBackgroundClip: 'text',
|
||||||
|
WebkitTextFillColor: 'transparent',
|
||||||
|
backgroundClip: 'text',
|
||||||
|
}}>
|
||||||
|
{form.kennel_name}
|
||||||
|
</span>
|
||||||
|
{form.kennel_tagline && (
|
||||||
|
<span style={{ color: 'var(--text-muted)', fontSize: '0.8rem', fontStyle: 'italic' }}>
|
||||||
|
— {form.kennel_tagline}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '0.75rem', alignItems: 'center' }}>
|
||||||
|
{saved && (
|
||||||
|
<span style={{ display: 'flex', alignItems: 'center', gap: '0.4rem', color: 'var(--success)', fontSize: '0.875rem' }}>
|
||||||
|
<CheckCircle size={16} /> Saved!
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="btn btn-primary"
|
||||||
|
disabled={saving}
|
||||||
|
>
|
||||||
|
<Save size={16} />
|
||||||
|
{saving ? 'Saving...' : 'Save Settings'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user