Skip to content
Open
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" },
};
};
55 changes: 43 additions & 12 deletions src/components/CreateStreamModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,21 @@ function isValidStellarAddress(value: string): boolean {
interface CreateStreamModalProps {
isOpen: boolean;
onClose: () => void;
/** Called when user completes the flow and clicks "Create stream" on step 3. Use to show success modal. */
onStreamCreated?: () => void;
onSubmit?: (payload: CreateStreamFormData) => void;
}

export default function CreateStreamModal({ isOpen, onClose, onStreamCreated }: CreateStreamModalProps) {
export interface CreateStreamFormData {
recipient: string;
depositAmount: number;
accrualRate: number;
durationInMonths: number;
startTimeOption: 'now' | 'custom';
customStartDate: string;
cliffEnabled: boolean;
cliffDate: string;
}

export default function CreateStreamModal({ isOpen, onClose, onSubmit }: CreateStreamModalProps) {
const [recipient, setRecipient] = useState('');
const [depositAmount, setDepositAmount] = useState('');
const [accrualRate, setAccrualRate] = useState('38.62');
Expand All @@ -37,12 +47,12 @@ 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;
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
if (isSubmitting) return;
onClose();
}
if (e.key === 'Tab') {
Expand All @@ -63,7 +73,24 @@ export default function CreateStreamModal({ isOpen, onClose, onStreamCreated }:
};
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, [isOpen, onClose]);
}, [isOpen, isSubmitting, onClose]);

useEffect(() => {
if (!isOpen) {
setIsSubmitting(false);
}
}, [isOpen]);

const buildSubmissionPayload = (): CreateStreamFormData => ({
recipient: recipient.trim(),
depositAmount: parseFloat(depositAmount.replace(/,/g, '')),
accrualRate: parseFloat(accrualRate),
durationInMonths: parseFloat(duration),
startTimeOption,
customStartDate,
cliffEnabled,
cliffDate,
});

const validateStep1 = (): boolean => {
if (!recipient.trim()) {
Expand Down Expand Up @@ -138,12 +165,14 @@ export default function CreateStreamModal({ isOpen, onClose, onStreamCreated }:
setError(null);
setCurrentStep(3);
} else if (currentStep === 3) {
if (isSubmitting) return;
setIsSubmitting(true);
onStreamCreated?.();
setTimeout(() => {
onClose();
setError(null);
onSubmit?.(buildSubmissionPayload());
if (!onSubmit) {
setIsSubmitting(false);
}, 400);
onClose();
}
}
};

Expand All @@ -158,13 +187,14 @@ export default function CreateStreamModal({ isOpen, onClose, onStreamCreated }:
};

const handleCancel = () => {
if (isSubmitting) return;
onClose();
};

if (!isOpen) return null;

return (
<div className="modal-overlay create-stream-overlay" onClick={onClose}>
<div className="modal-overlay create-stream-overlay" onClick={isSubmitting ? undefined : onClose}>
<div
className="modal-content create-stream-modal"
ref={modalRef}
Expand All @@ -175,7 +205,7 @@ export default function CreateStreamModal({ isOpen, onClose, onStreamCreated }:
>
<div className="modal-header">
<h2 id="create-stream-title">Create stream</h2>
<button type="button" className="close-button" onClick={onClose} aria-label="Close modal">
<button type="button" className="close-button" onClick={onClose} aria-label="Close modal" disabled={isSubmitting}>
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
Expand Down Expand Up @@ -370,12 +400,14 @@ export default function CreateStreamModal({ isOpen, onClose, onStreamCreated }:
<label className="form-label">Start time</label>
<div className="segmented-control">
<button
type="button"
className={`segment-btn ${startTimeOption === 'now' ? 'active' : ''}`}
onClick={() => setStartTimeOption('now')}
>
Start now
</button>
<button
type="button"
className={`segment-btn ${startTimeOption === 'custom' ? 'active' : ''}`}
onClick={() => setStartTimeOption('custom')}
>
Expand Down Expand Up @@ -573,7 +605,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
Loading