Skip to content

Commit 1099d77

Browse files
Add donation donor chart (#82)
* add donor chart * fix: remove 'use client from files as that's only for Next.js * add donor chart * add donor chart * fix: remove 'use client from files as that's only for Next.js * feat: add admin guard for chart route * fix: example .env, proxy addr change, stripe API version * fix: update stripe --------- Co-authored-by: thaninbew <kongkiatsophon.t@northeastern.edu> Co-authored-by: Thanin Kongkiatsophon <108406347+thaninbew@users.noreply.github.com>
1 parent f9ef3f9 commit 1099d77

14 files changed

Lines changed: 1596 additions & 23 deletions

File tree

apps/backend/src/donations/donations.controller.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,9 @@ export class DonationsController {
204204
status: 401,
205205
description: 'unauthorized',
206206
})
207+
@UseInterceptors(CurrentUserInterceptor)
207208
async findAll(
209+
@Req() req: any,
208210
@Query('page', new ParseIntPipe({ optional: true })) page = 1,
209211
@Query('perPage', new ParseIntPipe({ optional: true }))
210212
perPage = 20,
@@ -223,6 +225,13 @@ export class DonationsController {
223225
perPage: number;
224226
totalPages: number;
225227
}> {
228+
if (
229+
req.user.status !== Status.ADMIN &&
230+
req.user.status !== Status.STANDARD
231+
) {
232+
throw new UnauthorizedException('Admin access required');
233+
}
234+
226235
const filters: PaginationFilters = {
227236
donationType,
228237
status,

apps/backend/src/payments/payments.module.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { DonationsModule } from '../donations/donations.module';
1313
provide: 'STRIPE_CLIENT',
1414
useFactory: (configService: ConfigService) => {
1515
return new Stripe(configService.get<string>('STRIPE_SECRET_KEY'), {
16-
apiVersion: '2025-12-15.clover',
16+
apiVersion: '2026-01-28.clover',
1717
});
1818
},
1919
inject: [ConfigService],

apps/frontend/proxy.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"/api": {
3-
"target": "http://localhost:3001",
3+
"target": "http://localhost:3000",
44
"secure": false
55
}
66
}

apps/frontend/src/api/apiClient.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,37 @@ export class ApiClient {
111111
}
112112
}
113113

114+
public async getDonations(params?: {
115+
page?: number;
116+
perPage?: number;
117+
donationType?: 'one_time' | 'recurring';
118+
status?: 'pending' | 'succeeded' | 'failed' | 'cancelled';
119+
}): Promise<{
120+
rows: Array<{
121+
id: number;
122+
firstName: string;
123+
lastName: string;
124+
email: string;
125+
amount: number;
126+
donationType: 'one_time' | 'recurring';
127+
status: string;
128+
createdAt: string;
129+
}>;
130+
total: number;
131+
page: number;
132+
perPage: number;
133+
totalPages: number;
134+
}> {
135+
try {
136+
const res = await this.axiosInstance.get('/api/donations', {
137+
params,
138+
});
139+
return res.data;
140+
} catch (err: unknown) {
141+
this.handleAxiosError(err, 'Failed to fetch donations');
142+
}
143+
}
144+
114145
private async get(path: string): Promise<unknown> {
115146
return this.axiosInstance.get(path).then((response) => response.data);
116147
}

apps/frontend/src/app.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ import { DonationForm } from '@containers/donations/DonationForm';
99
import { ShadcnExample } from '@components/ShadcnExample';
1010
import { AuthProvider } from '@components/AuthProvider';
1111
import { ProtectedRoute } from '@components/ProtectedRoute';
12+
import { AdminRoute } from '@components/AdminRoute';
1213
import { LoginPage } from '@containers/auth/LoginPage';
1314
import { DashboardPage } from '@containers/dashboard/DashboardPage';
15+
import { DonorStatsChart } from '@components/DonorStatsChart';
1416

1517
const router = createBrowserRouter([
1618
{
@@ -40,6 +42,16 @@ const router = createBrowserRouter([
4042
path: '/shadcn-example',
4143
element: <ShadcnExample />,
4244
},
45+
{
46+
path: '/chart',
47+
element: <AdminRoute />,
48+
children: [
49+
{
50+
path: '',
51+
element: <DonorStatsChart />,
52+
},
53+
],
54+
},
4355
{
4456
path: '/donate',
4557
element: (
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React from 'react';
2+
import { Navigate, Outlet } from 'react-router-dom';
3+
import { useAuth } from './AuthProvider';
4+
5+
export const AdminRoute: React.FC = () => {
6+
const { isAuthenticated, isLoading, user } = useAuth();
7+
8+
if (isLoading) {
9+
return <div>Loading authentication...</div>;
10+
}
11+
12+
if (!isAuthenticated) {
13+
return <Navigate to="/login" replace />;
14+
}
15+
16+
if (user?.status !== 'ADMIN' && user?.status !== 'STANDARD') {
17+
console.log('[AdminRoute] blocked, user:', user);
18+
return <Navigate to="/dashboard" replace />;
19+
}
20+
21+
return <Outlet />;
22+
};

0 commit comments

Comments
 (0)