index.jsx 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725
  1. /*
  2. Copyright (C) 2025 QuantumNous
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Affero General Public License as
  5. published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License
  12. along with this program. If not, see <https://www.gnu.org/licenses/>.
  13. For commercial licensing, please contact support@quantumnous.com
  14. */
  15. import React, { useEffect, useState, useContext, useRef } from 'react';
  16. import {
  17. API,
  18. showError,
  19. showInfo,
  20. showSuccess,
  21. renderQuota,
  22. renderQuotaWithAmount,
  23. copy,
  24. getQuotaPerUnit,
  25. } from '../../helpers';
  26. import { Modal, Toast } from '@douyinfe/semi-ui';
  27. import { useTranslation } from 'react-i18next';
  28. import { UserContext } from '../../context/User';
  29. import { StatusContext } from '../../context/Status';
  30. import RechargeCard from './RechargeCard';
  31. import InvitationCard from './InvitationCard';
  32. import TransferModal from './modals/TransferModal';
  33. import PaymentConfirmModal from './modals/PaymentConfirmModal';
  34. import TopupHistoryModal from './modals/TopupHistoryModal';
  35. const TopUp = () => {
  36. const { t } = useTranslation();
  37. const [userState, userDispatch] = useContext(UserContext);
  38. const [statusState] = useContext(StatusContext);
  39. const [redemptionCode, setRedemptionCode] = useState('');
  40. const [amount, setAmount] = useState(0.0);
  41. const [minTopUp, setMinTopUp] = useState(statusState?.status?.min_topup || 1);
  42. const [topUpCount, setTopUpCount] = useState(
  43. statusState?.status?.min_topup || 1,
  44. );
  45. const [topUpLink, setTopUpLink] = useState(
  46. statusState?.status?.top_up_link || '',
  47. );
  48. const [enableOnlineTopUp, setEnableOnlineTopUp] = useState(
  49. statusState?.status?.enable_online_topup || false,
  50. );
  51. const [priceRatio, setPriceRatio] = useState(statusState?.status?.price || 1);
  52. const [enableStripeTopUp, setEnableStripeTopUp] = useState(
  53. statusState?.status?.enable_stripe_topup || false,
  54. );
  55. const [statusLoading, setStatusLoading] = useState(true);
  56. // Creem 相关状态
  57. const [creemProducts, setCreemProducts] = useState([]);
  58. const [enableCreemTopUp, setEnableCreemTopUp] = useState(false);
  59. const [creemOpen, setCreemOpen] = useState(false);
  60. const [selectedCreemProduct, setSelectedCreemProduct] = useState(null);
  61. const [isSubmitting, setIsSubmitting] = useState(false);
  62. const [open, setOpen] = useState(false);
  63. const [payWay, setPayWay] = useState('');
  64. const [amountLoading, setAmountLoading] = useState(false);
  65. const [paymentLoading, setPaymentLoading] = useState(false);
  66. const [confirmLoading, setConfirmLoading] = useState(false);
  67. const [payMethods, setPayMethods] = useState([]);
  68. const affFetchedRef = useRef(false);
  69. // 邀请相关状态
  70. const [affLink, setAffLink] = useState('');
  71. const [openTransfer, setOpenTransfer] = useState(false);
  72. const [transferAmount, setTransferAmount] = useState(0);
  73. // 账单Modal状态
  74. const [openHistory, setOpenHistory] = useState(false);
  75. // 预设充值额度选项
  76. const [presetAmounts, setPresetAmounts] = useState([]);
  77. const [selectedPreset, setSelectedPreset] = useState(null);
  78. // 充值配置信息
  79. const [topupInfo, setTopupInfo] = useState({
  80. amount_options: [],
  81. discount: {},
  82. });
  83. const topUp = async () => {
  84. if (redemptionCode === '') {
  85. showInfo(t('请输入兑换码!'));
  86. return;
  87. }
  88. setIsSubmitting(true);
  89. try {
  90. const res = await API.post('/api/user/topup', {
  91. key: redemptionCode,
  92. });
  93. const { success, message, data } = res.data;
  94. if (success) {
  95. showSuccess(t('兑换成功!'));
  96. Modal.success({
  97. title: t('兑换成功!'),
  98. content: t('成功兑换额度:') + renderQuota(data),
  99. centered: true,
  100. });
  101. if (userState.user) {
  102. const updatedUser = {
  103. ...userState.user,
  104. quota: userState.user.quota + data,
  105. };
  106. userDispatch({ type: 'login', payload: updatedUser });
  107. }
  108. setRedemptionCode('');
  109. } else {
  110. showError(message);
  111. }
  112. } catch (err) {
  113. showError(t('请求失败'));
  114. } finally {
  115. setIsSubmitting(false);
  116. }
  117. };
  118. const openTopUpLink = () => {
  119. if (!topUpLink) {
  120. showError(t('超级管理员未设置充值链接!'));
  121. return;
  122. }
  123. window.open(topUpLink, '_blank');
  124. };
  125. const preTopUp = async (payment) => {
  126. if (payment === 'stripe') {
  127. if (!enableStripeTopUp) {
  128. showError(t('管理员未开启Stripe充值!'));
  129. return;
  130. }
  131. } else {
  132. if (!enableOnlineTopUp) {
  133. showError(t('管理员未开启在线充值!'));
  134. return;
  135. }
  136. }
  137. setPayWay(payment);
  138. setPaymentLoading(true);
  139. try {
  140. if (payment === 'stripe') {
  141. await getStripeAmount();
  142. } else {
  143. await getAmount();
  144. }
  145. if (topUpCount < minTopUp) {
  146. showError(t('充值数量不能小于') + minTopUp);
  147. return;
  148. }
  149. setOpen(true);
  150. } catch (error) {
  151. showError(t('获取金额失败'));
  152. } finally {
  153. setPaymentLoading(false);
  154. }
  155. };
  156. const onlineTopUp = async () => {
  157. if (payWay === 'stripe') {
  158. // Stripe 支付处理
  159. if (amount === 0) {
  160. await getStripeAmount();
  161. }
  162. } else {
  163. // 普通支付处理
  164. if (amount === 0) {
  165. await getAmount();
  166. }
  167. }
  168. if (topUpCount < minTopUp) {
  169. showError('充值数量不能小于' + minTopUp);
  170. return;
  171. }
  172. setConfirmLoading(true);
  173. try {
  174. let res;
  175. if (payWay === 'stripe') {
  176. // Stripe 支付请求
  177. res = await API.post('/api/user/stripe/pay', {
  178. amount: parseInt(topUpCount),
  179. payment_method: 'stripe',
  180. });
  181. } else {
  182. // 普通支付请求
  183. res = await API.post('/api/user/pay', {
  184. amount: parseInt(topUpCount),
  185. payment_method: payWay,
  186. });
  187. }
  188. if (res !== undefined) {
  189. const { message, data } = res.data;
  190. if (message === 'success') {
  191. if (payWay === 'stripe') {
  192. // Stripe 支付回调处理
  193. window.open(data.pay_link, '_blank');
  194. } else {
  195. // 普通支付表单提交
  196. let params = data;
  197. let url = res.data.url;
  198. let form = document.createElement('form');
  199. form.action = url;
  200. form.method = 'POST';
  201. let isSafari =
  202. navigator.userAgent.indexOf('Safari') > -1 &&
  203. navigator.userAgent.indexOf('Chrome') < 1;
  204. if (!isSafari) {
  205. form.target = '_blank';
  206. }
  207. for (let key in params) {
  208. let input = document.createElement('input');
  209. input.type = 'hidden';
  210. input.name = key;
  211. input.value = params[key];
  212. form.appendChild(input);
  213. }
  214. document.body.appendChild(form);
  215. form.submit();
  216. document.body.removeChild(form);
  217. }
  218. } else {
  219. showError(data);
  220. }
  221. } else {
  222. showError(res);
  223. }
  224. } catch (err) {
  225. console.log(err);
  226. showError(t('支付请求失败'));
  227. } finally {
  228. setOpen(false);
  229. setConfirmLoading(false);
  230. }
  231. };
  232. const creemPreTopUp = async (product) => {
  233. if (!enableCreemTopUp) {
  234. showError(t('管理员未开启 Creem 充值!'));
  235. return;
  236. }
  237. setSelectedCreemProduct(product);
  238. setCreemOpen(true);
  239. };
  240. const onlineCreemTopUp = async () => {
  241. if (!selectedCreemProduct) {
  242. showError(t('请选择产品'));
  243. return;
  244. }
  245. // Validate product has required fields
  246. if (!selectedCreemProduct.productId) {
  247. showError(t('产品配置错误,请联系管理员'));
  248. return;
  249. }
  250. setConfirmLoading(true);
  251. try {
  252. const res = await API.post('/api/user/creem/pay', {
  253. product_id: selectedCreemProduct.productId,
  254. payment_method: 'creem',
  255. });
  256. if (res !== undefined) {
  257. const { message, data } = res.data;
  258. if (message === 'success') {
  259. processCreemCallback(data);
  260. } else {
  261. showError(data);
  262. }
  263. } else {
  264. showError(res);
  265. }
  266. } catch (err) {
  267. console.log(err);
  268. showError(t('支付请求失败'));
  269. } finally {
  270. setCreemOpen(false);
  271. setConfirmLoading(false);
  272. }
  273. };
  274. const processCreemCallback = (data) => {
  275. // 与 Stripe 保持一致的实现方式
  276. window.open(data.checkout_url, '_blank');
  277. };
  278. const getUserQuota = async () => {
  279. let res = await API.get(`/api/user/self`);
  280. const { success, message, data } = res.data;
  281. if (success) {
  282. userDispatch({ type: 'login', payload: data });
  283. } else {
  284. showError(message);
  285. }
  286. };
  287. // 获取充值配置信息
  288. const getTopupInfo = async () => {
  289. try {
  290. const res = await API.get('/api/user/topup/info');
  291. const { message, data, success } = res.data;
  292. if (success) {
  293. setTopupInfo({
  294. amount_options: data.amount_options || [],
  295. discount: data.discount || {},
  296. });
  297. // 处理支付方式
  298. let payMethods = data.pay_methods || [];
  299. try {
  300. if (typeof payMethods === 'string') {
  301. payMethods = JSON.parse(payMethods);
  302. }
  303. if (payMethods && payMethods.length > 0) {
  304. // 检查name和type是否为空
  305. payMethods = payMethods.filter((method) => {
  306. return method.name && method.type;
  307. });
  308. // 如果没有color,则设置默认颜色
  309. payMethods = payMethods.map((method) => {
  310. // 规范化最小充值数
  311. const normalizedMinTopup = Number(method.min_topup);
  312. method.min_topup = Number.isFinite(normalizedMinTopup)
  313. ? normalizedMinTopup
  314. : 0;
  315. // Stripe 的最小充值从后端字段回填
  316. if (
  317. method.type === 'stripe' &&
  318. (!method.min_topup || method.min_topup <= 0)
  319. ) {
  320. const stripeMin = Number(data.stripe_min_topup);
  321. if (Number.isFinite(stripeMin)) {
  322. method.min_topup = stripeMin;
  323. }
  324. }
  325. if (!method.color) {
  326. if (method.type === 'alipay') {
  327. method.color = 'rgba(var(--semi-blue-5), 1)';
  328. } else if (method.type === 'wxpay') {
  329. method.color = 'rgba(var(--semi-green-5), 1)';
  330. } else if (method.type === 'stripe') {
  331. method.color = 'rgba(var(--semi-purple-5), 1)';
  332. } else {
  333. method.color = 'rgba(var(--semi-primary-5), 1)';
  334. }
  335. }
  336. return method;
  337. });
  338. } else {
  339. payMethods = [];
  340. }
  341. // 如果启用了 Stripe 支付,添加到支付方法列表
  342. // 这个逻辑现在由后端处理,如果 Stripe 启用,后端会在 pay_methods 中包含它
  343. setPayMethods(payMethods);
  344. const enableStripeTopUp = data.enable_stripe_topup || false;
  345. const enableOnlineTopUp = data.enable_online_topup || false;
  346. const enableCreemTopUp = data.enable_creem_topup || false;
  347. const minTopUpValue = enableOnlineTopUp
  348. ? data.min_topup
  349. : enableStripeTopUp
  350. ? data.stripe_min_topup
  351. : 1;
  352. setEnableOnlineTopUp(enableOnlineTopUp);
  353. setEnableStripeTopUp(enableStripeTopUp);
  354. setEnableCreemTopUp(enableCreemTopUp);
  355. setMinTopUp(minTopUpValue);
  356. setTopUpCount(minTopUpValue);
  357. // 设置 Creem 产品
  358. try {
  359. console.log(' data is ?', data);
  360. console.log(' creem products is ?', data.creem_products);
  361. const products = JSON.parse(data.creem_products || '[]');
  362. setCreemProducts(products);
  363. } catch (e) {
  364. setCreemProducts([]);
  365. }
  366. // 如果没有自定义充值数量选项,根据最小充值金额生成预设充值额度选项
  367. if (topupInfo.amount_options.length === 0) {
  368. setPresetAmounts(generatePresetAmounts(minTopUpValue));
  369. }
  370. // 初始化显示实付金额
  371. getAmount(minTopUpValue);
  372. } catch (e) {
  373. console.log('解析支付方式失败:', e);
  374. setPayMethods([]);
  375. }
  376. // 如果有自定义充值数量选项,使用它们替换默认的预设选项
  377. if (data.amount_options && data.amount_options.length > 0) {
  378. const customPresets = data.amount_options.map((amount) => ({
  379. value: amount,
  380. discount: data.discount[amount] || 1.0,
  381. }));
  382. setPresetAmounts(customPresets);
  383. }
  384. } else {
  385. console.error('获取充值配置失败:', data);
  386. }
  387. } catch (error) {
  388. console.error('获取充值配置异常:', error);
  389. }
  390. };
  391. // 获取邀请链接
  392. const getAffLink = async () => {
  393. const res = await API.get('/api/user/aff');
  394. const { success, message, data } = res.data;
  395. if (success) {
  396. let link = `${window.location.origin}/register?aff=${data}`;
  397. setAffLink(link);
  398. } else {
  399. showError(message);
  400. }
  401. };
  402. // 划转邀请额度
  403. const transfer = async () => {
  404. if (transferAmount < getQuotaPerUnit()) {
  405. showError(t('划转金额最低为') + ' ' + renderQuota(getQuotaPerUnit()));
  406. return;
  407. }
  408. const res = await API.post(`/api/user/aff_transfer`, {
  409. quota: transferAmount,
  410. });
  411. const { success, message } = res.data;
  412. if (success) {
  413. showSuccess(message);
  414. setOpenTransfer(false);
  415. getUserQuota().then();
  416. } else {
  417. showError(message);
  418. }
  419. };
  420. // 复制邀请链接
  421. const handleAffLinkClick = async () => {
  422. await copy(affLink);
  423. showSuccess(t('邀请链接已复制到剪切板'));
  424. };
  425. useEffect(() => {
  426. if (!userState?.user?.id) {
  427. getUserQuota().then();
  428. }
  429. setTransferAmount(getQuotaPerUnit());
  430. }, []);
  431. useEffect(() => {
  432. if (affFetchedRef.current) return;
  433. affFetchedRef.current = true;
  434. getAffLink().then();
  435. }, []);
  436. // 在 statusState 可用时获取充值信息
  437. useEffect(() => {
  438. getTopupInfo().then();
  439. }, []);
  440. useEffect(() => {
  441. if (statusState?.status) {
  442. // const minTopUpValue = statusState.status.min_topup || 1;
  443. // setMinTopUp(minTopUpValue);
  444. // setTopUpCount(minTopUpValue);
  445. setTopUpLink(statusState.status.top_up_link || '');
  446. setPriceRatio(statusState.status.price || 1);
  447. setStatusLoading(false);
  448. }
  449. }, [statusState?.status]);
  450. const renderAmount = () => {
  451. return amount + ' ' + t('元');
  452. };
  453. const getAmount = async (value) => {
  454. if (value === undefined) {
  455. value = topUpCount;
  456. }
  457. setAmountLoading(true);
  458. try {
  459. const res = await API.post('/api/user/amount', {
  460. amount: parseFloat(value),
  461. });
  462. if (res !== undefined) {
  463. const { message, data } = res.data;
  464. if (message === 'success') {
  465. setAmount(parseFloat(data));
  466. } else {
  467. setAmount(0);
  468. Toast.error({ content: '错误:' + data, id: 'getAmount' });
  469. }
  470. } else {
  471. showError(res);
  472. }
  473. } catch (err) {
  474. console.log(err);
  475. }
  476. setAmountLoading(false);
  477. };
  478. const getStripeAmount = async (value) => {
  479. if (value === undefined) {
  480. value = topUpCount;
  481. }
  482. setAmountLoading(true);
  483. try {
  484. const res = await API.post('/api/user/stripe/amount', {
  485. amount: parseFloat(value),
  486. });
  487. if (res !== undefined) {
  488. const { message, data } = res.data;
  489. if (message === 'success') {
  490. setAmount(parseFloat(data));
  491. } else {
  492. setAmount(0);
  493. Toast.error({ content: '错误:' + data, id: 'getAmount' });
  494. }
  495. } else {
  496. showError(res);
  497. }
  498. } catch (err) {
  499. console.log(err);
  500. } finally {
  501. setAmountLoading(false);
  502. }
  503. };
  504. const handleCancel = () => {
  505. setOpen(false);
  506. };
  507. const handleTransferCancel = () => {
  508. setOpenTransfer(false);
  509. };
  510. const handleOpenHistory = () => {
  511. setOpenHistory(true);
  512. };
  513. const handleHistoryCancel = () => {
  514. setOpenHistory(false);
  515. };
  516. const handleCreemCancel = () => {
  517. setCreemOpen(false);
  518. setSelectedCreemProduct(null);
  519. };
  520. // 选择预设充值额度
  521. const selectPresetAmount = (preset) => {
  522. setTopUpCount(preset.value);
  523. setSelectedPreset(preset.value);
  524. // 计算实际支付金额,考虑折扣
  525. const discount = preset.discount || topupInfo.discount[preset.value] || 1.0;
  526. const discountedAmount = preset.value * priceRatio * discount;
  527. setAmount(discountedAmount);
  528. };
  529. // 格式化大数字显示
  530. const formatLargeNumber = (num) => {
  531. return num.toString();
  532. };
  533. // 根据最小充值金额生成预设充值额度选项
  534. const generatePresetAmounts = (minAmount) => {
  535. const multipliers = [1, 5, 10, 30, 50, 100, 300, 500];
  536. return multipliers.map((multiplier) => ({
  537. value: minAmount * multiplier,
  538. }));
  539. };
  540. return (
  541. <div className='w-full max-w-7xl mx-auto relative min-h-screen lg:min-h-0 mt-[60px] px-2'>
  542. {/* 划转模态框 */}
  543. <TransferModal
  544. t={t}
  545. openTransfer={openTransfer}
  546. transfer={transfer}
  547. handleTransferCancel={handleTransferCancel}
  548. userState={userState}
  549. renderQuota={renderQuota}
  550. getQuotaPerUnit={getQuotaPerUnit}
  551. transferAmount={transferAmount}
  552. setTransferAmount={setTransferAmount}
  553. />
  554. {/* 充值确认模态框 */}
  555. <PaymentConfirmModal
  556. t={t}
  557. open={open}
  558. onlineTopUp={onlineTopUp}
  559. handleCancel={handleCancel}
  560. confirmLoading={confirmLoading}
  561. topUpCount={topUpCount}
  562. renderQuotaWithAmount={renderQuotaWithAmount}
  563. amountLoading={amountLoading}
  564. renderAmount={renderAmount}
  565. payWay={payWay}
  566. payMethods={payMethods}
  567. amountNumber={amount}
  568. discountRate={topupInfo?.discount?.[topUpCount] || 1.0}
  569. />
  570. {/* 充值账单模态框 */}
  571. <TopupHistoryModal
  572. visible={openHistory}
  573. onCancel={handleHistoryCancel}
  574. t={t}
  575. />
  576. {/* Creem 充值确认模态框 */}
  577. <Modal
  578. title={t('确定要充值 $')}
  579. visible={creemOpen}
  580. onOk={onlineCreemTopUp}
  581. onCancel={handleCreemCancel}
  582. maskClosable={false}
  583. size='small'
  584. centered
  585. confirmLoading={confirmLoading}
  586. >
  587. {selectedCreemProduct && (
  588. <>
  589. <p>
  590. {t('产品名称')}:{selectedCreemProduct.name}
  591. </p>
  592. <p>
  593. {t('价格')}:{selectedCreemProduct.currency === 'EUR' ? '€' : '$'}{selectedCreemProduct.price}
  594. </p>
  595. <p>
  596. {t('充值额度')}:{selectedCreemProduct.quota}
  597. </p>
  598. <p>{t('是否确认充值?')}</p>
  599. </>
  600. )}
  601. </Modal>
  602. {/* 用户信息头部 */}
  603. <div className='space-y-6'>
  604. <div className='grid grid-cols-1 lg:grid-cols-12 gap-6'>
  605. {/* 左侧充值区域 */}
  606. <div className='lg:col-span-7 space-y-6 w-full'>
  607. <RechargeCard
  608. t={t}
  609. enableOnlineTopUp={enableOnlineTopUp}
  610. enableStripeTopUp={enableStripeTopUp}
  611. enableCreemTopUp={enableCreemTopUp}
  612. creemProducts={creemProducts}
  613. creemPreTopUp={creemPreTopUp}
  614. presetAmounts={presetAmounts}
  615. selectedPreset={selectedPreset}
  616. selectPresetAmount={selectPresetAmount}
  617. formatLargeNumber={formatLargeNumber}
  618. priceRatio={priceRatio}
  619. topUpCount={topUpCount}
  620. minTopUp={minTopUp}
  621. renderQuotaWithAmount={renderQuotaWithAmount}
  622. getAmount={getAmount}
  623. setTopUpCount={setTopUpCount}
  624. setSelectedPreset={setSelectedPreset}
  625. renderAmount={renderAmount}
  626. amountLoading={amountLoading}
  627. payMethods={payMethods}
  628. preTopUp={preTopUp}
  629. paymentLoading={paymentLoading}
  630. payWay={payWay}
  631. redemptionCode={redemptionCode}
  632. setRedemptionCode={setRedemptionCode}
  633. topUp={topUp}
  634. isSubmitting={isSubmitting}
  635. topUpLink={topUpLink}
  636. openTopUpLink={openTopUpLink}
  637. userState={userState}
  638. renderQuota={renderQuota}
  639. statusLoading={statusLoading}
  640. topupInfo={topupInfo}
  641. onOpenHistory={handleOpenHistory}
  642. />
  643. </div>
  644. {/* 右侧信息区域 */}
  645. <div className='lg:col-span-5'>
  646. <InvitationCard
  647. t={t}
  648. userState={userState}
  649. renderQuota={renderQuota}
  650. setOpenTransfer={setOpenTransfer}
  651. affLink={affLink}
  652. handleAffLinkClick={handleAffLinkClick}
  653. />
  654. </div>
  655. </div>
  656. </div>
  657. </div>
  658. );
  659. };
  660. export default TopUp;