tee3apps-cms-sdk-react 0.0.4 → 0.0.6

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.
@@ -0,0 +1,163 @@
1
+ import React from 'react';
2
+ import '../App.css';
3
+ // Import your CSS file here
4
+
5
+ interface ImageData {
6
+ isDynamic: boolean;
7
+ shape: string;
8
+ url: string;
9
+ alt: string;
10
+ }
11
+
12
+ interface ImageModeProps {
13
+ borderRadius: number;
14
+ width: string;
15
+ height: string;
16
+ }
17
+
18
+ interface ImageComponentProps {
19
+ images: any;
20
+ mode: {
21
+ web: ImageModeProps;
22
+ mobileweb: ImageModeProps;
23
+ mobileapp: ImageModeProps;
24
+ tablet: ImageModeProps;
25
+ };
26
+ linktype: string;
27
+ link: {
28
+ url: string;
29
+ target: string;
30
+ };
31
+ product: {
32
+ _id: string;
33
+ code: string;
34
+ name: object;
35
+ pd_type: string;
36
+ };
37
+ page: {
38
+ _id: string;
39
+ code: string;
40
+ name: object;
41
+ pg_type: string;
42
+ facet_params: any[];
43
+ track_params: any[];
44
+ };
45
+ tag: {
46
+ _id: string;
47
+ code: string;
48
+ name: object;
49
+ tagtype: string;
50
+ };
51
+ type: any[];
52
+ }
53
+
54
+ interface ImageComponentMainProps {
55
+ props: ImageComponentProps;
56
+ deviceMode?: string;
57
+ boxHeight?: string; // Add this prop to receive box height
58
+ }
59
+
60
+ const ImageComponent: React.FC<ImageComponentMainProps> = ({
61
+ props,
62
+ deviceMode = 'web',
63
+ boxHeight = '280px',
64
+ }) => {
65
+
66
+ const bannerLink = (element: any): string | null => {
67
+ if (!element || !element.linktype) return null;
68
+
69
+ switch (element.linktype) {
70
+ case 'NONE':
71
+ return null;
72
+
73
+ case 'EXTERNAL':
74
+ case 'EXTERNAL_LINK':
75
+ return element.link?.url || null;
76
+
77
+ case 'PRODUCT': {
78
+ const pdType = element.product?.pd_type;
79
+ const code =
80
+ element.product?.pd_id?.code ||
81
+ element.product?.code ||
82
+ element.product?.product_data?.code;
83
+
84
+ if (!code) return null;
85
+
86
+ return pdType === 'VARIANT'
87
+ ? `/variant/${code}`
88
+ : `model/${code}`;
89
+ }
90
+
91
+ case 'TAG': {
92
+ const tagCode = element.tag?.code;
93
+ if (!tagCode) return null;
94
+ // return `/${tagCode}/s?type=tag`;
95
+ return `/tag/${tagCode}`;
96
+ }
97
+
98
+ case 'PAGE': {
99
+ const pageId = element.page?._id || element.page?.code;
100
+ const pgType = element.page?.pg_type;
101
+ if (!pageId || !pgType) return null;
102
+ return `/page/${pageId}?type=${pgType}`;
103
+ }
104
+
105
+ default:
106
+ return element.slug || null;
107
+ }
108
+ };
109
+
110
+ const getCurrentMode = () => {
111
+ switch (deviceMode) {
112
+ case 'mobileweb':
113
+ return props.mode.mobileweb;
114
+ case 'mobileapp':
115
+ return props.mode.mobileapp;
116
+ case 'tablet':
117
+ return props.mode.tablet;
118
+ case 'web':
119
+ default:
120
+ return props.mode.web;
121
+ }
122
+ };
123
+
124
+ const currentMode = getCurrentMode();
125
+ const containerHeight = boxHeight === 'auto' ? 'auto' : boxHeight;
126
+ const link = bannerLink(props);
127
+ const imageUrl = `https://pim.in-maa-1.linodeobjects.com/${props.images?.url || props.images?.all?.url}`;
128
+ const altText = props.images.alt || '';
129
+
130
+ const imageElement = (
131
+ <img
132
+ src={imageUrl}
133
+ alt={altText}
134
+ className="fitted-image"
135
+ style={{ cursor: link ? 'pointer' : 'default' }}
136
+ />
137
+ );
138
+
139
+ return (
140
+ <div
141
+ className="image-box"
142
+ style={{
143
+ borderRadius: `${currentMode.borderRadius}px`,
144
+ height: containerHeight,
145
+ overflow: 'hidden',
146
+ }}
147
+ >
148
+ {link ? (
149
+ <a
150
+ href={link}
151
+ target={props?.linktype=='EXTERNAL' ? props?.link?.target :'_self'}
152
+ style={{ display: 'block', width: '100%', height: '100%' }}
153
+ >
154
+ {imageElement}
155
+ </a>
156
+ ) : (
157
+ imageElement
158
+ )}
159
+ </div>
160
+ );
161
+ };
162
+
163
+ export default ImageComponent;
@@ -0,0 +1,62 @@
1
+ import React from 'react';
2
+ import type { ComponentProps } from '../types';
3
+ import './Styles/InputField.css'; // Import external CSS
4
+
5
+ interface InputFieldProps {
6
+ props: ComponentProps;
7
+ value?: any;
8
+ onChange?: (code: string, value: any) => void;
9
+ }
10
+
11
+ const InputField: React.FC<InputFieldProps> = ({ props, value = '', onChange }) => {
12
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
13
+ const newValue = e.target.value;
14
+ if (onChange) {
15
+ const code = props.code || `inputfield_${props.name?.all || 'field'}`;
16
+ onChange(code, newValue);
17
+ }
18
+ };
19
+
20
+ return (
21
+ <div className="input-field">
22
+ <label className="input-field__label">
23
+ {props.name?.all || 'Input Field'}
24
+ {props.required && <span className="input-field__required">*</span>}
25
+ </label>
26
+
27
+ {props.textArea ? (
28
+ <textarea
29
+ value={value || ''}
30
+ onChange={handleChange}
31
+ minLength={props.minLength}
32
+ maxLength={props.maxLength}
33
+ rows={4}
34
+ required={props.required}
35
+ className="input-field__textarea"
36
+ />
37
+ ) : (
38
+ <input
39
+ type="text"
40
+ value={value || ''}
41
+ onChange={handleChange}
42
+ minLength={props.minLength}
43
+ maxLength={props.maxLength}
44
+ required={props.required}
45
+ className="input-field__input"
46
+ />
47
+ )}
48
+
49
+ {props.helperText && (
50
+ <p className="input-field__helper-text">{props.helperText}</p>
51
+ )}
52
+
53
+ <div className="input-field__status">
54
+ {props.format && `Format: ${props.format}`}
55
+ {props.postaltype && ` • Type: ${props.postaltype}`}
56
+ {props.minLength && props.maxLength && ` • Length: ${props.minLength}-${props.maxLength}`}
57
+ </div>
58
+ </div>
59
+ );
60
+ };
61
+
62
+ export default InputField;
@@ -0,0 +1,56 @@
1
+ import React from 'react';
2
+ import type { ComponentProps } from '../types';
3
+ import './Styles/NumberField.css'; // Import the external CSS
4
+
5
+ interface NumberFieldProps {
6
+ props: ComponentProps;
7
+ value?: any;
8
+ onChange?: (code: string, value: any) => void;
9
+ }
10
+
11
+ const NumberField: React.FC<NumberFieldProps> = ({ props, value = '', onChange }) => {
12
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
13
+ if (onChange) {
14
+ const code = props.code || `numberfield_${props.name?.all || 'field'}`;
15
+ onChange(code, e.target.value);
16
+ }
17
+ };
18
+
19
+ return (
20
+ <div className="number-field">
21
+ <label className="number-field__label">
22
+ {props.name?.all || 'Number Field'}
23
+ {props.required && <span className="number-field__required">*</span>}
24
+ </label>
25
+
26
+ <div className="number-field__input-wrapper">
27
+ <input
28
+ type="number"
29
+ value={value || ''}
30
+ onChange={handleChange}
31
+ min={props.min}
32
+ max={props.max}
33
+ step={props.step}
34
+ required={props.required}
35
+ className="number-field__input"
36
+ />
37
+
38
+ {props.suffix && (
39
+ <span className="number-field__suffix">
40
+ {props.suffix}
41
+ </span>
42
+ )}
43
+ </div>
44
+
45
+ {props.helperText && (
46
+ <p className="number-field__helper-text">{props.helperText}</p>
47
+ )}
48
+
49
+ <div className="number-field__status">
50
+ Range: {props.min}-{props.max} • Step: {props.step}
51
+ </div>
52
+ </div>
53
+ );
54
+ };
55
+
56
+ export default NumberField;
@@ -0,0 +1,67 @@
1
+ .page-form {
2
+ min-height: 100vh;
3
+ background: linear-gradient(to bottom right, #f9fafb, #f3f4f6); /* from-gray-50 to-gray-100 */
4
+ padding-top: 2rem;
5
+ padding-bottom: 2rem;
6
+ }
7
+
8
+ .page-form__container {
9
+ max-width: 112rem; /* max-w-7xl (1120px) */
10
+ margin-left: auto;
11
+ margin-right: auto;
12
+ padding-left: 1rem;
13
+ padding-right: 1rem;
14
+ }
15
+
16
+ @media (min-width: 640px) {
17
+ .page-form__container {
18
+ padding-left: 1.5rem; /* sm:px-6 */
19
+ padding-right: 1.5rem;
20
+ }
21
+ }
22
+
23
+ @media (min-width: 1024px) {
24
+ .page-form__container {
25
+ padding-left: 2rem; /* lg:px-8 */
26
+ padding-right: 2rem;
27
+ }
28
+ }
29
+
30
+ .page-form__footer {
31
+ margin-top: 3rem; /* mt-12 */
32
+ text-align: center;
33
+ }
34
+
35
+ .page-form__status {
36
+ display: inline-flex;
37
+ align-items: center;
38
+ gap: 0.5rem; /* gap-2 */
39
+ padding: 0.5rem 1rem; /* px-4 py-2 */
40
+ background-color: #fff; /* bg-white */
41
+ border-radius: 9999px; /* rounded-full */
42
+ box-shadow: 0 1px 2px rgb(0 0 0 / 0.05); /* shadow-sm */
43
+ border: 1px solid #e5e7eb; /* border-gray-200 */
44
+ }
45
+
46
+ .page-form__status-indicator {
47
+ width: 0.5rem; /* w-2 */
48
+ height: 0.5rem; /* h-2 */
49
+ background-color: #10b981; /* green-500 */
50
+ border-radius: 9999px; /* rounded-full */
51
+ animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
52
+ }
53
+
54
+ .page-form__status-text {
55
+ font-size: 0.875rem; /* text-sm */
56
+ color: #4b5563; /* gray-600 */
57
+ }
58
+
59
+ /* pulse animation */
60
+ @keyframes pulse {
61
+ 0%, 100% {
62
+ opacity: 1;
63
+ }
64
+ 50% {
65
+ opacity: 0.4;
66
+ }
67
+ }
@@ -0,0 +1,195 @@
1
+ import React, { useState, useCallback, useEffect } from 'react';
2
+ import type { Row } from '../types';
3
+ import RowComponent from './RowComponent';
4
+ import './PageForm.css'; // Import the new CSS file
5
+
6
+ interface PageFormProps {
7
+ jsonData: Row[];
8
+ onSubmit?: (data: Record<string, any>) => void;
9
+ }
10
+
11
+ const PageForm: React.FC<PageFormProps> = ({ jsonData, onSubmit }) => {
12
+ // Form state to store all field values
13
+ const [formValues, setFormValues] = useState<Record<string, any>>({});
14
+ const [deviceMode, setDeviceMode] = useState<string>('web');
15
+ const [validationErrors, setValidationErrors] = useState<Record<string, boolean>>({});
16
+
17
+ // Function to determine device mode based on screen width
18
+ const getDeviceMode = (width: number) => {
19
+ if (width <= 480) return 'mobileapp';
20
+ if (width <= 768) return 'mobileweb';
21
+ if (width <= 1024) return 'tablet';
22
+ return 'web';
23
+ };
24
+
25
+ // Function to get field code from component
26
+ const getFieldCode = (component: any) => {
27
+ return component.props.code || `${component.name.toLowerCase()}_${component.props.name?.all || 'field'}`;
28
+ };
29
+
30
+ // Function to get field name from component
31
+ const getFieldName = (component: any) => {
32
+ return component.props.name?.all || component.name;
33
+ };
34
+
35
+ // Function to create a mapping of field codes to field names
36
+ const getFieldCodeToNameMap = useCallback(() => {
37
+ const codeToNameMap: Record<string, string> = {};
38
+
39
+ jsonData.forEach((row) => {
40
+ row.columns?.forEach((box) => {
41
+ box.components?.forEach((component) => {
42
+ // Check if component is a form field
43
+ const fieldTypes = ['DateField', 'NumberField', 'SelectField', 'RadioField', 'InputField', 'BooleanField'];
44
+ if (fieldTypes.includes(component.name)) {
45
+ const code = getFieldCode(component);
46
+ const name = getFieldName(component);
47
+ codeToNameMap[code] = name;
48
+ }
49
+ });
50
+ });
51
+ });
52
+
53
+ return codeToNameMap;
54
+ }, [jsonData]);
55
+
56
+ // Function to collect all required fields from jsonData
57
+ const getRequiredFields = useCallback(() => {
58
+ const requiredFields: Record<string, { code: string; name: string }> = {};
59
+
60
+ jsonData.forEach((row) => {
61
+ row.columns?.forEach((box) => {
62
+ box.components?.forEach((component) => {
63
+ // Check if component is a form field and is required
64
+ const fieldTypes = ['DateField', 'NumberField', 'SelectField', 'RadioField', 'InputField', 'BooleanField'];
65
+ if (fieldTypes.includes(component.name) && component.props.required) {
66
+ const code = getFieldCode(component);
67
+ const name = getFieldName(component);
68
+ requiredFields[code] = { code, name };
69
+ }
70
+ });
71
+ });
72
+ });
73
+
74
+ return requiredFields;
75
+ }, [jsonData]);
76
+
77
+ // Function to transform form values from code-based to name-based
78
+ const transformFormValuesToNames = useCallback(() => {
79
+ const codeToNameMap = getFieldCodeToNameMap();
80
+ const transformedData: Record<string, any> = {};
81
+
82
+ Object.keys(formValues).forEach((code) => {
83
+ const fieldName = codeToNameMap[code];
84
+ if (fieldName) {
85
+ transformedData[fieldName] = formValues[code];
86
+ } else {
87
+ // If no mapping found, keep the code as key
88
+ transformedData[code] = formValues[code];
89
+ }
90
+ });
91
+
92
+ return transformedData;
93
+ }, [formValues, getFieldCodeToNameMap]);
94
+
95
+ // Effect to handle window resize
96
+ useEffect(() => {
97
+ const handleResize = () => {
98
+ const width = window.innerWidth;
99
+ setDeviceMode(getDeviceMode(width));
100
+ };
101
+
102
+ // Call once on mount
103
+ handleResize();
104
+
105
+ window.addEventListener('resize', handleResize);
106
+ return () => {
107
+ window.removeEventListener('resize', handleResize);
108
+ };
109
+ }, []);
110
+
111
+ // Handler to update form values
112
+ const handleFieldChange = useCallback((code: string, value: any) => {
113
+ setFormValues(prev => ({
114
+ ...prev,
115
+ [code]: value
116
+ }));
117
+ // Clear validation error for this field when user types
118
+ if (validationErrors[code]) {
119
+ setValidationErrors(prev => {
120
+ const newErrors = { ...prev };
121
+ delete newErrors[code];
122
+ return newErrors;
123
+ });
124
+ }
125
+ }, [validationErrors]);
126
+
127
+ // Function to validate required fields
128
+ const validateForm = useCallback(() => {
129
+ const requiredFields = getRequiredFields();
130
+ const errors: Record<string, boolean> = {};
131
+
132
+ Object.keys(requiredFields).forEach((code) => {
133
+ const value = formValues[code];
134
+ // Check if value is empty, null, undefined, empty array, or empty string
135
+ if (
136
+ value === undefined ||
137
+ value === null ||
138
+ value === '' ||
139
+ (Array.isArray(value) && value.length === 0)
140
+ ) {
141
+ errors[code] = true;
142
+ }
143
+ });
144
+
145
+ setValidationErrors(errors);
146
+ return { isValid: Object.keys(errors).length === 0, errors };
147
+ }, [formValues, getRequiredFields]);
148
+
149
+ // Single method to handle form submission - validates, transforms, and sends data to parent
150
+ const handleFormSubmit = useCallback(() => {
151
+ // Validate form and get validation result
152
+ const { isValid, errors } = validateForm();
153
+
154
+ if (isValid) {
155
+ // Transform form values to use field names instead of codes
156
+ const transformedData = transformFormValuesToNames();
157
+
158
+ // Send transformed data back to parent component
159
+ if (onSubmit) {
160
+ onSubmit(transformedData);
161
+ }
162
+ } else {
163
+ // If validation fails, show error message with field names
164
+ const requiredFields = getRequiredFields();
165
+ const errorFields = Object.keys(errors)
166
+ .map(code => requiredFields[code]?.name || code)
167
+ .join(', ');
168
+ alert(`Please fill in all required fields: ${errorFields}`);
169
+ }
170
+ }, [formValues, validateForm, getRequiredFields, transformFormValuesToNames, onSubmit]);
171
+
172
+ return (
173
+ <div className="page-form">
174
+ <div className="page-form__container">
175
+ {/* Render each row */}
176
+ {jsonData.map((row, rowIndex) => (
177
+ <RowComponent
178
+ key={rowIndex}
179
+ row={row}
180
+ deviceMode={deviceMode}
181
+ formValues={formValues}
182
+ validationErrors={validationErrors}
183
+ onFieldChange={handleFieldChange}
184
+ onFormSubmit={handleFormSubmit}
185
+ />
186
+ ))}
187
+
188
+ {/* Footer */}
189
+
190
+ </div>
191
+ </div>
192
+ );
193
+ };
194
+
195
+ export default PageForm;
@@ -0,0 +1,59 @@
1
+ import React from 'react';
2
+ import type { ComponentProps } from '../types';
3
+ import './Styles/RadioField.css'; // Import external stylesheet
4
+
5
+ interface RadioFieldProps {
6
+ props: ComponentProps;
7
+ value?: any;
8
+ onChange?: (code: string, value: any) => void;
9
+ }
10
+
11
+ const RadioField: React.FC<RadioFieldProps> = ({ props, value = '', onChange }) => {
12
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
13
+ if (onChange) {
14
+ const code = props.code || `radiofield_${props.name?.all || 'field'}`;
15
+ onChange(code, e.target.value);
16
+ }
17
+ };
18
+
19
+ return (
20
+ <div className="radio-field">
21
+ <label className="radio-field__label">
22
+ {props.name?.all || 'Radio Field'}
23
+ {props.required && <span className="radio-field__required">*</span>}
24
+ </label>
25
+
26
+ <div className="radio-field__options">
27
+ {props.lov?.length === 0 ? (
28
+ <div className="radio-field__empty">No options available</div>
29
+ ) : (
30
+ props.lov?.map((item) => (
31
+ <label
32
+ key={item.id}
33
+ className="radio-field__option"
34
+ >
35
+ <input
36
+ type="radio"
37
+ name={props.code}
38
+ value={item.code}
39
+ checked={value === item.code}
40
+ onChange={handleChange}
41
+ className="radio-field__input"
42
+ required={props.required}
43
+ />
44
+ <span className="radio-field__option-label">{item.name}</span>
45
+ </label>
46
+ ))
47
+ )}
48
+ </div>
49
+
50
+ {props.helperText && (
51
+ <p className="radio-field__helper-text">{props.helperText}</p>
52
+ )}
53
+
54
+
55
+ </div>
56
+ );
57
+ };
58
+
59
+ export default RadioField;
@@ -0,0 +1,82 @@
1
+ import React from 'react';
2
+ import BoxRenderer from './BoxRenderer';
3
+ import type { Row } from '../types';
4
+
5
+ interface RowComponentProps {
6
+ row: Row;
7
+ deviceMode?: string;
8
+ formValues?: Record<string, any>;
9
+ validationErrors?: Record<string, boolean>;
10
+ onFieldChange?: (code: string, value: any) => void;
11
+ onFormSubmit?: () => void;
12
+ }
13
+
14
+ const RowComponent: React.FC<RowComponentProps> = ({
15
+ row,
16
+ deviceMode = 'web',
17
+ formValues = {},
18
+ validationErrors = {},
19
+ onFieldChange,
20
+ onFormSubmit
21
+ }) => {
22
+ const getCurrentMode = () => {
23
+ switch(deviceMode) {
24
+ case 'mobileweb':
25
+ return row.props.mode.mobileweb || {};
26
+ case 'mobileapp':
27
+ return row.props.mode.mobileapp || {};
28
+ case 'tablet':
29
+ return row.props.mode.tablet || {};
30
+ case 'web':
31
+ default:
32
+ return row.props.mode.web;
33
+ }
34
+ };
35
+
36
+ const currentMode = getCurrentMode();
37
+
38
+ return (
39
+ <div
40
+ style={{
41
+ minHeight: row.props.minHeight,
42
+ backgroundColor: row.props.bgColor,
43
+ backgroundImage: row.props.bgImage ? `url(${row.props.bgImage})` : 'none',
44
+ backgroundSize: row.props.bgsize,
45
+ backgroundPosition: 'center',
46
+ width: '100%',
47
+ marginBottom: '2px',
48
+ border: '1px solid #e0e0e0',
49
+ borderRadius: '4px',
50
+ boxSizing: 'border-box',
51
+ overflow: 'hidden',
52
+ position: 'relative',
53
+ display: currentMode.isVisible !== false ? 'block' : 'none',
54
+ }}
55
+ >
56
+ {/* Row Content */}
57
+ <div style={{
58
+ width: '100%',
59
+ display: 'block',
60
+ position: 'relative',
61
+ padding: '5px',
62
+ boxSizing: 'border-box'
63
+ }}>
64
+ {row.columns.map((box, boxIndex) => (
65
+ <BoxRenderer
66
+ key={boxIndex}
67
+ box={box}
68
+ deviceMode={deviceMode}
69
+ formValues={formValues}
70
+ validationErrors={validationErrors}
71
+ onFieldChange={onFieldChange}
72
+ onFormSubmit={onFormSubmit}
73
+ />
74
+ ))}
75
+ {/* Clear float after all children */}
76
+ <div style={{ clear: 'both' }} />
77
+ </div>
78
+ </div>
79
+ );
80
+ };
81
+
82
+ export default RowComponent;