Skip to content

Commit 4e2c01e

Browse files
committed
feat: integrate Sentry for error tracking and reporting
- Added Sentry configuration for both server and edge environments to capture errors and performance metrics. - Updated error handling across various components to report errors to Sentry, enhancing monitoring capabilities. - Introduced a new MessagesProvider for managing in-app messaging and notifications. - Refactored the .env.example file to include Sentry DSN and related configurations for production environments. - Removed the deprecated connect-wallet component to streamline the codebase.
1 parent 336ccc0 commit 4e2c01e

97 files changed

Lines changed: 6671 additions & 7528 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.cursor/rules/trustless-rule.mdc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,15 @@ You are methodical, precise, and a master at reasoning through complex requireme
3030
When working with Trustless Work:
3131
- Documentation (I'll provide you the docs in the cursor docs management):
3232
- React Library → <https://docs.trustlesswork.com/trustless-work/react-library>
33-
- Wallet Kit → <https://docs.trustlesswork.com/trustless-work/developer-resources/stellar-wallet-kit-quick-integration>
3433
- Types → <https://docs.trustlesswork.com/trustless-work/developer-resources/types>
3534
- Ensure proper installation and configuration before usage.
3635
- Use provided Types from the documentation when applicable.
3736
- Follow the API and component usage exactly as described in the docs.
3837
- Do not use any, instead always you must search for the Trustless Work entities.
3938

39+
## Wallet (backend-managed)
40+
- Wallet data is provided by the backend wallet API (see `lib/api/wallet.ts`). The wallet provider holds the API response in state and exposes `refreshWallet()`, `syncWallet()`, and trustline helpers. Use the in-app wallet UI (FamilyWalletDrawer, WalletSheet) for sync and add-trustline; there is no external "connect wallet" flow or Stellar Wallet Kit.
41+
4042
## Code Implementation Guidelines
4143
- Use **TailwindCSS classes** for styling; avoid plain CSS.
4244
- For conditional classes, prefer `clsx` or similar helper functions over ternary operators in JSX.

.env.example

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
# Created by Vercel CLI
22
GOOGLE_CLIENT_ID=""
33
GOOGLE_CLIENT_SECRET="your_google_client_secret"
4-
NEXTAUTH_SECRET="PiRlq+7IyU5QJuCiVwjiuwZUGCqc/3qw1QPgLOt2AFg="
4+
# Set a strong secret per environment (e.g. openssl rand -base64 32). Do not commit real values.
5+
NEXTAUTH_SECRET=""
56
NEXTAUTH_URL="http://localhost:3000"
7+
# In production set to your production API base (e.g. https://api.boundlessfi.xyz). Do not rely on staging fallback.
68
NEXT_PUBLIC_API_URL="https://stage-api.boundlessfi.xyz/api"
79
NEXT_PUBLIC_APP_DESCRIPTION="Stellar-based application"
810
NEXT_PUBLIC_APP_ICON="/logo.svg"
911
NEXT_PUBLIC_APP_NAME="Boundless"
1012
NEXT_PUBLIC_APP_URL="http://localhost:3000"
13+
# Required in production for realtime/socket. No localhost fallback in production.
1114
NEXT_PUBLIC_BETTER_AUTH_URL="https://stage-api.boundlessfi.xyz"
15+
# Required for escrow in production. Set to your platform wallet address.
1216
NEXT_PUBLIC_BOUNDLESS_PLATFORM_ADDRESS=""
1317
NEXT_PUBLIC_DEBUG_MODE="false"
1418
NEXT_PUBLIC_ENABLE_MULTI_WALLET="true"
@@ -20,4 +24,10 @@ NEXT_PUBLIC_HORIZON_TESTNET_URL="https://horizon-testnet.stellar.org"
2024
NEXT_PUBLIC_STELLAR_NETWORK="testnet"
2125
NEXT_PUBLIC_TRUSTLESS_WORK_API_KEY=""
2226
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID="your_wallet_connect_project_id"
27+
# Error reporting (optional). When set, errors are sent to Sentry.
28+
NEXT_PUBLIC_SENTRY_DSN=""
29+
SENTRY_DSN=""
30+
SENTRY_ORG=""
31+
SENTRY_PROJECT="boundless-next"
32+
SENTRY_AUTH_TOKEN="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"
2333
NODE_ENV="dev"

README.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Boundless is a decentralized crowdfunding and grants platform built on the Stell
99
- **Secure and transparent community voting and feedback systems**
1010
- **Flexible user roles** including campaign creators, grant applicants, managers, and admins
1111
- **Integrated authentication** with email, social login, and KYC verification
12-
- **Multi-wallet support** for Stellar ecosystem (Freighter, Albedo, Rabet, xBull, Lobstr, Hana, HOT Wallet)
12+
- **Backend-managed wallet** with sync and trustline support in-app (no external connect-wallet flow)
1313
- **Comprehensive backend support** with RESTful API endpoints and robust security measures
1414
- **Automated contract deployment and upgrade processes** using CI/CD pipelines
1515

@@ -81,7 +81,6 @@ boundless/
8181
│ │ └── LaunchCampaignFlow.tsx
8282
│ ├── comment/ # Comment components
8383
│ ├── comments/ # Comments system
84-
│ ├── connect-wallet/ # Wallet connection UI
8584
│ ├── landing-page/ # Landing page components
8685
│ ├── layout/ # Layout components
8786
│ ├── loading/ # Loading components
@@ -93,7 +92,7 @@ boundless/
9392
│ ├── stepper/ # Stepper components
9493
│ ├── testimonials/ # Testimonial components
9594
│ ├── ui/ # Reusable UI components (48 components)
96-
│ ├── wallet/ # Wallet-related components
95+
│ ├── wallet/ # In-app wallet UI (drawer, sheet, sync, trustlines)
9796
│ └── waitlist/ # Waitlist components
9897
├── hooks/ # Custom React hooks
9998
│ ├── use-account.ts # Account management hook

app/(landing)/hackathons/[slug]/HackathonPageClient.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
} from '@/lib/api/hackathons/index';
3030
import { Megaphone } from 'lucide-react';
3131
import { AnnouncementsTab } from '@/components/hackathons/announcements/AnnouncementsTab';
32+
import { reportError, reportMessage } from '@/lib/error-reporting';
3233

3334
export default function HackathonPageClient() {
3435
const router = useRouter();
@@ -84,8 +85,10 @@ export default function HackathonPageClient() {
8485
// Only show published announcements for public view
8586
setAnnouncements(data.filter(a => !a.isDraft));
8687
} catch (error) {
87-
// eslint-disable-next-line no-console
88-
console.error('Failed to fetch announcements:', error);
88+
reportError(error, {
89+
context: 'hackathon-fetchAnnouncements',
90+
hackathonId,
91+
});
8992
} finally {
9093
setAnnouncementsLoading(false);
9194
}
@@ -197,8 +200,10 @@ export default function HackathonPageClient() {
197200
process.env.NODE_ENV === 'development' &&
198201
currentHackathon?.enabledTabs
199202
) {
200-
console.warn(
201-
`[HackathonPageClient] Tab "${tab.id}" (enabled key: ${key}) is not in currentHackathon.enabledTabs and will be hidden. Add the tab id to tabIdToEnabledKey and ensure the backend includes the key in enabledTabs when the tab should be visible.`
203+
reportMessage(
204+
`Tab "${tab.id}" (enabled key: ${key}) is not in currentHackathon.enabledTabs and will be hidden`,
205+
'warning',
206+
{ tabId: tab.id, key }
202207
);
203208
}
204209
return isVisible;

app/(landing)/hackathons/[slug]/announcements/[announcementId]/page.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
type HackathonAnnouncement,
1010
} from '@/lib/api/hackathons/index';
1111
import { useMarkdown } from '@/hooks/use-markdown';
12+
import { reportError } from '@/lib/error-reporting';
1213
import { BoundlessButton } from '@/components/buttons';
1314
import Loading from '@/components/Loading';
1415
import { Badge } from '@/components/ui/badge';
@@ -37,7 +38,11 @@ export default function AnnouncementDetailPage() {
3738
setAnnouncement(announcementData);
3839
setHackathonName(hackathonData.data.name);
3940
} catch (err) {
40-
console.error('Failed to fetch details:', err);
41+
reportError(err, {
42+
context: 'announcement-details',
43+
announcementId,
44+
slug,
45+
});
4146
setError(
4247
'Failed to load announcement. It may have been deleted or moved.'
4348
);

app/(landing)/hackathons/preview/[orgId]/[draftId]/page.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useRouter, useSearchParams } from 'next/navigation';
55
import { ArrowLeft } from 'lucide-react';
66
import { getDraft, PrizeTier, VenueType } from '@/lib/api/hackathons';
77
import { getOrganization } from '@/lib/api/organization';
8+
import { reportError } from '@/lib/error-reporting';
89
import { HackathonBanner } from '@/components/hackathons/hackathonBanner';
910
import { HackathonNavTabs } from '@/components/hackathons/hackathonNavTabs';
1011
import { HackathonOverview } from '@/components/hackathons/overview/hackathonOverview';
@@ -104,7 +105,10 @@ export default function DraftPreviewPage({ params }: PreviewPageProps) {
104105
};
105106
}
106107
} catch (orgErr) {
107-
console.error('Failed to fetch organization for preview:', orgErr);
108+
reportError(orgErr, {
109+
context: 'hackathon-preview-fetchOrg',
110+
orgId: resolvedParams.orgId,
111+
});
108112
}
109113

110114
if (response.success && response.data) {

app/(landing)/organizations/[id]/hackathons/[hackathonId]/announcement/page.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
DialogFooter,
2828
} from '@/components/ui/dialog';
2929
import { Switch } from '@/components/ui/switch';
30+
import { reportError } from '@/lib/error-reporting';
3031

3132
export default function AnnouncementPage() {
3233
const params = useParams();
@@ -57,7 +58,7 @@ export default function AnnouncementPage() {
5758
const data = await listAnnouncements(hackathonId);
5859
setAnnouncements(data);
5960
} catch (error) {
60-
console.error('Failed to fetch announcements:', error);
61+
reportError(error, { context: 'announcement-fetch', hackathonId });
6162
toast.error('Failed to load announcements');
6263
} finally {
6364
setIsLoading(false);
@@ -100,7 +101,7 @@ export default function AnnouncementPage() {
100101
resetForm();
101102
fetchAnnouncements();
102103
} catch (error) {
103-
console.error('Failed to save announcement:', error);
104+
reportError(error, { context: 'announcement-save', hackathonId });
104105
toast.error('Failed to save announcement');
105106
} finally {
106107
setIsSubmitting(false);
@@ -119,7 +120,7 @@ export default function AnnouncementPage() {
119120
toast.success('Announcement deleted');
120121
fetchAnnouncements();
121122
} catch (error) {
122-
console.error('Failed to delete announcement:', error);
123+
reportError(error, { context: 'announcement-delete', hackathonId });
123124
toast.error('Failed to delete announcement');
124125
} finally {
125126
setIsDeleteDialogOpen(false);
@@ -133,7 +134,7 @@ export default function AnnouncementPage() {
133134
toast.success('Announcement published');
134135
fetchAnnouncements();
135136
} catch (error) {
136-
console.error('Failed to publish announcement:', error);
137+
reportError(error, { context: 'announcement-publish', hackathonId });
137138
toast.error('Failed to publish announcement');
138139
}
139140
};

app/(landing)/organizations/[id]/hackathons/[hackathonId]/judging/page.tsx

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { toast } from 'sonner';
2929
import { Button } from '@/components/ui/button';
3030
import { AuthGuard } from '@/components/auth/AuthGuard';
3131
import Loading from '@/components/Loading';
32+
import { reportError, reportMessage } from '@/lib/error-reporting';
3233
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
3334
import { JudgingCriteriaList } from '@/components/organization/hackathons/judging/JudgingCriteriaList';
3435
import JudgingResultsTable from '@/components/organization/hackathons/judging/JudgingResultsTable';
@@ -87,9 +88,10 @@ export default function JudgingPage() {
8788
finalMembers = membersRes.data;
8889
}
8990
} catch (err) {
90-
console.warn(
91-
'Legacy member fetch failed, trying Better Auth fallback:',
92-
err
91+
reportMessage(
92+
'Legacy member fetch failed, trying Better Auth fallback',
93+
'warning',
94+
{ error: err instanceof Error ? err.message : String(err) }
9395
);
9496
}
9597

@@ -111,7 +113,10 @@ export default function JudgingPage() {
111113
}
112114
}
113115
} catch (err) {
114-
console.error('All member fetching attempts failed:', err);
116+
reportError(err, {
117+
context: 'judging-fetchMembers',
118+
organizationId: targetOrgId,
119+
});
115120
}
116121

117122
// 2. Fetch judges
@@ -121,7 +126,7 @@ export default function JudgingPage() {
121126
judges = judgesRes.data || [];
122127
}
123128
} catch (err) {
124-
console.error('Failed to fetch judges:', err);
129+
reportError(err, { context: 'judging-fetchJudges', hackathonId });
125130
}
126131

127132
setOrgMembers(finalMembers);
@@ -168,7 +173,11 @@ export default function JudgingPage() {
168173
}
169174
}
170175
} catch (error: any) {
171-
console.error('Error fetching results:', error);
176+
reportError(error, {
177+
context: 'judging-fetchResults',
178+
organizationId,
179+
hackathonId,
180+
});
172181
setJudgingResults([]);
173182
setJudgingSummary(null);
174183
toast.error(
@@ -190,7 +199,11 @@ export default function JudgingPage() {
190199
setWinners(Array.isArray(res.data) ? res.data : []);
191200
}
192201
} catch (error) {
193-
console.error('Error fetching winners:', error);
202+
reportError(error, {
203+
context: 'judging-fetchWinners',
204+
organizationId,
205+
hackathonId,
206+
});
194207
} finally {
195208
setIsFetchingWinners(false);
196209
}
@@ -284,10 +297,10 @@ export default function JudgingPage() {
284297
}
285298
return sub;
286299
} catch (err) {
287-
console.error(
288-
`Failed to fetch details for submission ${sub.id}`,
289-
err
290-
);
300+
reportError(err, {
301+
context: 'judging-fetchSubmissionDetails',
302+
submissionId: sub.id,
303+
});
291304
return sub;
292305
}
293306
});
@@ -301,7 +314,11 @@ export default function JudgingPage() {
301314
// Handle criteria response safely
302315
setCriteria(Array.isArray(criteriaRes) ? criteriaRes : []);
303316
} catch (error) {
304-
console.error('Judging data fetch error:', error);
317+
reportError(error, {
318+
context: 'judging-fetchData',
319+
organizationId,
320+
hackathonId,
321+
});
305322
toast.error('Failed to load judging data');
306323
} finally {
307324
setIsLoading(false);
@@ -331,7 +348,11 @@ export default function JudgingPage() {
331348
toast.error(res.message || 'Failed to assign judge');
332349
}
333350
} catch (error: any) {
334-
console.error('Error adding judge:', error);
351+
reportError(error, {
352+
context: 'judging-addJudge',
353+
organizationId,
354+
hackathonId,
355+
});
335356
toast.error(
336357
error.response?.data?.message ||
337358
error.message ||
@@ -352,7 +373,11 @@ export default function JudgingPage() {
352373
toast.error(res.message || 'Failed to remove judge');
353374
}
354375
} catch (error: any) {
355-
console.error('Error removing judge:', error);
376+
reportError(error, {
377+
context: 'judging-removeJudge',
378+
organizationId,
379+
hackathonId,
380+
});
356381
toast.error(
357382
error.response?.data?.message ||
358383
error.message ||

app/(landing)/organizations/[id]/hackathons/[hackathonId]/participants/page.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
import { Participant } from '@/lib/api/hackathons';
2424
import { useDebounce } from '@/hooks/use-debounce';
2525
import { toast } from 'sonner';
26+
import { reportError } from '@/lib/error-reporting';
2627

2728
const PAGE_SIZE = 12;
2829

@@ -146,7 +147,11 @@ const ParticipantsPage: React.FC = () => {
146147
submissionsCount: stats.totalSubmissions ?? 0,
147148
});
148149
} catch (err) {
149-
console.error('Failed to load statistics', err);
150+
reportError(err, {
151+
context: 'participants-statistics',
152+
organizationId,
153+
hackathonId: actualHackathonId,
154+
});
150155
} finally {
151156
setStatisticsLoading(false);
152157
}
@@ -198,7 +203,11 @@ const ParticipantsPage: React.FC = () => {
198203
toast.error('Failed to load judging criteria');
199204
}
200205
} catch (err) {
201-
console.error('Failed to load criteria', err);
206+
reportError(err, {
207+
context: 'participants-loadCriteria',
208+
organizationId,
209+
hackathonId,
210+
});
202211
setCriteria([]);
203212
toast.error('An error occurred while loading judging criteria');
204213
} finally {

app/(landing)/organizations/[id]/hackathons/[hackathonId]/submissions/page.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import Loading from '@/components/Loading';
1010
import { SubmissionsManagement } from '@/components/organization/hackathons/submissions/SubmissionsManagement';
1111
import { authClient } from '@/lib/auth-client';
1212
import { getHackathon, type Hackathon } from '@/lib/api/hackathons';
13+
import { reportError } from '@/lib/error-reporting';
1314

1415
export default function SubmissionsPage() {
1516
const params = useParams();
@@ -41,7 +42,10 @@ export default function SubmissionsPage() {
4142
setHackathon(res.data);
4243
}
4344
} catch (err) {
44-
console.error('Failed to fetch hackathon details:', err);
45+
reportError(err, {
46+
context: 'org-submissions-fetchHackathon',
47+
hackathonId,
48+
});
4549
}
4650
};
4751
fetchHackathonDetails();
@@ -56,7 +60,7 @@ export default function SubmissionsPage() {
5660
setCurrentUserId(session.user.id);
5761
}
5862
} catch (err) {
59-
console.error('Failed to fetch session:', err);
63+
reportError(err, { context: 'org-submissions-fetchSession' });
6064
}
6165
};
6266
fetchSession();

0 commit comments

Comments
 (0)