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