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
2,092 changes: 1,736 additions & 356 deletions package-lock.json

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@
"@auth/prisma-adapter": "^1.6.0",
"@hookform/resolvers": "^3.9.0",
"@jinglescode/nostr-chat-plugin": "^0.0.11",
"@meshsdk/core": "^1.9.0-beta.18",
"@meshsdk/core-csl": "^1.9.0-beta.18",
"@meshsdk/core-cst": "^1.9.0-beta.19",
"@meshsdk/react": "^1.9.0-beta.18",
"@meshsdk/core": "^1.9.0-beta.77",
"@meshsdk/core-csl": "^1.9.0-beta.77",
"@meshsdk/core-cst": "^1.9.0-beta.77",
"@meshsdk/react": "^1.9.0-beta.77",
"@octokit/core": "^6.1.2",
"@prisma/client": "^6.4.1",
"@radix-ui/react-accordion": "^1.2.0",
Expand Down Expand Up @@ -60,14 +60,14 @@
"clsx": "^2.1.1",
"cors": "^2.8.5",
"dexie": "^4.0.11",
"formidable": "^3.5.2",
"formidable": "^3.5.4",
"framer-motion": "^11.11.9",
"geist": "^1.3.0",
"idb-keyval": "^6.2.1",
"jsonld": "^8.3.3",
"jsonwebtoken": "^9.0.2",
"lucide-react": "^0.439.0",
"next": "^14.2.4",
"next": "^15.2.4",
"next-auth": "^4.24.7",
"papaparse": "^5.5.3",
"react": "^18.3.1",
Expand Down
5 changes: 5 additions & 0 deletions prisma/migrations/20250804021426_add_drep_key/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "drepKeyHash" TEXT NOT NULL DEFAULT '';

-- AlterTable
ALTER TABLE "Wallet" ADD COLUMN "signersDRepKeys" TEXT[];
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "NewWallet" ADD COLUMN "signersDRepKeys" TEXT[];
3 changes: 3 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ model User {
id String @id @default(cuid())
address String @unique
stakeAddress String @unique
drepKeyHash String @default("")
nostrKey String @unique
discordId String @default("")
}
Expand All @@ -24,6 +25,7 @@ model Wallet {
description String?
signersAddresses String[]
signersStakeKeys String[]
signersDRepKeys String[]
signersDescriptions String[]
numRequiredSigners Int?
verified String[]
Expand Down Expand Up @@ -70,6 +72,7 @@ model NewWallet {
description String?
signersAddresses String[]
signersStakeKeys String[]
signersDRepKeys String[]
signersDescriptions String[]
numRequiredSigners Int?
ownerAddress String
Expand Down
4 changes: 2 additions & 2 deletions src/components/common/card-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default function CardUI({
headerDom?: ReactNode;
}) {
return (
<Card className={`self-start ${cardClassName}`}>
<Card className={`w-full max-w-4xl ${cardClassName}`}>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-xl font-medium">{title}</CardTitle>
{headerDom && headerDom}
Expand All @@ -33,7 +33,7 @@ export default function CardUI({
</>
)}
</CardHeader>
<CardContent>
<CardContent className="overflow-y-auto max-h-[calc(100vh-200px)]">
<div className="mt-1 flex flex-col gap-2">
{description && (
<p className="text-sm text-muted-foreground">{description}</p>
Expand Down
171 changes: 128 additions & 43 deletions src/components/common/overall-layout/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import useUser from "@/hooks/useUser";
import { useUserStore } from "@/lib/zustand/user";
import useAppWallet from "@/hooks/useAppWallet";

import SessionProvider from "@/components/SessionProvider"
import SessionProvider from "@/components/SessionProvider";
import { getServerSession } from "next-auth";

import MenuWallets from "@/components/common/overall-layout/menus/wallets";
Expand All @@ -35,26 +35,33 @@ import {
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb";

// Simple error boundary component
class SimpleErrorBoundary extends Component<
// Enhanced error boundary component for wallet errors
class WalletErrorBoundary extends Component<
{ children: ReactNode; fallback: ReactNode },
{ hasError: boolean }
{ hasError: boolean; error: Error | null }
> {
constructor(props: { children: ReactNode; fallback: ReactNode }) {
super(props);
this.state = { hasError: false };
this.state = { hasError: false, error: null };
}

static getDerivedStateFromError() {
return { hasError: true };
static getDerivedStateFromError(error: Error) {
return { hasError: true, error };
}

componentDidCatch(error: Error, errorInfo: any) {
console.error('Error caught by boundary:', error, errorInfo);
console.error('Error caught by wallet boundary:', error, errorInfo);

// Handle specific wallet errors
if (error.message.includes("account changed")) {
console.log("Wallet account changed error caught by boundary, reloading page...");
window.location.reload();
return;
}
}

render() {
if (this.state.hasError) {
if (this.state.hasError && this.state.error && !this.state.error.message.includes("account changed")) {
return this.props.fallback;
}
return this.props.children;
Expand All @@ -75,44 +82,93 @@ export default function RootLayout({
const userAddress = useUserStore((state) => state.userAddress);
const setUserAddress = useUserStore((state) => state.setUserAddress);

// Global error handler for unhandled promise rejections
useEffect(() => {
const handleUnhandledRejection = (event: PromiseRejectionEvent) => {
console.error('Unhandled promise rejection:', event.reason);

// Handle wallet-related errors specifically
if (event.reason && typeof event.reason === 'object') {
const error = event.reason as Error;
if (error.message && error.message.includes("account changed")) {
console.log("Account changed error caught by global handler, reloading page...");
event.preventDefault(); // Prevent the error from being logged to console
window.location.reload();
return;
}
}
};

window.addEventListener('unhandledrejection', handleUnhandledRejection);

return () => {
window.removeEventListener('unhandledrejection', handleUnhandledRejection);
};
}, []);

const { mutate: createUser } = api.user.createUser.useMutation({
onError: (e) => console.error(e),
});
const { mutate: updateUser } = api.user.updateUser.useMutation({
onError: (e) => console.error(e),
});

// Single effect for address + user creation
useEffect(() => {
(async () => {
if (!connected || !wallet) return;

// 1) Set user address in store
let address = (await wallet.getUsedAddresses())[0];
if (!address) address = (await wallet.getUnusedAddresses())[0];
if (address) setUserAddress(address);
try {
// 1) Set user address in store
let address = (await wallet.getUsedAddresses())[0];
if (!address) address = (await wallet.getUnusedAddresses())[0];
if (address) setUserAddress(address);

// 2) If user doesn't exist, create it
if (!isLoading && user === null) {
// 2) Get stake address
const stakeAddress = (await wallet.getRewardAddresses())[0];
if (!stakeAddress || !address) {
console.error("No stake address or payment address found");
return;
}
const nostrKey = generateNsec();
createUser({
address,
stakeAddress,
nostrKey: JSON.stringify(nostrKey),
});

// 3) Get DRep key hash (optional)
let drepKeyHash = "";
try {
const dRepKey = await wallet.getDRep();
console.log("DRep key:", dRepKey);
if (dRepKey && dRepKey.publicKeyHash) {
drepKeyHash = dRepKey.publicKeyHash;
}
} catch (error) {
// DRep key is optional, so we continue without it
console.log("No DRep key available for this wallet");
}

// 4) Create or update user (upsert pattern handles both cases)
if (!isLoading) {
const nostrKey = generateNsec();
createUser({
address,
stakeAddress,
drepKeyHash,
nostrKey: JSON.stringify(nostrKey),
});
}
} catch (error) {
console.error("Error in wallet initialization effect:", error);

// If we get an "account changed" error, reload the page
if (error instanceof Error && error.message.includes("account changed")) {
console.log("Account changed detected, reloading page...");
window.location.reload();
return;
}

// For other errors, don't throw to prevent app crash
// The user can retry by reconnecting their wallet
}
})();
}, [
connected,
wallet,
user,
isLoading,
createUser,
generateNsec,
setUserAddress,
]);
}, [connected, wallet, user, isLoading, createUser, generateNsec, setUserAddress]);

const isWalletPath = router.pathname.includes("/wallets/[wallet]");
const walletPageRoute = router.pathname.split("/wallets/[wallet]/")[1];
Expand All @@ -125,12 +181,18 @@ export default function RootLayout({
{isLoading && <Loading />}

{/* Sidebar for larger screens */}
<aside className="hidden border-r border-gray-200/30 dark:border-white/[0.03] bg-muted/40 md:block">
<aside className="hidden border-r border-gray-200/30 bg-muted/40 dark:border-white/[0.03] md:block">
<div className="flex h-full max-h-screen flex-col">
<header className="flex h-14 items-center border-b border-gray-200/30 dark:border-white/[0.03] px-4 lg:h-16 lg:px-6" id="logo-header" data-header="sidebar">
<header
className="flex h-14 items-center border-b border-gray-200/30 px-4 dark:border-white/[0.03] lg:h-16 lg:px-6"
id="logo-header"
data-header="sidebar"
>
<Link href="/" className="flex items-center gap-3">
<Logo />
<span className="font-medium text-sm md:text-base lg:text-lg tracking-[-0.01em] select-none">Multi-Sig Platform</span>
<span className="select-none text-sm font-medium tracking-[-0.01em] md:text-base lg:text-lg">
Multi-Sig Platform
</span>
</Link>
</header>
<nav className="flex-1 pt-2">
Expand All @@ -143,27 +205,35 @@ export default function RootLayout({

{/* Main content area */}
<div className="flex h-screen flex-col">
<header className="pointer-events-auto relative z-10 border-b border-gray-200/30 dark:border-white/[0.03] bg-muted/40 px-4 lg:px-6" data-header="main">
<header
className="pointer-events-auto relative z-10 border-b border-gray-200/30 bg-muted/40 px-4 dark:border-white/[0.03] lg:px-6"
data-header="main"
>
<div className="flex h-14 items-center gap-4 lg:h-16">
{/* Mobile menu button */}
<MobileNavigation isWalletPath={isWalletPath} />

{/* Logo in mobile header - centered */}
<div className="flex-1 flex justify-center md:hidden">
<Link href="/" className="flex items-center gap-2 px-2 py-1 rounded-md hover:bg-gray-100/50 dark:hover:bg-gray-800/50 transition-colors">
<div className="flex flex-1 justify-center md:hidden">
<Link
href="/"
className="flex items-center gap-2 rounded-md px-2 py-1 transition-colors hover:bg-gray-100/50 dark:hover:bg-gray-800/50"
>
<svg
className="h-7 w-7 text-foreground flex-shrink-0"
className="h-7 w-7 flex-shrink-0 text-foreground"
enableBackground="new 0 0 300 200"
viewBox="0 0 300 200"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
>
<path d="m289 127-45-60-45-60c-.9-1.3-2.4-2-4-2s-3.1.7-4 2l-37 49.3c-2 2.7-6 2.7-8 0l-37-49.3c-.9-1.3-2.4-2-4-2s-3.1.7-4 2l-45 60-45 60c-1.3 1.8-1.3 4.2 0 6l45 60c.9 1.3 2.4 2 4 2s3.1-.7 4-2l37-49.3c2-2.7 6-2.7 8 0l37 49.3c.9 1.3 2.4 2 4 2s3.1-.7 4-2l37-49.3c2-2.7 6-2.7 8 0l37 49.3c.9 1.3 2.4 2 4 2s3.1-.7 4-2l45-60c1.3-1.8 1.3-4.2 0-6zm-90-103.3 32.5 43.3c1.3 1.8 1.3 4.2 0 6l-32.5 43.3c-2 2.7-6 2.7-8 0l-32.5-43.3c-1.3-1.8-1.3-4.2 0-6l32.5-43.3c2-2.7 6-2.7 8 0zm-90 0 32.5 43.3c1.3 1.8 1.3 4.2 0 6l-32.5 43.3c-2 2.7-6 2.7-8 0l-32.5-43.3c-1.3-1.8-1.3-4.2 0-6l32.5-43.3c2-2.7 6-2.7 8 0zm-53 152.6-32.5-43.3c-1.3-1.8-1.3-4.2 0-6l32.5-43.3c2-2.7 6-2.7 8 0l32.5 43.3c1.3 1.8 1.3 4.2 0 6l-32.5 43.3c-2 2.7-6 2.7-8 0zm90 0-32.5-43.3c-1.3-1.8-1.3-4.2 0-6l32.5-43.3c2-2.7 6-2.7 8 0l32.5 43.3c1.3 1.8 1.3 4.2 0 6l-32.5 43.3c-2 2.7-6 2.7-8 0zm90 0-32.5-43.3c-1.3-1.8-1.3-4.2 0-6l32.5-43.3c2-2.7 6-2.7 8 0l32.5 43.3c1.3 1.8 1.3 4.2 0 6l-32.5 43.3c-2 2.7-6 2.7-8 0z" />
</svg>
<span className="text-base font-medium text-foreground whitespace-nowrap">Multi-Sig Platform</span>
<span className="whitespace-nowrap text-base font-medium text-foreground">
Multi-Sig Platform
</span>
</Link>
</div>

{/* Wallet selection + breadcrumb row on desktop */}
{isLoggedIn && (
<div className="hidden md:block">
Expand Down Expand Up @@ -210,7 +280,7 @@ export default function RootLayout({
) : (
<>
{/* Desktop buttons */}
<div className="hidden md:flex items-center space-x-2">
<div className="hidden items-center space-x-2 md:flex">
<WalletDataLoaderWrapper mode="button" />
<DialogReportWrapper mode="button" />
<UserDropDownWrapper mode="button" />
Expand All @@ -220,7 +290,7 @@ export default function RootLayout({
<WalletDataLoaderWrapper mode="menu-item" />
<DialogReportWrapper mode="menu-item" />
<UserDropDownWrapper mode="menu-item" />
<div className="h-px bg-border -mx-2 my-1" />
<div className="-mx-2 my-1 h-px bg-border" />
<LogoutWrapper mode="menu-item" />
</MobileActionsMenu>
</>
Expand All @@ -230,7 +300,22 @@ export default function RootLayout({
</header>

<main className="relative flex flex-1 flex-col gap-4 overflow-y-auto overflow-x-hidden p-4 md:p-8">
{pageIsPublic || userAddress ? children : <PageHomepage />}
<WalletErrorBoundary
fallback={
<div className="flex flex-col items-center justify-center h-full">
<h2 className="text-xl font-semibold mb-2">Something went wrong</h2>
<p className="text-gray-600 mb-4">Please try refreshing the page or reconnecting your wallet.</p>
<button
onClick={() => window.location.reload()}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
Refresh Page
</button>
</div>
}
>
{pageIsPublic || userAddress ? children : <PageHomepage />}
</WalletErrorBoundary>
</main>
</div>
</div>
Expand Down
Loading