payment-kit 1.15.23 → 1.15.25

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/blocklet.yml CHANGED
@@ -14,7 +14,7 @@ repository:
14
14
  type: git
15
15
  url: git+https://github.com/blocklet/payment-kit.git
16
16
  specVersion: 1.2.8
17
- version: 1.15.23
17
+ version: 1.15.25
18
18
  logo: logo.png
19
19
  files:
20
20
  - dist
package/index.html CHANGED
@@ -1,14 +1,20 @@
1
- <!DOCTYPE html>
1
+ <!doctype html>
2
2
  <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <link rel="icon" href="/favicon.ico?imageFilter=resize&w=32" />
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
7
- <meta name="theme-color" content="#4F6AF5" />
8
- </head>
9
- <body>
10
- <noscript>You need to enable JavaScript to run this app. </noscript>
11
- <div id="app"></div>
12
- <script type="module" src="/src/index.tsx"></script>
13
- </body>
14
- </html>
3
+
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <link rel="icon" href="/favicon.ico?imageFilter=resize&w=32" />
7
+ <link rel="preconnect" href="https://unpkg.com" crossorigin />
8
+ <link rel="preconnect" href="https://js.stripe.com" crossorigin />
9
+ <link rel="dns-prefetch" href="https://js.stripe.com" />
10
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
11
+ <meta name="theme-color" content="#4F6AF5" />
12
+ </head>
13
+
14
+ <body>
15
+ <noscript>You need to enable JavaScript to run this app. </noscript>
16
+ <div id="app"></div>
17
+ <script type="module" src="/src/index.tsx"></script>
18
+ </body>
19
+
20
+ </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payment-kit",
3
- "version": "1.15.23",
3
+ "version": "1.15.25",
4
4
  "scripts": {
5
5
  "dev": "blocklet dev --open",
6
6
  "eject": "vite eject",
@@ -13,6 +13,7 @@
13
13
  "clean": "node scripts/build-clean.js",
14
14
  "bundle": "tsc --noEmit && npm run bundle:client && npm run bundle:api",
15
15
  "bundle:client": "vite build",
16
+ "bundle:analyze": "cross-env ANALYZE=true vite build",
16
17
  "bundle:api": "npm run clean && tsc -p tsconfig.api.json && blocklet bundle --compact --monorepo --create-release --changelog",
17
18
  "build": "npm run clean && tsc -p tsconfig.api.json && npm run bundle:client",
18
19
  "types": "rm -rf types && tsc -p tsconfig.types.json && rm -f ../../packages/types/lib/*.d.ts && cp -f types/store/models/*.d.ts ../../packages/types/lib",
@@ -42,31 +43,30 @@
42
43
  ]
43
44
  },
44
45
  "dependencies": {
45
- "@abtnode/cron": "^1.16.32",
46
- "@arcblock/did": "^1.18.136",
46
+ "@abtnode/cron": "1.16.33-beta-20241031-073543-49b1ff9b",
47
+ "@arcblock/did": "^1.18.137",
47
48
  "@arcblock/did-auth-storage-nedb": "^1.7.1",
48
- "@arcblock/did-connect": "^2.10.51",
49
- "@arcblock/did-util": "^1.18.136",
50
- "@arcblock/jwt": "^1.18.136",
51
- "@arcblock/ux": "^2.10.51",
52
- "@arcblock/validator": "^1.18.136",
53
- "@blocklet/js-sdk": "^1.16.32",
54
- "@blocklet/logger": "^1.16.32",
55
- "@blocklet/payment-react": "1.15.23",
56
- "@blocklet/sdk": "^1.16.32",
57
- "@blocklet/ui-react": "^2.10.51",
58
- "@blocklet/uploader": "^0.1.46",
49
+ "@arcblock/did-connect": "^2.10.55",
50
+ "@arcblock/did-util": "^1.18.137",
51
+ "@arcblock/jwt": "^1.18.137",
52
+ "@arcblock/ux": "^2.10.55",
53
+ "@arcblock/validator": "^1.18.137",
54
+ "@blocklet/js-sdk": "1.16.33-beta-20241031-073543-49b1ff9b",
55
+ "@blocklet/logger": "1.16.33-beta-20241031-073543-49b1ff9b",
56
+ "@blocklet/payment-react": "1.15.25",
57
+ "@blocklet/sdk": "1.16.33-beta-20241031-073543-49b1ff9b",
58
+ "@blocklet/ui-react": "^2.10.55",
59
+ "@blocklet/uploader": "^0.1.51",
59
60
  "@blocklet/xss": "^0.1.12",
60
61
  "@mui/icons-material": "^5.16.6",
61
62
  "@mui/lab": "^5.0.0-alpha.173",
62
63
  "@mui/material": "^5.16.6",
63
64
  "@mui/system": "^5.16.6",
64
- "@ocap/asset": "^1.18.136",
65
- "@ocap/client": "^1.18.136",
66
- "@ocap/mcrypto": "^1.18.136",
67
- "@ocap/util": "^1.18.136",
68
- "@ocap/wallet": "^1.18.136",
69
- "@react-pdf/renderer": "^3.4.4",
65
+ "@ocap/asset": "^1.18.137",
66
+ "@ocap/client": "^1.18.137",
67
+ "@ocap/mcrypto": "^1.18.137",
68
+ "@ocap/util": "^1.18.137",
69
+ "@ocap/wallet": "^1.18.137",
70
70
  "@stripe/react-stripe-js": "^2.7.3",
71
71
  "@stripe/stripe-js": "^2.4.0",
72
72
  "ahooks": "^3.8.0",
@@ -87,9 +87,11 @@
87
87
  "fastq": "^1.17.1",
88
88
  "flat": "^5.0.2",
89
89
  "google-libphonenumber": "^3.2.38",
90
+ "html2canvas": "^1.4.1",
90
91
  "iframe-resizer-react": "^1.1.1",
91
92
  "joi": "^17.13.3",
92
93
  "json-stable-stringify": "^1.1.1",
94
+ "jspdf": "^2.5.2",
93
95
  "lodash": "^4.17.21",
94
96
  "morgan": "^1.10.0",
95
97
  "mui-daterange-picker": "^1.0.5",
@@ -116,9 +118,9 @@
116
118
  "validator": "^13.12.0"
117
119
  },
118
120
  "devDependencies": {
119
- "@abtnode/types": "^1.16.32",
121
+ "@abtnode/types": "1.16.33-beta-20241031-073543-49b1ff9b",
120
122
  "@arcblock/eslint-config-ts": "^0.3.3",
121
- "@blocklet/payment-types": "1.15.23",
123
+ "@blocklet/payment-types": "1.15.25",
122
124
  "@types/cookie-parser": "^1.4.7",
123
125
  "@types/cors": "^2.8.17",
124
126
  "@types/debug": "^4.1.12",
@@ -128,6 +130,7 @@
128
130
  "@types/react": "^18.3.3",
129
131
  "@types/react-dom": "^18.3.0",
130
132
  "@vitejs/plugin-react": "^4.3.1",
133
+ "babel-plugin-lodash": "^3.3.4",
131
134
  "bumpp": "^8.2.1",
132
135
  "cross-env": "^7.0.3",
133
136
  "eslint": "^8.57.0",
@@ -138,12 +141,14 @@
138
141
  "npm-run-all": "^4.1.5",
139
142
  "prettier": "^3.3.3",
140
143
  "prettier-plugin-import-sort": "^0.0.7",
144
+ "rollup-plugin-visualizer": "^5.12.0",
141
145
  "ts-jest": "^29.2.5",
142
146
  "ts-node": "^10.9.2",
143
147
  "type-fest": "^4.23.0",
144
148
  "typescript": "^4.9.5",
145
149
  "vite": "^5.3.5",
146
150
  "vite-node": "^2.0.4",
151
+ "vite-plugin-babel-import": "^2.0.5",
147
152
  "vite-plugin-blocklet": "^0.9.11",
148
153
  "vite-plugin-node-polyfills": "^0.21.0",
149
154
  "vite-plugin-svgr": "^4.2.0",
@@ -160,5 +165,5 @@
160
165
  "parser": "typescript"
161
166
  }
162
167
  },
163
- "gitHead": "94862642b9c4f0f753a47e8c68d9b70315d8f008"
168
+ "gitHead": "c7d493c6e185b11844ef0ceb8abe688f1faad67c"
164
169
  }
package/src/app.tsx CHANGED
@@ -1,6 +1,7 @@
1
1
  import './global.css';
2
2
 
3
3
  import Center from '@arcblock/ux/lib/Center';
4
+ import withTracker from '@arcblock/ux/lib/withTracker';
4
5
  import { LocaleProvider } from '@arcblock/ux/lib/Locale/context';
5
6
  // import { createTheme } from '@arcblock/ux/lib/Theme';
6
7
  import { ToastProvider } from '@arcblock/ux/lib/Toast';
@@ -143,6 +144,8 @@ function App() {
143
144
  );
144
145
  }
145
146
 
147
+ const AppWithTracker = withTracker(App);
148
+
146
149
  export default function WrappedApp() {
147
150
  // While the blocklet is deploy to a sub path, this will be work properly.
148
151
  const prefix = window?.blocklet?.prefix || '/';
@@ -153,7 +156,7 @@ export default function WrappedApp() {
153
156
  serviceHost={prefix}
154
157
  protectedRoutes={['/admin/*', '/customer/*'].map((item) => joinURL(prefix, item))}>
155
158
  <Router basename={prefix}>
156
- <App />
159
+ <AppWithTracker />
157
160
  </Router>
158
161
  </SessionProvider>
159
162
  </ToastProvider>
@@ -7,6 +7,7 @@ export default function Copyable({ text, children, style }: { text: string; chil
7
7
  const { locale } = useLocaleContext();
8
8
  return (
9
9
  <CopyButton
10
+ showTooltip={false}
10
11
  content={text}
11
12
  locale={locale}
12
13
  style={style}
@@ -1,14 +1,11 @@
1
1
  import 'react-international-phone/style.css';
2
2
 
3
3
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
4
- import { FormInput, PhoneInput, CountrySelect } from '@blocklet/payment-react';
4
+ import { FormInput, PhoneInput, CountrySelect, validatePhoneNumber } from '@blocklet/payment-react';
5
5
  import { FormLabel, Stack } from '@mui/material';
6
- import { PhoneNumberUtil } from 'google-libphonenumber';
7
6
  import { Controller, useFormContext } from 'react-hook-form';
8
7
  import isEmail from 'validator/es/lib/isEmail';
9
8
 
10
- const phoneUtil = PhoneNumberUtil.getInstance();
11
-
12
9
  export default function CustomerForm() {
13
10
  const { t } = useLocaleContext();
14
11
  const { control } = useFormContext();
@@ -63,14 +60,9 @@ export default function CustomerForm() {
63
60
  placeholder={t('payment.checkout.customer.phonePlaceholder')}
64
61
  rules={{
65
62
  required: t('payment.checkout.required'),
66
- validate: (x: string) => {
67
- try {
68
- const parsed = phoneUtil.parseAndKeepRawInput(x);
69
- return phoneUtil.isValidNumber(parsed) ? true : t('payment.checkout.invalid');
70
- } catch (err) {
71
- console.error(err, x);
72
- return t('payment.checkout.invalid');
73
- }
63
+ validate: async (x: string) => {
64
+ const isValid = await validatePhoneNumber(x);
65
+ return isValid ? true : t('payment.checkout.invalid');
74
66
  },
75
67
  }}
76
68
  />
@@ -1,185 +1,118 @@
1
+ /* eslint-disable import/no-extraneous-dependencies */
1
2
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
2
- import { formatTime, getPrefix } from '@blocklet/payment-react';
3
3
  import type { TInvoiceExpanded } from '@blocklet/payment-types';
4
- import { Button } from '@mui/material';
5
- import {
6
- Font,
7
- PDFDownloadLink,
8
- Document as PdfDocument,
9
- Image as PdfImage,
10
- Page as PdfPage,
11
- Text as PdfText,
12
- View as PdfView,
13
- } from '@react-pdf/renderer';
14
- import { joinURL } from 'ufo';
4
+ import { Box, Button, CircularProgress } from '@mui/material';
5
+ import JsPDF from 'jspdf';
6
+ import { useEffect, useState } from 'react';
15
7
 
16
- import { getInvoiceRows } from '../invoice/table';
17
- import compose from './compose';
8
+ import { loadFont, loadImage } from './utils';
9
+ import { InvoiceTemplate } from './template';
18
10
 
19
- Font.register({
20
- family: 'Noto Sans SC',
21
- src: joinURL(getPrefix(), '/fonts/noto-sans-sc-chinese-simplified-500-normal.ttf'),
22
- });
11
+ export function Download({ data }: { data: TInvoiceExpanded }) {
12
+ const { t } = useLocaleContext();
13
+ const [isReady, setIsReady] = useState(false);
14
+ const [isGenerating, setIsGenerating] = useState(false);
23
15
 
24
- function Document({ children }: any) {
25
- return <PdfDocument>{children}</PdfDocument>;
26
- }
16
+ useEffect(() => {
17
+ const preload = async () => {
18
+ try {
19
+ const [fontLoaded] = await Promise.all([loadFont(), loadImage(window.blocklet.appLogo?.split('?')[0] || '')]);
20
+ setIsReady(fontLoaded);
21
+ } catch (error) {
22
+ console.error('Resource loading failed:', error);
23
+ }
24
+ };
25
+ preload();
26
+ }, []);
27
27
 
28
- function Text2({ className, value }: any) {
29
- return <PdfText style={compose(`span ${className || ''}`)}>{value}</PdfText>;
30
- }
28
+ const generatePDF = async () => {
29
+ if (!isReady || isGenerating) return;
31
30
 
32
- function Text({ className, children }: any) {
33
- return <PdfText style={compose(`span ${className || ''}`)}>{children}</PdfText>;
34
- }
31
+ try {
32
+ setIsGenerating(true);
35
33
 
36
- function View({ className, children }: any) {
37
- return <PdfView style={compose(`view ${className || ''}`)}>{children}</PdfView>;
38
- }
34
+ const element = document.getElementById('invoice-template');
35
+ if (!element) return;
36
+ const html2canvas = (await import(/* webpackChunkName: "html2canvas" */ 'html2canvas')).default;
37
+ const canvas = await html2canvas(element, {
38
+ scale: 5,
39
+ useCORS: true,
40
+ logging: false,
41
+ windowWidth: 794,
42
+ windowHeight: 1123,
43
+ allowTaint: true,
44
+ backgroundColor: '#ffffff',
45
+ imageTimeout: 15000,
46
+ removeContainer: true,
47
+ onclone: (clonedDoc) => {
48
+ const clonedElement = clonedDoc.getElementById('invoice-template');
49
+ if (clonedElement) {
50
+ clonedElement.style.position = 'relative';
51
+ clonedElement.style.left = '0';
52
+ clonedElement.style.transform = 'scale(1)';
53
+ clonedElement.style.transformOrigin = 'top left';
54
+ clonedElement.style.textRendering = 'geometricPrecision';
55
+ (clonedElement.style as any)['-webkit-font-smoothing'] = 'antialiased';
56
+ (clonedElement.style as any)['-moz-osx-font-smoothing'] = 'grayscale';
57
+ }
58
+ },
59
+ });
39
60
 
40
- function Page({ className, children }: any) {
41
- return (
42
- <PdfPage size="A4" style={compose(`page ${className || ''}`)}>
43
- {children}
44
- </PdfPage>
45
- );
46
- }
61
+ const pdf = new JsPDF({
62
+ orientation: 'portrait',
63
+ unit: 'pt',
64
+ format: 'a4',
65
+ compress: true,
66
+ precision: 32,
67
+ hotfixes: ['px_scaling'],
68
+ });
47
69
 
48
- export function Download({ data }: { data: TInvoiceExpanded }) {
49
- const { t } = useLocaleContext();
50
- const title = data.number;
51
- return (
52
- <div className="download-pdf ">
53
- <PDFDownloadLink
54
- key="pdf"
55
- document={<InvoicePage data={data} t={t} />}
56
- fileName={`${title}.pdf`}
57
- aria-label="Save PDF"
58
- title="Save PDF"
59
- className="download-pdf__pdf">
60
- <Button
61
- variant="outlined"
62
- color="primary"
63
- size="small"
64
- sx={{ borderColor: 'var(--stroke-border-base, #EFF1F5)' }}>
65
- {t('payment.customer.invoice.download')}
66
- </Button>
67
- </PDFDownloadLink>
68
- </div>
69
- );
70
- }
71
-
72
- function InvoicePage({ data, t }: { data: TInvoiceExpanded; t: any }) {
73
- const { detail, summary } = getInvoiceRows(data);
70
+ const imgWidth = canvas.width;
71
+ const imgHeight = canvas.height;
72
+ const pdfWidth = pdf.internal.pageSize.getWidth();
73
+ const pdfHeight = pdf.internal.pageSize.getHeight();
74
+ const ratio = Math.min(pdfWidth / imgWidth, pdfHeight / imgHeight);
74
75
 
75
- return (
76
- <Document>
77
- <Page className="invoice-wrapper">
78
- <View className="flex">
79
- <View className="w-50 flex-1">
80
- <Text2 className="fs-20 bold" value={t('admin.invoice.name')} />
81
- </View>
82
- <View className="right">
83
- <PdfImage
84
- style={{
85
- ...compose('image logo center'),
86
- }}
87
- src={window.blocklet.appLogo.split('?')[0]}
88
- />
89
- <Text2 value={window.blocklet.appName} className="center p-10" />
90
- </View>
91
- </View>
92
-
93
- <View className="flex mt-40">
94
- <View className="w-60">
95
- <Text2 className="bold dark mb-5" value={t('admin.invoice.billTo')} />
96
- <Text2 value={data.customer.name} />
97
- <Text2 className="bold fs-12" value={`DID:ABT:${data.customer.did}`} />
98
- </View>
99
- <View className="w-45">
100
- <View className="flex mb-5">
101
- <View className="w-45">
102
- <Text2 className="bold" value={t('admin.invoice.number')} />
103
- </View>
104
- <View className="w-60">
105
- <Text2 value={data.number} className="gray" />
106
- </View>
107
- </View>
108
- <View className="flex mb-5">
109
- <View className="w-45">
110
- <Text2 className="bold" value={t('admin.invoice.paidAt')} />
111
- </View>
112
- <View className="w-60">
113
- <Text2 value={formatTime(data.period_start * 1000)} className="gray" />
114
- </View>
115
- </View>
116
- <View className="flex mb-5">
117
- <View className="w-45">
118
- <Text2 className="bold" value={t('admin.invoice.dueDate')} />
119
- </View>
120
- <View className="w-60">
121
- <Text2 value={formatTime(data.period_end * 1000)} className="gray" />
122
- </View>
123
- </View>
124
- </View>
125
- </View>
126
-
127
- <View className="mt-30 row flex">
128
- <View className="w-48 p-4-8">
129
- <Text2 className="bold" value={t('admin.subscription.product')} />
130
- </View>
131
- <View className="w-17 p-4-8">
132
- <Text2 className="bold right" value={t('common.quantity')} />
133
- </View>
134
- <View className="w-17 p-4-8">
135
- <Text2 className="bold right" value={t('payment.customer.invoice.unitPrice')} />
136
- </View>
137
- <View className="w-18 p-4-8">
138
- <Text2 className="bold right" value={t('common.amount')} />
139
- </View>
140
- </View>
76
+ pdf.addImage(
77
+ canvas.toDataURL('image/png', 1.0),
78
+ 'PNG',
79
+ 0,
80
+ 0,
81
+ imgWidth * ratio,
82
+ imgHeight * ratio,
83
+ undefined,
84
+ 'FAST'
85
+ );
141
86
 
142
- {detail.map((line) => {
143
- return (
144
- <View key={line.id} className="row flex">
145
- <View className="w-48 p-4-8 pb-10">
146
- <Text2 className="dark" value={line.product} />
147
- </View>
148
- <View className="w-17 p-4-8 pb-10">
149
- <Text2 className="dark right" value={line.quantity} />
150
- </View>
151
- <View className="w-17 p-4-8 pb-10">
152
- <Text className="dark right">{line.price ? `${line.price} ${data.paymentCurrency.symbol}` : ''}</Text>
153
- </View>
154
- <View className="w-18 p-4-8 pb-10">
155
- <Text className="dark right">
156
- {line.amount} {data.paymentCurrency.symbol}
157
- </Text>
158
- </View>
159
- </View>
160
- );
161
- })}
87
+ pdf.save(`${data.number}.pdf`);
88
+ } catch (error) {
89
+ console.error('PDF generation failed:', error);
90
+ } finally {
91
+ setIsGenerating(false);
92
+ }
93
+ };
162
94
 
163
- <View className="flex">
164
- <View className="w-50 mt-10" />
165
- <View className="w-50 mt-20">
166
- {summary.map((line) => (
167
- <View className="flex" key={line.key}>
168
- <View className="w-50 p-5">
169
- <Text2 value={t(line.key)} className="bold" />
170
- </View>
171
- <View className="w-50 p-5">
172
- <Text className="right bold dark">
173
- {line.value} {data.paymentCurrency.symbol}
174
- </Text>
175
- </View>
176
- </View>
177
- ))}
178
- </View>
179
- </View>
180
- </Page>
181
- </Document>
95
+ return (
96
+ <div className="download-pdf">
97
+ <Button
98
+ variant="outlined"
99
+ color="primary"
100
+ size="small"
101
+ onClick={generatePDF}
102
+ disabled={!isReady || isGenerating}
103
+ sx={{ borderColor: 'var(--stroke-border-base, #EFF1F5)' }}>
104
+ {isGenerating ? (
105
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
106
+ <CircularProgress size={16} sx={{ color: 'inherit' }} />
107
+ {t('payment.customer.invoice.download')}
108
+ </Box>
109
+ ) : (
110
+ t('payment.customer.invoice.download')
111
+ )}
112
+ </Button>
113
+ <InvoiceTemplate data={data} t={t} />
114
+ </div>
182
115
  );
183
116
  }
184
117
 
185
- export default InvoicePage;
118
+ export default Download;
@@ -0,0 +1,134 @@
1
+ import type { InvoiceStyles } from './types';
2
+
3
+ export const colorDark = '#222';
4
+ export const colorDark2 = '#888';
5
+ export const colorGray = '#e3e3e3';
6
+ export const colorWhite = '#fff';
7
+
8
+ export const pdfStyles: InvoiceStyles = {
9
+ template: {
10
+ padding: '40px 35px',
11
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
12
+ fontSize: '11px',
13
+ color: '#555',
14
+ backgroundColor: '#fff',
15
+ width: '210mm',
16
+ minHeight: '297mm',
17
+ position: 'absolute',
18
+ left: '-9999px',
19
+ printColorAdjust: 'exact',
20
+ letterSpacing: '0.3px',
21
+ lineHeight: '1.5',
22
+ },
23
+ dark: {
24
+ color: colorDark,
25
+ },
26
+ white: {
27
+ color: colorWhite,
28
+ },
29
+ gray: {
30
+ color: colorDark2,
31
+ },
32
+ 'bg-dark': {
33
+ backgroundColor: colorDark2,
34
+ },
35
+ 'bg-gray': {
36
+ backgroundColor: colorGray,
37
+ },
38
+ flex: {
39
+ display: 'flex',
40
+ flexDirection: 'row',
41
+ flexWrap: 'nowrap',
42
+ alignItems: 'flex-start',
43
+ },
44
+ bold: {
45
+ fontWeight: 'bold',
46
+ },
47
+ 'w-auto': {
48
+ flex: 1,
49
+ paddingRight: '8px',
50
+ },
51
+ 'w-100': { width: '100%' },
52
+ 'w-50': { width: '50%' },
53
+ 'w-55': { width: '55%' },
54
+ 'w-45': { width: '45%' },
55
+ 'w-60': { width: '60%' },
56
+ 'w-40': { width: '40%' },
57
+ 'w-48': { width: '48%' },
58
+ 'w-17': { width: '17%' },
59
+ 'w-18': { width: '18%' },
60
+ row: {
61
+ borderBottom: `1px solid ${colorGray}`,
62
+ marginBottom: '2px',
63
+ },
64
+ 'mt-60': { marginTop: '60px' },
65
+ 'mt-40': { marginTop: '40px' },
66
+ 'mt-30': { marginTop: '30px' },
67
+ 'mt-20': { marginTop: '20px' },
68
+ 'mt-10': { marginTop: '10px' },
69
+ 'mb-5': { marginBottom: '5px' },
70
+ 'p-4-8': { padding: '4px 8px' },
71
+ 'p-5': { padding: '5px' },
72
+ 'p-10': { padding: '10px' },
73
+ 'pb-15': { paddingBottom: '15px' },
74
+ right: {
75
+ marginLeft: 'auto',
76
+ textAlign: 'right' as const,
77
+ },
78
+ center: { textAlign: 'center' },
79
+ 'fs-20': { fontSize: '20px' },
80
+ 'fs-30': { fontSize: '30px' },
81
+ 'fs-12': { fontSize: '12px' },
82
+ logo: {
83
+ display: 'block',
84
+ borderRadius: '10px',
85
+ width: '60px',
86
+ height: '60px',
87
+ objectFit: 'contain',
88
+ },
89
+ 'flex-1': {
90
+ flex: 1,
91
+ },
92
+ 'justify-between': {
93
+ justifyContent: 'space-between',
94
+ },
95
+ 'items-center': {
96
+ alignItems: 'center',
97
+ },
98
+ 'gap-20': {
99
+ gap: '20px',
100
+ },
101
+ block: { display: 'block' },
102
+ 'ml-40': { marginLeft: '40px' },
103
+ };
104
+
105
+ export const styles = `
106
+ .invoice-header {
107
+ display: flex;
108
+ justify-content: flex-end;
109
+ align-items: center;
110
+ gap: 20px;
111
+ }
112
+
113
+ .invoice-content {
114
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
115
+ letter-spacing: 0.3px;
116
+ }
117
+
118
+ .date-format {
119
+ font-family: 'SF Mono', SFMono-Regular, ui-monospace, Menlo, Monaco, Consolas, monospace;
120
+ letter-spacing: 0.5px;
121
+ }
122
+
123
+ * {
124
+ -webkit-font-smoothing: antialiased;
125
+ -moz-osx-font-smoothing: grayscale;
126
+ text-rendering: optimizeLegibility;
127
+ }
128
+ `;
129
+
130
+ export const applyStyles = () => {
131
+ const styleElement = document.createElement('style');
132
+ styleElement.textContent = styles;
133
+ document.head.appendChild(styleElement);
134
+ };