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
23 changes: 23 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

71 changes: 64 additions & 7 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { useEffect, useState, lazy, Suspense } from "react";
import { lazy, Suspense, useEffect, useState } from "react";
import {
BrowserRouter as Router,
Routes,
Route,
Navigate,
Route,
Routes,
} from "react-router-dom";
import * as Sentry from "@sentry/react";
import Navbar from "./components/Navbar";
import ErrorFallback from "./components/ErrorFallback";
import { ThemeProvider } from "./context/ThemeContext";
import { VaultProvider } from "./context/VaultContext";
import { fetchUsdcBalance } from "./lib/stellarAccount";
import "./index.css";
import { KeyboardShortcutProvider } from "./context/KeyboardShortcutContext";
import Navbar from "./components/Navbar";
import ShortcutHelpModal from "./components/ShortcutHelpModal";
Expand All @@ -23,6 +28,11 @@ const Portfolio = lazy(() => import("./pages/Portfolio"));
const Analytics = lazy(() => import("./pages/Analytics"));
const UIPreview = lazy(() => import("./pages/UIPreview"));

const LoadingPage = () => (
<div className="loading-page" role="status" aria-live="polite">
<div style={{ textAlign: "center" }}>
<div className="text-gradient loading-title">Loading...</div>
<div style={{ opacity: 0.7 }}>Securing RWA connection</div>
const LoadingPage = () => {
const { t } = useTranslation();
return (
Expand Down Expand Up @@ -59,7 +69,7 @@ function AppContent() {
const [walletAddress, setWalletAddress] = useState<string | null>(null);
const [usdcBalance, setUsdcBalance] = useState(0);

const handleConnect = async (address: string) => {
const handleConnect = (address: string) => {
setWalletAddress(address);
};

Expand All @@ -76,17 +86,64 @@ function AppContent() {
}

try {
const discoveredBalance = await fetchUsdcBalance(walletAddress);
setUsdcBalance(discoveredBalance);
setUsdcBalance(await fetchUsdcBalance(walletAddress));
} catch {
setUsdcBalance(0);
}
};

loadBalance();
void loadBalance();
}, [walletAddress]);

return (
<Sentry.ErrorBoundary
fallback={({ error, resetError }) => (
<ErrorFallback error={error as Error} resetError={resetError} />
)}
showDialog
>
<ThemeProvider>
<ToastProvider>
<VaultProvider>
<Router>
<a className="skip-link" href="#main-content">
Skip to main content
</a>
<div className="app-container">
<Navbar
walletAddress={walletAddress}
onConnect={handleConnect}
onDisconnect={handleDisconnect}
/>
<main id="main-content" className="container app-main">
<Suspense fallback={<LoadingPage />}>
<SentryRoutes>
<Route
path="/"
element={
<Home
walletAddress={walletAddress}
usdcBalance={usdcBalance}
/>
}
/>
<Route
path="/portfolio"
element={<Portfolio walletAddress={walletAddress} />}
/>
<Route path="/analytics" element={<Analytics />} />
<Route
path="/transactions"
element={<TransactionHistory walletAddress={walletAddress} />}
/>
<Route path="*" element={<Navigate to="/" replace />} />
</SentryRoutes>
</Suspense>
</main>
</div>
</Router>
</VaultProvider>
</ToastProvider>
<KeyboardShortcutProvider>
<div className="app-container">
<Navbar
Expand Down
1 change: 1 addition & 0 deletions frontend/src/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const Navbar: React.FC<NavbarProps> = ({
const { t } = useTranslation();
return (
<nav
aria-label="Primary"
style={{
position: "fixed",
top: 0,
Expand Down
8 changes: 6 additions & 2 deletions frontend/src/components/Pagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ export const Pagination: React.FC<PaginationProps> = ({
</div>

<div className="pagination-controls-wrapper">
<div className="pagination-size-label" aria-live="polite">
Page {page} of {totalPages}
</div>

{onPageSizeChange && (
<div className="pagination-size-selector">
<label htmlFor="pageSizeSelect" className="sr-only">
Expand Down Expand Up @@ -83,7 +87,7 @@ export const Pagination: React.FC<PaginationProps> = ({
className="btn btn-outline pagination-btn-nav"
onClick={() => onPageChange?.(page - 1)}
disabled={page <= 1}
aria-label="Go to previous page"
aria-label="Previous"
>
<ChevronLeft size={16} />
</button>
Expand Down Expand Up @@ -124,7 +128,7 @@ export const Pagination: React.FC<PaginationProps> = ({
className="btn btn-outline pagination-btn-nav"
onClick={() => onPageChange?.(page + 1)}
disabled={page >= totalPages}
aria-label="Go to next page"
aria-label="Next"
>
<ChevronRight size={16} />
</button>
Expand Down
40 changes: 39 additions & 1 deletion frontend/src/components/Tabs.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, { createContext, useContext, useState, useEffect } from "react";
import type { KeyboardEvent, ReactNode } from "react";
import { useSearchParams } from "react-router-dom";
import "./Tabs.css";

interface TabsContextType {
Expand Down Expand Up @@ -36,6 +35,15 @@ function TabsWithUrl({
urlParam = "tab",
children,
className = "",
}: TabsProps) {
const [internalValue, setInternalValue] = useState(defaultValue || "");

const [urlValue, setUrlValue] = useState<string | null>(() => {
if (!syncWithUrl || typeof window === "undefined") {
return null;
}
return new URLSearchParams(window.location.search).get(urlParam);
});
}: Omit<TabsProps, "syncWithUrl">) {
const [searchParams, setSearchParams] = useSearchParams();
const [internalValue, setInternalValue] = useState(defaultValue || "");
Expand All @@ -49,6 +57,31 @@ function TabsWithUrl({
: internalValue;

useEffect(() => {
if (!syncWithUrl || typeof window === "undefined") {
return;
}

const handlePopState = () => {
setUrlValue(new URLSearchParams(window.location.search).get(urlParam));
};

window.addEventListener("popstate", handlePopState);
return () => {
window.removeEventListener("popstate", handlePopState);
};
}, [syncWithUrl, urlParam]);

useEffect(() => {
if (!syncWithUrl || typeof window === "undefined") {
return;
}
if (!urlValue && defaultValue) {
const params = new URLSearchParams(window.location.search);
params.set(urlParam, defaultValue);
window.history.replaceState({}, "", `${window.location.pathname}?${params.toString()}`);
setUrlValue(defaultValue);
}
}, [syncWithUrl, urlValue, defaultValue, urlParam]);
if (!urlValue && defaultValue) {
setSearchParams(
(prev) => {
Expand All @@ -66,6 +99,11 @@ function TabsWithUrl({
setInternalValue(newValue);
}

if (syncWithUrl) {
const params = new URLSearchParams(window.location.search);
params.set(urlParam, newValue);
window.history.replaceState({}, "", `${window.location.pathname}?${params.toString()}`);
setUrlValue(newValue);
setSearchParams(
(prev) => {
const newParams = new URLSearchParams(prev);
Expand Down
Loading
Loading