Skip to content

Commit

Permalink
add simple server side cache and list sets in the sidebar nav
Browse files Browse the repository at this point in the history
  • Loading branch information
krzysu committed Dec 11, 2024
1 parent b5115e4 commit cb58cde
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 63 deletions.
29 changes: 24 additions & 5 deletions packages/circle-demo-webapp/app/components/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Link, NavLink } from '@remix-run/react';
import { User, Box, LayoutDashboard } from 'lucide-react';
import { User, Box, LayoutDashboard, Database } from 'lucide-react';
import React from 'react';
import type { WalletSet } from 'web3-circle-sdk';

import circleLogo from './circle-logo.svg';

Expand All @@ -26,18 +27,36 @@ function SidebarNavLink({ to, icon, label }: SidebarNavLinkProps) {
);
}

export function Sidebar() {
export interface SidebarProps {
walletSets: WalletSet[];
}

export function Sidebar({ walletSets = [] }: SidebarProps) {
return (
<aside className="bg-white w-64 h-full shadow-md flex flex-col">
<aside className="bg-white w-64 h-full shadow-md flex flex-col overflow-y-auto">
<div className="p-6 max-w-[180px]">
<Link to="/">
<img src={circleLogo} alt="Circle Logo" />
</Link>
</div>
<nav className="flex-1 px-4">
<SidebarNavLink to="/wallets" icon={<LayoutDashboard />} label="Wallet Sets" />
<SidebarNavLink to="/customers" icon={<User />} label="Customers" />
<SidebarNavLink to="/products" icon={<Box />} label="Products" />

{walletSets.length > 0 && (
<div className="mt-12">
<p className="px-4 text-xs font-semibold text-gray-500 mb-2">
All Wallet Sets
</p>
{walletSets.map((set) => (
<SidebarNavLink
key={set.id}
to={`/wallets/${set.id}`}
icon={<Database />}
label={set.name ?? 'Unnamed'}
/>
))}
</div>
)}
</nav>
</aside>
);
Expand Down
41 changes: 41 additions & 0 deletions packages/circle-demo-webapp/app/lib/cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import QuickLRU from 'quick-lru';

// Default cache configuration
const DEFAULT_MAX_SIZE = 100;
const DEFAULT_MAX_AGE = 600000; // 10 minutes in milliseconds

// Single global cache instance
const globalCache = new QuickLRU<string, unknown>({
maxSize: DEFAULT_MAX_SIZE,
maxAge: DEFAULT_MAX_AGE,
});

/**
* Invalidate entire cache namespace
* @param name Cache namespace to invalidate
*/
export function invalidateCache(name: string): void {
globalCache.delete(name);
}

/**
* Cached loader utility
* @param name Cache name
* @param fetchFn Function to fetch data if not in cache
*/
export async function cachedLoader<V>(
name: string,
fetchFn: () => Promise<V>,
): Promise<V> {
// Check cache first
const cachedData = globalCache.get(name);
if (cachedData !== undefined) {
return cachedData as V;
}

// Fetch and cache new data
const freshData = await fetchFn();
globalCache.set(name, freshData);

return freshData;
}
13 changes: 11 additions & 2 deletions packages/circle-demo-webapp/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@ import {
Outlet,
Scripts,
ScrollRestoration,
useLoaderData,
useNavigation,
} from '@remix-run/react';
import { LoaderCircle } from 'lucide-react';

import { cachedLoader } from '~/lib/cache';
import { sdk } from '~/lib/sdk';

import { Sidebar } from './components/Sidebar';

import './tailwind.css';
Expand All @@ -17,6 +21,10 @@ export const meta: MetaFunction = () => {
return [{ title: 'Circle SDK Demo' }];
};

export async function loader() {
return cachedLoader('walletSets', () => sdk.walletSet.list());
}

export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
Expand All @@ -36,11 +44,12 @@ export function Layout({ children }: { children: React.ReactNode }) {
}

export default function App() {
const navigation = useNavigation(); // Hook to track navigation state
const walletSets = useLoaderData<typeof loader>();
const navigation = useNavigation();

return (
<div className="flex h-screen">
<Sidebar />
<Sidebar walletSets={walletSets} />

<div className="flex-1 p-12 overflow-y-auto bg-gray-50 relative">
{navigation.state === 'loading' && (
Expand Down
7 changes: 0 additions & 7 deletions packages/circle-demo-webapp/app/routes/customers.tsx

This file was deleted.

7 changes: 0 additions & 7 deletions packages/circle-demo-webapp/app/routes/products.tsx

This file was deleted.

44 changes: 27 additions & 17 deletions packages/circle-demo-webapp/app/routes/wallets.$id/route.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ActionFunctionArgs } from '@remix-run/node';
import { Link, useLoaderData, useParams } from '@remix-run/react';
import type { BLOCKCHAIN, WalletSet } from 'web3-circle-sdk';

import { Button } from '~/components/ui/button';
import { Card } from '~/components/ui/card';
Expand All @@ -15,14 +16,22 @@ export async function loader({ params }: { params: { id: string } }) {
throw new Error('Wallet Set ID is required');
}

return sdk.wallet.list({ walletSetId: id });
const [wallets, walletSet] = await Promise.all([
sdk.wallet.list({ walletSetId: id }),
sdk.walletSet.get(id),
]);

return {
wallets,
walletSet,
};
}

export async function action({ request }: ActionFunctionArgs) {
const body = await request.formData();
const name = String(body.get('name'));
const walletSetId = String(body.get('walletSetId'));
const blockchain = String(body.get('blockchain'));
const blockchain = String(body.get('blockchain')) as BLOCKCHAIN;

await sdk.wallet.create({
walletSetId,
Expand All @@ -37,9 +46,22 @@ export async function action({ request }: ActionFunctionArgs) {
return null;
}

function Header({ walletSet }: { walletSet: WalletSet }) {
return (
<header className="flex justify-between items-center mb-6">
<div>
<h1 className="text-2xl font-semibold text-gray-900">Wallet Set</h1>
<p>Name: {walletSet.name}</p>
<p>ID: {walletSet.id}</p>
</div>
<NewWalletDialog walletSetId={walletSet.id} />
</header>
);
}

export default function Page() {
const { id } = useParams();
const wallets = useLoaderData<typeof loader>();
const { wallets, walletSet } = useLoaderData<typeof loader>();

if (!id) {
return null;
Expand All @@ -48,13 +70,7 @@ export default function Page() {
if (!wallets.length) {
return (
<div className="space-y-6">
<header className="flex justify-between items-center mb-6">
<div>
<h1 className="text-2xl font-semibold text-gray-900">Wallet Set</h1>
<p>ID: {id}</p>
</div>
<NewWalletDialog walletSetId={id} />
</header>
<Header walletSet={walletSet} />

<h2>No wallets found</h2>
</div>
Expand All @@ -63,13 +79,7 @@ export default function Page() {

return (
<div className="space-y-6">
<header className="flex justify-between items-center mb-6">
<div>
<h1 className="text-2xl font-semibold text-gray-900">Wallet Set</h1>
<p>ID: {id}</p>
</div>
<NewWalletDialog walletSetId={id} />
</header>
<Header walletSet={walletSet} />

<div className="flex flex-wrap items-center gap-6">
{wallets.map((wallet) => (
Expand Down
51 changes: 26 additions & 25 deletions packages/circle-demo-webapp/app/routes/wallets._index/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import { Link, useLoaderData } from '@remix-run/react';
import { Button } from '~/components/ui/button';
import { Card } from '~/components/ui/card';
import { WalletSetDetails } from '~/components/WalletSetDetails/WalletSetDetails';
import { cachedLoader, invalidateCache } from '~/lib/cache';
import { sdk } from '~/lib/sdk';

import { NewWalletSetDialog } from './components/NewWalletSetDialog';

export async function loader() {
return sdk.walletSet.list();
return cachedLoader('walletSets', () => sdk.walletSet.list());
}

export async function action({ request }: ActionFunctionArgs) {
Expand All @@ -20,22 +21,30 @@ export async function action({ request }: ActionFunctionArgs) {
name,
});

invalidateCache('walletSets');

return null;
}

function Header() {
return (
<header className="flex justify-between items-center mb-6">
<div>
<h1 className="text-2xl font-semibold text-gray-900">Wallet Sets</h1>
<p>All wallet sets</p>
</div>
<NewWalletSetDialog />
</header>
);
}

export default function Page() {
const walletSets = useLoaderData<typeof loader>();

if (!walletSets.length) {
return (
<div className="space-y-6">
<header className="flex justify-between items-center mb-6">
<div>
<h1 className="text-2xl font-semibold text-gray-900">Wallet Sets</h1>
<p>All wallet sets</p>
</div>
<NewWalletSetDialog />
</header>
<Header />

<h2>No wallet sets found</h2>
</div>
Expand All @@ -44,25 +53,17 @@ export default function Page() {

return (
<div className="space-y-6">
<header className="flex justify-between items-center mb-6">
<div>
<h1 className="text-2xl font-semibold text-gray-900">Wallet Sets</h1>
<p>All wallet sets</p>
</div>
<NewWalletSetDialog />
</header>
<Header />

<div className="flex flex-wrap items-center gap-6">
<div className="grid grid-cols-1 lg:grid-cols-2 2xl:grid-cols-3 gap-6">
{walletSets.map((set) => (
<div key={set.id} className="flex-1 min-w-[360px]">
<Card className="p-4">
<WalletSetDetails walletSet={set}>
<Button variant="outline" asChild>
<Link to={`/wallets/${set.id}`}>Show Wallets</Link>
</Button>
</WalletSetDetails>
</Card>
</div>
<Card key={set.id} className="p-4">
<WalletSetDetails walletSet={set}>
<Button variant="outline" asChild>
<Link to={`/wallets/${set.id}`}>Show Wallets</Link>
</Button>
</WalletSetDetails>
</Card>
))}
</div>
</div>
Expand Down
1 change: 1 addition & 0 deletions packages/circle-demo-webapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"isbot": "^4.1.0",
"lucide-react": "^0.462.0",
"qrcode.react": "^4.1.0",
"quick-lru": "^7.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tailwind-merge": "^2.5.5",
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7877,6 +7877,11 @@ queue-microtask@^1.2.2:
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==

quick-lru@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-7.0.0.tgz#447f6925b33ae4d2d637e211967d74bae4b99c3f"
integrity sha512-MX8gB7cVYTrYcFfAnfLlhRd0+Toyl8yX8uBx1MrX7K0jegiz9TumwOK27ldXrgDlHRdVi+MqU9Ssw6dr4BNreg==

rambda@^7.4.0:
version "7.5.0"
resolved "https://registry.yarnpkg.com/rambda/-/rambda-7.5.0.tgz#1865044c59bc0b16f63026c6e5a97e4b1bbe98fe"
Expand Down

0 comments on commit cb58cde

Please sign in to comment.