- {/* Right Panel - Carousel */}
{showCarousel && (
diff --git a/components/campaigns/CampaignTable.tsx b/components/campaigns/CampaignTable.tsx
index 2276f3f66..6cfd18e7b 100644
--- a/components/campaigns/CampaignTable.tsx
+++ b/components/campaigns/CampaignTable.tsx
@@ -23,6 +23,7 @@ import {
import BackingHistory from './backing-history';
import { sampleBackers } from '@/lib/data/backing-history-mock';
import { CampaignTableSkeleton } from '../skeleton/UserPageSkeleton';
+import Pagination from '../ui/pagination';
const CampaignRow = ({
campaign,
@@ -417,7 +418,15 @@ const CampaignRow = ({
);
};
-const CampaignTable = () => {
+interface CampaignTableProps {
+ limit?: number;
+ showPagination?: boolean;
+}
+
+const CampaignTable = ({
+ limit = 100,
+ showPagination = false,
+}: CampaignTableProps) => {
const [statusFilter, setStatusFilter] = useState
('all');
const [tabFilter, setTabFilter] = useState('mine');
const [campaigns, setCampaigns] = useState([]);
@@ -427,7 +436,7 @@ const CampaignTable = () => {
const [backingHistoryOpen, setBackingHistoryOpen] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
- const itemsPerPage = 10;
+ const itemsPerPage = limit;
// Quick filter options - keeping it simple for now
const filterOptions = [
@@ -448,9 +457,10 @@ const CampaignTable = () => {
page,
itemsPerPage
);
- // TODO: Handle empty state better - maybe add a refresh button?
setCampaigns(response.data);
- setTotalPages(Math.ceil(response.total / itemsPerPage));
+ if (showPagination) {
+ setTotalPages(Math.ceil(response.total / itemsPerPage));
+ }
} catch {
setError('Failed to fetch campaigns');
toast.error('Failed to fetch campaigns');
@@ -458,7 +468,7 @@ const CampaignTable = () => {
setLoading(false);
}
},
- [statusFilter, tabFilter, itemsPerPage]
+ [statusFilter, tabFilter, itemsPerPage, showPagination]
);
// Handle different campaign actions - TODO: extract this to a separate hook
@@ -506,8 +516,10 @@ const CampaignTable = () => {
}, [statusFilter, tabFilter, fetchCampaigns]);
useEffect(() => {
- fetchCampaigns(currentPage);
- }, [currentPage, fetchCampaigns]);
+ if (showPagination) {
+ fetchCampaigns(currentPage);
+ }
+ }, [currentPage, fetchCampaigns, showPagination]);
const handlePageChange = (page: number) => {
setCurrentPage(page);
@@ -650,74 +662,12 @@ const CampaignTable = () => {
))
)}
- {totalPages > 1 && campaigns.length > 0 && (
-
-
-
- Page {currentPage} of {totalPages}
-
-
-
-
-
- {/* Basic pagination numbers */}
- {Array.from({ length: totalPages }, (_, i) => i + 1).map(
- page => {
- // Only show if we're within 2 pages or it's first/last
- if (
- page === 1 ||
- page === totalPages ||
- Math.abs(page - currentPage) <= 1
- ) {
- return (
-
- );
- } else if (
- page === currentPage - 2 ||
- page === currentPage + 2
- ) {
- return (
-
- ...
-
- );
- }
- return null;
- }
- )}
-
-
-
-
-
+ {showPagination && campaigns.length > 0 && (
+
)}
diff --git a/components/landing-page/footer.tsx b/components/landing-page/footer.tsx
new file mode 100644
index 000000000..a7caf2b04
--- /dev/null
+++ b/components/landing-page/footer.tsx
@@ -0,0 +1,40 @@
+import Link from 'next/link';
+
+export function Footer() {
+ const currentYear = new Date().getFullYear();
+
+ return (
+
+ );
+}
diff --git a/components/landing-page/index.ts b/components/landing-page/index.ts
new file mode 100644
index 000000000..72a8e6247
--- /dev/null
+++ b/components/landing-page/index.ts
@@ -0,0 +1,2 @@
+export { Navbar } from './navbar';
+export { Footer } from './footer';
diff --git a/components/landing-page/navbar.tsx b/components/landing-page/navbar.tsx
new file mode 100644
index 000000000..0116c4080
--- /dev/null
+++ b/components/landing-page/navbar.tsx
@@ -0,0 +1,450 @@
+'use client';
+import Link from 'next/link';
+import { ChevronDown, Menu, XIcon } from 'lucide-react';
+import Image from 'next/image';
+import { useRef } from 'react';
+import { useGSAP } from '@gsap/react';
+import gsap from 'gsap';
+import { SplitText } from 'gsap/SplitText';
+import { BoundlessButton } from '../buttons';
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu';
+import { Sheet, SheetTrigger, SheetContent, SheetClose } from '../ui/sheet';
+
+gsap.registerPlugin(useGSAP, SplitText);
+
+const menuItems = [
+ { href: '/projects', label: 'Projects' },
+ { href: '/hackathons', label: 'Hackathons' },
+ { href: '/grants', label: 'Grants' },
+];
+
+const learnMenuItems = [
+ { href: '/about', label: 'About' },
+ { href: '/code-of-conduct', label: 'Code of Conduct' },
+ { href: '/privacy', label: 'Privacy Policy' },
+ { href: '/terms', label: 'Terms of Service' },
+ { href: '/disclaimer', label: 'Disclaimer' },
+];
+
+export function Navbar() {
+ const navbarRef = useRef
(null);
+ const logoRef = useRef(null);
+ const menuRef = useRef(null);
+ const ctaRef = useRef(null);
+
+ useGSAP(
+ () => {
+ const tl = gsap.timeline({ defaults: { ease: 'power2.out' } });
+
+ tl.fromTo(
+ navbarRef.current,
+ { y: -20, opacity: 0 },
+ { y: 0, opacity: 1, duration: 0.6 }
+ )
+ .fromTo(
+ logoRef.current,
+ { scale: 0.8, opacity: 0 },
+ { scale: 1, opacity: 1, duration: 0.4 },
+ '-=0.3'
+ )
+ .fromTo(
+ menuRef.current?.children || [],
+ { y: -15, opacity: 0 },
+ { y: 0, opacity: 1, duration: 0.4, stagger: 0.1 },
+ '-=0.2'
+ )
+ .fromTo(
+ ctaRef.current,
+ { y: -15, opacity: 0, scale: 0.95 },
+ { y: 0, opacity: 1, scale: 1, duration: 0.4 },
+ '-=0.1'
+ );
+
+ gsap.to(logoRef.current, {
+ y: -2,
+ duration: 2,
+ ease: 'power2.inOut',
+ yoyo: true,
+ repeat: -1,
+ });
+
+ const logoHover = gsap.to(logoRef.current, {
+ scale: 1.05,
+ duration: 0.2,
+ ease: 'power2.out',
+ paused: true,
+ });
+
+ const logoEnterHandler = () => logoHover.play();
+ const logoLeaveHandler = () => logoHover.reverse();
+
+ logoRef.current?.addEventListener('mouseenter', logoEnterHandler);
+ logoRef.current?.addEventListener('mouseleave', logoLeaveHandler);
+
+ const menuItems = menuRef.current?.querySelectorAll('a');
+ const menuItemAnimations: Array<{
+ item: Element;
+ hoverTl: gsap.core.Timeline;
+ enterHandler: () => void;
+ leaveHandler: () => void;
+ split: any;
+ }> = [];
+
+ menuItems?.forEach(item => {
+ const split = SplitText.create(item, {
+ type: 'chars',
+ charsClass: 'nav-char',
+ });
+
+ const hoverTl = gsap.timeline({ paused: true });
+ hoverTl.to(item, { y: -2, duration: 0.2, ease: 'power2.out' });
+
+ hoverTl.to(
+ split.chars,
+ {
+ y: -3,
+ duration: 0.3,
+ stagger: 0.02,
+ ease: 'power2.out',
+ },
+ '-=0.1'
+ );
+
+ const enterHandler = () => hoverTl.play();
+ const leaveHandler = () => hoverTl.reverse();
+
+ item.addEventListener('mouseenter', enterHandler);
+ item.addEventListener('mouseleave', leaveHandler);
+
+ menuItemAnimations.push({
+ item,
+ hoverTl,
+ enterHandler,
+ leaveHandler,
+ split,
+ });
+ });
+
+ const ctaHover = gsap.to(ctaRef.current, {
+ scale: 1.02,
+ duration: 0.2,
+ ease: 'power2.out',
+ paused: true,
+ });
+
+ const ctaEnterHandler = () => ctaHover.play();
+ const ctaLeaveHandler = () => ctaHover.reverse();
+
+ ctaRef.current?.addEventListener('mouseenter', ctaEnterHandler);
+ ctaRef.current?.addEventListener('mouseleave', ctaLeaveHandler);
+
+ const scrollTl = gsap.timeline({ paused: true });
+ scrollTl.to(navbarRef.current, {
+ backgroundColor: 'rgba(0, 0, 0, 0.8)',
+ backdropFilter: 'blur(10px)',
+ duration: 0.3,
+ ease: 'power2.out',
+ });
+
+ const handleScroll = () => {
+ if (window.scrollY > 50) {
+ scrollTl.play();
+ } else {
+ scrollTl.reverse();
+ }
+ };
+
+ window.addEventListener('scroll', handleScroll);
+
+ return () => {
+ logoHover.kill();
+ ctaHover.kill();
+ scrollTl.kill();
+
+ logoRef.current?.removeEventListener('mouseenter', logoEnterHandler);
+ logoRef.current?.removeEventListener('mouseleave', logoLeaveHandler);
+
+ menuItemAnimations.forEach(
+ ({ item, hoverTl, enterHandler, leaveHandler, split }) => {
+ hoverTl.kill();
+ split.revert();
+ item.removeEventListener('mouseenter', enterHandler);
+ item.removeEventListener('mouseleave', leaveHandler);
+ }
+ );
+
+ ctaRef.current?.removeEventListener('mouseenter', ctaEnterHandler);
+ ctaRef.current?.removeEventListener('mouseleave', ctaLeaveHandler);
+
+ window.removeEventListener('scroll', handleScroll);
+ };
+ },
+ { scope: navbarRef }
+ );
+
+ return (
+
+ );
+}
+
+function MobileMenu() {
+ const mobileMenuRef = useRef(null);
+ const mobileButtonRef = useRef(null);
+ const mobileLogoRef = useRef(null);
+ const mobileMenuItemsRef = useRef(null);
+ const mobileCTARef = useRef(null);
+
+ useGSAP(
+ () => {
+ gsap.fromTo(
+ mobileButtonRef.current,
+ { scale: 0.8, opacity: 0 },
+ { scale: 1, opacity: 1, duration: 0.4, delay: 0.8 }
+ );
+
+ const buttonHover = gsap.to(mobileButtonRef.current, {
+ scale: 1.05,
+ duration: 0.2,
+ ease: 'power2.out',
+ paused: true,
+ });
+
+ const buttonEnterHandler = () => buttonHover.play();
+ const buttonLeaveHandler = () => buttonHover.reverse();
+
+ mobileButtonRef.current?.addEventListener(
+ 'mouseenter',
+ buttonEnterHandler
+ );
+ mobileButtonRef.current?.addEventListener(
+ 'mouseleave',
+ buttonLeaveHandler
+ );
+
+ return () => {
+ buttonHover.kill();
+ mobileButtonRef.current?.removeEventListener(
+ 'mouseenter',
+ buttonEnterHandler
+ );
+ mobileButtonRef.current?.removeEventListener(
+ 'mouseleave',
+ buttonLeaveHandler
+ );
+ };
+ },
+ { scope: mobileMenuRef }
+ );
+
+ const animateMobileMenuOpen = () => {
+ gsap.fromTo(
+ mobileLogoRef.current,
+ { x: -20, opacity: 0 },
+ { x: 0, opacity: 1, duration: 0.5, ease: 'power2.out' }
+ );
+
+ const menuItems = mobileMenuItemsRef.current?.querySelectorAll('a');
+ if (menuItems) {
+ menuItems.forEach((item, index) => {
+ const split = SplitText.create(item, {
+ type: 'chars, words',
+ charsClass: 'char',
+ wordsClass: 'word',
+ });
+
+ gsap.fromTo(
+ split.words,
+ {
+ y: 20,
+ opacity: 0,
+ rotationX: -90,
+ transformOrigin: '50% 50% -20px',
+ },
+ {
+ y: 0,
+ opacity: 1,
+ rotationX: 0,
+ duration: 0.6,
+ stagger: 0.03,
+ delay: 0.2 + index * 0.1,
+ ease: 'back.out(1.7)',
+ }
+ );
+ });
+ }
+
+ gsap.fromTo(
+ mobileCTARef.current,
+ { y: 30, opacity: 0, scale: 0.9 },
+ {
+ y: 0,
+ opacity: 1,
+ scale: 1,
+ duration: 0.5,
+ delay: 0.8,
+ ease: 'back.out(1.7)',
+ }
+ );
+ };
+
+ const animateMobileMenuClose = () => {
+ gsap.to(
+ [mobileLogoRef.current, mobileMenuItemsRef.current, mobileCTARef.current],
+ {
+ opacity: 0,
+ y: -20,
+ duration: 0.3,
+ ease: 'power2.in',
+ }
+ );
+ };
+
+ return (
+
+
{
+ if (open) {
+ setTimeout(animateMobileMenuOpen, 100);
+ } else {
+ animateMobileMenuClose();
+ }
+ }}
+ >
+
+
+
+
+
+
+
+
+ {menuItems.map(item => (
+
+ {item.label}
+
+ ))}
+
+ How it works
+
+
+ About us
+
+
+ Blog
+
+
+
+
+ Get Started
+
+
+
+
+
+ );
+}
diff --git a/components/overview/RecentProjects.tsx b/components/overview/RecentProjects.tsx
index d0e2b7cff..d87108079 100644
--- a/components/overview/RecentProjects.tsx
+++ b/components/overview/RecentProjects.tsx
@@ -1,7 +1,7 @@
'use client';
import React, { useState, useEffect, useCallback } from 'react';
import { RecentProjectsProps, Project } from '@/types/project';
-import { Plus, ChevronRight, ChevronDown } from 'lucide-react';
+import { Plus, ChevronDown } from 'lucide-react';
import ProjectCard from '../project-card';
import EmptyState from '../EmptyState';
import { BoundlessButton } from '../buttons';
@@ -15,11 +15,11 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from '../ui/dropdown-menu';
-import Link from 'next/link';
import { getProjects } from '@/lib/api/project';
import { toast } from 'sonner';
import { useAuth } from '@/hooks/use-auth';
import { RecentProjectsSkeleton } from '../skeleton/UserPageSkeleton';
+import Link from 'next/link';
type StatusFilter =
| 'all'
@@ -144,40 +144,54 @@ const RecentProjects = () => {
variants={fadeInUp}
>
-
- {tabFilter === 'mine' ? 'My Projects' : 'All Projects'}
-
-
-
-
-
-
setTabFilter(value as TabFilter)}
className='w-full sm:w-auto'
>
-
+
- Mine
+ My Projects
- Others
+ Explore ({projects.length})
+
+
+ {/*
setStatusFilter(value as StatusFilter)}
+ className='w-full sm:w-auto'
+ >
+
+
+ All
+
+
+ Funding
+
+
+ Funded
+
+
+ */}
{
let filteredProjects = projects;
// Filter based on user ownership
- if (!isAuthenticated) {
- // If not authenticated, show all projects in both tabs
- } else if (tabFilter === 'mine') {
- // Show only user's own projects
- filteredProjects = projects.filter(
- project =>
- project.owner === user?.id ||
- project.ownerUsername === user?.email ||
- project.ownerName === user?.name
- );
+ if (tabFilter === 'mine') {
+ if (isAuthenticated && user) {
+ // Show only user's own projects when authenticated
+ filteredProjects = projects.filter(
+ project =>
+ project.owner === user?.id ||
+ project.ownerUsername === user?.email ||
+ project.ownerName === user?.name
+ );
+ } else {
+ // If not authenticated, show empty for "mine" tab
+ filteredProjects = [];
+ }
} else {
- // Show all projects except user's own
- filteredProjects = projects.filter(
- project =>
- project.owner !== user?.id &&
- project.ownerUsername !== user?.email &&
- project.ownerName !== user?.name
- );
+ // Show all projects except user's own in "explore" tab
+ if (isAuthenticated && user) {
+ filteredProjects = projects.filter(
+ project =>
+ project.owner !== user?.id &&
+ project.ownerUsername !== user?.email &&
+ project.ownerName !== user?.name
+ );
+ } else {
+ // If not authenticated, show all projects in explore tab
+ filteredProjects = projects;
+ }
}
- // Additional filtering based on status if needed
+ // Additional filtering based on status
if (statusFilter !== 'all') {
filteredProjects = filteredProjects.filter(
project => project.status === statusFilter
@@ -279,11 +301,33 @@ const RecentProjects = () => {
filteredProjects = filteredProjects.slice(0, 3);
if (filteredProjects.length > 0) {
- return filteredProjects.map((project, index) => (
-
-
-
- ));
+ return (
+ <>
+ {filteredProjects.map((project, index) => (
+
+
+
+ ))}
+ {/* Show "View All" button when there are projects */}
+
+
+
+ View All
+
+
+
+ >
+ );
} else {
return (
@@ -291,17 +335,21 @@ const RecentProjects = () => {
void;
showCreatorName?: boolean;
showEllipsisMenu?: boolean;
+ currentUserId?: string | null;
+ currentUserEmail?: string | null;
+ currentUserName?: string | null;
}
type StepState = 'active' | 'pending' | 'completed';
@@ -97,10 +100,22 @@ const ProjectCard: React.FC = ({
onVote,
showCreatorName = false,
showEllipsisMenu = false,
+ currentUserId,
+ currentUserEmail,
+ currentUserName,
}) => {
const [validationSheetOpen, setValidationSheetOpen] = useState(false);
const [launchCampaignSheetOpen, setLaunchCampaignSheetOpen] = useState(false);
+ // Check if current user owns this project
+ const isProjectOwner =
+ (currentUserId && project.owner === currentUserId) ||
+ (currentUserEmail && project.ownerUsername === currentUserEmail) ||
+ (currentUserName && project.ownerName === currentUserName);
+
+ // Only show ellipsis menu if user owns the project
+ const shouldShowEllipsisMenu = showEllipsisMenu && isProjectOwner;
+
// Wallet protection hook
const {
requireWallet,
@@ -289,7 +304,7 @@ const ProjectCard: React.FC = ({
? `${project.name} by Collins Odumeje`
: project.name}
- {showEllipsisMenu && (
+ {shouldShowEllipsisMenu && (
) {
- return (
-
- );
+interface PaginationProps {
+ currentPage: number;
+ totalPages: number;
+ onPageChange: (page: number) => void;
+ className?: string;
}
-function PaginationContent({
- className,
- ...props
-}: React.ComponentProps<'ul'>) {
- return (
-
- );
-}
+const Pagination: React.FC = ({
+ currentPage,
+ totalPages,
+ onPageChange,
+ className = '',
+}) => {
+ if (totalPages <= 1) {
+ return null;
+ }
-function PaginationItem({ ...props }: React.ComponentProps<'li'>) {
- return ;
-}
-
-type PaginationLinkProps = {
- isActive?: boolean;
-} & Pick, 'size'> &
- React.ComponentProps<'a'>;
-
-function PaginationLink({
- className,
- isActive,
- size = 'icon',
- ...props
-}: PaginationLinkProps) {
return (
-
- );
-}
+
+
+
+ Page {currentPage} of {totalPages}
+
-function PaginationPrevious({
- className,
- ...props
-}: React.ComponentProps
) {
- return (
-
-
- Previous
-
- );
-}
+
+
onPageChange(currentPage - 1)}
+ disabled={currentPage === 1}
+ size='sm'
+ variant='outline'
+ className=''
+ >
+ Previous
+
-function PaginationNext({
- className,
- ...props
-}: React.ComponentProps
) {
- return (
-
- Next
-
-
- );
-}
+ {Array.from({ length: totalPages }, (_, i) => i + 1).map(page => {
+ if (
+ page === 1 ||
+ page === totalPages ||
+ Math.abs(page - currentPage) <= 1
+ ) {
+ return (
+ onPageChange(page)}
+ size='sm'
+ variant={currentPage === page ? 'default' : 'outline'}
+ className={
+ currentPage === page
+ ? 'bg-blue-600'
+ : 'border-[#2B2B2B] text-white hover:bg-[#2B2B2B]'
+ }
+ >
+ {page}
+
+ );
+ } else if (page === currentPage - 2 || page === currentPage + 2) {
+ return (
+
+ ...
+
+ );
+ }
+ return null;
+ })}
-function PaginationEllipsis({
- className,
- ...props
-}: React.ComponentProps<'span'>) {
- return (
-
-
- More pages
-
+ onPageChange(currentPage + 1)}
+ disabled={currentPage === totalPages}
+ size='sm'
+ variant='outline'
+ className='border-[#2B2B2B] text-white'
+ >
+ Next
+
+
+
+
);
-}
-
-export {
- Pagination,
- PaginationContent,
- PaginationLink,
- PaginationItem,
- PaginationPrevious,
- PaginationNext,
- PaginationEllipsis,
};
+
+export default Pagination;
diff --git a/lib/api/project.ts b/lib/api/project.ts
index 8fddc04a2..9e1b691e5 100644
--- a/lib/api/project.ts
+++ b/lib/api/project.ts
@@ -9,10 +9,37 @@ export const initProject = async (data: ProjectInitRequest) => {
return res;
};
-export const getProjects = async (): Promise<{
+export const getProjects = async (
+ page = 1,
+ limit = 9,
+ filters?: {
+ status?: string;
+ owner?: string;
+ }
+): Promise<{
projects: RecentProjectsProps[];
+ pagination: {
+ currentPage: number;
+ totalPages: number;
+ totalItems: number;
+ hasNextPage: boolean;
+ hasPrevPage: boolean;
+ };
}> => {
- const res = await api.get('/projects');
+ const params = new URLSearchParams({
+ page: page.toString(),
+ limit: limit.toString(),
+ });
+
+ if (filters?.status && filters.status !== 'all') {
+ params.append('status', filters.status);
+ }
+
+ if (filters?.owner) {
+ params.append('owner', filters.owner);
+ }
+
+ const res = await api.get(`/projects?${params.toString()}`);
return res.data.data;
};
diff --git a/package-lock.json b/package-lock.json
index d07269b9d..aa968fdee 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,7 @@
"version": "0.1.0",
"dependencies": {
"@creit.tech/stellar-wallets-kit": "^1.7.6",
+ "@gsap/react": "^2.1.2",
"@hookform/resolvers": "^5.2.0",
"@radix-ui/react-accordion": "^1.2.11",
"@radix-ui/react-alert-dialog": "^1.1.14",
@@ -46,10 +47,11 @@
"embla-carousel-autoplay": "^8.6.0",
"embla-carousel-react": "^8.6.0",
"framer-motion": "^12.23.12",
+ "gsap": "^3.13.0",
"input-otp": "^1.4.2",
"js-cookie": "^3.0.5",
"lucide-react": "^0.525.0",
- "next": "15.4.4",
+ "next": "^15.5.2",
"next-auth": "^5.0.0-beta.29",
"next-themes": "^0.4.6",
"react": "19.1.0",
@@ -1260,6 +1262,16 @@
"integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
"license": "MIT"
},
+ "node_modules/@gsap/react": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/@gsap/react/-/react-2.1.2.tgz",
+ "integrity": "sha512-JqliybO1837UcgH2hVOM4VO+38APk3ECNrsuSM4MuXp+rbf+/2IG2K1YJiqfTcXQHH7XlA0m3ykniFYstfq0Iw==",
+ "license": "SEE LICENSE AT https://gsap.com/standard-license",
+ "peerDependencies": {
+ "gsap": "^3.12.5",
+ "react": ">=17"
+ }
+ },
"node_modules/@hookform/resolvers": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.2.0.tgz",
@@ -2116,6 +2128,28 @@
"license": "Apache-2.0",
"peer": true
},
+ "node_modules/@near-js/providers/node_modules/node-fetch": {
+ "version": "2.6.7",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
+ "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@near-js/signers": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@near-js/signers/-/signers-0.2.2.tgz",
@@ -2233,9 +2267,9 @@
"license": "MIT"
},
"node_modules/@next/env": {
- "version": "15.4.4",
- "resolved": "https://registry.npmjs.org/@next/env/-/env-15.4.4.tgz",
- "integrity": "sha512-SJKOOkULKENyHSYXE5+KiFU6itcIb6wSBjgM92meK0HVKpo94dNOLZVdLLuS7/BxImROkGoPsjR4EnuDucqiiA==",
+ "version": "15.5.2",
+ "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.2.tgz",
+ "integrity": "sha512-Qe06ew4zt12LeO6N7j8/nULSOe3fMXE4dM6xgpBQNvdzyK1sv5y4oAP3bq4LamrvGCZtmRYnW8URFCeX5nFgGg==",
"license": "MIT"
},
"node_modules/@next/eslint-plugin-next": {
@@ -2249,9 +2283,9 @@
}
},
"node_modules/@next/swc-darwin-arm64": {
- "version": "15.4.4",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.4.4.tgz",
- "integrity": "sha512-eVG55dnGwfUuG+TtnUCt+mEJ+8TGgul6nHEvdb8HEH7dmJIFYOCApAaFrIrxwtEq2Cdf+0m5sG1Np8cNpw9EAw==",
+ "version": "15.5.2",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.2.tgz",
+ "integrity": "sha512-8bGt577BXGSd4iqFygmzIfTYizHb0LGWqH+qgIF/2EDxS5JsSdERJKA8WgwDyNBZgTIIA4D8qUtoQHmxIIquoQ==",
"cpu": [
"arm64"
],
@@ -2265,9 +2299,9 @@
}
},
"node_modules/@next/swc-darwin-x64": {
- "version": "15.4.4",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.4.4.tgz",
- "integrity": "sha512-zqG+/8apsu49CltEj4NAmCGZvHcZbOOOsNoTVeIXphYWIbE4l6A/vuQHyqll0flU2o3dmYCXsBW5FmbrGDgljQ==",
+ "version": "15.5.2",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.2.tgz",
+ "integrity": "sha512-2DjnmR6JHK4X+dgTXt5/sOCu/7yPtqpYt8s8hLkHFK3MGkka2snTv3yRMdHvuRtJVkPwCGsvBSwmoQCHatauFQ==",
"cpu": [
"x64"
],
@@ -2281,9 +2315,9 @@
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
- "version": "15.4.4",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.4.4.tgz",
- "integrity": "sha512-LRD4l2lq4R+2QCHBQVC0wjxxkLlALGJCwigaJ5FSRSqnje+MRKHljQNZgDCaKUZQzO/TXxlmUdkZP/X3KNGZaw==",
+ "version": "15.5.2",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.2.tgz",
+ "integrity": "sha512-3j7SWDBS2Wov/L9q0mFJtEvQ5miIqfO4l7d2m9Mo06ddsgUK8gWfHGgbjdFlCp2Ek7MmMQZSxpGFqcC8zGh2AA==",
"cpu": [
"arm64"
],
@@ -2297,9 +2331,9 @@
}
},
"node_modules/@next/swc-linux-arm64-musl": {
- "version": "15.4.4",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.4.4.tgz",
- "integrity": "sha512-LsGUCTvuZ0690fFWerA4lnQvjkYg9gHo12A3wiPUR4kCxbx/d+SlwmonuTH2SWZI+RVGA9VL3N0S03WTYv6bYg==",
+ "version": "15.5.2",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.2.tgz",
+ "integrity": "sha512-s6N8k8dF9YGc5T01UPQ08yxsK6fUow5gG1/axWc1HVVBYQBgOjca4oUZF7s4p+kwhkB1bDSGR8QznWrFZ/Rt5g==",
"cpu": [
"arm64"
],
@@ -2313,9 +2347,9 @@
}
},
"node_modules/@next/swc-linux-x64-gnu": {
- "version": "15.4.4",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.4.4.tgz",
- "integrity": "sha512-aOy5yNRpLL3wNiJVkFYl6w22hdREERNjvegE6vvtix8LHRdsTHhWTpgvcYdCK7AIDCQW5ATmzr9XkPHvSoAnvg==",
+ "version": "15.5.2",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.2.tgz",
+ "integrity": "sha512-o1RV/KOODQh6dM6ZRJGZbc+MOAHww33Vbs5JC9Mp1gDk8cpEO+cYC/l7rweiEalkSm5/1WGa4zY7xrNwObN4+Q==",
"cpu": [
"x64"
],
@@ -2329,9 +2363,9 @@
}
},
"node_modules/@next/swc-linux-x64-musl": {
- "version": "15.4.4",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.4.4.tgz",
- "integrity": "sha512-FL7OAn4UkR8hKQRGBmlHiHinzOb07tsfARdGh7v0Z0jEJ3sz8/7L5bR23ble9E6DZMabSStqlATHlSxv1fuzAg==",
+ "version": "15.5.2",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.2.tgz",
+ "integrity": "sha512-/VUnh7w8RElYZ0IV83nUcP/J4KJ6LLYliiBIri3p3aW2giF+PAVgZb6mk8jbQSB3WlTai8gEmCAr7kptFa1H6g==",
"cpu": [
"x64"
],
@@ -2345,9 +2379,9 @@
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
- "version": "15.4.4",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.4.4.tgz",
- "integrity": "sha512-eEdNW/TXwjYhOulQh0pffTMMItWVwKCQpbziSBmgBNFZIIRn2GTXrhrewevs8wP8KXWYMx8Z+mNU0X+AfvtrRg==",
+ "version": "15.5.2",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.2.tgz",
+ "integrity": "sha512-sMPyTvRcNKXseNQ/7qRfVRLa0VhR0esmQ29DD6pqvG71+JdVnESJaHPA8t7bc67KD5spP3+DOCNLhqlEI2ZgQg==",
"cpu": [
"arm64"
],
@@ -2361,9 +2395,9 @@
}
},
"node_modules/@next/swc-win32-x64-msvc": {
- "version": "15.4.4",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.4.4.tgz",
- "integrity": "sha512-SE5pYNbn/xZKMy1RE3pAs+4xD32OI4rY6mzJa4XUkp/ItZY+OMjIgilskmErt8ls/fVJ+Ihopi2QIeW6O3TrMw==",
+ "version": "15.5.2",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.2.tgz",
+ "integrity": "sha512-W5VvyZHnxG/2ukhZF/9Ikdra5fdNftxI6ybeVKYvBPDtyx7x4jPPSNduUkfH5fo3zG0JQ0bPxgy41af2JX5D4Q==",
"cpu": [
"x64"
],
@@ -11141,6 +11175,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/gsap": {
+ "version": "3.13.0",
+ "resolved": "https://registry.npmjs.org/gsap/-/gsap-3.13.0.tgz",
+ "integrity": "sha512-QL7MJ2WMjm1PHWsoFrAQH/J8wUeqZvMtHO58qdekHpCfhvhSL4gSiz6vJf5EeMP0LOn3ZCprL2ki/gjED8ghVw==",
+ "license": "Standard 'no charge' license: https://gsap.com/standard-license."
+ },
"node_modules/h3": {
"version": "1.15.3",
"resolved": "https://registry.npmjs.org/h3/-/h3-1.15.3.tgz",
@@ -13314,12 +13354,12 @@
}
},
"node_modules/next": {
- "version": "15.4.4",
- "resolved": "https://registry.npmjs.org/next/-/next-15.4.4.tgz",
- "integrity": "sha512-kNcubvJjOL9yUOfwtZF3HfDhuhp+kVD+FM2A6Tyua1eI/xfmY4r/8ZS913MMz+oWKDlbps/dQOWdDricuIkXLw==",
+ "version": "15.5.2",
+ "resolved": "https://registry.npmjs.org/next/-/next-15.5.2.tgz",
+ "integrity": "sha512-H8Otr7abj1glFhbGnvUt3gz++0AF1+QoCXEBmd/6aKbfdFwrn0LpA836Ed5+00va/7HQSDD+mOoVhn3tNy3e/Q==",
"license": "MIT",
"dependencies": {
- "@next/env": "15.4.4",
+ "@next/env": "15.5.2",
"@swc/helpers": "0.5.15",
"caniuse-lite": "^1.0.30001579",
"postcss": "8.4.31",
@@ -13332,14 +13372,14 @@
"node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
},
"optionalDependencies": {
- "@next/swc-darwin-arm64": "15.4.4",
- "@next/swc-darwin-x64": "15.4.4",
- "@next/swc-linux-arm64-gnu": "15.4.4",
- "@next/swc-linux-arm64-musl": "15.4.4",
- "@next/swc-linux-x64-gnu": "15.4.4",
- "@next/swc-linux-x64-musl": "15.4.4",
- "@next/swc-win32-arm64-msvc": "15.4.4",
- "@next/swc-win32-x64-msvc": "15.4.4",
+ "@next/swc-darwin-arm64": "15.5.2",
+ "@next/swc-darwin-x64": "15.5.2",
+ "@next/swc-linux-arm64-gnu": "15.5.2",
+ "@next/swc-linux-arm64-musl": "15.5.2",
+ "@next/swc-linux-x64-gnu": "15.5.2",
+ "@next/swc-linux-x64-musl": "15.5.2",
+ "@next/swc-win32-arm64-msvc": "15.5.2",
+ "@next/swc-win32-x64-msvc": "15.5.2",
"sharp": "^0.34.3"
},
"peerDependencies": {
diff --git a/package.json b/package.json
index 496e88312..423bec613 100644
--- a/package.json
+++ b/package.json
@@ -24,6 +24,7 @@
},
"dependencies": {
"@creit.tech/stellar-wallets-kit": "^1.7.6",
+ "@gsap/react": "^2.1.2",
"@hookform/resolvers": "^5.2.0",
"@radix-ui/react-accordion": "^1.2.11",
"@radix-ui/react-alert-dialog": "^1.1.14",
@@ -61,10 +62,11 @@
"embla-carousel-autoplay": "^8.6.0",
"embla-carousel-react": "^8.6.0",
"framer-motion": "^12.23.12",
+ "gsap": "^3.13.0",
"input-otp": "^1.4.2",
"js-cookie": "^3.0.5",
"lucide-react": "^0.525.0",
- "next": "15.4.4",
+ "next": "^15.5.2",
"next-auth": "^5.0.0-beta.29",
"next-themes": "^0.4.6",
"react": "19.1.0",
diff --git a/public/_ui-check-circle.svg b/public/_ui-check-circle.svg
new file mode 100644
index 000000000..ada2adbfc
--- /dev/null
+++ b/public/_ui-check-circle.svg
@@ -0,0 +1,4 @@
+