payment-kit 1.20.10 → 1.20.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -24
- package/api/src/index.ts +2 -0
- package/api/src/integrations/stripe/handlers/invoice.ts +63 -5
- package/api/src/integrations/stripe/handlers/payment-intent.ts +1 -0
- package/api/src/integrations/stripe/resource.ts +253 -2
- package/api/src/libs/currency.ts +31 -0
- package/api/src/libs/discount/coupon.ts +1061 -0
- package/api/src/libs/discount/discount.ts +349 -0
- package/api/src/libs/discount/nft.ts +239 -0
- package/api/src/libs/discount/redemption.ts +636 -0
- package/api/src/libs/discount/vc.ts +73 -0
- package/api/src/libs/invoice.ts +50 -16
- package/api/src/libs/math-utils.ts +6 -0
- package/api/src/libs/price.ts +43 -0
- package/api/src/libs/session.ts +242 -57
- package/api/src/libs/subscription.ts +2 -6
- package/api/src/locales/en.ts +38 -38
- package/api/src/queues/auto-recharge.ts +1 -1
- package/api/src/queues/discount-status.ts +200 -0
- package/api/src/queues/subscription.ts +98 -5
- package/api/src/queues/usage-record.ts +1 -1
- package/api/src/routes/auto-recharge-configs.ts +5 -3
- package/api/src/routes/checkout-sessions.ts +755 -64
- package/api/src/routes/connect/change-payment.ts +6 -1
- package/api/src/routes/connect/change-plan.ts +6 -1
- package/api/src/routes/connect/setup.ts +6 -1
- package/api/src/routes/connect/shared.ts +80 -9
- package/api/src/routes/connect/subscribe.ts +12 -2
- package/api/src/routes/coupons.ts +518 -0
- package/api/src/routes/index.ts +4 -0
- package/api/src/routes/invoices.ts +44 -3
- package/api/src/routes/meter-events.ts +2 -1
- package/api/src/routes/payment-currencies.ts +1 -0
- package/api/src/routes/promotion-codes.ts +482 -0
- package/api/src/routes/subscriptions.ts +23 -2
- package/api/src/store/migrations/20250904-discount.ts +136 -0
- package/api/src/store/migrations/20250910-timestamp-fields.ts +116 -0
- package/api/src/store/migrations/20250916-add-description-fields.ts +30 -0
- package/api/src/store/models/checkout-session.ts +12 -0
- package/api/src/store/models/coupon.ts +144 -4
- package/api/src/store/models/discount.ts +23 -10
- package/api/src/store/models/index.ts +13 -2
- package/api/src/store/models/promotion-code.ts +295 -18
- package/api/src/store/models/types.ts +30 -1
- package/api/tests/libs/session.spec.ts +48 -27
- package/blocklet.yml +1 -1
- package/doc/vendor_fulfillment_system.md +38 -38
- package/package.json +20 -20
- package/src/app.tsx +2 -0
- package/src/components/customer/link.tsx +1 -1
- package/src/components/discount/discount-info.tsx +178 -0
- package/src/components/invoice/table.tsx +140 -48
- package/src/components/invoice-pdf/styles.ts +6 -0
- package/src/components/invoice-pdf/template.tsx +59 -33
- package/src/components/metadata/form.tsx +14 -5
- package/src/components/payment-link/actions.tsx +42 -0
- package/src/components/price/form.tsx +91 -65
- package/src/components/product/vendor-config.tsx +5 -3
- package/src/components/promotion/active-redemptions.tsx +534 -0
- package/src/components/promotion/currency-multi-select.tsx +350 -0
- package/src/components/promotion/currency-restrictions.tsx +117 -0
- package/src/components/promotion/product-select.tsx +292 -0
- package/src/components/promotion/promotion-code-form.tsx +534 -0
- package/src/components/subscription/portal/list.tsx +6 -1
- package/src/components/subscription/vendor-service-list.tsx +13 -2
- package/src/locales/en.tsx +253 -26
- package/src/locales/zh.tsx +222 -1
- package/src/pages/admin/billing/subscriptions/detail.tsx +5 -0
- package/src/pages/admin/products/coupons/applicable-products.tsx +166 -0
- package/src/pages/admin/products/coupons/create.tsx +612 -0
- package/src/pages/admin/products/coupons/detail.tsx +538 -0
- package/src/pages/admin/products/coupons/edit.tsx +127 -0
- package/src/pages/admin/products/coupons/index.tsx +210 -3
- package/src/pages/admin/products/index.tsx +22 -3
- package/src/pages/admin/products/products/detail.tsx +12 -2
- package/src/pages/admin/products/promotion-codes/actions.tsx +103 -0
- package/src/pages/admin/products/promotion-codes/create.tsx +235 -0
- package/src/pages/admin/products/promotion-codes/detail.tsx +416 -0
- package/src/pages/admin/products/promotion-codes/list.tsx +247 -0
- package/src/pages/admin/products/promotion-codes/verification-config.tsx +327 -0
- package/src/pages/admin/products/vendors/index.tsx +17 -5
- package/src/pages/customer/subscription/detail.tsx +5 -0
- package/vite.config.ts +4 -3
package/src/locales/zh.tsx
CHANGED
|
@@ -5,6 +5,7 @@ export default flat({
|
|
|
5
5
|
redirecting: '跳转中...',
|
|
6
6
|
title: '名称',
|
|
7
7
|
estimated: '预估',
|
|
8
|
+
active: '生效中',
|
|
8
9
|
metadata: {
|
|
9
10
|
label: '元数据',
|
|
10
11
|
description: '添加自定义键值对以存储有关此计量器的其他信息。',
|
|
@@ -47,6 +48,20 @@ export default flat({
|
|
|
47
48
|
cancel: '取消',
|
|
48
49
|
know: '知道了',
|
|
49
50
|
confirm: '确认',
|
|
51
|
+
edit: '编辑',
|
|
52
|
+
delete: '删除',
|
|
53
|
+
deleting: '删除中...',
|
|
54
|
+
yes: '是',
|
|
55
|
+
no: '否',
|
|
56
|
+
never: '永不',
|
|
57
|
+
created: '创建时间',
|
|
58
|
+
updated: '更新时间',
|
|
59
|
+
valid: '有效',
|
|
60
|
+
events: '事件',
|
|
61
|
+
details: '详情',
|
|
62
|
+
discount: '折扣',
|
|
63
|
+
inactive: '无效',
|
|
64
|
+
deleted: '已删除',
|
|
50
65
|
days: {
|
|
51
66
|
sunday: '星期日',
|
|
52
67
|
monday: '星期一',
|
|
@@ -551,6 +566,206 @@ export default flat({
|
|
|
551
566
|
coupon: {
|
|
552
567
|
create: '创建优惠券',
|
|
553
568
|
view: '查看优惠券',
|
|
569
|
+
description: '优惠券可用于对发票、订阅或整个客户账户提供折扣。',
|
|
570
|
+
listTitle: '优惠券',
|
|
571
|
+
terms: '优惠条款',
|
|
572
|
+
tab: '优惠券',
|
|
573
|
+
redemptions: '兑换次数',
|
|
574
|
+
expires: '过期时间',
|
|
575
|
+
noDiscount: '无折扣',
|
|
576
|
+
name: '名称',
|
|
577
|
+
nameHelp: '这将显示在客户的收据和发票上。',
|
|
578
|
+
desc: '描述',
|
|
579
|
+
descriptionInternal: '此描述仅供内部使用,不会向客户显示。',
|
|
580
|
+
id: 'ID',
|
|
581
|
+
idOptional: 'ID(可选)',
|
|
582
|
+
idHelp: '这将在 API 中标识此优惠券。我们建议将此留空,以便我们为您生成 ID。',
|
|
583
|
+
type: '类型',
|
|
584
|
+
percentageOff: '百分比折扣',
|
|
585
|
+
fixedAmountOff: '固定金额折扣',
|
|
586
|
+
percentageDiscount: '百分比折扣',
|
|
587
|
+
fixedAmount: '折扣金额',
|
|
588
|
+
currency: '货币',
|
|
589
|
+
applyToProducts: '应用于特定产品',
|
|
590
|
+
duration: '持续时间',
|
|
591
|
+
once: '一次',
|
|
592
|
+
forever: '永久',
|
|
593
|
+
repeating: '重复',
|
|
594
|
+
durationHelp: '对于订阅和客户,这决定了此优惠券一旦兑换后的有效期。一次性发票接受"一次"和"永久"优惠券。',
|
|
595
|
+
durationInMonths: '持续月数',
|
|
596
|
+
redemptionLimits: '兑换限制',
|
|
597
|
+
limitDateRange: '限制客户可以兑换此优惠券的日期范围',
|
|
598
|
+
limitTotalNumber: '限制此优惠券可以被兑换的总次数',
|
|
599
|
+
codes: '代码',
|
|
600
|
+
useCustomerFacingCodes: '使用面向客户的优惠券代码',
|
|
601
|
+
promotionCodes: '促销码',
|
|
602
|
+
promotionCodesHelp: '促销码将在优惠券保存后创建。',
|
|
603
|
+
addPromotionCode: '添加促销码',
|
|
604
|
+
eligibleFirstTime: '仅限首次订单(每个用户只能享受一次优惠券折扣)',
|
|
605
|
+
limitNumberRedemptions: '限制此代码可以被兑换的次数',
|
|
606
|
+
addExpirationDate: '添加过期日期',
|
|
607
|
+
requireMinimumOrder: '要求最小订单金额',
|
|
608
|
+
addAnotherCode: '添加另一个代码',
|
|
609
|
+
saved: '优惠券保存成功',
|
|
610
|
+
// 促销码表单
|
|
611
|
+
codeOptional: '代码(可选)',
|
|
612
|
+
codeHelp: '留空将在创建时自动生成',
|
|
613
|
+
generate: '生成',
|
|
614
|
+
verificationType: '验证类型',
|
|
615
|
+
codeOnly: '仅代码',
|
|
616
|
+
nftVerification: 'NFT 验证',
|
|
617
|
+
vcVerification: '通行证验证',
|
|
618
|
+
userWhitelist: '限制特定用户',
|
|
619
|
+
nftSettings: 'NFT 验证设置',
|
|
620
|
+
vcSettings: '通行证验证设置',
|
|
621
|
+
userWhitelistSettings: '特定用户设置',
|
|
622
|
+
nftAddresses: 'NFT 地址',
|
|
623
|
+
nftAddressesPlaceholder: '输入 NFT 合约地址',
|
|
624
|
+
nftTags: 'NFT 标签',
|
|
625
|
+
nftTagsPlaceholder: '输入 NFT 标签',
|
|
626
|
+
trustedIssuers: '可信发行者',
|
|
627
|
+
trustedIssuersPlaceholder: '输入可信发行者',
|
|
628
|
+
trustedParents: '可信 NFT 工厂',
|
|
629
|
+
trustedParentsPlaceholder: '输入可信 NFT 工厂',
|
|
630
|
+
minBalance: 'NFT 所需数量',
|
|
631
|
+
requiredRoles: '必需角色',
|
|
632
|
+
requiredRolesPlaceholder: '输入必需角色',
|
|
633
|
+
trustedVcIssuers: '可信 VC 发行者',
|
|
634
|
+
trustedVcIssuersPlaceholder: '输入可信 VC 发行者 DID',
|
|
635
|
+
customerDids: '客户 DID',
|
|
636
|
+
customerDidsPlaceholder: '输入客户 DID',
|
|
637
|
+
customerDidsHelp: '您可以选择现有客户或手动输入DID',
|
|
638
|
+
maxRedemptions: '最大兑换次数',
|
|
639
|
+
expiresAt: '过期时间',
|
|
640
|
+
pressEnterToAdd: '按回车键添加',
|
|
641
|
+
// 更新的段落名称
|
|
642
|
+
couponConfiguration: '优惠券配置',
|
|
643
|
+
selectProducts: '选择产品',
|
|
644
|
+
noProductsSelected: '未选择产品',
|
|
645
|
+
newCode: '新代码',
|
|
646
|
+
createPromotionCode: '创建促销码',
|
|
647
|
+
promotionCodeDescription: '为此优惠券创建促销码,让客户兑换折扣。',
|
|
648
|
+
minimumAmount: '最低金额',
|
|
649
|
+
minimumAmountHelp: '为不同货币设置最低订单金额',
|
|
650
|
+
discountAmount: '折扣金额',
|
|
651
|
+
currencies: '货币',
|
|
652
|
+
times: '次',
|
|
653
|
+
months: '月',
|
|
654
|
+
addProduct: '添加另一个产品',
|
|
655
|
+
unlimited: '不限量',
|
|
656
|
+
inactive: '优惠券已停用',
|
|
657
|
+
inactiveTip: '此优惠券当前处于停用状态,无法使用。',
|
|
658
|
+
discount: '折扣',
|
|
659
|
+
updated: '更新时间',
|
|
660
|
+
timesRedeemed: '已使用次数',
|
|
661
|
+
deletedSuccessfully: '优惠券删除成功',
|
|
662
|
+
deleteConfirmTitle: '删除优惠券',
|
|
663
|
+
deleteConfirmMessage: '确定要删除此优惠券吗?此操作无法撤销。',
|
|
664
|
+
cannotDeleteWithPromotionCodes: '存在促销码时无法删除优惠券',
|
|
665
|
+
couponTermsPercentage: '{percent}%',
|
|
666
|
+
couponTermsFixedAmount: '{amount} {symbol}',
|
|
667
|
+
couponTerms: {
|
|
668
|
+
forever: '永久享 {couponOff} 折扣',
|
|
669
|
+
once: '单次享 {couponOff} 折扣',
|
|
670
|
+
repeating: '{months} 个月内享 {couponOff} 折扣',
|
|
671
|
+
},
|
|
672
|
+
couponTermsDuration: {
|
|
673
|
+
forever: '永久',
|
|
674
|
+
once: '单次',
|
|
675
|
+
repeating: '{months} 个月',
|
|
676
|
+
},
|
|
677
|
+
applicableProducts: '适用产品',
|
|
678
|
+
noApplicableProducts: '此优惠券适用于所有产品',
|
|
679
|
+
activeRedemptions: '兑换使用记录',
|
|
680
|
+
customers: '客户',
|
|
681
|
+
subscriptions: '订阅',
|
|
682
|
+
noCustomersFound: '未找到客户',
|
|
683
|
+
noCustomersRedeem: '当前没有客户使用此优惠券',
|
|
684
|
+
noSubscriptionsFound: '未找到订阅',
|
|
685
|
+
noSubscriptionsRedeem: '当前没有订阅使用此优惠券',
|
|
686
|
+
rename: '重命名',
|
|
687
|
+
renameCoupon: '重命名优惠券',
|
|
688
|
+
updateCouponDetails: '更新优惠券详情',
|
|
689
|
+
nameWillAppear: '此名称将显示在客户的收据和发票上。',
|
|
690
|
+
updateCouponButton: '更新优惠券',
|
|
691
|
+
discountStart: '折扣开始时间',
|
|
692
|
+
// Additional fields for active-redemptions
|
|
693
|
+
usageStats: '使用统计',
|
|
694
|
+
totalDiscounts: '总折扣记录',
|
|
695
|
+
uniqueSessions: '独立会话',
|
|
696
|
+
uniqueSubscriptions: '独立订阅',
|
|
697
|
+
promotionCodesUsed: '使用的促销码',
|
|
698
|
+
noExpiration: '无过期时间',
|
|
699
|
+
usagePeriod: '使用时期',
|
|
700
|
+
firstUsed: '首次使用',
|
|
701
|
+
lastUsed: '最后使用',
|
|
702
|
+
promotionCodeInfo: '促销码信息',
|
|
703
|
+
directCouponUsage: '直接优惠券使用',
|
|
704
|
+
usageLimit: '使用次数',
|
|
705
|
+
discountInfo: '折扣信息',
|
|
706
|
+
discountPeriod: '折扣有效期',
|
|
707
|
+
couponExpires: '优惠券过期',
|
|
708
|
+
totalSavings: '累计优惠',
|
|
709
|
+
viewPromotionCode: '查看促销码',
|
|
710
|
+
},
|
|
711
|
+
discount: {
|
|
712
|
+
totalSaved: '总共节省',
|
|
713
|
+
timesUsed: '使用次数',
|
|
714
|
+
appliedDiscounts: '应用的优惠',
|
|
715
|
+
},
|
|
716
|
+
promotionCode: {
|
|
717
|
+
code: '代码',
|
|
718
|
+
status: '状态',
|
|
719
|
+
redemptions: '兑换次数',
|
|
720
|
+
expires: '过期时间',
|
|
721
|
+
created: '创建时间',
|
|
722
|
+
actions: '操作',
|
|
723
|
+
active: '生效中',
|
|
724
|
+
inactive: '已停用',
|
|
725
|
+
expired: '已过期',
|
|
726
|
+
maxedOut: '已用尽',
|
|
727
|
+
viewDetails: '查看详情',
|
|
728
|
+
edit: '编辑促销码',
|
|
729
|
+
delete: '删除促销码',
|
|
730
|
+
archive: '存档促销码',
|
|
731
|
+
archiveTip: '存档后,此促销码将不再允许新兑换。确定要存档此促销码吗?',
|
|
732
|
+
deleteTip: '删除将永久删除此促销码。确定要删除此促销码吗?',
|
|
733
|
+
title: '促销码',
|
|
734
|
+
id: 'ID',
|
|
735
|
+
type: '类型',
|
|
736
|
+
couponId: '优惠券ID',
|
|
737
|
+
maxRedemptions: '最大兑换次数',
|
|
738
|
+
timesRedeemed: '已兑换次数',
|
|
739
|
+
verificationType: '验证类型',
|
|
740
|
+
updated: '更新时间',
|
|
741
|
+
activate: '激活',
|
|
742
|
+
deactivate: '停用',
|
|
743
|
+
inactiveTitle: '促销码已停用',
|
|
744
|
+
inactiveTip: '此促销码目前已停用,无法使用。',
|
|
745
|
+
deleteSuccess: '促销码删除成功',
|
|
746
|
+
activateSuccess: '促销码激活成功',
|
|
747
|
+
deactivateSuccess: '促销码停用成功',
|
|
748
|
+
verificationConfig: '验证配置',
|
|
749
|
+
nftAddresses: 'NFT地址',
|
|
750
|
+
nftTags: 'NFT标签',
|
|
751
|
+
minBalance: '最小余额',
|
|
752
|
+
requiredRoles: '所需角色',
|
|
753
|
+
trustedIssuers: '可信签发方',
|
|
754
|
+
allowedUsers: '允许的用户',
|
|
755
|
+
noNftConfig: '无NFT配置',
|
|
756
|
+
noVcConfig: '无VC配置',
|
|
757
|
+
noUsersSpecified: '未指定用户',
|
|
758
|
+
codeVerificationOnly: '仅代码验证',
|
|
759
|
+
unlimited: '无限制',
|
|
760
|
+
never: '永不过期',
|
|
761
|
+
yes: '是',
|
|
762
|
+
no: '否',
|
|
763
|
+
verificationTypeMap: {
|
|
764
|
+
code: '仅代码',
|
|
765
|
+
nft: 'NFT验证',
|
|
766
|
+
vc: '通行证验证',
|
|
767
|
+
user_restricted: '限制特定用户',
|
|
768
|
+
},
|
|
554
769
|
},
|
|
555
770
|
|
|
556
771
|
paymentLink: {
|
|
@@ -568,7 +783,11 @@ export default flat({
|
|
|
568
783
|
addProduct: '添加另一个产品',
|
|
569
784
|
requireBillingAddress: '收集客户的账单地址',
|
|
570
785
|
requirePhoneNumber: '收集客户的电话号码',
|
|
571
|
-
allowPromotionCodes: '
|
|
786
|
+
allowPromotionCodes: '允许促销码',
|
|
787
|
+
enablePromotionCodes: '启用促销码',
|
|
788
|
+
disablePromotionCodes: '禁用促销码',
|
|
789
|
+
enablePromotionCodesTip: '为此支付链接启用促销码功能。客户在结账时将能够使用折扣代码。',
|
|
790
|
+
disablePromotionCodesTip: '为此支付链接禁用促销码功能。客户在结账时将无法使用折扣代码。',
|
|
572
791
|
includeFreeTrial: '包含免费试用',
|
|
573
792
|
noStakeRequired: '无需质押',
|
|
574
793
|
showProductFeatures: '显示产品特性',
|
|
@@ -931,6 +1150,7 @@ export default flat({
|
|
|
931
1150
|
ended: '结束于 {date}',
|
|
932
1151
|
renew: '下次续费: {date}',
|
|
933
1152
|
discount: '折扣',
|
|
1153
|
+
activeDiscount: '当前优惠',
|
|
934
1154
|
startedAt: '开始于',
|
|
935
1155
|
nextInvoice: '下次账单时间',
|
|
936
1156
|
nextInvoiceAmount: '下次账单金额',
|
|
@@ -1361,6 +1581,7 @@ export default flat({
|
|
|
1361
1581
|
relatedInvoice: '关联账单',
|
|
1362
1582
|
donation: '打赏记录',
|
|
1363
1583
|
creditsInfo: '总共包含 {amount} {currency}',
|
|
1584
|
+
appliedDiscounts: '已应用优惠',
|
|
1364
1585
|
},
|
|
1365
1586
|
payout: {
|
|
1366
1587
|
empty: '没有收款记录',
|
|
@@ -30,6 +30,7 @@ import SectionHeader from '../../../../components/section/header';
|
|
|
30
30
|
import SubscriptionActions from '../../../../components/subscription/actions';
|
|
31
31
|
import SubscriptionItemList from '../../../../components/subscription/items';
|
|
32
32
|
import SubscriptionMetrics from '../../../../components/subscription/metrics';
|
|
33
|
+
import DiscountInfo from '../../../../components/discount/discount-info';
|
|
33
34
|
import { goBackOrFallback } from '../../../../libs/util';
|
|
34
35
|
import InfoRowGroup from '../../../../components/info-row-group';
|
|
35
36
|
|
|
@@ -313,6 +314,10 @@ export default function SubscriptionDetail(props: { id: string }) {
|
|
|
313
314
|
</InfoRowGroup>
|
|
314
315
|
</Box>
|
|
315
316
|
<Divider />
|
|
317
|
+
|
|
318
|
+
{/* Discount Information */}
|
|
319
|
+
{(data as any).discountStats && <DiscountInfo discountStats={(data as any).discountStats} />}
|
|
320
|
+
|
|
316
321
|
<Box className="section">
|
|
317
322
|
<SectionHeader title={t('admin.product.pricing')} />
|
|
318
323
|
<Box className="section-body">
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/* eslint-disable react/no-unstable-nested-components */
|
|
2
|
+
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
3
|
+
import {
|
|
4
|
+
findCurrency,
|
|
5
|
+
formatPrice,
|
|
6
|
+
formatTime,
|
|
7
|
+
Table,
|
|
8
|
+
TruncatedText,
|
|
9
|
+
useMobile,
|
|
10
|
+
usePaymentContext,
|
|
11
|
+
} from '@blocklet/payment-react';
|
|
12
|
+
import type { TProductExpanded } from '@blocklet/payment-types';
|
|
13
|
+
import { Avatar, Stack, Typography, Box } from '@mui/material';
|
|
14
|
+
import { styled } from '@mui/system';
|
|
15
|
+
import { Link } from 'react-router-dom';
|
|
16
|
+
|
|
17
|
+
interface Props {
|
|
18
|
+
products: any[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default function ApplicableProductsList({ products }: Props) {
|
|
22
|
+
const { t } = useLocaleContext();
|
|
23
|
+
const { isMobile } = useMobile();
|
|
24
|
+
const { settings } = usePaymentContext();
|
|
25
|
+
|
|
26
|
+
const getPriceDisplay = (product: TProductExpanded) => {
|
|
27
|
+
if (!product.prices || product.prices.length === 0) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (product.prices.length === 1) {
|
|
32
|
+
const price = product.prices[0];
|
|
33
|
+
if (price) {
|
|
34
|
+
const currency = findCurrency(settings.paymentMethods, price.currency_id ?? '') || settings.baseCurrency;
|
|
35
|
+
return formatPrice(price, currency!);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return t('admin.price.count', { count: product.prices.length });
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const size = isMobile ? { width: 24, height: 24 } : { width: 48, height: 48 };
|
|
43
|
+
|
|
44
|
+
const columns = [
|
|
45
|
+
{
|
|
46
|
+
label: t('admin.subscription.product'),
|
|
47
|
+
name: 'name',
|
|
48
|
+
options: {
|
|
49
|
+
customBodyRenderLite: (_: string, index: number) => {
|
|
50
|
+
const product = products[index];
|
|
51
|
+
return (
|
|
52
|
+
<Stack
|
|
53
|
+
sx={{
|
|
54
|
+
flexDirection: 'row',
|
|
55
|
+
flexWrap: 'wrap',
|
|
56
|
+
alignItems: 'center',
|
|
57
|
+
gap: {
|
|
58
|
+
xs: 1,
|
|
59
|
+
sm: 2,
|
|
60
|
+
},
|
|
61
|
+
}}>
|
|
62
|
+
<Box>
|
|
63
|
+
{product.images && product.images.length > 0 ? (
|
|
64
|
+
<Avatar key={product.id} src={product.images[0]} alt={product.name} variant="rounded" sx={size} />
|
|
65
|
+
) : (
|
|
66
|
+
<Avatar key={product.id} variant="rounded" sx={size}>
|
|
67
|
+
{product.name.slice(0, 1)}
|
|
68
|
+
</Avatar>
|
|
69
|
+
)}
|
|
70
|
+
</Box>
|
|
71
|
+
<Box>
|
|
72
|
+
<Typography
|
|
73
|
+
sx={{
|
|
74
|
+
color: 'text.primary',
|
|
75
|
+
fontWeight: 600,
|
|
76
|
+
}}>
|
|
77
|
+
<Link to={`/admin/products/${product.id}`}>
|
|
78
|
+
<TruncatedText text={product.name} maxLength={isMobile ? 20 : 50} useWidth />
|
|
79
|
+
</Link>
|
|
80
|
+
</Typography>
|
|
81
|
+
{!isMobile && (
|
|
82
|
+
<Typography
|
|
83
|
+
sx={{
|
|
84
|
+
color: 'text.secondary',
|
|
85
|
+
whiteSpace: 'nowrap',
|
|
86
|
+
}}>
|
|
87
|
+
{getPriceDisplay(product)}
|
|
88
|
+
</Typography>
|
|
89
|
+
)}
|
|
90
|
+
</Box>
|
|
91
|
+
</Stack>
|
|
92
|
+
);
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
isMobile && {
|
|
97
|
+
label: t('admin.product.pricing'),
|
|
98
|
+
name: 'prices',
|
|
99
|
+
options: {
|
|
100
|
+
customBodyRenderLite: (_: string, index: number) => {
|
|
101
|
+
const product = products[index];
|
|
102
|
+
return <Link to={`/admin/products/${product.id}`}>{getPriceDisplay(product)}</Link>;
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
label: t('admin.product.type.label'),
|
|
108
|
+
name: 'type',
|
|
109
|
+
options: {
|
|
110
|
+
customBodyRenderLite: (_: string, index: number) => {
|
|
111
|
+
const product = products[index];
|
|
112
|
+
return t(`admin.product.type.${product.type}`);
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
label: t('common.created'),
|
|
118
|
+
name: 'created_at',
|
|
119
|
+
options: {
|
|
120
|
+
customBodyRenderLite: (_: string, index: number) => {
|
|
121
|
+
const product = products[index];
|
|
122
|
+
return formatTime(product.created_at);
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
].filter(Boolean);
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<ApplicableProductsTableRoot>
|
|
130
|
+
<Table
|
|
131
|
+
data={products}
|
|
132
|
+
columns={columns}
|
|
133
|
+
loading={false}
|
|
134
|
+
footer={false}
|
|
135
|
+
toolbar={false}
|
|
136
|
+
components={{
|
|
137
|
+
TableToolbar: () => null,
|
|
138
|
+
TableFooter: () => null,
|
|
139
|
+
}}
|
|
140
|
+
mobileTDFlexDirection="row"
|
|
141
|
+
options={{
|
|
142
|
+
count: products.length,
|
|
143
|
+
page: 0,
|
|
144
|
+
rowsPerPage: 100,
|
|
145
|
+
selectableRows: 'none',
|
|
146
|
+
pagination: false,
|
|
147
|
+
}}
|
|
148
|
+
emptyNodeText={t('admin.coupon.noApplicableProducts')}
|
|
149
|
+
/>
|
|
150
|
+
</ApplicableProductsTableRoot>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const ApplicableProductsTableRoot = styled(Box)`
|
|
155
|
+
@media (max-width: ${({ theme }) => theme.breakpoints.values.md}px) {
|
|
156
|
+
.MuiTable-root > .MuiTableBody-root > .MuiTableRow-root > td.MuiTableCell-root {
|
|
157
|
+
align-items: center;
|
|
158
|
+
padding: 4px 0;
|
|
159
|
+
> div {
|
|
160
|
+
width: fit-content;
|
|
161
|
+
flex: inherit;
|
|
162
|
+
font-size: 14px;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
`;
|