@campxdev/shared 0.6.7 → 0.6.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@campxdev/shared",
3
- "version": "0.6.7",
3
+ "version": "0.6.9",
4
4
  "main": "./exports.ts",
5
5
  "scripts": {
6
6
  "start": "react-scripts start",
@@ -26,6 +26,7 @@
26
26
  "dependencies": {
27
27
  "@emotion/react": "^11.9.0",
28
28
  "@emotion/styled": "^11.8.1",
29
+ "@hookform/resolvers": "^2.9.10",
29
30
  "@mui/icons-material": "^5.6.2",
30
31
  "@mui/material": "^5.7.0",
31
32
  "@mui/x-date-pickers": "^5.0.0-alpha.3",
@@ -37,20 +38,19 @@
37
38
  "react": "^18.2.0",
38
39
  "react-dom": "^18.2.0",
39
40
  "react-error-boundary": "^3.1.4",
40
- "react-hook-form": "^7.31.1",
41
+ "react-hook-form": "^7.40.0",
41
42
  "react-query": "^3.39.0",
42
43
  "react-router-dom": "^6.4.2",
43
44
  "react-table": "^7.8.0",
44
45
  "react-toastify": "^9.0.1",
45
46
  "styled-components": "^5.3.5",
46
- "swiper": "^8.1.5"
47
+ "swiper": "^8.1.5",
48
+ "yup": "^0.32.11"
47
49
  },
48
50
  "devDependencies": {
49
51
  "@types/js-cookie": "^3.0.2",
50
52
  "@types/node": "^18.11.8",
51
- "typescript": "^4.8.4",
52
53
  "@types/react": "^18.0.25",
53
- "react-scripts": "^5.0.1",
54
54
  "@typescript-eslint/eslint-plugin": "^5.35.1",
55
55
  "@typescript-eslint/parser": "^5.35.1",
56
56
  "eslint": "^8.23.0",
@@ -60,6 +60,8 @@
60
60
  "eslint-plugin-prettier": "^4.2.1",
61
61
  "eslint-plugin-react": "^7.31.1",
62
62
  "lerna": "^5.5.1",
63
- "prettier": "^2.5.0"
63
+ "prettier": "^2.5.0",
64
+ "react-scripts": "^5.0.1",
65
+ "typescript": "^4.8.4"
64
66
  }
65
67
  }
@@ -0,0 +1,156 @@
1
+ import axios, { axiosErrorToast } from '../../config/axios'
2
+ import { yupResolver } from '@hookform/resolvers/yup'
3
+ import { Alert, Box, Button, ButtonProps, Typography } from '@mui/material'
4
+ import RenderForm, { generateYupSchema, RenderFormProps } from './RenderForm'
5
+ import { ReactNode, useState } from 'react'
6
+ import { useForm } from 'react-hook-form'
7
+ import { useMutation, useQueryClient } from 'react-query'
8
+ import { toast } from 'react-toastify'
9
+
10
+ const makePostRequest = async ({
11
+ endpoint,
12
+ postBody,
13
+ }: {
14
+ endpoint: string
15
+ postBody: any
16
+ }) => {
17
+ const method = postBody?.id ? 'PUT' : 'POST'
18
+ const url = postBody?.id ? `${endpoint}/${postBody?.id}` : endpoint
19
+ return await axios({
20
+ url,
21
+ method,
22
+ data: postBody,
23
+ })
24
+ }
25
+
26
+ interface FormProps extends Omit<RenderFormProps, 'control'> {
27
+ buttonProps?: ButtonProps
28
+ submitBtn?: { label?: ReactNode }
29
+ cancelBtn?: { label?: ReactNode }
30
+ onSubmit?: (formData) => any
31
+ onCancel?: () => void
32
+ endPoint?: string
33
+ defaultValues?: any
34
+ refetchKey?: string
35
+ onSuccess?: () => void
36
+ previousData?: any
37
+ }
38
+
39
+ export default function Form({
40
+ submitBtn = { label: 'Submit' },
41
+ cancelBtn = { label: 'Cancel' },
42
+ buttonProps,
43
+ onSubmit = () => {},
44
+ endPoint,
45
+ defaultValues,
46
+ cols,
47
+ fields,
48
+ dropdowns,
49
+ refetchKey,
50
+ onSuccess = () => {},
51
+ onCancel,
52
+ previousData,
53
+ }: FormProps) {
54
+ const queryClient = useQueryClient()
55
+ const [error, setError] = useState<any>(null)
56
+ const { control, watch, handleSubmit } = useForm({
57
+ defaultValues,
58
+ resolver: yupResolver(generateYupSchema([fields])),
59
+ })
60
+
61
+ const {
62
+ data,
63
+ mutate,
64
+ isLoading: posting,
65
+ } = useMutation(makePostRequest, {
66
+ onSuccess(data) {
67
+ queryClient.invalidateQueries(refetchKey)
68
+ toast.success('Created/Updated Successfully')
69
+ onSuccess && onSuccess()
70
+ },
71
+ onError(error: any) {
72
+ setError(error?.response?.data?.message)
73
+ axiosErrorToast(error)
74
+ },
75
+ })
76
+
77
+ const onError = (error) => {
78
+ const errorArray = Object.values(error)?.map((item: any) => item?.message)
79
+ setError(errorArray)
80
+ }
81
+
82
+ return (
83
+ <form
84
+ onSubmit={handleSubmit((originalFormData) => {
85
+ const modifiedFormData = onSubmit(originalFormData) ?? originalFormData
86
+
87
+ mutate({
88
+ endpoint: endPoint,
89
+ postBody: modifiedFormData,
90
+ })
91
+ }, onError)}
92
+ >
93
+ <RenderForm
94
+ cols={cols}
95
+ fields={fields}
96
+ control={control}
97
+ dropdowns={dropdowns}
98
+ />
99
+
100
+ <FormErrorDisplay message={error} />
101
+ <Box
102
+ sx={{
103
+ display: 'flex',
104
+ gap: '20px',
105
+ marginTop: '1rem',
106
+ }}
107
+ >
108
+ <Button fullWidth type="submit" {...buttonProps}>
109
+ {submitBtn?.label}
110
+ </Button>
111
+ <Button
112
+ fullWidth
113
+ variant="outlined"
114
+ onClick={() => {
115
+ onCancel ? onCancel() : null
116
+ }}
117
+ {...buttonProps}
118
+ >
119
+ {cancelBtn?.label}
120
+ </Button>
121
+ </Box>
122
+ </form>
123
+ )
124
+ }
125
+
126
+ const FormErrorDisplay = ({ message }: { message: string | string[] }) => {
127
+ if (!message) return null
128
+
129
+ if (typeof message === 'string')
130
+ return (
131
+ <Alert severity="error" sx={{ marginTop: '1rem' }}>
132
+ {message}
133
+ </Alert>
134
+ )
135
+
136
+ return (
137
+ <Alert
138
+ severity="error"
139
+ sx={{
140
+ marginTop: '1rem',
141
+ '& .MuiAlert-icon': {
142
+ display: 'flex',
143
+ alignItems: 'center',
144
+ },
145
+ }}
146
+ >
147
+ <Box>
148
+ {message?.map((item, index) => (
149
+ <Typography color="error" variant="body1" key={index}>
150
+ {item}
151
+ </Typography>
152
+ ))}
153
+ </Box>
154
+ </Alert>
155
+ )
156
+ }
@@ -0,0 +1,188 @@
1
+ import { Box, Typography } from '@mui/material'
2
+ import { ReactNode } from 'react'
3
+ import * as yup from 'yup'
4
+ import { Control } from 'react-hook-form'
5
+ import {
6
+ FormDatePicker,
7
+ FormRadioGroup,
8
+ FormSingleSelect,
9
+ FormTextField,
10
+ } from '../HookForm'
11
+
12
+ export const generateYupSchema = (
13
+ normalFieldGroups: any[][],
14
+ fieldArrayGroups?: any[],
15
+ ) => {
16
+ const flatArray = normalFieldGroups.flat()
17
+
18
+ const validations = flatArray
19
+ ?.filter(
20
+ (item) => item?.required !== undefined || item?.validation !== undefined,
21
+ )
22
+ ?.reduce((acc, curr) => {
23
+ return {
24
+ [curr.name]:
25
+ curr?.validation ??
26
+ yup.string().required(`${curr?.label} is required`),
27
+ ...acc,
28
+ }
29
+ }, {})
30
+
31
+ return yup.object().shape(validations)
32
+ }
33
+
34
+ type FormTypes =
35
+ | 'FormTextField'
36
+ | 'Display'
37
+ | 'FormSingleSelect'
38
+ | 'FormRadioGroup'
39
+ | 'FormDatePicker'
40
+
41
+ export interface Field {
42
+ render: FormTypes
43
+ name: string
44
+ label?: string
45
+ required?: boolean
46
+ elementProps?: any
47
+ disabled?: boolean
48
+ valiation?: any
49
+ }
50
+
51
+ export interface RenderFormProps {
52
+ fields: Field[]
53
+ dropdowns?: { [x: string]: { label: string | ReactNode; value: any }[] }
54
+ control: Control<any, any>
55
+ cols: number
56
+ data?: any
57
+ fieldArrayOptions?: { index: number; name: string }
58
+ gap?: string | number
59
+ }
60
+
61
+ const RenderForm = ({
62
+ fields,
63
+ dropdowns,
64
+ control,
65
+ cols,
66
+ data = null,
67
+ fieldArrayOptions,
68
+ gap = '1rem',
69
+ }: RenderFormProps) => {
70
+ return (
71
+ <>
72
+ <Box
73
+ sx={{
74
+ display: 'grid',
75
+ gridTemplateColumns: `repeat(${cols}, 1fr)`,
76
+ gap,
77
+ width: '100%',
78
+ }}
79
+ >
80
+ {fields?.map((item) => {
81
+ return renderFormField({
82
+ field: item,
83
+ control,
84
+ dropdown: dropdowns ? dropdowns[item?.name] : null,
85
+ elementProps: item?.elementProps,
86
+ value: data ? data[item?.name] : null,
87
+ fieldArrayOptions,
88
+ })
89
+ })}
90
+ </Box>
91
+ </>
92
+ )
93
+ }
94
+ export default RenderForm
95
+
96
+ const renderFormField = ({
97
+ field,
98
+ control,
99
+ dropdown,
100
+ elementProps,
101
+ value,
102
+ fieldArrayOptions,
103
+ }) => {
104
+ const name = fieldArrayOptions?.name
105
+ ? `${fieldArrayOptions?.name}.${fieldArrayOptions?.index}.${field?.name}`
106
+ : field?.name
107
+
108
+ switch (field.render) {
109
+ case 'FormTextField':
110
+ return (
111
+ <FormTextField
112
+ control={control}
113
+ name={name}
114
+ label={field?.label}
115
+ required={field?.required}
116
+ disabled={field?.disabled}
117
+ {...elementProps}
118
+ />
119
+ )
120
+
121
+ case 'FormSingleSelect':
122
+ return (
123
+ <FormSingleSelect
124
+ options={dropdown}
125
+ control={control}
126
+ name={name}
127
+ label={field?.label}
128
+ required={field?.required}
129
+ disabled={field?.disabled}
130
+ {...elementProps}
131
+ />
132
+ )
133
+ case 'FormDatePicker':
134
+ return (
135
+ <FormDatePicker
136
+ size="medium"
137
+ control={control}
138
+ name={name}
139
+ label={field?.label}
140
+ required={field?.required}
141
+ disabled={field?.disabled}
142
+ {...elementProps}
143
+ />
144
+ )
145
+ case 'FormRadioGroup':
146
+ return (
147
+ <FormRadioGroup
148
+ row={true}
149
+ options={dropdown}
150
+ control={control}
151
+ name={name}
152
+ label={field?.label}
153
+ required={field?.required}
154
+ disabled={field?.disabled}
155
+ {...elementProps}
156
+ />
157
+ )
158
+ case 'Display':
159
+ return <FormDisplay label={field?.label} value={value} />
160
+
161
+ case 'NestedForm':
162
+ return <NestedForm label={field?.label} nestedFields={[]} />
163
+
164
+ default:
165
+ return <>{field?.label}</>
166
+ }
167
+ }
168
+
169
+ const FormDisplay = ({ label, value }) => {
170
+ return (
171
+ <Box>
172
+ <Typography variant="subtitle2">{label}</Typography>
173
+ <Typography variant="body1" marginTop={'5px'}>
174
+ {value ?? '-'}
175
+ </Typography>
176
+ </Box>
177
+ )
178
+ }
179
+
180
+ const NestedForm = ({
181
+ nestedFields,
182
+ label,
183
+ }: {
184
+ label: ReactNode
185
+ nestedFields: Field[]
186
+ }) => {
187
+ return <></>
188
+ }
@@ -26,7 +26,7 @@ const StyledFormControl = styled(FormControl)(({ theme }) => ({}))
26
26
  export default function FormSingleSelect(props: Props) {
27
27
  const {
28
28
  name = 'select',
29
- options,
29
+ options = [],
30
30
  control,
31
31
  label,
32
32
  textColor,
@@ -24,7 +24,7 @@ interface Props extends SelectProps {
24
24
  export default function SingleSelect(props: Props) {
25
25
  const {
26
26
  name = 'select',
27
- options,
27
+ options = [],
28
28
  control,
29
29
  label,
30
30
  textColor,
@@ -22,17 +22,21 @@ export default function UploadButton({
22
22
  onChange,
23
23
  uploadUrl,
24
24
  onSuccess,
25
+ styledUpload,
26
+ loadingState,
27
+ refetchFn,
25
28
  }: UploadButtonProps) {
26
29
  const [loading, setLoading] = useState<boolean>(false)
27
30
 
28
31
  const formRef: any = useRef()
29
32
  const fileInputRef: any = useRef()
30
-
33
+ const ref: any = useRef()
31
34
  const handleChange = (e: any) => {
32
35
  if (e.target.files[0]) {
33
36
  const formData = new FormData()
34
37
  formData.append('file', e.target.files[0])
35
38
  setLoading(true)
39
+ loadingState && setLoading(true)
36
40
  axios
37
41
  .post(uploadUrl, formData)
38
42
  .then((res) => {
@@ -43,12 +47,15 @@ export default function UploadButton({
43
47
  fileName: res.data?.mediaObject?.originalFileName ?? '',
44
48
  id: res.data?.mediaObject?.id ?? '',
45
49
  })
50
+ refetchFn()
46
51
  setLoading(false)
52
+ loadingState && setLoading(false)
47
53
  if (onSuccess) onSuccess()
48
54
  formRef?.current.reset()
49
55
  })
50
56
  .catch((err) => {
51
57
  setLoading(false)
58
+ loadingState && setLoading(false)
52
59
  toast.error(
53
60
  err?.response?.data?.message ?? 'Server Error While Uploading File',
54
61
  )
@@ -58,6 +65,25 @@ export default function UploadButton({
58
65
  }
59
66
 
60
67
  return (
68
+ <>
69
+ {styledUpload ? (
70
+ <>
71
+ <div
72
+ onClick={() => {
73
+ ref.current.click()
74
+ }}
75
+ >
76
+ {styledUpload}
77
+ </div>
78
+ <input
79
+ accept={getAccept(type)}
80
+ type="file"
81
+ hidden
82
+ ref={ref}
83
+ onChange={handleChange}
84
+ />
85
+ </>
86
+ ) : (
61
87
  <>
62
88
  <form ref={formRef}>
63
89
  <input
@@ -78,6 +104,7 @@ export default function UploadButton({
78
104
  {getLabel(type)}
79
105
  </StyledButton>
80
106
  </form>
107
+ </>)}
81
108
  </>
82
109
  )
83
110
  }
@@ -1,3 +1,5 @@
1
+ import { ReactNode } from "react"
2
+
1
3
  interface Media {
2
4
  type: 'image' | 'video' | 'audio' | 'file'
3
5
  url: string
@@ -10,5 +12,8 @@ interface UploadButtonProps {
10
12
  onChange: (res: Media) => void
11
13
  uploadUrl: string
12
14
  onSuccess?: () => void
15
+ styledUpload?: ReactNode
16
+ loadingState?: (v) => void
17
+ refetchFn?: () => void
13
18
  }
14
19
  export type { Media, UploadButtonProps }
@@ -23,6 +23,8 @@ import PageNotFound from './PageNotFound'
23
23
  import ChangePassword from './ChangePassword'
24
24
  import UploadButton from './UploadButton'
25
25
  import DialogWrapper from './DrawerWrapper/DialogWrapper'
26
+ import Form from './Form/Form'
27
+ import RenderForm from './Form/RenderForm'
26
28
  export { default as Image } from './Image'
27
29
  export { default as PageHeader } from './PageHeader'
28
30
  export { PageContent } from './PageContent'
@@ -74,6 +76,8 @@ export {
74
76
  ChangePassword,
75
77
  UploadButton,
76
78
  DialogWrapper,
79
+ Form,
80
+ RenderForm,
77
81
  }
78
82
 
79
83
  export * from './UploadButton/types'
@@ -4,6 +4,7 @@ import { toast } from 'react-toastify'
4
4
  import Cookies from 'js-cookie'
5
5
  import { NetworkStore } from '../components/ErrorBoundary/GlobalNetworkLoadingIndicator'
6
6
  import { isDevelopment } from '../constants'
7
+ import { campxTenantKey } from '../contexts/Providers'
7
8
 
8
9
  const sessionKey = Cookies.get('campx_session_key')
9
10
  const clientId = window.location.pathname.split('/')[1] ?? 'campx_dev'
@@ -21,7 +22,7 @@ let axios = Axios.create({
21
22
  baseURL: process.env.REACT_APP_API_HOST,
22
23
  withCredentials: true,
23
24
  headers: {
24
- 'x-tenant-id': isDevelopment ? 'campx_dev' : clientId,
25
+ 'x-tenant-id': isDevelopment ? 'campx_dev' : campxTenantKey,
25
26
  ...(isDevelopment &&
26
27
  sessionKey && {
27
28
  campx_session_key: sessionKey,