Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 23 additions & 7 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
import { BrowserRouter, Routes, Route, Navigate, useParams } from "react-router-dom";
import { useEffect, useState } from "react";
import Layout from "./components/Layout";
import AppNavbar from "./components/navigation/AppNavbar";
import { WalletProvider } from "./components/wallet-connect/Walletcontext";
import Home from "./pages/Home";
import Dashboard from "./pages/Dashboard";
import Streams from "./pages/Streams";
import Recipient from "./pages/Recipient";
import ConnectWallet from "./pages/ConnectWallet";
import { useState, useEffect } from "react";
import Landing from "./pages/Landing";
import AppNavbar from "./components/navigation/AppNavbar";
import { WalletProvider } from "./components/wallet-connect/Walletcontext";
import ErrorPage from './pages/ErrorPage';
import TreasuryPage from "./pages/TreasuryPage";
import ErrorPage from "./pages/ErrorPage";
import NotFound from "./pages/NotFound";

function LegacyStreamRedirect() {
const { streamId } = useParams();
return (
<Navigate
to={streamId ? `/app/streams/${streamId}` : "/app/streams"}
replace
/>
);
}

export default function App() {
const [theme, setTheme] = useState<"light" | "dark">(() => {
const saved = localStorage.getItem("theme");
Expand Down Expand Up @@ -86,18 +97,23 @@ export default function App() {
return (
<BrowserRouter>
<WalletProvider>
{/* Global navbar — anon vs connected state handled internally */}
<AppNavbar onThemeToggle={handleThemeToggle} theme={theme} />

<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Navigate to="/app" replace />} />
<Route path="/streams" element={<Navigate to="/app/streams" replace />} />
<Route path="/streams/:streamId" element={<LegacyStreamRedirect />} />
<Route path="/landing" element={<Landing theme={theme} />} />
<Route path="/app" element={<Layout onThemeToggle={handleThemeToggle} theme={theme} />}>
<Route
path="/app"
element={<Layout onThemeToggle={handleThemeToggle} theme={theme} />}
>
<Route index element={<Dashboard />} />
<Route path="streams" element={<Streams />} />
<Route path="streams/:streamId" element={<Streams />} />
<Route path="recipient" element={<Recipient />} />
<Route path="treasurypage" element={<TreasuryPage />} />
<Route path="error" element={<ErrorPage />} />
</Route>
<Route path="/connect-wallet" element={<ConnectWallet />} />
Expand Down
14 changes: 7 additions & 7 deletions src/components/AppNavbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ function truncateAddress(addr: string): string {

function usePageTitle(): string {
const { pathname } = useLocation();
const map: Record<string, string> = {
"/app": "Dashboard",
"/app/streams": "Streams",
"/app/recipient": "Recipient",
};
return map[pathname] ?? "Dashboard";
if (pathname === "/app") return "Dashboard";
if (pathname.startsWith("/app/streams/")) return "Stream Detail";
if (pathname.startsWith("/app/streams")) return "Streams";
if (pathname.startsWith("/app/recipient")) return "Recipient";
if (pathname.startsWith("/app/treasurypage")) return "Treasury";
return "Dashboard";
}

function MoonIcon() {
Expand Down Expand Up @@ -310,4 +310,4 @@ const styles: Record<string, React.CSSProperties> = {
dropdown: { position: "absolute", right: 0, top: "calc(100% + 8px)", minWidth: "185px", background: "var(--navbar-bg)", border: "1px solid var(--navbar-border)", borderRadius: "10px", boxShadow: "var(--navbar-shadow)", padding: "5px", zIndex: 200 },
dropdownItem: { display: "flex", alignItems: "center", gap: "8px", width: "100%", padding: "8px 10px", border: "none", background: "transparent", color: "var(--text)", fontFamily: "'Plus Jakarta Sans', system-ui, sans-serif", fontSize: "0.85rem", fontWeight: 500, cursor: "pointer", borderRadius: "6px", textAlign: "left", minHeight: "36px", transition: "background 0.15s" },
dropdownDivider: { height: "1px", background: "var(--navbar-border)", margin: "4px 0" },
};
};
2 changes: 0 additions & 2 deletions src/components/CreateStreamModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ export default function CreateStreamModal({ isOpen, onClose, onStreamCreated }:
const [isSubmitting, setIsSubmitting] = useState(false);
const userDeposit = 200.0;
const requiredDeposit = (parseFloat(accrualRate || '0') * parseFloat(duration || '0')).toFixed(2);
const displayDeposit = depositAmount.trim() ? parseFloat(depositAmount.replace(/,/g, '')).toFixed(2) : requiredDeposit;

useEffect(() => {
if (!isOpen) return;
Expand Down Expand Up @@ -573,7 +572,6 @@ export default function CreateStreamModal({ isOpen, onClose, onStreamCreated }:
className="btn btn-next"
onClick={handleNext}
disabled={isSubmitting}
aria-busy={isSubmitting && currentStep === 3}
>
Next
</button>
Expand Down
11 changes: 8 additions & 3 deletions src/components/Layout.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.app-layout {
display: flex;
flex-direction: column; /* ← change row to column */
flex-direction: column;
min-height: 100vh;
position: relative;
background: var(--bg);
Expand All @@ -12,8 +12,6 @@
overflow: hidden;
}

.app-layout__sidebar {
width: 220px;
.app-sidebar {
width: 244px;
background: var(--surface);
Expand Down Expand Up @@ -94,6 +92,12 @@
color: #ecf4ff;
}

.app-nav-link.is-active {
background: rgba(45, 212, 191, 0.12);
border: 1px solid rgba(45, 212, 191, 0.2);
color: #ecfeff;
}

.app-nav-badge {
width: 1.6rem;
height: 1.6rem;
Expand Down Expand Up @@ -148,6 +152,7 @@
flex-direction: column;
flex: 1;
min-width: 0;
min-height: 0;
}

.app-mobile-topbar {
Expand Down
135 changes: 79 additions & 56 deletions src/components/Layout.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useState } from "react";
import { Link, Outlet } from "react-router-dom";
import { NavLink, Outlet, useLocation } from "react-router-dom";
import ConnectWalletModal from "./ConnectWalletModal";
import Footer from "./Footer";
import "./layout.css";
import "./Layout.css";

type NavItem = { to: string; label: string; shortLabel: string };

Expand All @@ -17,75 +17,98 @@ interface LayoutProps {
theme?: "light" | "dark";
}

export default function Layout({ theme: _theme = "light" }: LayoutProps) {
export default function Layout({
onThemeToggle: _onThemeToggle,
theme: _theme = "light",
}: LayoutProps) {
const location = useLocation();
const [isModalOpen, setIsModalOpen] = useState(false);
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false);
const [isMobileSidebarOpen, setIsMobileSidebarOpen] = useState(false);

const showFooter = !location.pathname.includes("/treasurypage");
const closeMobileSidebar = () => setIsMobileSidebarOpen(false);

return (
<div className={`app-layout${isSidebarCollapsed ? " is-collapsed" : ""}${isMobileSidebarOpen ? " is-mobile-open" : ""}`}>
<aside id="app-sidebar" className="app-sidebar" aria-label="Primary navigation">
<div className="app-sidebar-header">
<div className="app-logo" aria-label="Fluxora">
{isSidebarCollapsed ? "Fx" : "Fluxora"}
<div className="app-layout__body">
<aside id="app-sidebar" className="app-sidebar" aria-label="Primary navigation">
<div className="app-sidebar-header">
<div className="app-logo" aria-label="Fluxora">
{isSidebarCollapsed ? "Fx" : "Fluxora"}
</div>
<button
type="button"
className="app-sidebar-toggle"
aria-label={isSidebarCollapsed ? "Expand sidebar" : "Collapse sidebar"}
onClick={() => setIsSidebarCollapsed((prev) => !prev)}
>
<span className={`app-toggle-chevron${isSidebarCollapsed ? " is-rotated" : ""}`} aria-hidden="true">
<svg viewBox="0 0 24 24">
<path
d="M15 19l-7-7 7-7"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</span>
</button>
</div>
<button
type="button"
className="app-sidebar-toggle"
aria-label={isSidebarCollapsed ? "Expand sidebar" : "Collapse sidebar"}
onClick={() => setIsSidebarCollapsed((prev) => !prev)}
>
<span className={`app-toggle-chevron${isSidebarCollapsed ? " is-rotated" : ""}`} aria-hidden="true">

<nav className="app-nav">
{NAV_ITEMS.map((item) => (
<NavLink
key={item.to}
to={item.to}
end={item.to === "/app"}
className={({ isActive }) =>
`app-nav-link${isActive ? " is-active" : ""}`
}
onClick={closeMobileSidebar}
>
<span className="app-nav-badge" aria-hidden="true">
{item.shortLabel}
</span>
<span className="app-nav-label">{item.label}</span>
</NavLink>
))}
</nav>

<button className="app-connect-button" onClick={() => setIsModalOpen(true)}>
<span className="app-connect-icon" aria-hidden="true">
<svg viewBox="0 0 24 24">
<path d="M15 19l-7-7 7-7" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M9 12h6" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
<path d="M12 9v6" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
<rect x="4" y="6" width="16" height="12" rx="3" fill="none" stroke="currentColor" strokeWidth="1.6" />
</svg>
</span>
<span className="app-connect-label">Connect wallet</span>
</button>
</div>
</aside>

<nav className="app-nav">
{NAV_ITEMS.map((item) => (
<Link key={item.to} to={item.to} className="app-nav-link" onClick={closeMobileSidebar}>
<span className="app-nav-badge" aria-hidden="true">{item.shortLabel}</span>
<span className="app-nav-label">{item.label}</span>
</Link>
))}
</nav>
<div className="app-content-area">
<header className="app-mobile-topbar">
<button
type="button"
className="app-mobile-menu-btn"
onClick={() => setIsMobileSidebarOpen((prev) => !prev)}
aria-label={isMobileSidebarOpen ? "Close sidebar" : "Open sidebar"}
aria-expanded={isMobileSidebarOpen}
aria-controls="app-sidebar"
>
<span /><span /><span />
</button>
<div className="app-mobile-title">Fluxora</div>
</header>

<button className="app-connect-button" onClick={() => setIsModalOpen(true)}>
<span className="app-connect-icon" aria-hidden="true">
<svg viewBox="0 0 24 24">
<path d="M9 12h6" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
<path d="M12 9v6" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
<rect x="4" y="6" width="16" height="12" rx="3" fill="none" stroke="currentColor" strokeWidth="1.6" />
</svg>
</span>
<span className="app-connect-label">Connect wallet</span>
</button>
</aside>
<main className="app-main">
<Outlet />
</main>

<div className="app-content-area">
<header className="app-mobile-topbar">
<button
type="button"
className="app-mobile-menu-btn"
onClick={() => setIsMobileSidebarOpen((prev) => !prev)}
aria-label={isMobileSidebarOpen ? "Close sidebar" : "Open sidebar"}
aria-expanded={isMobileSidebarOpen}
aria-controls="app-sidebar"
>
<span /><span /><span />
</button>
<div className="app-mobile-title">Fluxora</div>
</header>

<main className="app-main">
<Outlet />
</main>

<Footer />
{showFooter ? <Footer /> : null}
</div>
</div>

<button
Expand Down
2 changes: 1 addition & 1 deletion src/components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState, useEffect } from "react";
import { Link, useLocation } from "react-router-dom";
import { Link } from "react-router-dom";

import WalletButton from "./wallet-connect/Walletbutton";

Expand Down
4 changes: 2 additions & 2 deletions src/components/RecentStreams.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ interface RecentStreamsProps {
viewAllUrl?: string;
}

export default function RecentStreams({ streams, viewAllUrl = '/streams' }: RecentStreamsProps) {
export default function RecentStreams({ streams, viewAllUrl = '/app/streams' }: RecentStreamsProps) {
return (
<section style={sectionContainer}>
<div style={header}>
Expand Down Expand Up @@ -55,7 +55,7 @@ export default function RecentStreams({ streams, viewAllUrl = '/streams' }: Rece
</td>
<td style={td}>
<Link
to={stream.detailUrl || `/streams/${stream.id}`}
to={stream.detailUrl || `/app/streams/${stream.id}`}
style={viewLink}
aria-label={`View details for ${stream.name}`}
>
Expand Down
1 change: 0 additions & 1 deletion src/components/RecipientEmptyState.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import React from "react";
import EmptyState from "./EmptyState";

interface RecipientEmptyStateProps {
Expand Down
1 change: 0 additions & 1 deletion src/components/RecipientLoading.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import React from "react";
import { Skeleton, SkeletonCard } from "./Skeleton";
import "./skeleton.css";

Expand Down
1 change: 0 additions & 1 deletion src/components/StreamsLoading.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import React from "react";
import { Skeleton, SkeletonCard } from "./Skeleton";
import "./skeleton.css";

Expand Down
1 change: 0 additions & 1 deletion src/components/TreasuryOverviewLoading.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import React from "react";
import { Skeleton, SkeletonCard } from "./Skeleton";
import "./skeleton.css";

Expand Down
4 changes: 2 additions & 2 deletions src/components/treasuryOverviewPage/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ export default function Header() {
</div>

<button
onClick={() => navigate("/streams/create")}
onClick={() => navigate("/app/streams")}
className="flex items-center gap-2 bg-cyan-600/70 shadow-cyan-600/70 px-4 py-2 text-white rounded-lg"
>
<span className="text-xl font-bold">+</span>
Create stream
</button>
</div>
);
}
}
7 changes: 3 additions & 4 deletions src/components/treasuryOverviewPage/Metric.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import React from "react";
import { ReactNode } from "react";
import type { ReactNode } from "react";

export interface Metric {
// icon can be a URL string, an emoji, or a full React node such as an <img />
icon: React.ReactNode;
icon: ReactNode;
label: string;
value: string;
desc: string;
}
}
4 changes: 2 additions & 2 deletions src/components/treasuryOverviewPage/RecentStreams.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default function RecentStreams() {
<div className="flex items-center justify-between mb-4">
<h2 className="text-xl font-bold text-black">Recent streams</h2>
<button
onClick={() => navigate("/streams")}
onClick={() => navigate("/app/streams")}
className="text-teal-400"
>
View all →
Expand All @@ -19,4 +19,4 @@ export default function RecentStreams() {
<StreamsTable />
</div>
);
}
}
Loading