feat: Build Client Dashboard for Project Tracking, Updates, and Payments (#63)#68
Conversation
Closes #63 - Add Firestore SDK integration to firebase.ts - Create ProtectedRoute component for authenticated access - Create DashboardLayout with responsive sidebar navigation - Implement Dashboard Overview with stat cards and project summary - Implement Project Progress tracking with stage timeline - Implement Development Updates feed with categorized entries - Implement Payment Management with summary and history table - Implement Invoice Management with download capability - Implement Project Resources with categorized link cards - Wire up /dashboard routes with nested layout - Redirect sign-in/sign-up to /dashboard after auth - Add demo data fallback when Firestore has no projects - Full responsive design for desktop and mobile Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughAdds a complete protected client dashboard at ChangesClient Dashboard Portal
Sequence Diagram(s)sequenceDiagram
participant User
participant SignIn as SignIn / SignUp
participant ProtectedRoute
participant useProjects
participant Firestore
User->>SignIn: Submit credentials / Google popup
SignIn-->>User: navigate('/dashboard')
User->>ProtectedRoute: Route /dashboard
ProtectedRoute->>ProtectedRoute: loading → spinner
ProtectedRoute->>ProtectedRoute: no currentUser → Navigate /signin
ProtectedRoute-->>DashboardLayout: render children
DashboardLayout->>useProjects: mount
useProjects->>Firestore: fetchClientProjects(uid)
Firestore-->>useProjects: Project[] (empty if no data)
useProjects->>useProjects: empty / error → DEMO_PROJECT
useProjects-->>DashboardLayout: projects, loading, isDemo
DashboardLayout-->>User: Render dashboard page via Outlet
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 12
🧹 Nitpick comments (1)
src/dashboard/hooks/useProjects.ts (1)
32-36: ⚡ Quick winAdd error logging for production debugging.
The empty catch block silently swallows all errors (network failures, permission issues, Firestore errors). While falling back to demo data keeps the UI functional, this makes debugging production issues difficult since errors go unnoticed.
🔍 Add error logging
- } catch { + } catch (error) { if (cancelled) return; + console.error('Failed to fetch projects, falling back to demo data:', error); setProjects([DEMO_PROJECT]);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/dashboard/hooks/useProjects.ts` around lines 32 - 36, The catch block in the useProjects hook is currently empty and silently swallows all errors without any logging, making production debugging difficult. Add error logging inside the catch block to capture and log the error details (network failures, permission issues, Firestore errors, etc.) before the fallback to demo data occurs, ensuring errors are visible for debugging while maintaining the UI functionality.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/dashboard/components/DashboardLayout.tsx`:
- Around line 67-70: The handleSignOut function lacks error handling for the
signOut call, which can cause uncaught exceptions if Firebase authentication
fails. Wrap the await signOut(auth) call in a try-catch block, and in the catch
block, log the error and display an appropriate error message to the user (such
as a toast notification or alert) before attempting navigation. The
navigate("/") call should only occur in the success path within the try block.
- Around line 72-79: The initials calculation in the displayName processing
chain does not handle consecutive spaces properly. When split(" ) creates empty
string elements from consecutive spaces, accessing n[0] on those empty strings
results in undefined values appearing in the final initials string. Fix this by
adding a filter operation after the split(" ) call in the
currentUser.displayName chain to remove empty string segments before the map
operation that extracts the first character, ensuring only valid non-empty
segments have their first character extracted.
In `@src/dashboard/pages/DashboardOverview.tsx`:
- Around line 60-65: The overallProgress calculation assumes project.stages
exists and is non-empty, but since the data is cast with "as Project" upstream,
malformed payloads can cause NaN results or runtime errors. Add validation to
check if project.stages exists and has entries before performing the
calculation. If stages is missing or empty, provide a sensible fallback value
(such as 0). Also validate that the completionPercent values are valid numbers
to prevent NaN propagation in the Math.round operation.
- Around line 242-243: The Recent Updates section in DashboardOverview.tsx is
displaying the first 3 items from project.updates without sorting them by
timestamp, which results in potentially showing older items if Firestore returns
them unordered. Before the slice(0, 3) call on project.updates, add a sort
operation that orders the updates by their timestamp in descending order (most
recent first) so that the slice(0, 3) captures the three most recent updates.
Make sure to sort based on the appropriate timestamp property in the update
object to ensure chronological ordering.
- Around line 306-313: The Progress component value calculation and the
percentage text display both divide by project.totalCost without validation,
which can produce Infinity or NaN if totalCost is zero or invalid. Add a guard
condition that checks if project.totalCost is greater than zero before
performing the division calculation in both locations (the Progress component's
value prop and the Math.round call). If totalCost is zero or invalid, default to
returning 0 for the percentage value to prevent rendering issues with the
progress bar width and text display.
In `@src/dashboard/pages/InvoiceManagement.tsx`:
- Around line 70-96: In the handleDownload function, the
URL.revokeObjectURL(url) call is executed immediately after a.click(), but since
the download operation is asynchronous, the URL may be revoked before the
browser has finished reading it. Defer the URL revocation by wrapping the
URL.revokeObjectURL(url) call in a setTimeout with a small delay (such as 100ms)
to ensure the browser has completed the download before the object URL is
released.
In `@src/dashboard/pages/PaymentManagement.tsx`:
- Around line 71-74: The paidPercent calculation divides by project.totalCost
without checking if it is zero, resulting in Infinity when totalCost is 0, which
breaks the Progress bar and displays "Infinity%" in the UI. Add a guard
condition before the paidPercent calculation to check if project.totalCost is
zero or falsy, and if so, set paidPercent to 0 (or another appropriate default).
Otherwise, perform the existing division and rounding logic to ensure the
Progress bar and percentage display work correctly for all project cost
scenarios.
In `@src/dashboard/pages/ProjectProgress.tsx`:
- Around line 87-102: Add a guard check immediately after the `const detail =
project.stages[stage.key]` assignment to ensure detail exists before attempting
to access detail.status in the StageStatusIcon component. If detail is undefined
or missing, either skip rendering that stage iteration with an early return
statement, or provide a fallback status value (such as 'unknown' or 'pending').
This prevents a render-time crash when a stage key is not present in the
project.stages object due to incomplete Firestore data mapping.
In `@src/dashboard/pages/UpdatesFeed.tsx`:
- Around line 75-77: The sort operation on project.updates at lines 75-77
assumes all update.date values are parseable strings, but Firestore can return
timestamp-like objects that fail to parse correctly, resulting in NaN sort
values. Add a normalization helper function that converts both string dates and
Firestore timestamp objects to consistent Date instances, then apply this
normalizer to the date values before sorting. Also apply the same normalization
at line 136 where update dates are rendered to ensure consistent, readable
output throughout the component.
In `@src/dashboard/services/dashboardService.ts`:
- Around line 18-24: The fetchProjectById function exports without ownership
verification, creating a security risk. Either remove this unused dead code from
the export, or if it is used in the codebase, modify the function signature to
accept a uid parameter, add a check after retrieving the document to verify the
returned data's clientId matches the provided uid (returning null if it doesn't
match), following the same authorization pattern used in fetchClientProjects.
- Around line 12-16: The fetchClientProjects function and the similar function
at lines 21-23 use unsafe type casting with "as Project" on raw Firestore data
without validating the required complex fields (stages, totalCost, amountPaid,
payments, invoices, updates, resources). Add runtime validation before returning
Project objects by implementing either a Firestore data converter, using a
validation library like Zod, or adding manual validation checks to ensure all
required fields exist and have the correct structure. This will prevent
downstream code from encountering NaN values during arithmetic operations or
TypeErrors when accessing nested properties like project.stages[stage.key].
In `@src/dashboard/services/demoData.ts`:
- Around line 177-180: The resource object with id "r4" and label "Project
Brief" in the demoData.ts file has a placeholder url value of "#" which creates
a non-functional link. Replace the "#" value in the url field with an actual
valid URL path, or alternatively remove this entire resource object from the
demo data if the Project Brief document is not yet available. This will ensure
users have functioning navigation links in the client portal.
---
Nitpick comments:
In `@src/dashboard/hooks/useProjects.ts`:
- Around line 32-36: The catch block in the useProjects hook is currently empty
and silently swallows all errors without any logging, making production
debugging difficult. Add error logging inside the catch block to capture and log
the error details (network failures, permission issues, Firestore errors, etc.)
before the fallback to demo data occurs, ensuring errors are visible for
debugging while maintaining the UI functionality.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 3719ce3f-9234-49e3-b6d8-c8c45bb80a13
📒 Files selected for processing (16)
src/Firebase/SignIn.tsxsrc/Firebase/SignUp.tsxsrc/Firebase/firebase.tssrc/app/App.tsxsrc/dashboard/components/DashboardLayout.tsxsrc/dashboard/components/ProtectedRoute.tsxsrc/dashboard/hooks/useProjects.tssrc/dashboard/pages/DashboardOverview.tsxsrc/dashboard/pages/InvoiceManagement.tsxsrc/dashboard/pages/PaymentManagement.tsxsrc/dashboard/pages/ProjectProgress.tsxsrc/dashboard/pages/ProjectResources.tsxsrc/dashboard/pages/UpdatesFeed.tsxsrc/dashboard/services/dashboardService.tssrc/dashboard/services/demoData.tssrc/dashboard/types.ts
| {project.updates.slice(0, 3).map((update) => ( | ||
| <div key={update.id} className="flex gap-3"> |
There was a problem hiding this comment.
Ensure “Recent Updates” is actually recent.
Line 242 slices the first 3 items without sorting, so this can show older items if Firestore returns unordered updates.
Suggested fix
- {project.updates.slice(0, 3).map((update) => (
+ {[...project.updates]
+ .sort(
+ (a, b) =>
+ new Date(b.date).getTime() - new Date(a.date).getTime(),
+ )
+ .slice(0, 3)
+ .map((update) => (
<div key={update.id} className="flex gap-3">
...
- ))}
+ ))}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/dashboard/pages/DashboardOverview.tsx` around lines 242 - 243, The Recent
Updates section in DashboardOverview.tsx is displaying the first 3 items from
project.updates without sorting them by timestamp, which results in potentially
showing older items if Firestore returns them unordered. Before the slice(0, 3)
call on project.updates, add a sort operation that orders the updates by their
timestamp in descending order (most recent first) so that the slice(0, 3)
captures the three most recent updates. Make sure to sort based on the
appropriate timestamp property in the update object to ensure chronological
ordering.
| <Progress | ||
| value={(project.amountPaid / project.totalCost) * 100} | ||
| className="h-2" | ||
| /> | ||
| <p className="text-xs text-center text-gray-500 dark:text-gray-400"> | ||
| {Math.round( | ||
| (project.amountPaid / project.totalCost) * 100, | ||
| )} |
There was a problem hiding this comment.
Guard payment progress against zero/invalid total cost.
Line 307 and Line 312 divide by project.totalCost directly. If totalCost is 0 (or bad data), this renders Infinity%/NaN and can break progress width rendering.
Suggested fix
+ const paidPercent =
+ project.totalCost > 0
+ ? Math.min(100, Math.max(0, (project.amountPaid / project.totalCost) * 100))
+ : 0;
...
- <Progress
- value={(project.amountPaid / project.totalCost) * 100}
- className="h-2"
- />
+ <Progress value={paidPercent} className="h-2" />
<p className="text-xs text-center text-gray-500 dark:text-gray-400">
- {Math.round(
- (project.amountPaid / project.totalCost) * 100,
- )}
+ {Math.round(paidPercent)}
% of total paid
</p>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/dashboard/pages/DashboardOverview.tsx` around lines 306 - 313, The
Progress component value calculation and the percentage text display both divide
by project.totalCost without validation, which can produce Infinity or NaN if
totalCost is zero or invalid. Add a guard condition that checks if
project.totalCost is greater than zero before performing the division
calculation in both locations (the Progress component's value prop and the
Math.round call). If totalCost is zero or invalid, default to returning 0 for
the percentage value to prevent rendering issues with the progress bar width and
text display.
| {PROJECT_STAGES.map((stage, idx) => { | ||
| const detail = project.stages[stage.key]; | ||
| const isLast = idx === PROJECT_STAGES.length - 1; | ||
|
|
||
| return ( | ||
| <motion.div | ||
| key={stage.key} | ||
| initial={{ opacity: 0, x: -20 }} | ||
| animate={{ opacity: 1, x: 0 }} | ||
| transition={{ duration: 0.3, delay: idx * 0.06 }} | ||
| > | ||
| <div className="flex gap-4"> | ||
| {/* Timeline connector */} | ||
| <div className="flex flex-col items-center"> | ||
| <StageStatusIcon status={detail.status} /> | ||
| {!isLast && ( |
There was a problem hiding this comment.
Guard stage lookup before using detail.status.
At Line 88, const detail = project.stages[stage.key] is assumed present, but upstream Firestore mapping currently uses unchecked casting ({ id, ...d.data() } as Project). If any stage key is missing, this path crashes at Line 101 (detail.status). Please normalize/validate stages at the service boundary and add a local fallback guard here to prevent render-time failures.
Proposed hardening
{PROJECT_STAGES.map((stage, idx) => {
const detail = project.stages[stage.key];
+ if (!detail) {
+ return null;
+ }
const isLast = idx === PROJECT_STAGES.length - 1;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {PROJECT_STAGES.map((stage, idx) => { | |
| const detail = project.stages[stage.key]; | |
| const isLast = idx === PROJECT_STAGES.length - 1; | |
| return ( | |
| <motion.div | |
| key={stage.key} | |
| initial={{ opacity: 0, x: -20 }} | |
| animate={{ opacity: 1, x: 0 }} | |
| transition={{ duration: 0.3, delay: idx * 0.06 }} | |
| > | |
| <div className="flex gap-4"> | |
| {/* Timeline connector */} | |
| <div className="flex flex-col items-center"> | |
| <StageStatusIcon status={detail.status} /> | |
| {!isLast && ( | |
| {PROJECT_STAGES.map((stage, idx) => { | |
| const detail = project.stages[stage.key]; | |
| if (!detail) { | |
| return null; | |
| } | |
| const isLast = idx === PROJECT_STAGES.length - 1; | |
| return ( | |
| <motion.div | |
| key={stage.key} | |
| initial={{ opacity: 0, x: -20 }} | |
| animate={{ opacity: 1, x: 0 }} | |
| transition={{ duration: 0.3, delay: idx * 0.06 }} | |
| > | |
| <div className="flex gap-4"> | |
| {/* Timeline connector */} | |
| <div className="flex flex-col items-center"> | |
| <StageStatusIcon status={detail.status} /> | |
| {!isLast && ( |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/dashboard/pages/ProjectProgress.tsx` around lines 87 - 102, Add a guard
check immediately after the `const detail = project.stages[stage.key]`
assignment to ensure detail exists before attempting to access detail.status in
the StageStatusIcon component. If detail is undefined or missing, either skip
rendering that stage iteration with an early return statement, or provide a
fallback status value (such as 'unknown' or 'pending'). This prevents a
render-time crash when a stage key is not present in the project.stages object
due to incomplete Firestore data mapping.
| const updates = [...project.updates].sort( | ||
| (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime(), | ||
| ); |
There was a problem hiding this comment.
Normalize update date values before sorting/rendering.
Lines 75–77 assume update.date is always a parseable string. With current Firestore casting, timestamp-like objects can slip through, causing NaN sort results and non-readable output at Line 136. Please normalize dates in the service (preferred) or add a local converter before sort/render.
Proposed local safeguard
+function toDateString(value: unknown): string {
+ if (typeof value === "string") return value;
+ if (value instanceof Date) return value.toISOString().slice(0, 10);
+ if (value && typeof value === "object" && "toDate" in value && typeof (value as { toDate: () => Date }).toDate === "function") {
+ return (value as { toDate: () => Date }).toDate().toISOString().slice(0, 10);
+ }
+ return "";
+}
+
+function toEpoch(value: unknown): number {
+ const s = toDateString(value);
+ return s ? new Date(s).getTime() : 0;
+}
-
- const updates = [...project.updates].sort(
- (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime(),
- );
+ const updates = [...project.updates].sort((a, b) => toEpoch(b.date) - toEpoch(a.date));Also applies to: 135-137
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/dashboard/pages/UpdatesFeed.tsx` around lines 75 - 77, The sort operation
on project.updates at lines 75-77 assumes all update.date values are parseable
strings, but Firestore can return timestamp-like objects that fail to parse
correctly, resulting in NaN sort values. Add a normalization helper function
that converts both string dates and Firestore timestamp objects to consistent
Date instances, then apply this normalizer to the date values before sorting.
Also apply the same normalization at line 136 where update dates are rendered to
ensure consistent, readable output throughout the component.
| export async function fetchClientProjects(uid: string): Promise<Project[]> { | ||
| const q = query(collection(db, "projects"), where("clientId", "==", uid)); | ||
| const snapshot = await getDocs(q); | ||
| return snapshot.docs.map((d) => ({ id: d.id, ...d.data() }) as Project); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify all project writes/reads and whether runtime validation exists.
# Expected: either Firestore converters/validators are present, or this confirms blind casting risk.
rg -n -C3 'collection\(.*"projects"|doc\(.*"projects"|setDoc\(|addDoc\(|updateDoc\(|as Project|withConverter\(' --type=ts --type=tsxRepository: hrx01-dev/Servio
Length of output: 88
🏁 Script executed:
# Fix the command and examine the file
cat -n src/dashboard/services/dashboardService.tsRepository: hrx01-dev/Servio
Length of output: 924
🏁 Script executed:
# Corrected search for Project type assertions and Firestore patterns
rg -n -C3 'collection\(.*"projects"|doc\(.*"projects"|setDoc\(|addDoc\(|updateDoc\(|as Project|withConverter' --type=tsRepository: hrx01-dev/Servio
Length of output: 1302
🏁 Script executed:
# Search for validation or converter patterns
rg -n 'validate|schema|zod|io-ts|parseObject|converter|Schema' --type=ts | grep -i projectRepository: hrx01-dev/Servio
Length of output: 42
🏁 Script executed:
# View the Project type definition
cat -n src/dashboard/types.tsRepository: hrx01-dev/Servio
Length of output: 2424
🏁 Script executed:
# Search for how fetchProjectById is used - to see if validation happens upstream
rg -n 'fetchProjectById' --type=tsRepository: hrx01-dev/Servio
Length of output: 146
🏁 Script executed:
# Search for any validation middleware, converters, or security patterns
rg -n 'converter|validate|auth|permission|uid|clientId' --type=ts | head -50Repository: hrx01-dev/Servio
Length of output: 3653
🏁 Script executed:
# Check if Firestore rules file exists
find . -name '*firestore*' -o -name '*rules*' | grep -v node_modulesRepository: hrx01-dev/Servio
Length of output: 42
🏁 Script executed:
# Search more broadly for fetchProjectById usage - might be exported/used differently
rg -n 'fetchProjectById|export.*fetchProjectById' --type=ts -A 2 -B 2Repository: hrx01-dev/Servio
Length of output: 383
🏁 Script executed:
# Search for all project-related writes (setDoc, addDoc, updateDoc) to see if validation occurs
rg -n 'projects.*setDoc|projects.*addDoc|projects.*updateDoc|setDoc.*projects|addDoc.*projects|updateDoc.*projects' --type=tsRepository: hrx01-dev/Servio
Length of output: 42
🏁 Script executed:
# Check if there's any validation or transformation when projects are created/written
rg -n 'function.*Project|export.*Project.*function' --type=tsRepository: hrx01-dev/Servio
Length of output: 695
🏁 Script executed:
# Look for any Firestore configuration or security rules
find . -type f \( -name '*firestore*' -o -name '*rules*' -o -name 'firebase.json' \) | head -20Repository: hrx01-dev/Servio
Length of output: 1658
🏁 Script executed:
# Check firebase.json contents
cat -n firebase.jsonRepository: hrx01-dev/Servio
Length of output: 971
🏁 Script executed:
# Examine the useProjects hook to see how projects are used
cat -n src/dashboard/hooks/useProjects.tsRepository: hrx01-dev/Servio
Length of output: 1625
🏁 Script executed:
# Search for any downstream usage that accesses Project fields (to gauge impact)
rg -n '\.stages\[|\.payments\[|\.totalCost|\.amountPaid' --type=ts | head -20Repository: hrx01-dev/Servio
Length of output: 1322
Add runtime validation before returning Project objects.
Lines 15 and 23 use as Project on raw Firestore payloads without any validation. The Project type requires complex fields (stages, totalCost, amountPaid, payments, invoices, updates, resources), and downstream code assumes they exist:
- Arithmetic operations:
project.totalCost - project.amountPaidand(project.amountPaid / project.totalCost) * 100will produceNaNif either field is missing or malformed - Property access:
project.stages[stage.key]will throwTypeErrorifstagesis undefined
Parse and validate the document shape before returning (using Firestore data converters, a validation library like Zod, or manual checks).
Also applies to: 21-23
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/dashboard/services/dashboardService.ts` around lines 12 - 16, The
fetchClientProjects function and the similar function at lines 21-23 use unsafe
type casting with "as Project" on raw Firestore data without validating the
required complex fields (stages, totalCost, amountPaid, payments, invoices,
updates, resources). Add runtime validation before returning Project objects by
implementing either a Firestore data converter, using a validation library like
Zod, or adding manual validation checks to ensure all required fields exist and
have the correct structure. This will prevent downstream code from encountering
NaN values during arithmetic operations or TypeErrors when accessing nested
properties like project.stages[stage.key].
| export async function fetchProjectById( | ||
| projectId: string, | ||
| ): Promise<Project | null> { | ||
| const snap = await getDoc(doc(db, "projects", projectId)); | ||
| if (!snap.exists()) return null; | ||
| return { id: snap.id, ...snap.data() } as Project; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify whether security rules enforce per-user access and whether callers pass uid context.
# Expected: firestore.rules contains ownership constraints on /projects; otherwise this is an authz gap.
fd -i 'firestore.rules'
rg -n -C4 'match.*/projects|allow read|allow get|allow list|request.auth|resource.data.clientId' firestore.rules
rg -n -C3 '\bfetchProjectById\s*\(' --type=ts --type=tsxRepository: hrx01-dev/Servio
Length of output: 144
🏁 Script executed:
# Find rules files with various naming patterns
find . -type f \( -name "*firestore*" -o -name "*rules*" \) ! -path "*/node_modules/*" ! -path "*/.git/*" | head -20Repository: hrx01-dev/Servio
Length of output: 42
🏁 Script executed:
# Read the dashboardService.ts file to verify implementation
cat -n src/dashboard/services/dashboardService.tsRepository: hrx01-dev/Servio
Length of output: 924
🏁 Script executed:
# Search for fetchProjectById usage across the codebase
rg -n 'fetchProjectById' --type tsRepository: hrx01-dev/Servio
Length of output: 146
🏁 Script executed:
# Check for authentication/uid context in the codebase
rg -n 'clientId|uid|userId|getCurrentUser|getAuth' --type ts -B2 -A2 | head -100Repository: hrx01-dev/Servio
Length of output: 2474
🏁 Script executed:
# Look for Firestore security configuration
fd -type f -name "*.json" -o -name "*.ts" -o -name "*.js" | xargs grep -l "firestore" 2>/dev/null | head -10Repository: hrx01-dev/Servio
Length of output: 231
🏁 Script executed:
# Find all files that import or use fetchProjectById
rg 'import.*fetchProjectById|from.*dashboardService' --type tsRepository: hrx01-dev/Servio
Length of output: 163
🏁 Script executed:
# Search for rules files with broader patterns
find . -type f \( -name "*.rules" -o -name "*rules*" \) 2>/dev/null | grep -v node_modules | grep -v .gitRepository: hrx01-dev/Servio
Length of output: 42
🏁 Script executed:
# Look for API routes or endpoints
find . -type f \( -name "*route*" -o -name "*api*" \) 2>/dev/null | grep -v node_modules | grep -v .git | head -20Repository: hrx01-dev/Servio
Length of output: 42
🏁 Script executed:
# Check for middleware or auth protection
rg 'middleware|withAuth|requireAuth|protectedRoute' --type tsRepository: hrx01-dev/Servio
Length of output: 42
🏁 Script executed:
# Search for where fetchProjectById might be dynamically imported or used differently
rg 'fetchProjectById' -A5 -B5Repository: hrx01-dev/Servio
Length of output: 964
🏁 Script executed:
# Search exhaustively for fetchProjectById in all file types and patterns
rg 'fetchProjectById' --no-type --type-list | head -5Repository: hrx01-dev/Servio
Length of output: 496
🏁 Script executed:
# Look at the entire repository structure to understand context
ls -la src/Repository: hrx01-dev/Servio
Length of output: 626
🏁 Script executed:
# Check if this is a library or main app - look for package.json
cat package.json | grep -E '"name"|"type"|"exports"|"main"' | head -10Repository: hrx01-dev/Servio
Length of output: 118
🏁 Script executed:
# Check the Project type definition to see if clientId field exists
rg 'type Project|interface Project' -A10Repository: hrx01-dev/Servio
Length of output: 1887
🏁 Script executed:
# Final comprehensive search for any reference to this function
rg 'fetchProjectById' -r '\0' 2>/dev/null | wc -lRepository: hrx01-dev/Servio
Length of output: 62
🏁 Script executed:
# Get the full Project interface definition
cat -n src/dashboard/types.ts | grep -A 20 "export interface Project {"Repository: hrx01-dev/Servio
Length of output: 544
🏁 Script executed:
# Check if there are any external consumers or if this is listed in exports
cat package.jsonRepository: hrx01-dev/Servio
Length of output: 3367
🏁 Script executed:
# Verify the complete state - all occurrences of fetchProjectById
rg 'fetchProjectById' --count-matchesRepository: hrx01-dev/Servio
Length of output: 105
Remove or properly implement fetchProjectById with ownership checks.
fetchProjectById is exported but unused in the codebase. If retained, it must accept a uid parameter and filter by clientId === uid (following the pattern in fetchClientProjects). Currently, it bypasses ownership entirely and would expose any project by ID. Since Firestore security rules are not present, client-side authorization is the only protection.
Recommendation: Either remove this dead code or implement it as:
export async function fetchProjectById(
projectId: string,
uid: string,
): Promise<Project | null> {
const snap = await getDoc(doc(db, "projects", projectId));
if (!snap.exists()) return null;
const data = snap.data();
if (data.clientId !== uid) return null;
return { id: snap.id, ...data } as Project;
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/dashboard/services/dashboardService.ts` around lines 18 - 24, The
fetchProjectById function exports without ownership verification, creating a
security risk. Either remove this unused dead code from the export, or if it is
used in the codebase, modify the function signature to accept a uid parameter,
add a check after retrieving the document to verify the returned data's clientId
matches the provided uid (returning null if it doesn't match), following the
same authorization pattern used in fetchClientProjects.
| id: "r4", | ||
| label: "Project Brief", | ||
| url: "#", | ||
| type: "document", |
There was a problem hiding this comment.
Replace placeholder document URL with a valid path or hide the card.
Line 179 uses "#", which renders a non-functional resource link in the client portal. Use a real URL (or omit that resource until available) to avoid dead navigation.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/dashboard/services/demoData.ts` around lines 177 - 180, The resource
object with id "r4" and label "Project Brief" in the demoData.ts file has a
placeholder url value of "#" which creates a non-functional link. Replace the
"#" value in the url field with an actual valid URL path, or alternatively
remove this entire resource object from the demo data if the Project Brief
document is not yet available. This will ensure users have functioning
navigation links in the client portal.
hrx01-dev
left a comment
There was a problem hiding this comment.
Work on the issues given in the review by @coderabbitai
End-to-End Test ResultsAll 4 tests passed across all 6 dashboard pages. Test Results
Verified Data (demo data fallback)
Environment Notes
CodeRabbit Review FixesAlso addressed the 3 CodeRabbit review comments (commit prepared locally, blocked by branch protection rules):
|
|
If another commit is blocked by protection rules create another PR with the updated changes |
…edge case, progress math hardening Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
fix: address CodeRabbit review — error handling, displayName, progress math
|
Everything looks fine but can you attach the UI of the dashboard page that you made |
Summary
Implements a full client dashboard portal (closes #63) accessible at
/dashboardafter Firebase authentication, giving clients a centralized place to track project progress, view updates, manage payments, and access resources.Architecture
New
src/dashboard/module with this structure:Key decisions
getFirestore(app)tofirebase.ts, exportingdb. Service layer queriesprojectscollection filtered byclientId == uid. Falls back to demo data on empty results or errors, so the dashboard works immediately without Firestore setup./dashboardwithDashboardLayoutas the layout route using<Outlet/>.ProtectedRoutewraps the layout and redirects to/signinif unauthenticated.SignInandSignUpnow navigate to/dashboardafter successful auth instead of/.Firestore document structure (for
projectscollection)Link to Devin session: https://app.devin.ai/sessions/3cc968b3f6af4fc09059e162d7190a95
Requested by: @hrx01-dev
Summary by CodeRabbit
Release Notes
New Features
/dashboardexperience with a responsive sidebar (mobile slide-out + close controls) and a dashboard layout.Auth Updates
/dashboard.