-
+
DatEx
-
+
Terms of Service • Privacy Policy
© 2026 Data Exchange Inc.
@@ -81,3 +70,108 @@ export default function Footer() {
);
}
+
+import Marquee from "@/components/ui/Marquee";
+import FlipCard from "@/components/ui/FlipCard";
+import { Mail, Send, Instagram } from "lucide-react";
+
+function SupportMarquee() {
+ const socials = [
+ { name: "Discord", icon:
, href: "#" },
+ { name: "Telegram", icon:
, href: "#" },
+ { name: "X", icon:
, href: "#" },
+ { name: "Instagram", icon:
, href: "#" },
+ { name: "Gmail", icon:
, href: "#" },
+ ];
+
+ return (
+
+ {/* Row 1: Connections */}
+
+
+ }
+ >
+ {/* Front: Black */}
+
+ {social.icon}
+ {social.name}
+
+
+ ))}
+
+
+
+ {/* Row 2: Get Support Now */}
+
+
+
+
+ {/* Row 3: Connections (Reverse) */}
+
+
+ }
+ >
+ {/* Front: White */}
+
+ {social.icon}
+ {social.name}
+
+
+ ))}
+
+
+
+ );
+}
+
+function SocialIcon({ platform }: { platform: string }) {
+ // Simplified for bottom bar
+ return (
+
+ {platform === 'discord' && }
+ {platform === 'x' && }
+ {platform === 'instagram' && }
+
+ );
+}
+
+// Icons
+function DiscordIcon({ className = "w-6 h-6" }: { className?: string }) {
+ return (
+
+ )
+}
+
+function XIcon({ className = "w-6 h-6" }: { className?: string }) {
+ return (
+
+ )
+}
+
diff --git a/components/Navbar.tsx b/components/Navbar.tsx
index 2a84c3e..27ec729 100644
--- a/components/Navbar.tsx
+++ b/components/Navbar.tsx
@@ -17,8 +17,8 @@ export default function Navbar() {
Home
-
- App
+
+ Marketplace
About
diff --git a/components/marketplace/DataDetail.tsx b/components/marketplace/DataDetail.tsx
index b99b2f2..ba3d52d 100644
--- a/components/marketplace/DataDetail.tsx
+++ b/components/marketplace/DataDetail.tsx
@@ -46,50 +46,50 @@ export default function DataDetail({ asset, onBack }: DataDetailProps) {
gsap.set(sealRef.current, { scale: 0.5, opacity: 0, rotation: 0 });
gsap.set(progressRef.current, { width: "0%" });
gsap.set(iconRef.current, { scale: 1, opacity: 1, color: "white" });
-
+
// Animation Sequence
tl.to(overlayRef.current, { opacity: 1, duration: 0.2 })
- .to(sealRef.current, {
- scale: 1,
- opacity: 1,
- duration: 0.5,
- ease: "back.out(1.7)"
+ .to(sealRef.current, {
+ scale: 1,
+ opacity: 1,
+ duration: 0.5,
+ ease: "back.out(1.7)"
})
.add(() => {
- if(textRef.current) textRef.current.innerText = "VERIFYING OWNERSHIP";
- if(subTextRef.current) subTextRef.current.innerText = "Checking wallet signature...";
+ if (textRef.current) textRef.current.innerText = "VERIFYING OWNERSHIP";
+ if (subTextRef.current) subTextRef.current.innerText = "Checking wallet signature...";
})
.to(sealRef.current, { rotation: 180, duration: 1, ease: "power2.inOut" })
.to(progressRef.current, { width: "40%", duration: 1 }, "<")
.to(iconRef.current, { scale: 0, rotation: 90, duration: 0.2 })
.add(() => {
- if(iconRef.current) iconRef.current.innerText = "lock_open";
- if(textRef.current) textRef.current.innerText = "DECRYPTING SHARDS";
- if(subTextRef.current) subTextRef.current.innerText = "Reassembling Walrus Protocol data...";
+ if (iconRef.current) iconRef.current.innerText = "lock_open";
+ if (textRef.current) textRef.current.innerText = "DECRYPTING SHARDS";
+ if (subTextRef.current) subTextRef.current.innerText = "Reassembling Walrus Protocol data...";
})
.to(iconRef.current, { scale: 1, rotation: 90, duration: 0.2 })
.to(progressRef.current, { width: "75%", duration: 1.2 })
.to(sealRef.current, { x: 5, duration: 0.05, yoyo: true, repeat: 5 })
- .to(sealRef.current, {
- borderColor: "#ccff00",
- backgroundColor: "#101618",
- boxShadow: "0 0 50px rgba(204, 255, 0, 0.4)",
- scale: 1.1,
- duration: 0.3
+ .to(sealRef.current, {
+ borderColor: "#ccff00",
+ backgroundColor: "#101618",
+ boxShadow: "0 0 50px rgba(204, 255, 0, 0.4)",
+ scale: 1.1,
+ duration: 0.3
})
.to(iconRef.current, { scale: 0, duration: 0.1 }, "<")
.add(() => {
- if(iconRef.current) {
- iconRef.current.innerText = "download";
- iconRef.current.style.color = "#ccff00";
- iconRef.current.style.transform = "rotate(0deg)";
- }
- if(textRef.current) {
- textRef.current.innerText = "DECRYPTION COMPLETE";
- textRef.current.classList.add("text-accent-lime");
- }
- if(subTextRef.current) subTextRef.current.innerText = "Download starting now.";
- if(pathRef.current) pathRef.current.style.fill = "#ccff00";
+ if (iconRef.current) {
+ iconRef.current.innerText = "download";
+ iconRef.current.style.color = "#ccff00";
+ iconRef.current.style.transform = "rotate(0deg)";
+ }
+ if (textRef.current) {
+ textRef.current.innerText = "DECRYPTION COMPLETE";
+ textRef.current.classList.add("text-accent-lime");
+ }
+ if (subTextRef.current) subTextRef.current.innerText = "Download starting now.";
+ if (pathRef.current) pathRef.current.style.fill = "#ccff00";
})
.to(iconRef.current, { scale: 1.5, rotation: 180, duration: 0.4, ease: "elastic.out(1, 0.5)" })
.to(progressRef.current, { width: "100%", backgroundColor: "#ccff00", duration: 0.3 });
@@ -100,14 +100,14 @@ export default function DataDetail({ asset, onBack }: DataDetailProps) {
{/* LEFT PANEL */}
-
-
+
{asset.tags && asset.tags.length > 0 ? (
@@ -126,9 +126,9 @@ export default function DataDetail({ asset, onBack }: DataDetailProps) {
{asset.title}
Updated: {asset.updatedAt || 'Recently'}
-
+
-
+
Storage Size
@@ -146,7 +146,7 @@ export default function DataDetail({ asset, onBack }: DataDetailProps) {
{asset.formats?.join(' / ') || 'CSV'}
-
+
Current Price
@@ -155,7 +155,7 @@ export default function DataDetail({ asset, onBack }: DataDetailProps) {
≈ ${(asset.price * 1.25).toFixed(2)} USD
-
-
Secured by Walrus Protocol
@@ -172,19 +171,19 @@ export default function DataDetail({ asset, onBack }: DataDetailProps) {
-
+
{/* Hero Image */}
-
-
-
+
{asset.reviews ? asset.reviews.map(review => (
{review.avatar ? (
-
) : (
@@ -330,7 +329,7 @@ export default function DataDetail({ asset, onBack }: DataDetailProps) {
You are about to purchase
{asset.title}.
-
+
Total
@@ -360,22 +359,22 @@ export default function DataDetail({ asset, onBack }: DataDetailProps) {
{/* Decryption Animation Overlay */}
{isDownloadProcessing && (
-
{/* The Decryption Seal */}
-
{/* Animated Rings */}
-
+
{/* Icon */}
lock
-
+
{/* Decorative Text Ring */}
{/* Status Text */}
-
INITIALIZING
@@ -400,8 +399,8 @@ export default function DataDetail({ asset, onBack }: DataDetailProps) {
{/* Progress Bar */}
diff --git a/components/marketplace/MarketplaceHeader.tsx b/components/marketplace/MarketplaceHeader.tsx
index a550e1e..f19e0fb 100644
--- a/components/marketplace/MarketplaceHeader.tsx
+++ b/components/marketplace/MarketplaceHeader.tsx
@@ -2,7 +2,23 @@
import React from 'react';
-export default function MarketplaceHeader() {
+interface MarketplaceHeaderProps {
+ search?: string;
+ onSearchChange?: (value: string) => void;
+ onSearch?: () => void;
+}
+
+export default function MarketplaceHeader({
+ search = '',
+ onSearchChange,
+ onSearch,
+}: MarketplaceHeaderProps) {
+ const handleKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === 'Enter') {
+ onSearch?.();
+ }
+ };
+
return (
Category
- {Object.entries(categories).map(([key, value]) => (
+ {filters.categories.map((category) => (
))}
@@ -214,28 +261,38 @@ export default function MarketplaceSidebar() {
Price Range (SUI)
-
-
-
+
0 SUI
- 1000 SUI
+ {filters.priceRange.max} SUI
-
Reliability Score
+ {/*
Reliability Score
*/}
setVerified(!verified)}
+ onClick={() => setVerifiedOnly(!filters.verifiedOnly)}
>
Verified Source
@@ -245,18 +302,31 @@ export default function MarketplaceSidebar() {
Asset Status
-
-
+
{/* Balances */}
diff --git a/components/ui/FlipCard.tsx b/components/ui/FlipCard.tsx
new file mode 100644
index 0000000..486dcfb
--- /dev/null
+++ b/components/ui/FlipCard.tsx
@@ -0,0 +1,50 @@
+"use client";
+
+import { motion } from "motion/react";
+import React from "react";
+import { cn } from "@/lib/utils";
+
+interface FlipCardProps {
+ children: React.ReactNode;
+ backChildren: React.ReactNode;
+ className?: string;
+ href?: string;
+ duration?: number;
+}
+
+export default function FlipCard({
+ children,
+ backChildren,
+ className,
+ href,
+ duration = 0.6,
+ ...props
+}: FlipCardProps & React.ComponentProps<"a"> & React.ComponentProps<"button">) {
+ const Tag = href ? "a" : "button";
+
+ return (
+
+
+ {/* Front */}
+
+ {children}
+
+
+ {/* Back */}
+
+ {backChildren}
+
+
+
+ );
+}
diff --git a/components/ui/Marquee.tsx b/components/ui/Marquee.tsx
new file mode 100644
index 0000000..9f0e2ad
--- /dev/null
+++ b/components/ui/Marquee.tsx
@@ -0,0 +1,66 @@
+"use client";
+
+import { cn } from "@/lib/utils";
+import { motion } from "motion/react";
+import React from "react";
+
+interface MarqueeProps {
+ className?: string;
+ reverse?: boolean;
+ pauseOnHover?: boolean;
+ children?: React.ReactNode;
+ vertical?: boolean;
+ repeat?: number;
+ duration?: number;
+}
+
+export default function Marquee({
+ className,
+ reverse,
+ pauseOnHover = false,
+ children,
+ vertical = false,
+ repeat = 4,
+ duration = 20,
+ ...props
+}: MarqueeProps) {
+ return (
+
+ {Array(repeat)
+ .fill(0)
+ .map((_, i) => (
+
+ {children}
+
+ ))}
+
+ );
+}
diff --git a/contexts/MarketplaceFilterContext.tsx b/contexts/MarketplaceFilterContext.tsx
new file mode 100644
index 0000000..aebadf5
--- /dev/null
+++ b/contexts/MarketplaceFilterContext.tsx
@@ -0,0 +1,24 @@
+'use client';
+
+import React, { createContext, useContext, ReactNode } from 'react';
+import { useMarketplaceFilters, UseMarketplaceFiltersReturn } from '@/hooks/useMarketplaceFilters';
+
+const MarketplaceFilterContext = createContext
(null);
+
+export function MarketplaceFilterProvider({ children }: { children: ReactNode }) {
+ const filterState = useMarketplaceFilters();
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useMarketplaceFilterContext() {
+ const context = useContext(MarketplaceFilterContext);
+ if (!context) {
+ throw new Error('useMarketplaceFilterContext must be used within MarketplaceFilterProvider');
+ }
+ return context;
+}
diff --git a/hooks/useMarketplaceFilters.ts b/hooks/useMarketplaceFilters.ts
new file mode 100644
index 0000000..81685b0
--- /dev/null
+++ b/hooks/useMarketplaceFilters.ts
@@ -0,0 +1,135 @@
+'use client';
+
+import { useState, useCallback, useMemo } from 'react';
+import { FilterState, CategoryFilter, SortOption, DEFAULT_FILTERS, DataAsset } from '@/types/marketplace';
+
+export interface UseMarketplaceFiltersReturn {
+ filters: FilterState;
+ setCategory: (id: string, checked: boolean) => void;
+ setPriceRange: (min: number, max: number) => void;
+ setVerifiedOnly: (value: boolean) => void;
+ setSearch: (query: string) => void;
+ setSortBy: (sort: SortOption) => void;
+ resetFilters: () => void;
+ filterAssets: (assets: DataAsset[]) => DataAsset[];
+}
+
+export function useMarketplaceFilters(): UseMarketplaceFiltersReturn {
+ const [filters, setFilters] = useState(DEFAULT_FILTERS);
+
+ const setCategory = useCallback((id: string, checked: boolean) => {
+ setFilters((prev) => ({
+ ...prev,
+ categories: prev.categories.map((cat) =>
+ cat.id === id ? { ...cat, checked } : cat
+ ),
+ }));
+ }, []);
+
+ const setPriceRange = useCallback((min: number, max: number) => {
+ setFilters((prev) => ({
+ ...prev,
+ priceRange: { min, max },
+ }));
+ }, []);
+
+ const setVerifiedOnly = useCallback((value: boolean) => {
+ setFilters((prev) => ({
+ ...prev,
+ verifiedOnly: value,
+ }));
+ }, []);
+
+ const setSearch = useCallback((query: string) => {
+ setFilters((prev) => ({
+ ...prev,
+ search: query,
+ }));
+ }, []);
+
+ const setSortBy = useCallback((sort: SortOption) => {
+ setFilters((prev) => ({
+ ...prev,
+ sortBy: sort,
+ }));
+ }, []);
+
+ const resetFilters = useCallback(() => {
+ setFilters(DEFAULT_FILTERS);
+ }, []);
+
+ // Client-side filtering - replace with API call later
+ const filterAssets = useCallback(
+ (assets: DataAsset[]): DataAsset[] => {
+ let result = [...assets];
+
+ // Filter by search
+ if (filters.search.trim()) {
+ const searchLower = filters.search.toLowerCase();
+ result = result.filter(
+ (asset) =>
+ asset.title.toLowerCase().includes(searchLower) ||
+ asset.description.toLowerCase().includes(searchLower)
+ );
+ }
+
+ // Filter by categories
+ const activeCategories = filters.categories.filter((c) => c.checked);
+ if (activeCategories.length > 0) {
+ result = result.filter((asset) =>
+ asset.tags.some((tag) =>
+ activeCategories.some(
+ (cat) =>
+ tag.label.toLowerCase().includes(cat.id.toLowerCase()) ||
+ cat.label.toLowerCase().includes(tag.label.toLowerCase())
+ )
+ )
+ );
+ }
+
+ // Filter by price range
+ result = result.filter(
+ (asset) =>
+ asset.price >= filters.priceRange.min &&
+ asset.price <= filters.priceRange.max
+ );
+
+ // Filter by verified only
+ if (filters.verifiedOnly) {
+ result = result.filter((asset) =>
+ asset.tags.some((tag) => tag.type === 'verified')
+ );
+ }
+
+ // Sort
+ switch (filters.sortBy) {
+ case 'newest':
+ // Keep original order (assuming newest first)
+ break;
+ case 'oldest':
+ result = result.reverse();
+ break;
+ case 'price_asc':
+ result = result.sort((a, b) => a.price - b.price);
+ break;
+ case 'price_desc':
+ result = result.sort((a, b) => b.price - a.price);
+ break;
+ }
+
+ return result;
+ },
+ [filters]
+ );
+
+ return {
+ filters,
+ setCategory,
+ setPriceRange,
+ setVerifiedOnly,
+ setSearch,
+ setSortBy,
+ resetFilters,
+ filterAssets,
+ };
+}
diff --git a/types/marketplace.ts b/types/marketplace.ts
index 963fd90..e35af9a 100644
--- a/types/marketplace.ts
+++ b/types/marketplace.ts
@@ -68,6 +68,39 @@ export interface Purchase {
iconBg: string;
}
+// From landing branch
+export type SortOption = 'newest' | 'oldest' | 'price_asc' | 'price_desc';
+
+export interface CategoryFilter {
+ id: string;
+ label: string;
+ checked: boolean;
+}
+
+export interface FilterState {
+ categories: CategoryFilter[];
+ priceRange: { min: number; max: number };
+ verifiedOnly: boolean;
+ search: string;
+ sortBy: SortOption;
+}
+
+export const DEFAULT_CATEGORIES: CategoryFilter[] = [
+ { id: 'defi', label: 'Finance (DeFi)', checked: false },
+ { id: 'social', label: 'Social Graph', checked: false },
+ { id: 'healthcare', label: 'Healthcare', checked: false },
+ { id: 'gaming', label: 'Gaming', checked: false },
+];
+
+export const DEFAULT_FILTERS: FilterState = {
+ categories: DEFAULT_CATEGORIES,
+ priceRange: { min: 0, max: 1000 },
+ verifiedOnly: false,
+ search: '',
+ sortBy: 'newest',
+};
+
+// From main branch
export interface DatasetListing {
id: string;
seller: string;