mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-29 18:13:47 +00:00
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:
@@ -74,6 +74,7 @@ export interface GiftPurchaseStatus {
|
|||||||
status: GiftPurchaseStatusValue;
|
status: GiftPurchaseStatusValue;
|
||||||
is_gift: boolean;
|
is_gift: boolean;
|
||||||
is_code_only: boolean;
|
is_code_only: boolean;
|
||||||
|
is_claimable: boolean;
|
||||||
purchase_token: string | null;
|
purchase_token: string | null;
|
||||||
recipient_contact_value: string | null;
|
recipient_contact_value: string | null;
|
||||||
gift_message: string | null;
|
gift_message: string | null;
|
||||||
|
|||||||
@@ -4850,7 +4850,9 @@
|
|||||||
"activateTelegram": "Activate in Telegram",
|
"activateTelegram": "Activate in Telegram",
|
||||||
"activateWeb": "Activate by email",
|
"activateWeb": "Activate by email",
|
||||||
"emailLabel": "Your email",
|
"emailLabel": "Your email",
|
||||||
"claimNow": "Get my gift"
|
"claimNow": "Get my gift",
|
||||||
|
"failedTitle": "Gift unavailable",
|
||||||
|
"failedDesc": "The payment for this gift did not go through, so it cannot be activated."
|
||||||
},
|
},
|
||||||
"giftLink": {
|
"giftLink": {
|
||||||
"shareText": "I have a gift for you! Activate it here:",
|
"shareText": "I have a gift for you! Activate it here:",
|
||||||
|
|||||||
@@ -4510,7 +4510,9 @@
|
|||||||
"activateTelegram": "فعالسازی در تلگرام",
|
"activateTelegram": "فعالسازی در تلگرام",
|
||||||
"activateWeb": "فعالسازی با ایمیل",
|
"activateWeb": "فعالسازی با ایمیل",
|
||||||
"emailLabel": "ایمیل شما",
|
"emailLabel": "ایمیل شما",
|
||||||
"claimNow": "دریافت هدیه"
|
"claimNow": "دریافت هدیه",
|
||||||
|
"failedTitle": "هدیه در دسترس نیست",
|
||||||
|
"failedDesc": "پرداخت این هدیه انجام نشد، بنابراین قابل فعالسازی نیست."
|
||||||
},
|
},
|
||||||
"giftLink": {
|
"giftLink": {
|
||||||
"shareText": "یک هدیه برای شما دارم! اینجا فعال کنید:",
|
"shareText": "یک هدیه برای شما دارم! اینجا فعال کنید:",
|
||||||
|
|||||||
@@ -5405,7 +5405,9 @@
|
|||||||
"activateTelegram": "Активировать в Telegram",
|
"activateTelegram": "Активировать в Telegram",
|
||||||
"activateWeb": "Активировать по почте",
|
"activateWeb": "Активировать по почте",
|
||||||
"emailLabel": "Ваш email",
|
"emailLabel": "Ваш email",
|
||||||
"claimNow": "Получить подарок"
|
"claimNow": "Получить подарок",
|
||||||
|
"failedTitle": "Подарок недоступен",
|
||||||
|
"failedDesc": "Оплата подарка не прошла, поэтому активировать его нельзя."
|
||||||
},
|
},
|
||||||
"giftLink": {
|
"giftLink": {
|
||||||
"shareText": "Дарю вам подписку! Активируйте здесь:",
|
"shareText": "Дарю вам подписку! Активируйте здесь:",
|
||||||
|
|||||||
@@ -4509,7 +4509,9 @@
|
|||||||
"activateTelegram": "在 Telegram 中激活",
|
"activateTelegram": "在 Telegram 中激活",
|
||||||
"activateWeb": "通过邮箱激活",
|
"activateWeb": "通过邮箱激活",
|
||||||
"emailLabel": "您的邮箱",
|
"emailLabel": "您的邮箱",
|
||||||
"claimNow": "领取礼物"
|
"claimNow": "领取礼物",
|
||||||
|
"failedTitle": "礼物不可用",
|
||||||
|
"failedDesc": "此礼物的付款未成功,无法激活。"
|
||||||
},
|
},
|
||||||
"giftLink": {
|
"giftLink": {
|
||||||
"shareText": "我送您一份订阅!在此激活:",
|
"shareText": "我送您一份订阅!在此激活:",
|
||||||
|
|||||||
@@ -142,6 +142,25 @@ export default function GiftClaim() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Payment failed/expired → tell the recipient instead of spinning forever
|
||||||
|
if (gift.status === 'failed' || gift.status === 'expired') {
|
||||||
|
return (
|
||||||
|
<Shell>
|
||||||
|
<div className="flex flex-col items-center gap-3 py-6 text-center">
|
||||||
|
<h1 className="text-lg font-semibold text-dark-50">
|
||||||
|
{t('landing.giftClaim.failedTitle', 'Gift unavailable')}
|
||||||
|
</h1>
|
||||||
|
<p className="text-sm text-dark-400">
|
||||||
|
{t(
|
||||||
|
'landing.giftClaim.failedDesc',
|
||||||
|
'The payment for this gift did not go through, so it cannot be activated.',
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Shell>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Successful web claim → show connection link
|
// Successful web claim → show connection link
|
||||||
if (result) {
|
if (result) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -476,8 +476,9 @@ export default function GiftResult() {
|
|||||||
const s = d?.status;
|
const s = d?.status;
|
||||||
if (s === 'delivered' || s === 'failed' || s === 'pending_activation' || s === 'expired')
|
if (s === 'delivered' || s === 'failed' || s === 'pending_activation' || s === 'expired')
|
||||||
return false;
|
return false;
|
||||||
// Code-only gifts stay in 'paid' status — stop polling
|
// Claimable gifts (code-only AND directed) stay in 'paid' until claimed —
|
||||||
if (s === 'paid' && d?.is_code_only) return false;
|
// stop polling; the buyer shares the link.
|
||||||
|
if (s === 'paid' && d?.is_claimable) return false;
|
||||||
|
|
||||||
// Check poll timeout
|
// Check poll timeout
|
||||||
if (Date.now() - pollStart.current > MAX_POLL_MS) {
|
if (Date.now() - pollStart.current > MAX_POLL_MS) {
|
||||||
@@ -511,8 +512,8 @@ export default function GiftResult() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isCodeOnlyPaid =
|
const isClaimablePaid =
|
||||||
status?.status === 'paid' && status?.is_code_only && status?.purchase_token != null;
|
status?.status === 'paid' && status?.is_claimable && status?.purchase_token != null;
|
||||||
const isDelivered = status?.status === 'delivered';
|
const isDelivered = status?.status === 'delivered';
|
||||||
const isPendingActivation = status?.status === 'pending_activation';
|
const isPendingActivation = status?.status === 'pending_activation';
|
||||||
const isFailed = status?.status === 'failed' || status?.status === 'expired';
|
const isFailed = status?.status === 'failed' || status?.status === 'expired';
|
||||||
@@ -531,7 +532,7 @@ export default function GiftResult() {
|
|||||||
>
|
>
|
||||||
{isError ? (
|
{isError ? (
|
||||||
<PollErrorState />
|
<PollErrorState />
|
||||||
) : isCodeOnlyPaid ? (
|
) : isClaimablePaid ? (
|
||||||
<CodeOnlySuccessState
|
<CodeOnlySuccessState
|
||||||
purchaseToken={status.purchase_token!}
|
purchaseToken={status.purchase_token!}
|
||||||
tariffName={status.tariff_name}
|
tariffName={status.tariff_name}
|
||||||
|
|||||||
@@ -695,10 +695,10 @@ export default function PurchaseSuccess() {
|
|||||||
refetchInterval: (query) => {
|
refetchInterval: (query) => {
|
||||||
const data = query.state.data;
|
const data = query.state.data;
|
||||||
const currentStatus = data?.status;
|
const currentStatus = data?.status;
|
||||||
// A gift that reached PAID is claimable and terminal for the BUYER (it
|
// A gift that reached PAID is terminal for the BUYER (it stays PAID until
|
||||||
// stays PAID until the recipient claims) — stop polling and show the
|
// the recipient claims) — stop polling and show the share link instead of
|
||||||
// share link instead of spinning forever.
|
// spinning. A paid gift is always claimable, so don't gate on is_claimable.
|
||||||
if (currentStatus === 'paid' && data?.is_gift && data?.is_claimable) return false;
|
if (currentStatus === 'paid' && data?.is_gift) return false;
|
||||||
if (currentStatus === 'pending' || currentStatus === 'paid') {
|
if (currentStatus === 'pending' || currentStatus === 'paid') {
|
||||||
if (Date.now() - pollStart.current > MAX_POLL_MS) {
|
if (Date.now() - pollStart.current > MAX_POLL_MS) {
|
||||||
setPollTimedOut(true);
|
setPollTimedOut(true);
|
||||||
@@ -766,10 +766,7 @@ export default function PurchaseSuccess() {
|
|||||||
|
|
||||||
// Deferred gift the buyer just paid for → show the transferable claim link to
|
// Deferred gift the buyer just paid for → show the transferable claim link to
|
||||||
// forward (it stays PAID until the recipient claims it).
|
// forward (it stays PAID until the recipient claims it).
|
||||||
const isBuyerGiftLink =
|
const isBuyerGiftLink = purchaseStatus?.status === 'paid' && !!purchaseStatus?.is_gift;
|
||||||
purchaseStatus?.status === 'paid' &&
|
|
||||||
!!purchaseStatus?.is_gift &&
|
|
||||||
!!purchaseStatus?.is_claimable;
|
|
||||||
|
|
||||||
// Gift pending activation → buyer sees "gift sent" message, not the activate button.
|
// 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.
|
// Recipient arrives via email link with ?activate=1 and sees the activate button instead.
|
||||||
|
|||||||
Reference in New Issue
Block a user