@digi-frontend/dgate-api-documentation 1.0.20 → 1.0.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.
Files changed (42) hide show
  1. package/dist/_virtual/index3.js +1 -1
  2. package/dist/_virtual/index4.js +1 -1
  3. package/dist/_virtual/index5.js +1 -1
  4. package/dist/_virtual/index6.js +1 -1
  5. package/dist/node_modules/toposort/index.js +1 -1
  6. package/dist/node_modules/yup/index.esm.js +1 -1
  7. package/dist/src/components/InfoForm/InfoForm.js +1 -1
  8. package/dist/src/components/InfoForm/InfoForm.js.map +1 -1
  9. package/dist/src/components/JsonInput/JsonInput.js +1 -1
  10. package/dist/src/components/JsonInput/JsonInput.js.map +1 -1
  11. package/dist/src/components/LivePreview/LivePreview.js +1 -1
  12. package/dist/src/components/LivePreview/LivePreview.js.map +1 -1
  13. package/dist/src/components/MethodAccordion/MethodAccordion.js +1 -1
  14. package/dist/src/components/MethodAccordion/MethodAccordion.js.map +1 -1
  15. package/dist/src/components/Tooltip/Tooltip.js.map +1 -1
  16. package/dist/src/components/table/table.js +1 -1
  17. package/dist/src/components/table/table.js.map +1 -1
  18. package/dist/src/components/table/tags-table.js +1 -1
  19. package/dist/src/components/table/tags-table.js.map +1 -1
  20. package/dist/src/constants/regex.js +1 -1
  21. package/dist/src/constants/regex.js.map +1 -1
  22. package/dist/src/layout/layout.js +1 -1
  23. package/dist/src/layout/layout.js.map +1 -1
  24. package/dist/src/validator/form.scheme.js +1 -1
  25. package/dist/src/validator/form.scheme.js.map +1 -1
  26. package/dist/styles.css +488 -488
  27. package/dist/types/components/Tooltip/Tooltip.d.ts +2 -2
  28. package/dist/types/constants/regex.d.ts +1 -0
  29. package/dist/types/layout/layout.d.ts +2 -1
  30. package/dist/types/validator/form.scheme.d.ts +1 -1
  31. package/package.json +2 -2
  32. package/src/components/InfoForm/InfoForm.tsx +37 -15
  33. package/src/components/JsonInput/JsonInput.tsx +7 -1
  34. package/src/components/LivePreview/LivePreview.tsx +40 -22
  35. package/src/components/MethodAccordion/MethodAccordion.tsx +37 -15
  36. package/src/components/Tooltip/Tooltip.scss +3 -3
  37. package/src/components/Tooltip/Tooltip.tsx +2 -3
  38. package/src/components/table/table.tsx +3 -1
  39. package/src/components/table/tags-table.tsx +27 -5
  40. package/src/constants/regex.ts +1 -0
  41. package/src/layout/layout.tsx +49 -7
  42. package/src/validator/form.scheme.ts +9 -9
@@ -1,8 +1,8 @@
1
1
  import React, { ReactNode } from 'react';
2
+ import { TippyProps } from '@tippyjs/react';
2
3
  import 'tippy.js/dist/tippy.css';
3
4
  import './Tooltip.scss';
4
- interface TooltipProps {
5
- children: ReactNode;
5
+ interface TooltipProps extends TippyProps {
6
6
  content: ReactNode;
7
7
  success?: boolean;
8
8
  onMouseEnter?: (event: React.MouseEvent<HTMLDivElement>) => void;
@@ -1,5 +1,6 @@
1
1
  declare const regex: {
2
2
  basic: RegExp;
3
3
  restrictNone: RegExp;
4
+ ASCII: RegExp;
4
5
  };
5
6
  export default regex;
@@ -3,6 +3,7 @@ import { OpenAPIFile } from '../types/openApi';
3
3
  interface ILayoutProps {
4
4
  openApiJson?: OpenAPIFile;
5
5
  handleSave?: (values: unknown) => unknown;
6
+ setIsFormDirty?: any;
6
7
  }
7
- declare const Layout: ({ openApiJson, handleSave }: ILayoutProps) => JSX.Element;
8
+ declare const Layout: ({ openApiJson, handleSave, setIsFormDirty }: ILayoutProps) => JSX.Element;
8
9
  export default Layout;
@@ -1,4 +1,4 @@
1
- import * as yup from "yup";
1
+ import * as yup from 'yup';
2
2
  export declare const schemaValidation: yup.ObjectSchema<{
3
3
  openapi: string;
4
4
  info: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
- "name": "@digi-frontend/dgate-api-documentation",
3
- "version": "1.0.20",
2
+ "name": "@digi-frontend/dgate-api-documentation",
3
+ "version": "1.0.25",
4
4
  "main": "dist/src/index.js",
5
5
  "module": "dist/src/index.js",
6
6
  "types": "dist/types/index.d.ts",
@@ -30,7 +30,10 @@ const InfoForm = ({ readOnly }: { readOnly?: boolean }) => {
30
30
  if (values && values.components && values.components.securitySchemes) {
31
31
  const authenticatorKeys = Object.keys(values.components.securitySchemes)
32
32
  if (authenticatorKeys.length) {
33
- setAuthType(values.components.securitySchemes[authenticatorKeys[0]].scheme)
33
+ setAuthType(
34
+ values.components.securitySchemes[authenticatorKeys[0]].scheme ||
35
+ values.components.securitySchemes[authenticatorKeys[0]].type
36
+ )
34
37
  }
35
38
  }
36
39
  }, [])
@@ -94,6 +97,26 @@ const InfoForm = ({ readOnly }: { readOnly?: boolean }) => {
94
97
  tagName: item.name,
95
98
  description: (
96
99
  <div className={styles.paramDescContainer}>
100
+ <Tooltip
101
+ key={`${index}-description`}
102
+ allowHTML
103
+ disabled={values.tags[index].description?.length <= 12}
104
+ content={<div style={{ padding: '0.625rem' }}>{values.tags[index].description}</div>}
105
+ arrowWithBorder
106
+ placement="bottom-end"
107
+ type="function"
108
+ delay={[0, 0]}
109
+ onShow={() => tooltipRefs[index]?.hide()}
110
+ >
111
+ <p style={{ alignSelf: 'center', fontWeight: 600, fontSize: '1rem' }}>
112
+ {values.tags[index].description
113
+ ? values.tags[index].description.substring(0, 12)
114
+ : readOnly && '-'}
115
+ {values.tags[index].description && values.tags[index].description.length > 12
116
+ ? '...'
117
+ : ''}
118
+ </p>
119
+ </Tooltip>
97
120
  <Tooltip
98
121
  arrowWithBorder
99
122
  placement="bottom-end"
@@ -137,14 +160,7 @@ const InfoForm = ({ readOnly }: { readOnly?: boolean }) => {
137
160
  variant="link"
138
161
  color="action"
139
162
  endIcon={<SVGLoader src={EditIcon} width="1.5rem" height="1.5rem" />}
140
- >
141
- {values.tags[index].description
142
- ? values.tags[index].description.substring(0, 12)
143
- : '-'}
144
- {values.tags[index].description && values.tags[index].description.length > 12
145
- ? '...'
146
- : ''}
147
- </Button>
163
+ ></Button>
148
164
  ) : (
149
165
  <Button
150
166
  className={styles.editDescBtn}
@@ -292,7 +308,7 @@ const InfoForm = ({ readOnly }: { readOnly?: boolean }) => {
292
308
  setFieldValue('info.title', '')
293
309
  }}
294
310
  errorMsg={errors?.info?.title}
295
- restrictedCharsRegex={regex.basic}
311
+ restrictedCharactersRegex={regex.basic}
296
312
  />
297
313
  <div className={styles.apiDocRow}>
298
314
  <Input
@@ -310,14 +326,16 @@ const InfoForm = ({ readOnly }: { readOnly?: boolean }) => {
310
326
  label="Description"
311
327
  value={values?.info?.description}
312
328
  maxLength={120}
329
+ required
313
330
  onChange={(value: string) => {
314
- setFieldValue('info.description', value)
331
+ if (value === '' || regex.ASCII.test(value)) {
332
+ setFieldValue('info.description', value)
333
+ }
315
334
  }}
316
335
  onClear={() => {
317
336
  setFieldValue('info.description', '')
318
337
  }}
319
338
  errorMessage={errors?.info?.description}
320
- restrictedCharsRegex={regex.restrictNone}
321
339
  />
322
340
  <div className={styles.paramsTable}>
323
341
  <TagsTable
@@ -343,17 +361,21 @@ const InfoForm = ({ readOnly }: { readOnly?: boolean }) => {
343
361
  className="delete-msg-container"
344
362
  >
345
363
  Are you sure you want to delete
346
- <span className="plan-name">{` Tag ${selectedTagName} `}</span>?
364
+ <span className="plan-name">
365
+ {' '}
366
+ Tag <strong>{selectedTagName}</strong>
367
+ </span>
368
+ ?
347
369
  </p>
348
370
  }
349
371
  onSubmit={{
350
372
  onClick: confirmDeleteTag,
351
- text: 'Yes',
373
+ text: 'Delete',
352
374
  color: 'error',
353
375
  fullWidth: true,
354
376
  }}
355
377
  onCancel={{
356
- text: 'No',
378
+ text: 'Cancel',
357
379
  color: 'normal',
358
380
  fullWidth: true,
359
381
  }}
@@ -2,6 +2,12 @@ import React, { useEffect, useState } from 'react'
2
2
  import yaml from 'js-yaml'
3
3
  import styles from './style.module.scss'
4
4
 
5
+ const errorMapping = {
6
+ BOTH: 'Invalid JSON or YAML format',
7
+ JSON: 'Invalid JSON format',
8
+ YML: 'Invalid YAML format',
9
+ }
10
+
5
11
  const JsonInput = ({
6
12
  placeholder,
7
13
  label,
@@ -129,7 +135,7 @@ const JsonInput = ({
129
135
  )}
130
136
  </div>
131
137
  {(errorMessage || (value !== '' && isValid === false)) && (
132
- <p className={styles['error-message']}>{errorMessage || 'Invalid JSON or YAML format'}</p>
138
+ <p className={styles['error-message']}>{errorMessage || errorMapping[acceptType]}</p>
133
139
  )}
134
140
  </div>
135
141
  )
@@ -7,6 +7,7 @@ import { useFormikContext } from 'formik'
7
7
  import { methodColorMapping, tagsTableHeaders } from '../../constants/index'
8
8
  import TagsTable from '../table/tags-table'
9
9
  import { Button } from 'digitinary-ui'
10
+ import Tooltip from '../../components/Tooltip/Tooltip'
10
11
 
11
12
  interface LivePreviewProps {
12
13
  transformedData?: TransformedOpenApi
@@ -37,12 +38,23 @@ const LivePreview: React.FC<LivePreviewProps> = ({ transformedData }) => {
37
38
  id: index,
38
39
  tagName: item.name,
39
40
  description: (
40
- <div className={styles.paramDescContainer}>
41
- <Button className={styles.editDescBtn} variant="link" color="action">
42
- {item.description ? item.description.substring(0, 12) : '-'}
43
- {item.description && item.description.length > 12 ? '...' : ''}
44
- </Button>
45
- </div>
41
+ <Tooltip
42
+ key={`${index}-description`}
43
+ allowHTML
44
+ disabled={item.description?.length <= 12}
45
+ content={<div style={{ padding: '0.625rem' }}>{item.description}</div>}
46
+ arrowWithBorder
47
+ placement="bottom-end"
48
+ type="function"
49
+ delay={[0, 0]}
50
+ >
51
+ <div className={styles.paramDescContainer}>
52
+ <p className={styles.editDescBtn}>
53
+ {item.description ? item.description.substring(0, 12) : '-'}
54
+ {item.description && item.description.length > 12 ? '...' : ''}
55
+ </p>
56
+ </div>
57
+ </Tooltip>
46
58
  ),
47
59
  externalDocs: (
48
60
  <div className={styles.paramDescContainer}>
@@ -91,6 +103,7 @@ const LivePreview: React.FC<LivePreviewProps> = ({ transformedData }) => {
91
103
  ),
92
104
  }))
93
105
  }
106
+
94
107
  return (
95
108
  <div>
96
109
  <div className="row">
@@ -99,7 +112,11 @@ const LivePreview: React.FC<LivePreviewProps> = ({ transformedData }) => {
99
112
  <SimpleLabelValue
100
113
  key={'APIAuthenticationType'}
101
114
  label={'API authentication type: '}
102
- value={!!securityKey ? securitySchemes[securityKey].scheme : '-'}
115
+ value={
116
+ !!securityKey
117
+ ? securitySchemes[securityKey].scheme || securitySchemes[securityKey].type
118
+ : '-'
119
+ }
103
120
  />
104
121
  <SimpleLabelValue key={'version'} label={'Version: '} value={info?.version || '-'} />
105
122
  <SimpleLabelValue
@@ -124,20 +141,22 @@ const LivePreview: React.FC<LivePreviewProps> = ({ transformedData }) => {
124
141
  <div className={styles.methodsContainer} key={path.path}>
125
142
  {Object.entries(
126
143
  path.methods
127
- .sort((a, b) => methodColorMapping[a.type].order + methodColorMapping[b.type].order)
128
- .reduce((groupedMethods, method) => {
129
- // Handle methods without tags
130
- const tags = method.tags?.length ? method?.tags : ['default'];
131
-
132
- tags.forEach((tag) => {
133
- if (!groupedMethods[tag]) {
134
- groupedMethods[tag] = [];
135
- }
136
- groupedMethods[tag].push(method);
137
- });
138
-
139
- return groupedMethods;
140
- }, {})
144
+ .sort(
145
+ (a, b) => methodColorMapping[a.type].order + methodColorMapping[b.type].order
146
+ )
147
+ .reduce((groupedMethods, method) => {
148
+ // Handle methods without tags
149
+ const tags = method.tags?.length ? method?.tags : ['default']
150
+
151
+ tags.forEach((tag) => {
152
+ if (!groupedMethods[tag]) {
153
+ groupedMethods[tag] = []
154
+ }
155
+ groupedMethods[tag].push(method)
156
+ })
157
+
158
+ return groupedMethods
159
+ }, {})
141
160
  ).map(([tag, methods]) => (
142
161
  <div key={tag}>
143
162
  <h3>{tag}</h3>
@@ -147,7 +166,6 @@ const LivePreview: React.FC<LivePreviewProps> = ({ transformedData }) => {
147
166
  method={method}
148
167
  path={path.path}
149
168
  tags={values.tags}
150
- handleSave={() => null}
151
169
  />
152
170
  ))}
153
171
  </div>
@@ -100,6 +100,31 @@ const MethodsAccordion = ({
100
100
  description: (
101
101
  <div className={styles.paramDescContainer}>
102
102
  <Tooltip
103
+ key={`${index}-description`}
104
+ allowHTML
105
+ disabled={method.parameters[index].description?.length <= 12}
106
+ content={
107
+ <div style={{ padding: '0.625rem' }}>{method.parameters[index].description}</div>
108
+ }
109
+ arrowWithBorder
110
+ placement="bottom-end"
111
+ type="function"
112
+ delay={[0, 0]}
113
+ onShow={() => tooltipRefs[index]?.hide()}
114
+ >
115
+ <p style={{ alignSelf: 'center' }}>
116
+ {method.parameters[index].description
117
+ ? method.parameters[index].description.substring(0, 12)
118
+ : '-'}
119
+ {method.parameters[index].description &&
120
+ method.parameters[index].description.length > 12
121
+ ? '...'
122
+ : ''}
123
+ </p>
124
+ </Tooltip>
125
+ <Tooltip
126
+ key={`${index}-add-edit-description`}
127
+ allowHTML
103
128
  arrowWithBorder
104
129
  placement="bottom-end"
105
130
  type="function"
@@ -142,15 +167,7 @@ const MethodsAccordion = ({
142
167
  variant="link"
143
168
  color="action"
144
169
  endIcon={<SVGLoader src={EditIcon} width="1.5rem" height="1.5rem" />}
145
- >
146
- {method.parameters[index].description
147
- ? method.parameters[index].description.substring(0, 12)
148
- : '-'}
149
- {method.parameters[index].description &&
150
- method.parameters[index].description.length > 12
151
- ? '...'
152
- : ''}
153
- </Button>
170
+ ></Button>
154
171
  ) : (
155
172
  <Button
156
173
  className={styles.editDescBtn}
@@ -210,14 +227,13 @@ const MethodsAccordion = ({
210
227
 
211
228
  useEffect(() => {
212
229
  if (method?.parameters) {
213
- setTableRecords(generateTableData(method.parameters))
214
230
  setTableData(method.parameters)
215
231
  }
216
232
  }, [method, path])
217
233
 
218
234
  useEffect(() => {
219
235
  // prepare tags selection list
220
- if (!selectionTags.length) {
236
+ if (method?.tags.length || tags.length) {
221
237
  const convertedStringArray = (method?.tags || [])?.map((item) => ({
222
238
  label: capitalize(item),
223
239
  value: item,
@@ -230,6 +246,7 @@ const MethodsAccordion = ({
230
246
  const filteredArray = mergedArray.filter(
231
247
  (value, index, self) => index === self.findIndex((t) => t.value === value.value)
232
248
  )
249
+
233
250
  setSelectionTags(filteredArray)
234
251
  }
235
252
  }, [tags, method])
@@ -287,7 +304,7 @@ const MethodsAccordion = ({
287
304
  isMultiple={true}
288
305
  withSearch={false}
289
306
  clearable={false}
290
- />{' '}
307
+ />
291
308
  {!readOnly ? (
292
309
  <TextArea
293
310
  className={styles.methodDesc}
@@ -343,6 +360,7 @@ const MethodsAccordion = ({
343
360
  }
344
361
  children={
345
362
  <JsonInput
363
+ acceptType="JSON"
346
364
  withFooter={!readOnly}
347
365
  className={'jsonField'}
348
366
  placeholder="Enter your request body as a JSON object...."
@@ -399,6 +417,7 @@ const MethodsAccordion = ({
399
417
  }
400
418
  children={
401
419
  <JsonInput
420
+ acceptType="JSON"
402
421
  withFooter={!readOnly}
403
422
  className={'jsonField'}
404
423
  placeholder="Enter your response as a JSON object..."
@@ -451,17 +470,20 @@ const MethodsAccordion = ({
451
470
  className="delete-msg-container"
452
471
  >
453
472
  Are you sure you want to delete
454
- <span className="plan-name">{` Parameter ${selectedParamName} `}</span>?
473
+ <span className="plan-name">
474
+ Parameter <strong>{selectedParamName}</strong>
475
+ </span>
476
+ ?
455
477
  </p>
456
478
  }
457
479
  onSubmit={{
458
480
  onClick: confirmDeleteParameter,
459
- text: 'Yes',
481
+ text: 'Delete',
460
482
  color: 'error',
461
483
  fullWidth: true,
462
484
  }}
463
485
  onCancel={{
464
- text: 'No',
486
+ text: 'Cancel',
465
487
  color: 'normal',
466
488
  fullWidth: true,
467
489
  }}
@@ -113,15 +113,15 @@
113
113
  height: 1px;
114
114
 
115
115
  &:before {
116
- width: 1rem;
117
- height: 1rem;
116
+ width: 0.625rem;
117
+ height: 0.625rem;
118
118
  background-color: white;
119
119
  transform: rotate(45deg);
120
120
  border: none;
121
121
  border-left: 1px solid #d8dae5 !important;
122
122
  border-top: 1px solid #d8dae5 !important;
123
123
  z-index: 20000000000;
124
- top: -0.6rem;
124
+ top: -0.45rem;
125
125
  transform-origin: center !important;
126
126
  }
127
127
  }
@@ -1,10 +1,9 @@
1
1
  import React, { useState, useEffect, ReactNode } from 'react'
2
- import Tippy from '@tippyjs/react'
2
+ import Tippy, { TippyProps } from '@tippyjs/react'
3
3
  import 'tippy.js/dist/tippy.css'
4
4
  import './Tooltip.scss'
5
5
 
6
- interface TooltipProps {
7
- children: ReactNode
6
+ interface TooltipProps extends TippyProps {
8
7
  content: ReactNode
9
8
  success?: boolean
10
9
  onMouseEnter?: (event: React.MouseEvent<HTMLDivElement>) => void
@@ -8,6 +8,7 @@ import styles from '../MethodAccordion/MethodAccordion.module.scss'
8
8
  import { useFormik } from 'formik'
9
9
  import * as yup from 'yup'
10
10
  import { capitalize } from '../../helpers/methodAccordion.helper'
11
+ import regex from '../../constants/regex'
11
12
 
12
13
  const ParamterTable = ({
13
14
  id,
@@ -131,6 +132,7 @@ const ParamterTable = ({
131
132
  }} // Pass the value directly
132
133
  value={values.name} // Bind value to the state
133
134
  disabled={readOnly}
135
+ restrictedCharactersRegex={regex.basic}
134
136
  />
135
137
  </div>
136
138
  </td>
@@ -225,7 +227,7 @@ const ParamterTable = ({
225
227
  <TextArea
226
228
  value={text || values.description}
227
229
  onChange={(value) => {
228
- setText(value)
230
+ if (value === '' || regex.ASCII.test(value)) setText(value)
229
231
  }}
230
232
  disabled={readOnly}
231
233
  placeholder="Describe parameter..."
@@ -8,6 +8,9 @@ import { AddRow, EditIcon, DeleteIcon } from '../../assets/icons'
8
8
  import styles from '../MethodAccordion/MethodAccordion.module.scss'
9
9
  import { useFormik } from 'formik'
10
10
  import * as yup from 'yup'
11
+ import regex from '../../constants/regex'
12
+
13
+ const urlRegex = /^(https?:\/\/)?(www\.)?([a-zA-Z0-9-]+(\.[a-zA-Z]{2,})+)(\/[^\s]*)?$/
11
14
 
12
15
  const TagsTable = ({ id, headCells, data, isFormOpen, setIsFormOpen, saveNewRow, readOnly }) => {
13
16
  const [text, setText] = useState('')
@@ -28,7 +31,20 @@ const TagsTable = ({ id, headCells, data, isFormOpen, setIsFormOpen, saveNewRow,
28
31
  validationSchema: yup.object().shape({
29
32
  name: yup.string().required('Tag name is required'),
30
33
  description: yup.string().optional(),
31
- externalDocs: yup.object().optional(),
34
+ externalDocs: yup
35
+ .object()
36
+ .shape({
37
+ url: yup
38
+ .string()
39
+ .matches(urlRegex, 'Invalid URL')
40
+ .when('description', (description, schema) => {
41
+ return description?.at(0)
42
+ ? schema.required('URL is required when description is provided')
43
+ : schema.optional()
44
+ }),
45
+ description: yup.string().optional(),
46
+ })
47
+ .optional(),
32
48
  }),
33
49
  onSubmit: (values) => {
34
50
  saveNewRow(values)
@@ -38,7 +54,9 @@ const TagsTable = ({ id, headCells, data, isFormOpen, setIsFormOpen, saveNewRow,
38
54
  resetForm()
39
55
  setIsFormOpen(false)
40
56
  },
57
+ validateOnChange: true,
41
58
  })
59
+
42
60
  return (
43
61
  <div className="tableSectionContainer">
44
62
  <div className="tableContainer">
@@ -107,7 +125,7 @@ const TagsTable = ({ id, headCells, data, isFormOpen, setIsFormOpen, saveNewRow,
107
125
  size="large"
108
126
  type="text"
109
127
  onChange={(value) => {
110
- setFieldValue('name', value)
128
+ !regex.basic.test(value) && setFieldValue('name', value)
111
129
  }} // Pass the value directly
112
130
  value={values.name} // Bind value to the state
113
131
  disabled={readOnly}
@@ -131,7 +149,7 @@ const TagsTable = ({ id, headCells, data, isFormOpen, setIsFormOpen, saveNewRow,
131
149
  <TextArea
132
150
  value={text || values.description}
133
151
  onChange={(value) => {
134
- setText(value)
152
+ if (value === '' || regex.ASCII.test(value)) setText(value)
135
153
  }}
136
154
  disabled={readOnly}
137
155
  placeholder="Describe Tag..."
@@ -184,7 +202,9 @@ const TagsTable = ({ id, headCells, data, isFormOpen, setIsFormOpen, saveNewRow,
184
202
  placeholder="Describe External Doc..."
185
203
  value={externalDesc || values.externalDocs.description}
186
204
  disabled={readOnly}
187
- onChange={(value) => setExternalDesc(value)}
205
+ onChange={(value) => {
206
+ if (value === '' || regex.ASCII.test(value)) setExternalDesc(value)
207
+ }}
188
208
  />
189
209
  <p className={_styles.editDescTooltipContent_header}>
190
210
  External Docs Link
@@ -193,7 +213,9 @@ const TagsTable = ({ id, headCells, data, isFormOpen, setIsFormOpen, saveNewRow,
193
213
  placeholder="External Docs Link..."
194
214
  value={externalUrl || values.externalDocs.url}
195
215
  disabled={readOnly}
196
- onChange={(value) => setExternalUrl(value)}
216
+ onChange={(value) => {
217
+ if (value === '' || regex.ASCII.test(value)) setExternalUrl(value)
218
+ }}
197
219
  />
198
220
  {!readOnly && (
199
221
  <Button
@@ -1,5 +1,6 @@
1
1
  const regex = {
2
2
  basic: /[^a-zA-Z0-9-_ ]/, // not (alphanumeric + underscore + dash + space)
3
3
  restrictNone: /^$/, // restrict nothing
4
+ ASCII: /^[\x00-\x7F]+$/,
4
5
  }
5
6
  export default regex
@@ -1,4 +1,4 @@
1
- import { JSX, useEffect } from 'react'
1
+ import { JSX, useEffect, useState } from 'react'
2
2
  import { Alert, Button } from 'digitinary-ui'
3
3
  import MethodsAccordion from '../components/MethodAccordion/MethodAccordion'
4
4
  import styles from './layout.module.css'
@@ -11,13 +11,15 @@ import { FormikProvider, useFormik } from 'formik'
11
11
  import { schemaValidation } from '../validator/form.scheme'
12
12
  import { TransformedOpenApi } from '@entities/transformedOpenApi'
13
13
  import { methodColorMapping } from '../constants/index'
14
+ import CommonDialog from '../components/dialog'
14
15
 
15
16
  interface ILayoutProps {
16
17
  openApiJson?: OpenAPIFile
17
18
  handleSave?: (values: unknown) => unknown
19
+ setIsFormDirty?: any
18
20
  }
19
21
 
20
- const Layout = ({ openApiJson, handleSave }: ILayoutProps): JSX.Element => {
22
+ const Layout = ({ openApiJson, handleSave, setIsFormDirty }: ILayoutProps): JSX.Element => {
21
23
  const clonedOpenApiJson = structuredClone(openApiJson)
22
24
  const transformedOpenApi = transformOpenApiObject(clonedOpenApiJson)
23
25
  const formik = useFormik<TransformedOpenApi>({
@@ -35,13 +37,21 @@ const Layout = ({ openApiJson, handleSave }: ILayoutProps): JSX.Element => {
35
37
  validateForm(values)
36
38
  },
37
39
  })
40
+ const [isPublishDialogOpen, setIsPublishDialogOpen] = useState(false)
41
+
42
+ useEffect(() => {
43
+ if (setIsFormDirty) {
44
+ setIsFormDirty(formik.dirty)
45
+ }
46
+ }, [formik.dirty])
38
47
 
39
48
  return (
40
49
  <div className={styles.docsLayout}>
41
- <Alert className={styles.apiDocAlert} color="info">
42
- Changes are saved and published for API information and each endpoint individually as you
43
- update.
44
- </Alert>
50
+ {formik.dirty && (
51
+ <Alert className={styles.apiDocAlert} color="warning" severity="warning">
52
+ There are changes you made may not be saved
53
+ </Alert>
54
+ )}
45
55
  <div className={styles.layoutContainer}>
46
56
  <div className={`${styles.editorSide} ${styles.docSide}`}>
47
57
  <SectionHead
@@ -56,7 +66,8 @@ const Layout = ({ openApiJson, handleSave }: ILayoutProps): JSX.Element => {
56
66
  type="submit"
57
67
  variant="contained"
58
68
  color="primary"
59
- onClick={formik.submitForm}
69
+ onClick={() => setIsPublishDialogOpen(true)}
70
+ disabled={!formik.isValid || formik.isSubmitting}
60
71
  >
61
72
  Save
62
73
  </Button>
@@ -97,6 +108,37 @@ const Layout = ({ openApiJson, handleSave }: ILayoutProps): JSX.Element => {
97
108
  )}
98
109
  </div>
99
110
  </div>
111
+ <CommonDialog
112
+ status="warning"
113
+ content={
114
+ <p
115
+ style={{
116
+ textAlign: 'center',
117
+ fontWeight: 400,
118
+ fontSize: '1rem',
119
+ lineHeight: '1.4375rem',
120
+ }}
121
+ >
122
+ Are you sure you want to Publish your changes?
123
+ </p>
124
+ }
125
+ onSubmit={{
126
+ onClick: () => {
127
+ formik.handleSubmit()
128
+ setIsPublishDialogOpen(false)
129
+ },
130
+ text: 'Publish',
131
+ color: 'warning',
132
+ fullWidth: true,
133
+ }}
134
+ onCancel={{
135
+ text: 'Cancel',
136
+ color: 'normal',
137
+ fullWidth: true,
138
+ }}
139
+ onClose={() => setIsPublishDialogOpen(false)}
140
+ open={isPublishDialogOpen}
141
+ />
100
142
  </div>
101
143
  )
102
144
  }