Full codebase cleanup and optimization pass

- Fix all hardcoded light colors breaking dark mode (FileAttachment, TaskDetail, RequestDetail)
- Parallelize sequential DB fetches in TaskDetail, CompanyDetail, MyProjects
- Add error handling: NewRequest project/file upload, MyCompany update, CompanyDetail prices, AuthContext profile fetch
- Fix currentUser.company_id → currentUser.company?.id in NewRequest
- Remove stale company.email references from InvoiceDetail, ProjectDetail, TaskDetail
- Clean up dead email field from Companies form reset

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Krao Hasanee
2026-03-27 14:46:08 -04:00
parent 195c828f8b
commit 8034f15fb5
11 changed files with 61 additions and 57 deletions
+2 -2
View File
@@ -46,7 +46,7 @@ export default function FileAttachment({ files, onChange }) {
<div style={{ <div style={{
border: `2px dashed ${files.length > 0 ? 'var(--accent)' : 'var(--border)'}`, border: `2px dashed ${files.length > 0 ? 'var(--accent)' : 'var(--border)'}`,
borderRadius: 8, padding: '18px 16px', textAlign: 'center', borderRadius: 8, padding: '18px 16px', textAlign: 'center',
background: files.length > 0 ? '#fffbeb' : '#fafafa', transition: 'all 0.15s', background: 'var(--bg)', transition: 'all 0.15s',
}}> }}>
<input type="file" multiple onChange={handleChange} style={{ display: 'none' }} id="req-file-upload" /> <input type="file" multiple onChange={handleChange} style={{ display: 'none' }} id="req-file-upload" />
<label htmlFor="req-file-upload" style={{ cursor: 'pointer' }}> <label htmlFor="req-file-upload" style={{ cursor: 'pointer' }}>
@@ -67,7 +67,7 @@ export default function FileAttachment({ files, onChange }) {
{files.map((file, i) => ( {files.map((file, i) => (
<div key={i} style={{ <div key={i} style={{
display: 'flex', alignItems: 'center', justifyContent: 'space-between', display: 'flex', alignItems: 'center', justifyContent: 'space-between',
padding: '7px 12px', background: 'white', borderRadius: 8, border: '1px solid var(--border)', padding: '7px 12px', background: 'var(--card-bg)', borderRadius: 8, border: '1px solid var(--border)',
}}> }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<span>📄</span> <span>📄</span>
+13 -9
View File
@@ -16,15 +16,19 @@ export function AuthProvider({ children }) {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const fetchAndCacheProfile = async (authUser) => { const fetchAndCacheProfile = async (authUser) => {
const { data } = await supabase try {
.from('profiles') const { data } = await supabase
.select('*, company:companies(id, name, phone, address)') .from('profiles')
.eq('id', authUser.id) .select('*, company:companies(id, name, phone, address)')
.single(); .eq('id', authUser.id)
if (data) { .single();
const profile = { ...data, email: authUser.email }; if (data) {
setCurrentUser(profile); const profile = { ...data, email: authUser.email };
localStorage.setItem(PROFILE_CACHE_KEY, JSON.stringify(profile)); setCurrentUser(profile);
localStorage.setItem(PROFILE_CACHE_KEY, JSON.stringify(profile));
}
} catch (err) {
console.error('Profile fetch failed:', err);
} }
}; };
+2 -1
View File
@@ -27,12 +27,13 @@ export default function MyCompany() {
const handleSave = async (e) => { const handleSave = async (e) => {
e.preventDefault(); e.preventDefault();
setSaving(true); setSaving(true);
await supabase.from('companies').update({ const { error } = await supabase.from('companies').update({
name: form.name.trim(), name: form.name.trim(),
phone: form.phone.trim(), phone: form.phone.trim(),
address: form.address.trim(), address: form.address.trim(),
}).eq('id', company.id); }).eq('id', company.id);
setSaving(false); setSaving(false);
if (error) { alert('Failed to save. Please try again.'); return; }
setEditing(false); setEditing(false);
}; };
+4 -8
View File
@@ -125,15 +125,11 @@ export default function MyProjects() {
useEffect(() => { useEffect(() => {
async function load() { async function load() {
const { data: p } = await supabase const [{ data: p }, { data: t }] = await Promise.all([
.from('projects').select('*').order('created_at', { ascending: false }); supabase.from('projects').select('*').order('created_at', { ascending: false }),
supabase.from('tasks').select('*').order('submitted_at', { ascending: false }),
]);
setProjects(p || []); setProjects(p || []);
if (!p || p.length === 0) { setLoading(false); return; }
const { data: t } = await supabase
.from('tasks').select('*').in('project_id', p.map(pr => pr.id))
.order('submitted_at', { ascending: false });
setTasks(t || []); setTasks(t || []);
if (t && t.length > 0) { if (t && t.length > 0) {
+23 -9
View File
@@ -16,6 +16,7 @@ export default function NewRequest() {
const [existingProjects, setExistingProjects] = useState([]); const [existingProjects, setExistingProjects] = useState([]);
const [submitted, setSubmitted] = useState(false); const [submitted, setSubmitted] = useState(false);
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const [error, setError] = useState(null);
const [form, setForm] = useState({ project: preselectedProject, serviceType: '', title: '', deadline: '', description: '' }); const [form, setForm] = useState({ project: preselectedProject, serviceType: '', title: '', deadline: '', description: '' });
const [files, setFiles] = useState([]); const [files, setFiles] = useState([]);
const [customProjects, setCustomProjects] = useState([]); const [customProjects, setCustomProjects] = useState([]);
@@ -24,16 +25,16 @@ export default function NewRequest() {
useEffect(() => { useEffect(() => {
async function load() { async function load() {
if (!currentUser.company_id) return; if (!currentUser.company?.id) return;
const { data: p } = await supabase const { data: p } = await supabase
.from('projects') .from('projects')
.select('id, name') .select('id, name')
.eq('company_id', currentUser.company_id) .eq('company_id', currentUser.company.id)
.order('created_at', { ascending: false }); .order('created_at', { ascending: false });
setExistingProjects((p || []).map(pr => ({ id: pr.id, name: pr.name }))); setExistingProjects((p || []).map(pr => ({ id: pr.id, name: pr.name })));
} }
load(); load();
}, [currentUser.company_id]); }, [currentUser.company?.id]);
const allProjectNames = [ const allProjectNames = [
...existingProjects.map(p => p.name), ...existingProjects.map(p => p.name),
@@ -64,11 +65,12 @@ export default function NewRequest() {
const handleSubmit = async (e) => { const handleSubmit = async (e) => {
e.preventDefault(); e.preventDefault();
if (!currentUser.company_id) { if (!currentUser.company?.id) {
alert('Your account is not yet assigned to a company. Please contact support.'); alert('Your account is not yet assigned to a company. Please contact support.');
return; return;
} }
setSaving(true); setSaving(true);
setError(null);
// Find existing project by name within this company, or create new one // Find existing project by name within this company, or create new one
let projectId; let projectId;
@@ -76,12 +78,13 @@ export default function NewRequest() {
if (existing) { if (existing) {
projectId = existing.id; projectId = existing.id;
} else { } else {
const { data: newProject } = await supabase.from('projects').insert({ const { data: newProject, error: projectError } = await supabase.from('projects').insert({
company_id: currentUser.company_id, company_id: currentUser.company.id,
name: form.project, name: form.project,
status: 'active', status: 'active',
}).select().single(); }).select().single();
projectId = newProject?.id; if (projectError || !newProject) { setError('Failed to create project.'); setSaving(false); return; }
projectId = newProject.id;
} }
if (!projectId) { setSaving(false); return; } if (!projectId) { setSaving(false); return; }
@@ -112,7 +115,12 @@ export default function NewRequest() {
if (submission && files.length > 0) { if (submission && files.length > 0) {
for (const file of files) { for (const file of files) {
const path = `${task.id}/${Date.now()}_${file.name}`; const path = `${task.id}/${Date.now()}_${file.name}`;
const { data: uploaded } = await supabase.storage.from('submissions').upload(path, file); const { data: uploaded, error: uploadError } = await supabase.storage.from('submissions').upload(path, file);
if (uploadError) {
setError(`Failed to upload "${file.name}": ${uploadError.message}`);
setSaving(false);
return;
}
if (uploaded) { if (uploaded) {
await supabase.from('submission_files').insert({ await supabase.from('submission_files').insert({
submission_id: submission.id, submission_id: submission.id,
@@ -139,7 +147,7 @@ export default function NewRequest() {
setSubmitted(true); setSubmitted(true);
}; };
if (!currentUser.company_id) { if (!currentUser.company?.id) {
return ( return (
<Layout> <Layout>
<div style={{ maxWidth: 480, margin: '0 auto', textAlign: 'center', paddingTop: 48 }}> <div style={{ maxWidth: 480, margin: '0 auto', textAlign: 'center', paddingTop: 48 }}>
@@ -251,6 +259,12 @@ export default function NewRequest() {
<FileAttachment files={files} onChange={setFiles} /> <FileAttachment files={files} onChange={setFiles} />
{error && (
<div className="notification notification-error" style={{ marginBottom: 16 }}>
{error}
</div>
)}
<div className="notification notification-info" style={{ marginBottom: 16 }}> <div className="notification notification-info" style={{ marginBottom: 16 }}>
Submitting as <strong>{currentUser?.name}</strong> · {currentUser?.company?.name} Submitting as <strong>{currentUser?.name}</strong> · {currentUser?.company?.name}
</div> </div>
+1 -1
View File
@@ -217,7 +217,7 @@ export default function RequestDetail() {
</div> </div>
{action === 'confirm-delete' && ( {action === 'confirm-delete' && (
<div className="card" style={{ background: '#fef2f2', borderColor: '#fecaca', marginBottom: 24 }}> <div className="card" style={{ background: 'var(--bg)', borderColor: 'var(--danger)', marginBottom: 24 }}>
<div style={{ fontWeight: 600, marginBottom: 8 }}> Delete this request?</div> <div style={{ fontWeight: 600, marginBottom: 8 }}> Delete this request?</div>
<p style={{ fontSize: 13, color: 'var(--text-secondary)', marginBottom: 16 }}> <p style={{ fontSize: 13, color: 'var(--text-secondary)', marginBottom: 16 }}>
This will permanently delete <strong>{titleWithVersion}</strong> and all its history. This cannot be undone. This will permanently delete <strong>{titleWithVersion}</strong> and all its history. This cannot be undone.
+1 -1
View File
@@ -44,7 +44,7 @@ export default function Companies() {
setSaving(false); setSaving(false);
if (data) { if (data) {
setShowNew(false); setShowNew(false);
setNewForm({ name: '', email: '', phone: '' }); setNewForm({ name: '', phone: '', address: '' });
navigate(`/companies/${data.id}`); navigate(`/companies/${data.id}`);
} }
}; };
+8 -13
View File
@@ -25,27 +25,20 @@ export default function CompanyDetail() {
}, [id]); }, [id]);
async function load() { async function load() {
const [{ data: co }, { data: p }, { data: pr }, { data: u }, { data: unassignedUsers }] = await Promise.all([ const [{ data: co }, { data: p }, { data: pr }, { data: u }, { data: unassignedUsers }, { data: t }] = await Promise.all([
supabase.from('companies').select('*').eq('id', id).single(), supabase.from('companies').select('*').eq('id', id).single(),
supabase.from('projects').select('*').eq('company_id', id).order('created_at', { ascending: false }), supabase.from('projects').select('*').eq('company_id', id).order('created_at', { ascending: false }),
supabase.from('company_prices').select('*').eq('company_id', id), supabase.from('company_prices').select('*').eq('company_id', id),
supabase.from('profiles').select('id, name, email, created_at').eq('company_id', id).eq('role', 'client'), supabase.from('profiles').select('id, name, email, created_at').eq('company_id', id).eq('role', 'client'),
supabase.from('profiles').select('id, name, email').eq('role', 'client').is('company_id', null), supabase.from('profiles').select('id, name, email').eq('role', 'client').is('company_id', null),
supabase.from('tasks').select('*, project:projects!inner(company_id)').eq('project.company_id', id),
]); ]);
setCompany(co); setCompany(co);
const projectList = p || []; setProjects(p || []);
setProjects(projectList);
setPrices(pr || []); setPrices(pr || []);
setUsers(u || []); setUsers(u || []);
setUnassigned(unassignedUsers || []); setUnassigned(unassignedUsers || []);
setTasks(t || []);
if (projectList.length > 0) {
const { data: t } = await supabase
.from('tasks')
.select('*')
.in('project_id', projectList.map(pr => pr.id));
setTasks(t || []);
}
setLoading(false); setLoading(false);
} }
@@ -86,11 +79,13 @@ export default function CompanyDetail() {
const priceVal = getPrice(serviceType); const priceVal = getPrice(serviceType);
const existing = prices.find(p => p.service_type === serviceType && p.id); const existing = prices.find(p => p.service_type === serviceType && p.id);
if (existing) { if (existing) {
await supabase.from('company_prices').update({ price: Number(priceVal) }).eq('id', existing.id); const { error: updateError } = await supabase.from('company_prices').update({ price: Number(priceVal) }).eq('id', existing.id);
if (updateError) { setSavingPrice(null); alert('Failed to save price. Please try again.'); return; }
} else { } else {
const { data } = await supabase.from('company_prices').insert({ const { data, error: insertError } = await supabase.from('company_prices').insert({
company_id: id, service_type: serviceType, price: Number(priceVal), company_id: id, service_type: serviceType, price: Number(priceVal),
}).select().single(); }).select().single();
if (insertError) { setSavingPrice(null); alert('Failed to save price. Please try again.'); return; }
if (data) setPrices(prev => prev.map(p => p.service_type === serviceType ? data : p)); if (data) setPrices(prev => prev.map(p => p.service_type === serviceType ? data : p));
} }
setSavingPrice(null); setSavingPrice(null);
-1
View File
@@ -89,7 +89,6 @@ export default function InvoiceDetail() {
<div className="card"> <div className="card">
<div className="card-title">Bill To</div> <div className="card-title">Bill To</div>
<div style={{ fontSize: 15, fontWeight: 700 }}>{company?.name}</div> <div style={{ fontSize: 15, fontWeight: 700 }}>{company?.name}</div>
{company?.email && <div style={{ fontSize: 13, color: 'var(--text-muted)', marginTop: 2 }}>{company.email}</div>}
<div style={{ marginTop: 12 }}> <div style={{ marginTop: 12 }}>
<Link to={`/companies/${company?.id}`} className="btn btn-outline btn-sm">View Company</Link> <Link to={`/companies/${company?.id}`} className="btn btn-outline btn-sm">View Company</Link>
</div> </div>
-1
View File
@@ -56,7 +56,6 @@ export default function ProjectDetail() {
<div className="card-title">Project Info</div> <div className="card-title">Project Info</div>
<div className="detail-grid" style={{ marginBottom: 0 }}> <div className="detail-grid" style={{ marginBottom: 0 }}>
<div className="detail-item"><label>Company</label><p>{company?.name || '—'}</p></div> <div className="detail-item"><label>Company</label><p>{company?.name || '—'}</p></div>
<div className="detail-item"><label>Contact</label><p>{company?.email || '—'}</p></div>
<div className="detail-item"><label>Status</label><p><StatusBadge status={project.status} /></p></div> <div className="detail-item"><label>Status</label><p><StatusBadge status={project.status} /></p></div>
<div className="detail-item"><label>Started</label><p>{new Date(project.created_at).toLocaleDateString()}</p></div> <div className="detail-item"><label>Started</label><p>{new Date(project.created_at).toLocaleDateString()}</p></div>
</div> </div>
+7 -11
View File
@@ -37,18 +37,14 @@ export default function TaskDetail() {
setTask(t); setTask(t);
const [{ data: p }, { data: subs }, { data: team }] = await Promise.all([ const [{ data: p }, { data: subs }, { data: team }] = await Promise.all([
supabase.from('projects').select('*').eq('id', t.project_id).single(), supabase.from('projects').select('*, company:companies(*)').eq('id', t.project_id).single(),
supabase.from('submissions').select('*, delivery:deliveries(*, files:delivery_files(*))').eq('task_id', id).order('version_number'), supabase.from('submissions').select('*, delivery:deliveries(*, files:delivery_files(*))').eq('task_id', id).order('version_number'),
supabase.from('profiles').select('*').eq('role', 'team'), supabase.from('profiles').select('*').eq('role', 'team'),
]); ]);
setProject(p); setProject(p);
setCompany(p?.company || null);
setSubmissions(subs || []); setSubmissions(subs || []);
setTeamMembers(team || []); setTeamMembers(team || []);
if (p) {
const { data: co } = await supabase.from('companies').select('*').eq('id', p.company_id).single();
setCompany(co);
}
setLoading(false); setLoading(false);
} }
load(); load();
@@ -187,7 +183,7 @@ export default function TaskDetail() {
<div className="card" style={{ marginBottom: 24, border: '1px solid var(--accent)', borderRadius: 12 }}> <div className="card" style={{ marginBottom: 24, border: '1px solid var(--accent)', borderRadius: 12 }}>
<div className="card-title">Send to Client {company?.name}</div> <div className="card-title">Send to Client {company?.name}</div>
<p style={{ fontSize: 13, color: 'var(--text-secondary)', marginBottom: 16 }}> <p style={{ fontSize: 13, color: 'var(--text-secondary)', marginBottom: 16 }}>
Upload the completed file and add an optional message. An email will be sent to <strong>{company?.email}</strong>. Upload the completed file and add an optional message for the client.
</p> </p>
<form onSubmit={handleSendToClient}> <form onSubmit={handleSendToClient}>
<div className="form-group"> <div className="form-group">
@@ -200,7 +196,7 @@ export default function TaskDetail() {
<div style={{ <div style={{
border: `2px dashed ${sendForm.files.length > 0 ? 'var(--accent)' : 'var(--border)'}`, border: `2px dashed ${sendForm.files.length > 0 ? 'var(--accent)' : 'var(--border)'}`,
borderRadius: 8, padding: '20px 16px', textAlign: 'center', borderRadius: 8, padding: '20px 16px', textAlign: 'center',
background: sendForm.files.length > 0 ? '#fffbeb' : '#fafafa', background: 'var(--bg)',
}}> }}>
<input type="file" multiple onChange={handleFileChange} style={{ display: 'none' }} id="file-upload" /> <input type="file" multiple onChange={handleFileChange} style={{ display: 'none' }} id="file-upload" />
<label htmlFor="file-upload" style={{ cursor: 'pointer' }}> <label htmlFor="file-upload" style={{ cursor: 'pointer' }}>
@@ -215,7 +211,7 @@ export default function TaskDetail() {
{sendForm.files.length > 0 && ( {sendForm.files.length > 0 && (
<div style={{ marginTop: 10, display: 'flex', flexDirection: 'column', gap: 6 }}> <div style={{ marginTop: 10, display: 'flex', flexDirection: 'column', gap: 6 }}>
{sendForm.files.map((file, i) => ( {sendForm.files.map((file, i) => (
<div key={i} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '8px 12px', background: 'white', borderRadius: 8, border: '1px solid var(--border)' }}> <div key={i} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '8px 12px', background: 'var(--card-bg)', borderRadius: 8, border: '1px solid var(--border)' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<span>📄</span> <span>📄</span>
<div> <div>
@@ -278,12 +274,12 @@ export default function TaskDetail() {
<button className="btn btn-primary" onClick={handleResume} disabled={saving}> Resume</button> <button className="btn btn-primary" onClick={handleResume} disabled={saving}> Resume</button>
)} )}
{task.status === 'client_review' && ( {task.status === 'client_review' && (
<div style={{ padding: '10px 14px', background: '#f5f3ff', borderRadius: 8, fontSize: 13, color: '#7c3aed', fontWeight: 500 }}> <div style={{ padding: '10px 14px', background: 'var(--bg)', borderRadius: 8, fontSize: 13, color: '#7c3aed', fontWeight: 500 }}>
Awaiting client review no action needed. Awaiting client review no action needed.
</div> </div>
)} )}
{task.status === 'client_approved' && ( {task.status === 'client_approved' && (
<div style={{ padding: '10px 14px', background: '#f0fdf4', borderRadius: 8, fontSize: 13, color: '#16a34a', fontWeight: 500 }}> <div style={{ padding: '10px 14px', background: 'var(--bg)', borderRadius: 8, fontSize: 13, color: '#16a34a', fontWeight: 500 }}>
Client approved this job. Client approved this job.
</div> </div>
)} )}