payment-kit 1.13.15

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.
Files changed (222) hide show
  1. package/.eslintrc.js +15 -0
  2. package/README.md +3 -0
  3. package/api/dev.ts +6 -0
  4. package/api/hooks/pre-start.js +12 -0
  5. package/api/src/hooks/pre-start.ts +21 -0
  6. package/api/src/index.ts +92 -0
  7. package/api/src/jobs/event.ts +72 -0
  8. package/api/src/jobs/invoice.ts +148 -0
  9. package/api/src/jobs/payment.ts +208 -0
  10. package/api/src/jobs/subscription.ts +301 -0
  11. package/api/src/jobs/webhook.ts +113 -0
  12. package/api/src/libs/audit.ts +73 -0
  13. package/api/src/libs/auth.ts +40 -0
  14. package/api/src/libs/chain/arcblock.ts +13 -0
  15. package/api/src/libs/dayjs.ts +17 -0
  16. package/api/src/libs/env.ts +5 -0
  17. package/api/src/libs/hooks.ts +42 -0
  18. package/api/src/libs/logger.ts +27 -0
  19. package/api/src/libs/middleware.ts +12 -0
  20. package/api/src/libs/payment.ts +53 -0
  21. package/api/src/libs/queue/index.ts +263 -0
  22. package/api/src/libs/queue/store.ts +47 -0
  23. package/api/src/libs/security.ts +95 -0
  24. package/api/src/libs/session.ts +164 -0
  25. package/api/src/libs/util.ts +93 -0
  26. package/api/src/locales/en.ts +3 -0
  27. package/api/src/locales/index.ts +37 -0
  28. package/api/src/locales/zh.ts +3 -0
  29. package/api/src/routes/checkout-sessions.ts +536 -0
  30. package/api/src/routes/connect/collect.ts +109 -0
  31. package/api/src/routes/connect/pay.ts +116 -0
  32. package/api/src/routes/connect/setup.ts +121 -0
  33. package/api/src/routes/connect/shared.ts +410 -0
  34. package/api/src/routes/connect/subscribe.ts +128 -0
  35. package/api/src/routes/customers.ts +70 -0
  36. package/api/src/routes/events.ts +76 -0
  37. package/api/src/routes/index.ts +59 -0
  38. package/api/src/routes/invoices.ts +126 -0
  39. package/api/src/routes/payment-currencies.ts +38 -0
  40. package/api/src/routes/payment-intents.ts +122 -0
  41. package/api/src/routes/payment-links.ts +221 -0
  42. package/api/src/routes/payment-methods.ts +39 -0
  43. package/api/src/routes/prices.ts +134 -0
  44. package/api/src/routes/products.ts +191 -0
  45. package/api/src/routes/settings.ts +33 -0
  46. package/api/src/routes/subscription-items.ts +148 -0
  47. package/api/src/routes/subscriptions.ts +254 -0
  48. package/api/src/routes/usage-records.ts +120 -0
  49. package/api/src/routes/webhook-attempts.ts +57 -0
  50. package/api/src/routes/webhook-endpoints.ts +105 -0
  51. package/api/src/store/migrate.ts +16 -0
  52. package/api/src/store/migrations/20230905-genesis.ts +52 -0
  53. package/api/src/store/migrations/20230911-seeding.ts +145 -0
  54. package/api/src/store/models/checkout-session.ts +395 -0
  55. package/api/src/store/models/coupon.ts +137 -0
  56. package/api/src/store/models/customer.ts +199 -0
  57. package/api/src/store/models/discount.ts +116 -0
  58. package/api/src/store/models/event.ts +111 -0
  59. package/api/src/store/models/index.ts +165 -0
  60. package/api/src/store/models/invoice-item.ts +185 -0
  61. package/api/src/store/models/invoice.ts +492 -0
  62. package/api/src/store/models/job.ts +75 -0
  63. package/api/src/store/models/payment-currency.ts +139 -0
  64. package/api/src/store/models/payment-intent.ts +282 -0
  65. package/api/src/store/models/payment-link.ts +219 -0
  66. package/api/src/store/models/payment-method.ts +169 -0
  67. package/api/src/store/models/price.ts +266 -0
  68. package/api/src/store/models/product.ts +162 -0
  69. package/api/src/store/models/promotion-code.ts +112 -0
  70. package/api/src/store/models/setup-intent.ts +206 -0
  71. package/api/src/store/models/subscription-item.ts +103 -0
  72. package/api/src/store/models/subscription-schedule.ts +157 -0
  73. package/api/src/store/models/subscription.ts +307 -0
  74. package/api/src/store/models/types.ts +406 -0
  75. package/api/src/store/models/usage-record.ts +132 -0
  76. package/api/src/store/models/webhook-attempt.ts +96 -0
  77. package/api/src/store/models/webhook-endpoint.ts +96 -0
  78. package/api/src/store/sequelize.ts +15 -0
  79. package/api/third.d.ts +28 -0
  80. package/blocklet.md +3 -0
  81. package/blocklet.yml +89 -0
  82. package/index.html +14 -0
  83. package/logo.png +0 -0
  84. package/package.json +133 -0
  85. package/public/.gitkeep +0 -0
  86. package/screenshots/.gitkeep +0 -0
  87. package/screenshots/1-subscription.png +0 -0
  88. package/screenshots/2-customer-1.png +0 -0
  89. package/screenshots/3-customer-2.png +0 -0
  90. package/screenshots/4-admin-3.png +0 -0
  91. package/screenshots/5-admin-4.png +0 -0
  92. package/scripts/build-clean.js +6 -0
  93. package/scripts/bump-version.mjs +35 -0
  94. package/src/app.tsx +68 -0
  95. package/src/components/actions.tsx +85 -0
  96. package/src/components/blockchain/tx.tsx +29 -0
  97. package/src/components/checkout/amount.tsx +24 -0
  98. package/src/components/checkout/error.tsx +30 -0
  99. package/src/components/checkout/footer.tsx +12 -0
  100. package/src/components/checkout/form/address.tsx +38 -0
  101. package/src/components/checkout/form/index.tsx +295 -0
  102. package/src/components/checkout/header.tsx +23 -0
  103. package/src/components/checkout/pay.tsx +222 -0
  104. package/src/components/checkout/product-card.tsx +56 -0
  105. package/src/components/checkout/product-item.tsx +37 -0
  106. package/src/components/checkout/skeleton/overview.tsx +21 -0
  107. package/src/components/checkout/skeleton/payment.tsx +35 -0
  108. package/src/components/checkout/success.tsx +183 -0
  109. package/src/components/checkout/summary.tsx +34 -0
  110. package/src/components/collapse.tsx +50 -0
  111. package/src/components/confirm.tsx +55 -0
  112. package/src/components/copyable.tsx +38 -0
  113. package/src/components/currency.tsx +15 -0
  114. package/src/components/customer/actions.tsx +73 -0
  115. package/src/components/data.tsx +20 -0
  116. package/src/components/drawer-form.tsx +77 -0
  117. package/src/components/error-fallback.tsx +7 -0
  118. package/src/components/error.tsx +39 -0
  119. package/src/components/event/list.tsx +217 -0
  120. package/src/components/info-card.tsx +40 -0
  121. package/src/components/info-metric.tsx +35 -0
  122. package/src/components/info-row.tsx +28 -0
  123. package/src/components/input.tsx +40 -0
  124. package/src/components/invoice/action.tsx +94 -0
  125. package/src/components/invoice/list.tsx +225 -0
  126. package/src/components/invoice/table.tsx +110 -0
  127. package/src/components/layout.tsx +70 -0
  128. package/src/components/livemode.tsx +23 -0
  129. package/src/components/metadata/editor.tsx +57 -0
  130. package/src/components/metadata/form.tsx +45 -0
  131. package/src/components/payment-intent/actions.tsx +81 -0
  132. package/src/components/payment-intent/list.tsx +204 -0
  133. package/src/components/payment-link/actions.tsx +114 -0
  134. package/src/components/payment-link/after-pay.tsx +87 -0
  135. package/src/components/payment-link/before-pay.tsx +175 -0
  136. package/src/components/payment-link/item.tsx +135 -0
  137. package/src/components/payment-link/product-select.tsx +66 -0
  138. package/src/components/payment-link/rename.tsx +64 -0
  139. package/src/components/portal/invoice/list.tsx +110 -0
  140. package/src/components/portal/subscription/cancel.tsx +83 -0
  141. package/src/components/portal/subscription/list.tsx +232 -0
  142. package/src/components/price/actions.tsx +21 -0
  143. package/src/components/price/form.tsx +292 -0
  144. package/src/components/product/actions.tsx +125 -0
  145. package/src/components/product/add-price.tsx +59 -0
  146. package/src/components/product/create.tsx +97 -0
  147. package/src/components/product/edit-price.tsx +75 -0
  148. package/src/components/product/edit.tsx +67 -0
  149. package/src/components/product/features.tsx +32 -0
  150. package/src/components/product/form.tsx +76 -0
  151. package/src/components/relative-time.tsx +41 -0
  152. package/src/components/section/header.tsx +29 -0
  153. package/src/components/status.tsx +12 -0
  154. package/src/components/subscription/actions/cancel.tsx +66 -0
  155. package/src/components/subscription/actions/index.tsx +172 -0
  156. package/src/components/subscription/actions/pause.tsx +83 -0
  157. package/src/components/subscription/items/actions.tsx +31 -0
  158. package/src/components/subscription/items/index.tsx +107 -0
  159. package/src/components/subscription/list.tsx +200 -0
  160. package/src/components/switch.tsx +48 -0
  161. package/src/components/table.tsx +66 -0
  162. package/src/components/uploader.tsx +81 -0
  163. package/src/components/webhook/attempts.tsx +149 -0
  164. package/src/contexts/products.tsx +42 -0
  165. package/src/contexts/session.ts +10 -0
  166. package/src/contexts/settings.tsx +54 -0
  167. package/src/env.d.ts +17 -0
  168. package/src/global.css +97 -0
  169. package/src/hooks/mobile.ts +15 -0
  170. package/src/index.tsx +6 -0
  171. package/src/libs/api.ts +19 -0
  172. package/src/libs/dayjs.ts +17 -0
  173. package/src/libs/util.ts +474 -0
  174. package/src/locales/en.tsx +395 -0
  175. package/src/locales/index.tsx +8 -0
  176. package/src/locales/zh.tsx +389 -0
  177. package/src/pages/admin/billing/index.tsx +56 -0
  178. package/src/pages/admin/billing/invoices/detail.tsx +215 -0
  179. package/src/pages/admin/billing/invoices/index.tsx +5 -0
  180. package/src/pages/admin/billing/subscriptions/detail.tsx +237 -0
  181. package/src/pages/admin/billing/subscriptions/index.tsx +5 -0
  182. package/src/pages/admin/customers/customers/detail.tsx +209 -0
  183. package/src/pages/admin/customers/customers/index.tsx +109 -0
  184. package/src/pages/admin/customers/index.tsx +47 -0
  185. package/src/pages/admin/developers/events/detail.tsx +77 -0
  186. package/src/pages/admin/developers/events/index.tsx +5 -0
  187. package/src/pages/admin/developers/index.tsx +60 -0
  188. package/src/pages/admin/developers/logs.tsx +3 -0
  189. package/src/pages/admin/developers/overview.tsx +3 -0
  190. package/src/pages/admin/developers/webhooks/detail.tsx +109 -0
  191. package/src/pages/admin/developers/webhooks/index.tsx +102 -0
  192. package/src/pages/admin/index.tsx +120 -0
  193. package/src/pages/admin/overview.tsx +3 -0
  194. package/src/pages/admin/payments/index.tsx +65 -0
  195. package/src/pages/admin/payments/intents/detail.tsx +205 -0
  196. package/src/pages/admin/payments/intents/index.tsx +5 -0
  197. package/src/pages/admin/payments/links/create.tsx +141 -0
  198. package/src/pages/admin/payments/links/detail.tsx +318 -0
  199. package/src/pages/admin/payments/links/index.tsx +167 -0
  200. package/src/pages/admin/products/coupons/index.tsx +3 -0
  201. package/src/pages/admin/products/index.tsx +81 -0
  202. package/src/pages/admin/products/prices/actions.tsx +151 -0
  203. package/src/pages/admin/products/prices/detail.tsx +203 -0
  204. package/src/pages/admin/products/prices/list.tsx +95 -0
  205. package/src/pages/admin/products/pricing-tables.tsx +3 -0
  206. package/src/pages/admin/products/products/create.tsx +105 -0
  207. package/src/pages/admin/products/products/detail.tsx +246 -0
  208. package/src/pages/admin/products/products/index.tsx +154 -0
  209. package/src/pages/admin/settings/branding.tsx +3 -0
  210. package/src/pages/admin/settings/business.tsx +3 -0
  211. package/src/pages/admin/settings/index.tsx +47 -0
  212. package/src/pages/admin/settings/payment-methods.tsx +80 -0
  213. package/src/pages/checkout/index.tsx +38 -0
  214. package/src/pages/checkout/pay.tsx +89 -0
  215. package/src/pages/customer/index.tsx +93 -0
  216. package/src/pages/customer/invoice.tsx +147 -0
  217. package/src/pages/home.tsx +9 -0
  218. package/tsconfig.api.json +9 -0
  219. package/tsconfig.eslint.json +7 -0
  220. package/tsconfig.json +99 -0
  221. package/tsconfig.types.json +11 -0
  222. package/vite.config.ts +19 -0
@@ -0,0 +1,94 @@
1
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
2
+ import Toast from '@arcblock/ux/lib/Toast';
3
+ import type { TInvoiceExpanded } from '@did-pay/types';
4
+ import { useSetState } from 'ahooks';
5
+ import { useNavigate } from 'react-router-dom';
6
+ import type { LiteralUnion } from 'type-fest';
7
+
8
+ import api from '../../libs/api';
9
+ import { formatError } from '../../libs/util';
10
+ import Actions from '../actions';
11
+ import ConfirmDialog from '../confirm';
12
+
13
+ type Props = {
14
+ data: TInvoiceExpanded;
15
+ variant?: LiteralUnion<'compact' | 'normal', string>;
16
+ onChange: (action: string) => void;
17
+ };
18
+
19
+ InvoiceActions.defaultProps = {
20
+ variant: 'compact',
21
+ };
22
+
23
+ export default function InvoiceActions({ data, variant, onChange }: Props) {
24
+ const { t } = useLocaleContext();
25
+ const navigate = useNavigate();
26
+ const [state, setState] = useSetState({
27
+ action: '',
28
+ loading: false,
29
+ });
30
+
31
+ const handleAction = async () => {
32
+ try {
33
+ setState({ loading: true });
34
+ await api.put(`/api/invoices/${data.id}/xxx`).then((res) => res.data);
35
+ Toast.success(t('common.saved'));
36
+ onChange(state.action);
37
+ } catch (err) {
38
+ console.error(err);
39
+ Toast.error(formatError(err));
40
+ } finally {
41
+ setState({ loading: false, action: '' });
42
+ }
43
+ };
44
+
45
+ const actions = [
46
+ {
47
+ label: t('admin.invoice.download'),
48
+ handler: () => setState({ action: 'download' }),
49
+ color: 'primary',
50
+ disabled: data.status !== 'paid',
51
+ },
52
+ {
53
+ label: t('admin.invoice.edit'),
54
+ handler: () => setState({ action: 'edit' }),
55
+ color: 'primary',
56
+ disabled: data.status !== 'draft',
57
+ },
58
+ {
59
+ label: t('admin.invoice.duplicate'),
60
+ handler: () => setState({ action: 'duplicate' }),
61
+ color: 'primary',
62
+ disabled: data.status !== 'draft',
63
+ divider: true,
64
+ },
65
+ {
66
+ label: t('admin.customer.view'),
67
+ handler: () => navigate(`/admin/customers/${data.customer_id}`),
68
+ color: 'primary',
69
+ },
70
+ ];
71
+
72
+ if (variant === 'compact') {
73
+ actions.push({
74
+ label: t('admin.invoice.view'),
75
+ handler: () => navigate(`/admin/billing/${data.id}`),
76
+ color: 'primary',
77
+ });
78
+ }
79
+
80
+ return (
81
+ <>
82
+ <Actions variant={variant} actions={actions} />
83
+ {state.action === 'xxx' && (
84
+ <ConfirmDialog
85
+ onConfirm={handleAction}
86
+ onCancel={() => setState({ action: '' })}
87
+ title={t('admin.invoice.edit')}
88
+ message={t('admin.invoice.edit')}
89
+ loading={state.loading}
90
+ />
91
+ )}
92
+ </>
93
+ );
94
+ }
@@ -0,0 +1,225 @@
1
+ /* eslint-disable react/no-unstable-nested-components */
2
+ import { getDurableData } from '@arcblock/ux/lib/Datatable';
3
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
4
+ import type { TInvoiceExpanded } from '@did-pay/types';
5
+ import { Alert, CircularProgress, ToggleButton, ToggleButtonGroup, Typography } from '@mui/material';
6
+ import { fromUnitToToken } from '@ocap/util';
7
+ import { useRequest } from 'ahooks';
8
+ import { useEffect, useState } from 'react';
9
+ import { useNavigate } from 'react-router-dom';
10
+
11
+ import api from '../../libs/api';
12
+ import { formatTime, getInvoiceStatusColor } from '../../libs/util';
13
+ import Status from '../status';
14
+ import Table from '../table';
15
+ import InvoiceActions from './action';
16
+
17
+ const fetchData = (params: Record<string, any> = {}): Promise<{ list: TInvoiceExpanded[]; count: number }> => {
18
+ const search = new URLSearchParams();
19
+ Object.keys(params).forEach((key) => {
20
+ search.set(key, String(params[key]));
21
+ });
22
+ return api.get(`/api/invoices?${search.toString()}`).then((res) => res.data);
23
+ };
24
+
25
+ type SearchProps = {
26
+ status: string;
27
+ pageSize: number;
28
+ page: number;
29
+ customer_id?: string;
30
+ subscription_id?: string;
31
+ };
32
+
33
+ type ListProps = {
34
+ features?: {
35
+ customer?: boolean;
36
+ toolbar?: boolean;
37
+ footer?: boolean;
38
+ };
39
+ customer_id?: string;
40
+ subscription_id?: string;
41
+ status?: string;
42
+ };
43
+
44
+ const getListKey = (props: ListProps) => {
45
+ if (props.customer_id) {
46
+ return `customer-invoices-${props.customer_id}`;
47
+ }
48
+ if (props.subscription_id) {
49
+ return `subscription-invoices-${props.subscription_id}`;
50
+ }
51
+
52
+ return 'invoices';
53
+ };
54
+
55
+ InvoiceList.defaultProps = {
56
+ features: {
57
+ customer: true,
58
+ filter: true,
59
+ },
60
+ customer_id: '',
61
+ subscription_id: '',
62
+ status: '',
63
+ };
64
+
65
+ export default function InvoiceList({ customer_id, subscription_id, features, status }: ListProps) {
66
+ const listKey = getListKey({ customer_id, subscription_id });
67
+ const persisted = getDurableData(listKey);
68
+
69
+ const { t } = useLocaleContext();
70
+ const navigate = useNavigate();
71
+ const [search, setSearch] = useState<SearchProps>({
72
+ status: status as string,
73
+ customer_id,
74
+ subscription_id,
75
+ pageSize: persisted.rowsPerPage || 20,
76
+ page: persisted.page ? persisted.page + 1 : 1,
77
+ });
78
+
79
+ const { loading, error, data, refresh } = useRequest(() => fetchData(search));
80
+ useEffect(() => {
81
+ refresh();
82
+ }, [search, refresh]);
83
+
84
+ if (error) {
85
+ return <Alert severity="error">{error.message}</Alert>;
86
+ }
87
+
88
+ if (loading || !data) {
89
+ return <CircularProgress />;
90
+ }
91
+
92
+ const columns = [
93
+ {
94
+ label: t('common.amount'),
95
+ name: 'total',
96
+ width: 60,
97
+ options: {
98
+ customBodyRenderLite: (_: string, index: number) => {
99
+ const item = data.list[index];
100
+ return (
101
+ <Typography component="span">
102
+ {fromUnitToToken(item?.total, item?.paymentCurrency.decimal)}&nbsp;
103
+ {item?.paymentCurrency.symbol}
104
+ </Typography>
105
+ );
106
+ },
107
+ },
108
+ },
109
+ {
110
+ label: t('common.status'),
111
+ name: 'status',
112
+ width: 60,
113
+ options: {
114
+ customBodyRenderLite: (_: string, index: number) => {
115
+ const item = data.list[index] as TInvoiceExpanded;
116
+ return <Status label={item?.status} color={getInvoiceStatusColor(item?.status)} />;
117
+ },
118
+ },
119
+ },
120
+ {
121
+ label: t('admin.invoice.number'),
122
+ name: 'number',
123
+ },
124
+ {
125
+ label: t('common.description'),
126
+ name: 'description',
127
+ options: {
128
+ customBodyRenderLite: (_: string, index: number) => {
129
+ const item = data.list[index];
130
+ return item?.description || item?.id;
131
+ },
132
+ },
133
+ },
134
+ {
135
+ label: t('admin.invoice.dueDate'),
136
+ name: 'due_date',
137
+ options: {
138
+ customBodyRender: (e: number) => {
139
+ return e ? formatTime(e * 1000) : '-';
140
+ },
141
+ },
142
+ },
143
+ {
144
+ label: t('common.createdAt'),
145
+ name: 'created_at',
146
+ options: {
147
+ customBodyRender: (e: string) => {
148
+ return formatTime(e);
149
+ },
150
+ },
151
+ },
152
+ {
153
+ label: t('common.actions'),
154
+ name: 'id',
155
+ width: 100,
156
+ align: 'center',
157
+ options: {
158
+ sort: false,
159
+ customBodyRenderLite: (_: string, index: number) => {
160
+ const item = data.list[index] as TInvoiceExpanded;
161
+ return <InvoiceActions data={item} onChange={refresh} />;
162
+ },
163
+ },
164
+ },
165
+ ].filter(Boolean);
166
+
167
+ if (features?.customer) {
168
+ // @ts-ignore
169
+ columns.splice(3, 0, {
170
+ label: t('common.customer'),
171
+ name: 'customer_id',
172
+ width: 60,
173
+ options: {
174
+ customBodyRenderLite: (_: string, index: number) => {
175
+ const item = data.list[index] as TInvoiceExpanded;
176
+ return item.customer.email;
177
+ },
178
+ },
179
+ });
180
+ }
181
+
182
+ const onTableChange = ({ page, rowsPerPage }: any) => {
183
+ if (search.pageSize !== rowsPerPage) {
184
+ setSearch((x) => ({ ...x, pageSize: rowsPerPage, page: 1 }));
185
+ } else if (search.page !== page + 1) {
186
+ setSearch((x) => ({ ...x, page: page + 1 }));
187
+ }
188
+ };
189
+
190
+ return (
191
+ <Table
192
+ durable={listKey}
193
+ durableKeys={['page', 'rowsPerPage']}
194
+ data={data.list}
195
+ columns={columns}
196
+ loading={loading}
197
+ onChange={onTableChange}
198
+ options={{
199
+ count: data.count,
200
+ page: search.page - 1,
201
+ rowsPerPage: search.pageSize,
202
+ onRowClick: (_: any, { dataIndex }: any) => {
203
+ const item = data.list[dataIndex] as TInvoiceExpanded;
204
+ navigate(`/admin/billing/${item.id}`);
205
+ },
206
+ }}
207
+ toolbar={features?.toolbar}
208
+ footer={features?.footer}
209
+ title={
210
+ <div className="table-toolbar-left">
211
+ <ToggleButtonGroup
212
+ value={search.status}
213
+ onChange={(_, value) => setSearch((x) => ({ ...x, status: value }))}
214
+ exclusive>
215
+ <ToggleButton value="">All</ToggleButton>
216
+ <ToggleButton value="draft">Draft</ToggleButton>
217
+ <ToggleButton value="uncollectible">Outstanding</ToggleButton>
218
+ <ToggleButton value="open">Past Due</ToggleButton>
219
+ <ToggleButton value="paid">Paid</ToggleButton>
220
+ </ToggleButtonGroup>
221
+ </div>
222
+ }
223
+ />
224
+ );
225
+ }
@@ -0,0 +1,110 @@
1
+ import type { TInvoiceExpanded } from '@did-pay/types';
2
+ import { Table, TableBody, TableCell, TableHead, TableRow, Typography } from '@mui/material';
3
+ import { styled } from '@mui/system';
4
+
5
+ import { formatAmount, formatToDate } from '../../libs/util';
6
+ import LineItemActions from '../subscription/items/actions';
7
+
8
+ type Props = {
9
+ invoice: TInvoiceExpanded;
10
+ simple?: boolean;
11
+ };
12
+
13
+ InvoiceTable.defaultProps = {
14
+ simple: false,
15
+ };
16
+
17
+ export default function InvoiceTable({ invoice, simple }: Props) {
18
+ return (
19
+ <StyledTable>
20
+ <TableHead>
21
+ <TableRow sx={{ borderBottom: '1px solid #eee' }}>
22
+ <TableCell sx={{ textTransform: 'none', fontWeight: 'normal' }}>Description</TableCell>
23
+ <TableCell sx={{ textTransform: 'none', fontWeight: 'normal', width: 80 }} align="right">
24
+ Quantity
25
+ </TableCell>
26
+ <TableCell sx={{ textTransform: 'none', fontWeight: 'normal', width: 120 }} align="right">
27
+ Unit Price
28
+ </TableCell>
29
+ <TableCell sx={{ textTransform: 'none', fontWeight: 'normal', width: 100 }} align="right">
30
+ Amount
31
+ </TableCell>
32
+ {!simple && (
33
+ <TableCell sx={{ textTransform: 'none', fontWeight: 'normal', width: 50 }} align="right">
34
+ &nbsp;
35
+ </TableCell>
36
+ )}
37
+ </TableRow>
38
+ {invoice.period_end && invoice.period_start && (
39
+ <TableRow sx={{ borderBottom: '1px solid #eee' }}>
40
+ <TableCell align="left" colSpan={simple ? 4 : 5}>
41
+ <Typography component="span" variant="body1" color="text.secondary">
42
+ {formatToDate(invoice.period_start * 1000)} - {formatToDate(invoice.period_end * 1000)}
43
+ </Typography>
44
+ </TableCell>
45
+ </TableRow>
46
+ )}
47
+ </TableHead>
48
+ <TableBody>
49
+ {invoice.lines.map((line) => (
50
+ <TableRow key={line.id} sx={{ borderBottom: '1px solid #eee' }}>
51
+ <TableCell sx={{ fontWeight: 600 }}>
52
+ {line.description}
53
+ {line.price.product.unit_label ? ` (per ${line.price.product.unit_label})` : ''}
54
+ </TableCell>
55
+ <TableCell align="right">{line.quantity}</TableCell>
56
+ <TableCell align="right">{formatAmount(line.price.unit_amount, invoice.paymentCurrency.decimal)}</TableCell>
57
+ <TableCell align="right">{formatAmount(line.amount, invoice.paymentCurrency.decimal)}</TableCell>
58
+ {!simple && (
59
+ <TableCell align="right">
60
+ <LineItemActions data={line} />
61
+ </TableCell>
62
+ )}
63
+ </TableRow>
64
+ ))}
65
+ <TableRow>
66
+ <TableCell colSpan={3} align="right" sx={{ fontWeight: 600 }}>
67
+ Subtotal
68
+ </TableCell>
69
+ <TableCell align="right" sx={{ fontWeight: 600 }}>
70
+ {formatAmount(invoice.subtotal, invoice.paymentCurrency.decimal)}
71
+ </TableCell>
72
+ <TableCell>&nbsp;</TableCell>
73
+ </TableRow>
74
+ <TableRow sx={{ borderBottom: '1px solid #eee' }}>
75
+ <TableCell colSpan={3} align="right" sx={{ fontWeight: 600 }}>
76
+ Total
77
+ </TableCell>
78
+ <TableCell align="right" sx={{ fontWeight: 600 }}>
79
+ {formatAmount(invoice.total, invoice.paymentCurrency.decimal)}
80
+ </TableCell>
81
+ <TableCell>&nbsp;</TableCell>
82
+ </TableRow>
83
+ <TableRow>
84
+ <TableCell colSpan={3} align="right" sx={{ fontWeight: 600, color: 'text.secondary' }}>
85
+ Amount Paid
86
+ </TableCell>
87
+ <TableCell align="right" sx={{ fontWeight: 600 }}>
88
+ -{formatAmount(invoice.amount_paid, invoice.paymentCurrency.decimal)}
89
+ </TableCell>
90
+ <TableCell>&nbsp;</TableCell>
91
+ </TableRow>
92
+ <TableRow>
93
+ <TableCell colSpan={3} align="right" sx={{ fontWeight: 600 }}>
94
+ Amount Due
95
+ </TableCell>
96
+ <TableCell align="right" sx={{ fontWeight: 600 }}>
97
+ {formatAmount(invoice.amount_due, invoice.paymentCurrency.decimal)}
98
+ </TableCell>
99
+ <TableCell>&nbsp;</TableCell>
100
+ </TableRow>
101
+ </TableBody>
102
+ </StyledTable>
103
+ );
104
+ }
105
+
106
+ const StyledTable = styled(Table)`
107
+ .MuiTableCell-root {
108
+ padding: 8px 0;
109
+ }
110
+ `;
@@ -0,0 +1,70 @@
1
+ /* eslint-disable react-hooks/exhaustive-deps */
2
+ import Dashboard from '@blocklet/ui-react/lib/Dashboard';
3
+ import { styled } from '@mui/system';
4
+ import { useEffect, useState } from 'react';
5
+
6
+ import { useSessionContext } from '../contexts/session';
7
+
8
+ const Root = styled(Dashboard)`
9
+ width: 100%;
10
+ background-color: white;
11
+
12
+ > .dashboard-body > .dashboard-main {
13
+ > .dashboard-content {
14
+ .MuiTab-root {
15
+ padding: 0;
16
+ margin-right: 24px;
17
+ min-height: 32px;
18
+ min-width: auto;
19
+ }
20
+
21
+ .MuiTabs-root {
22
+ min-height: 32px;
23
+ margin-bottom: 8px;
24
+ }
25
+
26
+ .MuiTabs-scroller {
27
+ border-bottom: 1px solid #eee;
28
+ }
29
+
30
+ .page-content {
31
+ margin-top: 16px;
32
+ }
33
+
34
+ .MuiToggleButton-root {
35
+ padding: 4px 16px;
36
+ }
37
+ }
38
+
39
+ > .dashboard-footer {
40
+ margin-top: 0;
41
+ padding: 8px 0;
42
+
43
+ .logo-container {
44
+ svg {
45
+ height: 100%;
46
+ }
47
+ }
48
+ }
49
+ }
50
+ `;
51
+
52
+ export default function Layout(props: any) {
53
+ const { session } = useSessionContext();
54
+ const [loggingIn, setLoggingIn] = useState(false);
55
+
56
+ useEffect(() => {
57
+ if (!session.user && !loggingIn) {
58
+ setLoggingIn(true);
59
+ session.login(() => {
60
+ setLoggingIn(false);
61
+ });
62
+ }
63
+ }, [session.user]);
64
+
65
+ if (session.user) {
66
+ return <Root {...props} footerProps={{ className: 'dashboard-footer' }} />;
67
+ }
68
+
69
+ return null;
70
+ }
@@ -0,0 +1,23 @@
1
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
2
+ import { Chip } from '@mui/material';
3
+
4
+ export default function Livemode() {
5
+ const { t } = useLocaleContext();
6
+ return (
7
+ <Chip
8
+ label={t('common.livemode')}
9
+ size="small"
10
+ sx={{
11
+ ml: 2,
12
+ height: 18,
13
+ lineHeight: 18,
14
+ textTransform: 'uppercase',
15
+ fontSize: '0.8rem',
16
+ fontWeight: 'bold',
17
+ borderRadius: '4px',
18
+ backgroundColor: '#ffde92',
19
+ color: '#bb5504',
20
+ }}
21
+ />
22
+ );
23
+ }
@@ -0,0 +1,57 @@
1
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
2
+ import { Button, CircularProgress, Stack } from '@mui/material';
3
+ import type { EventHandler } from 'react';
4
+ import { FormProvider, useForm } from 'react-hook-form';
5
+
6
+ import MetadataForm from './form';
7
+
8
+ export default function MetadataEditor({
9
+ data,
10
+ loading,
11
+ onSave,
12
+ onCancel,
13
+ }: {
14
+ data: any;
15
+ loading: boolean;
16
+ onSave: EventHandler<any>;
17
+ onCancel: EventHandler<any>;
18
+ }) {
19
+ const { t } = useLocaleContext();
20
+ const metadata = data.metadata || {};
21
+ const methods = useForm<any>({
22
+ defaultValues: {
23
+ ...data,
24
+ // @ts-ignore
25
+ metadata: Object.keys(metadata).map((key: string) => ({ key, value: metadata[key] })),
26
+ },
27
+ });
28
+
29
+ const { handleSubmit, reset } = methods;
30
+ const onReset = (e: any) => {
31
+ reset();
32
+ onCancel(e);
33
+ };
34
+ // eslint-disable-next-line @typescript-eslint/no-shadow
35
+ const onSubmit = async (data: any) => {
36
+ await handleSubmit(onSave)(data);
37
+ reset();
38
+ onCancel(null);
39
+ };
40
+
41
+ return (
42
+ <FormProvider {...methods}>
43
+ <MetadataForm
44
+ actions={
45
+ <Stack direction="row">
46
+ <Button size="small" sx={{ mr: 2 }} onClick={onReset}>
47
+ {t('common.cancel')}
48
+ </Button>
49
+ <Button variant="contained" color="primary" size="small" disabled={loading} onClick={onSubmit}>
50
+ {loading && <CircularProgress size="small" />} {t('common.save')}
51
+ </Button>
52
+ </Stack>
53
+ }
54
+ />
55
+ </FormProvider>
56
+ );
57
+ }
@@ -0,0 +1,45 @@
1
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
2
+ import { AddOutlined, DeleteOutlineOutlined } from '@mui/icons-material';
3
+ import { Box, Button, IconButton, Stack, TextField, Typography } from '@mui/material';
4
+ import { Controller, useFieldArray, useFormContext } from 'react-hook-form';
5
+
6
+ export default function MetadataForm({ title, actions }: { title?: string; actions?: React.ReactNode }) {
7
+ const { t } = useLocaleContext();
8
+ const { control } = useFormContext();
9
+ const metadata = useFieldArray({ control, name: 'metadata' });
10
+ return (
11
+ <Box sx={{ width: 1 }}>
12
+ {!!title && <Typography>{title}</Typography>}
13
+ {metadata.fields.map((meta, index) => (
14
+ <Stack key={meta.id} mt={2} spacing={2} direction="row" alignItems="center">
15
+ <Stack direction="row" spacing={2}>
16
+ <Controller
17
+ render={({ field }) => <TextField {...field} sx={{ flex: 1 }} placeholder="Key" size="small" />}
18
+ name={`metadata.${index}.key`}
19
+ control={control}
20
+ />
21
+ <Controller
22
+ render={({ field }) => <TextField {...field} sx={{ flex: 2 }} placeholder="Value" size="small" />}
23
+ name={`metadata.${index}.value`}
24
+ control={control}
25
+ />
26
+ </Stack>
27
+ <IconButton size="small" onClick={() => metadata.remove(index)}>
28
+ <DeleteOutlineOutlined color="error" sx={{ opacity: 0.75 }} />
29
+ </IconButton>
30
+ </Stack>
31
+ ))}
32
+ <Stack mt={metadata.fields.length ? 2 : 1} direction="row" justifyContent="space-between">
33
+ <Button size="small" variant="outlined" color="inherit" onClick={() => metadata.append({ key: '', value: '' })}>
34
+ <AddOutlined fontSize="small" /> {t('common.metadata.add')}
35
+ </Button>
36
+ {actions}
37
+ </Stack>
38
+ </Box>
39
+ );
40
+ }
41
+
42
+ MetadataForm.defaultProps = {
43
+ title: true,
44
+ actions: null,
45
+ };