esewa-ui-library 1.0.3 → 1.0.5
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 +184 -14
- package/dist/@types/index.d.ts +8 -5
- package/dist/components/Radio/eSewaRadio.d.ts +5 -3
- package/dist/index.js +21 -14
- package/dist/index.js.map +1 -1
- package/dist/index.modern.js +21 -14
- package/dist/index.modern.js.map +1 -1
- package/dist/services/eSewaService.d.ts +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -108,15 +108,25 @@ export default App;
|
|
|
108
108
|
|
|
109
109
|
The `ESewaAlertCard` component is customizable app bar for your eSewa Mini App with support for back, title, and action icons, making navigation and interactions seamless.
|
|
110
110
|
|
|
111
|
+
Use useESewaDataProvider to set title for appbar.
|
|
112
|
+
|
|
111
113
|
### Usage:
|
|
112
114
|
|
|
113
115
|
```tsx
|
|
114
|
-
import { ESewaAppBar } from 'esewa-ui-library';
|
|
116
|
+
import { ESewaAppBar, useESewaDataProvider } from 'esewa-ui-library';
|
|
115
117
|
|
|
116
118
|
const App = () => {
|
|
119
|
+
const { updateData } = useESewaDataProvider();
|
|
120
|
+
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
updateData({
|
|
123
|
+
title: "Merchant Product Form",
|
|
124
|
+
});
|
|
125
|
+
}, []);
|
|
126
|
+
|
|
117
127
|
return (
|
|
118
128
|
<ESewaAppBar
|
|
119
|
-
|
|
129
|
+
icon="icon-es-arrow-left"
|
|
120
130
|
titleposition="left"
|
|
121
131
|
onBackIconClick={() => console.log('Back icon clicked')}
|
|
122
132
|
onTitleClick={() => console.log('Title clicked')}
|
|
@@ -582,17 +592,19 @@ const App = () => {
|
|
|
582
592
|
<div>
|
|
583
593
|
<button onClick={openDialog}>Open Dialog</button>
|
|
584
594
|
|
|
585
|
-
|
|
586
|
-
isOpen
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
595
|
+
{
|
|
596
|
+
isOpen && (<ESewaDialog
|
|
597
|
+
isOpen={isOpen}
|
|
598
|
+
title="Sample Dialog"
|
|
599
|
+
okText="Confirm"
|
|
600
|
+
cancelText="Cancel"
|
|
601
|
+
onOk={handleOk}
|
|
602
|
+
onCancel={closeDialog}
|
|
603
|
+
position="center"
|
|
604
|
+
>
|
|
605
|
+
<p>Dialog content goes here.</p>
|
|
606
|
+
</ESewaDialog>)
|
|
607
|
+
}
|
|
596
608
|
</div>
|
|
597
609
|
);
|
|
598
610
|
};
|
|
@@ -1298,9 +1310,10 @@ The `ESewaRadio` component accepts the following props:
|
|
|
1298
1310
|
| `label` | `string` | No | `undefined` | The label displayed next to the radio button. |
|
|
1299
1311
|
| `name` | `string` | Yes | N/A | The name of the radio button, which groups it together with other radio buttons. |
|
|
1300
1312
|
| `onChange` | `(checked: boolean) => void` | No | `undefined` | Callback function that is triggered when the radio button's checked state changes. |
|
|
1301
|
-
| `checked` | `boolean` |
|
|
1313
|
+
| `checked` | `boolean` | Yes | false | Determines whether the radio button is selected (`true`) or not (`false`). |
|
|
1302
1314
|
| `labelClass`| `string` | No | `''` | A custom class applied to the label element for styling. |
|
|
1303
1315
|
| `className` | `string` | No | `''` | A custom class applied to the root wrapper element of the radio button component. |
|
|
1316
|
+
| `disabled` | `boolean` | No | false | Used to disable radio state |
|
|
1304
1317
|
|
|
1305
1318
|
# ESewaSanitizeHtml Component
|
|
1306
1319
|
|
|
@@ -1487,7 +1500,110 @@ export default TooltipExample
|
|
|
1487
1500
|
### Providers
|
|
1488
1501
|
|
|
1489
1502
|
- **ThemeProvider**: `ThemeProvider`
|
|
1503
|
+
|
|
1504
|
+
Wrap your component with this provider to apply theme.
|
|
1505
|
+
|
|
1506
|
+
```tsx
|
|
1507
|
+
import { StrictMode } from 'react'
|
|
1508
|
+
import { createRoot } from 'react-dom/client'
|
|
1509
|
+
import { ESewaThemeProvider } from 'esewa-ui-library'
|
|
1510
|
+
import App from './App.tsx'
|
|
1511
|
+
|
|
1512
|
+
createRoot(document.getElementById('root')!).render(
|
|
1513
|
+
<StrictMode>
|
|
1514
|
+
<ESewaThemeProvider>
|
|
1515
|
+
<App />
|
|
1516
|
+
</ESewaThemeProvider>
|
|
1517
|
+
</StrictMode>,
|
|
1518
|
+
)
|
|
1519
|
+
|
|
1520
|
+
```
|
|
1521
|
+
|
|
1522
|
+
In your page
|
|
1523
|
+
|
|
1524
|
+
```tsx
|
|
1525
|
+
import { useEffect, useState } from "react";
|
|
1526
|
+
import {
|
|
1527
|
+
MoonIcon,
|
|
1528
|
+
SunIcon,
|
|
1529
|
+
} from "@heroicons/react/24/outline";
|
|
1530
|
+
|
|
1531
|
+
const [darkMode, setDarkMode] = useState<boolean>(
|
|
1532
|
+
window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
1533
|
+
);
|
|
1534
|
+
|
|
1535
|
+
const handleThemeToggle = () => {
|
|
1536
|
+
setDarkMode((prevMode) => !prevMode);
|
|
1537
|
+
};
|
|
1538
|
+
|
|
1539
|
+
useEffect(() => {
|
|
1540
|
+
const theme = darkMode ? 'dark' : 'light';
|
|
1541
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
1542
|
+
}, [darkMode]);
|
|
1543
|
+
|
|
1544
|
+
<button
|
|
1545
|
+
onClick={handleThemeToggle}
|
|
1546
|
+
>
|
|
1547
|
+
{darkMode ? (
|
|
1548
|
+
<SunIcon className="h-6 w-6" />
|
|
1549
|
+
) : (
|
|
1550
|
+
<MoonIcon className="h-6 w-6" />
|
|
1551
|
+
)}
|
|
1552
|
+
</button>
|
|
1553
|
+
|
|
1554
|
+
```
|
|
1555
|
+
|
|
1490
1556
|
- **ESewaProvider**: `ESewaProvider`
|
|
1557
|
+
|
|
1558
|
+
Wrap your component with this provider.
|
|
1559
|
+
|
|
1560
|
+
```tsx
|
|
1561
|
+
import { StrictMode } from 'react'
|
|
1562
|
+
import { createRoot } from 'react-dom/client'
|
|
1563
|
+
import { ESewaProvider } from 'esewa-ui-library'
|
|
1564
|
+
import App from './App.tsx'
|
|
1565
|
+
|
|
1566
|
+
createRoot(document.getElementById('root')!).render(
|
|
1567
|
+
<StrictMode>
|
|
1568
|
+
<ESewaProvider>
|
|
1569
|
+
<App />
|
|
1570
|
+
</ESewaProvider>
|
|
1571
|
+
</StrictMode>,
|
|
1572
|
+
)
|
|
1573
|
+
|
|
1574
|
+
```
|
|
1575
|
+
|
|
1576
|
+
then you can use useESewaDataProvider
|
|
1577
|
+
|
|
1578
|
+
```tsx
|
|
1579
|
+
import { ESewaAppBar, useESewaDataProvider } from 'esewa-ui-library';
|
|
1580
|
+
|
|
1581
|
+
const App = () => {
|
|
1582
|
+
const { updateData } = useESewaDataProvider();
|
|
1583
|
+
|
|
1584
|
+
useEffect(() => {
|
|
1585
|
+
updateData({
|
|
1586
|
+
title: "Merchant Product Form",
|
|
1587
|
+
});
|
|
1588
|
+
}, []);
|
|
1589
|
+
|
|
1590
|
+
return (
|
|
1591
|
+
<ESewaAppBar
|
|
1592
|
+
icon="icon-es-arrow-left"
|
|
1593
|
+
titleposition="left"
|
|
1594
|
+
onBackIconClick={() => console.log('Back icon clicked')}
|
|
1595
|
+
onTitleClick={() => console.log('Title clicked')}
|
|
1596
|
+
onActionIconClick={() => console.log('Action icon clicked')}
|
|
1597
|
+
actionIcon="icon-settings"
|
|
1598
|
+
/>
|
|
1599
|
+
);
|
|
1600
|
+
};
|
|
1601
|
+
|
|
1602
|
+
export default App;
|
|
1603
|
+
|
|
1604
|
+
```
|
|
1605
|
+
|
|
1606
|
+
|
|
1491
1607
|
- **ESewaPaymentProvider**: `ESewaPaymentProvider`
|
|
1492
1608
|
|
|
1493
1609
|
# Services
|
|
@@ -1504,6 +1620,7 @@ This library provides a set of services that allow interaction between the merch
|
|
|
1504
1620
|
- [User Detail Request](#user-detail-request)
|
|
1505
1621
|
- [Media Access Request](#media-access-request)
|
|
1506
1622
|
- [Validate Payment](#validate-payment)
|
|
1623
|
+
- [Request Payment](#request-payment)
|
|
1507
1624
|
- [Error Handling](#error-handling)
|
|
1508
1625
|
|
|
1509
1626
|
## Initialization
|
|
@@ -1693,6 +1810,59 @@ const onValidateTransactionClick = () => {
|
|
|
1693
1810
|
requestFromMiniApp(obj, CALLBACK_TYPE_ENUM.VALIDATE_TRANSACTION_CALLBACK, validateTransactionCallBack);
|
|
1694
1811
|
};
|
|
1695
1812
|
```
|
|
1813
|
+
#### Request Payment
|
|
1814
|
+
Pass token received from initialize request call with respective request type, callback key , your merchant identifier and request payload
|
|
1815
|
+
|
|
1816
|
+
```tsx
|
|
1817
|
+
const onRequestPaymentClick = () => {
|
|
1818
|
+
const productPayload = {
|
|
1819
|
+
"product_code": "NP-ES-VIANET",
|
|
1820
|
+
"amount": 28.48,
|
|
1821
|
+
"properties": {
|
|
1822
|
+
"productId": "3299",
|
|
1823
|
+
"payment_id": "1613103_PP",
|
|
1824
|
+
"refId": "400005",
|
|
1825
|
+
"customer_id": "48707",
|
|
1826
|
+
"payer": "CUSTOMER"
|
|
1827
|
+
},
|
|
1828
|
+
"channel": "WEB_USER"
|
|
1829
|
+
};
|
|
1830
|
+
|
|
1831
|
+
const obj = {
|
|
1832
|
+
requestType: REQUEST_TYPE_ENUM.REQUEST_PAYMENT,
|
|
1833
|
+
merchant_identifier: 'IAAAAABTOBAbFhAXHhEHAgoXX0FRR1FJJiw3LCwkJzE=',
|
|
1834
|
+
token: sessionStorage.getItem('miniAppAuthToken'),
|
|
1835
|
+
data: productPayload,
|
|
1836
|
+
callbackKey: "REQUEST_PAYMENT_CALLBACK"
|
|
1837
|
+
};
|
|
1838
|
+
|
|
1839
|
+
const requestPaymentCallBack: any = (data:any) => {
|
|
1840
|
+
console.log("Make payment Response:", data);
|
|
1841
|
+
|
|
1842
|
+
try {
|
|
1843
|
+
if (!data) {
|
|
1844
|
+
throw new Error('Received null or undefined response');
|
|
1845
|
+
}
|
|
1846
|
+
const res = JSON.parse(data);
|
|
1847
|
+
|
|
1848
|
+
if (!res || typeof res !== 'object') {
|
|
1849
|
+
throw new Error('Parsed response is not a valid object');
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
if (res?.error_message) {
|
|
1853
|
+
throw new Error('Error res', res.error_message);
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
setMakePaymentRes(res);
|
|
1857
|
+
} catch (error) {
|
|
1858
|
+
console.error('Error parsing response:', error);
|
|
1859
|
+
}
|
|
1860
|
+
};
|
|
1861
|
+
|
|
1862
|
+
requestFromMiniApp(obj, CALLBACK_TYPE_ENUM.REQUEST_PAYMENT_CALLBACK, requestPaymentCallBack);
|
|
1863
|
+
};
|
|
1864
|
+
```
|
|
1865
|
+
|
|
1696
1866
|
## Error Handling
|
|
1697
1867
|
|
|
1698
1868
|
Errors are returned as JSON responses with an `error_message` field.
|
package/dist/@types/index.d.ts
CHANGED
|
@@ -46,16 +46,19 @@ declare global {
|
|
|
46
46
|
VALIDATE_TRANSACTION_CALLBACK?: Callback | null;
|
|
47
47
|
};
|
|
48
48
|
webkit: {
|
|
49
|
-
requestApp: (data: any) => void;
|
|
50
|
-
receiveFromApp: (data: any) => void;
|
|
51
49
|
messageHandlers: {
|
|
52
50
|
iOSNative: {
|
|
53
|
-
|
|
51
|
+
postMessage: (data: any) => void;
|
|
52
|
+
INIT_APP_CALLBACK?: Callback | null;
|
|
53
|
+
REQUEST_PAYMENT_CALLBACK?: Callback | null;
|
|
54
|
+
USER_DETAIL_ACCESS_CALLBACK?: Callback | null;
|
|
55
|
+
MEDIA_ACCESS_CALLBACK?: Callback | null;
|
|
56
|
+
LOCATION_ACCESS_CALLBACK?: Callback | null;
|
|
57
|
+
VALIDATE_TRANSACTION_CALLBACK?: Callback | null;
|
|
54
58
|
};
|
|
55
59
|
};
|
|
56
|
-
receiveDataFromWeb: (data: any) => void;
|
|
57
|
-
receiveDataFromApp: (data: any) => void;
|
|
58
60
|
};
|
|
59
61
|
}
|
|
60
62
|
function getDataFromMiniApp(e: any): void;
|
|
61
63
|
}
|
|
64
|
+
export declare type CallbackKey = keyof Window['Android'] | 'INIT_APP_CALLBACK' | 'REQUEST_PAYMENT_CALLBACK' | 'USER_DETAIL_ACCESS_CALLBACK' | 'MEDIA_ACCESS_CALLBACK' | 'LOCATION_ACCESS_CALLBACK' | 'VALIDATE_TRANSACTION_CALLBACK';
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
declare type RadioProps = {
|
|
3
|
-
key
|
|
3
|
+
key?: any;
|
|
4
4
|
label?: string;
|
|
5
|
+
value?: any;
|
|
5
6
|
name: string;
|
|
6
|
-
onChange?: (checked:
|
|
7
|
-
checked
|
|
7
|
+
onChange?: (value: any, checked: boolean) => void;
|
|
8
|
+
checked: boolean;
|
|
8
9
|
labelClass?: string;
|
|
9
10
|
className?: string;
|
|
11
|
+
disabled?: boolean;
|
|
10
12
|
};
|
|
11
13
|
export declare const ESewaRadio: React.FC<RadioProps>;
|
|
12
14
|
export default ESewaRadio;
|
package/dist/index.js
CHANGED
|
@@ -186,7 +186,7 @@ var StyledAppBar = styled__default.header.attrs(function (_ref) {
|
|
|
186
186
|
return {
|
|
187
187
|
titleposition: titleposition
|
|
188
188
|
};
|
|
189
|
-
})(_templateObject$3 || (_templateObject$3 = _taggedTemplateLiteralLoose(["\n .e-app-bar {\n display: flex;\n justify-content: space-between;\n flex-direction: row;\n align-items: center;\n background-color: var(--appbar-bg-top);\n padding: 7px 12px;\n position:
|
|
189
|
+
})(_templateObject$3 || (_templateObject$3 = _taggedTemplateLiteralLoose(["\n width: 100%;\n\n .e-app-bar {\n display: flex;\n justify-content: space-between;\n flex-direction: row;\n align-items: center;\n background-color: var(--appbar-bg-top);\n padding: 7px 12px;\n position: relative;\n height: 42px;\n // left: 0;\n // right: 0;\n gap: var(--values-value-8);\n\n &--nav-icon, &--close-icon {\n padding: 7px;\n color: var(--white);\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n /* Title section */\n &--title-wrapper {\n display: flex;\n justify-content: ", ";\n align-items: center;\n width: 100%; // Ensures the title section takes full width to apply the alignment\n }\n\n &--title {\n display: flex;\n align-items: center;\n gap: 12px;\n overflow: hidden;\n // display: -webkit-box;\n // -webkit-line-clamp: 1;\n // line-clamp: 1;\n // -webkit-box-orient: vertical;\n }\n\n &--title-image img {\n width: 32px;\n object-fit: cover;\n // margin-left: -32px;\n background: var(--white);\n border: 1px solid var(--border);\n border-radius: var(--grid-borderradius-border-radius-xs);\n }\n\n &--title-label {\n color: var(--white);\n letter-spacing: 0.4px;\n font-size: var(--values-value-16);\n font-weight: 500;\n }\n }\n"])), function (props) {
|
|
190
190
|
return props.titleposition === 'left' ? 'flex-start' : props.titleposition === 'right' ? 'flex-end' : 'center';
|
|
191
191
|
});
|
|
192
192
|
var ESewaAppBar = function ESewaAppBar(_ref2) {
|
|
@@ -7083,18 +7083,22 @@ var EsewaFullPageLoadingScreen = function EsewaFullPageLoadingScreen(_ref) {
|
|
|
7083
7083
|
};
|
|
7084
7084
|
|
|
7085
7085
|
var _templateObject$l;
|
|
7086
|
-
var StyledRadio = styled__default.div(_templateObject$l || (_templateObject$l = _taggedTemplateLiteralLoose(["\n .container{\n display:flex;\n align-items: center;\n gap: 8px;\n }\n\n .container input[type=\"radio\" i] {\n margin: 0;\n appearance: none;\n width: 16px;\n height: 16px;\n border: 2px solid var(--secondary);\n border-radius: 50%;\n position: relative;\n cursor: pointer;\n transition: border-color 0.3s ease;\n }\n\n .container input[type=\"radio\"]:checked {\n border-color: var(--primary);\n }\n\n .container input[type=\"radio\"]::before {\n content: '';\n width: 10px;\n height: 10px;\n background-color: transparent;\n border-radius: 50%;\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n transition: background-color 0.3s ease;\n }\n\n .container input[type=\"radio\"]:checked::before {\n background-color: var(--primary);\n }\n"])));
|
|
7086
|
+
var StyledRadio = styled__default.div(_templateObject$l || (_templateObject$l = _taggedTemplateLiteralLoose(["\n .container{\n display:flex;\n align-items: center;\n gap: 8px;\n }\n\n .container input[type=\"radio\" i] {\n margin: 0;\n appearance: none;\n width: 16px;\n height: 16px;\n border: 2px solid var(--secondary);\n border-radius: 50%;\n position: relative;\n cursor: pointer;\n transition: border-color 0.3s ease;\n }\n\n .container input[type=\"radio\"]:checked {\n border-color: var(--primary);\n }\n\n .container input[type=\"radio\"]::before {\n content: '';\n width: 10px;\n height: 10px;\n background-color: transparent;\n border-radius: 50%;\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n transition: background-color 0.3s ease;\n }\n\n .container input[type=\"radio\"]:checked::before {\n background-color: var(--primary);\n }\n\n .container input[type=\"radio\"]:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n }\n"])));
|
|
7087
7087
|
var ESewaRadio = function ESewaRadio(_ref) {
|
|
7088
7088
|
var label = _ref.label,
|
|
7089
|
+
value = _ref.value,
|
|
7089
7090
|
name = _ref.name,
|
|
7090
7091
|
className = _ref.className,
|
|
7091
7092
|
_ref$labelClass = _ref.labelClass,
|
|
7092
7093
|
labelClass = _ref$labelClass === void 0 ? '' : _ref$labelClass,
|
|
7093
7094
|
onChange = _ref.onChange,
|
|
7094
|
-
checked = _ref.checked
|
|
7095
|
+
_ref$checked = _ref.checked,
|
|
7096
|
+
checked = _ref$checked === void 0 ? false : _ref$checked,
|
|
7097
|
+
_ref$disabled = _ref.disabled,
|
|
7098
|
+
disabled = _ref$disabled === void 0 ? false : _ref$disabled;
|
|
7095
7099
|
var handleRadioChange = function handleRadioChange(e) {
|
|
7096
|
-
if (onChange) {
|
|
7097
|
-
onChange(e.target.checked);
|
|
7100
|
+
if (onChange && !disabled) {
|
|
7101
|
+
onChange(value, e.target.checked);
|
|
7098
7102
|
}
|
|
7099
7103
|
};
|
|
7100
7104
|
return React__default.createElement(StyledRadio, null, React__default.createElement("div", {
|
|
@@ -7104,8 +7108,10 @@ var ESewaRadio = function ESewaRadio(_ref) {
|
|
|
7104
7108
|
}, React__default.createElement("input", {
|
|
7105
7109
|
type: 'radio',
|
|
7106
7110
|
name: name,
|
|
7111
|
+
value: value,
|
|
7107
7112
|
checked: checked,
|
|
7108
|
-
onChange: handleRadioChange
|
|
7113
|
+
onChange: handleRadioChange,
|
|
7114
|
+
disabled: disabled
|
|
7109
7115
|
}), React__default.createElement("span", {
|
|
7110
7116
|
className: "" + labelClass
|
|
7111
7117
|
}, label))));
|
|
@@ -7683,7 +7689,7 @@ var CONNECT_APP = function CONNECT_APP(data) {
|
|
|
7683
7689
|
if (isiOS) {
|
|
7684
7690
|
var _window$webkit;
|
|
7685
7691
|
if ((_window$webkit = window.webkit) !== null && _window$webkit !== void 0 && _window$webkit.messageHandlers.iOSNative) {
|
|
7686
|
-
window.webkit.messageHandlers.iOSNative.
|
|
7692
|
+
window.webkit.messageHandlers.iOSNative.postMessage(data);
|
|
7687
7693
|
} else {
|
|
7688
7694
|
console.warn('iOS interface not available');
|
|
7689
7695
|
}
|
|
@@ -7694,8 +7700,7 @@ var requestFromMiniApp = function requestFromMiniApp(data, callbackKey, callback
|
|
|
7694
7700
|
if (window.Android) {
|
|
7695
7701
|
window.Android[callbackKey] = callback;
|
|
7696
7702
|
} else if ((_window$webkit2 = window.webkit) !== null && _window$webkit2 !== void 0 && (_window$webkit2$messa = _window$webkit2.messageHandlers) !== null && _window$webkit2$messa !== void 0 && _window$webkit2$messa.iOSNative) {
|
|
7697
|
-
window.
|
|
7698
|
-
window.miniAppResponse[callbackKey] = callback;
|
|
7703
|
+
window.webkit.messageHandlers.iOSNative[callbackKey] = callback;
|
|
7699
7704
|
}
|
|
7700
7705
|
CONNECT_APP(JSON.stringify(data));
|
|
7701
7706
|
};
|
|
@@ -7865,11 +7870,13 @@ var useDebounce = function useDebounce(value, delay) {
|
|
|
7865
7870
|
};
|
|
7866
7871
|
|
|
7867
7872
|
var loadGoogleFont = function loadGoogleFont() {
|
|
7868
|
-
if (
|
|
7869
|
-
|
|
7870
|
-
|
|
7871
|
-
|
|
7872
|
-
|
|
7873
|
+
if (typeof window !== 'undefined' && document) {
|
|
7874
|
+
if (!document.querySelector("link[href*='Source+Sans+Pro']")) {
|
|
7875
|
+
var link = document.createElement('link');
|
|
7876
|
+
link.href = 'https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@300;400;600;700&display=swap';
|
|
7877
|
+
link.rel = 'stylesheet';
|
|
7878
|
+
document.head.appendChild(link);
|
|
7879
|
+
}
|
|
7873
7880
|
}
|
|
7874
7881
|
};
|
|
7875
7882
|
loadGoogleFont();
|