From 1cda0d7aae831706833888e1273c3c32f7858619 Mon Sep 17 00:00:00 2001 From: paolomolo Date: Tue, 25 Nov 2025 12:19:21 +0100 Subject: [PATCH 01/58] feat(dashboard): add left rail with connect wallet and swap widget - Implement LeftRail component with Connect Wallet card and SwapForm - specific Connect Wallet card for logged out users - Update SocialLayout to use LeftRail in Shell --- src/components/layout/LeftRail.tsx | 727 ++----------------------- src/components/layout/SocialLayout.tsx | 5 +- 2 files changed, 57 insertions(+), 675 deletions(-) diff --git a/src/components/layout/LeftRail.tsx b/src/components/layout/LeftRail.tsx index 23301b5f3..1b59a61ba 100644 --- a/src/components/layout/LeftRail.tsx +++ b/src/components/layout/LeftRail.tsx @@ -1,687 +1,68 @@ -import React, { useEffect, useState } from "react"; -import { useLocation, useNavigate } from "react-router-dom"; -import { SuperheroApi } from "../../api/backend"; -import { useAeSdk } from "../../hooks"; -import WebSocketClient from "../../libs/WebSocketClient"; - -interface TrendingTag { - tag: string; - score: number; - source?: string; -} - -interface TokenItem { - address: string; - name: string; - symbol: string; - price: number; - market_cap: number; - holders_count: number; - sale_address?: string; - trending_score?: number; -} - -interface LiveTransaction { - sale_address: string; - token_name: string; - type: string; - created_at: string; -} +import React from "react"; +import { useAeSdk, useWalletConnect } from "../../hooks"; +import SwapForm from "../dex/core/SwapForm"; +import AeButton from "../AeButton"; +import { IconWallet } from "../../icons"; export default function LeftRail() { - const { sdk, currentBlockHeight } = useAeSdk(); - const navigate = useNavigate(); - const location = useLocation(); - const [currentTime, setCurrentTime] = useState(new Date()); - const [isOnline, setIsOnline] = useState(navigator.onLine); - const [isRefreshing, setIsRefreshing] = useState(false); - const [showTips, setShowTips] = useState(false); - const [showLiveFeed, setShowLiveFeed] = useState(true); - const [trendingTags, setTrendingTags] = useState([]); - const [liveTransactions, setLiveTransactions] = useState( - [] - ); - const [marketStats, setMarketStats] = useState(null); - const [topTokens, setTopTokens] = useState([]); - const [priceAlerts, setPriceAlerts] = useState< - Array<{ token: string; price: number; change: number }> - >([]); - // Removed local API status (moved to footer) - - // Timer, online status, and block height - useEffect(() => { - const timer = setInterval(() => setCurrentTime(new Date()), 1000); - const handleOnline = () => setIsOnline(true); - const handleOffline = () => setIsOnline(false); - - window.addEventListener("online", handleOnline); - window.addEventListener("offline", handleOffline); - - return () => { - clearInterval(timer); - window.removeEventListener("online", handleOnline); - window.removeEventListener("offline", handleOffline); - }; - }, []); - - // Enhanced time formatting with emoji and block height - const formatTime = (date: Date) => { - const hour = date.getHours(); - let timeEmoji = "πŸŒ…"; - if (hour >= 6 && hour < 12) timeEmoji = "πŸŒ…"; - else if (hour >= 12 && hour < 17) timeEmoji = "β˜€οΈ"; - else if (hour >= 17 && hour < 20) timeEmoji = "πŸŒ†"; - else timeEmoji = "πŸŒ™"; - - const timeString = date.toLocaleTimeString("en-US", { - hour12: false, - hour: "2-digit", - minute: "2-digit", - second: "2-digit", - }); - - const dateString = date.toLocaleDateString("en-US", { - weekday: "short", - month: "short", - day: "numeric", - }); - - return { timeEmoji, timeString, dateString }; - }; - - // Load trending data - useEffect(() => { - let cancelled = false; - async function loadTrendingData() { - try { - const [tagsResp, tokensResp, statsResp] = await Promise.all([ - SuperheroApi.listTrendingTags({ - orderBy: "score", - orderDirection: "DESC", - limit: 10, - }), - SuperheroApi.listTokens({ - orderBy: "market_cap", - orderDirection: "DESC", - limit: 5, - }), - SuperheroApi.fetchJson("/api/analytics/past-24-hours"), - ]); - - if (!cancelled) { - try { - const tags = Array.isArray(tagsResp?.items) ? tagsResp.items : []; - const mappedTags = tags.map((it: any) => ({ - tag: it.tag ?? it.name ?? "", - score: Number(it.score ?? it.value ?? 0), - source: it.source || it.platform || undefined, - })); - setTrendingTags(mappedTags.filter((t) => t.tag)); - - const tokens = tokensResp?.items ?? []; - // Ensure token data is properly formatted - const formattedTokens = tokens.map((token: any) => ({ - ...token, - price: token.price ? Number(token.price) : null, - market_cap: token.market_cap ? Number(token.market_cap) : 0, - holders_count: token.holders_count - ? Number(token.holders_count) - : 0, - })); - setTopTokens(formattedTokens); - - setMarketStats(statsResp); - } catch (parseError) { - console.error("Failed to parse trending data:", parseError); - // Set empty arrays as fallback - setTrendingTags([]); - setTopTokens([]); - setMarketStats(null); - } - } - } catch (error) { - console.error("Failed to load trending data:", error); - // Set empty arrays as fallback - if (!cancelled) { - setTrendingTags([]); - setTopTokens([]); - setMarketStats(null); - } - } - } - loadTrendingData(); - return () => { - cancelled = true; - }; - }, []); - - // Load live transactions - useEffect(() => { - let cancelled = false; - async function loadLiveTransactions() { - try { - const [txResp, createdResp] = await Promise.all([ - SuperheroApi.fetchJson("/api/transactions?limit=5"), - SuperheroApi.fetchJson( - "/api/tokens?order_by=created_at&order_direction=DESC&limit=3" - ), - ]); - - if (!cancelled) { - try { - const txItems = txResp?.items ?? []; - const createdItems = (createdResp?.items ?? []).map((t: any) => ({ - sale_address: t.sale_address || t.address || "", - token_name: t.name || "Unknown Token", - type: "CREATED", - created_at: t.created_at || new Date().toISOString(), - })); - setLiveTransactions([...createdItems, ...txItems].slice(0, 8)); - } catch (parseError) { - console.error("Failed to parse live transactions:", parseError); - setLiveTransactions([]); - } - } - } catch (error) { - console.error("Failed to load live transactions:", error); - if (!cancelled) { - setLiveTransactions([]); - } - } - } - loadLiveTransactions(); - - // WebSocket subscriptions for real-time updates - const unsub1 = WebSocketClient.subscribeForTokenHistories("TokenTransaction", (tx) => { - setLiveTransactions((prev) => - [ - { - sale_address: tx?.sale_address || tx?.token_address || "", - token_name: tx?.token_name || "Unknown", - type: "TRADE", - created_at: new Date().toISOString(), - }, - ...prev, - ].slice(0, 8) - ); - }); - - const unsub2 = WebSocketClient.subscribeForTokenHistories("TokenCreated", (payload) => { - setLiveTransactions((prev) => - [ - { - sale_address: payload?.sale_address || payload?.address || "", - token_name: payload?.name || "New Token", - type: "CREATED", - created_at: payload?.created_at || new Date().toISOString(), - }, - ...prev, - ].slice(0, 8) - ); - }); - - return () => { - cancelled = true; - unsub1(); - unsub2(); - }; - }, []); - - // API status moved to footer - - // Simulate price alerts (in real app, this would come from user preferences) - useEffect(() => { - const alerts = [ - { token: "AE", price: 0.15, change: 2.5 }, - { token: "SUPER", price: 0.08, change: -1.2 }, - { token: "MEME", price: 0.003, change: 15.7 }, - ]; - setPriceAlerts(alerts); - }, []); - - const handleQuickAction = (action: string) => { - switch (action) { - case "explore": - navigate("/pool/add-tokens"); - break; - case "bridge": - navigate("/dex"); - break; - case "nfts": - navigate("/trends"); - break; - case "trending": - navigate("/trends"); - break; - case "governance": - navigate("/voting"); - break; - case "meet": - navigate("/meet"); - break; - default: - break; - } - }; - - const handleTrendingTopic = (topic: string) => { - navigate(`/trends?q=${encodeURIComponent(topic)}`); - }; - - const handleTokenClick = (token: TokenItem) => { - navigate(`/trends/tokens/${token.name}`); - }; - - const formatMarketCap = (amount: number): string => { - if (amount >= 1000000) { - return `$${(amount / 1000000).toFixed(1)}M`; - } else if (amount >= 1000) { - return `$${(amount / 1000).toFixed(1)}K`; - } - return `$${amount.toFixed(0)}`; - }; - - const enhancedTips = [ - { - icon: "πŸ’Ž", - color: "var(--neon-teal)", - text: "Use hardware wallets for large amounts", - expanded: - "Hardware wallets like Ledger or Trezor provide the highest security for storing significant amounts of cryptocurrency.", - category: "Security", - }, - { - icon: "πŸ”’", - color: "var(--neon-pink)", - text: "Always verify contract addresses", - expanded: - "Double-check contract addresses before interacting. One wrong character can lead to permanent loss of funds.", - category: "Security", - }, - { - icon: "⚑", - color: "var(--neon-blue)", - text: "Keep some AE for gas fees", - expanded: - "Always maintain a small balance of AE tokens to pay for transaction fees on the Γ¦ternity network.", - category: "Trading", - }, - { - icon: "πŸ›‘οΈ", - color: "var(--neon-yellow)", - text: "Never share your private keys", - expanded: - "Your private keys are like the password to your bank account. Never share them with anyone, including support.", - category: "Security", - }, - { - icon: "πŸ“±", - color: "var(--neon-purple)", - text: "Enable 2FA on exchanges", - expanded: - "Use two-factor authentication on all cryptocurrency exchanges to add an extra layer of security.", - category: "Security", - }, - { - icon: "πŸš€", - color: "var(--neon-green)", - text: "Diversify your portfolio", - expanded: - "Don't put all your eggs in one basket. Spread your investments across different tokens and projects.", - category: "Investment", - }, - ]; + const { activeAccount } = useAeSdk(); + const { connectWallet } = useWalletConnect(); return ( -
- {/* Enhanced Quick Stats Dashboard */} -
-
- - πŸ“Š - -

- Live Dashboard -

-
-
- -
-
- Blockchain Status - - {isOnline ? "🟒 Connected" : "πŸ”΄ Offline"} - -
- - {/* Enhanced Current Time Display */} -
- {/* Animated background effect */} -
- -
- {/* Time Emoji and Label */} -
- - Current Time - - - {formatTime(currentTime).timeEmoji} - -
- - {/* Main Time Display */} -
-
- {formatTime(currentTime).timeString} -
-
- - {/* Date Display */} -
-
- {formatTime(currentTime).dateString} -
-
- - {/* Block Height (if available) */} - {currentBlockHeight !== null && ( -
- - - Block #{currentBlockHeight.toLocaleString()} - -
- )} -
-
- - {marketStats && ( -
-
-
- Market Cap -
-
- {formatMarketCap(marketStats.total_market_cap_sum || 0)} -
-
-
-
- Total Tokens -
-
- {marketStats.total_tokens || 0} -
-
+
+ + {/* Connect Wallet Card - Only show if not connected */} + {!activeAccount && ( +
+
+ +
+
+
- )} - - {/* Network Status moved to footer */} -
-
- - {/* Enhanced Quick Actions - moved to RightRail */} - - {/* Live Trending Topics */} -
-
- πŸ”₯ -

- Live Trending -

- -
-
- -
- {trendingTags.slice(0, 6).map((tag, index) => ( -
handleTrendingTopic(tag.tag)} - onMouseEnter={(e) => { - e.currentTarget.style.background = "rgba(255,255,255,0.12)"; - e.currentTarget.style.color = "white"; - e.currentTarget.style.transform = - "translateY(-2px) scale(1.02)"; - }} - onMouseLeave={(e) => { - e.currentTarget.style.background = `rgba(255,255,255,${ - 0.03 + index * 0.02 - })`; - e.currentTarget.style.color = "#b8c5d6"; - e.currentTarget.style.transform = "translateY(0) scale(1)"; - }} - title={`Search for ${tag.tag} (Score: ${tag.score})`} + +

+ Connect Wallet +

+ +

+ Access your assets and start trading on Superhero. +

+ + - - #{index + 1} - - {tag.tag} -
- ))} + Connect Now + +
-
- - {/* Top Tokens - moved to RightRail */} - - {/* Live Activity Feed - moved to RightRail */} - - {/* Price Alerts - moved to RightRail */} - - {/* Enhanced Pro Tips */} -
-
setShowTips(!showTips)} - title="Click to expand tips" - > - πŸ’‘ -

- Pro Tips -

- +
+
+ ⇄ +

Swap

+
+ - β–Ό - + + + + + +
- -
- {enhancedTips.map((tip, index) => ( -
-
{ - // Show expanded tip in a toast or modal - alert(`${tip.icon} ${tip.category}: ${tip.expanded}`); - }} - onMouseEnter={(e) => { - e.currentTarget.style.background = "rgba(255,255,255,0.05)"; - }} - onMouseLeave={(e) => { - e.currentTarget.style.background = "transparent"; - }} - title={`${tip.category}: Click for more details`} - > - - {tip.icon} - -
- - {tip.text} - -
- {tip.category} -
-
-
- {showTips && ( -
- {tip.expanded} -
- )} -
- ))} +
+
- - +
+ +
); diff --git a/src/components/layout/Shell.tsx b/src/components/layout/Shell.tsx index b6e1c4628..6e0eb5b30 100644 --- a/src/components/layout/Shell.tsx +++ b/src/components/layout/Shell.tsx @@ -64,7 +64,7 @@ export default function Shell({ left, right, children, containerClassName }: She >
- {/* Desktop: keep existing pill style */} + {/* Desktop: use dropdown like mobile */}
-
- onSortChange("hot")} - variant={sortBy === "hot" ? "default" : "ghost"} - size="xs" - noShadow={true} - className={cn( - "rounded-full px-3 py-1 text-xs font-semibold transition-all flex-1 w-full md:w-24 md:uppercase", - sortBy === "hot" - ? "bg-[#1161FE] text-white hover:bg-[#1161FE] focus:bg-[#1161FE]" - : "text-white/70 hover:text-white hover:bg-white/10 focus:text-white focus:bg-white/10" - )} - > - Popular - - onSortChange("latest")} - variant={sortBy === "latest" ? "default" : "ghost"} - size="xs" - noShadow={true} - className={cn( - "rounded-full px-3 py-1 text-xs font-semibold transition-all flex-1 w-full md:w-24 md:uppercase", - sortBy === "latest" - ? "bg-[#1161FE] text-white hover:bg-[#1161FE] focus:bg-[#1161FE]" - : "text-white/70 hover:text-white hover:bg-white/10 focus:text-white focus:bg-white/10" - )} + + + + + - Latest - -
- {sortBy === 'hot' && ( -
- {(['24h','7d','all'] as const).map((tf) => { - const isActive = popularWindow === tf; - const label = tf === '24h' ? 'Today' : tf === '7d' ? 'This week' : 'All time'; - return ( - - ); - })} -
- )} + Popular today + + handleMobileOptionSelect("this-week")} + className="cursor-pointer focus:bg-white/10 focus:text-white px-4 py-2.5 text-sm" + > + Popular this week + + handleMobileOptionSelect("all-time")} + className="cursor-pointer focus:bg-white/10 focus:text-white px-4 py-2.5 text-sm" + > + Popular all time + + + )} + +
); diff --git a/src/features/social/views/FeedList.tsx b/src/features/social/views/FeedList.tsx index fd6368d40..5da14c5e9 100644 --- a/src/features/social/views/FeedList.tsx +++ b/src/features/social/views/FeedList.tsx @@ -28,7 +28,8 @@ function useUrlQuery() { export default function FeedList({ standalone = true, -}: { standalone?: boolean } = {}) { + compact = false, +}: { standalone?: boolean; compact?: boolean } = {}) { const navigate = useNavigate(); const location = useLocation(); const urlQuery = useUrlQuery(); @@ -996,6 +997,7 @@ export default function FeedList({ commentCount={item.total_comments ?? 0} allowInlineRepliesToggle={false} onOpenPost={handleItemClick} + compact={compact} /> ); i += 1; @@ -1325,6 +1327,7 @@ export default function FeedList({ commentCount={item.total_comments ?? 0} allowInlineRepliesToggle={false} onOpenPost={handleItemClick} + compact={compact} /> ))} diff --git a/src/styles/layout-variants.scss b/src/styles/layout-variants.scss index fb0bd70fa..85c77e6b6 100644 --- a/src/styles/layout-variants.scss +++ b/src/styles/layout-variants.scss @@ -24,20 +24,20 @@ body.layout-variant-hybrid { margin-bottom: 0 !important; } - // Rail borders + // Rail borders - removed vertical dividers .shell-container { .grid { gap: 0.75rem; aside { - // Left rail + // Left rail - no border &:first-child { - border-right: 1px solid rgba(255, 255, 255, 0.08); + border-right: none; } - // Right rail + // Right rail - no border &:last-child { - border-left: 1px solid rgba(255, 255, 255, 0.08); + border-left: none; } } } From dc9281ac5948ea669068786b2963b937b6f7c8d2 Mon Sep 17 00:00:00 2001 From: paolomolo Date: Tue, 9 Dec 2025 18:52:19 +0100 Subject: [PATCH 14/58] refactor: scale down dashboard trend token list and improve layout - Reduced header size and spacing - Scaled down table padding, fonts, and elements - Changed rank badges to use numbers with dark text for top 3 - Removed trending score column - Right-aligned market cap and price columns - Reduced gap between rank and token name columns - Removed hover shift-up effect from table rows --- src/views/DashboardTrendingTokens.tsx | 113 +++++++++++--------------- 1 file changed, 47 insertions(+), 66 deletions(-) diff --git a/src/views/DashboardTrendingTokens.tsx b/src/views/DashboardTrendingTokens.tsx index 4ea4befc3..fa2bf6b22 100644 --- a/src/views/DashboardTrendingTokens.tsx +++ b/src/views/DashboardTrendingTokens.tsx @@ -93,23 +93,21 @@ export default function DashboardTrendingTokens() { }; const getRankIcon = (rank: number) => { - if (rank === 1) return 'πŸ₯‡'; - if (rank === 2) return 'πŸ₯ˆ'; - if (rank === 3) return 'πŸ₯‰'; + // Return null to use numbers instead of emojis return null; }; return (
{/* Header */} -
-
-
- +
+
+
+
-

Trending Tokens

-

Ranked by trending score

+

Trending Tokens

+

Ranked by trending score

@@ -120,64 +118,51 @@ export default function DashboardTrendingTokens() { - - - - - - + + + + + {tokens.map((token: TokenDto, index: number) => { const rank = index + 1; const tokenName = token.name || token.symbol || 'Unnamed'; - const scoreNum = getTrendingScore(token); - const maxScore = tokens.length > 0 ? getTrendingScore(tokens[0]) : 1; return ( navigate(`/trends/tokens/${encodeURIComponent(tokenName)}`)} > {/* Rank */} - {/* Token Name */} - - {/* Trending Score */} - - {/* Market Cap */} -
RankTokenTrending ScoreMarket CapPriceChartRankTokenMarket CapPriceChart
-
- {getRankIcon(rank) || rank} +
+
+ {rank}
-
- - # +
+
+ + # {token.symbol || token.name} {rank <= 3 && ( - {rank === 1 && } - {rank === 2 && } - {rank === 3 && } + {rank === 1 && } + {rank === 2 && } + {rank === 3 && } )}
-
-
- - {Math.round(scoreNum).toLocaleString()} -
-
-
-
+
+
{/* Price */} -
-
+
+
{/* Chart */} -
+ {token.sale_address && ( -
+
@@ -218,12 +203,12 @@ export default function DashboardTrendingTokens() { {/* Load More Button */} {hasNextPage && ( -
+
+
); From f5123a9aa4eddcab8fc95c7277af49b6ccf2e73d Mon Sep 17 00:00:00 2001 From: paolomolo Date: Mon, 15 Dec 2025 22:13:07 +0100 Subject: [PATCH 30/58] feat(mini-apps): implement plugin system for community extensibility - Create plugin registry system for mini-apps - Add TypeScript types and interfaces for plugins - Migrate all built-in mini-apps to plugin system - Implement dynamic route generation from registry - Update MiniAppsLanding to use registry - Create documentation page at /docs/mini-apps - Add community plugin registration API - Fix duplicate AddTokens import - Update legacy routes to use redirects - Enable community developers to register apps without forking --- docs/MINI_APPS.md | 231 ++++++++++++++++++ src/features/dex/views/MiniAppsLanding.tsx | 178 -------------- src/features/mini-apps/README.md | 90 +++++++ src/features/mini-apps/built-in.ts | 151 ++++++++++++ src/features/mini-apps/community.ts | 48 ++++ src/features/mini-apps/index.ts | 34 +++ src/features/mini-apps/plugins.ts | 43 ++++ src/features/mini-apps/registry.ts | 180 ++++++++++++++ src/features/mini-apps/types.ts | 72 ++++++ src/features/mini-apps/views/MiniAppsDocs.tsx | 179 ++++++++++++++ .../mini-apps/views/MiniAppsLanding.tsx | 153 ++++++++++++ src/routes.tsx | 65 ++--- 12 files changed, 1195 insertions(+), 229 deletions(-) create mode 100644 docs/MINI_APPS.md delete mode 100644 src/features/dex/views/MiniAppsLanding.tsx create mode 100644 src/features/mini-apps/README.md create mode 100644 src/features/mini-apps/built-in.ts create mode 100644 src/features/mini-apps/community.ts create mode 100644 src/features/mini-apps/index.ts create mode 100644 src/features/mini-apps/plugins.ts create mode 100644 src/features/mini-apps/registry.ts create mode 100644 src/features/mini-apps/types.ts create mode 100644 src/features/mini-apps/views/MiniAppsDocs.tsx create mode 100644 src/features/mini-apps/views/MiniAppsLanding.tsx diff --git a/docs/MINI_APPS.md b/docs/MINI_APPS.md new file mode 100644 index 000000000..b2865d878 --- /dev/null +++ b/docs/MINI_APPS.md @@ -0,0 +1,231 @@ +# Mini-Apps Plugin System + +The Superhero platform includes a powerful plugin system that allows community developers to easily create and register their own mini-apps without forking the main repository. + +## Overview + +Mini-apps are self-contained applications that integrate seamlessly into the Superhero platform. They appear in the `/apps` directory and are automatically listed on the Mini-Apps landing page. + +## Quick Start + +### 1. Create Your Mini-App Component + +Create a React component for your mini-app: + +```tsx +// src/features/my-app/MyApp.tsx +import React from 'react'; + +export default function MyApp() { + return ( +
+ {/* Your app content */} +

My Custom Mini-App

+
+ ); +} +``` + +### 2. Register Your Mini-App + +Create a plugin file and register your app: + +```tsx +// src/features/my-app/plugin.ts +import { lazy } from 'react'; +import { registerMiniApp } from '@/features/mini-apps'; + +registerMiniApp({ + metadata: { + id: 'my-app', + name: 'My App', + description: 'A cool mini-app built by the community', + icon: 'πŸš€', + path: '/apps/my-app', + category: 'utility', + gradient: 'from-purple-500 to-pink-500', + author: 'Your Name', + authorUrl: 'https://github.com/yourusername', + version: '1.0.0', + tags: ['utility', 'community'], + }, + route: { + path: '/apps/my-app', + component: lazy(() => import('./MyApp')), + }, +}); +``` + +### 3. Import Your Plugin + +Add your plugin import to the plugins file: + +```tsx +// src/features/mini-apps/plugins.ts +import { registerBuiltInMiniApps } from './built-in'; +import './my-app/plugin'; // Add this line +``` + +## Plugin Structure + +### Metadata + +The `metadata` object defines how your mini-app appears in the UI: + +- **id**: Unique identifier (required) +- **name**: Display name (required) +- **description**: Short description shown on the landing page (required) +- **icon**: Emoji string or React component (required) +- **path**: Route path (required, should start with `/apps/`) +- **category**: One of `'trading' | 'bridge' | 'explore' | 'community' | 'utility'` (required) +- **gradient**: Tailwind gradient classes for icon background (required) +- **author**: Your name (optional) +- **authorUrl**: Your GitHub/profile URL (optional) +- **version**: Version string (optional) +- **tags**: Array of tags for filtering (optional) +- **requiresAuth**: Whether the app requires authentication (optional) + +### Route Configuration + +The `route` object defines how your app is routed: + +- **path**: Route path pattern (required) +- **component**: Lazy-loaded React component (required) +- **layout**: Custom layout wrapper (optional, defaults to `SocialLayout`) +- **options**: Additional route options (optional) + +## Examples + +### Simple Utility App + +```tsx +import { lazy } from 'react'; +import { registerMiniApp } from '@/features/mini-apps'; + +registerMiniApp({ + metadata: { + id: 'calculator', + name: 'Calculator', + description: 'A simple calculator tool', + icon: 'πŸ”’', + path: '/apps/calculator', + category: 'utility', + gradient: 'from-blue-500 to-cyan-500', + author: 'John Doe', + }, + route: { + path: '/apps/calculator', + component: lazy(() => import('./Calculator')), + }, +}); +``` + +### App with Custom Layout + +```tsx +import { lazy } from 'react'; +import { registerMiniApp } from '@/features/mini-apps'; +import CustomLayout from './CustomLayout'; + +registerMiniApp({ + metadata: { + id: 'custom-app', + name: 'Custom App', + description: 'An app with custom layout', + icon: '⭐', + path: '/apps/custom', + category: 'community', + gradient: 'from-purple-500 to-pink-500', + }, + route: { + path: '/apps/custom', + component: lazy(() => import('./CustomApp')), + layout: CustomLayout, + }, +}); +``` + +### App with Initialization + +```tsx +import { lazy } from 'react'; +import { registerMiniApp } from '@/features/mini-apps'; + +registerMiniApp({ + metadata: { + id: 'analytics', + name: 'Analytics', + description: 'Token analytics dashboard', + icon: 'πŸ“Š', + path: '/apps/analytics', + category: 'explore', + gradient: 'from-green-500 to-teal-500', + }, + route: { + path: '/apps/analytics', + component: lazy(() => import('./Analytics')), + }, + initialize: () => { + // Initialize analytics tracking, etc. + console.log('Analytics app initialized'); + }, + cleanup: () => { + // Cleanup when app is unregistered + console.log('Analytics app cleaned up'); + }, +}); +``` + +## Best Practices + +1. **Use lazy loading**: Always use `lazy()` for your component imports to enable code splitting +2. **Follow naming conventions**: Use kebab-case for IDs and paths +3. **Provide good descriptions**: Help users understand what your app does +4. **Use appropriate categories**: Choose the category that best fits your app +5. **Add tags**: Tags help users discover your app +6. **Test your routes**: Make sure your route paths don't conflict with existing routes +7. **Handle errors gracefully**: Use error boundaries and proper error handling + +## Integration Points + +### Using Platform Features + +Your mini-app can access platform features through hooks and context: + +```tsx +import { useAeSdk } from '@/hooks'; +import { useToast } from '@/components/ToastProvider'; + +export default function MyApp() { + const { activeAccount, sdk } = useAeSdk(); + const toast = useToast(); + + // Use platform features +} +``` + +### Styling + +Use Tailwind CSS classes and follow the platform's design system: + +```tsx +
+ {/* Your content */} +
+``` + +## Community Guidelines + +1. **Be respectful**: Don't create apps that harm users or violate terms of service +2. **Open source**: Consider open-sourcing your mini-app for the community +3. **Documentation**: Provide clear documentation for your app +4. **Testing**: Test your app thoroughly before registering +5. **Updates**: Keep your app updated and maintain compatibility + +## Questions? + +Join our developer community: +- GitHub: https://github.com/superhero-com/superhero +- Discord: [Link to Discord] +- Documentation: [Link to docs] + diff --git a/src/features/dex/views/MiniAppsLanding.tsx b/src/features/dex/views/MiniAppsLanding.tsx deleted file mode 100644 index e1b6006ae..000000000 --- a/src/features/dex/views/MiniAppsLanding.tsx +++ /dev/null @@ -1,178 +0,0 @@ -import React from 'react'; -import { useNavigate } from 'react-router-dom'; -import { GlassSurface } from '@/components/ui/GlassSurface'; - -interface MiniApp { - id: string; - name: string; - description: string; - icon: string; - path: string; - category: 'trading' | 'bridge' | 'explore'; - gradient: string; -} - -const miniApps: MiniApp[] = [ - { - id: 'swap', - name: 'Swap', - description: 'Trade any supported AEX-9 tokens instantly', - icon: 'πŸ”„', - path: '/apps/swap', - category: 'trading', - gradient: 'from-blue-500 to-cyan-500', - }, - { - id: 'pool', - name: 'Pool', - description: 'Manage liquidity positions and earn fees', - icon: 'πŸ’§', - path: '/apps/pool', - category: 'trading', - gradient: 'from-emerald-500 to-teal-500', - }, - { - id: 'wrap', - name: 'Wrap', - description: 'Convert AE ↔ WAE seamlessly', - icon: 'πŸ“¦', - path: '/apps/wrap', - category: 'trading', - gradient: 'from-purple-500 to-pink-500', - }, - { - id: 'bridge', - name: 'Bridge', - description: 'Bridge tokens between Ethereum and Γ¦ternity', - icon: 'πŸŒ‰', - path: '/apps/bridge', - category: 'bridge', - gradient: 'from-orange-500 to-red-500', - }, - { - id: 'buy-ae', - name: 'Buy AE', - description: 'Buy AE tokens with ETH', - icon: 'πŸ’Ž', - path: '/apps/buy-ae-with-eth', - category: 'bridge', - gradient: 'from-yellow-500 to-amber-500', - }, - { - id: 'explorer', - name: 'Explorer', - description: 'Browse tokens, pools, and transactions', - icon: 'πŸ”', - path: '/apps/explore/tokens', - category: 'explore', - gradient: 'from-indigo-500 to-blue-500', - }, -]; - -const categoryLabels = { - trading: 'Trading', - bridge: 'Bridge', - explore: 'Explore', -}; - -export default function MiniAppsLanding() { - const navigate = useNavigate(); - - const groupedApps = miniApps.reduce((acc, app) => { - if (!acc[app.category]) { - acc[app.category] = []; - } - acc[app.category].push(app); - return acc; - }, {} as Record); - - return ( -
-
- {/* Header */} -
-

- Mini-Apps -

-

- Discover powerful DeFi tools and utilities built on Γ¦ternity. Each mini-app is designed to be fast, secure, and easy to use. -

-
- - {/* Mini-Apps Grid */} -
- {Object.entries(groupedApps).map(([category, apps]) => ( -
-

- {category === 'trading' ? 'πŸ’Ή' : category === 'bridge' ? 'πŸŒ‰' : 'πŸ”'} - {categoryLabels[category as keyof typeof categoryLabels]} -

-
- {apps.map((app) => ( - navigate(app.path)} - interactive - > -
- {/* Icon */} -
- {app.icon} -
- - {/* Content */} -

- {app.name} -

-

- {app.description} -

- - {/* Arrow */} -
- Open app - - - -
-
-
- ))} -
-
- ))} -
- - {/* Footer CTA */} -
- -

- Build Your Own Mini-App -

-

- Have an idea for a mini-app? Join our developer community and build on Γ¦ternity. -

- - - - - View on GitHub - -
-
-
-
- ); -} - diff --git a/src/features/mini-apps/README.md b/src/features/mini-apps/README.md new file mode 100644 index 000000000..3245bf679 --- /dev/null +++ b/src/features/mini-apps/README.md @@ -0,0 +1,90 @@ +# Mini-Apps Plugin System + +A plugin-based architecture for extending the Superhero platform with community-built mini-apps. + +## Architecture + +``` +src/features/mini-apps/ +β”œβ”€β”€ types.ts # TypeScript types and interfaces +β”œβ”€β”€ registry.ts # Core registry system +β”œβ”€β”€ built-in.ts # Built-in mini-app registrations +β”œβ”€β”€ plugins.ts # Plugin initialization +β”œβ”€β”€ community.ts # Community plugin registry +β”œβ”€β”€ views/ +β”‚ └── MiniAppsLanding.tsx # Landing page component +└── README.md # This file +``` + +## How It Works + +1. **Registry**: The `MiniAppRegistry` class manages all registered mini-apps +2. **Registration**: Apps register themselves using `registerMiniApp()` +3. **Initialization**: All plugins are initialized when the app starts +4. **Routes**: Routes are dynamically generated from registered plugins +5. **UI**: The landing page automatically displays all registered apps + +## Usage + +### For Core Developers + +Built-in apps are registered in `built-in.ts`. To add a new built-in app: + +```tsx +import { registerMiniApp } from './registry'; +import { lazy } from 'react'; + +registerMiniApp({ + metadata: { /* ... */ }, + route: { /* ... */ }, +}); +``` + +### For Community Developers + +See `/docs/MINI_APPS.md` for complete documentation on creating and registering community mini-apps. + +## Key Features + +- βœ… **Plugin-based**: Easy to extend without modifying core code +- βœ… **Type-safe**: Full TypeScript support +- βœ… **Lazy loading**: Automatic code splitting +- βœ… **Dynamic routes**: Routes generated from registry +- βœ… **Category grouping**: Apps organized by category +- βœ… **Metadata rich**: Support for tags, authors, versions, etc. +- βœ… **Initialization hooks**: Apps can initialize/cleanup resources + +## API Reference + +### `registerMiniApp(plugin: MiniAppPlugin)` + +Register a new mini-app plugin. + +### `unregisterMiniApp(id: string)` + +Unregister a mini-app by ID. + +### `miniAppRegistry.getAll()` + +Get all registered plugins. + +### `miniAppRegistry.getByCategory(category)` + +Get plugins filtered by category. + +### `getMiniAppRoutes()` + +Get all routes for the router (used in `routes.tsx`). + +## Categories + +- `trading`: Trading and DEX-related apps +- `bridge`: Cross-chain bridging apps +- `explore`: Exploration and discovery tools +- `community`: Community-built apps +- `utility`: Utility tools and helpers + +## Examples + +See `/docs/MINI_APPS.md` for complete examples. + diff --git a/src/features/mini-apps/built-in.ts b/src/features/mini-apps/built-in.ts new file mode 100644 index 000000000..22d7ed8c9 --- /dev/null +++ b/src/features/mini-apps/built-in.ts @@ -0,0 +1,151 @@ +import { lazy } from 'react'; +import { registerMiniApp } from './registry'; +import type { MiniAppPlugin } from './types'; + +// Import built-in mini-app components +const DexSwap = lazy(() => import('../dex/views/DexSwap')); +const DexWrap = lazy(() => import('../dex/views/DexWrap')); +const DexBridge = lazy(() => import('../dex/views/DexBridge')); +const Bridge = lazy(() => import('../ae-eth-bridge/views/Bridge')); +const Pool = lazy(() => import('../dex/views/Pool')); +const AddTokens = lazy(() => import('../../views/AddTokens')); +const DexExploreTokens = lazy(() => import('../dex/views/DexExploreTokens')); + +/** + * Register all built-in mini-apps + */ +export function registerBuiltInMiniApps(): void { + // Swap + registerMiniApp({ + metadata: { + id: 'swap', + name: 'Swap', + description: 'Trade any supported AEX-9 tokens instantly', + icon: 'πŸ”„', + path: '/apps/swap', + category: 'trading', + gradient: 'from-blue-500 to-cyan-500', + builtIn: true, + tags: ['trading', 'swap', 'dex'], + }, + route: { + path: '/apps/swap', + component: DexSwap, + }, + }); + + // Pool + registerMiniApp({ + metadata: { + id: 'pool', + name: 'Pool', + description: 'Manage liquidity positions and earn fees', + icon: 'πŸ’§', + path: '/apps/pool', + category: 'trading', + gradient: 'from-emerald-500 to-teal-500', + builtIn: true, + tags: ['trading', 'liquidity', 'pool', 'lp'], + }, + route: { + path: '/apps/pool', + component: Pool, + }, + }); + + // Wrap + registerMiniApp({ + metadata: { + id: 'wrap', + name: 'Wrap', + description: 'Convert AE ↔ WAE seamlessly', + icon: 'πŸ“¦', + path: '/apps/wrap', + category: 'trading', + gradient: 'from-purple-500 to-pink-500', + builtIn: true, + tags: ['trading', 'wrap', 'wae'], + }, + route: { + path: '/apps/wrap', + component: DexWrap, + }, + }); + + // Bridge (AE-ETH) + registerMiniApp({ + metadata: { + id: 'bridge', + name: 'Bridge', + description: 'Bridge tokens between Ethereum and Γ¦ternity', + icon: 'πŸŒ‰', + path: '/apps/bridge', + category: 'bridge', + gradient: 'from-indigo-500 to-blue-500', + builtIn: true, + tags: ['bridge', 'ethereum', 'cross-chain'], + }, + route: { + path: '/apps/bridge', + component: Bridge, + }, + }); + + // Buy AE + registerMiniApp({ + metadata: { + id: 'buy-ae', + name: 'Buy AE', + description: 'Buy AE tokens with ETH', + icon: 'πŸ’Ž', + path: '/apps/buy-ae-with-eth', + category: 'bridge', + gradient: 'from-rose-500 to-orange-500', + builtIn: true, + tags: ['bridge', 'buy', 'ethereum'], + }, + route: { + path: '/apps/buy-ae-with-eth', + component: DexBridge, + }, + }); + + // Explorer + registerMiniApp({ + metadata: { + id: 'explorer', + name: 'Explorer', + description: 'Browse tokens, pools, and transactions', + icon: 'πŸ”', + path: '/apps/explore/tokens', + category: 'explore', + gradient: 'from-indigo-500 to-blue-500', + builtIn: true, + tags: ['explore', 'tokens', 'pools', 'transactions'], + }, + route: { + path: '/apps/explore/tokens', + component: DexExploreTokens, + }, + }); + + // Add Tokens + registerMiniApp({ + metadata: { + id: 'add-tokens', + name: 'Add Tokens', + description: 'Discover tokens from your wallet and add them to the DEX', + icon: 'βž•', + path: '/apps/pool/add-tokens', + category: 'trading', + gradient: 'from-violet-500 to-purple-500', + builtIn: true, + tags: ['trading', 'tokens', 'wallet'], + }, + route: { + path: '/apps/pool/add-tokens', + component: AddTokens, + }, + }); +} + diff --git a/src/features/mini-apps/community.ts b/src/features/mini-apps/community.ts new file mode 100644 index 000000000..65d96471f --- /dev/null +++ b/src/features/mini-apps/community.ts @@ -0,0 +1,48 @@ +/** + * Community Mini-Apps Registry + * + * This file is where community developers can register their own mini-apps. + * Simply import this file in your plugin code and use registerMiniApp(). + * + * Alternatively, you can create your own plugin file and import it in plugins.ts + * + * @example + * ```tsx + * import { registerMiniApp } from '@/features/mini-apps'; + * import { lazy } from 'react'; + * + * registerMiniApp({ + * metadata: { + * id: 'my-community-app', + * name: 'My Community App', + * description: 'A cool community-built mini-app', + * icon: 'πŸš€', + * path: '/apps/my-community-app', + * category: 'community', + * gradient: 'from-purple-500 to-pink-500', + * author: 'Your Name', + * authorUrl: 'https://github.com/yourusername', + * version: '1.0.0', + * tags: ['community', 'utility'], + * }, + * route: { + * path: '/apps/my-community-app', + * component: lazy(() => import('./MyCommunityApp')), + * }, + * }); + * ``` + */ + +// Import community plugins here +// Example: +// import './community-plugins/my-app'; + +// Or register directly: +// import { registerMiniApp } from './registry'; +// import { lazy } from 'react'; +// +// registerMiniApp({ +// metadata: { ... }, +// route: { ... }, +// }); + diff --git a/src/features/mini-apps/index.ts b/src/features/mini-apps/index.ts new file mode 100644 index 000000000..397dc68c3 --- /dev/null +++ b/src/features/mini-apps/index.ts @@ -0,0 +1,34 @@ +/** + * Mini-Apps Plugin System + * + * This module provides a plugin system for mini-apps, allowing community + * developers to easily register and extend the platform with their own apps. + * + * @example + * ```tsx + * import { registerMiniApp } from '@/features/mini-apps'; + * import MyApp from './MyApp'; + * + * registerMiniApp({ + * metadata: { + * id: 'my-app', + * name: 'My App', + * description: 'A cool mini-app', + * icon: 'πŸš€', + * path: '/apps/my-app', + * category: 'utility', + * gradient: 'from-purple-500 to-pink-500', + * }, + * route: { + * path: '/apps/my-app', + * component: lazy(() => import('./MyApp')), + * }, + * }); + * ``` + */ + +export * from './types'; +export * from './registry'; +export { registerBuiltInMiniApps } from './built-in'; +export { default as MiniAppsLanding } from './views/MiniAppsLanding'; + diff --git a/src/features/mini-apps/plugins.ts b/src/features/mini-apps/plugins.ts new file mode 100644 index 000000000..27bb1cef7 --- /dev/null +++ b/src/features/mini-apps/plugins.ts @@ -0,0 +1,43 @@ +/** + * Mini-App Plugins Loader + * + * This file initializes the mini-app plugin system and loads all plugins. + * Community developers can create their own plugin files and import them here, + * or use the registerMiniApp function directly in their code. + */ + +import { registerBuiltInMiniApps } from './built-in'; +import { miniAppRegistry } from './registry'; + +// Import community plugins (if any) +// Community developers can create their own plugin files and import them here +// Example: +// import './community/my-custom-app'; + +/** + * Initialize all mini-app plugins + * + * This function should be called during app initialization to register + * all built-in mini-apps and any community plugins. + */ +export function initializeMiniApps(): void { + // Register built-in mini-apps first + registerBuiltInMiniApps(); + + // Initialize the registry (calls initialize() on all plugins) + miniAppRegistry.initialize(); + + // Log registered apps for debugging + if (process.env.NODE_ENV === 'development') { + console.log(`[Mini-Apps] Registered ${miniAppRegistry.getAll().length} mini-apps:`, + miniAppRegistry.getAllMetadata().map(app => app.id).join(', ')); + } +} + +/** + * Get all mini-app routes for the router + */ +export function getMiniAppRoutes() { + return miniAppRegistry.getAllRoutes(); +} + diff --git a/src/features/mini-apps/registry.ts b/src/features/mini-apps/registry.ts new file mode 100644 index 000000000..b1b66b212 --- /dev/null +++ b/src/features/mini-apps/registry.ts @@ -0,0 +1,180 @@ +import React from 'react'; +import SocialLayout from '@/components/layout/SocialLayout'; +import type { MiniAppPlugin, MiniAppMetadata, MiniAppCategory } from './types'; + +/** + * Mini-App Registry + * + * This registry stores all registered mini-apps. Community developers can + * register their own mini-apps by importing and using the registerMiniApp function. + */ +class MiniAppRegistry { + private plugins: Map = new Map(); + private initialized = false; + + /** + * Register a new mini-app plugin + */ + register(plugin: MiniAppPlugin): void { + if (this.plugins.has(plugin.metadata.id)) { + console.warn(`Mini-app with id "${plugin.metadata.id}" is already registered. Overwriting...`); + } + + // Validate plugin + this.validatePlugin(plugin); + + this.plugins.set(plugin.metadata.id, plugin); + + // Initialize if provided + if (plugin.initialize && this.initialized) { + try { + plugin.initialize(); + } catch (error) { + console.error(`Failed to initialize mini-app "${plugin.metadata.id}":`, error); + } + } + } + + /** + * Unregister a mini-app + */ + unregister(id: string): void { + const plugin = this.plugins.get(id); + if (plugin?.cleanup) { + try { + plugin.cleanup(); + } catch (error) { + console.error(`Failed to cleanup mini-app "${id}":`, error); + } + } + this.plugins.delete(id); + } + + /** + * Get a plugin by ID + */ + get(id: string): MiniAppPlugin | undefined { + return this.plugins.get(id); + } + + /** + * Get all registered plugins + */ + getAll(): MiniAppPlugin[] { + return Array.from(this.plugins.values()); + } + + /** + * Get plugins by category + */ + getByCategory(category: MiniAppCategory): MiniAppPlugin[] { + return this.getAll().filter(plugin => plugin.metadata.category === category); + } + + /** + * Get all metadata (for landing page, etc.) + */ + getAllMetadata(): MiniAppMetadata[] { + return this.getAll().map(plugin => plugin.metadata); + } + + /** + * Get all routes (for router configuration) + */ + getAllRoutes() { + return this.getAll().map(plugin => { + const Component = plugin.route.component; + const Layout = plugin.route.layout || SocialLayout; + + return { + path: plugin.route.path, + element: React.createElement(Layout, {}, React.createElement(Component)), + ...plugin.route.options, + }; + }); + } + + /** + * Initialize all plugins + */ + initialize(): void { + if (this.initialized) { + return; + } + + this.getAll().forEach(plugin => { + if (plugin.initialize) { + try { + plugin.initialize(); + } catch (error) { + console.error(`Failed to initialize mini-app "${plugin.metadata.id}":`, error); + } + } + }); + + this.initialized = true; + } + + /** + * Validate plugin structure + */ + private validatePlugin(plugin: MiniAppPlugin): void { + if (!plugin.metadata.id) { + throw new Error('Mini-app metadata must have an id'); + } + if (!plugin.metadata.name) { + throw new Error('Mini-app metadata must have a name'); + } + if (!plugin.metadata.path) { + throw new Error('Mini-app metadata must have a path'); + } + if (!plugin.route.component) { + throw new Error('Mini-app must have a route component'); + } + } +} + +// Singleton instance +export const miniAppRegistry = new MiniAppRegistry(); + +/** + * Register a mini-app plugin + * + * This is the main API for registering mini-apps. Community developers + * can use this function to register their own mini-apps. + * + * @example + * ```tsx + * import { registerMiniApp } from '@/features/mini-apps'; + * import MyApp from './MyApp'; + * + * registerMiniApp({ + * metadata: { + * id: 'my-app', + * name: 'My App', + * description: 'A cool mini-app', + * icon: 'πŸš€', + * path: '/apps/my-app', + * category: 'utility', + * gradient: 'from-purple-500 to-pink-500', + * author: 'John Doe', + * authorUrl: 'https://github.com/johndoe', + * }, + * route: { + * path: '/apps/my-app', + * component: lazy(() => import('./MyApp')), + * }, + * }); + * ``` + */ +export function registerMiniApp(plugin: MiniAppPlugin): void { + miniAppRegistry.register(plugin); +} + +/** + * Unregister a mini-app + */ +export function unregisterMiniApp(id: string): void { + miniAppRegistry.unregister(id); +} + diff --git a/src/features/mini-apps/types.ts b/src/features/mini-apps/types.ts new file mode 100644 index 000000000..5266f13f7 --- /dev/null +++ b/src/features/mini-apps/types.ts @@ -0,0 +1,72 @@ +import { ComponentType, LazyExoticComponent } from 'react'; + +/** + * Mini-App Category + */ +export type MiniAppCategory = 'trading' | 'bridge' | 'explore' | 'community' | 'utility'; + +/** + * Mini-App Metadata + */ +export interface MiniAppMetadata { + /** Unique identifier for the mini-app */ + id: string; + /** Display name */ + name: string; + /** Short description */ + description: string; + /** Icon (emoji or icon component) */ + icon: string | ComponentType<{ className?: string }>; + /** Route path (e.g., '/apps/my-app') */ + path: string; + /** Category for grouping */ + category: MiniAppCategory; + /** Gradient colors for icon background */ + gradient: string; + /** Author/developer name */ + author?: string; + /** Author website or GitHub */ + authorUrl?: string; + /** Version */ + version?: string; + /** Whether this is a built-in app */ + builtIn?: boolean; + /** Tags for search/filtering */ + tags?: string[]; + /** Whether the app requires authentication */ + requiresAuth?: boolean; +} + +/** + * Mini-App Route Configuration + */ +export interface MiniAppRoute { + /** Route path pattern */ + path: string; + /** Lazy-loaded component */ + component: LazyExoticComponent>; + /** Layout wrapper (defaults to SocialLayout) */ + layout?: ComponentType<{ children: React.ReactNode }>; + /** Additional route options */ + options?: { + /** Whether to include in navigation */ + includeInNav?: boolean; + /** Route metadata */ + meta?: Record; + }; +} + +/** + * Complete Mini-App Plugin Definition + */ +export interface MiniAppPlugin { + /** App metadata */ + metadata: MiniAppMetadata; + /** Route configuration */ + route: MiniAppRoute; + /** Optional initialization function */ + initialize?: () => void | Promise; + /** Optional cleanup function */ + cleanup?: () => void; +} + diff --git a/src/features/mini-apps/views/MiniAppsDocs.tsx b/src/features/mini-apps/views/MiniAppsDocs.tsx new file mode 100644 index 000000000..fd533450d --- /dev/null +++ b/src/features/mini-apps/views/MiniAppsDocs.tsx @@ -0,0 +1,179 @@ +import React from 'react'; +import { useNavigate } from 'react-router-dom'; +import { GlassSurface } from '@/components/ui/GlassSurface'; +import { ArrowLeft } from 'lucide-react'; + +export default function MiniAppsDocs() { + const navigate = useNavigate(); + + return ( +
+ {/* Header */} +
+
+ +
+

Mini-Apps Documentation

+

Learn how to build and register your own mini-apps

+
+
+
+ + {/* Documentation Content */} + +
+

Overview

+

+ The Superhero platform includes a powerful plugin system that allows community developers to easily create and register their own mini-apps without forking the main repository. +

+

+ Mini-apps are self-contained applications that integrate seamlessly into the Superhero platform. They appear in the /apps directory and are automatically listed on the Mini-Apps landing page. +

+ +

Quick Start

+ +

1. Create Your Mini-App Component

+

Create a React component for your mini-app:

+
+            {`// src/features/my-app/MyApp.tsx
+import React from 'react';
+
+export default function MyApp() {
+  return (
+    
+ {/* Your app content */} +

My Custom Mini-App

+
+ ); +}`}
+
+ +

2. Register Your Mini-App

+

Create a plugin file and register your app:

+
+            {`// src/features/my-app/plugin.ts
+import { lazy } from 'react';
+import { registerMiniApp } from '@/features/mini-apps';
+
+registerMiniApp({
+  metadata: {
+    id: 'my-app',
+    name: 'My App',
+    description: 'A cool mini-app built by the community',
+    icon: 'πŸš€',
+    path: '/apps/my-app',
+    category: 'utility',
+    gradient: 'from-purple-500 to-pink-500',
+    author: 'Your Name',
+    authorUrl: 'https://github.com/yourusername',
+    version: '1.0.0',
+    tags: ['utility', 'community'],
+  },
+  route: {
+    path: '/apps/my-app',
+    component: lazy(() => import('./MyApp')),
+  },
+});`}
+          
+ +

3. Import Your Plugin

+

Add your plugin import to the plugins file:

+
+            {`// src/features/mini-apps/plugins.ts
+import { registerBuiltInMiniApps } from './built-in';
+import './my-app/plugin'; // Add this line`}
+          
+ +

Plugin Structure

+ +

Metadata

+

The metadata object defines how your mini-app appears in the UI:

+
    +
  • id: Unique identifier (required)
  • +
  • name: Display name (required)
  • +
  • description: Short description shown on the landing page (required)
  • +
  • icon: Emoji string or React component (required)
  • +
  • path: Route path (required, should start with /apps/)
  • +
  • category: One of 'trading' | 'bridge' | 'explore' | 'community' | 'utility' (required)
  • +
  • gradient: Tailwind gradient classes for icon background (required)
  • +
  • author: Your name (optional)
  • +
  • authorUrl: Your GitHub/profile URL (optional)
  • +
  • version: Version string (optional)
  • +
  • tags: Array of tags for filtering (optional)
  • +
  • requiresAuth: Whether the app requires authentication (optional)
  • +
+ +

Route Configuration

+

The route object defines how your app is routed:

+
    +
  • path: Route path pattern (required)
  • +
  • component: Lazy-loaded React component (required)
  • +
  • layout: Custom layout wrapper (optional, defaults to SocialLayout)
  • +
  • options: Additional route options (optional)
  • +
+ +

Best Practices

+
    +
  • Use lazy loading: Always use lazy() for your component imports to enable code splitting
  • +
  • Follow naming conventions: Use kebab-case for IDs and paths
  • +
  • Provide good descriptions: Help users understand what your app does
  • +
  • Use appropriate categories: Choose the category that best fits your app
  • +
  • Add tags: Tags help users discover your app
  • +
  • Test your routes: Make sure your route paths don't conflict with existing routes
  • +
  • Handle errors gracefully: Use error boundaries and proper error handling
  • +
+ +

Integration Points

+ +

Using Platform Features

+

Your mini-app can access platform features through hooks and context:

+
+            {`import { useAeSdk } from '@/hooks';
+import { useToast } from '@/components/ToastProvider';
+
+export default function MyApp() {
+  const { activeAccount, sdk } = useAeSdk();
+  const toast = useToast();
+  
+  // Use platform features
+}`}
+          
+ +

Community Guidelines

+
    +
  • Be respectful: Don't create apps that harm users or violate terms of service
  • +
  • Open source: Consider open-sourcing your mini-app for the community
  • +
  • Documentation: Provide clear documentation for your app
  • +
  • Testing: Test your app thoroughly before registering
  • +
  • Updates: Keep your app updated and maintain compatibility
  • +
+ +
+

Questions?

+

Join our developer community:

+ +
+
+
+
+ ); +} + diff --git a/src/features/mini-apps/views/MiniAppsLanding.tsx b/src/features/mini-apps/views/MiniAppsLanding.tsx new file mode 100644 index 000000000..252957ae9 --- /dev/null +++ b/src/features/mini-apps/views/MiniAppsLanding.tsx @@ -0,0 +1,153 @@ +import React from 'react'; +import { useNavigate } from 'react-router-dom'; +import { GlassSurface } from '@/components/ui/GlassSurface'; +import { miniAppRegistry } from '../registry'; +import type { MiniAppCategory } from '../types'; + +const categoryLabels: Record = { + trading: 'Trading', + bridge: 'Bridge', + explore: 'Explore', + community: 'Community', + utility: 'Utility', +}; + +const categoryIcons: Record = { + trading: 'πŸ’Ή', + bridge: 'πŸŒ‰', + explore: 'πŸ”', + community: 'πŸ‘₯', + utility: 'πŸ› οΈ', +}; + +export default function MiniAppsLanding() { + const navigate = useNavigate(); + + const allApps = miniAppRegistry.getAllMetadata(); + + const groupedApps = allApps.reduce((acc, app) => { + if (!acc[app.category]) { + acc[app.category] = []; + } + acc[app.category].push(app); + return acc; + }, {} as Record); + + return ( +
+
+ {/* Header */} +
+

+ Mini-Apps +

+

+ Discover powerful DeFi tools and utilities built on Γ¦ternity. Each mini-app is designed to be fast, secure, and easy to use. +

+
+ + {/* Mini-Apps Grid */} +
+ {Object.entries(groupedApps).map(([category, apps]) => ( +
+

+ {categoryIcons[category as MiniAppCategory]} + {categoryLabels[category as MiniAppCategory]} +

+
+ {apps.map((app) => ( + navigate(app.path)} + interactive + > +
+ {/* Icon */} +
+ {typeof app.icon === 'string' ? app.icon : } +
+ + {/* Content */} +
+

+ {app.name} +

+ {!app.builtIn && app.author && ( + by {app.author} + )} +
+

+ {app.description} +

+ + {/* Tags */} + {app.tags && app.tags.length > 0 && ( +
+ {app.tags.slice(0, 3).map((tag) => ( + + {tag} + + ))} +
+ )} + + {/* Arrow */} +
+ Open app + + + +
+
+
+ ))} +
+
+ ))} +
+ + {/* Footer CTA */} +
+ +

+ Build Your Own Mini-App +

+

+ Have an idea for a mini-app? Join our developer community and build on Γ¦ternity. + Register your mini-app using our plugin system - no fork required! +

+ +
+
+
+
+ ); +} + diff --git a/src/routes.tsx b/src/routes.tsx index cf3896c10..a2f360b65 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -1,6 +1,11 @@ import React, { lazy } from "react"; import { RouteObject, Navigate, useParams } from "react-router-dom"; import SocialLayout from "./components/layout/SocialLayout"; +import { initializeMiniApps, getMiniAppRoutes } from "./features/mini-apps/plugins"; + +// Initialize mini-apps plugin system +// This registers all built-in mini-apps and any community plugins +initializeMiniApps(); const FeedList = lazy(() => import("./features/social/views/FeedList")); const TokenList = lazy(() => import("./features/trending/views/TokenList")); @@ -38,11 +43,8 @@ const TxQueue = lazy(() => import("./views/TxQueue")); // DEX Components const DexLayout = lazy(() => import("./features/dex/layouts/DexLayout")); -const MiniAppsLanding = lazy(() => import("./features/dex/views/MiniAppsLanding")); -const DexSwap = lazy(() => import("./features/dex/views/DexSwap")); -const DexWrap = lazy(() => import("./features/dex/views/DexWrap")); -const DexBridge = lazy(() => import("./features/dex/views/DexBridge")); -const Pool = lazy(() => import("./features/dex/views/Pool")); +const MiniAppsLanding = lazy(() => import("./features/mini-apps/views/MiniAppsLanding")); +const MiniAppsDocs = lazy(() => import("./features/mini-apps/views/MiniAppsDocs")); const DexExploreTokens = lazy( () => import("./features/dex/views/DexExploreTokens") ); @@ -52,7 +54,6 @@ const DexExplorePools = lazy( const DexExploreTransactions = lazy( () => import("./features/dex/views/DexExploreTransactions") ); -const Bridge = lazy(() => import("./features/ae-eth-bridge/views/Bridge")); // Legacy DEX components (for backward compatibility) const Explore = lazy(() => import("./views/Explore")); @@ -156,7 +157,7 @@ export const routes: RouteObject[] = [ { path: "/voting/account", element: }, { path: "/voting/create", element: }, - // New DEX Routes with Layout + // Mini-Apps Routes (dynamically generated from registry) { path: "/apps", element: ( @@ -166,53 +167,15 @@ export const routes: RouteObject[] = [ ), }, { - path: "/apps/swap", - element: ( - - - - ), - }, - { - path: "/apps/wrap", - element: ( - - - - ), - }, - { - path: "/apps/buy-ae-with-eth", - element: ( - - - - ), - }, - { - path: "/apps/bridge", - element: ( - - - - ), - }, - { - path: "/apps/pool", - element: ( - - - - ), - }, - { - path: "/apps/pool/add-tokens", + path: "/docs/mini-apps", element: ( - + ), }, + // Dynamic routes from mini-app registry + ...getMiniAppRoutes(), { path: "/apps/explore/tokens", element: ( @@ -268,11 +231,11 @@ export const routes: RouteObject[] = [ { path: "/defi/explore/pools", element: }, { path: "/defi/explore/pools/:poolAddress", element: }, { path: "/defi/explore/transactions", element: }, - { path: "/pool", element: }, + { path: "/pool", element: }, { path: "/explore", element: }, { path: "/explore/tokens/:id", element: }, { path: "/explore/pools/:id", element: }, - { path: "/pool/add-tokens", element: }, + { path: "/pool/add-tokens", element: }, { path: "/terms", element: }, { path: "/privacy", element: }, From bb3d98a671b1c7788cee09ae66c8c22211009cc1 Mon Sep 17 00:00:00 2001 From: paolomolo Date: Mon, 15 Dec 2025 22:52:41 +0100 Subject: [PATCH 31/58] feat: improve mini app layouts with responsive design and remove fixed widths - Add 8 responsive layout options for Pool mini app with auto-fit grids - Remove fixed widths (480px) from all mini app forms and widgets - Remove nested card styling from forms (transparent containers) - Add bottom padding (pb-6) to all form components - Update Swap, Wrap, and Bridge mini apps to use responsive auto-fit grids - Implement mobile-first approach with proper wrapping behavior - Cards now wrap to multiple rows when space is constrained - All layouts adapt to available space accounting for left rail --- docs/MINI_APPS.md | 1 + src/components/dex/core/SwapForm.tsx | 2 +- .../ae-eth-buy/components/BuyAeWidget.tsx | 6 +- src/features/dex/WrapUnwrapWidget.tsx | 2 +- .../dex/components/AddLiquidityForm.tsx | 2 +- .../dex/components/RemoveLiquidityForm.tsx | 8 +- src/features/dex/views/DexBridge.tsx | 8 +- src/features/dex/views/DexSwap.tsx | 10 +- src/features/dex/views/DexWrap.tsx | 2 +- src/features/dex/views/Pool.tsx | 497 +++++++++++++----- src/features/mini-apps/README.md | 1 + src/features/mini-apps/community.ts | 1 + src/features/mini-apps/index.ts | 1 + src/features/mini-apps/plugins.ts | 1 + src/features/mini-apps/types.ts | 1 + src/features/mini-apps/views/MiniAppsDocs.tsx | 1 + .../mini-apps/views/MiniAppsLanding.tsx | 1 + .../trending/components/PercentageChange.tsx | 1 + 18 files changed, 401 insertions(+), 145 deletions(-) diff --git a/docs/MINI_APPS.md b/docs/MINI_APPS.md index b2865d878..939e51ddb 100644 --- a/docs/MINI_APPS.md +++ b/docs/MINI_APPS.md @@ -229,3 +229,4 @@ Join our developer community: - Discord: [Link to Discord] - Documentation: [Link to docs] + diff --git a/src/components/dex/core/SwapForm.tsx b/src/components/dex/core/SwapForm.tsx index b7c3699ca..3e5e53200 100644 --- a/src/components/dex/core/SwapForm.tsx +++ b/src/components/dex/core/SwapForm.tsx @@ -370,7 +370,7 @@ export default function SwapForm({ onPairSelected, onFromTokenSelected, embedded }, [swapLoading, amountIn, amountOut, tokenIn, tokenOut, hasInsufficientBalance, routeInfo.path.length, hasNoLiquidity, routeInfo.liquidityStatus]); return ( -
+
{/* Header */}

diff --git a/src/features/ae-eth-buy/components/BuyAeWidget.tsx b/src/features/ae-eth-buy/components/BuyAeWidget.tsx index 43df15851..81f70d655 100644 --- a/src/features/ae-eth-buy/components/BuyAeWidget.tsx +++ b/src/features/ae-eth-buy/components/BuyAeWidget.tsx @@ -306,11 +306,7 @@ function BuyAeWidgetContent({ return (
{/* Header */}
diff --git a/src/features/dex/WrapUnwrapWidget.tsx b/src/features/dex/WrapUnwrapWidget.tsx index cb0bc2440..e5116f47c 100644 --- a/src/features/dex/WrapUnwrapWidget.tsx +++ b/src/features/dex/WrapUnwrapWidget.tsx @@ -132,7 +132,7 @@ export function WrapUnwrapWidget({ className, style }: WrapUnwrapWidgetProps) { return (
diff --git a/src/features/dex/components/AddLiquidityForm.tsx b/src/features/dex/components/AddLiquidityForm.tsx index 463c99139..9d50a04ac 100644 --- a/src/features/dex/components/AddLiquidityForm.tsx +++ b/src/features/dex/components/AddLiquidityForm.tsx @@ -437,7 +437,7 @@ export default function AddLiquidityForm() { hasInsufficientBalance; return ( -
+
{/* Header */}
diff --git a/src/features/dex/components/RemoveLiquidityForm.tsx b/src/features/dex/components/RemoveLiquidityForm.tsx index d76cd46f2..b16c88285 100644 --- a/src/features/dex/components/RemoveLiquidityForm.tsx +++ b/src/features/dex/components/RemoveLiquidityForm.tsx @@ -33,7 +33,7 @@ export default function RemoveLiquidityForm() { if (!selectedPosition) { return ( -
+
πŸ’§
@@ -49,7 +49,7 @@ export default function RemoveLiquidityForm() { if (!address) { return ( -
+
); @@ -129,7 +129,7 @@ export default function RemoveLiquidityForm() { if (showConfirm) { return ( -
+
{/* Header */}
@@ -236,7 +236,7 @@ export default function RemoveLiquidityForm() { } return ( -
+
{/* Header */}
diff --git a/src/features/dex/views/DexBridge.tsx b/src/features/dex/views/DexBridge.tsx index 19e0f4536..ee139a16c 100644 --- a/src/features/dex/views/DexBridge.tsx +++ b/src/features/dex/views/DexBridge.tsx @@ -33,7 +33,7 @@ export default function DexBridge() {
{/* Main Content - wrapped in card */} -
+
{/* Browser Window Header */}
-
-
+
+
{/* Buy AE Widget */} -
+
diff --git a/src/features/dex/views/DexSwap.tsx b/src/features/dex/views/DexSwap.tsx index ded12a2eb..a00e00ffb 100644 --- a/src/features/dex/views/DexSwap.tsx +++ b/src/features/dex/views/DexSwap.tsx @@ -43,7 +43,7 @@ export default function DexSwap() {
{/* Main Content - wrapped in card */} -
+
{/* Browser Window Header */}
-
-
+
+
{/* Swap Form */} -
+
+
{/* Main Content - wrapped in card */} -
+
{/* Browser Window Header */}
('option1'); + const [showSwitcher, setShowSwitcher] = useState(true); + const [showDropdown, setShowDropdown] = useState(false); + const dropdownRef = useRef(null); const handleFormSelect = () => { // Focus on the forms section @@ -22,8 +29,225 @@ function PoolContent() { } }; + const layoutOptions = [ + { value: 'option1', label: 'Balanced Two-Column', description: 'Mobile: Stacked | Desktop: 60/40 split' }, + { value: 'option2', label: 'Three-Column Grid', description: 'Mobile: Stacked | Tablet: 2 cols | Desktop: 3 cols' }, + { value: 'option3', label: 'Vertical Stack', description: 'Mobile-first - Always stacked, max-width' }, + { value: 'option4', label: 'Side-by-Side Equal', description: 'Mobile: Stacked | Desktop: 50/50 split' }, + { value: 'option5', label: 'Wide Form Layout', description: 'Mobile: Stacked | Desktop: Form 70%' }, + { value: 'option6', label: 'Compact Dashboard', description: 'Mobile: Stacked | Tablet: 2 cols | Desktop: 3 cols' }, + { value: 'option7', label: 'Single Column', description: 'Mobile-first - Full width, always stacked' }, + { value: 'option8', label: 'Asymmetric Focus', description: 'Mobile: Stacked | Desktop: Large form focus' }, + ]; + + // Close dropdown when clicking outside + useEffect(() => { + function handleClickOutside(event: MouseEvent) { + if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { + setShowDropdown(false); + } + } + + if (showDropdown) { + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + } + }, [showDropdown]); + + // Reusable Positions Card Component - Improved Responsive + const PositionsCard = () => ( +
+ {/* Header */} +
+

+ Your Liquidity Positions +

+

+ Manage your liquidity positions and track earnings +

+
+ + {/* Stats Overview - Better responsive grid */} +
+
+
+ Positions +
+
+ {positions.length} +
+
+
+
+ Total Value +
+
+ ${positions.reduce((sum, pos) => sum + (Number(pos.valueUsd) || 0), 0).toLocaleString()} +
+
+
+
+ Fees Earned +
+
+ $0.00 +
+
+
+ + {/* Positions List - Improved Responsive */} +
+
+
+

+ Active Positions +

+ {loading && positions.length > 0 && ( + + )} +
+
+ {activeAccount && ( + + )} +
+
+ + {loading && positions.length === 0 ? ( +
+ + Loading your positions... +
+ ) : error ? ( +
+ {error} +
+ ) : positions.length === 0 ? ( +
+
+ πŸ’§ +
+
+ No liquidity positions found +
+
+ Start earning fees by providing liquidity to trading pairs +
+ {!activeAccount && ( + + )} +
+ ) : ( +
+ {positions.filter(position => position?.pair?.address).map((position, index) => ( + { + selectPositionForRemove(position); + handleFormSelect(); + }} + onAdd={(position) => { + selectPositionForAdd(position); + handleFormSelect(); + }} + /> + ))} +
+ )} +
+
+ ); + return ( -
+
+ {/* Fixed Layout Switcher - Top Right Corner */} + {showSwitcher && ( +
+ + {showDropdown && ( +
+
+
+ Layout Options (Temp) +
+
+ {layoutOptions.map((option) => ( + + ))} +
+ +
+
+ )} +
+ )} + + {/* Show Switcher Button (when hidden) */} + {!showSwitcher && ( + + )} + {/* Header */}
@@ -48,7 +272,7 @@ function PoolContent() {
{/* Main Content - wrapped in card */} -
+
{/* Browser Window Header */}
-
- {/* Top Row - Forms and Positions */} -
- {/* Liquidity Forms */} -
-
- {currentAction === 'remove' ? ( - - ) : ( - - )} -
-
- - {/* Positions */} -
-
- {/* Header */} -
-

- Your Liquidity Positions -

-

- Manage your liquidity positions and track earnings -

+
+ {/* OPTION 1: Balanced Two-Column Layout - Auto-fit with minmax, wraps when needed */} + {layoutOption === 'option1' && ( +
+
+
+ {currentAction === 'remove' ? ( + + ) : ( + + )} +
+
+
+ +
+ +
+
+ )} - {/* Stats Overview */} -
-
-
- Positions + {/* OPTION 2: Three-Column Grid Layout - Auto-fit with minmax */} + {layoutOption === 'option2' && ( +
+
+
+ {currentAction === 'remove' ? ( + + ) : ( + + )}
-
- {positions.length} +
+
+ +
+
+
+ +
+
+
+ )} + + {/* OPTION 3: Vertical Stack Layout - Always stacked */} + {layoutOption === 'option3' && ( +
+
+
+ {currentAction === 'remove' ? ( + + ) : ( + + )}
-
-
- Total Value +
+
+ +
+
+
-
- ${positions.reduce((sum, pos) => sum + (Number(pos.valueUsd) || 0), 0).toLocaleString()} +
+
+ )} + + {/* OPTION 4: Side-by-Side Equal Cards - Auto-fit with minmax */} + {layoutOption === 'option4' && ( +
+
+
+ {currentAction === 'remove' ? ( + + ) : ( + + )}
-
-
- Fees Earned +
+ + +
+
+ )} + + {/* OPTION 5: Wide Form Layout - Auto-fit with minmax */} + {layoutOption === 'option5' && ( +
+
+
+ {currentAction === 'remove' ? ( + + ) : ( + + )}
-
- $0.00 +
+
+ +
+
+ )} - {/* Positions List */} -
-
-
-

- Active Positions -

- {loading && positions.length > 0 && ( - + {/* OPTION 6: Compact Dashboard - Auto-fit with minmax */} + {layoutOption === 'option6' && ( +
+
+
+ {currentAction === 'remove' ? ( + + ) : ( + )}
-
- {activeAccount && ( - +
+
+ +
+
+ +
+
+ )} + + {/* OPTION 7: Single Column - Always stacked */} + {layoutOption === 'option7' && ( +
+
+
+ {currentAction === 'remove' ? ( + + ) : ( + )}
+
+ +
+
+ +
+
+ )} - {loading && positions.length === 0 ? ( -
- - Loading your positions... -
- ) : error ? ( -
- {error} -
- ) : positions.length === 0 ? ( -
-
- πŸ’§ -
-
- No liquidity positions found -
-
- Start earning fees by providing liquidity to trading pairs -
- {!activeAccount && ( - + {/* OPTION 8: Asymmetric Focus - Auto-fit with minmax */} + {layoutOption === 'option8' && ( +
+
+
+ {currentAction === 'remove' ? ( + + ) : ( + )}
- ) : ( -
- {positions.filter(position => position?.pair?.address).map((position, index) => ( - { - selectPositionForRemove(position); - handleFormSelect(); - }} - onAdd={(position) => { - selectPositionForAdd(position); - handleFormSelect(); - }} - /> - ))} +
+
+ +
+
- )} +
-
- {/* Recent Activity under Your Liquidity Positions */} -
- -
-
-
+ )}
diff --git a/src/features/mini-apps/README.md b/src/features/mini-apps/README.md index 3245bf679..9b7d1d29c 100644 --- a/src/features/mini-apps/README.md +++ b/src/features/mini-apps/README.md @@ -88,3 +88,4 @@ Get all routes for the router (used in `routes.tsx`). See `/docs/MINI_APPS.md` for complete examples. + diff --git a/src/features/mini-apps/community.ts b/src/features/mini-apps/community.ts index 65d96471f..d901658c8 100644 --- a/src/features/mini-apps/community.ts +++ b/src/features/mini-apps/community.ts @@ -46,3 +46,4 @@ // route: { ... }, // }); + diff --git a/src/features/mini-apps/index.ts b/src/features/mini-apps/index.ts index 397dc68c3..6ffc9e1ed 100644 --- a/src/features/mini-apps/index.ts +++ b/src/features/mini-apps/index.ts @@ -32,3 +32,4 @@ export * from './registry'; export { registerBuiltInMiniApps } from './built-in'; export { default as MiniAppsLanding } from './views/MiniAppsLanding'; + diff --git a/src/features/mini-apps/plugins.ts b/src/features/mini-apps/plugins.ts index 27bb1cef7..8a8c1318d 100644 --- a/src/features/mini-apps/plugins.ts +++ b/src/features/mini-apps/plugins.ts @@ -41,3 +41,4 @@ export function getMiniAppRoutes() { return miniAppRegistry.getAllRoutes(); } + diff --git a/src/features/mini-apps/types.ts b/src/features/mini-apps/types.ts index 5266f13f7..cc6c537b8 100644 --- a/src/features/mini-apps/types.ts +++ b/src/features/mini-apps/types.ts @@ -70,3 +70,4 @@ export interface MiniAppPlugin { cleanup?: () => void; } + diff --git a/src/features/mini-apps/views/MiniAppsDocs.tsx b/src/features/mini-apps/views/MiniAppsDocs.tsx index fd533450d..84083aa2d 100644 --- a/src/features/mini-apps/views/MiniAppsDocs.tsx +++ b/src/features/mini-apps/views/MiniAppsDocs.tsx @@ -177,3 +177,4 @@ export default function MyApp() { ); } + diff --git a/src/features/mini-apps/views/MiniAppsLanding.tsx b/src/features/mini-apps/views/MiniAppsLanding.tsx index 252957ae9..8581534da 100644 --- a/src/features/mini-apps/views/MiniAppsLanding.tsx +++ b/src/features/mini-apps/views/MiniAppsLanding.tsx @@ -151,3 +151,4 @@ export default function MiniAppsLanding() { ); } + diff --git a/src/features/trending/components/PercentageChange.tsx b/src/features/trending/components/PercentageChange.tsx index 07edba822..66e44b760 100644 --- a/src/features/trending/components/PercentageChange.tsx +++ b/src/features/trending/components/PercentageChange.tsx @@ -39,3 +39,4 @@ export default function PercentageChange({ + From 9341496aed83c3e719e0a8c95167ca275ddbea6e Mon Sep 17 00:00:00 2001 From: paolomolo Date: Mon, 15 Dec 2025 23:00:02 +0100 Subject: [PATCH 32/58] fix: fix bridge component loading issue - Remove wrapper div causing layout issues - Fix component structure and closing tags - Update styling to match responsive layout pattern --- .../ae-eth-bridge/components/AeEthBridge.tsx | 8 +++----- src/features/ae-eth-bridge/views/Bridge.tsx | 12 ++++++------ 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/features/ae-eth-bridge/components/AeEthBridge.tsx b/src/features/ae-eth-bridge/components/AeEthBridge.tsx index e37f37ab3..6f6b4684e 100644 --- a/src/features/ae-eth-bridge/components/AeEthBridge.tsx +++ b/src/features/ae-eth-bridge/components/AeEthBridge.tsx @@ -753,10 +753,9 @@ export function AeEthBridge() { return ( <> -
-
- {/* Header */} -
+
+ {/* Header */} +

{t('bridge.title')}

@@ -1033,7 +1032,6 @@ export function AeEthBridge() { }
-
{/* Success Dialog */} diff --git a/src/features/ae-eth-bridge/views/Bridge.tsx b/src/features/ae-eth-bridge/views/Bridge.tsx index 8c345d367..861f98fa8 100644 --- a/src/features/ae-eth-bridge/views/Bridge.tsx +++ b/src/features/ae-eth-bridge/views/Bridge.tsx @@ -1,6 +1,6 @@ import { useNavigate } from "react-router-dom"; import { AeEthBridge } from "../components/AeEthBridge"; -import { Bridge as BridgeIcon, X } from "lucide-react"; +import { Network, X } from "lucide-react"; export default function Bridge() { const navigate = useNavigate(); @@ -11,7 +11,7 @@ export default function Bridge() {
- +

Bridge

@@ -30,7 +30,7 @@ export default function Bridge() {
{/* Main Content - wrapped in card */} -
+
{/* Browser Window Header */}
-
-
+
+
{/* Bridge Widget */} -
+
From 73e534bf1e1ca100059c4cbc2ca27ae2c732930f Mon Sep 17 00:00:00 2001 From: paolomolo Date: Mon, 15 Dec 2025 23:07:42 +0100 Subject: [PATCH 33/58] feat: implement iframe-like isolation for mini-apps - Add MiniAppContainer for isolated mini-app contexts - Create ScopedDialog component that portals to mini-app container - Create ScopedSelect component for scoped dropdowns - Update AeEthBridge to use ScopedDialog and ScopedSelect - Fix MiniAppContainer import in registry - Enhance container styling for proper isolation and stacking context --- .../ae-eth-bridge/components/AeEthBridge.tsx | 4 +- .../mini-apps/components/MiniAppContainer.tsx | 62 +++++++ .../mini-apps/components/ScopedDialog.tsx | 148 +++++++++++++++ .../mini-apps/components/ScopedSelect.tsx | 174 ++++++++++++++++++ src/features/mini-apps/index.ts | 2 + src/features/mini-apps/registry.ts | 11 +- 6 files changed, 398 insertions(+), 3 deletions(-) create mode 100644 src/features/mini-apps/components/MiniAppContainer.tsx create mode 100644 src/features/mini-apps/components/ScopedDialog.tsx create mode 100644 src/features/mini-apps/components/ScopedSelect.tsx diff --git a/src/features/ae-eth-bridge/components/AeEthBridge.tsx b/src/features/ae-eth-bridge/components/AeEthBridge.tsx index 6f6b4684e..ef86f6efe 100644 --- a/src/features/ae-eth-bridge/components/AeEthBridge.tsx +++ b/src/features/ae-eth-bridge/components/AeEthBridge.tsx @@ -12,14 +12,14 @@ import { DialogFooter, DialogHeader, DialogTitle, -} from '@/components/ui/dialog'; +} from '@/features/mini-apps/components/ScopedDialog'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from '@/components/ui/select'; +} from '@/features/mini-apps/components/ScopedSelect'; import { useAeSdk } from '@/hooks/useAeSdk'; import { useRecentActivities } from '@/hooks/useRecentActivities'; diff --git a/src/features/mini-apps/components/MiniAppContainer.tsx b/src/features/mini-apps/components/MiniAppContainer.tsx new file mode 100644 index 000000000..3e79871c8 --- /dev/null +++ b/src/features/mini-apps/components/MiniAppContainer.tsx @@ -0,0 +1,62 @@ +import React, { createContext, useContext, useRef, ReactNode } from 'react'; + +/** + * Context for mini-app container reference + * Allows child components to portal dialogs/modals to the mini-app container + */ +interface MiniAppContainerContextValue { + containerRef: React.RefObject; +} + +const MiniAppContainerContext = createContext(null); + +/** + * Hook to access the mini-app container ref + * Use this to portal dialogs/modals to the mini-app container instead of document.body + */ +export function useMiniAppContainer() { + const context = useContext(MiniAppContainerContext); + if (!context) { + throw new Error('useMiniAppContainer must be used within a MiniAppContainer'); + } + return context; +} + +interface MiniAppContainerProps { + children: ReactNode; + className?: string; +} + +/** + * MiniAppContainer + * + * Creates an isolated container for mini-app content that behaves like an iframe. + * Dialogs/modals portaled to this container will only overlay the mini-app content, + * not the entire page. + * + * Features: + * - Creates a new stacking context (isolation: isolate) + * - Contains overflow to prevent content from escaping + * - Provides a portal target for scoped dialogs/modals + */ +export function MiniAppContainer({ children, className = '' }: MiniAppContainerProps) { + const containerRef = useRef(null); + + return ( + +
+ {children} +
+
+ ); +} diff --git a/src/features/mini-apps/components/ScopedDialog.tsx b/src/features/mini-apps/components/ScopedDialog.tsx new file mode 100644 index 000000000..3b67144c4 --- /dev/null +++ b/src/features/mini-apps/components/ScopedDialog.tsx @@ -0,0 +1,148 @@ +import * as React from "react" +import * as DialogPrimitive from "@radix-ui/react-dialog" +import { X } from "lucide-react" +import { useMiniAppContainer } from "./MiniAppContainer" +import { cn } from "@/lib/utils" + +const Dialog = DialogPrimitive.Root + +const DialogTrigger = DialogPrimitive.Trigger + +const DialogClose = DialogPrimitive.Close + +/** + * Scoped DialogOverlay that overlays only the mini-app container + * Uses fixed positioning relative to the container + */ +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + return ( + + ) +}) +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName + +/** + * Scoped DialogPortal that portals to the mini-app container + */ +const DialogPortal = ({ children, ...props }: DialogPrimitive.DialogPortalProps) => { + const { containerRef } = useMiniAppContainer(); + + // Get container directly from ref - Radix Portal handles null/undefined gracefully + const container = containerRef.current; + + return ( + + {children} + + ); +} + +/** + * Scoped DialogContent that renders within the mini-app container + * Uses fixed positioning to center relative to viewport, but portaled to container + */ +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => { + const { containerRef } = useMiniAppContainer(); + + return ( + + + + {children} + + + Close + + + + ); +}) +DialogContent.displayName = DialogPrimitive.Content.displayName + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogHeader.displayName = "DialogHeader" + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogFooter.displayName = "DialogFooter" + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogTitle.displayName = DialogPrimitive.Title.displayName + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogDescription.displayName = DialogPrimitive.Description.displayName + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogTrigger, + DialogClose, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +} diff --git a/src/features/mini-apps/components/ScopedSelect.tsx b/src/features/mini-apps/components/ScopedSelect.tsx new file mode 100644 index 000000000..c3f12dd29 --- /dev/null +++ b/src/features/mini-apps/components/ScopedSelect.tsx @@ -0,0 +1,174 @@ +import * as React from "react" +import * as SelectPrimitive from "@radix-ui/react-select" +import { Check, ChevronDown, ChevronUp } from "lucide-react" +import { useMiniAppContainer } from "./MiniAppContainer" +import { cn } from "@/lib/utils" + +const Select = SelectPrimitive.Root + +const SelectGroup = SelectPrimitive.Group + +const SelectValue = SelectPrimitive.Value + +const SelectTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + span]:line-clamp-1", + className + )} + {...props} + > + {children} + + + + +)) +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName + +const SelectScrollUpButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName + +const SelectScrollDownButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +SelectScrollDownButton.displayName = + SelectPrimitive.ScrollDownButton.displayName + +/** + * Scoped SelectPortal that portals to the mini-app container + */ +const SelectPortal = ({ children, ...props }: SelectPrimitive.SelectPortalProps) => { + const { containerRef } = useMiniAppContainer(); + const container = containerRef.current; + + return ( + + {children} + + ); +} + +/** + * Scoped SelectContent that renders within the mini-app container + */ +const SelectContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, position = "popper", ...props }, ref) => ( + + + + + {children} + + + + +)) +SelectContent.displayName = SelectPrimitive.Content.displayName + +const SelectLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SelectLabel.displayName = SelectPrimitive.Label.displayName + +const SelectItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)) +SelectItem.displayName = SelectPrimitive.Item.displayName + +const SelectSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SelectSeparator.displayName = SelectPrimitive.Separator.displayName + +export { + Select, + SelectGroup, + SelectValue, + SelectTrigger, + SelectContent, + SelectLabel, + SelectItem, + SelectSeparator, + SelectScrollUpButton, + SelectScrollDownButton, +} diff --git a/src/features/mini-apps/index.ts b/src/features/mini-apps/index.ts index 6ffc9e1ed..990163236 100644 --- a/src/features/mini-apps/index.ts +++ b/src/features/mini-apps/index.ts @@ -31,5 +31,7 @@ export * from './types'; export * from './registry'; export { registerBuiltInMiniApps } from './built-in'; export { default as MiniAppsLanding } from './views/MiniAppsLanding'; +export { MiniAppContainer, useMiniAppContainer } from './components/MiniAppContainer'; +export * from './components/ScopedDialog'; diff --git a/src/features/mini-apps/registry.ts b/src/features/mini-apps/registry.ts index b1b66b212..0047ca4de 100644 --- a/src/features/mini-apps/registry.ts +++ b/src/features/mini-apps/registry.ts @@ -1,5 +1,6 @@ import React from 'react'; import SocialLayout from '@/components/layout/SocialLayout'; +import { MiniAppContainer } from './components/MiniAppContainer'; import type { MiniAppPlugin, MiniAppMetadata, MiniAppCategory } from './types'; /** @@ -88,7 +89,15 @@ class MiniAppRegistry { return { path: plugin.route.path, - element: React.createElement(Layout, {}, React.createElement(Component)), + element: React.createElement( + Layout, + {}, + React.createElement( + MiniAppContainer, + {}, + React.createElement(Component) + ) + ), ...plugin.route.options, }; }); From 728684f9aed94819af7ff42be166a20f3a0980da Mon Sep 17 00:00:00 2001 From: paolomolo Date: Mon, 15 Dec 2025 23:24:02 +0100 Subject: [PATCH 34/58] feat: add fixed bottom liquid glass navigation bar - Create liquid glass navigation bar with purple-blue glow effect - Add Home, Trends, Mini-Apps, and Wallet navigation items - Change first nav item label from Feed to Home - Add wallet button that shows address when connected - Navigate to user profile when wallet button clicked (if connected) - Open connect wallet modal when wallet button clicked (if not connected) - Make navigation visible on all screen sizes (not just mobile) - Use high z-index to float over all content --- src/components/layout/MobileBottomNav.tsx | 173 ++++++++++++++++++ src/components/layout/Shell.tsx | 5 +- .../layout/app-header/navigationItems.tsx | 2 +- .../mini-apps/components/ScopedSelect.tsx | 1 + 4 files changed, 179 insertions(+), 2 deletions(-) create mode 100644 src/components/layout/MobileBottomNav.tsx diff --git a/src/components/layout/MobileBottomNav.tsx b/src/components/layout/MobileBottomNav.tsx new file mode 100644 index 000000000..f51ab3b80 --- /dev/null +++ b/src/components/layout/MobileBottomNav.tsx @@ -0,0 +1,173 @@ +import React from 'react'; +import { useNavigate, useLocation } from 'react-router-dom'; +import { getNavigationItems } from './app-header/navigationItems'; +import { useTranslation } from 'react-i18next'; +import { useAeSdk } from '../../hooks/useAeSdk'; +import { useModal } from '../../hooks/useModal'; + +export default function MobileBottomNav() { + const navigate = useNavigate(); + const location = useLocation(); + const { t } = useTranslation('navigation'); + const { activeAccount } = useAeSdk(); + const { openModal } = useModal(); + const navigationItems = getNavigationItems(t); + + // Filter to only show Home, Trends, and Mini-Apps + const mainNavItems = navigationItems + .filter((item) => item.id === 'home' || item.id === 'trending' || item.id === 'dex') + .map((item) => ({ + id: item.id, + label: item.label, + path: item.path, + icon: item.icon, + })); + + // Format wallet address for display + const formatAddress = (address: string) => { + if (!address) return ''; + return `${address.slice(0, 6)}...${address.slice(-4)}`; + }; + + // Wallet icon SVG + const WalletIcon = ({ className }: { className?: string }) => ( + + + + + + ); + + const isActiveRoute = (path: string) => { + if (path === '/') return location.pathname === '/'; + if (path === '/trends/tokens') { + // Check if we're on any trends route + return location.pathname.startsWith('/trends') || location.pathname.startsWith('/trending'); + } + if (path === '/apps') { + // Check if we're on any mini-apps route + return location.pathname.startsWith('/apps'); + } + return location.pathname.startsWith(path); + }; + + return ( + + ); +} diff --git a/src/components/layout/Shell.tsx b/src/components/layout/Shell.tsx index 4e9a84cd2..bec88c583 100644 --- a/src/components/layout/Shell.tsx +++ b/src/components/layout/Shell.tsx @@ -1,5 +1,6 @@ import React from "react"; import BackToTop from "./BackToTop"; +import MobileBottomNav from "./MobileBottomNav"; import { useLayoutVariant } from "../../contexts/LayoutVariantContext"; type ShellProps = { @@ -78,7 +79,7 @@ export default function Shell({ left, right, children, containerClassName }: She )} -
{children}
+
{children}
{showVisualRight && (
+ {/* Mobile Bottom Navigation */} + ); } diff --git a/src/components/layout/app-header/navigationItems.tsx b/src/components/layout/app-header/navigationItems.tsx index b9842893a..84d4d6111 100644 --- a/src/components/layout/app-header/navigationItems.tsx +++ b/src/components/layout/app-header/navigationItems.tsx @@ -14,7 +14,7 @@ export interface NavigationItem { export const getNavigationItems = (t: TFunction): NavigationItem[] => [ { id: "home", - label: "Feed", // Renamed from t('home') for clarity as per user request + label: "Home", path: "/", icon: ( diff --git a/src/features/mini-apps/components/ScopedSelect.tsx b/src/features/mini-apps/components/ScopedSelect.tsx index c3f12dd29..2e1417ded 100644 --- a/src/features/mini-apps/components/ScopedSelect.tsx +++ b/src/features/mini-apps/components/ScopedSelect.tsx @@ -172,3 +172,4 @@ export { SelectScrollUpButton, SelectScrollDownButton, } + From e81ad9d37c54d86e51b0ca443622c45dd2e3e680 Mon Sep 17 00:00:00 2001 From: paolomolo Date: Mon, 15 Dec 2025 23:31:12 +0100 Subject: [PATCH 35/58] feat: improve wallet button in bottom navigation - Show account avatar instead of wallet icon when connected - Display AE balance in second row under address - Make connected account button more compact to match other nav items - Reduce avatar size and adjust text sizes for better fit - Navigate to user profile when wallet button clicked (if connected) --- src/components/layout/MobileBottomNav.tsx | 59 ++++++++++++----------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/src/components/layout/MobileBottomNav.tsx b/src/components/layout/MobileBottomNav.tsx index f51ab3b80..c37c09189 100644 --- a/src/components/layout/MobileBottomNav.tsx +++ b/src/components/layout/MobileBottomNav.tsx @@ -4,6 +4,8 @@ import { getNavigationItems } from './app-header/navigationItems'; import { useTranslation } from 'react-i18next'; import { useAeSdk } from '../../hooks/useAeSdk'; import { useModal } from '../../hooks/useModal'; +import { useAccountBalances } from '../../hooks/useAccountBalances'; +import AddressAvatar from '../AddressAvatar'; export default function MobileBottomNav() { const navigate = useNavigate(); @@ -11,6 +13,7 @@ export default function MobileBottomNav() { const { t } = useTranslation('navigation'); const { activeAccount } = useAeSdk(); const { openModal } = useModal(); + const { decimalBalance } = useAccountBalances(activeAccount || ''); const navigationItems = getNavigationItems(t); // Filter to only show Home, Trends, and Mini-Apps @@ -129,7 +132,7 @@ export default function MobileBottomNav() { } }} className={` - relative flex items-center gap-2 px-4 py-2.5 rounded-xl + relative flex items-center gap-2 px-3 py-2 rounded-xl transition-all duration-300 ease-out ${activeAccount ? 'bg-gray-800/80' // Dark gray pill background when connected @@ -138,34 +141,34 @@ export default function MobileBottomNav() { `} aria-label={activeAccount ? 'View Profile' : 'Connect Wallet'} > - {/* Icon */} -
- -
+ {/* Avatar or Icon */} + {activeAccount ? ( +
+ +
+ ) : ( +
+ +
+ )} - {/* Label or Address */} - - {activeAccount ? formatAddress(activeAccount) : 'Wallet'} - + {/* Address and Balance */} + {activeAccount ? ( +
+ + {formatAddress(activeAccount)} + + + {decimalBalance.prettify()} AE + +
+ ) : ( + + Wallet + + )}
From 8a9c3a93ef0823fed6d7c61c0942ef56e2ffc973 Mon Sep 17 00:00:00 2001 From: paolomolo Date: Mon, 15 Dec 2025 23:36:32 +0100 Subject: [PATCH 36/58] refactor: remove temporary layout switcher from Pool component - Remove layout switcher button and dropdown menu - Remove all layout options except Balanced Two-Column - Simplify component by removing layout state management - Keep only the default responsive layout --- src/features/dex/views/Pool.tsx | 279 ++------------------------------ 1 file changed, 15 insertions(+), 264 deletions(-) diff --git a/src/features/dex/views/Pool.tsx b/src/features/dex/views/Pool.tsx index 39903cb2f..161f238ac 100644 --- a/src/features/dex/views/Pool.tsx +++ b/src/features/dex/views/Pool.tsx @@ -1,4 +1,4 @@ -import { useState, useRef, useEffect } from 'react'; +import React from 'react'; import { useNavigate } from 'react-router-dom'; import ConnectWalletButton from '../../../components/ConnectWalletButton'; import RecentActivity from '../../../components/dex/supporting/RecentActivity'; @@ -7,19 +7,13 @@ import { AddLiquidityForm, LiquidityPositionCard, RemoveLiquidityForm } from '.. import { PoolProvider, usePool } from '../context/PoolProvider'; import { useLiquidityPositions } from '../hooks'; import Spinner from '../../../components/Spinner'; -import { Droplets, X, LayoutGrid } from 'lucide-react'; - -type LayoutOption = 'option1' | 'option2' | 'option3' | 'option4' | 'option5' | 'option6' | 'option7' | 'option8'; +import { Droplets, X } from 'lucide-react'; function PoolContent() { const navigate = useNavigate(); const { activeAccount } = useAccount(); const { positions, loading, error, refreshPositions } = useLiquidityPositions(); const { selectPositionForAdd, selectPositionForRemove, currentAction } = usePool(); - const [layoutOption, setLayoutOption] = useState('option1'); - const [showSwitcher, setShowSwitcher] = useState(true); - const [showDropdown, setShowDropdown] = useState(false); - const dropdownRef = useRef(null); const handleFormSelect = () => { // Focus on the forms section @@ -29,33 +23,6 @@ function PoolContent() { } }; - const layoutOptions = [ - { value: 'option1', label: 'Balanced Two-Column', description: 'Mobile: Stacked | Desktop: 60/40 split' }, - { value: 'option2', label: 'Three-Column Grid', description: 'Mobile: Stacked | Tablet: 2 cols | Desktop: 3 cols' }, - { value: 'option3', label: 'Vertical Stack', description: 'Mobile-first - Always stacked, max-width' }, - { value: 'option4', label: 'Side-by-Side Equal', description: 'Mobile: Stacked | Desktop: 50/50 split' }, - { value: 'option5', label: 'Wide Form Layout', description: 'Mobile: Stacked | Desktop: Form 70%' }, - { value: 'option6', label: 'Compact Dashboard', description: 'Mobile: Stacked | Tablet: 2 cols | Desktop: 3 cols' }, - { value: 'option7', label: 'Single Column', description: 'Mobile-first - Full width, always stacked' }, - { value: 'option8', label: 'Asymmetric Focus', description: 'Mobile: Stacked | Desktop: Large form focus' }, - ]; - - // Close dropdown when clicking outside - useEffect(() => { - function handleClickOutside(event: MouseEvent) { - if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { - setShowDropdown(false); - } - } - - if (showDropdown) { - document.addEventListener('mousedown', handleClickOutside); - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; - } - }, [showDropdown]); - // Reusable Positions Card Component - Improved Responsive const PositionsCard = () => (
@@ -183,71 +150,6 @@ function PoolContent() { return (
- {/* Fixed Layout Switcher - Top Right Corner */} - {showSwitcher && ( -
- - {showDropdown && ( -
-
-
- Layout Options (Temp) -
-
- {layoutOptions.map((option) => ( - - ))} -
- -
-
- )} -
- )} - - {/* Show Switcher Button (when hidden) */} - {!showSwitcher && ( - - )} - {/* Header */}
@@ -293,175 +195,24 @@ function PoolContent() {
- {/* OPTION 1: Balanced Two-Column Layout - Auto-fit with minmax, wraps when needed */} - {layoutOption === 'option1' && ( -
-
-
- {currentAction === 'remove' ? ( - - ) : ( - - )} -
-
-
- -
- -
-
-
- )} - - {/* OPTION 2: Three-Column Grid Layout - Auto-fit with minmax */} - {layoutOption === 'option2' && ( -
-
-
- {currentAction === 'remove' ? ( - - ) : ( - - )} -
-
-
- -
-
-
- -
-
-
- )} - - {/* OPTION 3: Vertical Stack Layout - Always stacked */} - {layoutOption === 'option3' && ( -
-
-
- {currentAction === 'remove' ? ( - - ) : ( - - )} -
-
-
-
- -
-
- -
-
-
- )} - - {/* OPTION 4: Side-by-Side Equal Cards - Auto-fit with minmax */} - {layoutOption === 'option4' && ( -
-
-
- {currentAction === 'remove' ? ( - - ) : ( - - )} -
-
-
- - -
-
- )} - - {/* OPTION 5: Wide Form Layout - Auto-fit with minmax */} - {layoutOption === 'option5' && ( -
-
-
- {currentAction === 'remove' ? ( - - ) : ( - - )} -
-
-
- -
- -
-
-
- )} - - {/* OPTION 6: Compact Dashboard - Auto-fit with minmax */} - {layoutOption === 'option6' && ( -
-
-
- {currentAction === 'remove' ? ( - - ) : ( - - )} -
-
-
- -
-
- + {/* Balanced Two-Column Layout - Auto-fit with minmax, wraps when needed */} +
+
+
+ {currentAction === 'remove' ? ( + + ) : ( + + )}
- )} - - {/* OPTION 7: Single Column - Always stacked */} - {layoutOption === 'option7' && ( -
-
-
- {currentAction === 'remove' ? ( - - ) : ( - - )} -
-
-
- -
-
+
+ +
- )} - - {/* OPTION 8: Asymmetric Focus - Auto-fit with minmax */} - {layoutOption === 'option8' && ( -
-
-
- {currentAction === 'remove' ? ( - - ) : ( - - )} -
-
-
- -
- -
-
-
- )} +
From 1c7ae76e7980624d10f132bdc3ae7151a2ae2ae4 Mon Sep 17 00:00:00 2001 From: paolomolo Date: Tue, 16 Dec 2025 00:34:58 +0100 Subject: [PATCH 37/58] feat: show left rail on wide screens for mini-apps and hide logo header - Add left rail visibility on 2xl+ screens (>1500px) for mini-app routes - Hide logo header on mini-app pages when left rail is visible (2xl+) - Update grid layout to accommodate left rail | mini-app | right rail on wide screens - Apply consistent spacing and structure across all mini-app views --- src/components/layout/MobileBottomNav.tsx | 17 +- src/components/layout/Shell.tsx | 43 +++- src/components/layout/SocialLayout.tsx | 52 +++- src/features/ae-eth-bridge/views/Bridge.tsx | 29 ++- src/features/dex/views/DexBridge.tsx | 29 ++- src/features/dex/views/DexSwap.tsx | 29 ++- src/features/dex/views/DexWrap.tsx | 29 ++- src/features/dex/views/Pool.tsx | 29 ++- .../mini-apps/views/MiniAppsLanding.tsx | 231 ++++++++++-------- 9 files changed, 304 insertions(+), 184 deletions(-) diff --git a/src/components/layout/MobileBottomNav.tsx b/src/components/layout/MobileBottomNav.tsx index c37c09189..d8a632a17 100644 --- a/src/components/layout/MobileBottomNav.tsx +++ b/src/components/layout/MobileBottomNav.tsx @@ -54,6 +54,9 @@ export default function MobileBottomNav() { return location.pathname.startsWith(path); }; + // Check if we're on the profile page + const isOnProfilePage = activeAccount && location.pathname.startsWith(`/users/${activeAccount}`); + return (