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
57 changes: 48 additions & 9 deletions app/components/explore/EventCheckout/TicketInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ import { useCooldown } from "@/hooks/useCooldown";
import { CooldownMessage } from "@/app/components/AntiSpam/CooldownMessage";
import { TransactionStatusBanner } from "@/components/TransactionStatusBanner";
import type { TransactionStatus } from "@/hooks/useTransactionStatus";
import { useFailureState } from "@/hooks/useFailureState";
import { FailureStateModal } from "@/components/FailureStateModal";
import { FailureStateBanner } from "@/components/FailureStateBanner";

type PaymentStatus = "idle" | "processing" | "failed";

Expand Down Expand Up @@ -90,6 +93,32 @@ export const TicketInfo: FC<TicketInfoProps> = ({

const { isOnCooldown, remainingSeconds, startCooldown } = useCooldown({ duration: 8 });

const {
failureState,
isOpen: isFailureModalOpen,
triggerFailure,
clearFailure,
downloadDiagnostics,
} = useFailureState();

useEffect(() => {
if (hasPaymentFailed && paymentError) {
triggerFailure(paymentError, {
customMessage: paymentError,
technicalDetails: `Checkout payment failed for event ${eventId}.`,
});
} else if (txState.status === "failed" && txState.error) {
triggerFailure(txState.error, {
txHash: txState.txHash,
technicalDetails: `Transaction failed after ${txState.attempts} polling attempts.`,
});
} else if (walletState.error) {
triggerFailure(walletState.error, {
technicalDetails: `Wallet connection error during checkout.`,
});
Comment on lines +104 to +118

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Keep source errors in the shared diagnostic payload. Supplying static technicalDetails overrides useFailureState’s normal error serialization, so users and support lose the actual failure cause.

  • app/components/explore/EventCheckout/TicketInfo.tsx#L104-L118: append the sanitized payment, transaction, or wallet error to the contextual diagnostic string.
  • app/components/organizer/ConnectWalletPrompt.tsx#L28-L34: append the sanitized wallet SDK error to the contextual diagnostic string.
📍 Affects 2 files
  • app/components/explore/EventCheckout/TicketInfo.tsx#L104-L118 (this comment)
  • app/components/organizer/ConnectWalletPrompt.tsx#L28-L34
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/explore/EventCheckout/TicketInfo.tsx` around lines 104 - 118,
The failure handlers in TicketInfo.tsx (the checkout useEffect) and
ConnectWalletPrompt.tsx (the wallet failure handler) overwrite serialized source
errors with static technicalDetails. Append each sanitized payment, transaction,
wallet, or wallet SDK error to its contextual diagnostic string while preserving
the existing context and avoiding raw unsanitized error data.

}
}, [hasPaymentFailed, paymentError, txState.status, txState.error, walletState.error, triggerFailure, eventId, txState.txHash, txState.attempts]);

const intervalRef = useRef<NodeJS.Timeout | null>(null);

const stopPolling = () => {
Expand Down Expand Up @@ -413,13 +442,12 @@ export const TicketInfo: FC<TicketInfoProps> = ({
{/* Cooldown message */}
<CooldownMessage remainingSeconds={remainingSeconds} />

{/* Payment error from parent (e.g. sold out, reconcile failure) */}
{hasPaymentFailed && txState.status === "idle" && (
<div className="bg-[#FFF2F2] border border-[#FBCACA] text-[#B42318] py-3 px-5 rounded-lg">
<p className="text-xs font-medium">
{paymentError ?? "Payment failed. Please retry."}
</p>
</div>
{/* Unified Failure State Banner for payment failure */}
{hasPaymentFailed && failureState && txState.status === "idle" && (
<FailureStateBanner
failureState={failureState}
onRetry={handleRetry}
/>
Comment on lines +445 to +450

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use a shared close-and-retry path. FailureStateModal delegates RETRY without closing, while these callbacks only reset source state; retries therefore run beneath an active modal and retain stale failure state.

  • app/components/explore/EventCheckout/TicketInfo.tsx#L445-L450: wire banner retry through a failure-state reset before checkout retry.
  • app/components/explore/EventCheckout/TicketInfo.tsx#L483-L499: use the same close-and-retry callback for the wallet banner and modal.
  • app/components/organizer/ConnectWalletPrompt.tsx#L91-L108: clear/reset failure state before invoking handleConnectWallet.
📍 Affects 2 files
  • app/components/explore/EventCheckout/TicketInfo.tsx#L445-L450 (this comment)
  • app/components/explore/EventCheckout/TicketInfo.tsx#L483-L499
  • app/components/organizer/ConnectWalletPrompt.tsx#L91-L108
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/explore/EventCheckout/TicketInfo.tsx` around lines 445 - 450,
Use a shared close-and-retry callback that clears or resets the failure state
before invoking the retry action. In
app/components/explore/EventCheckout/TicketInfo.tsx lines 445-450, route the
failure banner’s onRetry through this reset-then-retry path; in lines 483-499,
reuse the same callback for both the wallet banner and FailureStateModal. In
app/components/organizer/ConnectWalletPrompt.tsx lines 91-108, clear/reset the
failure state before calling handleConnectWallet.

)}

<div className="bg-[#F2FFF2] dark:bg-[#131313] dark:text-[#0BD330] text-[#0ABA2A] py-3 px-5 gap-4 flex">
Expand Down Expand Up @@ -452,12 +480,23 @@ export const TicketInfo: FC<TicketInfoProps> = ({
<>{buttonLabel()}</>
)}
</button>
{walletState.error && (
<p className="mt-2 text-sm text-red-500">{walletState.error}</p>
{walletState.error && failureState && (
<div className="mt-3">
<FailureStateBanner failureState={failureState} onRetry={handleRetry} />
</div>
)}
</div>
</fieldset>
</form>

{/* Unified Failure State Modal */}
<FailureStateModal
isOpen={isFailureModalOpen}
onClose={clearFailure}
failureState={failureState}
onRetry={handleRetry}
onDownloadDiagnostics={downloadDiagnostics}
/>
</div>
);
};
46 changes: 39 additions & 7 deletions app/components/organizer/ConnectWalletPrompt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

import Image from "next/image";
import { ChevronRight, Loader2 } from "lucide-react";
import { useState } from "react";
import { useState, useEffect } from "react";
import { trackAnalyticsEvent } from "@/lib/privacyAnalytics";
import { loadWalletSDK, preloadWalletSDK, WalletLoadState } from "@/lib/walletSdk";
import { useUserSessionSync } from "@/lib/user-session-sync";
import { useFailureState } from "@/hooks/useFailureState";
import { FailureStateModal } from "@/components/FailureStateModal";
import { FailureStateBanner } from "@/components/FailureStateBanner";

export default function ConnectWalletPrompt() {
const [walletState, setWalletState] = useState<WalletLoadState>({
Expand All @@ -14,6 +17,22 @@ export default function ConnectWalletPrompt() {
});
const { walletConnected, setWalletConnected } = useUserSessionSync();

const {
failureState,
isOpen: isFailureModalOpen,
triggerFailure,
clearFailure,
downloadDiagnostics,
} = useFailureState();

useEffect(() => {
if (walletState.error) {
triggerFailure(walletState.error, {
technicalDetails: "Wallet connection load failed in organizer prompt.",
});
}
}, [walletState.error, triggerFailure]);

async function handleConnectWallet() {
trackAnalyticsEvent("wallet_connect_cta_clicked", { source: "organizer_prompt" });
setWalletState({ isLoading: true, error: null });
Expand All @@ -30,7 +49,7 @@ export default function ConnectWalletPrompt() {
}

return (
<div className="w-full flex flex-col md:flex-row items-center justify-center gap-6 md:gap-16 border border-[#E3E3E3] rounded-2xl py-8 px-6 md:px-16 bg-white">
<div className="w-full flex flex-col md:flex-row items-center justify-center gap-6 md:gap-16 border border-[#E3E3E3] rounded-2xl py-8 px-6 md:px-16 bg-white dark:bg-[#121212] dark:border-[#232323]">
<div className="shrink-0">
<Image
src="/images/connect-wallet-illustration.png"
Expand All @@ -42,15 +61,15 @@ export default function ConnectWalletPrompt() {
</div>

<div className="flex flex-col items-center text-center gap-3">
<h2 className="text-xl md:text-2xl font-bold text-[#1D2939]">
<h2 className="text-xl md:text-2xl font-bold text-[#1D2939] dark:text-[#E0E0E0]">
Connect your wallet
</h2>

<p className="text-sm md:text-base text-[#475467] leading-relaxed max-w-70">
<p className="text-sm md:text-base text-[#475467] dark:text-[#98A2B3] leading-relaxed max-w-70">
Connect your wallet to receive payments from paid events.
</p>

<div className="mt-2">
<div className="mt-2 flex flex-col items-center gap-3">
<button
onClick={handleConnectWallet}
onMouseEnter={preloadWalletSDK}
Expand All @@ -69,11 +88,24 @@ export default function ConnectWalletPrompt() {
</>
)}
</button>
{walletState.error && (
<p className="mt-2 text-sm text-red-500">{walletState.error}</p>
{walletState.error && failureState && (
<div className="w-full max-w-sm">
<FailureStateBanner
failureState={failureState}
onRetry={handleConnectWallet}
/>
</div>
)}
</div>
</div>

<FailureStateModal
isOpen={isFailureModalOpen}
onClose={clearFailure}
failureState={failureState}
onRetry={handleConnectWallet}
onDownloadDiagnostics={downloadDiagnostics}
/>
</div>
);
}
Loading