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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .cursor/rules/trustless-rule.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,15 @@ You are methodical, precise, and a master at reasoning through complex requireme
When working with Trustless Work:
- Documentation (I'll provide you the docs in the cursor docs management):
- React Library → <https://docs.trustlesswork.com/trustless-work/react-library>
- Wallet Kit → <https://docs.trustlesswork.com/trustless-work/developer-resources/stellar-wallet-kit-quick-integration>
- Types → <https://docs.trustlesswork.com/trustless-work/developer-resources/types>
- Ensure proper installation and configuration before usage.
- Use provided Types from the documentation when applicable.
- Follow the API and component usage exactly as described in the docs.
- Do not use any, instead always you must search for the Trustless Work entities.

## Wallet (backend-managed)
- 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.

## Code Implementation Guidelines
- Use **TailwindCSS classes** for styling; avoid plain CSS.
- For conditional classes, prefer `clsx` or similar helper functions over ternary operators in JSX.
Expand Down
12 changes: 11 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
# Created by Vercel CLI
GOOGLE_CLIENT_ID=""
GOOGLE_CLIENT_SECRET="your_google_client_secret"
NEXTAUTH_SECRET="PiRlq+7IyU5QJuCiVwjiuwZUGCqc/3qw1QPgLOt2AFg="
# Set a strong secret per environment (e.g. openssl rand -base64 32). Do not commit real values.
NEXTAUTH_SECRET=""
NEXTAUTH_URL="http://localhost:3000"
# In production set to your production API base (e.g. https://api.boundlessfi.xyz). Do not rely on staging fallback.
NEXT_PUBLIC_API_URL="https://stage-api.boundlessfi.xyz/api"
NEXT_PUBLIC_APP_DESCRIPTION="Stellar-based application"
NEXT_PUBLIC_APP_ICON="/logo.svg"
NEXT_PUBLIC_APP_NAME="Boundless"
NEXT_PUBLIC_APP_URL="http://localhost:3000"
# Required in production for realtime/socket. No localhost fallback in production.
NEXT_PUBLIC_BETTER_AUTH_URL="https://stage-api.boundlessfi.xyz"
# Required for escrow in production. Set to your platform wallet address.
NEXT_PUBLIC_BOUNDLESS_PLATFORM_ADDRESS=""
NEXT_PUBLIC_DEBUG_MODE="false"
NEXT_PUBLIC_ENABLE_MULTI_WALLET="true"
Expand All @@ -20,4 +24,10 @@ NEXT_PUBLIC_HORIZON_TESTNET_URL="https://horizon-testnet.stellar.org"
NEXT_PUBLIC_STELLAR_NETWORK="testnet"
NEXT_PUBLIC_TRUSTLESS_WORK_API_KEY=""
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID="your_wallet_connect_project_id"
# Error reporting (optional). When set, errors are sent to Sentry.
NEXT_PUBLIC_SENTRY_DSN=""
SENTRY_DSN=""
SENTRY_ORG=""
SENTRY_PROJECT="boundless-next"
SENTRY_AUTH_TOKEN="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"
NODE_ENV="dev"
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Boundless is a decentralized crowdfunding and grants platform built on the Stell
- **Secure and transparent community voting and feedback systems**
- **Flexible user roles** including campaign creators, grant applicants, managers, and admins
- **Integrated authentication** with email, social login, and KYC verification
- **Multi-wallet support** for Stellar ecosystem (Freighter, Albedo, Rabet, xBull, Lobstr, Hana, HOT Wallet)
- **Backend-managed wallet** with sync and trustline support in-app (no external connect-wallet flow)
- **Comprehensive backend support** with RESTful API endpoints and robust security measures
- **Automated contract deployment and upgrade processes** using CI/CD pipelines

Expand Down Expand Up @@ -81,7 +81,6 @@ boundless/
│ │ └── LaunchCampaignFlow.tsx
│ ├── comment/ # Comment components
│ ├── comments/ # Comments system
│ ├── connect-wallet/ # Wallet connection UI
│ ├── landing-page/ # Landing page components
│ ├── layout/ # Layout components
│ ├── loading/ # Loading components
Expand All @@ -93,7 +92,7 @@ boundless/
│ ├── stepper/ # Stepper components
│ ├── testimonials/ # Testimonial components
│ ├── ui/ # Reusable UI components (48 components)
│ ├── wallet/ # Wallet-related components
│ ├── wallet/ # In-app wallet UI (drawer, sheet, sync, trustlines)
│ └── waitlist/ # Waitlist components
├── hooks/ # Custom React hooks
│ ├── use-account.ts # Account management hook
Expand Down
13 changes: 9 additions & 4 deletions app/(landing)/hackathons/[slug]/HackathonPageClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
} from '@/lib/api/hackathons/index';
import { Megaphone } from 'lucide-react';
import { AnnouncementsTab } from '@/components/hackathons/announcements/AnnouncementsTab';
import { reportError, reportMessage } from '@/lib/error-reporting';

export default function HackathonPageClient() {
const router = useRouter();
Expand Down Expand Up @@ -84,8 +85,10 @@ export default function HackathonPageClient() {
// Only show published announcements for public view
setAnnouncements(data.filter(a => !a.isDraft));
} catch (error) {
// eslint-disable-next-line no-console
console.error('Failed to fetch announcements:', error);
reportError(error, {
context: 'hackathon-fetchAnnouncements',
hackathonId,
});
} finally {
setAnnouncementsLoading(false);
}
Expand Down Expand Up @@ -197,8 +200,10 @@ export default function HackathonPageClient() {
process.env.NODE_ENV === 'development' &&
currentHackathon?.enabledTabs
) {
console.warn(
`[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.`
reportMessage(
`Tab "${tab.id}" (enabled key: ${key}) is not in currentHackathon.enabledTabs and will be hidden`,
'warning',
{ tabId: tab.id, key }
);
}
return isVisible;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
type HackathonAnnouncement,
} from '@/lib/api/hackathons/index';
import { useMarkdown } from '@/hooks/use-markdown';
import { reportError } from '@/lib/error-reporting';
import { BoundlessButton } from '@/components/buttons';
import Loading from '@/components/Loading';
import { Badge } from '@/components/ui/badge';
Expand Down Expand Up @@ -37,7 +38,11 @@ export default function AnnouncementDetailPage() {
setAnnouncement(announcementData);
setHackathonName(hackathonData.data.name);
} catch (err) {
console.error('Failed to fetch details:', err);
reportError(err, {
context: 'announcement-details',
announcementId,
slug,
});
setError(
'Failed to load announcement. It may have been deleted or moved.'
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useRouter, useSearchParams } from 'next/navigation';
import { ArrowLeft } from 'lucide-react';
import { getDraft, PrizeTier, VenueType } from '@/lib/api/hackathons';
import { getOrganization } from '@/lib/api/organization';
import { reportError } from '@/lib/error-reporting';
import { HackathonBanner } from '@/components/hackathons/hackathonBanner';
import { HackathonNavTabs } from '@/components/hackathons/hackathonNavTabs';
import { HackathonOverview } from '@/components/hackathons/overview/hackathonOverview';
Expand Down Expand Up @@ -104,7 +105,10 @@ export default function DraftPreviewPage({ params }: PreviewPageProps) {
};
}
} catch (orgErr) {
console.error('Failed to fetch organization for preview:', orgErr);
reportError(orgErr, {
context: 'hackathon-preview-fetchOrg',
orgId: resolvedParams.orgId,
});
}

if (response.success && response.data) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
DialogFooter,
} from '@/components/ui/dialog';
import { Switch } from '@/components/ui/switch';
import { reportError } from '@/lib/error-reporting';

export default function AnnouncementPage() {
const params = useParams();
Expand Down Expand Up @@ -57,7 +58,7 @@ export default function AnnouncementPage() {
const data = await listAnnouncements(hackathonId);
setAnnouncements(data);
} catch (error) {
console.error('Failed to fetch announcements:', error);
reportError(error, { context: 'announcement-fetch', hackathonId });
toast.error('Failed to load announcements');
} finally {
setIsLoading(false);
Expand Down Expand Up @@ -100,7 +101,7 @@ export default function AnnouncementPage() {
resetForm();
fetchAnnouncements();
} catch (error) {
console.error('Failed to save announcement:', error);
reportError(error, { context: 'announcement-save', hackathonId });
toast.error('Failed to save announcement');
} finally {
setIsSubmitting(false);
Expand All @@ -119,7 +120,7 @@ export default function AnnouncementPage() {
toast.success('Announcement deleted');
fetchAnnouncements();
} catch (error) {
console.error('Failed to delete announcement:', error);
reportError(error, { context: 'announcement-delete', hackathonId });
toast.error('Failed to delete announcement');
} finally {
setIsDeleteDialogOpen(false);
Expand All @@ -133,7 +134,7 @@ export default function AnnouncementPage() {
toast.success('Announcement published');
fetchAnnouncements();
} catch (error) {
console.error('Failed to publish announcement:', error);
reportError(error, { context: 'announcement-publish', hackathonId });
toast.error('Failed to publish announcement');
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { toast } from 'sonner';
import { Button } from '@/components/ui/button';
import { AuthGuard } from '@/components/auth/AuthGuard';
import Loading from '@/components/Loading';
import { reportError, reportMessage } from '@/lib/error-reporting';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { JudgingCriteriaList } from '@/components/organization/hackathons/judging/JudgingCriteriaList';
import JudgingResultsTable from '@/components/organization/hackathons/judging/JudgingResultsTable';
Expand Down Expand Up @@ -87,9 +88,10 @@ export default function JudgingPage() {
finalMembers = membersRes.data;
}
} catch (err) {
console.warn(
'Legacy member fetch failed, trying Better Auth fallback:',
err
reportMessage(
'Legacy member fetch failed, trying Better Auth fallback',
'warning',
{ error: err instanceof Error ? err.message : String(err) }
);
}

Expand All @@ -111,7 +113,10 @@ export default function JudgingPage() {
}
}
} catch (err) {
console.error('All member fetching attempts failed:', err);
reportError(err, {
context: 'judging-fetchMembers',
organizationId: targetOrgId,
});
}

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

setOrgMembers(finalMembers);
Expand Down Expand Up @@ -168,7 +173,11 @@ export default function JudgingPage() {
}
}
} catch (error: any) {
console.error('Error fetching results:', error);
reportError(error, {
context: 'judging-fetchResults',
organizationId,
hackathonId,
});
setJudgingResults([]);
setJudgingSummary(null);
toast.error(
Expand All @@ -190,7 +199,11 @@ export default function JudgingPage() {
setWinners(Array.isArray(res.data) ? res.data : []);
}
} catch (error) {
console.error('Error fetching winners:', error);
reportError(error, {
context: 'judging-fetchWinners',
organizationId,
hackathonId,
});
} finally {
setIsFetchingWinners(false);
}
Expand Down Expand Up @@ -284,10 +297,10 @@ export default function JudgingPage() {
}
return sub;
} catch (err) {
console.error(
`Failed to fetch details for submission ${sub.id}`,
err
);
reportError(err, {
context: 'judging-fetchSubmissionDetails',
submissionId: sub.id,
});
return sub;
}
});
Expand All @@ -301,7 +314,11 @@ export default function JudgingPage() {
// Handle criteria response safely
setCriteria(Array.isArray(criteriaRes) ? criteriaRes : []);
} catch (error) {
console.error('Judging data fetch error:', error);
reportError(error, {
context: 'judging-fetchData',
organizationId,
hackathonId,
});
toast.error('Failed to load judging data');
} finally {
setIsLoading(false);
Expand Down Expand Up @@ -331,7 +348,11 @@ export default function JudgingPage() {
toast.error(res.message || 'Failed to assign judge');
}
} catch (error: any) {
console.error('Error adding judge:', error);
reportError(error, {
context: 'judging-addJudge',
organizationId,
hackathonId,
});
toast.error(
error.response?.data?.message ||
error.message ||
Expand All @@ -352,7 +373,11 @@ export default function JudgingPage() {
toast.error(res.message || 'Failed to remove judge');
}
} catch (error: any) {
console.error('Error removing judge:', error);
reportError(error, {
context: 'judging-removeJudge',
organizationId,
hackathonId,
});
toast.error(
error.response?.data?.message ||
error.message ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
import { Participant } from '@/lib/api/hackathons';
import { useDebounce } from '@/hooks/use-debounce';
import { toast } from 'sonner';
import { reportError } from '@/lib/error-reporting';

const PAGE_SIZE = 12;

Expand Down Expand Up @@ -146,7 +147,11 @@ const ParticipantsPage: React.FC = () => {
submissionsCount: stats.totalSubmissions ?? 0,
});
} catch (err) {
console.error('Failed to load statistics', err);
reportError(err, {
context: 'participants-statistics',
organizationId,
hackathonId: actualHackathonId,
});
} finally {
setStatisticsLoading(false);
}
Expand Down Expand Up @@ -198,7 +203,11 @@ const ParticipantsPage: React.FC = () => {
toast.error('Failed to load judging criteria');
}
} catch (err) {
console.error('Failed to load criteria', err);
reportError(err, {
context: 'participants-loadCriteria',
organizationId,
hackathonId,
});
setCriteria([]);
toast.error('An error occurred while loading judging criteria');
} finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Loading from '@/components/Loading';
import { SubmissionsManagement } from '@/components/organization/hackathons/submissions/SubmissionsManagement';
import { authClient } from '@/lib/auth-client';
import { getHackathon, type Hackathon } from '@/lib/api/hackathons';
import { reportError } from '@/lib/error-reporting';

export default function SubmissionsPage() {
const params = useParams();
Expand Down Expand Up @@ -41,7 +42,10 @@ export default function SubmissionsPage() {
setHackathon(res.data);
}
} catch (err) {
console.error('Failed to fetch hackathon details:', err);
reportError(err, {
context: 'org-submissions-fetchHackathon',
hackathonId,
});
}
};
fetchHackathonDetails();
Expand All @@ -56,7 +60,7 @@ export default function SubmissionsPage() {
setCurrentUserId(session.user.id);
}
} catch (err) {
console.error('Failed to fetch session:', err);
reportError(err, { context: 'org-submissions-fetchSession' });
}
};
fetchSession();
Expand Down
Loading
Loading