Refactor: clients → companies schema v2
This commit is contained in:
Executable
+142
@@ -0,0 +1,142 @@
|
||||
import { useState } from 'react';
|
||||
import Layout from '../components/Layout';
|
||||
import { supabase } from '../lib/supabase';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
|
||||
export default function Settings() {
|
||||
const { currentUser } = useAuth();
|
||||
const [form, setForm] = useState({
|
||||
name: currentUser?.name || '',
|
||||
company: currentUser?.company || '',
|
||||
});
|
||||
const [passwords, setPasswords] = useState({ current: '', next: '', confirm: '' });
|
||||
const [profileSaved, setProfileSaved] = useState(false);
|
||||
const [passwordSaved, setPasswordSaved] = useState(false);
|
||||
const [passwordError, setPasswordError] = useState('');
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [savingPw, setSavingPw] = useState(false);
|
||||
|
||||
const set = (field) => (e) => setForm(f => ({ ...f, [field]: e.target.value }));
|
||||
const setPw = (field) => (e) => setPasswords(p => ({ ...p, [field]: e.target.value }));
|
||||
|
||||
const handleProfileSave = async (e) => {
|
||||
e.preventDefault();
|
||||
setSaving(true);
|
||||
await supabase.from('profiles').update({
|
||||
name: form.name.trim(),
|
||||
company: form.company.trim(),
|
||||
}).eq('id', currentUser.id);
|
||||
setProfileSaved(true);
|
||||
setSaving(false);
|
||||
setTimeout(() => setProfileSaved(false), 3000);
|
||||
};
|
||||
|
||||
const handlePasswordSave = async (e) => {
|
||||
e.preventDefault();
|
||||
setPasswordError('');
|
||||
if (passwords.next !== passwords.confirm) { setPasswordError('New passwords do not match.'); return; }
|
||||
if (passwords.next.length < 6) { setPasswordError('Password must be at least 6 characters.'); return; }
|
||||
setSavingPw(true);
|
||||
const { error } = await supabase.auth.updateUser({ password: passwords.next });
|
||||
if (error) { setPasswordError(error.message); setSavingPw(false); return; }
|
||||
setPasswords({ current: '', next: '', confirm: '' });
|
||||
setPasswordSaved(true);
|
||||
setSavingPw(false);
|
||||
setTimeout(() => setPasswordSaved(false), 3000);
|
||||
};
|
||||
|
||||
const initials = form.name
|
||||
.split(' ')
|
||||
.map(n => n[0])
|
||||
.join('')
|
||||
.toUpperCase()
|
||||
.slice(0, 2);
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className="page-header">
|
||||
<div>
|
||||
<div className="page-title">Profile & Settings</div>
|
||||
<div className="page-subtitle">Update your name, company, and password.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ maxWidth: 520, display: 'flex', flexDirection: 'column', gap: 24 }}>
|
||||
|
||||
{/* Avatar preview */}
|
||||
<div className="card" style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
|
||||
<div className="sidebar-avatar" style={{ width: 56, height: 56, fontSize: 20, flexShrink: 0 }}>
|
||||
{initials || '?'}
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontWeight: 700, fontSize: 15 }}>{form.name || 'Your Name'}</div>
|
||||
<div style={{ fontSize: 13, color: 'var(--text-secondary)' }}>{currentUser?.email}</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 2, textTransform: 'capitalize' }}>{currentUser?.role}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Profile form */}
|
||||
<div className="card">
|
||||
<div className="card-title">Profile Info</div>
|
||||
<form onSubmit={handleProfileSave}>
|
||||
<div className="form-group">
|
||||
<label>Full Name *</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="First Last"
|
||||
value={form.name}
|
||||
onChange={set('name')}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>Company / Organization</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Your company"
|
||||
value={form.company}
|
||||
onChange={set('company')}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>Email Address</label>
|
||||
<input type="email" value={currentUser?.email} disabled style={{ opacity: 0.6, cursor: 'not-allowed' }} />
|
||||
<div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 4 }}>Contact Fourge to change your email.</div>
|
||||
</div>
|
||||
{profileSaved && (
|
||||
<div className="notification notification-success" style={{ marginBottom: 12 }}>✓ Profile updated.</div>
|
||||
)}
|
||||
<button type="submit" className="btn btn-primary" disabled={saving}>
|
||||
{saving ? 'Saving...' : 'Save Changes'}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{/* Password form */}
|
||||
<div className="card">
|
||||
<div className="card-title">Change Password</div>
|
||||
<form onSubmit={handlePasswordSave}>
|
||||
<div className="form-group">
|
||||
<label>New Password *</label>
|
||||
<input type="password" placeholder="Min. 6 characters" value={passwords.next} onChange={setPw('next')} required />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>Confirm New Password *</label>
|
||||
<input type="password" placeholder="Repeat new password" value={passwords.confirm} onChange={setPw('confirm')} required />
|
||||
</div>
|
||||
{passwordError && (
|
||||
<div style={{ fontSize: 13, color: 'var(--danger)', marginBottom: 12 }}>⚠ {passwordError}</div>
|
||||
)}
|
||||
{passwordSaved && (
|
||||
<div className="notification notification-success" style={{ marginBottom: 12 }}>✓ Password updated.</div>
|
||||
)}
|
||||
<button type="submit" className="btn btn-primary" disabled={savingPw}>
|
||||
{savingPw ? 'Updating...' : 'Update Password'}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user