diff --git a/.husky/pre-push b/.husky/pre-push index 8fc868aa8..a4447e2ce 100755 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -11,8 +11,8 @@ echo "🔒 Running security audit..." npm audit --audit-level=moderate # Run build check one more time -echo "🏗️ Final build check..." -npm run build +# echo "🏗️ Final build check..." +# npm run build # Check for any uncommitted changes if ! git diff-index --quiet HEAD --; then diff --git a/LAUNCH_CAMPAIGN_README.md b/LAUNCH_CAMPAIGN_README.md deleted file mode 100644 index 55e7f055f..000000000 --- a/LAUNCH_CAMPAIGN_README.md +++ /dev/null @@ -1,201 +0,0 @@ -# Launch Campaign: Review and Launch Implementation - -This document describes the implementation of the Launch Campaign: Review and Launch feature for the Boundless platform. - -## Overview - -The Launch Campaign feature allows users to review their campaign details and launch their campaign after completing the initialization and validation steps. The implementation includes: - -1. **Review Campaign Screen** - Displays all campaign details for final review -2. **Campaign Live Success Screen** - Shows success message after launch -3. **Share Campaign Modal** - Allows sharing on social media platforms -4. **API Integration** - Mock endpoints for testing - -## Components - -### 1. ReviewCampaign Component - -- **Location**: `components/project/ReviewCampaign.tsx` -- **Purpose**: Displays campaign details for final review before launch -- **Features**: - - Campaign header with creator info and financials - - Engagement metrics (likes, comments, backers, days left) - - Campaign description and tags - - Campaign photos gallery - - Expandable milestones with details - - Confirmation checkbox - - Back and Launch buttons - -### 2. CampaignLiveSuccess Component - -- **Location**: `components/project/CampaignLiveSuccess.tsx` -- **Purpose**: Displays success screen after campaign launch -- **Features**: - - Large checkmark icon - - Success message - - Campaign preview - - Back to Dashboard and Share buttons - - Campaign link with copy functionality - -### 3. ShareCampaignModal Component - -- **Location**: `components/project/ShareCampaignModal.tsx` -- **Purpose**: Modal for sharing campaign on social media -- **Features**: - - Copyable campaign link - - Social media sharing buttons (Discord, X/Twitter, WhatsApp, Telegram) - - Campaign preview - - Copy to clipboard functionality - -### 4. LaunchCampaignFlow Component - -- **Location**: `components/project/LaunchCampaignFlow.tsx` -- **Purpose**: Manages the entire launch flow -- **Features**: - - State management for review, launching, and success steps - - Loading states during campaign launch - - Error handling - - Integration with API endpoints - -## API Endpoints - -### Mock Implementation - -The following endpoints are currently mocked for testing: - -1. **getCampaignDetails(projectId)** - Fetches campaign details for review -2. **launchCampaign(projectId)** - Launches the campaign (simulates blockchain deployment) -3. **generateCampaignLink(projectId)** - Generates shareable campaign link - -### Real Implementation - -When backend endpoints are available, replace the mock implementations in `lib/api/project.ts` with actual API calls. - -## Integration - -### ProjectSheetFlow Integration - -The Launch Campaign step has been integrated into the existing `ProjectSheetFlow` component: - -1. **Step 1**: Initialize (existing) -2. **Step 2**: Validate (existing, updated with onSuccess callback) -3. **Step 3**: Launch Campaign (new) - -### Flow Progression - -- After validation success, the flow automatically progresses to the Launch Campaign step -- Users can navigate back to previous steps -- The stepper UI updates to reflect current progress - -## Testing - -### Test Button - -A test button has been added to the dashboard (`app/dashboard/page.tsx`) to test the Launch Campaign flow: - -1. Navigate to `/dashboard` -2. Click "Test Launch Campaign" button -3. The flow will open in a modal sheet -4. Test the review, launch, and success screens - -### Mock Data - -Mock campaign data is defined in `lib/mock.ts` and includes: - -- Campaign details matching the Figma designs -- Realistic milestone information -- Engagement metrics -- Creator information - -## Design System Compliance - -The implementation follows the existing design system: - -- **Colors**: Uses predefined theme colors (`#F5F5F5`, `#B5B5B5`, `#2A2A2A`, etc.) -- **Typography**: Consistent with existing components -- **Spacing**: Follows established patterns -- **Components**: Reuses existing UI components (Button, Badge, Dialog, etc.) -- **Icons**: Uses Lucide React icons as specified - -## Accessibility - -The implementation includes accessibility features: - -- Proper ARIA labels -- Keyboard navigation support -- Screen reader friendly content -- Focus management -- Color contrast compliance - -## Responsive Design - -All components are responsive and work on: - -- Desktop (default) -- Tablet (responsive breakpoints) -- Mobile (optimized layouts) - -## Error Handling - -The implementation includes comprehensive error handling: - -- API error states -- Loading states -- Empty states -- User-friendly error messages -- Graceful fallbacks - -## Future Enhancements - -When backend integration is complete: - -1. Replace mock API calls with real endpoints -2. Add real-time status updates during campaign launch -3. Implement actual blockchain transaction handling -4. Add campaign analytics and tracking -5. Implement real social media sharing with tracking - -## File Structure - -``` -components/project/ -├── ReviewCampaign.tsx # Review campaign details -├── CampaignLiveSuccess.tsx # Success screen after launch -├── ShareCampaignModal.tsx # Social media sharing modal -├── LaunchCampaignFlow.tsx # Main flow controller -└── index.ts # Component exports - -lib/ -├── api/ -│ └── project.ts # API functions (mock) -└── mock.ts # Mock data - -app/dashboard/ -└── page.tsx # Test button integration -``` - -## Usage - -To use the Launch Campaign flow: - -1. Import the components: - -```tsx -import { LaunchCampaignFlow } from '@/components/project'; -``` - -2. Use in your component: - -```tsx - { - /* handle back */ - }} - onComplete={() => { - /* handle completion */ - }} -/> -``` - -The implementation is ready for testing and can be easily integrated with real backend endpoints when available. diff --git a/app/(landing)/about/OurTeam.tsx b/app/(landing)/about/OurTeam.tsx new file mode 100644 index 000000000..9f1cc1360 --- /dev/null +++ b/app/(landing)/about/OurTeam.tsx @@ -0,0 +1,145 @@ +import Image from 'next/image'; +import React from 'react'; + +const OurTeam = () => { + return ( +
+

+ Our Team +

+ +

+ Meet the Brains Behind Boundless +

+

+ A passionate team of innovators driving transparency, trust, and + opportunity in Web3 funding. +

+
+
+
+
+

Collins Ikechukwu

+

+ Blockchain Developer +

+
+

+ A skilled blockchain developer with a focus on decentralized + finance and smart contract solutions. +

+
+
+ linkedin +
+
+ {' '} + X +
+
+
+
+
+
+
+

Nnaji Benjamin

+

+ Full-Stack & Blockchain Developer +

+
+ +

+ A versatile full-stack and blockchain developer with strong + experience across Web3 platforms. +

+
+
+ linkedin +
+
+ {' '} + X +
+
+
+
+
+
+ ); +}; + +export default OurTeam; diff --git a/app/(landing)/about/Partners.tsx b/app/(landing)/about/Partners.tsx new file mode 100644 index 000000000..161437300 --- /dev/null +++ b/app/(landing)/about/Partners.tsx @@ -0,0 +1,45 @@ +import Image from 'next/image'; +import Link from 'next/link'; +import React from 'react'; + +const Partners = () => { + return ( +
+

+ Backed by Trusted Partners +

+

+ Want to collaborate?{' '} + + Contact Us + +

+
+
+ onlydust +
+
+ stellar-development +
+
+ stellar +
+
+ trustless +
+
+
+ ); +}; + +export default Partners; diff --git a/app/(landing)/about/page.tsx b/app/(landing)/about/page.tsx index 316d4af44..c29498bbf 100644 --- a/app/(landing)/about/page.tsx +++ b/app/(landing)/about/page.tsx @@ -1,9 +1,21 @@ import React from 'react'; +import { Metadata } from 'next'; +import { generatePageMetadata } from '@/lib/metadata'; +import TestimonialsSection from '@/components/testimonials/TestimonialsSection'; +import { testimonials } from '@/components/testimonials/data/testimonial'; +import OurTeam from './OurTeam'; +import Partners from './Partners'; + +export const metadata: Metadata = generatePageMetadata('about'); const AboutPage = () => { return ( -
- About Page +
+ + +
+ +
); }; diff --git a/app/(landing)/blog/page.tsx b/app/(landing)/blog/page.tsx new file mode 100644 index 000000000..064667571 --- /dev/null +++ b/app/(landing)/blog/page.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { Metadata } from 'next'; +import { generatePageMetadata } from '@/lib/metadata'; + +export const metadata: Metadata = generatePageMetadata('blog'); + +const BlogPage = () => { + return ( +
+ Blog Page +
+ ); +}; + +export default BlogPage; diff --git a/app/(landing)/code-of-conduct/page.tsx b/app/(landing)/code-of-conduct/page.tsx index 111b92ea7..758e294c6 100644 --- a/app/(landing)/code-of-conduct/page.tsx +++ b/app/(landing)/code-of-conduct/page.tsx @@ -1,4 +1,8 @@ import React from 'react'; +import { Metadata } from 'next'; +import { generatePageMetadata } from '@/lib/metadata'; + +export const metadata: Metadata = generatePageMetadata('codeOfConduct'); const CodeOfConductPage = () => { return ( diff --git a/app/(landing)/contact/page.tsx b/app/(landing)/contact/page.tsx index 329b3f78e..0495819e5 100644 --- a/app/(landing)/contact/page.tsx +++ b/app/(landing)/contact/page.tsx @@ -1,4 +1,8 @@ import React from 'react'; +import { Metadata } from 'next'; +import { generatePageMetadata } from '@/lib/metadata'; + +export const metadata: Metadata = generatePageMetadata('contact'); const ContactPage = () => { return ( diff --git a/app/(landing)/disclaimer/page.tsx b/app/(landing)/disclaimer/page.tsx index a4bf39da8..26555a0b9 100644 --- a/app/(landing)/disclaimer/page.tsx +++ b/app/(landing)/disclaimer/page.tsx @@ -1,4 +1,8 @@ import React from 'react'; +import { Metadata } from 'next'; +import { generatePageMetadata } from '@/lib/metadata'; + +export const metadata: Metadata = generatePageMetadata('disclaimer'); const DisclaimerPage = () => { return ( diff --git a/app/(landing)/grants/page.tsx b/app/(landing)/grants/page.tsx index c8b907b18..f8fe784e2 100644 --- a/app/(landing)/grants/page.tsx +++ b/app/(landing)/grants/page.tsx @@ -1,4 +1,8 @@ import React from 'react'; +import { Metadata } from 'next'; +import { generatePageMetadata } from '@/lib/metadata'; + +export const metadata: Metadata = generatePageMetadata('grants'); const GrantPage = () => { return ( diff --git a/app/(landing)/hackathons/page.tsx b/app/(landing)/hackathons/page.tsx index 399bb6c39..7b77c76c3 100644 --- a/app/(landing)/hackathons/page.tsx +++ b/app/(landing)/hackathons/page.tsx @@ -1,11 +1,15 @@ import React from 'react'; +import { Metadata } from 'next'; +import { generatePageMetadata } from '@/lib/metadata'; -const HackathonPage = () => { +export const metadata: Metadata = generatePageMetadata('hackathons'); + +const HackathonsPage = () => { return (
- Hackathon Page + Hackathons Page
); }; -export default HackathonPage; +export default HackathonsPage; diff --git a/app/(landing)/layout.tsx b/app/(landing)/layout.tsx index fd3038656..00d68e617 100644 --- a/app/(landing)/layout.tsx +++ b/app/(landing)/layout.tsx @@ -1,33 +1,10 @@ import { Metadata } from 'next'; import { ReactNode } from 'react'; import { Navbar } from '@/components/landing-page'; +import { generatePageMetadata } from '@/lib/metadata'; -export const metadata: Metadata = { - title: 'Boundless - Ideas Made Boundless', - description: - 'Validate, fund, and grow your project with milestone-based support on Stellar.', - openGraph: { - title: 'Boundless - Ideas Made Boundless', - description: - 'Validate, fund, and grow your project with milestone-based support on Stellar.', - type: 'website', - images: [ - { - url: '/og-image-placeholder.png', - width: 1200, - height: 630, - alt: 'Boundless', - }, - ], - }, - twitter: { - card: 'summary_large_image', - title: 'Boundless - Ideas Made Boundless', - description: - 'Validate, fund, and grow your project with milestone-based support on Stellar.', - images: ['/og-image-placeholder.png'], - }, -}; +// Generate metadata for the landing layout (home page) +export const metadata: Metadata = generatePageMetadata('home'); interface LandingLayoutProps { children: ReactNode; @@ -35,7 +12,7 @@ interface LandingLayoutProps { export default function LandingLayout({ children }: LandingLayoutProps) { return ( -
+
{children}
diff --git a/app/(landing)/page.tsx b/app/(landing)/page.tsx index 9d92e08ec..22b576fd0 100644 --- a/app/(landing)/page.tsx +++ b/app/(landing)/page.tsx @@ -1,9 +1,109 @@ +'use client'; +import gsap from 'gsap'; +import { useGSAP } from '@gsap/react'; +import { ScrollTrigger } from 'gsap/ScrollTrigger'; +import { ScrollToPlugin } from 'gsap/ScrollToPlugin'; +import { ScrollSmoother } from 'gsap/ScrollSmoother'; +import { useEffect, useRef } from 'react'; +import BeamBackground from '@/components/landing-page/BeamBackground'; +import { Hero } from '@/components/landing-page'; +import HowBoundlessWork from '@/components/landing-page/HowBoundlessWork'; +import WhyBoundless from '@/components/landing-page/WhyBoundless'; +import BackedBy from '@/components/landing-page/BackedBy'; +import NewsLetter from '@/components/landing-page/NewsLetter'; +import BlogSection from '@/components/landing-page/blog/BlogSection'; +import { useRouter } from 'next/navigation'; + export default function LandingPage() { + const containerRef = useRef(null); + const contentRef = useRef(null); + const router = useRouter(); + useEffect(() => { + router.push('/waitlist'); + }, [router]); + + gsap.registerPlugin(ScrollTrigger, ScrollSmoother, ScrollToPlugin); + + useGSAP( + () => { + ScrollSmoother.create({ + wrapper: containerRef.current, + content: contentRef.current, + smooth: 2, + smoothTouch: 0.1, + effects: true, + }); + + ScrollTrigger.create({ + trigger: '#hero', + start: 'top top', + endTrigger: '#how-boundless-work', + end: 'top top', + pin: true, + scrub: 1, + snap: { + snapTo: value => { + // value is a normalized progress (0-1) between start and end + // Snap to 0 (top of #hero) or 1 (top of #how-boundless-work) + return value < 0.5 ? 0 : 1; + }, + duration: { min: 0.2, max: 1 }, + delay: 0.1, + ease: 'power1.inOut', + }, + }); + const handleWheel = (e: WheelEvent) => { + const hero = document.getElementById('hero'); + const how = document.getElementById('how-boundless-work'); + if (!hero || !how) return; + + const heroRect = hero.getBoundingClientRect(); + if (heroRect.bottom - 10 <= window.innerHeight && e.deltaY > 0) { + e.preventDefault(); + gsap.to(window, { + duration: 0.8, + scrollTo: { y: how, offsetY: 0 }, + ease: 'power2.inOut', + }); + } + }; + + setTimeout(() => { + const hero = document.getElementById('hero'); + if (hero) { + hero.addEventListener('wheel', handleWheel, { passive: false }); + } + }, 0); + + ScrollTrigger.addEventListener('refreshInit', () => { + const hero = document.getElementById('hero'); + if (hero) { + hero.removeEventListener('wheel', handleWheel); + hero.addEventListener('wheel', handleWheel, { passive: false }); + } + }); + + return () => { + ScrollTrigger.getAll().forEach(trigger => trigger.kill()); + }; + }, + { scope: containerRef } + ); + return ( -
-

- Landing Page -

+
+ +
+ + + + + + +
); } diff --git a/app/(landing)/privacy/page.tsx b/app/(landing)/privacy/page.tsx index f6a3b2895..700b5c3e3 100644 --- a/app/(landing)/privacy/page.tsx +++ b/app/(landing)/privacy/page.tsx @@ -1,4 +1,8 @@ import React from 'react'; +import { Metadata } from 'next'; +import { generatePageMetadata } from '@/lib/metadata'; + +export const metadata: Metadata = generatePageMetadata('privacy'); const PrivacyPage = () => { return ( diff --git a/app/(landing)/projects/page.tsx b/app/(landing)/projects/page.tsx index 7e1108ba0..7ee3a1f3a 100644 --- a/app/(landing)/projects/page.tsx +++ b/app/(landing)/projects/page.tsx @@ -1,4 +1,8 @@ import React from 'react'; +import { Metadata } from 'next'; +import { generatePageMetadata } from '@/lib/metadata'; + +export const metadata: Metadata = generatePageMetadata('projects'); const ProjectsPage = () => { return ( diff --git a/app/(landing)/terms/page.tsx b/app/(landing)/terms/page.tsx index 52f920548..508c62145 100644 --- a/app/(landing)/terms/page.tsx +++ b/app/(landing)/terms/page.tsx @@ -1,11 +1,15 @@ import React from 'react'; +import { Metadata } from 'next'; +import { generatePageMetadata } from '@/lib/metadata'; -const TermsOfServicePage = () => { +export const metadata: Metadata = generatePageMetadata('terms'); + +const TermsPage = () => { return (
- Terms of Service Page + Terms Page
); }; -export default TermsOfServicePage; +export default TermsPage; diff --git a/app/(landing)/waitlist/page.tsx b/app/(landing)/waitlist/page.tsx index 4d5d53dc9..06fed0e2a 100644 --- a/app/(landing)/waitlist/page.tsx +++ b/app/(landing)/waitlist/page.tsx @@ -1,392 +1,9 @@ -'use client'; -import React, { useRef, useState } from 'react'; -import gsap from 'gsap'; -import { useGSAP } from '@gsap/react'; -import { SplitText } from 'gsap/SplitText'; -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from '@/components/ui/form'; -import { Input } from '@/components/ui/input'; -import { useForm } from 'react-hook-form'; -import { zodResolver } from '@hookform/resolvers/zod'; -import z from 'zod'; -import { BoundlessButton } from '@/components/buttons/BoundlessButton'; -import { ArrowRight, Mail, User } from 'lucide-react'; -import Link from 'next/link'; -import { cn } from '@/lib/utils'; +import { Metadata } from 'next'; +import { generatePageMetadata } from '@/lib/metadata'; +import WaitlistForm from '@/components/waitlist/WaitlistForm'; -const formSchema = z.object({ - email: z.string().email('Please enter a valid email'), - name: z.string().min(2, 'Name must be at least 2 characters'), -}); +export const metadata: Metadata = generatePageMetadata('waitlist'); -const WaitlistPage = () => { - gsap.registerPlugin(useGSAP, SplitText); - const container = useRef(null); - const titleRef = useRef(null); - const subtitleRef = useRef(null); - const [titleAnimation, setTitleAnimation] = useState(null); - const [titleSplit, setTitleSplit] = useState(null); - const [subtitleAnimation, setSubtitleAnimation] = useState(null); - const [subtitleSplit, setSubtitleSplit] = useState(null); - const [isSubmitted, setIsSubmitted] = useState(false); - const [isSubmitting, setIsSubmitting] = useState(false); - const tl = gsap.timeline(); - - const formRef = useRef(null); - const nameFieldRef = useRef(null); - const emailFieldRef = useRef(null); - const buttonRef = useRef(null); - const successRef = useRef(null); - - useGSAP( - () => { - if (titleRef.current) { - const split = SplitText.create(titleRef.current, { - type: 'chars,words,lines', - }); - - split.chars.forEach((char: Element) => { - (char as HTMLElement).style.background = - 'linear-gradient(180deg, #69726D 0%, #FFF 100%)'; - (char as HTMLElement).style.webkitBackgroundClip = 'text'; - (char as HTMLElement).style.backgroundClip = 'text'; - (char as HTMLElement).style.webkitTextFillColor = 'transparent'; - (char as HTMLElement).style.color = 'transparent'; - }); - - setTitleSplit(split); - - const animation = tl - .from(split.chars, { - x: 150, - opacity: 0, - duration: 1, - ease: 'power4', - stagger: 0.04, - }) - .to( - split.words, - { - duration: 0.6, - scale: 0.9, - stagger: 0.1, - }, - 'words' - ) - .to( - split.words, - { - duration: 0.8, - scale: 1, - stagger: 0.1, - }, - 'words+=0.1' - ); - - setTitleAnimation(animation); - } - - if (subtitleRef.current) { - const split = SplitText.create(subtitleRef.current, { type: 'words' }); - setSubtitleSplit(split); - split.words.forEach((word: Element) => { - (word as HTMLElement).style.background = - 'linear-gradient(273deg, rgba(167, 249, 80, 0.50) 13.84%, #3AE6B2 73.28%)'; - (word as HTMLElement).style.webkitBackgroundClip = 'text'; - (word as HTMLElement).style.backgroundClip = 'text'; - (word as HTMLElement).style.webkitTextFillColor = 'transparent'; - (word as HTMLElement).style.color = 'transparent'; - }); - - const animation = gsap.from(split.words, { - y: -100, - opacity: 0, - rotation: 'random(-80, 80)', - delay: 0.9, - duration: 0.7, - ease: 'back', - stagger: 0.15, - }); - setSubtitleAnimation(animation); - } - - if (formRef.current) { - gsap.fromTo( - formRef.current, - { opacity: 0, y: 50, scale: 0.95 }, - { - opacity: 1, - y: 0, - scale: 1, - duration: 0.8, - delay: 1.5, - ease: 'back.out(1.7)', - } - ); - } - }, - { scope: container } - ); - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - email: '', - name: '', - }, - }); - - const animateFieldFocus = ( - fieldRef: React.RefObject - ) => { - if (fieldRef.current) { - gsap.to(fieldRef.current, { - duration: 0.3, - scale: 1.02, - boxShadow: '0 0 0 1px rgb(167,249,80)', - ease: 'power2.out', - }); - } - }; - - const animateFieldBlur = ( - fieldRef: React.RefObject - ) => { - if (fieldRef.current) { - gsap.to(fieldRef.current, { - duration: 0.3, - scale: 1, - boxShadow: 'none', - ease: 'power2.out', - }); - } - }; - - const animateFormSubmission = () => { - setIsSubmitting(true); - - const tl = gsap.timeline(); - - if (buttonRef.current) { - tl.to(buttonRef.current, { - duration: 0.3, - scale: 0.95, - ease: 'power2.in', - }); - } - - setTimeout(() => { - setIsSubmitting(false); - setIsSubmitted(true); - - if (formRef.current && successRef.current) { - const tl = gsap.timeline(); - tl.to(formRef.current, { - duration: 0.5, - opacity: 0, - y: -30, - scale: 0.95, - ease: 'power2.in', - }).to( - successRef.current, - { - duration: 0.6, - opacity: 1, - y: 0, - scale: 1, - ease: 'back.out(1.7)', - }, - '-=0.3' - ); - } - }, 2000); - }; - - const onSubmit = (values: z.infer) => { - console.log(values); - animateFormSubmission(); - }; - - const handleTitleClick = () => { - if (titleAnimation && titleSplit) { - titleAnimation.revert(); - const newAnimation = tl.from(titleSplit.chars, { - x: 150, - opacity: 0, - duration: 0.7, - ease: 'power4', - stagger: 0.04, - }); - setTitleAnimation(newAnimation); - } - }; - - const handleSubtitleClick = () => { - if (subtitleAnimation && subtitleSplit) { - subtitleAnimation.revert(); - const newAnimation = gsap.from(subtitleSplit.words, { - y: -100, - opacity: 0, - rotation: 'random(-80, 80)', - duration: 0.7, - ease: 'back', - stagger: 0.15, - }); - setSubtitleAnimation(newAnimation); - } - }; - - return ( -
-
-

- Get Early Access to -

- -

- Boundless -

-
- {!isSubmitted ? ( -
-
- - ( - - Name - -
animateFieldFocus(nameFieldRef)} - onBlur={() => animateFieldBlur(nameFieldRef)} - > - - -
-
- -
- )} - /> - ( - - Email - -
animateFieldFocus(emailFieldRef)} - onBlur={() => animateFieldBlur(emailFieldRef)} - > - - -
-
- -
- )} - /> - - {isSubmitting ? 'Submitting...' : 'Join the waitlist'}{' '} - - - - -
- ) : ( -
-
- - - - - -

- You have been added to the waitlist -

-

- We'll let you know when Boundless is ready -

-
-
- )} -
-

- By joining, you agree to receive updates from Boundless. Learn more in - our{' '} - - Privacy policy - - . -

-
-
- ); -}; - -export default WaitlistPage; +export default function WaitlistPage() { + return ; +} diff --git a/app/api/waitlist/subscribe/route.ts b/app/api/waitlist/subscribe/route.ts new file mode 100644 index 000000000..fbe5e67a3 --- /dev/null +++ b/app/api/waitlist/subscribe/route.ts @@ -0,0 +1,46 @@ +import { NextRequest, NextResponse } from 'next/server'; + +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + + // Get the backend URL from environment or config + const backendUrl = + process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000/api'; + + // Forward the request to the backend + const response = await fetch(`${backendUrl}/waitlist/subscribe`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + // Forward any relevant headers + ...(request.headers.get('user-agent') && { + 'User-Agent': request.headers.get('user-agent')!, + }), + }, + body: JSON.stringify(body), + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + return NextResponse.json( + { + message: errorData.message || 'Failed to subscribe to waitlist', + status: response.status, + }, + { status: response.status } + ); + } + + const data = await response.json(); + return NextResponse.json(data, { status: 200 }); + } catch { + return NextResponse.json( + { + message: 'Internal server error', + status: 500, + }, + { status: 500 } + ); + } +} diff --git a/app/components/page.tsx b/app/components/page.tsx deleted file mode 100644 index 7924fbdab..000000000 --- a/app/components/page.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { PriceDisplay } from '@/components/PriceDisplay'; -import EmptyState from '@/components/EmptyState'; -import { BoundlessButton } from '@/components/buttons'; -import { Coins, History, Plus } from 'lucide-react'; -import Card from '@/components/card'; -import RecentProjects from '@/components/overview/RecentProjects'; -import RecentContributions from '@/components/overview/ReecntContributions'; -import GrantHistory from '@/components/overview/GrantHistory'; - -export default function Components() { - return ( -
-
-
- - } - iconPosition='right' - > - Add comment - - } - /> -
-
- - - -
- } - /> - - - No recent submissions -
- } - /> - - 0 - Approved Submissions -
- } - /> - } - bottomText={ -
- 6 grants available -
- } - /> -
- -
- - -
- -
- ); -} diff --git a/app/error.tsx b/app/error.tsx index 3bcb3dc83..59ef05702 100644 --- a/app/error.tsx +++ b/app/error.tsx @@ -95,7 +95,7 @@ const Error: React.FC = ({ error, reset }) => {

Still having issues?{' '} Contact Support diff --git a/app/favicon.ico b/app/favicon.ico index 718d6fea4..562b28a1e 100644 Binary files a/app/favicon.ico and b/app/favicon.ico differ diff --git a/app/globals.css b/app/globals.css index 5c742704f..cb10f508f 100644 --- a/app/globals.css +++ b/app/globals.css @@ -150,6 +150,7 @@ * { @apply border-border outline-ring/50; } + body { @apply bg-background text-foreground; } @@ -186,6 +187,7 @@ input[type='number'] { -moz-appearance: textfield; appearance: textfield; } + .text-gradient { background-image: linear-gradient(180deg, #69726d 0%, #fff 100%); -webkit-background-clip: text; @@ -198,6 +200,7 @@ input[type='number'] { opacity: 0; transform: translateY(-10px); } + to { opacity: 1; transform: translateY(0); @@ -233,3 +236,135 @@ input[type='number'] { will-change: transform; transition: transform 0.2s ease-out; } + +/* Waitlist page advanced text animations */ +.title-char, +.subtitle-char { + display: inline-block; + will-change: transform; + perspective: 1000px; + transform-style: preserve-3d; +} + +.title-word, +.subtitle-word { + display: inline-block; + will-change: transform; + perspective: 1000px; + transform-style: preserve-3d; +} + +.title-line, +.subtitle-line { + display: block; + will-change: transform; + perspective: 1000px; + transform-style: preserve-3d; +} + +/* 3D text effects */ +.title-char, +.subtitle-char { + backface-visibility: hidden; + transform-origin: 50% 50%; +} + +/* Hover effects for interactive text */ +.title-char:hover, +.subtitle-char:hover { + filter: brightness(1.2); + transform: scale(1.1) translateZ(10px); + transition: all 0.3s ease; +} + +/* Enhanced gradient text effects */ +.gradient-text-3d { + background: linear-gradient(45deg, #a7f950, #3ae6b2, #69726d); + background-size: 200% 200%; + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + animation: gradientShift 3s ease-in-out infinite; +} + +@keyframes gradientShift { + 0%, + 100% { + background-position: 0% 50%; + } + + 50% { + background-position: 100% 50%; + } +} + +/* Enhanced text glow effects */ +.text-glow { + filter: drop-shadow(0 0 20px rgba(167, 249, 80, 0.4)); + transition: filter 0.3s ease; +} + +.text-glow:hover { + filter: drop-shadow(0 0 30px rgba(167, 249, 80, 0.6)); +} + +/* Particle trail effect */ +.particle-trail { + position: relative; +} + +.particle-trail::after { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: radial-gradient( + circle, + rgba(167, 249, 80, 0.1) 0%, + transparent 70% + ); + opacity: 0; + transition: opacity 0.3s ease; +} + +.particle-trail:hover::after { + opacity: 1; +} + +/* 3D text depth */ +.text-3d { + text-shadow: + 0 1px 0 #ccc, + 0 2px 0 #c9c9c9, + 0 3px 0 #bbb, + 0 4px 0 #b9b9b9, + 0 5px 0 #aaa, + 0 6px 1px rgba(0, 0, 0, 0.1), + 0 0 5px rgba(0, 0, 0, 0.1), + 0 1px 3px rgba(0, 0, 0, 0.3), + 0 3px 5px rgba(0, 0, 0, 0.2), + 0 5px 10px rgba(0, 0, 0, 0.25), + 0 10px 10px rgba(0, 0, 0, 0.2), + 0 20px 20px rgba(0, 0, 0, 0.15); +} + +.gradient-text { + background-image: linear-gradient( + 273deg, + rgba(167, 249, 80, 0.5) 13.84%, + #3ae6b2 73.28% + ); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + -webkit-text-fill-color: transparent; +} + +.gradient-text-2 { + background: linear-gradient(93deg, #b5b5b5 15.93%, #fff 97.61%); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} diff --git a/app/layout.tsx b/app/layout.tsx index 240a4608a..a2e81ab97 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -3,6 +3,10 @@ import { Geist, Geist_Mono } from 'next/font/google'; import './globals.css'; import { Toaster } from 'sonner'; import { Providers } from './providers'; +import { + generateOrganizationStructuredData, + generateWebsiteStructuredData, +} from '@/lib/structured-data'; const geistSans = Geist({ variable: '--font-geist-sans', @@ -15,8 +19,52 @@ const geistMono = Geist_Mono({ }); export const metadata: Metadata = { - title: 'Boundless Project', - description: 'A platform for crowdfunding and grants', + title: 'Boundless - Ideas Made Boundless', + description: + 'Validate, fund, and grow your project with milestone-based support on Stellar.', + keywords: [ + 'crowdfunding', + 'stellar', + 'blockchain', + 'projects', + 'funding', + 'milestones', + 'boundless', + ], + authors: [{ name: 'Boundless Team' }], + creator: 'Boundless', + publisher: 'Boundless', + robots: 'index, follow', + openGraph: { + type: 'website', + locale: 'en_US', + url: 'https://boundlessfi.xyz', + siteName: 'Boundless', + title: 'Boundless - Ideas Made Boundless', + description: + 'Validate, fund, and grow your project with milestone-based support on Stellar.', + images: [ + { + url: '/BOUNDLESS.png', + width: 1200, + height: 630, + alt: 'Boundless', + }, + ], + }, + twitter: { + card: 'summary_large_image', + site: '@boundless', + creator: '@boundless', + title: 'Boundless - Ideas Made Boundless', + description: + 'Validate, fund, and grow your project with milestone-based support on Stellar.', + images: ['/BOUNDLESS.png'], + }, + alternates: { + canonical: 'https://boundlessfi.xyz', + }, + metadataBase: new URL('https://boundlessfi.xyz'), }; export default function RootLayout({ @@ -26,6 +74,20 @@ export default function RootLayout({ }>) { return ( + +