Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
2 changes: 1 addition & 1 deletion backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

73 changes: 71 additions & 2 deletions frontend/src/pages/Analytics.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,40 @@ export default function Analytics() {
fetchData();
}, [fetchData]);

if (loading) return <div className="container" style={{ display: 'flex', justifyContent: 'center', marginTop: '4rem', color: '#666' }}>Loading Analytics...</div>;
const SkeletonLoader = () => (
<div className="container" style={{ maxWidth: '1200px', margin: '0 auto', paddingBottom: '4rem' }}>

{/* Stats Cards Skeleton */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gap: '1.5rem', marginBottom: '2rem' }}>
{[1,2,3].map(i => (
<div key={i} className="card" style={{ padding: '1rem', position: 'relative' }}>
<div className="skeleton skeleton-text" style={{ width: '40%', height: '16px' }} />
<div className="skeleton skeleton-text" style={{ width: '60%', height: '36px', marginTop: '8px' }} />
<div className="skeleton skeleton-text" style={{ width: '100%', height: '6px', marginTop: '12px' }} />
</div>
))}
</div>

{/* Chart & Logs Skeleton */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(450px, 1fr))', gap: '2rem' }}>

{/* Chart Skeleton */}
<div className="card" style={{ height: '400px', padding: '1.5rem', display: 'flex', flexDirection: 'column' }}>
<div className="skeleton skeleton-text" style={{ width: '50%', height: '16px', marginBottom: '12px' }} />
<div className="skeleton skeleton-text" style={{ width: '100%', height: '340px' }} />
</div>

{/* Logs Skeleton */}
<div className="card" style={{ height: '400px', padding: '1.5rem', overflowY: 'auto' }}>
{[1,2,3,4,5,6,7,8,9,10].map(i => (
<div key={i} className="skeleton-row">
<div className="skeleton skeleton-text" style={{ width: '100%' }} />
</div>
))}
</div>
</div>
</div>
);

// Helper to format bytes
const formatBytes = (bytes) => {
Expand Down Expand Up @@ -67,6 +100,11 @@ export default function Analytics() {
</button>
</div>

{/* Main Content with Skeleton*/}
{loading ? (
<SkeletonLoader />
) : (
Comment on lines +104 to +106

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Potential crash if API call fails: data remains null but loading becomes false.

When the fetch fails, loading is set to false in the finally block (line 26), but data stays null. The rendered content then attempts to access data.totalRequests, data.storage, etc. (lines 120, 133, 136, etc.), causing a runtime error.

🐛 Suggested fix: add error state or null guard
 const [data, setData] = useState(null);
 const [loading, setLoading] = useState(true);
+const [error, setError] = useState(null);
 const [refreshing, setRefreshing] = useState(false);

 const fetchData = useCallback(async () => {
     try {
         setRefreshing(true);
+        setError(null);
         const res = await axios.get(`${API_URL}/api/projects/${projectId}/analytics`, {
             headers: { Authorization: `Bearer ${token}` }
         });
         setData(res.data);
     } catch (err) {
         console.error(err);
+        setError(err);
     } finally {
         setLoading(false);
         setRefreshing(false);
     }
 }, [projectId, token]);

Then in the render:

-{loading ? (
+{loading ? (
     <SkeletonLoader />
+) : error ? (
+    <div>Failed to load analytics. Please try again.</div>
 ) : (
🤖 Prompt for AI Agents
In @frontend/src/pages/Analytics.jsx around lines 104 - 106, The render can
crash when the API fails because loading becomes false but data stays null;
update the component to track an error state (e.g., add error / setError
alongside data and loading), set error = true inside the fetch catch block, and
then change the render branch that currently shows SkeletonLoader to also handle
error/null data by returning an error UI or fallback (e.g., if (error || !data)
show an error message or fallback component). Alternatively (or in addition),
guard individual accesses by using optional chaining for properties like
data?.totalRequests, data?.storage, data?.someField when rendering to prevent
runtime throws; reference the existing variables data, loading, SkeletonLoader
and the fetch/catch logic to implement these changes.

<>
{/* Stats Cards Grid */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gap: '1.5rem', marginBottom: '2rem' }}>

Expand Down Expand Up @@ -236,12 +274,43 @@ export default function Analytics() {
</div>
</div>
</div>
</>
)}

{/* Inline Styles for Spin Animation & Hover */}

<style>{`
/* Inline Styles for Spin Animation & Hover */

.spin { animation: spin 1s linear infinite; }
@keyframes spin { 100% { transform: rotate(360deg); } }
.log-row:hover { background-color: var(--color-bg-input); }

/* Loading states */

.skeleton {
position: relative;
overflow: hidden;
background: rgba(255,255,255,0.05);
border-radius: 4px;
}

.skeleton::after {
content: '';
position: absolute;
top: 0; left: -150%;
width: 150%; height: 100%;
background: linear-gradient(
90deg,
transparent,
rgba(255,255,255,0.08),
transparent
);
animation: shimmer 1.8s infinite;
}

@keyframes shimmer {
100% { left: 150%; }
}
`}</style>
</div>
);
Expand Down
62 changes: 55 additions & 7 deletions frontend/src/pages/Dashboard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,25 @@ export default function Dashboard() {
if (token) fetchProjects();
}, [token]);

const SkeletonLoader = () => (
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))',
gap: '1.5rem'
}}
>
{[1, 2, 3].map(i => (
<div key={i} className="card" style={{ padding: '1.5rem' }}>
<div className="skeleton skeleton-text" style={{ width: '50%', height: '14px' }} />
<div className="skeleton skeleton-text" style={{ width: '100%', height: '22px', marginTop: '10px' }} />
<div className="skeleton skeleton-text" style={{ width: '80%', height: '14px', marginTop: '8px' }} />
<div className="skeleton skeleton-text" style={{ width: '60%', height: '14px', marginTop: '12px' }} />
</div>
))}
</div>
);

// Inline Styles for Cards to match Landing Page aesthetic
const cardStyle = {
background: 'var(--color-bg-card)',
Expand All @@ -44,12 +63,6 @@ export default function Dashboard() {
overflow: 'hidden'
};

if (isLoading) return (
<div className="container" style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '60vh', color: 'var(--color-text-muted)', gap: '10px' }}>
<div className="spinner"></div> Loading workspace...
</div>
);

return (
<div className="container" style={{ maxWidth: '1400px', margin: '0 auto', paddingBottom: '4rem' }}>

Expand Down Expand Up @@ -112,7 +125,8 @@ export default function Dashboard() {
<div style={{ height: '1px', flex: 1, background: 'var(--color-border)' }}></div>
</div>

{projects.length === 0 ? (
{isLoading ? (
< SkeletonLoader /> ) : projects.length === 0 ? (
<div style={{
...cardStyle,
textAlign: 'center',
Expand Down Expand Up @@ -254,6 +268,40 @@ export default function Dashboard() {
animation: spin 1s linear infinite;
}
@keyframes spin { 100% { transform: rotate(360deg); } }

/* Loading states */
.skeleton {
position: relative;
overflow: hidden;
background: rgba(255,255,255,0.05);
border-radius: 4px;
}

.skeleton-text {
border-radius: 2px;
}

.skeleton::after {
content: '';
position: absolute;
top: 0;
left: -150%;
width: 150%;
height: 100%;
background: linear-gradient(
90deg,
transparent,
rgba(255,255,255,0.08),
transparent
);
animation: shimmer 1.8s infinite;
}

@keyframes shimmer {
100% {
left: 50%;
}
}
Comment thread
muktijain marked this conversation as resolved.
`}</style>
</div>
);
Expand Down
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions urBackend/backend/node_modules/.bin/baseline-browser-mapping

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions urBackend/backend/node_modules/.bin/bcrypt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions urBackend/backend/node_modules/.bin/bcrypt.cmd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions urBackend/backend/node_modules/.bin/bcrypt.ps1

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions urBackend/backend/node_modules/.bin/browserslist

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions urBackend/backend/node_modules/.bin/browserslist.cmd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions urBackend/backend/node_modules/.bin/browserslist.ps1

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading