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
49 changes: 40 additions & 9 deletions src/components/common/CreatorCard.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { useRef, useState } from 'react';
import { useAccount } from 'wagmi';
import { Button } from '@/components/ui/button';
import type { Course } from '@/services/course.service';
import { cn } from '@/lib/utils';
import { ShoppingCart, Wallet, Link as LinkIcon } from 'lucide-react';
import toast from 'react-hot-toast';
import showToast from '@/utils/toast.util';
import TransactionRetryNotice from '@/components/common/TransactionRetryNotice';

interface CreatorCardProps {
creator: Course;
Expand All @@ -13,6 +15,32 @@ interface CreatorCardProps {

const CreatorCard: React.FC<CreatorCardProps> = ({ creator, className }) => {
const { isConnected } = useAccount();
const [transactionState, setTransactionState] = useState<
'idle' | 'submitting' | 'failed'
>('idle');
const hasFailedOnceRef = useRef(false);

const runPurchaseAttempt = () => {
setTransactionState('submitting');
showToast.loading(`Purchasing keys for ${creator.title}...`);

window.setTimeout(() => {
toast.remove();

if (!hasFailedOnceRef.current) {
hasFailedOnceRef.current = true;
setTransactionState('failed');
return;
}

hasFailedOnceRef.current = false;
setTransactionState('idle');
showToast.transactionSuccess(
'Purchase Successful!',
`You successfully bought a key for ${creator.title}`
);
}, 1500);
};

const handleBuy = () => {
if (!isConnected) {
Expand All @@ -23,14 +51,7 @@ const CreatorCard: React.FC<CreatorCardProps> = ({ creator, className }) => {
return;
}

showToast.loading(`Purchasing keys for ${creator.title}...`);
// Implementation for contract interaction would go here
setTimeout(() => {
showToast.transactionSuccess(
'Purchase Successful!',
`You successfully bought a key for ${creator.title}`
);
}, 1500);
runPurchaseAttempt();
};

return (
Expand Down Expand Up @@ -82,14 +103,15 @@ const CreatorCard: React.FC<CreatorCardProps> = ({ creator, className }) => {
onClick={handleBuy}
variant={isConnected ? 'default' : 'outline'}
size="sm"
disabled={transactionState === 'submitting'}
className={cn(
'rounded-xl font-bold',
!isConnected &&
'border-white/10 text-white/60 hover:bg-white/5'
)}
>
<ShoppingCart className="mr-2 size-4" />
Buy Key
{transactionState === 'submitting' ? 'Processing...' : 'Buy Key'}
</Button>
</div>

Expand All @@ -99,6 +121,15 @@ const CreatorCard: React.FC<CreatorCardProps> = ({ creator, className }) => {
Wallet Required
</div>
)}

{transactionState === 'failed' && (
<TransactionRetryNotice
className="mt-4"
message="The previous purchase attempt failed before confirmation. Retry the Stellar action to try again."
retryLabel="Retry Purchase"
onRetry={runPurchaseAttempt}
/>
)}
</div>
);
};
Expand Down
57 changes: 57 additions & 0 deletions src/components/common/TransactionRetryNotice.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { AlertTriangle, RotateCcw } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';

interface TransactionRetryNoticeProps {
message: string;
onRetry: () => void;
title?: string;
retryLabel?: string;
className?: string;
disabled?: boolean;
}

const TransactionRetryNotice: React.FC<TransactionRetryNoticeProps> = ({
title = 'Transaction failed',
message,
onRetry,
retryLabel = 'Retry',
className,
disabled = false,
}) => {
return (
<div
role="status"
aria-live="polite"
className={cn(
'rounded-2xl border border-red-500/20 bg-red-500/10 p-4 text-left',
className
)}
>
<div className="flex items-start gap-3">
<div className="mt-0.5 rounded-full bg-red-500/15 p-2 text-red-300">
<AlertTriangle className="size-4" />
</div>
<div className="min-w-0 flex-1">
<p className="font-jakarta text-sm font-bold text-white">{title}</p>
<p className="mt-1 font-jakarta text-sm leading-relaxed text-white/70">
{message}
</p>
<Button
type="button"
size="sm"
variant="outline"
onClick={onRetry}
disabled={disabled}
className="mt-3 border-red-400/30 bg-transparent text-white hover:bg-red-500/10"
>
<RotateCcw className="size-4" />
{retryLabel}
</Button>
</div>
</div>
</div>
);
};

export default TransactionRetryNotice;
Loading