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
  •  
  •  
  •  
10 changes: 8 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,18 @@ NEXT_PUBLIC_GOOGLE_CLIENT_ID=""
NEXT_PUBLIC_HORIZON_PUBLIC_URL="https://horizon.stellar.org"
NEXT_PUBLIC_HORIZON_TESTNET_URL="https://horizon-testnet.stellar.org"
NEXT_PUBLIC_STELLAR_NETWORK="testnet"
# Smart Wallet (Passkey) — OpenZeppelin smart accounts via smart-account-kit
NEXT_PUBLIC_STELLAR_RPC_URL="https://soroban-testnet.stellar.org"
NEXT_PUBLIC_STELLAR_NETWORK_PASSPHRASE="Test SDF Network ; September 2015"
NEXT_PUBLIC_SMART_ACCOUNT_WASM_HASH="a12e8fa9621efd20315753bd4007d974390e31fbcb4a7ddc4dd0a0dec728bf2e"
NEXT_PUBLIC_WEBAUTHN_VERIFIER_ADDRESS="CBSHV66WG7UV6FQVUTB67P3DZUEJ2KJ5X6JKQH5MFRAAFNFJUAJVXJYV"
NEXT_PUBLIC_NATIVE_TOKEN_CONTRACT="CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC"
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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"
SENTRY_PROJECT=""
SENTRY_AUTH_TOKEN=""
NODE_ENV="dev"
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,6 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts

# Sentry Config File
.env.sentry-build-plugin
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@jsr:registry=https://npm.jsr.io
2 changes: 1 addition & 1 deletion app/(landing)/about/AboutUsHero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export default function AboutUsHero() {
/>
<div className='border-20px] absolute inset-0 top-1/2 left-1/2 h-[383px] w-[383px] -translate-x-1/2 -translate-y-1/2 rounded-full border-[#DBFFB7] opacity-[0.3] mix-blend-overlay blur-[25px]' />
<div className='absolute inset-0 top-1/2 left-1/2 h-[397px] w-[397px] -translate-x-1/2 -translate-y-1/2 rounded-full border-100 border-[#6DC01A] opacity-[0.2] mix-blend-hard-light blur-[100px]' />
<div className='absolute inset-0 top-1/2 left-1/2 h-[560px] w-[560px] -translate-x-1/2 -translate-y-1/2 rounded-full bg-[#A7F9503D] blur-[400px]' />
<div className='absolute inset-0 top-1/2 left-1/2 h-[560px] w-[560px] -translate-x-1/2 -translate-y-1/2 rounded-full bg-[#2EEDAA3D] blur-[400px]' />

<div
ref={contentRef}
Expand Down
2 changes: 1 addition & 1 deletion app/(landing)/about/OurTeam.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const OurTeam = () => {
className='mx-auto mb-4 w-fit bg-clip-text text-sm font-medium text-transparent md:text-base'
style={{
backgroundImage:
'linear-gradient(272.61deg, #A7F95080 13.84%, #3AE6B2 73.28%)',
'linear-gradient(272.61deg, #2EEDAA80 13.84%, #3AE6B2 73.28%)',
}}
>
Our Team
Expand Down
92 changes: 92 additions & 0 deletions app/(landing)/campaigns/[slug]/contributions/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
'use client';

import { use, useEffect, useState } from 'react';
import { getCrowdfundingProject } from '@/features/projects/api';
import type { Crowdfunding } from '@/features/projects/types';
import { ContributionsDataTable } from '@/features/projects/components/Contributions/ContributionsDataTable';
import { ArrowLeft } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { useRouter } from 'next/navigation';

interface ContributionsPageProps {
params: Promise<{
slug: string;
}>;
}

export default function ContributionsPage({ params }: ContributionsPageProps) {
const router = useRouter();
const resolvedParams = use(params);
const slug = resolvedParams.slug;

const [project, setProject] = useState<Crowdfunding | null>(null);
const [loading, setLoading] = useState(true);

useEffect(() => {
const fetchProject = async () => {
try {
setLoading(true);
const data = await getCrowdfundingProject(slug);
setProject(data);
} catch {
// Error handled by UI state
} finally {
setLoading(false);
}
};

fetchProject();
}, [slug]);

return (
<div className='bg-background-main-bg mx-auto flex min-h-screen max-w-[1440px] flex-col px-5 py-8 md:px-[50px] lg:px-[100px]'>
{/* Header */}
<div className='mb-8 space-y-4'>
<Button
variant='ghost'
onClick={() => router.back()}
className='-ml-2 text-[#B5B5B5] hover:bg-[#1A1A1A] hover:text-white'
>
<ArrowLeft className='mr-2 h-4 w-4' />
Back
</Button>

<div className='space-y-2'>
<h1 className='text-3xl font-bold text-white'>Contributions</h1>
{project && (
<div className='flex items-center gap-3'>
<p className='text-[#B5B5B5]'>
Project:{' '}
<span className='font-medium text-white'>
{project.project.title}
</span>
</p>
<span className='text-gray-800'>•</span>
<p className='text-[#B5B5B5]'>
{project.contributors.length}{' '}
{project.contributors.length === 1
? 'Contributor'
: 'Contributors'}
</p>
</div>
)}
</div>
</div>

{/* Table */}
<div className='flex-1'>
{loading ? (
<div className='flex items-center justify-center py-20'>
<div className='border-primary h-12 w-12 animate-spin rounded-full border-4 border-t-transparent' />
</div>
) : project ? (
<ContributionsDataTable data={project.contributors} loading={false} />
) : (
<div className='flex items-center justify-center py-20'>
<p className='text-gray-600'>Failed to load contributions</p>
</div>
)}
</div>
</div>
);
}
140 changes: 140 additions & 0 deletions app/(landing)/campaigns/[slug]/milestone/[milestoneId]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import React from 'react';
import MilstoneOverview from '@/components/project-details/project-milestone/milestone-details/MilstoneOverview';
import MilestoneDetails from '@/components/project-details/project-milestone/milestone-details/MilestoneDetails';
import MilestoneLinks from '@/components/project-details/project-milestone/milestone-details/MilestoneLinks';
import { MilestoneStatusCard } from '@/features/projects/components/Milestone/MilestoneStatusCard';
import { MilestoneEvidence } from '@/features/projects/components/Milestone/MilestoneEvidence';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import {
getCrowdfundingProject,
getCrowdfundingMilestone,
} from '@/features/projects/api';
import { Crowdfunding } from '@/features/projects/types';

interface MilestonePageProps {
params: Promise<{
slug: string; // Project slug
milestoneId: string; // Milestone index
}>;
}

const MilestonePage = async ({ params }: MilestonePageProps) => {
const { slug, milestoneId } = await params;

// Fetch project data and specific milestone
let project: Crowdfunding | null = null;
let milestone = null;

try {
// Fetch both project and milestone data
const [crowdfundingProject, milestoneData] = await Promise.all([
getCrowdfundingProject(slug),
getCrowdfundingMilestone(slug, milestoneId),
]);

project = crowdfundingProject;

// Transform milestone to match component expectations
milestone = milestoneData
? {
_id: milestoneData.id || milestoneData.name,
title: milestoneData.name,
description: milestoneData.description,
status: milestoneData.status,
dueDate: milestoneData.endDate,
amount: milestoneData.amount,
// Proof and submission data (optional)
...(milestoneData.submittedAt && {
submittedAt: milestoneData.submittedAt,
}),
...(milestoneData.approvedAt && {
approvedAt: milestoneData.approvedAt,
}),
...(milestoneData.rejectedAt && {
rejectedAt: milestoneData.rejectedAt,
}),
...(milestoneData.evidence && { evidence: milestoneData.evidence }),
// Voting data (optional)
...(milestoneData.votes && { votes: milestoneData.votes }),
...(milestoneData.userHasVoted !== undefined && {
userHasVoted: milestoneData.userHasVoted,
}),
...(milestoneData.userVote && { userVote: milestoneData.userVote }),
}
: null;
} catch {
// Handle error silently - milestone will be null
}

// If milestone not found, show error state
if (!milestone) {
return (
<section className='mx-auto mt-5 flex max-w-[1440px] flex-col justify-center gap-5 px-5 py-5 md:flex-row md:justify-between md:gap-18 md:px-[50px] lg:px-[100px]'>
<div className='w-full py-12 text-center'>
<h1 className='mb-4 text-2xl font-bold text-white'>
Milestone Not Found
</h1>
<p className='text-gray-400'>
The requested milestone could not be found.
</p>
</div>
</section>
);
}

return (
<section className='mx-auto mt-5 flex max-w-[1440px] flex-col justify-center gap-5 px-5 py-5 md:flex-row md:justify-between md:gap-18 md:px-[50px] lg:px-[100px]'>
<div className='w-full md:max-w-[500px]'>
<MilstoneOverview project={project?.project} milestone={milestone} />
</div>
<Tabs defaultValue='details' className='w-full'>
<div className='border-b border-gray-800 py-0'>
<TabsList className='mb-0 h-auto w-fit justify-start gap-6 rounded-none bg-transparent p-0'>
<TabsTrigger
value='details'
className='data-[state=active]:border-primary rounded-none border-x-0 border-t-0 bg-transparent px-0 py-2 text-sm font-medium text-gray-400 transition-colors hover:text-gray-300 focus-visible:border-0 focus-visible:ring-0 focus-visible:outline-none data-[state=active]:border-x-0 data-[state=active]:border-t-0 data-[state=active]:border-b-2 data-[state=active]:text-white'
>
Details
</TabsTrigger>
<TabsTrigger
value='proof'
className='data-[state=active]:border-primary rounded-none border-x-0 border-t-0 bg-transparent px-0 py-2 text-sm font-medium text-gray-400 transition-colors hover:text-gray-300 focus-visible:border-0 focus-visible:ring-0 focus-visible:outline-none data-[state=active]:border-x-0 data-[state=active]:border-t-0 data-[state=active]:border-b-2 data-[state=active]:text-white'
>
Proof & Status
</TabsTrigger>
<TabsTrigger
value='links'
className='data-[state=active]:border-primary rounded-none border-x-0 border-t-0 bg-transparent px-0 py-2 text-sm font-medium text-gray-400 transition-colors hover:text-gray-300 focus-visible:border-0 focus-visible:ring-0 focus-visible:outline-none data-[state=active]:border-x-0 data-[state=active]:border-t-0 data-[state=active]:border-b-2 data-[state=active]:text-white'
>
Links
</TabsTrigger>
</TabsList>
</div>
<TabsContent value='details'>
<MilestoneDetails
milestoneId={milestoneId}
project={project}
milestone={milestone}
/>
</TabsContent>
<TabsContent value='proof' className='space-y-6 pt-6'>
<MilestoneStatusCard
status={milestone.status}
submittedAt={milestone.submittedAt}
approvedAt={milestone.approvedAt}
rejectedAt={milestone.rejectedAt}
evidence={milestone.evidence?.text}
/>
{milestone.evidence && (
<MilestoneEvidence evidence={milestone.evidence} />
)}
</TabsContent>
<TabsContent value='links'>
<MilestoneLinks project={project} milestone={milestone} />
</TabsContent>
</Tabs>
</section>
);
};

export default MilestonePage;
Loading
Loading