Skip to content
Open
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 67 additions & 22 deletions app/me/crowdfunding/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

import * as React from 'react';
import Link from 'next/link';
import { Plus } from 'lucide-react';
import { Plus, AlertCircle } from 'lucide-react';

import { Button } from '@/components/ui/button';
import { getMyCrowdfundingProjects } from '@/features/projects/api';
import { CrowdfundingCampaign } from '@/lib/api/types';
import { useAuthStatus } from '@/hooks/use-auth';
import { CrowdfundingDataTable } from '@/components/crowdfunding-data-table';
import LoadingSpinner from '@/components/LoadingSpinner';
import EmptyState from '@/components/EmptyState';
import {
Card,
CardDescription,
Expand All @@ -18,8 +18,10 @@ import {
} from '@/components/ui/card';

export default function MyCrowdfundingPage() {
const { user } = useAuthStatus();
const { user, isLoading: authLoading } = useAuthStatus();
const [data, setData] = React.useState<CrowdfundingCampaign[]>([]);
const [error, setError] = React.useState<string | null>(null);
const [hasFetched, setHasFetched] = React.useState(false);
const [pagination, setPagination] = React.useState({
page: 1,
limit: 10,
Expand All @@ -31,31 +33,33 @@ export default function MyCrowdfundingPage() {
const fetchCampaigns = React.useCallback(async (page = 1, limit = 10) => {
try {
setLoading(true);
setError(null);
const response = await getMyCrowdfundingProjects(page, limit);
setData(response.data.data || []);
if (response.meta?.pagination) {
setPagination(response.meta.pagination);
}
} catch {
// Error handled by UI state
} catch (err: any) {
console.error('Failed to fetch crowdfunding campaigns:', err);
setError(err.message || 'Failed to load campaigns. Please try again.');
Comment on lines +42 to +44

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid any type; use unknown for caught errors.

The coding guidelines prohibit use of any. Narrow the unknown type with an instanceof check to safely extract the error message.

Proposed fix
-    } catch (err: any) {
+    } catch (err: unknown) {
       console.error('Failed to fetch crowdfunding campaigns:', err);
-      setError(err.message || 'Failed to load campaigns. Please try again.');
+      setError(
+        err instanceof Error
+          ? err.message
+          : 'Failed to load campaigns. Please try again.'
+      );
📝 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.

Suggested change
} catch (err: any) {
console.error('Failed to fetch crowdfunding campaigns:', err);
setError(err.message || 'Failed to load campaigns. Please try again.');
} catch (err: unknown) {
console.error('Failed to fetch crowdfunding campaigns:', err);
setError(
err instanceof Error
? err.message
: 'Failed to load campaigns. Please try again.'
);
🤖 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 `@app/me/crowdfunding/page.tsx` around lines 42 - 44, In the catch block,
replace the `err: any` type annotation with `err: unknown`. Then use an
`instanceof` check to safely narrow the type before accessing the `message`
property. Check if the caught error is an instance of `Error` using `instanceof
Error`, and only then access `err.message`; otherwise, provide a fallback error
message. This ensures type safety while maintaining the existing error handling
logic in the setError call.

Source: Coding guidelines

} finally {
setLoading(false);
setHasFetched(true);
}
}, []);

React.useEffect(() => {
if (user) {
fetchCampaigns();
} else if (!authLoading && !user) {
setHasFetched(true);
setError('Please log in to view your campaigns.');
}
}, [user, fetchCampaigns]);
}, [user, authLoading, fetchCampaigns]);

if (loading) {
return (
<div className='mx-auto flex h-screen items-center justify-center py-10'>
<LoadingSpinner size='xl' />
</div>
);
}
const isTableLoading = loading || authLoading || (!hasFetched && !error);
const hasEmptyState =
hasFetched && !isTableLoading && !error && data.length === 0;

return (
<Card className='bg-background border-border/10 container mx-auto py-10'>
Expand All @@ -82,15 +86,56 @@ export default function MyCrowdfundingPage() {
</div>

<div className='mt-6 space-y-4'>
<CrowdfundingDataTable
data={data}
pagination={pagination}
onPaginationChange={fetchCampaigns}
onDeleteSuccess={() =>
fetchCampaigns(pagination.page, pagination.limit)
}
loading={loading}
/>
{error && data.length > 0 && (
<div className='bg-destructive/15 text-destructive flex items-center space-x-2 rounded-md p-3 text-sm'>
<AlertCircle className='h-4 w-4' />
<span>{error}</span>
</div>
)}

{error && data.length === 0 ? (
<div className='flex flex-col items-center justify-center space-y-4 py-16 text-center'>
<AlertCircle className='text-destructive h-12 w-12' />
<div className='space-y-2'>
<h3 className='text-lg font-semibold'>
Failed to load campaigns
</h3>
<p className='text-muted-foreground text-sm'>{error}</p>
</div>
<Button
onClick={() => fetchCampaigns(pagination.page, pagination.limit)}
variant='outline'
>
Try Again
</Button>
</div>
) : hasEmptyState ? (
<EmptyState
title="You haven't created any campaigns yet"
description='Crowdfunding campaigns allow you to raise funds for your projects from the community. Start your first campaign today to bring your ideas to life.'
action={
<Button
asChild
className='bg-primary text-primary-foreground hover:bg-primary/90'
>
<Link href='/projects/create'>
<Plus className='mr-2 h-4 w-4' />
Create your first campaign
</Link>
</Button>
}
/>
) : (
<CrowdfundingDataTable
data={data}
pagination={pagination}
onPaginationChange={fetchCampaigns}
onDeleteSuccess={() =>
fetchCampaigns(pagination.page, pagination.limit)
}
loading={isTableLoading}
/>
)}
</div>
</Card>
);
Expand Down
18 changes: 10 additions & 8 deletions components/crowdfunding-data-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
TableHeader,
TableRow,
} from '@/components/ui/table';
import { Skeleton } from '@/components/ui/skeleton';
import {
ColumnFiltersState,
PaginationState,
Expand Down Expand Up @@ -135,14 +136,15 @@ export function CrowdfundingDataTable({
</TableHeader>
<TableBody>
{loading ? (
<TableRow>
<TableCell
colSpan={columns.length}
className='text-muted-foreground h-24 text-center'
>
Loading campaigns...
</TableCell>
</TableRow>
Array.from({ length: 5 }).map((_, index) => (
<TableRow key={index} className='hover:bg-transparent'>
{table.getVisibleLeafColumns().map(column => (
<TableCell key={column.id}>
<Skeleton className='h-6 w-full' />
</TableCell>
))}
</TableRow>
))
) : table.getRowModel().rows?.length ? (
table.getRowModel().rows.map(row => (
<TableRow
Expand Down