mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-29 18:13:47 +00:00
feat: gift subscription redesign — code-only purchase + 3-tab UI
- Remove recipient input from buy flow (code-only gifts) - 3-tab UI: Buy, Activate, My Gifts with animations - After purchase, switch to My Gifts tab to show gift code - Gift code display with copy/share buttons - Activate tab: enter gift code to activate subscription - My Gifts tab: sent/received gifts with status badges - Fix: use navigator.language instead of hardcoded ru-RU for dates - Fix: spread array before sort to prevent React Query cache mutation - Fix: ARIA tab linkage with proper id/aria-labelledby - Fix: guard VITE_BOT_USERNAME against undefined in share URLs - Fix: wrap clipboard/share fallbacks in try/catch - Add error state to MyGiftsTabContent
This commit is contained in:
@@ -45,8 +45,8 @@ export interface GiftConfig {
|
|||||||
export interface GiftPurchaseRequest {
|
export interface GiftPurchaseRequest {
|
||||||
tariff_id: number;
|
tariff_id: number;
|
||||||
period_days: number;
|
period_days: number;
|
||||||
recipient_type: 'email' | 'telegram';
|
recipient_type?: 'email' | 'telegram';
|
||||||
recipient_value: string;
|
recipient_value?: string;
|
||||||
gift_message?: string;
|
gift_message?: string;
|
||||||
payment_mode: 'balance' | 'gateway';
|
payment_mode: 'balance' | 'gateway';
|
||||||
payment_method?: string;
|
payment_method?: string;
|
||||||
@@ -86,6 +86,35 @@ export interface PendingGift {
|
|||||||
created_at: string | null;
|
created_at: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SentGift {
|
||||||
|
token: string;
|
||||||
|
tariff_name: string | null;
|
||||||
|
period_days: number;
|
||||||
|
device_limit: number;
|
||||||
|
status: string;
|
||||||
|
gift_recipient_value: string | null;
|
||||||
|
gift_message: string | null;
|
||||||
|
activated_by_username: string | null;
|
||||||
|
created_at: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ReceivedGift {
|
||||||
|
token: string;
|
||||||
|
tariff_name: string | null;
|
||||||
|
period_days: number;
|
||||||
|
device_limit: number;
|
||||||
|
status: string;
|
||||||
|
sender_display: string | null;
|
||||||
|
gift_message: string | null;
|
||||||
|
created_at: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ActivateGiftResponse {
|
||||||
|
status: string;
|
||||||
|
tariff_name: string | null;
|
||||||
|
period_days: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
// API
|
// API
|
||||||
|
|
||||||
export const giftApi = {
|
export const giftApi = {
|
||||||
@@ -108,4 +137,19 @@ export const giftApi = {
|
|||||||
const { data } = await apiClient.get<PendingGift[]>('/cabinet/gift/pending');
|
const { data } = await apiClient.get<PendingGift[]>('/cabinet/gift/pending');
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getSentGifts: async (): Promise<SentGift[]> => {
|
||||||
|
const { data } = await apiClient.get<SentGift[]>('/cabinet/gift/sent');
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
getReceivedGifts: async (): Promise<ReceivedGift[]> => {
|
||||||
|
const { data } = await apiClient.get<ReceivedGift[]>('/cabinet/gift/received');
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
activateGiftCode: async (code: string): Promise<ActivateGiftResponse> => {
|
||||||
|
const { data } = await apiClient.post<ActivateGiftResponse>('/cabinet/gift/activate', { code });
|
||||||
|
return data;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4201,6 +4201,41 @@
|
|||||||
},
|
},
|
||||||
"warning": {
|
"warning": {
|
||||||
"telegram_unresolvable": "Could not verify this Telegram username. The recipient may not receive a notification about the gift."
|
"telegram_unresolvable": "Could not verify this Telegram username. The recipient may not receive a notification about the gift."
|
||||||
}
|
},
|
||||||
|
"pageTitle": "Gifts",
|
||||||
|
"tabBuy": "Buy",
|
||||||
|
"tabActivate": "Activate",
|
||||||
|
"tabMyGifts": "My Gifts",
|
||||||
|
"selectTariff": "SELECT TARIFF",
|
||||||
|
"selectPeriod": "SUBSCRIPTION PERIOD",
|
||||||
|
"deviceCount": "{{count}} device",
|
||||||
|
"deviceCount_other": "{{count}} devices",
|
||||||
|
"activateTitle": "Enter gift code",
|
||||||
|
"activateDescription": "Enter the gift code you received from a friend",
|
||||||
|
"activateCodePlaceholder": "GIFT-XXXXXXXXXXXX",
|
||||||
|
"activateButton": "Activate gift",
|
||||||
|
"activating": "Activating...",
|
||||||
|
"activateSuccess": "Gift activated!",
|
||||||
|
"activateSuccessDesc": "{{tariff}} — {{days}} days added to your subscription",
|
||||||
|
"activateError": "Failed to activate gift",
|
||||||
|
"myGiftsEmpty": "No gifts yet",
|
||||||
|
"myGiftsEmptyDesc": "Buy a gift for a friend or activate a received code",
|
||||||
|
"sentGiftsTitle": "Sent",
|
||||||
|
"receivedGiftsTitle": "Received",
|
||||||
|
"statusAvailable": "AVAILABLE",
|
||||||
|
"statusActivated": "ACTIVATED",
|
||||||
|
"statusPending": "PENDING",
|
||||||
|
"statusDelivered": "DELIVERED",
|
||||||
|
"statusFailed": "FAILED",
|
||||||
|
"statusExpired": "EXPIRED",
|
||||||
|
"statusPendingActivation": "PENDING ACTIVATION",
|
||||||
|
"copyCode": "COPY",
|
||||||
|
"codeCopied": "Code copied!",
|
||||||
|
"shareGift": "SHARE",
|
||||||
|
"shareText": "I have a gift for you! Activate it here:",
|
||||||
|
"activatedBy": "Activated by {{username}}",
|
||||||
|
"sentTo": "Sent to {{recipient}}",
|
||||||
|
"daysShort": "days",
|
||||||
|
"devicesShort": "dev."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4763,6 +4763,41 @@
|
|||||||
},
|
},
|
||||||
"warning": {
|
"warning": {
|
||||||
"telegram_unresolvable": "Не удалось проверить этот Telegram-юзернейм. Получатель может не получить уведомление о подарке."
|
"telegram_unresolvable": "Не удалось проверить этот Telegram-юзернейм. Получатель может не получить уведомление о подарке."
|
||||||
}
|
},
|
||||||
|
"pageTitle": "Подарки",
|
||||||
|
"tabBuy": "Купить",
|
||||||
|
"tabActivate": "Активировать",
|
||||||
|
"tabMyGifts": "Мои подарки",
|
||||||
|
"selectTariff": "ВЫБЕРИТЕ ТАРИФ",
|
||||||
|
"selectPeriod": "ПЕРИОД ПОДПИСКИ",
|
||||||
|
"deviceCount": "{{count}} устройство",
|
||||||
|
"deviceCount_other": "{{count}} устройств",
|
||||||
|
"activateTitle": "Введите код подарка",
|
||||||
|
"activateDescription": "Введите код подарка, полученный от друга или знакомого",
|
||||||
|
"activateCodePlaceholder": "GIFT-XXXXXXXXXXXX",
|
||||||
|
"activateButton": "Активировать подарок",
|
||||||
|
"activating": "Активация...",
|
||||||
|
"activateSuccess": "Подарок активирован!",
|
||||||
|
"activateSuccessDesc": "{{tariff}} — {{days}} дн. добавлено к вашей подписке",
|
||||||
|
"activateError": "Не удалось активировать подарок",
|
||||||
|
"myGiftsEmpty": "У вас пока нет подарков",
|
||||||
|
"myGiftsEmptyDesc": "Купите подарок для друга или активируйте полученный код",
|
||||||
|
"sentGiftsTitle": "Отправленные",
|
||||||
|
"receivedGiftsTitle": "Полученные",
|
||||||
|
"statusAvailable": "ДОСТУПЕН",
|
||||||
|
"statusActivated": "АКТИВИРОВАН",
|
||||||
|
"statusPending": "ОЖИДАЕТ",
|
||||||
|
"statusDelivered": "ДОСТАВЛЕН",
|
||||||
|
"statusFailed": "ОШИБКА",
|
||||||
|
"statusExpired": "ПРОСРОЧЕН",
|
||||||
|
"statusPendingActivation": "ОЖИДАЕТ АКТИВАЦИИ",
|
||||||
|
"copyCode": "КОПИРОВАТЬ",
|
||||||
|
"codeCopied": "Код скопирован!",
|
||||||
|
"shareGift": "ПОДЕЛИТЬСЯ",
|
||||||
|
"shareText": "У меня есть подарок для тебя! Активируй его здесь:",
|
||||||
|
"activatedBy": "Активирован пользователем {{username}}",
|
||||||
|
"sentTo": "Отправлен {{recipient}}",
|
||||||
|
"daysShort": "дн.",
|
||||||
|
"devicesShort": "устр."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user