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
129 changes: 129 additions & 0 deletions components/txn-error-fallback-preview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
"use client";

import { useState } from "react";
import { Button } from "@/components/ui/button";
import { TxnErrorFallback } from "./ui/murphy/Txn-Feedback/txn-error-fallback";
import { TxnRetryButton } from "./ui/murphy/Txn-Feedback/txn-retry-button";
import { TxnFeedbackToast } from "./ui/murphy";
import type { TransactionStatus } from "@/types/transaction";

// ===== Types =====
type ErrorType = "simple" | "with-signature" | "with-logs";

type ErrorExample = {
error: string;
signature?: string;
showLogs?: boolean;
logs?: string[];
};

// ===== Data =====
const errorExamples: Record<ErrorType, ErrorExample> = {
simple: {
error: "Transaction failed: Insufficient funds for transaction fees",
},
"with-signature": {
error: "Transaction failed: Program error occurred during execution",
signature:
"5VfYmGC9L8ty3D4HutfxndoKXGBwXJWKKvxgF7qQzqK8xMjU9v7Rw2sP3nT6hL4jK9mN8bC1dF2eG3hI5jK6lM7n",
},
"with-logs": {
error: "Transaction failed: Custom program error: 0x1771",
signature:
"2B5VfYmGC9L8ty3D4HutfxndoKXGBwXJWKKvxgF7qQzqK8xMjU9v7Rw2sP3nT6hL4jK9mN8bC1dF2eG3hI5jK6lM",
showLogs: true,
logs: [
"Program 11111111111111111111111111111111 invoke [1]",
"Program log: Instruction: Transfer",
"Program log: Error: custom program error: 0x1771",
"Program 11111111111111111111111111111111 consumed 200000 of 200000 compute units",
"Program 11111111111111111111111111111111 failed: custom program error: 0x1771",
],
},
};

export default function TxnErrorFallbackPreview() {
const [showError, setShowError] = useState(false);
const [errorType, setErrorType] = useState<ErrorType>("simple");

const [currentErrorMsg, setCurrentErrorMsg] = useState<string>("");

const [toastStatus, setToastStatus] = useState<TransactionStatus>({
status: "idle",
});

const retryTxn = async () => {
await new Promise((r) => setTimeout(r, 800));
const ok = Math.random() > 0.5;

if (ok) {
const sig = errorExamples[errorType].signature;
setShowError(false);
setToastStatus({ status: "success", signature: sig });
return;
}

setToastStatus({
status: "error",
error: currentErrorMsg || errorExamples[errorType].error,
});
throw new Error("Simulated transaction failure");
};

const openWithType = (type: ErrorType) => {
setErrorType(type);
setCurrentErrorMsg(errorExamples[type].error);
setShowError(true);
};

const closeToast = () => setToastStatus({ status: "idle" });

return (
<div className="p-6 max-w-2xl mx-auto space-y-6">
<div className="space-y-3">
<Button
onClick={() => openWithType("simple")}
variant="destructive"
className="w-full"
>
Simple Error
</Button>
<Button
onClick={() => openWithType("with-signature")}
variant="destructive"
className="w-full"
>
Error with Signature
</Button>
<Button
onClick={() => openWithType("with-logs")}
variant="destructive"
className="w-full"
>
Error with Logs
</Button>
</div>

{showError && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
<div className="w-full max-w-md space-y-3">
<TxnErrorFallback
{...errorExamples[errorType]}
onClose={() => setShowError(false)}
/>
<TxnRetryButton
onRetry={retryTxn}
maxRetries={3}
retryDelay={1000}
className="w-full"
>
Try Again
</TxnRetryButton>
</div>
</div>
)}

<TxnFeedbackToast status={toastStatus} onClose={closeToast} />
</div>
);
}
112 changes: 112 additions & 0 deletions components/txn-feedback-toast-preview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
"use client";

import { useState } from "react";
import { Button } from "@/components/ui/button";
import type { TransactionStatus } from "@/types/transaction";
import { TxnFeedbackToast } from "@/components/ui/murphy";

export default function TxnFeedbackToastPreview() {
const [txStatus, setTxStatus] = useState<TransactionStatus>({
status: "idle",
});

const showToast = (
status: TransactionStatus["status"],
error?: string,
signature?: string
) => {
setTxStatus({ status, error, signature });
};

const simulateTransaction = async () => {
const statuses: TransactionStatus["status"][] = [
"preparing",
"signing",
"sending",
"confirming",
];

for (const status of statuses) {
setTxStatus({ status });
await new Promise((resolve) => setTimeout(resolve, 1500));
}

if (Math.random() > 0.3) {
setTxStatus({
status: "success",
signature:
"5VfYmGC9L8ty3D4HutfxndoKXGBwXJWKKvxgF7qQzqK8xMjU9v7Rw2sP3nT6hL4jK9mN8bC1dF2eG3hI5jK6lM7n",
});
} else {
setTxStatus({
status: "error",
error: "Transaction failed: Insufficient funds for transaction fees",
});
}
};

return (
<div className="container mx-auto px-4 py-6 max-w-4xl">
{/* Controls */}
<div className="space-y-6">
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
{["preparing", "signing", "sending", "confirming"].map((status) => (
<Button
key={status}
onClick={() => showToast(status as TransactionStatus["status"])}
variant="outline"
size="sm"
>
{status.charAt(0).toUpperCase() + status.slice(1)}
</Button>
))}
</div>

<div className="grid grid-cols-2 gap-2">
<Button
onClick={() =>
showToast(
"success",
undefined,
"5VfYmGC9L8ty3D4HutfxndoKXGBwXJWKKvxgF7qQzqK8xMjU9v7Rw2sP3nT6hL4jK9mN8bC1dF2eG3hI5jK6lM7n"
)
}
className="bg-green-600 hover:bg-green-700 text-white"
size="sm"
>
Success
</Button>
<Button
onClick={() =>
showToast("error", "Transaction failed: Insufficient funds")
}
variant="destructive"
size="sm"
>
Error
</Button>
</div>

<Button onClick={simulateTransaction} className="w-full">
Simulate Full Transaction
</Button>

<Button
onClick={() => setTxStatus({ status: "idle" })}
variant="ghost"
size="sm"
className="w-full"
>
Clear Toast
</Button>
</div>

{/* Toast */}
<TxnFeedbackToast
status={txStatus}
onRetry={simulateTransaction}
onClose={() => setTxStatus({ status: "idle" })}
/>
</div>
);
}
84 changes: 84 additions & 0 deletions components/txn-retry-button-preview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"use client";

import { useState } from "react";
import { TxnFeedbackToast } from "./ui/murphy";
import type { TransactionStatus } from "@/types/transaction";
import { TxnRetryButton } from "./ui/murphy/Txn-Feedback/txn-retry-button";

export default function TxnRetryButtonPreview() {
const [toastStatus, setToastStatus] = useState<TransactionStatus>({
status: "idle",
});

const simulateTransaction = async () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
const success = Math.random() < 0.6;

if (success) {
setToastStatus({
status: "success",
});
} else {
setToastStatus({
status: "error",
});
throw new Error("Simulated transaction failure");
}
};

const closeToast = () => {
setToastStatus({ status: "idle" });
};

return (
<div className="min-h-screen flex flex-col items-center justify-center px-4 py-8 space-y-10">
<div className="w-full max-w-md space-y-6">
<div className="space-y-2">
<h4 className="font-medium text-lg text-gray-900 dark:text-gray-100">
Standard Retry (3 attempts)
</h4>
<TxnRetryButton
onRetry={simulateTransaction}
maxRetries={3}
retryDelay={1000}
className="w-full"
>
Retry Transaction
</TxnRetryButton>
</div>

<div className="space-y-2">
<h4 className="font-medium text-lg text-gray-900 dark:text-gray-100">
Quick Retry (5 attempts, 500ms delay)
</h4>
<TxnRetryButton
onRetry={simulateTransaction}
maxRetries={5}
retryDelay={500}
variant="outline"
className="w-full"
>
Quick Retry
</TxnRetryButton>
</div>

<div className="space-y-2">
<h4 className="font-medium text-lg text-gray-900 dark:text-gray-100">
Single Retry (1 attempt)
</h4>
<TxnRetryButton
onRetry={simulateTransaction}
maxRetries={1}
retryDelay={2000}
variant="secondary"
className="w-full"
>
Single Retry
</TxnRetryButton>
</div>
</div>

<TxnFeedbackToast status={toastStatus} onClose={closeToast} />
</div>
);
}
Loading