Skip to content

Commit ba2cbd6

Browse files
committed
Enhance configuration and optimize caching for improved performance
- Updated next.config.js to allow unoptimized images for local proxy API routes and added tree-shaking optimizations. - Introduced bundle analyzer configuration to assist in analyzing the build size when the ANALYZE environment variable is set. - Modified package.json to include a new script for analyzing the build. - Refactored caching strategies in various API routes to optimize performance and reduce external API calls. - Implemented caching middleware for tRPC queries to improve data fetching efficiency. - Enhanced user and wallet data hooks with improved caching and stale time settings.
1 parent fd1703a commit ba2cbd6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1533
-458
lines changed

SECURITY_VULNERABILITIES_ANALYSIS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,3 +203,4 @@ Most vulnerabilities are low-risk for production use, but should be addressed th
203203
- Monitoring for package updates
204204

205205

206+

next.config.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ const config = {
4444
hostname: "gateway.pinata.cloud",
4545
},
4646
],
47+
// Allow unoptimized images for local proxy API routes
48+
unoptimized: false,
4749
},
4850
// Turbopack configuration (Next.js 16+)
4951
// Empty config silences the warning about webpack/turbopack conflict
@@ -56,8 +58,23 @@ const config = {
5658
asyncWebAssembly: true,
5759
layers: true,
5860
};
61+
62+
// Optimize tree-shaking by ensuring proper module resolution
63+
config.optimization = {
64+
...config.optimization,
65+
usedExports: true,
66+
sideEffects: false,
67+
};
68+
5969
return config;
6070
},
6171
};
6272

63-
export default config;
73+
// Bundle analyzer - only enable when ANALYZE env var is set
74+
const withBundleAnalyzer = process.env.ANALYZE === 'true'
75+
? require('@next/bundle-analyzer')({
76+
enabled: true,
77+
})
78+
: (config) => config;
79+
80+
export default withBundleAnalyzer(config);

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
"test": "jest",
1919
"test:watch": "jest --watch",
2020
"test:coverage": "jest --coverage",
21-
"test:ci": "jest --ci --coverage --watchAll=false"
21+
"test:ci": "jest --ci --coverage --watchAll=false",
22+
"analyze": "ANALYZE=true npm run build"
2223
},
2324
"dependencies": {
2425
"@auth/prisma-adapter": "^2.11.1",

prisma/schema.prisma

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ generator client {
33
}
44

55
datasource db {
6-
provider = "postgresql"
7-
url = env("DATABASE_URL")
6+
provider = "postgresql"
7+
url = env("DATABASE_URL")
88
// For Supabase + Vercel serverless optimization:
99
// - DATABASE_URL: Use Supabase's connection pooling URL (Transaction mode)
1010
// Example: postgresql://user:pass@host:6543/db?pgbouncer=true
@@ -55,6 +55,10 @@ model Transaction {
5555
txHash String?
5656
createdAt DateTime @default(now())
5757
updatedAt DateTime @updatedAt
58+
59+
@@index([walletId])
60+
@@index([state])
61+
@@index([walletId, state])
5862
}
5963

6064
model Signable {
@@ -122,6 +126,11 @@ model Proxy {
122126
createdAt DateTime @default(now())
123127
updatedAt DateTime @updatedAt
124128
userId String?
129+
130+
@@index([walletId])
131+
@@index([userId])
132+
@@index([walletId, isActive])
133+
@@index([userId, isActive])
125134
}
126135

127136
model BalanceSnapshot {

src/components/common/cardano-objects/connect-wallet.tsx

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -320,31 +320,31 @@ function ConnectWalletContent({
320320
if (isConnecting) {
321321
return (
322322
<>
323-
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
324-
<span className="font-medium">Connecting...</span>
323+
<Loader2 className="mr-2 h-4 w-4 animate-spin transition-opacity duration-300" />
324+
<span className="font-medium transition-opacity duration-300">Connecting...</span>
325325
</>
326326
);
327327
}
328328
if (isConnected && isLoading) {
329329
return (
330330
<>
331-
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
332-
<span className="font-medium">Loading...</span>
331+
<Loader2 className="mr-2 h-4 w-4 animate-spin transition-opacity duration-300" />
332+
<span className="font-medium transition-opacity duration-300">Loading...</span>
333333
</>
334334
);
335335
}
336336
if (isConnected && user && !userLoading) {
337337
return (
338338
<>
339-
<CheckCircle2 className="mr-2 h-4 w-4" />
340-
<span className="font-medium">{connectedWalletName || "Connected"}</span>
339+
<CheckCircle2 className="mr-2 h-4 w-4 transition-all duration-300" />
340+
<span className="font-medium transition-opacity duration-300">{connectedWalletName || "Connected"}</span>
341341
</>
342342
);
343343
}
344344
return (
345345
<>
346-
<Wallet className="mr-2 h-4 w-4" />
347-
<span className="font-medium">Connect Wallet</span>
346+
<Wallet className="mr-2 h-4 w-4 transition-all duration-300" />
347+
<span className="font-medium transition-opacity duration-300">Connect Wallet</span>
348348
</>
349349
);
350350
};
@@ -355,7 +355,8 @@ function ConnectWalletContent({
355355
<Button
356356
variant="secondary"
357357
className={cn(
358-
"rounded-full px-4 py-2 h-auto transition-all duration-300 ease-in-out",
358+
"rounded-full px-4 py-2 h-auto",
359+
"transition-all duration-300 ease-in-out",
359360
"shadow-sm hover:shadow-md",
360361
"border border-zinc-200 dark:border-zinc-800",
361362
"bg-white dark:bg-zinc-900",

src/components/common/overall-layout/layout.tsx

Lines changed: 81 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect, Component, ReactNode } from "react";
1+
import React, { useEffect, Component, ReactNode, useMemo, useCallback } from "react";
22
import { useRouter } from "next/router";
33
import Link from "next/link";
44
import { useNostrChat } from "@jinglescode/nostr-chat-plugin";
@@ -32,6 +32,7 @@ import { MobileNavigation } from "@/components/ui/mobile-navigation";
3232
import { MobileActionsMenu } from "@/components/ui/mobile-actions-menu";
3333
import { Button } from "@/components/ui/button";
3434
import { Card, CardContent } from "@/components/ui/card";
35+
import { cn } from "@/lib/utils";
3536

3637
// Dynamically import ConnectWallet with SSR disabled to avoid production SSR issues
3738
// Using a version-based key ensures fresh mount on updates, preventing cache issues
@@ -127,23 +128,68 @@ export default function RootLayout({
127128
}, []);
128129

129130
const { mutate: createUser } = api.user.createUser.useMutation({
131+
onMutate: async (variables) => {
132+
// Cancel outgoing refetches
133+
await ctx.user.getUserByAddress.cancel({ address: variables.address });
134+
135+
// Snapshot previous value
136+
const previous = ctx.user.getUserByAddress.getData({ address: variables.address });
137+
138+
// Optimistically update
139+
ctx.user.getUserByAddress.setData(
140+
{ address: variables.address },
141+
(old) => old ?? {
142+
address: variables.address,
143+
stakeAddress: variables.stakeAddress,
144+
drepKeyHash: variables.drepKeyHash ?? "",
145+
nostrKey: variables.nostrKey,
146+
createdAt: new Date(),
147+
updatedAt: new Date(),
148+
}
149+
);
150+
151+
return { previous };
152+
},
153+
onError: (err, variables, context) => {
154+
// Rollback on error
155+
if (context?.previous) {
156+
ctx.user.getUserByAddress.setData({ address: variables.address }, context.previous);
157+
}
158+
console.error("Error creating user:", err);
159+
},
130160
onSuccess: (_, variables) => {
131161
console.log("User created/updated successfully, invalidating user query");
132-
// Invalidate the user query so it refetches the newly created user
162+
// Invalidate to ensure we have the latest data
133163
void ctx.user.getUserByAddress.invalidate({ address: variables.address });
134164
},
135-
onError: (e) => {
136-
console.error("Error creating user:", e);
137-
},
138165
});
139166
const { mutate: updateUser } = api.user.updateUser.useMutation({
167+
onMutate: async (variables) => {
168+
// Cancel outgoing refetches
169+
await ctx.user.getUserByAddress.cancel({ address: variables.address });
170+
171+
// Snapshot previous value
172+
const previous = ctx.user.getUserByAddress.getData({ address: variables.address });
173+
174+
// Optimistically update
175+
ctx.user.getUserByAddress.setData(
176+
{ address: variables.address },
177+
(old) => old ? { ...old, ...variables, updatedAt: new Date() } : old
178+
);
179+
180+
return { previous };
181+
},
182+
onError: (err, variables, context) => {
183+
// Rollback on error
184+
if (context?.previous) {
185+
ctx.user.getUserByAddress.setData({ address: variables.address }, context.previous);
186+
}
187+
console.error("Error updating user:", err);
188+
},
140189
onSuccess: (_, variables) => {
141190
console.log("User updated successfully, invalidating user query");
142191
void ctx.user.getUserByAddress.invalidate({ address: variables.address });
143192
},
144-
onError: (e) => {
145-
console.error("Error updating user:", e);
146-
},
147193
});
148194

149195
// Sync address from hook to store
@@ -196,12 +242,13 @@ export default function RootLayout({
196242
initializeWallet();
197243
}, [connected, activeWallet, user, address, createUser, generateNsec]);
198244

199-
const isWalletPath = router.pathname.includes("/wallets/[wallet]");
200-
const walletPageRoute = router.pathname.split("/wallets/[wallet]/")[1];
201-
const walletPageNames = walletPageRoute ? walletPageRoute.split("/") : [];
202-
const pageIsPublic = publicRoutes.includes(router.pathname);
203-
const isLoggedIn = !!user;
204-
const isHomepage = router.pathname === "/";
245+
// Memoize computed route values
246+
const isWalletPath = useMemo(() => router.pathname.includes("/wallets/[wallet]"), [router.pathname]);
247+
const walletPageRoute = useMemo(() => router.pathname.split("/wallets/[wallet]/")[1], [router.pathname]);
248+
const walletPageNames = useMemo(() => walletPageRoute ? walletPageRoute.split("/") : [], [walletPageRoute]);
249+
const pageIsPublic = useMemo(() => publicRoutes.includes(router.pathname), [router.pathname]);
250+
const isLoggedIn = useMemo(() => !!user, [user]);
251+
const isHomepage = useMemo(() => router.pathname === "/", [router.pathname]);
205252

206253
// Keep track of the last visited wallet to show wallet menu even on other pages
207254
const [lastVisitedWalletId, setLastVisitedWalletId] = React.useState<string | null>(null);
@@ -224,7 +271,7 @@ export default function RootLayout({
224271
}
225272
}, [router.query.wallet, isWalletPath, appWallet, multisigWallet]);
226273

227-
const clearWalletContext = React.useCallback(() => {
274+
const clearWalletContext = useCallback(() => {
228275
setLastVisitedWalletId(null);
229276
setLastVisitedWalletName(null);
230277
setLastWalletStakingEnabled(null);
@@ -237,11 +284,25 @@ export default function RootLayout({
237284
}
238285
}, [isHomepage, lastVisitedWalletId, clearWalletContext]);
239286

240-
const showWalletMenu = isLoggedIn && (isWalletPath || !!lastVisitedWalletId);
287+
// Memoize computed values
288+
const showWalletMenu = useMemo(() => isLoggedIn && (isWalletPath || !!lastVisitedWalletId), [isLoggedIn, isWalletPath, lastVisitedWalletId]);
289+
290+
// Don't show background loading when wallet is connecting or just connected (button already shows spinner)
291+
// The connect button shows a spinner when: connecting OR (connected && (!user || userLoading))
292+
const isConnecting = useMemo(() => String(walletState) === String(WalletState.CONNECTING), [walletState]);
293+
const isButtonShowingSpinner = useMemo(() => isConnecting || (connected && (!user || isLoading)), [isConnecting, connected, user, isLoading]);
294+
const shouldShowBackgroundLoading = useMemo(() => isLoading && !isButtonShowingSpinner, [isLoading, isButtonShowingSpinner]);
295+
296+
// Memoize wallet ID for menu
297+
const walletIdForMenu = useMemo(() => (router.query.wallet as string) || lastVisitedWalletId || undefined, [router.query.wallet, lastVisitedWalletId]);
241298

242299
return (
243300
<div className="flex h-screen w-screen flex-col overflow-hidden">
244-
{isLoading && <Loading />}
301+
{shouldShowBackgroundLoading && (
302+
<div className="fixed inset-0 z-50 transition-opacity duration-300 ease-in-out opacity-100">
303+
<Loading />
304+
</div>
305+
)}
245306

246307
{/* Header - full width, always on top */}
247308
<header
@@ -250,11 +311,11 @@ export default function RootLayout({
250311
>
251312
<div className="flex h-14 items-center gap-4 lg:h-16">
252313
{/* Mobile menu button - hidden only on public homepage (not logged in) */}
253-
{(isLoggedIn || !isHomepage) && (
314+
{(isLoggedIn || !isHomepage) && (
254315
<MobileNavigation
255316
showWalletMenu={showWalletMenu}
256317
isLoggedIn={isLoggedIn}
257-
walletId={router.query.wallet as string || lastVisitedWalletId || undefined}
318+
walletId={walletIdForMenu}
258319
fallbackWalletName={lastVisitedWalletName}
259320
onClearWallet={clearWalletContext}
260321
stakingEnabled={lastWalletStakingEnabled ?? undefined}
@@ -342,7 +403,7 @@ export default function RootLayout({
342403
{showWalletMenu && (
343404
<div className="mt-4">
344405
<MenuWallet
345-
walletId={router.query.wallet as string || lastVisitedWalletId || undefined}
406+
walletId={walletIdForMenu}
346407
stakingEnabled={isWalletPath ? undefined : (lastWalletStakingEnabled ?? undefined)}
347408
/>
348409
</div>

src/components/common/overall-layout/loading.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ export default function Loading() {
22
return (
33
<div
44
role="status"
5-
className="fixed flex h-screen w-screen items-center justify-center"
5+
className="fixed flex h-screen w-screen items-center justify-center bg-background/80 backdrop-blur-sm"
66
>
77
<svg
88
aria-hidden="true"

0 commit comments

Comments
 (0)