Add client company dashboard as landing page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,7 @@ import CreateInvoice from './pages/team/CreateInvoice';
|
|||||||
import InvoiceDetail from './pages/team/InvoiceDetail';
|
import InvoiceDetail from './pages/team/InvoiceDetail';
|
||||||
|
|
||||||
import Settings from './pages/Settings';
|
import Settings from './pages/Settings';
|
||||||
|
import MyCompany from './pages/client/MyCompany';
|
||||||
import MyRequests from './pages/client/MyRequests';
|
import MyRequests from './pages/client/MyRequests';
|
||||||
import MyProjects from './pages/client/MyProjects';
|
import MyProjects from './pages/client/MyProjects';
|
||||||
import MyProjectDetail from './pages/client/MyProjectDetail';
|
import MyProjectDetail from './pages/client/MyProjectDetail';
|
||||||
@@ -45,6 +46,7 @@ export default function App() {
|
|||||||
|
|
||||||
<Route path="/settings" element={<ProtectedRoute><Settings /></ProtectedRoute>} />
|
<Route path="/settings" element={<ProtectedRoute><Settings /></ProtectedRoute>} />
|
||||||
|
|
||||||
|
<Route path="/my-company" element={<ProtectedRoute role="client"><MyCompany /></ProtectedRoute>} />
|
||||||
<Route path="/my-requests" element={<ProtectedRoute role="client"><MyRequests /></ProtectedRoute>} />
|
<Route path="/my-requests" element={<ProtectedRoute role="client"><MyRequests /></ProtectedRoute>} />
|
||||||
<Route path="/my-requests/:id" element={<ProtectedRoute role="client"><RequestDetail /></ProtectedRoute>} />
|
<Route path="/my-requests/:id" element={<ProtectedRoute role="client"><RequestDetail /></ProtectedRoute>} />
|
||||||
<Route path="/my-projects" element={<ProtectedRoute role="client"><MyProjects /></ProtectedRoute>} />
|
<Route path="/my-projects" element={<ProtectedRoute role="client"><MyProjects /></ProtectedRoute>} />
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ function ClientNav({ onNav }) {
|
|||||||
<div className="sidebar-section">
|
<div className="sidebar-section">
|
||||||
<div className="sidebar-section-label">My Work</div>
|
<div className="sidebar-section-label">My Work</div>
|
||||||
{[
|
{[
|
||||||
|
{ to: '/my-company', label: 'Company' },
|
||||||
{ to: '/my-projects', label: 'Projects' },
|
{ to: '/my-projects', label: 'Projects' },
|
||||||
{ to: '/my-invoices', label: 'Invoices' },
|
{ to: '/my-invoices', label: 'Invoices' },
|
||||||
].map(({ to, label }) => (
|
].map(({ to, label }) => (
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@ export default function Login() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentUser) {
|
if (currentUser) {
|
||||||
navigate(currentUser.role === 'team' ? '/dashboard' : '/my-projects', { replace: true });
|
navigate(currentUser.role === 'team' ? '/dashboard' : '/my-company', { replace: true });
|
||||||
}
|
}
|
||||||
}, [currentUser, navigate]);
|
}, [currentUser, navigate]);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import Layout from '../../components/Layout';
|
||||||
|
import { supabase } from '../../lib/supabase';
|
||||||
|
import { useAuth } from '../../context/AuthContext';
|
||||||
|
|
||||||
|
export default function MyCompany() {
|
||||||
|
const { currentUser } = useAuth();
|
||||||
|
const company = currentUser?.company;
|
||||||
|
const [members, setMembers] = useState([]);
|
||||||
|
const [stats, setStats] = useState({ projects: 0, active: 0 });
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!company?.id) { setLoading(false); return; }
|
||||||
|
async function load() {
|
||||||
|
const [{ data: m }, { data: p }, { data: t }] = await Promise.all([
|
||||||
|
supabase.from('profiles').select('id, name, email').eq('company_id', company.id).eq('role', 'client'),
|
||||||
|
supabase.from('projects').select('id').eq('company_id', company.id),
|
||||||
|
supabase.from('tasks').select('id, status, project_id'),
|
||||||
|
]);
|
||||||
|
setMembers(m || []);
|
||||||
|
const projectIds = (p || []).map(pr => pr.id);
|
||||||
|
const activeTasks = (t || []).filter(task => projectIds.includes(task.project_id) && task.status !== 'client_approved');
|
||||||
|
setStats({ projects: (p || []).length, active: activeTasks.length });
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
load();
|
||||||
|
}, [company?.id]);
|
||||||
|
|
||||||
|
if (loading) return <Layout><p style={{ padding: 24, color: 'var(--text-muted)' }}>Loading...</p></Layout>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<div className="page-header">
|
||||||
|
<div>
|
||||||
|
<div className="page-title">{company?.name || 'Your Company'}</div>
|
||||||
|
<div className="page-subtitle">
|
||||||
|
{[company?.phone, company?.address].filter(Boolean).join(' · ') || 'No contact info on file'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Link to="/new-request" className="btn btn-primary">+ New Request</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Stats */}
|
||||||
|
<div className="stats-grid" style={{ marginBottom: 28 }}>
|
||||||
|
<div className="stat-card">
|
||||||
|
<div className="stat-value">{stats.projects}</div>
|
||||||
|
<div className="stat-label">Projects</div>
|
||||||
|
</div>
|
||||||
|
<div className="stat-card">
|
||||||
|
<div className="stat-value">{stats.active}</div>
|
||||||
|
<div className="stat-label">Active Jobs</div>
|
||||||
|
</div>
|
||||||
|
<div className="stat-card">
|
||||||
|
<div className="stat-value">{members.length}</div>
|
||||||
|
<div className="stat-label">Team Members</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Team members */}
|
||||||
|
<div className="card">
|
||||||
|
<div className="card-title">People</div>
|
||||||
|
{members.length === 0 ? (
|
||||||
|
<p style={{ fontSize: 13, color: 'var(--text-muted)' }}>No members found.</p>
|
||||||
|
) : (
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 0 }}>
|
||||||
|
{members.map((member, i) => (
|
||||||
|
<div
|
||||||
|
key={member.id}
|
||||||
|
style={{
|
||||||
|
display: 'flex', alignItems: 'center', gap: 12,
|
||||||
|
padding: '12px 0',
|
||||||
|
borderBottom: i < members.length - 1 ? '1px solid var(--border)' : 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{
|
||||||
|
width: 36, height: 36, borderRadius: 6, background: 'var(--accent)',
|
||||||
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||||
|
fontSize: 13, fontWeight: 700, color: '#111', flexShrink: 0,
|
||||||
|
}}>
|
||||||
|
{member.name?.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div style={{ fontWeight: 600, fontSize: 14, color: 'var(--text-primary)' }}>
|
||||||
|
{member.name}
|
||||||
|
{member.id === currentUser.id && (
|
||||||
|
<span style={{ marginLeft: 8, fontSize: 11, color: 'var(--accent)', fontWeight: 500 }}>You</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 12, color: 'var(--text-muted)' }}>{member.email || '—'}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user