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={{
border: `2px dashed ${files.length > 0 ? 'var(--accent)' : 'var(--border)'}`,
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" />
<label htmlFor="req-file-upload" style={{ cursor: 'pointer' }}>
@@ -67,7 +67,7 @@ export default function FileAttachment({ files, onChange }) {
{files.map((file, i) => (
<div key={i} style={{
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 }}>
<span>📄</span>
+13 -9
View File
@@ -16,15 +16,19 @@ export function AuthProvider({ children }) {
const [loading, setLoading] = useState(true);
const fetchAndCacheProfile = async (authUser) => {
const { data } = await supabase
.from('profiles')
.select('*, company:companies(id, name, phone, address)')
.eq('id', authUser.id)
.single();
if (data) {
const profile = { ...data, email: authUser.email };
setCurrentUser(profile);
localStorage.setItem(PROFILE_CACHE_KEY, JSON.stringify(profile));
try {
const { data } = await supabase
.from('profiles')
.select('*, company:companies(id, name, phone, address)')
.eq('id', authUser.id)
.single();
if (data) {
const profile = { ...data, email: authUser.email };
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) => {
e.preventDefault();
setSaving(true);
await supabase.from('companies').update({
const { error } = await supabase.from('companies').update({
name: form.name.trim(),
phone: form.phone.trim(),
address: form.address.trim(),
}).eq('id', company.id);
setSaving(false);
if (error) { alert('Failed to save. Please try again.'); return; }
setEditing(false);
};
+4 -8
View File
@@ -125,15 +125,11 @@ export default function MyProjects() {
useEffect(() => {
async function load() {
const { data: p } = await supabase
.from('projects').select('*').order('created_at', { ascending: false });
const [{ data: p }, { data: t }] = await Promise.all([
supabase.from('projects').select('*').order('created_at', { ascending: false }),
supabase.from('tasks').select('*').order('submitted_at', { ascending: false }),
]);
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 || []);
if (t && t.length > 0) {
+23 -9
View File
@@ -16,6 +16,7 @@ export default function NewRequest() {
const [existingProjects, setExistingProjects] = useState([]);
const [submitted, setSubmitted] = useState(false);
const [saving, setSaving] = useState(false);
const [error, setError] = useState(null);
const [form, setForm] = useState({ project: preselectedProject, serviceType: '', title: '', deadline: '', description: '' });
const [files, setFiles] = useState([]);
const [customProjects, setCustomProjects] = useState([]);
@@ -24,16 +25,16 @@ export default function NewRequest() {
useEffect(() => {
async function load() {
if (!currentUser.company_id) return;
if (!currentUser.company?.id) return;
const { data: p } = await supabase
.from('projects')
.select('id, name')
.eq('company_id', currentUser.company_id)
.eq('company_id', currentUser.company.id)
.order('created_at', { ascending: false });
setExistingProjects((p || []).map(pr => ({ id: pr.id, name: pr.name })));
}
load();
}, [currentUser.company_id]);
}, [currentUser.company?.id]);
const allProjectNames = [
...existingProjects.map(p => p.name),
@@ -64,11 +65,12 @@ export default function NewRequest() {
const handleSubmit = async (e) => {
e.preventDefault();
if (!currentUser.company_id) {
if (!currentUser.company?.id) {
alert('Your account is not yet assigned to a company. Please contact support.');
return;
}
setSaving(true);
setError(null);
// Find existing project by name within this company, or create new one
let projectId;
@@ -76,12 +78,13 @@ export default function NewRequest() {
if (existing) {
projectId = existing.id;
} else {
const { data: newProject } = await supabase.from('projects').insert({
company_id: currentUser.company_id,
const { data: newProject, error: projectError } = await supabase.from('projects').insert({
company_id: currentUser.company.id,
name: form.project,
status: 'active',
}).select().single();
projectId = newProject?.id;
if (projectError || !newProject) { setError('Failed to create project.'); setSaving(false); return; }
projectId = newProject.id;
}
if (!projectId) { setSaving(false); return; }
@@ -112,7 +115,12 @@ export default function NewRequest() {
if (submission && files.length > 0) {
for (const file of files) {
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) {
await supabase.from('submission_files').insert({
submission_id: submission.id,
@@ -139,7 +147,7 @@ export default function NewRequest() {
setSubmitted(true);
};
if (!currentUser.company_id) {
if (!currentUser.company?.id) {
return (
<Layout>
<div style={{ maxWidth: 480, margin: '0 auto', textAlign: 'center', paddingTop: 48 }}>
@@ -251,6 +259,12 @@ export default function NewRequest() {
<FileAttachment files={files} onChange={setFiles} />
{error && (
<div className="notification notification-error" style={{ marginBottom: 16 }}>
{error}
</div>
)}
<div className="notification notification-info" style={{ marginBottom: 16 }}>
Submitting as <strong>{currentUser?.name}</strong> · {currentUser?.company?.name}
</div>
+1 -1
View File
@@ -217,7 +217,7 @@ export default function RequestDetail() {
</div>
{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>
<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.
+1 -1
View File
@@ -44,7 +44,7 @@ export default function Companies() {
setSaving(false);
if (data) {
setShowNew(false);
setNewForm({ name: '', email: '', phone: '' });
setNewForm({ name: '', phone: '', address: '' });
navigate(`/companies/${data.id}`);
}
};
+8 -13
View File
@@ -25,27 +25,20 @@ export default function CompanyDetail() {
}, [id]);
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('projects').select('*').eq('company_id', id).order('created_at', { ascending: false }),
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').eq('role', 'client').is('company_id', null),
supabase.from('tasks').select('*, project:projects!inner(company_id)').eq('project.company_id', id),
]);
setCompany(co);
const projectList = p || [];
setProjects(projectList);
setProjects(p || []);
setPrices(pr || []);
setUsers(u || []);
setUnassigned(unassignedUsers || []);
if (projectList.length > 0) {
const { data: t } = await supabase
.from('tasks')
.select('*')
.in('project_id', projectList.map(pr => pr.id));
setTasks(t || []);
}
setTasks(t || []);
setLoading(false);
}
@@ -86,11 +79,13 @@ export default function CompanyDetail() {
const priceVal = getPrice(serviceType);
const existing = prices.find(p => p.service_type === serviceType && p.id);
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 {
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),
}).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));
}
setSavingPrice(null);
-1
View File
@@ -89,7 +89,6 @@ export default function InvoiceDetail() {
<div className="card">
<div className="card-title">Bill To</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 }}>
<Link to={`/companies/${company?.id}`} className="btn btn-outline btn-sm">View Company</Link>
</div>
-1
View File
@@ -56,7 +56,6 @@ export default function ProjectDetail() {
<div className="card-title">Project Info</div>
<div className="detail-grid" style={{ marginBottom: 0 }}>
<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>Started</label><p>{new Date(project.created_at).toLocaleDateString()}</p></div>
</div>
+7 -11
View File
@@ -37,18 +37,14 @@ export default function TaskDetail() {
setTask(t);
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('profiles').select('*').eq('role', 'team'),
]);
setProject(p);
setCompany(p?.company || null);
setSubmissions(subs || []);
setTeamMembers(team || []);
if (p) {
const { data: co } = await supabase.from('companies').select('*').eq('id', p.company_id).single();
setCompany(co);
}
setLoading(false);
}
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-title">Send to Client {company?.name}</div>
<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>
<form onSubmit={handleSendToClient}>
<div className="form-group">
@@ -200,7 +196,7 @@ export default function TaskDetail() {
<div style={{
border: `2px dashed ${sendForm.files.length > 0 ? 'var(--accent)' : 'var(--border)'}`,
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" />
<label htmlFor="file-upload" style={{ cursor: 'pointer' }}>
@@ -215,7 +211,7 @@ export default function TaskDetail() {
{sendForm.files.length > 0 && (
<div style={{ marginTop: 10, display: 'flex', flexDirection: 'column', gap: 6 }}>
{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 }}>
<span>📄</span>
<div>
@@ -278,12 +274,12 @@ export default function TaskDetail() {
<button className="btn btn-primary" onClick={handleResume} disabled={saving}> Resume</button>
)}
{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.
</div>
)}
{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.
</div>
)}