Skip to content

Commit 83e55ef

Browse files
committed
Enhance transaction components and UI layout
- Refactored DiscordIcon to accept a className prop for better styling flexibility. - Updated CardUI to remove unnecessary self-start class, improving layout consistency. - Adjusted AllTransactions component to ensure full-width table display and improved overflow handling. - Enhanced TransactionCard with collapsible signers list and progress visualization for signing thresholds, improving user interaction and clarity. - Updated index.tsx to adjust maximum width settings for better responsiveness based on pending transactions count.
1 parent cf5ac82 commit 83e55ef

File tree

5 files changed

+149
-146
lines changed

5 files changed

+149
-146
lines changed

src/components/common/discordIcon.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1-
export default function DiscordIcon() {
1+
interface DiscordIconProps {
2+
className?: string;
3+
}
4+
5+
export default function DiscordIcon({ className }: DiscordIconProps) {
26
return (
37
<svg
48
xmlns="http://www.w3.org/2000/svg"
59
width="16"
610
height="16"
711
fill="currentColor"
812
viewBox="0 0 16 16"
13+
className={className}
914
>
1015
<path d="M13.545 2.907a13.2 13.2 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.2 12.2 0 0 0-3.658 0 8 8 0 0 0-.412-.833.05.05 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.04.04 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032q.003.022.021.037a13.3 13.3 0 0 0 3.995 2.02.05.05 0 0 0 .056-.019q.463-.63.818-1.329a.05.05 0 0 0-.01-.059l-.018-.011a9 9 0 0 1-1.248-.595.05.05 0 0 1-.02-.066l.015-.019q.127-.095.248-.195a.05.05 0 0 1 .051-.007c2.619 1.196 5.454 1.196 8.041 0a.05.05 0 0 1 .053.007q.121.1.248.195a.05.05 0 0 1-.004.085 8 8 0 0 1-1.249.594.05.05 0 0 0-.03.03.05.05 0 0 0 .003.041c.24.465.515.909.817 1.329a.05.05 0 0 0 .056.019 13.2 13.2 0 0 0 4.001-2.02.05.05 0 0 0 .021-.037c.334-3.451-.559-6.449-2.366-9.106a.03.03 0 0 0-.02-.019m-8.198 7.307c-.789 0-1.438-.724-1.438-1.612s.637-1.613 1.438-1.613c.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612m5.316 0c-.788 0-1.438-.724-1.438-1.612s.637-1.613 1.438-1.613c.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612" />
1116
</svg>

src/components/pages/wallet/transactions/all-transactions.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import LinkCardanoscan from "@/components/common/link-cardanoscan";
1212
import { Wallet } from "@/types/wallet";
1313
import useAllTransactions from "@/hooks/useAllTransactions";
1414
import { dateToFormatted, getFirstAndLast, lovelaceToAda, truncateTokenSymbol } from "@/utils/strings";
15-
import CardUI from "@/components/common/card-content";
15+
import CardUI from "@/components/ui/card-content";
1616
import { OnChainTransaction } from "@/types/transaction";
1717
import { useWalletsStore } from "@/lib/zustand/wallets";
1818
import { Transaction } from "@prisma/client";
@@ -71,8 +71,9 @@ export default function AllTransactions({ appWallet }: { appWallet: Wallet }) {
7171
</div>
7272

7373
{/* Desktop: Compact table without horizontal scroll */}
74-
<div className="hidden lg:block">
75-
<Table>
74+
<div className="hidden lg:block w-full overflow-visible">
75+
<div className="w-full">
76+
<Table className="w-full">
7677
<TableHeader>
7778
<TableRow>
7879
<TableHead className="w-[30%]">Transaction</TableHead>
@@ -96,6 +97,7 @@ export default function AllTransactions({ appWallet }: { appWallet: Wallet }) {
9697
))}
9798
</TableBody>
9899
</Table>
100+
</div>
99101
</div>
100102
</CardUI>
101103
);

src/components/pages/wallet/transactions/index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ export default function PageTransactions() {
2727
<div
2828
className={`grid gap-4 w-full ${
2929
pendingTransactions.length === 1
30-
? "max-w-2xl"
30+
? "max-w-4xl"
3131
: pendingTransactions.length === 2
32-
? "max-w-5xl sm:grid-cols-2"
33-
: "max-w-7xl sm:grid-cols-2 lg:grid-cols-3"
32+
? "max-w-7xl sm:grid-cols-2"
33+
: "max-w-[90rem] sm:grid-cols-2 lg:grid-cols-3"
3434
}`}
3535
>
3636
{pendingTransactions.map((tx) => {

src/components/pages/wallet/transactions/transaction-card.tsx

Lines changed: 134 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { Transaction } from "@prisma/client";
2020

2121
import { TooltipProvider, TooltipTrigger } from "@radix-ui/react-tooltip";
2222

23-
import { Check, Loader, MoreVertical, X, User, Copy, CheckCircle2, XCircle, MinusCircle, Vote } from "lucide-react";
23+
import { Check, Loader, MoreVertical, X, User, Copy, CheckCircle2, XCircle, MinusCircle, Vote, ChevronDown, ChevronUp } from "lucide-react";
2424
import { ToastAction } from "@/components/ui/toast";
2525
import { Tooltip, TooltipContent } from "@/components/ui/tooltip";
2626
import DiscordIcon from "@/components/common/discordIcon";
@@ -43,6 +43,11 @@ import {
4343
DropdownMenuSeparator,
4444
DropdownMenuTrigger,
4545
} from "@/components/ui/dropdown-menu";
46+
import {
47+
Collapsible,
48+
CollapsibleContent,
49+
CollapsibleTrigger,
50+
} from "@/components/ui/collapsible";
4651
import { get } from "http";
4752
import { getProvider } from "@/utils/get-provider";
4853
import { useSiteStore } from "@/lib/zustand/site";
@@ -59,6 +64,7 @@ export default function TransactionCard({
5964
const userAddress = useUserStore((state) => state.userAddress);
6065
const txJson = JSON.parse(transaction.txJson);
6166
const [loading, setLoading] = useState<boolean>(false);
67+
const [isSignersOpen, setIsSignersOpen] = useState<boolean>(false);
6268
const { toast } = useToast();
6369
const ctx = api.useUtils();
6470
const network = useSiteStore((state) => state.network);
@@ -380,26 +386,52 @@ export default function TransactionCard({
380386
}
381387

382388
if (!appWallet) return <></>;
389+
390+
// Calculate signing threshold info
391+
const signersCount = appWallet.signersAddresses.length;
392+
const requiredSigners = appWallet.numRequiredSigners ?? signersCount;
393+
const signedCount = transaction.signedAddresses.length;
394+
const rejectedCount = transaction.rejectedAddresses.length;
395+
396+
const getSignersText = () => {
397+
if (appWallet.type === 'all') {
398+
return `All ${signersCount} signers required`;
399+
} else if (appWallet.type === 'any') {
400+
return `Any of ${signersCount} signers`;
401+
} else {
402+
return `${requiredSigners} of ${signersCount} signers`;
403+
}
404+
};
405+
406+
const getRequiredCount = () => {
407+
if (appWallet.type === 'all') {
408+
return signersCount;
409+
} else if (appWallet.type === 'any') {
410+
return 1;
411+
} else {
412+
return requiredSigners;
413+
}
414+
};
415+
416+
const requiredCount = getRequiredCount();
417+
const isComplete = signedCount >= requiredCount;
418+
const progressPercentage = Math.min((signedCount / signersCount) * 100, 100);
419+
const thresholdPercentage = (requiredCount / signersCount) * 100;
420+
const pendingCount = signersCount - signedCount - rejectedCount;
421+
383422
return (
384-
<Card className="self-start overflow-hidden">
385-
<CardHeader className="flex flex-row items-start bg-muted/50 p-4 sm:p-6">
386-
<div className="grid gap-0.5 flex-1 min-w-0 pr-2">
387-
<CardTitle className="group flex items-center gap-2 text-base sm:text-lg break-words">
388-
{transaction.description}
389-
{/* <Button
390-
size="icon"
391-
variant="outline"
392-
className="h-6 w-6 opacity-0 transition-opacity group-hover:opacity-100"
393-
>
394-
<Copy className="h-3 w-3" />
395-
<span className="sr-only">Copy Order ID</span>
396-
</Button> */}
397-
</CardTitle>
398-
<CardDescription className="text-xs sm:text-sm">
399-
{dateToFormatted(transaction.createdAt)}
400-
</CardDescription>
401-
</div>
402-
<div className="ml-auto flex items-center gap-1 flex-shrink-0">
423+
<Card className="self-start overflow-hidden w-full">
424+
<CardHeader className="flex flex-col gap-3 bg-muted/50 p-4 sm:p-6">
425+
<div className="flex flex-row items-start w-full">
426+
<div className="grid gap-0.5 flex-1 min-w-0 pr-2">
427+
<CardTitle className="group flex items-center gap-2 text-base sm:text-lg break-words">
428+
{transaction.description}
429+
</CardTitle>
430+
<CardDescription className="text-xs sm:text-sm">
431+
{dateToFormatted(transaction.createdAt)}
432+
</CardDescription>
433+
</div>
434+
<div className="ml-auto flex items-center gap-1 flex-shrink-0">
403435
{/* <Button size="sm" variant="outline" className="h-8 gap-1">
404436
<Truck className="h-3.5 w-3.5" />
405437
<span className="lg:sr-only xl:not-sr-only xl:whitespace-nowrap">
@@ -456,9 +488,73 @@ export default function TransactionCard({
456488
</DropdownMenuContent>
457489
</DropdownMenu>
458490
</div>
491+
</div>
492+
493+
{/* Signing Threshold - Person Icons with Progress Bar */}
494+
<div className="w-full pt-3 border-t border-border/30">
495+
<div className="space-y-3">
496+
<div className="text-xs font-medium text-muted-foreground">
497+
{getSignersText()}
498+
</div>
499+
<div className="flex items-center gap-1.5">
500+
{Array.from({ length: signersCount }).map((_, index) => {
501+
let iconColor = "text-muted-foreground opacity-30"; // Light gray (not required, not signed)
502+
503+
if (index < signedCount) {
504+
iconColor = "text-green-500 dark:text-green-400"; // Green (signed, starting from left)
505+
} else if (index < requiredCount) {
506+
iconColor = "text-foreground opacity-100"; // White (threshold requirement, not signed)
507+
}
508+
509+
return (
510+
<User
511+
key={index}
512+
className={`h-5 w-5 sm:h-6 sm:w-6 ${iconColor}`}
513+
/>
514+
);
515+
})}
516+
</div>
517+
{/* Progress Bar */}
518+
<TooltipProvider>
519+
<Tooltip>
520+
<TooltipTrigger asChild>
521+
<div className="h-2.5 bg-muted rounded-full overflow-visible shadow-inner relative cursor-help">
522+
<div
523+
className="absolute top-0 bottom-0 w-0.5 bg-foreground/40 z-10"
524+
style={{ left: `${thresholdPercentage}%` }}
525+
>
526+
<div className="absolute -top-0.5 left-1/2 -translate-x-1/2 w-1 h-1 rounded-full bg-foreground/60" />
527+
</div>
528+
<div
529+
className={`h-full transition-all duration-500 ease-out relative rounded-full ${
530+
isComplete
531+
? "bg-gradient-to-r from-green-500 to-green-600 shadow-sm shadow-green-500/50"
532+
: "bg-gradient-to-r from-primary to-primary/90"
533+
}`}
534+
style={{ width: `${progressPercentage}%` }}
535+
/>
536+
</div>
537+
</TooltipTrigger>
538+
<TooltipContent>
539+
<div className="space-y-1 text-xs">
540+
<div className="font-semibold">{getSignersText()}</div>
541+
<div className="text-muted-foreground">
542+
{signedCount} signed
543+
{rejectedCount > 0 && ` • ${rejectedCount} rejected`}
544+
{pendingCount > 0 && ` • ${pendingCount} pending`}
545+
</div>
546+
<div className="text-muted-foreground">
547+
Progress: {signedCount} / {requiredCount} required
548+
</div>
549+
</div>
550+
</TooltipContent>
551+
</Tooltip>
552+
</TooltipProvider>
553+
</div>
554+
</div>
459555
</CardHeader>
460556
<CardContent className="p-4 sm:p-6 text-sm">
461-
<div className="grid gap-3 sm:gap-4">
557+
<div className="grid gap-3 sm:gap-4 max-w-4xl mx-auto w-full">
462558
{txJson.outputs.length > 0 && (
463559
<>
464560
<div className="space-y-3">
@@ -557,118 +653,27 @@ export default function TransactionCard({
557653
</>
558654
)}
559655

560-
{/* Signer Threshold Visualization */}
561-
{(() => {
562-
const signersCount = appWallet.signersAddresses.length;
563-
const requiredSigners = appWallet.numRequiredSigners ?? signersCount;
564-
const signedCount = transaction.signedAddresses.length;
565-
const rejectedCount = transaction.rejectedAddresses.length;
566-
567-
const getSignersText = () => {
568-
if (appWallet.type === 'all') {
569-
return `All ${signersCount} signers required`;
570-
} else if (appWallet.type === 'any') {
571-
return `Any of ${signersCount} signers`;
572-
} else {
573-
return `${requiredSigners} of ${signersCount} signers`;
574-
}
575-
};
576-
577-
const getRequiredCount = () => {
578-
if (appWallet.type === 'all') {
579-
return signersCount;
580-
} else if (appWallet.type === 'any') {
581-
return 1;
582-
} else {
583-
return requiredSigners;
584-
}
585-
};
586-
587-
const requiredCount = getRequiredCount();
588-
const isComplete = signedCount >= requiredCount;
589-
const progressPercentage = Math.min((signedCount / signersCount) * 100, 100);
590-
const thresholdPercentage = (requiredCount / signersCount) * 100;
591-
const pendingCount = signersCount - signedCount - rejectedCount;
592-
593-
return (
594-
<div className="space-y-4">
595-
{/* Signing Threshold Section */}
596-
<div className="rounded-lg border border-border/50 bg-muted/30 p-4 space-y-3">
597-
<div className="space-y-2">
598-
<div>
599-
<div className="text-xs font-semibold text-muted-foreground uppercase tracking-wide mb-1.5">
600-
Signing Threshold
601-
</div>
602-
<div className="text-base font-semibold">{getSignersText()}</div>
603-
</div>
604-
605-
{/* Progress Bar */}
606-
<div className="space-y-2">
607-
<div className="flex items-center justify-between text-xs">
608-
<span className="text-muted-foreground font-medium">Progress</span>
609-
<span className="font-semibold">
610-
{requiredCount} out of {signersCount}
611-
</span>
612-
</div>
613-
<div className="h-2.5 bg-muted rounded-full overflow-visible shadow-inner relative">
614-
{/* Threshold indicator line */}
615-
<div
616-
className="absolute top-0 bottom-0 w-0.5 bg-foreground/40 z-10"
617-
style={{ left: `${thresholdPercentage}%` }}
618-
>
619-
<div className="absolute -top-1 left-1/2 -translate-x-1/2 w-1 h-1 rounded-full bg-foreground/60" />
620-
</div>
621-
{/* Progress fill */}
622-
<div
623-
className={`h-full transition-all duration-500 ease-out relative rounded-full ${
624-
isComplete
625-
? "bg-gradient-to-r from-green-500 to-green-600 shadow-sm shadow-green-500/50"
626-
: "bg-gradient-to-r from-primary to-primary/90"
627-
}`}
628-
style={{ width: `${progressPercentage}%` }}
629-
>
630-
{progressPercentage > 0 && (
631-
<div className="absolute inset-0 bg-white/20 animate-pulse rounded-full" />
632-
)}
633-
</div>
634-
</div>
635-
</div>
636-
637-
{/* Status Summary */}
638-
<div className="flex flex-wrap items-center gap-3 pt-1">
639-
<div className="flex items-center gap-2">
640-
<div className="h-2.5 w-2.5 rounded-full bg-green-500 shadow-sm shadow-green-500/50" />
641-
<span className="text-xs text-muted-foreground font-medium">
642-
{signedCount} signed
643-
</span>
644-
</div>
645-
{rejectedCount > 0 && (
646-
<div className="flex items-center gap-2">
647-
<div className="h-2.5 w-2.5 rounded-full bg-red-500 shadow-sm shadow-red-500/50" />
648-
<span className="text-xs text-muted-foreground font-medium">
649-
{rejectedCount} rejected
650-
</span>
651-
</div>
652-
)}
653-
{pendingCount > 0 && (
654-
<div className="flex items-center gap-2">
655-
<div className="h-2.5 w-2.5 rounded-full bg-muted-foreground/50 border border-muted-foreground/30" />
656-
<span className="text-xs text-muted-foreground font-medium">
657-
{pendingCount} pending
658-
</span>
659-
</div>
660-
)}
661-
</div>
662-
</div>
663-
656+
{/* Signers List - Collapsible */}
657+
<Collapsible open={isSignersOpen} onOpenChange={setIsSignersOpen}>
658+
<CollapsibleTrigger className="flex items-center justify-between w-full p-3 rounded-lg border border-border/50 bg-muted/30 hover:bg-muted/50 transition-colors group">
659+
<div className="text-xs font-semibold text-muted-foreground uppercase tracking-wide">
660+
Signers ({signersCount})
661+
</div>
662+
{isSignersOpen ? (
663+
<ChevronUp className="h-4 w-4 text-muted-foreground transition-transform" />
664+
) : (
665+
<ChevronDown className="h-4 w-4 text-muted-foreground transition-transform" />
666+
)}
667+
</CollapsibleTrigger>
668+
<CollapsibleContent className="mt-3 space-y-3">
664669
{/* Remind All Button */}
665670
{pendingCount > 0 && (
666-
<div className="pt-3 border-t border-border/50">
671+
<div className="pb-2">
667672
<Button
668673
size="sm"
669674
variant="outline"
670675
onClick={handleRemindAll}
671-
className="w-full sm:w-auto hover:bg-primary/10 hover:border-primary/50 transition-colors"
676+
className="w-full hover:bg-primary/10 hover:border-primary/50 transition-colors"
672677
>
673678
<div className="flex flex-row items-center gap-1.5">
674679
<DiscordIcon className="h-3.5 w-3.5" />
@@ -677,13 +682,6 @@ export default function TransactionCard({
677682
</Button>
678683
</div>
679684
)}
680-
</div>
681-
682-
{/* Signers List */}
683-
<div className="space-y-3">
684-
<div className="text-xs font-semibold text-muted-foreground uppercase tracking-wide px-1">
685-
Signers
686-
</div>
687685
{appWallet.signersAddresses.map((signerAddress, index) => {
688686
const hasSigned = transaction.signedAddresses.includes(signerAddress);
689687
const hasRejected = transaction.rejectedAddresses.includes(signerAddress);
@@ -782,10 +780,8 @@ export default function TransactionCard({
782780
</div>
783781
);
784782
})}
785-
</div>
786-
</div>
787-
);
788-
})()}
783+
</CollapsibleContent>
784+
</Collapsible>
789785
</div>
790786
</CardContent>
791787

0 commit comments

Comments
 (0)