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

import { useRef, useState } from "react";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { TxnStep } from "@/types/transaction";
import { TxnProgressSteps } from "./ui/murphy/Txn-Feedback/txn-progress-steps";

export default function TxnProgressStepsPreview() {
const initialSteps: TxnStep[] = [
{
id: "1",
title: "Connect Wallet",
description: "Connect your Solana wallet",
status: "completed",
},
{
id: "2",
title: "Prepare Transaction",
description: "Preparing transaction data",
status: "completed",
},
{
id: "3",
title: "Sign Transaction",
description: "Sign with your wallet",
status: "active",
},
{
id: "4",
title: "Submit to Network",
description: "Broadcasting to Solana",
status: "pending",
},
{
id: "5",
title: "Confirmation",
description: "Waiting for confirmation",
status: "pending",
},
];

const [currentSteps, setCurrentSteps] = useState<TxnStep[]>(initialSteps);
const intervalRef = useRef<NodeJS.Timeout | null>(null);

const updateSteps = (
stepIndex: number,
status: TxnStep["status"]
): TxnStep[] => {
return initialSteps.map((step, index) => {
if (index < stepIndex) return { ...step, status: "completed" };
if (index === stepIndex) return { ...step, status };
return { ...step, status: "pending" };
});
};

const simulateProgress = () => {
if (intervalRef.current) return; // prevent multiple intervals

let step = 0;
intervalRef.current = setInterval(() => {
if (step < initialSteps.length) {
setCurrentSteps(updateSteps(step, "active"));
step++;
} else {
setCurrentSteps(updateSteps(initialSteps.length - 1, "completed"));
clearStimulate();
}
}, 1500);
};

const simulateError = () => {
const activeIndex = currentSteps.findIndex(
(step) => step.status === "active"
);
if (activeIndex === -1) return;

// Stop progression
clearStimulate();

// Set error step
setCurrentSteps((prev) =>
prev.map((step, index) =>
index === activeIndex ? { ...step, status: "error" } : step
)
);
};

const clearStimulate = () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
};

const reset = () => {
clearStimulate();
setCurrentSteps(initialSteps);
};

return (
<div className="space-y-10">
{/* Horizontal */}
<Card className="bg-white dark:bg-neutral-900 border dark:border-neutral-700">
<CardHeader>
<CardTitle className="dark:text-white">Horizontal Layout</CardTitle>
<CardDescription className="dark:text-neutral-400">
Perfect for desktop interfaces and wide layouts
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<TxnProgressSteps
steps={currentSteps}
orientation="horizontal"
className="dark:text-white"
/>
<div className="flex flex-wrap gap-2">
<Button
onClick={simulateProgress}
size="sm"
className="cursor-pointer"
>
Simulate Progress
</Button>
<Button
onClick={simulateError}
variant="destructive"
size="sm"
className="cursor-pointer"
>
Simulate Error
</Button>
<Button
onClick={reset}
variant="outline"
size="sm"
className="cursor-pointer"
>
Reset
</Button>
</div>
</CardContent>
</Card>

{/* Vertical */}
<Card className="bg-white dark:bg-neutral-900 border dark:border-neutral-700">
<CardHeader>
<CardTitle className="dark:text-white">Vertical Layout</CardTitle>
<CardDescription className="dark:text-neutral-400">
Ideal for mobile interfaces and detailed descriptions
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<TxnProgressSteps
steps={currentSteps}
orientation="vertical"
className="dark:text-white"
/>
</CardContent>
</Card>

{/* Step States */}
<Card className="bg-white dark:bg-neutral-900 border dark:border-neutral-700">
<CardHeader>
<CardTitle className="dark:text-white">Step States</CardTitle>
<CardDescription className="dark:text-neutral-400">
Different visual states for transaction steps
</CardDescription>
</CardHeader>
<CardContent className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h4 className="font-medium mb-3 dark:text-white">Success Flow</h4>
<TxnProgressSteps
steps={[
{ id: "1", title: "Connect", status: "completed" },
{ id: "2", title: "Sign", status: "completed" },
{ id: "3", title: "Submit", status: "completed" },
{ id: "4", title: "Confirm", status: "completed" },
]}
orientation="horizontal"
className="dark:text-white"
/>
</div>
<div>
<h4 className="font-medium mb-3 dark:text-white">Error Flow</h4>
<TxnProgressSteps
steps={[
{ id: "1", title: "Connect", status: "completed" },
{ id: "2", title: "Sign", status: "completed" },
{ id: "3", title: "Submit", status: "error" },
{ id: "4", title: "Confirm", status: "pending" },
]}
orientation="horizontal"
className="dark:text-white"
/>
</div>
</CardContent>
</Card>
</div>
);
}
83 changes: 83 additions & 0 deletions components/ui/murphy/Txn-Feedback/txn-progress-steps.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"use client";

import { Check, Loader2, X } from "lucide-react";
import { cn } from "@/lib/utils";
import type { TxnStep } from "@/types/transaction";

interface TxnProgressStepsProps {
steps: TxnStep[];
orientation?: "horizontal" | "vertical";
className?: string;
}

export function TxnProgressSteps({
steps,
orientation = "horizontal",
className,
}: TxnProgressStepsProps) {
const isVertical = orientation === "vertical";

return (
<div
className={cn(
"flex",
isVertical ? "flex-col space-y-4" : "items-center space-x-4",
className
)}
>
{steps.map((step, index) => (
<div
key={step.id}
className={cn(
"flex items-center",
isVertical ? "w-full" : "flex-col text-center"
)}
>
<div
className={cn(
"flex items-center justify-center w-8 h-8 rounded-full border-2 transition-all",
step.status === "completed" &&
"bg-green-500 border-green-500 text-white",
step.status === "active" &&
"bg-blue-500 border-blue-500 text-white",
step.status === "error" && "bg-red-500 border-red-500 text-white",
step.status === "pending" && "border-gray-300 text-gray-400"
)}
>
{step.status === "completed" && <Check className="w-4 h-4" />}
{step.status === "active" && (
<Loader2 className="w-4 h-4 animate-spin" />
)}
{step.status === "error" && <X className="w-4 h-4" />}
{step.status === "pending" && (
<span className="text-sm font-medium">{index + 1}</span>
)}
</div>

<div className={cn("ml-3", !isVertical && "ml-0 mt-2")}>
<div
className={cn(
"text-sm font-medium",
step.status === "completed" && "text-green-600",
step.status === "active" && "text-blue-600",
step.status === "error" && "text-red-600",
step.status === "pending" && "text-gray-500"
)}
>
{step.title}
</div>
{step.description && (
<div className="text-xs text-gray-500 mt-1">
{step.description}
</div>
)}
</div>

{!isVertical && index < steps.length - 1 && (
<div className="flex-1 h-px bg-gray-200 mx-4" />
)}
</div>
))}
</div>
);
}
7 changes: 4 additions & 3 deletions components/ui/murphy/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import CandyMachineForm from "./candy-machine-form";
import CoreCandyMachineForm from "./core-candy-machine-form";
import BubblegumLegacyForm from "./bubblegum-legacy-form";
import ImprovedCNFTManager from "./improved-cnft-manager";
import CompressedNFTViewer from "./compressed-nft-viewer"
import CompressedNFTViewer from "./compressed-nft-viewer";
import { CreateMerkleTree } from "./create-merkleTree-form";
import { TokenList } from "./token-list";
import { StakeForm } from "./stake-token-form";
Expand All @@ -37,7 +37,7 @@ import { CoreAssetLaunchpad } from "./core-asset-launchpad";
import { HydraFanoutForm } from "./hydra-fanout-form";
import { MPLHybridForm } from "./mpl-hybrid-form";
import { TokenMetadataViewer } from "./token-metadata-viewer";

import { TxnProgressSteps } from "@/components/ui/murphy/Txn-Feedback/txn-progress-steps";
export {
ConnectWalletButton,
SendTokenForm,
Expand Down Expand Up @@ -78,5 +78,6 @@ export {
TMLaunchpadForm,
HydraFanoutForm,
MPLHybridForm,
TokenMetadataViewer
TokenMetadataViewer,
TxnProgressSteps,
};
Loading