@digi-frontend/dgate-api-documentation 1.0.21 → 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 (35) hide show
  1. package/dist/src/components/InfoForm/InfoForm.js +1 -1
  2. package/dist/src/components/InfoForm/InfoForm.js.map +1 -1
  3. package/dist/src/components/JsonInput/JsonInput.js +1 -1
  4. package/dist/src/components/JsonInput/JsonInput.js.map +1 -1
  5. package/dist/src/components/LivePreview/LivePreview.js +1 -1
  6. package/dist/src/components/LivePreview/LivePreview.js.map +1 -1
  7. package/dist/src/components/MethodAccordion/MethodAccordion.js +1 -1
  8. package/dist/src/components/MethodAccordion/MethodAccordion.js.map +1 -1
  9. package/dist/src/components/Tooltip/Tooltip.js.map +1 -1
  10. package/dist/src/components/table/table.js +1 -1
  11. package/dist/src/components/table/table.js.map +1 -1
  12. package/dist/src/components/table/tags-table.js +1 -1
  13. package/dist/src/components/table/tags-table.js.map +1 -1
  14. package/dist/src/constants/regex.js +1 -1
  15. package/dist/src/constants/regex.js.map +1 -1
  16. package/dist/src/layout/layout.js +1 -1
  17. package/dist/src/layout/layout.js.map +1 -1
  18. package/dist/src/validator/form.scheme.js +1 -1
  19. package/dist/src/validator/form.scheme.js.map +1 -1
  20. package/dist/styles.css +473 -473
  21. package/dist/types/components/Tooltip/Tooltip.d.ts +2 -2
  22. package/dist/types/constants/regex.d.ts +1 -0
  23. package/dist/types/validator/form.scheme.d.ts +1 -1
  24. package/package.json +2 -2
  25. package/src/components/InfoForm/InfoForm.tsx +33 -14
  26. package/src/components/JsonInput/JsonInput.tsx +7 -1
  27. package/src/components/LivePreview/LivePreview.tsx +40 -21
  28. package/src/components/MethodAccordion/MethodAccordion.tsx +36 -14
  29. package/src/components/Tooltip/Tooltip.scss +3 -3
  30. package/src/components/Tooltip/Tooltip.tsx +2 -3
  31. package/src/components/table/table.tsx +3 -1
  32. package/src/components/table/tags-table.tsx +27 -5
  33. package/src/constants/regex.ts +1 -0
  34. package/src/layout/layout.tsx +41 -6
  35. 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;
@@ -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.21",
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",
@@ -97,6 +97,26 @@ const InfoForm = ({ readOnly }: { readOnly?: boolean }) => {
97
97
  tagName: item.name,
98
98
  description: (
99
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>
100
120
  <Tooltip
101
121
  arrowWithBorder
102
122
  placement="bottom-end"
@@ -140,14 +160,7 @@ const InfoForm = ({ readOnly }: { readOnly?: boolean }) => {
140
160
  variant="link"
141
161
  color="action"
142
162
  endIcon={<SVGLoader src={EditIcon} width="1.5rem" height="1.5rem" />}
143
- >
144
- {values.tags[index].description
145
- ? values.tags[index].description.substring(0, 12)
146
- : '-'}
147
- {values.tags[index].description && values.tags[index].description.length > 12
148
- ? '...'
149
- : ''}
150
- </Button>
163
+ ></Button>
151
164
  ) : (
152
165
  <Button
153
166
  className={styles.editDescBtn}
@@ -295,7 +308,7 @@ const InfoForm = ({ readOnly }: { readOnly?: boolean }) => {
295
308
  setFieldValue('info.title', '')
296
309
  }}
297
310
  errorMsg={errors?.info?.title}
298
- restrictedCharsRegex={regex.basic}
311
+ restrictedCharactersRegex={regex.basic}
299
312
  />
300
313
  <div className={styles.apiDocRow}>
301
314
  <Input
@@ -313,14 +326,16 @@ const InfoForm = ({ readOnly }: { readOnly?: boolean }) => {
313
326
  label="Description"
314
327
  value={values?.info?.description}
315
328
  maxLength={120}
329
+ required
316
330
  onChange={(value: string) => {
317
- setFieldValue('info.description', value)
331
+ if (value === '' || regex.ASCII.test(value)) {
332
+ setFieldValue('info.description', value)
333
+ }
318
334
  }}
319
335
  onClear={() => {
320
336
  setFieldValue('info.description', '')
321
337
  }}
322
338
  errorMessage={errors?.info?.description}
323
- restrictedCharsRegex={regex.restrictNone}
324
339
  />
325
340
  <div className={styles.paramsTable}>
326
341
  <TagsTable
@@ -346,17 +361,21 @@ const InfoForm = ({ readOnly }: { readOnly?: boolean }) => {
346
361
  className="delete-msg-container"
347
362
  >
348
363
  Are you sure you want to delete
349
- <span className="plan-name">{` Tag ${selectedTagName} `}</span>?
364
+ <span className="plan-name">
365
+ {' '}
366
+ Tag <strong>{selectedTagName}</strong>
367
+ </span>
368
+ ?
350
369
  </p>
351
370
  }
352
371
  onSubmit={{
353
372
  onClick: confirmDeleteTag,
354
- text: 'Yes',
373
+ text: 'Delete',
355
374
  color: 'error',
356
375
  fullWidth: true,
357
376
  }}
358
377
  onCancel={{
359
- text: 'No',
378
+ text: 'Cancel',
360
379
  color: 'normal',
361
380
  fullWidth: true,
362
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>
@@ -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])
@@ -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,6 +11,7 @@ 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
@@ -36,6 +37,7 @@ const Layout = ({ openApiJson, handleSave, setIsFormDirty }: ILayoutProps): JSX.
36
37
  validateForm(values)
37
38
  },
38
39
  })
40
+ const [isPublishDialogOpen, setIsPublishDialogOpen] = useState(false)
39
41
 
40
42
  useEffect(() => {
41
43
  if (setIsFormDirty) {
@@ -45,10 +47,11 @@ const Layout = ({ openApiJson, handleSave, setIsFormDirty }: ILayoutProps): JSX.
45
47
 
46
48
  return (
47
49
  <div className={styles.docsLayout}>
48
- <Alert className={styles.apiDocAlert} color="info">
49
- Changes are saved and published for API information and each endpoint individually as you
50
- update.
51
- </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
+ )}
52
55
  <div className={styles.layoutContainer}>
53
56
  <div className={`${styles.editorSide} ${styles.docSide}`}>
54
57
  <SectionHead
@@ -63,7 +66,8 @@ const Layout = ({ openApiJson, handleSave, setIsFormDirty }: ILayoutProps): JSX.
63
66
  type="submit"
64
67
  variant="contained"
65
68
  color="primary"
66
- onClick={formik.submitForm}
69
+ onClick={() => setIsPublishDialogOpen(true)}
70
+ disabled={!formik.isValid || formik.isSubmitting}
67
71
  >
68
72
  Save
69
73
  </Button>
@@ -104,6 +108,37 @@ const Layout = ({ openApiJson, handleSave, setIsFormDirty }: ILayoutProps): JSX.
104
108
  )}
105
109
  </div>
106
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
+ />
107
142
  </div>
108
143
  )
109
144
  }
@@ -1,11 +1,11 @@
1
- import * as yup from "yup";
1
+ import * as yup from 'yup'
2
2
  export const schemaValidation = yup.object({
3
3
  openapi: yup.string().required(),
4
4
  info: yup
5
5
  .object({
6
- title: yup.string().required("API Name is required."),
7
- description: yup.string().optional(),
8
- version: yup.string().required("API Version is required"),
6
+ title: yup.string().trim().required('API Name is required.'),
7
+ description: yup.string().required(),
8
+ version: yup.string().required('API Version is required'),
9
9
  })
10
10
  .required(),
11
11
  servers: yup
@@ -32,8 +32,8 @@ export const schemaValidation = yup.object({
32
32
  yup.object(
33
33
  props
34
34
  ? Object.keys(props).reduce((acc, propKey) => {
35
- acc[propKey] = yup.mixed();
36
- return acc;
35
+ acc[propKey] = yup.mixed()
36
+ return acc
37
37
  }, {})
38
38
  : {}
39
39
  )
@@ -42,8 +42,8 @@ export const schemaValidation = yup.object({
42
42
  items: yup.mixed().optional(),
43
43
  enum: yup.array(yup.string()).optional(),
44
44
  required: yup.array(yup.string()).optional(),
45
- });
46
- return acc;
45
+ })
46
+ return acc
47
47
  }, {})
48
48
  : {}
49
49
  )
@@ -67,4 +67,4 @@ export const schemaValidation = yup.object({
67
67
  })
68
68
  )
69
69
  .optional(),
70
- });
70
+ })