@plusscommunities/pluss-feature-builder-web-d 1.0.7 → 1.0.9-beta.3
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/dist/{index.cjs.js → index.js} +3803 -3504
- package/dist/index.js.map +1 -0
- package/package.json +20 -27
- package/.babelrc +0 -4
- package/rollup.config.js +0 -69
- package/src/actions/featureBuilderStringsActions.js +0 -88
- package/src/actions/featureDefinitionsIndex.js +0 -258
- package/src/actions/formActions.js +0 -301
- package/src/actions/index.js +0 -12
- package/src/actions/listingActions.js +0 -352
- package/src/actions/wizardActions.js +0 -228
- package/src/components/ActivityCardExample.jsx +0 -86
- package/src/components/ActivityCardExample.module.css +0 -130
- package/src/components/BackgroundLoader.jsx +0 -33
- package/src/components/BackgroundLoader.module.css +0 -46
- package/src/components/BaseFieldConfig.jsx +0 -305
- package/src/components/BaseFieldConfig.module.css +0 -42
- package/src/components/CenteredContainer.jsx +0 -29
- package/src/components/CenteredContainer.module.css +0 -171
- package/src/components/DeleteConfirmationPopup.jsx +0 -95
- package/src/components/DeleteConfirmationPopup.module.css +0 -12
- package/src/components/ErrorBoundary.jsx +0 -134
- package/src/components/ErrorBoundary.module.css +0 -77
- package/src/components/ErrorMessage.jsx +0 -85
- package/src/components/ErrorMessage.module.css +0 -116
- package/src/components/ExampleDisplay.jsx +0 -26
- package/src/components/ExampleDisplay.module.css +0 -3
- package/src/components/FeatureBuilderSidebar.jsx +0 -84
- package/src/components/FeatureBuilderSuccessPopup.jsx +0 -49
- package/src/components/FeatureBuilderSuccessPopup.module.css +0 -41
- package/src/components/FeatureBuilderWelcomePopup.jsx +0 -51
- package/src/components/FeatureBuilderWelcomePopup.module.css +0 -21
- package/src/components/FeatureListingCard.jsx +0 -104
- package/src/components/FeatureListingCard.module.css +0 -62
- package/src/components/Fields.jsx +0 -423
- package/src/components/Fields.module.css +0 -159
- package/src/components/IconLoader.jsx +0 -153
- package/src/components/IconLoader.module.css +0 -92
- package/src/components/IconSelector.jsx +0 -111
- package/src/components/IconSelector.module.css +0 -197
- package/src/components/ListingEditor.jsx +0 -405
- package/src/components/ListingEditor.module.css +0 -14
- package/src/components/ListingSuccessPopup.jsx +0 -52
- package/src/components/LoadingScreen.jsx +0 -54
- package/src/components/LoadingScreen.module.css +0 -103
- package/src/components/LoadingState.jsx +0 -40
- package/src/components/LoadingState.module.css +0 -18
- package/src/components/PreviewFull.js +0 -24
- package/src/components/PreviewFull.module.css +0 -11
- package/src/components/PreviewGrid.js +0 -14
- package/src/components/PreviewWidget.js +0 -27
- package/src/components/PreviewWidget.module.css +0 -15
- package/src/components/SidebarLayout.jsx +0 -252
- package/src/components/SidebarLayout.module.css +0 -71
- package/src/components/SkeletonLoader.jsx +0 -128
- package/src/components/SkeletonLoader.module.css +0 -295
- package/src/components/SortButtonGroup.jsx +0 -34
- package/src/components/SortButtonGroup.module.css +0 -51
- package/src/components/ToastContainer.jsx +0 -98
- package/src/components/ToastContainer.module.css +0 -156
- package/src/components/ToggleSwitch.js +0 -40
- package/src/components/ToggleSwitch.module.css +0 -48
- package/src/components/TwoColumnInput.jsx +0 -29
- package/src/components/TwoColumnInput.module.css +0 -32
- package/src/components/ViewFull.js +0 -139
- package/src/components/ViewFull.module.css +0 -71
- package/src/components/ViewWidget.js +0 -62
- package/src/components/ViewWidget.module.css +0 -28
- package/src/components/iconCategories.js +0 -135
- package/src/components/iconImports.js +0 -409
- package/src/components/index.js +0 -59
- package/src/components/listing/FileListItem.jsx +0 -86
- package/src/components/listing/GalleryDisplay.jsx +0 -330
- package/src/components/listing/GalleryDisplay.module.css +0 -309
- package/src/components/listing/ListingCTAInput.jsx +0 -82
- package/src/components/listing/ListingDescriptionInput.jsx +0 -73
- package/src/components/listing/ListingField.jsx +0 -101
- package/src/components/listing/ListingField.module.css +0 -106
- package/src/components/listing/ListingFileInput.jsx +0 -273
- package/src/components/listing/ListingFileInput.module.css +0 -189
- package/src/components/listing/ListingForm.jsx +0 -90
- package/src/components/listing/ListingForm.module.css +0 -38
- package/src/components/listing/ListingGalleryInput.jsx +0 -239
- package/src/components/listing/ListingGalleryInput.module.css +0 -132
- package/src/components/listing/ListingImageInput.jsx +0 -153
- package/src/components/listing/ListingTextInput.jsx +0 -72
- package/src/feature.config.js +0 -130
- package/src/helper/index.js +0 -135
- package/src/hooks/useFeatureDefinitionLoader.js +0 -66
- package/src/images/full.png +0 -0
- package/src/images/fullNoTitle.png +0 -0
- package/src/images/previewWidget.png +0 -0
- package/src/images/widget.png +0 -0
- package/src/index.js +0 -38
- package/src/pages/CreateListingPage.jsx +0 -49
- package/src/pages/EditListingPage.jsx +0 -58
- package/src/reducers/featureBuilderReducer.js +0 -739
- package/src/screens/CreateListing.module.css +0 -45
- package/src/screens/Form.module.css +0 -744
- package/src/screens/FormFieldsStep.jsx +0 -626
- package/src/screens/FormLayoutStep.jsx +0 -405
- package/src/screens/FormOverviewStep.jsx +0 -389
- package/src/screens/ListingScreen.jsx +0 -477
- package/src/screens/ListingScreen.module.css +0 -333
- package/src/selectors/featureBuilderSelectors.js +0 -533
- package/src/types/index.js +0 -91
- package/src/utils/textUtils.js +0 -89
- package/src/validators/galleryValidators.js +0 -345
- package/src/values.config.a.js +0 -49
- package/src/values.config.b.js +0 -49
- package/src/values.config.c.js +0 -49
- package/src/values.config.d.js +0 -49
- package/src/values.config.default.js +0 -49
- package/src/values.config.js +0 -49
- package/src/webapi/featureDefinitionActions.js +0 -0
- package/src/webapi/featuresActions.js +0 -90
- package/src/webapi/helper.js +0 -4
- package/src/webapi/index.js +0 -12
- package/src/webapi/listingActions.js +0 -176
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
3
|
-
import { GenericInput, Text } from "../index.js";
|
|
4
|
-
import { iconImports } from "../iconImports.js";
|
|
5
|
-
import styles from "./ListingField.module.css";
|
|
6
|
-
|
|
7
|
-
const ListingDescriptionInput = ({
|
|
8
|
-
field,
|
|
9
|
-
value,
|
|
10
|
-
onChange,
|
|
11
|
-
showError,
|
|
12
|
-
errorMessage,
|
|
13
|
-
}) => {
|
|
14
|
-
const { label, placeholder, isRequired, helpText } = field.values;
|
|
15
|
-
|
|
16
|
-
const handleChange = (e) => {
|
|
17
|
-
onChange(field.id, e.target.value);
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const isValid = () => {
|
|
21
|
-
if (isRequired) {
|
|
22
|
-
const isEmpty =
|
|
23
|
-
!value || (typeof value === "string" && value.trim() === "");
|
|
24
|
-
return !isEmpty;
|
|
25
|
-
}
|
|
26
|
-
return true;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const displayLabel = (
|
|
30
|
-
<>
|
|
31
|
-
{label}
|
|
32
|
-
{isRequired && <span className={styles.requiredAsterisk}>*</span>}
|
|
33
|
-
</>
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
return (
|
|
37
|
-
<>
|
|
38
|
-
{/* Field header with icon and description */}
|
|
39
|
-
<div className={styles.listingField__header}>
|
|
40
|
-
<div className={styles.listingField__icon}>
|
|
41
|
-
<FontAwesomeIcon icon={iconImports.alignLeft} />
|
|
42
|
-
</div>
|
|
43
|
-
<div className={styles.listingField__content}>
|
|
44
|
-
<h3 className={styles.listingField__title}>Description</h3>
|
|
45
|
-
<p className={styles.listingField__description}>
|
|
46
|
-
Longer text area for detailed information or stories
|
|
47
|
-
</p>
|
|
48
|
-
</div>
|
|
49
|
-
</div>
|
|
50
|
-
|
|
51
|
-
<GenericInput
|
|
52
|
-
type="textarea"
|
|
53
|
-
label={displayLabel}
|
|
54
|
-
placeholder={placeholder || ""}
|
|
55
|
-
value={value || ""}
|
|
56
|
-
onChange={handleChange}
|
|
57
|
-
isRequired={isRequired}
|
|
58
|
-
showError={!!showError}
|
|
59
|
-
errorMessage={errorMessage}
|
|
60
|
-
isValid={isValid()}
|
|
61
|
-
alwaysShowLabel
|
|
62
|
-
style={{ resize: "vertical" }}
|
|
63
|
-
/>
|
|
64
|
-
{helpText && (
|
|
65
|
-
<div style={{ marginTop: "8px", color: "#6c7a90", fontSize: "14px" }}>
|
|
66
|
-
{helpText}
|
|
67
|
-
</div>
|
|
68
|
-
)}
|
|
69
|
-
</>
|
|
70
|
-
);
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
export default ListingDescriptionInput;
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import ListingTextInput from "./ListingTextInput.jsx";
|
|
3
|
-
import ListingDescriptionInput from "./ListingDescriptionInput.jsx";
|
|
4
|
-
import ListingImageInput from "./ListingImageInput.jsx";
|
|
5
|
-
import ListingGalleryInput from "./ListingGalleryInput.jsx";
|
|
6
|
-
import ListingCTAInput from "./ListingCTAInput.jsx";
|
|
7
|
-
import ListingFileInput from "./ListingFileInput.jsx";
|
|
8
|
-
import styles from "./ListingField.module.css";
|
|
9
|
-
|
|
10
|
-
const ListingField = ({
|
|
11
|
-
field,
|
|
12
|
-
value,
|
|
13
|
-
onChange,
|
|
14
|
-
showError,
|
|
15
|
-
errorMessage,
|
|
16
|
-
isActive,
|
|
17
|
-
}) => {
|
|
18
|
-
const fieldClassName =
|
|
19
|
-
showError || isActive
|
|
20
|
-
? `${styles.listingField} ${styles.hasError}`
|
|
21
|
-
: styles.listingField;
|
|
22
|
-
|
|
23
|
-
const renderField = () => {
|
|
24
|
-
switch (field.type) {
|
|
25
|
-
case "title":
|
|
26
|
-
return (
|
|
27
|
-
<ListingTextInput
|
|
28
|
-
field={field}
|
|
29
|
-
value={value}
|
|
30
|
-
onChange={onChange}
|
|
31
|
-
showError={showError}
|
|
32
|
-
errorMessage={errorMessage}
|
|
33
|
-
/>
|
|
34
|
-
);
|
|
35
|
-
case "description":
|
|
36
|
-
return (
|
|
37
|
-
<ListingDescriptionInput
|
|
38
|
-
field={field}
|
|
39
|
-
value={value}
|
|
40
|
-
onChange={onChange}
|
|
41
|
-
showError={showError}
|
|
42
|
-
errorMessage={errorMessage}
|
|
43
|
-
/>
|
|
44
|
-
);
|
|
45
|
-
case "image":
|
|
46
|
-
case "feature-image":
|
|
47
|
-
return (
|
|
48
|
-
<ListingImageInput
|
|
49
|
-
field={field}
|
|
50
|
-
value={value}
|
|
51
|
-
onChange={onChange}
|
|
52
|
-
showError={showError}
|
|
53
|
-
errorMessage={errorMessage}
|
|
54
|
-
/>
|
|
55
|
-
);
|
|
56
|
-
case "gallery":
|
|
57
|
-
return (
|
|
58
|
-
<ListingGalleryInput
|
|
59
|
-
field={field}
|
|
60
|
-
value={value}
|
|
61
|
-
onChange={onChange}
|
|
62
|
-
showError={showError}
|
|
63
|
-
errorMessage={errorMessage}
|
|
64
|
-
/>
|
|
65
|
-
);
|
|
66
|
-
case "cta":
|
|
67
|
-
return (
|
|
68
|
-
<ListingCTAInput
|
|
69
|
-
field={field}
|
|
70
|
-
value={value}
|
|
71
|
-
onChange={onChange}
|
|
72
|
-
showError={showError}
|
|
73
|
-
errorMessage={errorMessage}
|
|
74
|
-
/>
|
|
75
|
-
);
|
|
76
|
-
case "file":
|
|
77
|
-
return (
|
|
78
|
-
<ListingFileInput
|
|
79
|
-
field={field}
|
|
80
|
-
value={value}
|
|
81
|
-
onChange={onChange}
|
|
82
|
-
showError={showError}
|
|
83
|
-
errorMessage={errorMessage}
|
|
84
|
-
/>
|
|
85
|
-
);
|
|
86
|
-
default:
|
|
87
|
-
return <div>Field type {field.type} not implemented</div>;
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
return (
|
|
92
|
-
<div className={fieldClassName} data-field-id={field.id}>
|
|
93
|
-
{renderField()}
|
|
94
|
-
{showError && errorMessage && (
|
|
95
|
-
<div className={styles.listingField__error}>{errorMessage}</div>
|
|
96
|
-
)}
|
|
97
|
-
</div>
|
|
98
|
-
);
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
export default ListingField;
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/* Simple listing field styling - clean vertical layout */
|
|
2
|
-
|
|
3
|
-
.listingField {
|
|
4
|
-
padding: 0.5rem 0.8rem 0.6rem;
|
|
5
|
-
border-radius: var(--border-radius-xl);
|
|
6
|
-
background: var(--bg-white);
|
|
7
|
-
transition: box-shadow var(--transition-base) ease;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/* Field header with icon and description */
|
|
11
|
-
.listingField__header {
|
|
12
|
-
display: flex;
|
|
13
|
-
display: none;
|
|
14
|
-
align-items: flex-start;
|
|
15
|
-
gap: 0.3rem;
|
|
16
|
-
|
|
17
|
-
padding: 0 0.6rem 0.3rem;
|
|
18
|
-
border-bottom: 1px solid var(--border-line-grey);
|
|
19
|
-
margin: 0 calc(-1 * 0.7rem);
|
|
20
|
-
margin-bottom: 0.5rem;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
.listingField__icon {
|
|
24
|
-
color: var(--text-bluegrey);
|
|
25
|
-
font-size: var(--font-size-base);
|
|
26
|
-
flex-shrink: 0;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
.listingField__content {
|
|
30
|
-
flex: 1;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
.listingField__title {
|
|
34
|
-
margin: 0 0 0.1rem 0;
|
|
35
|
-
font-weight: var(--font-weight-semibold);
|
|
36
|
-
font-size: var(--font-size-base);
|
|
37
|
-
color: var(--text-dark);
|
|
38
|
-
line-height: var(--line-height-snug);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
.listingField__description {
|
|
42
|
-
margin: 0;
|
|
43
|
-
font-size: var(--font-size-xs);
|
|
44
|
-
color: var(--text-bluegrey);
|
|
45
|
-
line-height: var(--line-height-relaxed);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
.listingField:last-child {
|
|
49
|
-
margin-bottom: 0;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/* Error state styling */
|
|
53
|
-
.listingField--error {
|
|
54
|
-
border-color: var(--colour-red);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/* Required field asterisk */
|
|
58
|
-
.requiredAsterisk {
|
|
59
|
-
color: var(--colour-red);
|
|
60
|
-
margin-left: 0.1rem;
|
|
61
|
-
font-weight: var(--font-weight-bold);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/* Error message styling */
|
|
65
|
-
.listingField__error {
|
|
66
|
-
margin-top: 0.1rem;
|
|
67
|
-
color: var(--colour-red);
|
|
68
|
-
font-size: var(--font-size-xs);
|
|
69
|
-
font-weight: var(--font-weight-normal);
|
|
70
|
-
line-height: var(--line-height-relaxed);
|
|
71
|
-
display: flex;
|
|
72
|
-
align-items: center;
|
|
73
|
-
gap: 0.1rem;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
.listingField__error::before {
|
|
77
|
-
content: "⚠";
|
|
78
|
-
font-size: var(--font-size-sm);
|
|
79
|
-
flex-shrink: 0;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/* Make error more visible */
|
|
83
|
-
.listingField.hasError {
|
|
84
|
-
position: relative;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
.listingField.hasError input,
|
|
88
|
-
.listingField.hasError textarea,
|
|
89
|
-
.listingField.hasError select,
|
|
90
|
-
.listingField.hasError .genericInput,
|
|
91
|
-
.listingField.hasError .genericInput input,
|
|
92
|
-
.listingField.hasError .genericInput textarea,
|
|
93
|
-
.listingField.hasError .imageInputOuter-single,
|
|
94
|
-
.listingField.hasError .imageInputOuter-grid,
|
|
95
|
-
.listingField.hasError .FileInput,
|
|
96
|
-
.listingField.hasError .imageInput,
|
|
97
|
-
.listingField.hasError input[type="file"],
|
|
98
|
-
.listingField.hasError .file-input,
|
|
99
|
-
.listingField.hasError div[class*="imageInput"],
|
|
100
|
-
.listingField.hasError div[class*="file-input"],
|
|
101
|
-
.listingField.hasError .image-container,
|
|
102
|
-
.listingField.hasError .image-grid,
|
|
103
|
-
.listingField.hasError img {
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/* Hover effect for better interactivity */
|
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
import React, { useRef, useState, useEffect } from "react";
|
|
2
|
-
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
3
|
-
import { FileInput, Text, Button } from "../../components";
|
|
4
|
-
import { iconImports } from "../../components/iconImports.js";
|
|
5
|
-
import FileListItem from "./FileListItem.jsx";
|
|
6
|
-
import listingFieldStyles from "./ListingField.module.css";
|
|
7
|
-
import styles from "./ListingFileInput.module.css";
|
|
8
|
-
|
|
9
|
-
const ListingFileInput = ({
|
|
10
|
-
field,
|
|
11
|
-
value,
|
|
12
|
-
onChange,
|
|
13
|
-
showError,
|
|
14
|
-
errorMessage,
|
|
15
|
-
disabled = false,
|
|
16
|
-
}) => {
|
|
17
|
-
const [inputs, setInputs] = useState([{ id: 0, fileUrl: null }]);
|
|
18
|
-
const nextIdRef = useRef(1);
|
|
19
|
-
|
|
20
|
-
// Initialize inputs from value prop - map each existing file to an input
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
if (value) {
|
|
23
|
-
let fileData = [];
|
|
24
|
-
|
|
25
|
-
// Handle multiple files - could be array of URLs or array of file objects
|
|
26
|
-
if (Array.isArray(value)) {
|
|
27
|
-
fileData = value
|
|
28
|
-
.map((file) => {
|
|
29
|
-
if (typeof file === "string") {
|
|
30
|
-
const fileName = file.split("/").pop() || "Unknown File";
|
|
31
|
-
return {
|
|
32
|
-
url: file,
|
|
33
|
-
name: fileName.replace(/\.[^/.]+$/, ""),
|
|
34
|
-
originalName: fileName,
|
|
35
|
-
};
|
|
36
|
-
} else if (file && typeof file === "object" && file.url) {
|
|
37
|
-
return {
|
|
38
|
-
url: file.url,
|
|
39
|
-
name: file.name || file.originalName || "Unknown File",
|
|
40
|
-
originalName:
|
|
41
|
-
file.originalName ||
|
|
42
|
-
file.url.split("/").pop() ||
|
|
43
|
-
"Unknown File",
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
return null;
|
|
47
|
-
})
|
|
48
|
-
.filter(Boolean);
|
|
49
|
-
} else {
|
|
50
|
-
// Handle single file values
|
|
51
|
-
let singleFile = null;
|
|
52
|
-
if (typeof value === "string") {
|
|
53
|
-
const fileName = value.split("/").pop() || "Unknown File";
|
|
54
|
-
singleFile = {
|
|
55
|
-
url: value,
|
|
56
|
-
name: fileName.replace(/\.[^/.]+$/, ""),
|
|
57
|
-
originalName: fileName,
|
|
58
|
-
};
|
|
59
|
-
} else if (value && typeof value === "object" && value.url) {
|
|
60
|
-
singleFile = {
|
|
61
|
-
url: value.url,
|
|
62
|
-
name:
|
|
63
|
-
value.name !== undefined
|
|
64
|
-
? value.name
|
|
65
|
-
: value.originalName || "Unknown File",
|
|
66
|
-
originalName:
|
|
67
|
-
value.originalName ||
|
|
68
|
-
value.url.split("/").pop() ||
|
|
69
|
-
"Unknown File",
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
fileData = singleFile ? [singleFile] : [];
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Create inputs for each existing file
|
|
76
|
-
const existingInputs = fileData.map((file, index) => ({
|
|
77
|
-
id: index,
|
|
78
|
-
fileUrl: file.url,
|
|
79
|
-
name: file.name,
|
|
80
|
-
originalName: file.originalName,
|
|
81
|
-
}));
|
|
82
|
-
// Add one empty input at the end
|
|
83
|
-
nextIdRef.current = fileData.length;
|
|
84
|
-
setInputs([...existingInputs, { id: nextIdRef.current, fileUrl: null }]);
|
|
85
|
-
nextIdRef.current++;
|
|
86
|
-
} else {
|
|
87
|
-
setInputs([{ id: 0, fileUrl: null }]);
|
|
88
|
-
}
|
|
89
|
-
}, [value]);
|
|
90
|
-
|
|
91
|
-
// Handle new file upload from FileInput component
|
|
92
|
-
const handleFileUpload = (inputId, uploadedUrl) => {
|
|
93
|
-
if (!uploadedUrl) {
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const fileName = uploadedUrl.split("/").pop() || "Unknown File";
|
|
98
|
-
|
|
99
|
-
// Find the input that triggered this upload and update it
|
|
100
|
-
setInputs((prevInputs) => {
|
|
101
|
-
const updatedInputs = prevInputs.map((input) => {
|
|
102
|
-
if (input.id === inputId) {
|
|
103
|
-
return {
|
|
104
|
-
...input,
|
|
105
|
-
fileUrl: uploadedUrl,
|
|
106
|
-
name: "",
|
|
107
|
-
originalName: fileName,
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
return input;
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
// Add a new empty input for the next upload
|
|
114
|
-
updatedInputs.push({ id: nextIdRef.current, fileUrl: null });
|
|
115
|
-
nextIdRef.current++;
|
|
116
|
-
|
|
117
|
-
return updatedInputs;
|
|
118
|
-
});
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
// Handle file name change
|
|
122
|
-
const handleNameChange = (url, newName) => {
|
|
123
|
-
setInputs((prevInputs) =>
|
|
124
|
-
prevInputs.map((input) =>
|
|
125
|
-
input.fileUrl === url ? { ...input, name: newName } : input,
|
|
126
|
-
),
|
|
127
|
-
);
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
// Handle file removal
|
|
131
|
-
const handleFileRemove = (url) => {
|
|
132
|
-
setInputs((prevInputs) => {
|
|
133
|
-
return prevInputs.map((input) => {
|
|
134
|
-
if (input.fileUrl === url) {
|
|
135
|
-
return { ...input, fileUrl: null };
|
|
136
|
-
}
|
|
137
|
-
return input;
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
// Get all uploaded files from inputs
|
|
143
|
-
const getUploadedFiles = () => {
|
|
144
|
-
return inputs
|
|
145
|
-
.filter((input) => input.fileUrl !== null)
|
|
146
|
-
.map((input) => ({
|
|
147
|
-
url: input.fileUrl,
|
|
148
|
-
name: input.name,
|
|
149
|
-
originalName: input.originalName,
|
|
150
|
-
}));
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
// Track previous uploaded files to prevent infinite loops
|
|
154
|
-
const prevFilesRef = useRef([]);
|
|
155
|
-
|
|
156
|
-
// Sync with parent when uploaded files actually change
|
|
157
|
-
useEffect(() => {
|
|
158
|
-
const files = getUploadedFiles();
|
|
159
|
-
const currentUrls = files.map((f) => f.url).join(",");
|
|
160
|
-
const prevUrls = prevFilesRef.current.map((f) => f.url).join(",");
|
|
161
|
-
|
|
162
|
-
// Only call onChange if the file list actually changed
|
|
163
|
-
if (currentUrls !== prevUrls && onChange) {
|
|
164
|
-
prevFilesRef.current = files;
|
|
165
|
-
onChange(field.id, files);
|
|
166
|
-
}
|
|
167
|
-
}, [inputs]);
|
|
168
|
-
|
|
169
|
-
// Define acceptable file types for documents and common formats
|
|
170
|
-
const acceptTypes = {
|
|
171
|
-
"application/pdf": [".pdf"],
|
|
172
|
-
"application/msword": [".doc"],
|
|
173
|
-
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": [
|
|
174
|
-
".docx",
|
|
175
|
-
],
|
|
176
|
-
"application/vnd.ms-excel": [".xls"],
|
|
177
|
-
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": [
|
|
178
|
-
".xlsx",
|
|
179
|
-
],
|
|
180
|
-
"text/plain": [".txt"],
|
|
181
|
-
"text/csv": [".csv"],
|
|
182
|
-
"image/jpeg": [".jpg", ".jpeg"],
|
|
183
|
-
"image/png": [".png"],
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
// Get the last empty input (the one to show)
|
|
187
|
-
const emptyInput = inputs.find((input) => input.fileUrl === null);
|
|
188
|
-
|
|
189
|
-
// Get uploaded files for display
|
|
190
|
-
const uploadedFiles = getUploadedFiles();
|
|
191
|
-
|
|
192
|
-
return (
|
|
193
|
-
<div className={styles.listingFileInput}>
|
|
194
|
-
{/* Field header with icon and description */}
|
|
195
|
-
<div className={listingFieldStyles.listingField__header}>
|
|
196
|
-
<div className={listingFieldStyles.listingField__icon}>
|
|
197
|
-
<FontAwesomeIcon icon={iconImports.file} />
|
|
198
|
-
</div>
|
|
199
|
-
<div className={listingFieldStyles.listingField__content}>
|
|
200
|
-
<h3 className={listingFieldStyles.listingField__title}>File</h3>
|
|
201
|
-
<p className={listingFieldStyles.listingField__description}>
|
|
202
|
-
Attach documents, PDFs, or other files
|
|
203
|
-
</p>
|
|
204
|
-
</div>
|
|
205
|
-
</div>
|
|
206
|
-
|
|
207
|
-
<Text type="formLabel" style={{ display: "block", marginBottom: "8px" }}>
|
|
208
|
-
{field.values.label}
|
|
209
|
-
{field.values.isRequired && (
|
|
210
|
-
<span className={listingFieldStyles.requiredAsterisk}>*</span>
|
|
211
|
-
)}
|
|
212
|
-
</Text>
|
|
213
|
-
|
|
214
|
-
{/* Drop zone - always show exactly one empty input */}
|
|
215
|
-
<div
|
|
216
|
-
className={`${styles.listingFileInput__dropZone} ${showError ? styles["listingFileInput__dropZone--error"] : ""}`}
|
|
217
|
-
>
|
|
218
|
-
{emptyInput && (
|
|
219
|
-
<FileInput
|
|
220
|
-
key={emptyInput.id}
|
|
221
|
-
refreshCallback={(url) => handleFileUpload(emptyInput.id, url)}
|
|
222
|
-
hasDefault={null}
|
|
223
|
-
accept={acceptTypes}
|
|
224
|
-
multiple={false}
|
|
225
|
-
disabled={disabled}
|
|
226
|
-
/>
|
|
227
|
-
)}
|
|
228
|
-
</div>
|
|
229
|
-
|
|
230
|
-
{/* File list */}
|
|
231
|
-
{uploadedFiles.length > 0 && (
|
|
232
|
-
<div className={styles.listingFileInput__fileList}>
|
|
233
|
-
{uploadedFiles.map((file) => (
|
|
234
|
-
<FileListItem
|
|
235
|
-
key={file.url}
|
|
236
|
-
file={file}
|
|
237
|
-
onNameChange={handleNameChange}
|
|
238
|
-
onRemove={handleFileRemove}
|
|
239
|
-
disabled={disabled}
|
|
240
|
-
/>
|
|
241
|
-
))}
|
|
242
|
-
</div>
|
|
243
|
-
)}
|
|
244
|
-
|
|
245
|
-
{showError && errorMessage && (
|
|
246
|
-
<Text
|
|
247
|
-
type="help"
|
|
248
|
-
style={{
|
|
249
|
-
color: "var(--colour-red)",
|
|
250
|
-
marginTop: "8px",
|
|
251
|
-
display: "block",
|
|
252
|
-
}}
|
|
253
|
-
>
|
|
254
|
-
{errorMessage}
|
|
255
|
-
</Text>
|
|
256
|
-
)}
|
|
257
|
-
{field.values.helpText && (
|
|
258
|
-
<Text
|
|
259
|
-
type="help"
|
|
260
|
-
style={{
|
|
261
|
-
color: "var(--text-bluegrey)",
|
|
262
|
-
marginTop: "8px",
|
|
263
|
-
display: "block",
|
|
264
|
-
}}
|
|
265
|
-
>
|
|
266
|
-
{field.values.helpText}
|
|
267
|
-
</Text>
|
|
268
|
-
)}
|
|
269
|
-
</div>
|
|
270
|
-
);
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
export default ListingFileInput;
|