fix(gift): cabinet GiftResult shows claim link for directed gifts + review fixes

Pairs with the backend cabinet-unification. Directed cabinet gifts now stay
PAID/claimable, so the buyer's GiftResult must show the share link for them too.

- GiftResult.tsx: render the share state (and stop polling) for ANY claimable
  paid gift via is_claimable, not just is_code_only.
- api/gift.ts: GiftPurchaseStatus gains is_claimable.
- GiftClaim.tsx: explicit failed/expired state (was falling into the 'almost
  ready' spinner forever).
- PurchaseSuccess.tsx: a paid gift is always claimable — stop polling and show
  the share block on `paid && is_gift` (don't gate on is_claimable, which could
  strand the buyer on a spinner).
- i18n: landing.giftClaim.failedTitle/failedDesc in ru/en/zh/fa.
This commit is contained in:
c0mrade
2026-06-03 14:34:19 +03:00
parent db31d395cd
commit b9521f1aa7
8 changed files with 43 additions and 17 deletions

View File

@@ -695,10 +695,10 @@ export default function PurchaseSuccess() {
refetchInterval: (query) => {
const data = query.state.data;
const currentStatus = data?.status;
// A gift that reached PAID is claimable and terminal for the BUYER (it
// stays PAID until the recipient claims) — stop polling and show the
// share link instead of spinning forever.
if (currentStatus === 'paid' && data?.is_gift && data?.is_claimable) return false;
// A gift that reached PAID is terminal for the BUYER (it stays PAID until
// the recipient claims) — stop polling and show the share link instead of
// spinning. A paid gift is always claimable, so don't gate on is_claimable.
if (currentStatus === 'paid' && data?.is_gift) return false;
if (currentStatus === 'pending' || currentStatus === 'paid') {
if (Date.now() - pollStart.current > MAX_POLL_MS) {
setPollTimedOut(true);
@@ -766,10 +766,7 @@ export default function PurchaseSuccess() {
// Deferred gift the buyer just paid for → show the transferable claim link to
// forward (it stays PAID until the recipient claims it).
const isBuyerGiftLink =
purchaseStatus?.status === 'paid' &&
!!purchaseStatus?.is_gift &&
!!purchaseStatus?.is_claimable;
const isBuyerGiftLink = purchaseStatus?.status === 'paid' && !!purchaseStatus?.is_gift;
// Gift pending activation → buyer sees "gift sent" message, not the activate button.
// Recipient arrives via email link with ?activate=1 and sees the activate button instead.