authscape 1.0.121 → 1.0.123
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/components/Datatable.js +122 -0
- package/components/FileUploader.js +348 -0
- package/jsconfig.json +1 -1
- package/package.json +15 -2
- package/services/apiService.js +253 -0
- package/services/authService.js +115 -0
- package/services/authorizationComponent.js +34 -0
- package/services/signInValidator.js +66 -0
- package/services/slug.js +11 -0
- package/services/storeWithExpiry.js +28 -0
- package/pages/Test.js +0 -8
- package/public/next.svg +0 -1
- package/public/vercel.svg +0 -1
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
import React, { Component } from 'react';
|
|
3
|
+
//import {apiService} from 'authscape';
|
|
4
|
+
import DataTable, {createTheme} from "react-data-table-component";
|
|
5
|
+
|
|
6
|
+
export class Datatable extends Component {
|
|
7
|
+
|
|
8
|
+
static defaultProps = {
|
|
9
|
+
options: {}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
constructor(props) {
|
|
13
|
+
super(props);
|
|
14
|
+
|
|
15
|
+
this.state = {
|
|
16
|
+
pageNumber : 1,
|
|
17
|
+
pageLength : props.pageLength ? props.pageLength : 10,
|
|
18
|
+
data: [],
|
|
19
|
+
loading: false,
|
|
20
|
+
totalRows: 0
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
componentDidMount = async () => {
|
|
25
|
+
await this.GetDataFromUrl(this.state.pageNumber, this.state.pageLength, this.props.params);
|
|
26
|
+
createTheme('dataTable', {
|
|
27
|
+
text:{
|
|
28
|
+
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
reload = async (reset = false) => {
|
|
34
|
+
|
|
35
|
+
if (reset === true)
|
|
36
|
+
{
|
|
37
|
+
this.setState({pageNumber: 1}, async () => {
|
|
38
|
+
await this.GetDataFromUrl(this.state.pageNumber, this.state.pageLength, this.props.params);
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
else
|
|
42
|
+
{
|
|
43
|
+
await this.GetDataFromUrl(this.state.pageNumber, this.state.pageLength, this.props.params);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
GetDataFromUrl = (page, length, postData = {}) => {
|
|
50
|
+
|
|
51
|
+
this.setState({
|
|
52
|
+
loading: true
|
|
53
|
+
}, async function () {
|
|
54
|
+
|
|
55
|
+
let data = postData;
|
|
56
|
+
|
|
57
|
+
data.offset = page;
|
|
58
|
+
data.length = length;
|
|
59
|
+
|
|
60
|
+
let response = null;
|
|
61
|
+
if (this.props.methodType == "get")
|
|
62
|
+
{
|
|
63
|
+
response = await apiService().get(this.props.url);
|
|
64
|
+
}
|
|
65
|
+
else
|
|
66
|
+
{
|
|
67
|
+
response = await apiService().post(this.props.url, postData);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (response != null && response.status === 200)
|
|
71
|
+
{
|
|
72
|
+
if (this.props.returnResult != null)
|
|
73
|
+
{
|
|
74
|
+
this.props.returnResult(response.data.data);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
this.setState({
|
|
78
|
+
totalRows: response.data.recordsTotal,
|
|
79
|
+
data: response.data.data,
|
|
80
|
+
loading: false
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
else
|
|
84
|
+
{
|
|
85
|
+
//console.error(response.status + " - " + response.data);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
handlePageChange = async page => {
|
|
93
|
+
const { pageLength } = this.state;
|
|
94
|
+
this.setState({pageNumber: page});
|
|
95
|
+
await this.GetDataFromUrl(page, pageLength, this.props.params);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
render() {
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<div>
|
|
102
|
+
<DataTable
|
|
103
|
+
title={this.props.title}
|
|
104
|
+
columns={this.props.columns}
|
|
105
|
+
data={this.state.data}
|
|
106
|
+
paginationRowsPerPageOptions={this.props.pageLength ? [this.props.pageLength] : [10]}
|
|
107
|
+
progressPending={this.state.loading}
|
|
108
|
+
//customStyles={this.props.customStyles}
|
|
109
|
+
paginationPerPage={this.props.pageLength ? this.props.pageLength : 10}
|
|
110
|
+
paginationServer
|
|
111
|
+
pagination
|
|
112
|
+
{...this.props.options}
|
|
113
|
+
//expandableRows={this.props.expandableRows}
|
|
114
|
+
//expandableRowsComponent={this.props.expandableRowsComponent}
|
|
115
|
+
paginationTotalRows={this.state.totalRows}
|
|
116
|
+
// onChangeRowsPerPage={this.handlePerRowsChange}
|
|
117
|
+
onChangePage={this.handlePageChange}
|
|
118
|
+
noDataComponent={this.props.noDataComponent}
|
|
119
|
+
/>
|
|
120
|
+
</div>);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React, { useState, useRef, useEffect } from "react";
|
|
4
|
+
import { apiService } from "../services/authService";
|
|
5
|
+
import LinearProgress from "@mui/material/LinearProgress";
|
|
6
|
+
import { Box } from "@mui/system";
|
|
7
|
+
import { Grid } from "@mui/material";
|
|
8
|
+
import FileCopyRoundedIcon from "@mui/icons-material/FileCopyRounded";
|
|
9
|
+
import UploadRoundedIcon from "@mui/icons-material/UploadRounded";
|
|
10
|
+
import Typography from "@mui/material/Typography";
|
|
11
|
+
import Stack from "@mui/material/Stack";
|
|
12
|
+
import FileDownloadOutlinedIcon from "@mui/icons-material/FileDownloadOutlined";
|
|
13
|
+
import IconButton from "@mui/material/IconButton";
|
|
14
|
+
import DeleteIcon from "@mui/icons-material/Delete";
|
|
15
|
+
import Button from "@mui/material/Button";
|
|
16
|
+
import Dialog from "@mui/material/Dialog";
|
|
17
|
+
import DialogActions from "@mui/material/DialogActions";
|
|
18
|
+
import DialogContent from "@mui/material/DialogContent";
|
|
19
|
+
import DialogContentText from "@mui/material/DialogContentText";
|
|
20
|
+
import DialogTitle from "@mui/material/DialogTitle";
|
|
21
|
+
import Tooltip from '@mui/material/Tooltip';
|
|
22
|
+
import Backdrop from '@mui/material/Backdrop';
|
|
23
|
+
import CircularProgress from '@mui/material/CircularProgress';
|
|
24
|
+
|
|
25
|
+
export default function FileUploader({
|
|
26
|
+
url,
|
|
27
|
+
params,
|
|
28
|
+
multiple = false,
|
|
29
|
+
fileLoaderUri = null,
|
|
30
|
+
children,
|
|
31
|
+
isHidden = false,
|
|
32
|
+
refOveride = null,
|
|
33
|
+
primaryColor = "#000",
|
|
34
|
+
deleteUri = "/Storage/RemoveFile?orderFileId="
|
|
35
|
+
}) {
|
|
36
|
+
// Declare a new state variable, which we'll call "count"
|
|
37
|
+
const [message, setMessage] = useState("");
|
|
38
|
+
const [loaded, setLoaded] = useState(0);
|
|
39
|
+
// const [selectedFile, setSelectedFile] = useState(null);
|
|
40
|
+
const [uploading, setUploading] = useState(false);
|
|
41
|
+
|
|
42
|
+
const [viewDeleteDialog, setViewDeleteDialog] = useState(false);
|
|
43
|
+
|
|
44
|
+
const [filesUploaded, setFilesUploaded] = useState([]);
|
|
45
|
+
|
|
46
|
+
const [orderFileId, setOrderFileId] = useState(null);
|
|
47
|
+
|
|
48
|
+
const [filesDownloadable, setFilesDownloadable] = useState(null);
|
|
49
|
+
|
|
50
|
+
const [parameters, setParameters] = useState([]);
|
|
51
|
+
|
|
52
|
+
const fileUploader = useRef();
|
|
53
|
+
|
|
54
|
+
const handleUpload = async (event) => {
|
|
55
|
+
let selectedFiles = event.target.files;
|
|
56
|
+
|
|
57
|
+
setLoaded(0);
|
|
58
|
+
setMessage(event.target.files[0] ? event.target.files[0].name : "");
|
|
59
|
+
|
|
60
|
+
if (uploading) return;
|
|
61
|
+
if (!selectedFiles) {
|
|
62
|
+
setMessage("Select a file first");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
setUploading(true);
|
|
67
|
+
|
|
68
|
+
let fileCount = selectedFiles.length;
|
|
69
|
+
let fileIndex = 1;
|
|
70
|
+
|
|
71
|
+
for (
|
|
72
|
+
let selectedIndex = 0;
|
|
73
|
+
selectedIndex < selectedFiles.length;
|
|
74
|
+
selectedIndex++
|
|
75
|
+
) {
|
|
76
|
+
const selectedFile = selectedFiles[selectedIndex];
|
|
77
|
+
|
|
78
|
+
const data = new FormData();
|
|
79
|
+
data.append("file", selectedFile, selectedFile.name);
|
|
80
|
+
|
|
81
|
+
for (let index = 0; index < parameters.length; index++) {
|
|
82
|
+
const element = parameters[index];
|
|
83
|
+
data.append(element.key, element.value);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
await apiService().post(url, data, {
|
|
88
|
+
onUploadProgress: (ProgressEvent) => {
|
|
89
|
+
let loadedTotal = Math.round(
|
|
90
|
+
(ProgressEvent.loaded / ProgressEvent.total) * 100
|
|
91
|
+
);
|
|
92
|
+
let percent = (fileIndex / fileCount) * 100;
|
|
93
|
+
setLoaded(percent);
|
|
94
|
+
|
|
95
|
+
if (percent == 100) {
|
|
96
|
+
setTimeout(() => {
|
|
97
|
+
setLoaded(0);
|
|
98
|
+
setMessage("");
|
|
99
|
+
reloadFiles();
|
|
100
|
+
}, 2000);
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
} catch (err) {
|
|
105
|
+
setUploading(false);
|
|
106
|
+
setMessage("Failed to upload");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
fileIndex++;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
setUploading(false);
|
|
113
|
+
setMessage("Uploaded successfully");
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const handleUploadFileInput = () => {
|
|
117
|
+
fileUploader.current.click();
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
if (params != null) {
|
|
122
|
+
const propertyNames = Object.keys(params);
|
|
123
|
+
const propertyValues = Object.values(params);
|
|
124
|
+
|
|
125
|
+
let array = [];
|
|
126
|
+
|
|
127
|
+
for (let index = 0; index < propertyNames.length; index++) {
|
|
128
|
+
array.push({
|
|
129
|
+
key: propertyNames[0],
|
|
130
|
+
value: propertyValues[0],
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
setParameters(array);
|
|
135
|
+
}
|
|
136
|
+
}, [params]);
|
|
137
|
+
|
|
138
|
+
const reloadFiles = async () => {
|
|
139
|
+
let response = await apiService().get(fileLoaderUri);
|
|
140
|
+
if (response != null && response.status == 200) {
|
|
141
|
+
setFilesDownloadable(response.data);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
if (fileLoaderUri != null) {
|
|
147
|
+
const fetchAsync = async () => {
|
|
148
|
+
await reloadFiles();
|
|
149
|
+
};
|
|
150
|
+
fetchAsync();
|
|
151
|
+
}
|
|
152
|
+
}, [fileLoaderUri]);
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<Box sx={{ marginTop: 1, display: isHidden ? "none" : "block" }}>
|
|
156
|
+
|
|
157
|
+
<input
|
|
158
|
+
className="inputfile"
|
|
159
|
+
id="file"
|
|
160
|
+
type="file"
|
|
161
|
+
name="file"
|
|
162
|
+
multiple={multiple}
|
|
163
|
+
ref={fileUploader}
|
|
164
|
+
onChange={handleUpload}
|
|
165
|
+
style={{ display: "none" }}
|
|
166
|
+
/>
|
|
167
|
+
|
|
168
|
+
<Grid container spacing={2}>
|
|
169
|
+
<Grid ref={refOveride} item xs={4} onClick={handleUploadFileInput}>
|
|
170
|
+
<Grid
|
|
171
|
+
container
|
|
172
|
+
sx={{
|
|
173
|
+
padding: 2,
|
|
174
|
+
backgroundColor: "#ECEDED",
|
|
175
|
+
fontSize: "14px",
|
|
176
|
+
cursor: "pointer",
|
|
177
|
+
borderRadius: "8px",
|
|
178
|
+
border: "1px dashed #C8D4D5",
|
|
179
|
+
}}
|
|
180
|
+
>
|
|
181
|
+
<Grid item xs={2}>
|
|
182
|
+
<Box>
|
|
183
|
+
<FileCopyRoundedIcon
|
|
184
|
+
sx={{ fill: "#C8D4D5", width: 50, height: 50 }}
|
|
185
|
+
/>
|
|
186
|
+
</Box>
|
|
187
|
+
</Grid>
|
|
188
|
+
<Grid item xs={10}>
|
|
189
|
+
<Box>Drag and drop files here or</Box>
|
|
190
|
+
<Box sx={{ marginTop: 1 }}>
|
|
191
|
+
<Stack direction="row" spacing={2}>
|
|
192
|
+
<UploadRoundedIcon
|
|
193
|
+
sx={{ fill: primaryColor, width: 30, height: 30 }}
|
|
194
|
+
/>
|
|
195
|
+
<Typography
|
|
196
|
+
variant="h3"
|
|
197
|
+
component={"span"}
|
|
198
|
+
sx={{ color: primaryColor, paddingTop: 0.6 }}
|
|
199
|
+
>
|
|
200
|
+
Upload
|
|
201
|
+
</Typography>
|
|
202
|
+
</Stack>
|
|
203
|
+
</Box>
|
|
204
|
+
</Grid>
|
|
205
|
+
<Grid item xs={12}>
|
|
206
|
+
{loaded > 0 && (
|
|
207
|
+
<LinearProgress
|
|
208
|
+
variant="buffer"
|
|
209
|
+
value={loaded}
|
|
210
|
+
sx={{ marginTop: 2 }}
|
|
211
|
+
/>
|
|
212
|
+
)}
|
|
213
|
+
|
|
214
|
+
{loaded == 100 && (
|
|
215
|
+
<Typography
|
|
216
|
+
variant="h3"
|
|
217
|
+
component={"span"}
|
|
218
|
+
sx={{ color: primaryColor, paddingTop: 0.6 }}>
|
|
219
|
+
Completed
|
|
220
|
+
</Typography>
|
|
221
|
+
)}
|
|
222
|
+
</Grid>
|
|
223
|
+
</Grid>
|
|
224
|
+
</Grid>
|
|
225
|
+
|
|
226
|
+
{children}
|
|
227
|
+
|
|
228
|
+
<Grid container sx={{ paddingTop: 1 }}>
|
|
229
|
+
{filesDownloadable != null &&
|
|
230
|
+
filesDownloadable.map((fileUpload, idx) => {
|
|
231
|
+
return (
|
|
232
|
+
<Grid
|
|
233
|
+
key={"fileDownloadable-" + idx}
|
|
234
|
+
item
|
|
235
|
+
xs={8}
|
|
236
|
+
sm={8}
|
|
237
|
+
md={5}
|
|
238
|
+
lg={3}
|
|
239
|
+
sx={{
|
|
240
|
+
marginLeft: 2,
|
|
241
|
+
padding: 1,
|
|
242
|
+
marginTop: 1,
|
|
243
|
+
backgroundColor: "#ECEDED",
|
|
244
|
+
position: "relative",
|
|
245
|
+
fontSize: "14px",
|
|
246
|
+
cursor: "pointer",
|
|
247
|
+
borderRadius: "8px",
|
|
248
|
+
border: "1px solid #54C7DD",
|
|
249
|
+
}}
|
|
250
|
+
onClick={async () => {
|
|
251
|
+
window.open(fileUpload.uri);
|
|
252
|
+
}}
|
|
253
|
+
>
|
|
254
|
+
<Tooltip placement="left" arrow title={fileUpload.name}>
|
|
255
|
+
<Stack
|
|
256
|
+
direction="row"
|
|
257
|
+
spacing={1}
|
|
258
|
+
display="flex"
|
|
259
|
+
justifyContent={"space-between"}
|
|
260
|
+
>
|
|
261
|
+
<Box display={"flex"} alignItems="center">
|
|
262
|
+
<FileDownloadOutlinedIcon sx={{ fill: "#92D6E3" }} />
|
|
263
|
+
<Box
|
|
264
|
+
sx={{
|
|
265
|
+
paddingTop: 0.6,
|
|
266
|
+
marginLeft: "5px",
|
|
267
|
+
}}
|
|
268
|
+
>
|
|
269
|
+
<Typography
|
|
270
|
+
sx={{
|
|
271
|
+
overflow: "hidden",
|
|
272
|
+
whiteSpace: "nowrap",
|
|
273
|
+
textOverflow: "ellipsis",
|
|
274
|
+
width:"350px"
|
|
275
|
+
}}
|
|
276
|
+
>
|
|
277
|
+
{fileUpload.name}
|
|
278
|
+
</Typography>
|
|
279
|
+
</Box>
|
|
280
|
+
|
|
281
|
+
<IconButton
|
|
282
|
+
aria-label="delete"
|
|
283
|
+
sx={{ position: "absolute", right: "0" }}
|
|
284
|
+
onClick={(evt) => {
|
|
285
|
+
evt.stopPropagation();
|
|
286
|
+
setOrderFileId(fileUpload.id);
|
|
287
|
+
setViewDeleteDialog(true);
|
|
288
|
+
}}
|
|
289
|
+
>
|
|
290
|
+
<DeleteIcon />
|
|
291
|
+
</IconButton>
|
|
292
|
+
</Box>
|
|
293
|
+
</Stack>
|
|
294
|
+
</Tooltip>
|
|
295
|
+
</Grid>
|
|
296
|
+
);
|
|
297
|
+
})}
|
|
298
|
+
</Grid>
|
|
299
|
+
</Grid>
|
|
300
|
+
|
|
301
|
+
<Dialog
|
|
302
|
+
open={viewDeleteDialog}
|
|
303
|
+
fullWidth={true}
|
|
304
|
+
maxWidth={"xs"}
|
|
305
|
+
onClose={() => {
|
|
306
|
+
setViewDeleteDialog(false);
|
|
307
|
+
}}
|
|
308
|
+
aria-labelledby="alert-dialog-title"
|
|
309
|
+
aria-describedby="alert-dialog-description">
|
|
310
|
+
<DialogTitle id="alert-dialog-title">Remove File</DialogTitle>
|
|
311
|
+
<DialogContent>
|
|
312
|
+
<DialogContentText id="alert-dialog-description">
|
|
313
|
+
Are you sure you want to remove this file?
|
|
314
|
+
</DialogContentText>
|
|
315
|
+
</DialogContent>
|
|
316
|
+
<DialogActions>
|
|
317
|
+
<Button
|
|
318
|
+
onClick={() => {
|
|
319
|
+
setViewDeleteDialog(false);
|
|
320
|
+
}}
|
|
321
|
+
>
|
|
322
|
+
No
|
|
323
|
+
</Button>
|
|
324
|
+
<Button
|
|
325
|
+
onClick={async () => {
|
|
326
|
+
|
|
327
|
+
let response = await apiService().delete(
|
|
328
|
+
deleteUri + orderFileId
|
|
329
|
+
);
|
|
330
|
+
if (response != null && response.status == 200) {
|
|
331
|
+
setViewDeleteDialog(false);
|
|
332
|
+
await reloadFiles();
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
}}
|
|
336
|
+
autoFocus
|
|
337
|
+
>
|
|
338
|
+
Yes
|
|
339
|
+
</Button>
|
|
340
|
+
</DialogActions>
|
|
341
|
+
</Dialog>
|
|
342
|
+
|
|
343
|
+
<Backdrop sx={{ color: '#fff', zIndex: 99999 }} open={uploading}>
|
|
344
|
+
<CircularProgress color="inherit" />
|
|
345
|
+
</Backdrop>
|
|
346
|
+
</Box>
|
|
347
|
+
);
|
|
348
|
+
}
|
package/jsconfig.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "authscape",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.123",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "next dev",
|
|
6
6
|
"build": "next build",
|
|
@@ -8,8 +8,21 @@
|
|
|
8
8
|
"lint": "next lint"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
+
"@emotion/react": "^11.11.0",
|
|
12
|
+
"@emotion/styled": "^11.11.0",
|
|
13
|
+
"@mui/icons-material": "^5.11.16",
|
|
14
|
+
"@mui/material": "^5.13.2",
|
|
15
|
+
"@mui/styled-engine-sc": "^5.12.0",
|
|
16
|
+
"@stripe/react-stripe-js": "^2.1.0",
|
|
17
|
+
"@stripe/stripe-js": "^1.53.0",
|
|
18
|
+
"axios": "^1.4.0",
|
|
19
|
+
"js-file-download": "^0.4.12",
|
|
11
20
|
"next": "13.4.4",
|
|
21
|
+
"nookies": "^2.5.2",
|
|
22
|
+
"query-string": "^8.1.0",
|
|
12
23
|
"react": "18.2.0",
|
|
13
|
-
"react-
|
|
24
|
+
"react-data-table-component": "^7.5.3",
|
|
25
|
+
"react-dom": "18.2.0",
|
|
26
|
+
"styled-components": "^5.3.11"
|
|
14
27
|
}
|
|
15
28
|
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import axios from 'axios'
|
|
2
|
+
import querystring from 'query-string';
|
|
3
|
+
import fileDownload from 'js-file-download';
|
|
4
|
+
import { parseCookies, setCookie, destroyCookie } from 'nookies';
|
|
5
|
+
|
|
6
|
+
const setupDefaultOptions = async (ctx = null) => {
|
|
7
|
+
|
|
8
|
+
let defaultOptions = {};
|
|
9
|
+
if (ctx == null)
|
|
10
|
+
{
|
|
11
|
+
let accessToken = parseCookies().access_token || '';
|
|
12
|
+
|
|
13
|
+
if (accessToken !== null && accessToken !== undefined && accessToken != "") {
|
|
14
|
+
defaultOptions = {
|
|
15
|
+
headers: {
|
|
16
|
+
Authorization: "Bearer " + accessToken
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
defaultOptions = {
|
|
22
|
+
headers: {
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
else
|
|
28
|
+
{
|
|
29
|
+
defaultOptions = {
|
|
30
|
+
headers: {
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return defaultOptions;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const RefreshToken = async (originalRequest, instance) => {
|
|
39
|
+
|
|
40
|
+
let accessToken = parseCookies().access_token || '';
|
|
41
|
+
let refreshToken = parseCookies().refresh_token || '';
|
|
42
|
+
|
|
43
|
+
let response = await instance.post(process.env.AUTHORITYURI + "/connect/token",
|
|
44
|
+
querystring.stringify({
|
|
45
|
+
grant_type: 'refresh_token',
|
|
46
|
+
client_id: process.env.client_id,
|
|
47
|
+
client_secret: process.env.client_secret,
|
|
48
|
+
refresh_token: refreshToken
|
|
49
|
+
}), {
|
|
50
|
+
headers: {
|
|
51
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
52
|
+
"Authorization": "Bearer " + accessToken
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (response != null && response.status == 200)
|
|
57
|
+
{
|
|
58
|
+
originalRequest.headers['Authorization'] = 'Bearer ' + response.data.access_token;
|
|
59
|
+
|
|
60
|
+
await setCookie(null, "access_token", response.data.access_token,
|
|
61
|
+
{
|
|
62
|
+
maxAge: 2147483647,
|
|
63
|
+
path: '/',
|
|
64
|
+
domain: process.env.cookieDomain,
|
|
65
|
+
secure: true
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
await setCookie(null, "expires_in", response.data.expires_in,
|
|
69
|
+
{
|
|
70
|
+
maxAge: 2147483647,
|
|
71
|
+
path: '/',
|
|
72
|
+
domain: process.env.cookieDomain,
|
|
73
|
+
secure: true
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
await setCookie(null, "refresh_token", response.data.refresh_token,
|
|
77
|
+
{
|
|
78
|
+
maxAge: 2147483647,
|
|
79
|
+
path: '/',
|
|
80
|
+
domain: process.env.cookieDomain,
|
|
81
|
+
secure: true
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const apiService = (ctx = null) => {
|
|
87
|
+
|
|
88
|
+
let env = process.env.STAGE;
|
|
89
|
+
if (env == "development")
|
|
90
|
+
{
|
|
91
|
+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let baseUri = process.env.APIURI + "/api";
|
|
95
|
+
|
|
96
|
+
const instance = axios.create({
|
|
97
|
+
baseURL: baseUri,
|
|
98
|
+
//timeout: 10000,
|
|
99
|
+
params: {} // do not remove this, its added to add params later in the config
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
instance.interceptors.response.use(
|
|
103
|
+
(response) => {
|
|
104
|
+
|
|
105
|
+
return response;
|
|
106
|
+
},
|
|
107
|
+
async (error) => {
|
|
108
|
+
const originalConfig = error.config;
|
|
109
|
+
if (error.response) {
|
|
110
|
+
|
|
111
|
+
if (error.response.status === 401 && !originalConfig._retry) {
|
|
112
|
+
originalConfig._retry = true;
|
|
113
|
+
|
|
114
|
+
// Do something, call refreshToken() request for example;
|
|
115
|
+
await RefreshToken(originalConfig, instance);
|
|
116
|
+
|
|
117
|
+
// return a request
|
|
118
|
+
return instance.request(originalConfig);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (error.response.status === 400) {
|
|
122
|
+
// Do something
|
|
123
|
+
|
|
124
|
+
if (error.response.config.url.includes("/connect/token")) // remove the access and refresh if invalid
|
|
125
|
+
{
|
|
126
|
+
destroyCookie(null, "access_token", {
|
|
127
|
+
maxAge: 2147483647,
|
|
128
|
+
path: '/',
|
|
129
|
+
domain: process.env.cookieDomain
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
destroyCookie(null, "refresh_token", {
|
|
133
|
+
maxAge: 2147483647,
|
|
134
|
+
path: '/',
|
|
135
|
+
domain: process.env.cookieDomain
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
destroyCookie(null, "expires_in", {
|
|
139
|
+
maxAge: 2147483647,
|
|
140
|
+
path: '/',
|
|
141
|
+
domain: process.env.cookieDomain
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return Promise.reject(error);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return Promise.reject(error);
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
|
|
155
|
+
get: async (url, options= {}) => {
|
|
156
|
+
|
|
157
|
+
try
|
|
158
|
+
{
|
|
159
|
+
let defaultOptions = await setupDefaultOptions(ctx);
|
|
160
|
+
return await instance.get(url, { ...defaultOptions, ...options });
|
|
161
|
+
}
|
|
162
|
+
catch(error)
|
|
163
|
+
{
|
|
164
|
+
return error.response;
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
post: async (url, data, options = {}) => {
|
|
168
|
+
|
|
169
|
+
try
|
|
170
|
+
{
|
|
171
|
+
let defaultOptions = await setupDefaultOptions(ctx);
|
|
172
|
+
return await instance.post(url, data, { ...defaultOptions, ...options });
|
|
173
|
+
}
|
|
174
|
+
catch(error)
|
|
175
|
+
{
|
|
176
|
+
return error.response;
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
put: async (url, data, options = {}) => {
|
|
180
|
+
|
|
181
|
+
try
|
|
182
|
+
{
|
|
183
|
+
let defaultOptions = await setupDefaultOptions(ctx);
|
|
184
|
+
return await instance.put(url, data, { ...defaultOptions, ...options });
|
|
185
|
+
}
|
|
186
|
+
catch(error)
|
|
187
|
+
{
|
|
188
|
+
return error.response;
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
delete: async (url, options = {}) => {
|
|
192
|
+
|
|
193
|
+
try
|
|
194
|
+
{
|
|
195
|
+
let defaultOptions = await setupDefaultOptions(ctx);
|
|
196
|
+
return await instance.delete(url, { ...defaultOptions, ...options });
|
|
197
|
+
}
|
|
198
|
+
catch(error)
|
|
199
|
+
{
|
|
200
|
+
return error.response;
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
GetCurrentUser: async () => {
|
|
204
|
+
|
|
205
|
+
try
|
|
206
|
+
{
|
|
207
|
+
let accessToken = parseCookies().access_token || null;
|
|
208
|
+
|
|
209
|
+
if (accessToken)
|
|
210
|
+
{
|
|
211
|
+
let defaultOptions = await setupDefaultOptions(null);
|
|
212
|
+
const response = await instance.get('/UserManagement', defaultOptions);
|
|
213
|
+
if (response != null && response.status == 200)
|
|
214
|
+
{
|
|
215
|
+
return response.data;
|
|
216
|
+
}
|
|
217
|
+
// else if (response != null && response.status == 401)
|
|
218
|
+
// {
|
|
219
|
+
// // call the login window maybe?
|
|
220
|
+
// }
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
} catch(exp) {
|
|
224
|
+
//return -1;
|
|
225
|
+
console.log(exp.message);
|
|
226
|
+
}
|
|
227
|
+
return null;
|
|
228
|
+
},
|
|
229
|
+
DownloadFile: async (url, fileName, completed) => {
|
|
230
|
+
|
|
231
|
+
try
|
|
232
|
+
{
|
|
233
|
+
//let defaultOptions = await setupDefaultOptions();
|
|
234
|
+
let defaultOptions = {};
|
|
235
|
+
let options = { responseType: "blob" };
|
|
236
|
+
let response = await instance.get(url, { ...defaultOptions, ...options });
|
|
237
|
+
if (response.status === 200) {
|
|
238
|
+
fileDownload(response.data, fileName);
|
|
239
|
+
if (completed !== undefined) {
|
|
240
|
+
completed();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
catch(error)
|
|
245
|
+
{
|
|
246
|
+
console.error(error);
|
|
247
|
+
if (completed !== undefined) {
|
|
248
|
+
completed();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { destroyCookie } from 'nookies';
|
|
3
|
+
|
|
4
|
+
export const authService = () => {
|
|
5
|
+
|
|
6
|
+
return {
|
|
7
|
+
|
|
8
|
+
dec2hex: (dec) => {
|
|
9
|
+
return ('0' + dec.toString(16)).substr(-2)
|
|
10
|
+
},
|
|
11
|
+
generateRandomString: () => {
|
|
12
|
+
var array = new Uint32Array(56/2);
|
|
13
|
+
window.crypto.getRandomValues(array);
|
|
14
|
+
return Array.from(array, authService().dec2hex).join('');
|
|
15
|
+
},
|
|
16
|
+
sha256: (plain) => {
|
|
17
|
+
const encoder = new TextEncoder();
|
|
18
|
+
const data = encoder.encode(plain);
|
|
19
|
+
return window.crypto.subtle.digest('SHA-256', data);
|
|
20
|
+
},
|
|
21
|
+
base64urlencode: (a) => {
|
|
22
|
+
var str = "";
|
|
23
|
+
var bytes = new Uint8Array(a);
|
|
24
|
+
var len = bytes.byteLength;
|
|
25
|
+
for (var i = 0; i < len; i++) {
|
|
26
|
+
str += String.fromCharCode(bytes[i]);
|
|
27
|
+
}
|
|
28
|
+
return btoa(str)
|
|
29
|
+
.replace(/\+/g, "-")
|
|
30
|
+
.replace(/\//g, "_")
|
|
31
|
+
.replace(/=+$/, "");
|
|
32
|
+
},
|
|
33
|
+
challenge_from_verifier: async (v) => {
|
|
34
|
+
let hashed = await authService().sha256(v);
|
|
35
|
+
let base64encoded = authService().base64urlencode(hashed);
|
|
36
|
+
return base64encoded;
|
|
37
|
+
},
|
|
38
|
+
login: async (redirectUserUri = null, dnsRecord = null, deviceId = null) => {
|
|
39
|
+
|
|
40
|
+
let state = "1234";
|
|
41
|
+
if (redirectUserUri != null)
|
|
42
|
+
{
|
|
43
|
+
localStorage.setItem("redirectUri", redirectUserUri);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let verifier = authService().generateRandomString();
|
|
47
|
+
var challenge = await authService().challenge_from_verifier(verifier);
|
|
48
|
+
|
|
49
|
+
window.localStorage.setItem("verifier", verifier);
|
|
50
|
+
|
|
51
|
+
let redirectUri = window.location.origin + "/signin-oidc";
|
|
52
|
+
let loginUri = process.env.AUTHORITYURI + "/connect/authorize?response_type=code&state=" + state + "&client_id=" + process.env.client_id + "&scope=email%20openid%20offline_access%20profile%20api1&redirect_uri=" + redirectUri + "&code_challenge=" + challenge + "&code_challenge_method=S256";
|
|
53
|
+
|
|
54
|
+
if (deviceId)
|
|
55
|
+
{
|
|
56
|
+
loginUri += "&deviceId=" + deviceId; // will be for chrome extention and mobile apps later
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
window.location.href = loginUri;
|
|
60
|
+
},
|
|
61
|
+
signUp: (redirectUrl = null) => {
|
|
62
|
+
|
|
63
|
+
let AuthUri = process.env.AUTHORITYURI;
|
|
64
|
+
|
|
65
|
+
let url = "";
|
|
66
|
+
if (redirectUrl == null)
|
|
67
|
+
{
|
|
68
|
+
url = AuthUri + "/Account/Register?returnUrl=" + window.location.href;
|
|
69
|
+
localStorage.setItem("redirectUri", window.location.href);
|
|
70
|
+
}
|
|
71
|
+
else
|
|
72
|
+
{
|
|
73
|
+
url = AuthUri + "/Account/Register?returnUrl=" + redirectUrl;
|
|
74
|
+
localStorage.setItem("redirectUri", redirectUrl);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
window.location.href = url;
|
|
78
|
+
},
|
|
79
|
+
logout: async (redirectUri = null) => {
|
|
80
|
+
|
|
81
|
+
let AuthUri = process.env.AUTHORITYURI;
|
|
82
|
+
let cookieDomain = process.env.cookieDomain;
|
|
83
|
+
|
|
84
|
+
destroyCookie({}, "access_token", {
|
|
85
|
+
maxAge: 2147483647,
|
|
86
|
+
path: '/',
|
|
87
|
+
domain: cookieDomain
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
destroyCookie({}, "refresh_token", {
|
|
91
|
+
maxAge: 2147483647,
|
|
92
|
+
path: '/',
|
|
93
|
+
domain: cookieDomain
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
destroyCookie({}, "expires_in", {
|
|
97
|
+
maxAge: 2147483647,
|
|
98
|
+
path: '/',
|
|
99
|
+
domain: cookieDomain
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
setTimeout(() => {
|
|
103
|
+
if (redirectUri == null)
|
|
104
|
+
{
|
|
105
|
+
window.location.href = AuthUri + "/connect/logout?redirect=" + window.location.href;
|
|
106
|
+
}
|
|
107
|
+
else
|
|
108
|
+
{
|
|
109
|
+
window.location.href = AuthUri + "/connect/logout?redirect=" + redirectUri;
|
|
110
|
+
}
|
|
111
|
+
}, 500);
|
|
112
|
+
|
|
113
|
+
},
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React, { useEffect, useState, useRef } from 'react';
|
|
2
|
+
//import apiService from './apiService';
|
|
3
|
+
|
|
4
|
+
export function AuthorizationComponent({children, setCurrentUser, userLoaded, isLoading}) {
|
|
5
|
+
|
|
6
|
+
const [loaded, setLoaded] = useState(false);
|
|
7
|
+
const validateUserSignedIn = async () => {
|
|
8
|
+
|
|
9
|
+
setLoaded(true);
|
|
10
|
+
|
|
11
|
+
let usr = await apiService().GetCurrentUser();
|
|
12
|
+
if (usr != null)
|
|
13
|
+
{
|
|
14
|
+
setCurrentUser(usr);
|
|
15
|
+
}
|
|
16
|
+
else
|
|
17
|
+
{
|
|
18
|
+
setCurrentUser(null);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
userLoaded();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
|
|
26
|
+
if (!loaded)
|
|
27
|
+
{
|
|
28
|
+
validateUserSignedIn();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
}, [loaded]);
|
|
32
|
+
|
|
33
|
+
return (children)
|
|
34
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
import querystring from "query-string";
|
|
4
|
+
import { setCookie } from 'nookies';
|
|
5
|
+
|
|
6
|
+
export const signInValidator = async (queryCode) => {
|
|
7
|
+
|
|
8
|
+
let codeVerifier = window.localStorage.getItem("verifier");
|
|
9
|
+
if (queryCode != null && codeVerifier != null)
|
|
10
|
+
{
|
|
11
|
+
const headers = {'Content-Type': 'application/x-www-form-urlencoded'}
|
|
12
|
+
|
|
13
|
+
let queryString = querystring.stringify({
|
|
14
|
+
code: queryCode,
|
|
15
|
+
grant_type: "authorization_code",
|
|
16
|
+
redirect_uri: window.location.origin + "/signin-oidc",
|
|
17
|
+
client_id: process.env.client_id,
|
|
18
|
+
client_secret: process.env.client_secret,
|
|
19
|
+
code_verifier: codeVerifier
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
let response = await axios.post(process.env.AUTHORITYURI + '/connect/token', queryString, {
|
|
23
|
+
headers: headers
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
window.localStorage.removeItem("verifier");
|
|
27
|
+
|
|
28
|
+
let domain = process.env.cookieDomain;
|
|
29
|
+
|
|
30
|
+
await setCookie(null, "access_token", response.data.access_token,
|
|
31
|
+
{
|
|
32
|
+
maxAge: 2147483647,
|
|
33
|
+
path: '/',
|
|
34
|
+
domain: domain,
|
|
35
|
+
secure: true
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
await setCookie(null, "expires_in", response.data.expires_in,
|
|
39
|
+
{
|
|
40
|
+
maxAge: 2147483647,
|
|
41
|
+
path: '/',
|
|
42
|
+
domain: domain,
|
|
43
|
+
secure: true
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
await setCookie(null, "refresh_token", response.data.refresh_token,
|
|
47
|
+
{
|
|
48
|
+
maxAge: 2147483647,
|
|
49
|
+
path: '/',
|
|
50
|
+
domain: domain,
|
|
51
|
+
secure: true
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
let redirectUri = localStorage.getItem("redirectUri")
|
|
56
|
+
localStorage.clear();
|
|
57
|
+
if (redirectUri != null)
|
|
58
|
+
{
|
|
59
|
+
window.location.href = redirectUri;
|
|
60
|
+
}
|
|
61
|
+
else
|
|
62
|
+
{
|
|
63
|
+
window.location.href = "/";
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
package/services/slug.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export const storeWithExpiry = () => {
|
|
2
|
+
|
|
3
|
+
return {
|
|
4
|
+
set: (key, value, ttl) => {
|
|
5
|
+
|
|
6
|
+
const now = new Date()
|
|
7
|
+
const item = {
|
|
8
|
+
value: value,
|
|
9
|
+
expiry: now.getTime() + ttl,
|
|
10
|
+
}
|
|
11
|
+
localStorage.setItem(key, JSON.stringify(item))
|
|
12
|
+
},
|
|
13
|
+
get: (key) => {
|
|
14
|
+
|
|
15
|
+
const itemStr = localStorage.getItem(key)
|
|
16
|
+
if (!itemStr) {
|
|
17
|
+
return null
|
|
18
|
+
}
|
|
19
|
+
const item = JSON.parse(itemStr)
|
|
20
|
+
const now = new Date()
|
|
21
|
+
if (now.getTime() > item.expiry) {
|
|
22
|
+
localStorage.removeItem(key)
|
|
23
|
+
return null
|
|
24
|
+
}
|
|
25
|
+
return item.value
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
package/pages/Test.js
DELETED
package/public/next.svg
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
package/public/vercel.svg
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>
|