@plusscommunities/pluss-feature-builder-web-a 1.0.2-beta.0

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 (117) hide show
  1. package/.babelrc +4 -0
  2. package/dist/index.cjs.js +7792 -0
  3. package/package.json +54 -0
  4. package/rollup.config.js +68 -0
  5. package/src/actions/featureBuilderStringsActions.js +88 -0
  6. package/src/actions/featureDefinitionsIndex.js +258 -0
  7. package/src/actions/formActions.js +311 -0
  8. package/src/actions/index.js +12 -0
  9. package/src/actions/listingActions.js +350 -0
  10. package/src/actions/wizardActions.js +240 -0
  11. package/src/components/ActivityCardExample.jsx +86 -0
  12. package/src/components/ActivityCardExample.module.css +130 -0
  13. package/src/components/BackgroundLoader.jsx +33 -0
  14. package/src/components/BackgroundLoader.module.css +46 -0
  15. package/src/components/BaseFieldConfig.jsx +305 -0
  16. package/src/components/BaseFieldConfig.module.css +42 -0
  17. package/src/components/CenteredContainer.jsx +29 -0
  18. package/src/components/CenteredContainer.module.css +171 -0
  19. package/src/components/DeleteConfirmationPopup.jsx +95 -0
  20. package/src/components/DeleteConfirmationPopup.module.css +12 -0
  21. package/src/components/ErrorBoundary.jsx +134 -0
  22. package/src/components/ErrorBoundary.module.css +77 -0
  23. package/src/components/ErrorMessage.jsx +85 -0
  24. package/src/components/ErrorMessage.module.css +116 -0
  25. package/src/components/ExampleDisplay.jsx +26 -0
  26. package/src/components/ExampleDisplay.module.css +3 -0
  27. package/src/components/FeatureBuilderSidebar.jsx +84 -0
  28. package/src/components/FeatureBuilderSuccessPopup.jsx +55 -0
  29. package/src/components/FeatureBuilderSuccessPopup.module.css +43 -0
  30. package/src/components/FeatureBuilderWelcomePopup.jsx +51 -0
  31. package/src/components/FeatureBuilderWelcomePopup.module.css +21 -0
  32. package/src/components/FeatureListingCard.jsx +104 -0
  33. package/src/components/FeatureListingCard.module.css +62 -0
  34. package/src/components/Fields.jsx +460 -0
  35. package/src/components/Fields.module.css +159 -0
  36. package/src/components/IconLoader.jsx +153 -0
  37. package/src/components/IconLoader.module.css +92 -0
  38. package/src/components/IconSelector.jsx +112 -0
  39. package/src/components/IconSelector.module.css +197 -0
  40. package/src/components/ListingEditor.jsx +406 -0
  41. package/src/components/ListingEditor.module.css +14 -0
  42. package/src/components/ListingSuccessPopup.jsx +52 -0
  43. package/src/components/LoadingScreen.jsx +54 -0
  44. package/src/components/LoadingScreen.module.css +103 -0
  45. package/src/components/LoadingState.jsx +40 -0
  46. package/src/components/LoadingState.module.css +18 -0
  47. package/src/components/PreviewFull.js +24 -0
  48. package/src/components/PreviewFull.module.css +11 -0
  49. package/src/components/PreviewGrid.js +14 -0
  50. package/src/components/PreviewWidget.js +27 -0
  51. package/src/components/PreviewWidget.module.css +15 -0
  52. package/src/components/SidebarLayout.jsx +292 -0
  53. package/src/components/SidebarLayout.module.css +145 -0
  54. package/src/components/SkeletonLoader.jsx +128 -0
  55. package/src/components/SkeletonLoader.module.css +295 -0
  56. package/src/components/SortButtonGroup.jsx +34 -0
  57. package/src/components/SortButtonGroup.module.css +51 -0
  58. package/src/components/ToastContainer.jsx +98 -0
  59. package/src/components/ToastContainer.module.css +156 -0
  60. package/src/components/ToggleSwitch.js +40 -0
  61. package/src/components/ToggleSwitch.module.css +48 -0
  62. package/src/components/TwoColumnInput.jsx +29 -0
  63. package/src/components/TwoColumnInput.module.css +32 -0
  64. package/src/components/ViewFull.js +139 -0
  65. package/src/components/ViewFull.module.css +71 -0
  66. package/src/components/ViewWidget.js +62 -0
  67. package/src/components/ViewWidget.module.css +28 -0
  68. package/src/components/iconCategories.js +135 -0
  69. package/src/components/iconImports.js +409 -0
  70. package/src/components/index.js +61 -0
  71. package/src/components/listing/FileListItem.jsx +86 -0
  72. package/src/components/listing/GalleryDisplay.jsx +331 -0
  73. package/src/components/listing/GalleryDisplay.module.css +309 -0
  74. package/src/components/listing/ListingCTAInput.jsx +82 -0
  75. package/src/components/listing/ListingDescriptionInput.jsx +73 -0
  76. package/src/components/listing/ListingField.jsx +101 -0
  77. package/src/components/listing/ListingField.module.css +106 -0
  78. package/src/components/listing/ListingFileInput.jsx +255 -0
  79. package/src/components/listing/ListingFileInput.module.css +192 -0
  80. package/src/components/listing/ListingForm.jsx +90 -0
  81. package/src/components/listing/ListingForm.module.css +38 -0
  82. package/src/components/listing/ListingGalleryInput.jsx +236 -0
  83. package/src/components/listing/ListingGalleryInput.module.css +131 -0
  84. package/src/components/listing/ListingImageInput.jsx +153 -0
  85. package/src/components/listing/ListingTextInput.jsx +72 -0
  86. package/src/feature.config.js +130 -0
  87. package/src/helper/index.js +135 -0
  88. package/src/hooks/useFeatureDefinitionLoader.js +62 -0
  89. package/src/images/full.png +0 -0
  90. package/src/images/fullNoTitle.png +0 -0
  91. package/src/images/previewWidget.png +0 -0
  92. package/src/images/widget.png +0 -0
  93. package/src/index.js +38 -0
  94. package/src/pages/CreateListingPage.jsx +49 -0
  95. package/src/pages/EditListingPage.jsx +58 -0
  96. package/src/reducers/featureBuilderReducer.js +744 -0
  97. package/src/screens/CreateListing.module.css +45 -0
  98. package/src/screens/Form.module.css +734 -0
  99. package/src/screens/FormFieldsStep.jsx +689 -0
  100. package/src/screens/FormLayoutStep.jsx +445 -0
  101. package/src/screens/FormOverviewStep.jsx +396 -0
  102. package/src/screens/ListingScreen.jsx +478 -0
  103. package/src/screens/ListingScreen.module.css +333 -0
  104. package/src/selectors/featureBuilderSelectors.js +529 -0
  105. package/src/types/index.js +91 -0
  106. package/src/utils/textUtils.js +89 -0
  107. package/src/validators/galleryValidators.js +345 -0
  108. package/src/values.config.a.js +49 -0
  109. package/src/values.config.b.js +49 -0
  110. package/src/values.config.c.js +49 -0
  111. package/src/values.config.d.js +49 -0
  112. package/src/values.config.js +49 -0
  113. package/src/webapi/featureDefinitionActions.js +0 -0
  114. package/src/webapi/featuresActions.js +90 -0
  115. package/src/webapi/helper.js +4 -0
  116. package/src/webapi/index.js +12 -0
  117. package/src/webapi/listingActions.js +176 -0
@@ -0,0 +1,29 @@
1
+ import React from "react";
2
+ import styles from "./TwoColumnInput.module.css";
3
+
4
+ /**
5
+ * Two Column Input layout component
6
+ * Renders form inputs in a side-by-side layout with proper spacing and styling
7
+ * Commonly used for label/placeholder pairs or related input fields
8
+ *
9
+ * @param {Object} props - Component props
10
+ * @param {React.ReactNode} props.left - Content for the left column (typically a form input)
11
+ * @param {React.ReactNode} props.right - Content for the right column (typically a form input)
12
+ * @param {boolean} [props.leftRequired=false] - Whether left column content is required
13
+ * @returns {React.ReactElement} Two-column layout wrapper
14
+ *
15
+ * @example
16
+ * <TwoColumnInput
17
+ * left={<GenericInput label="First Name" />}
18
+ * right={<GenericInput label="Last Name" />}
19
+ * leftRequired={true}
20
+ * />
21
+ */
22
+ export const TwoColumnInput = ({ left, right, leftRequired = false }) => {
23
+ return (
24
+ <div className={styles.twoColumnInput}>
25
+ <div className={`${styles.column} ${styles.leftColumn}`}>{left}</div>
26
+ <div className={`${styles.column} ${styles.rightColumn}`}>{right}</div>
27
+ </div>
28
+ );
29
+ };
@@ -0,0 +1,32 @@
1
+ /* TwoColumnInput BEM CSS Module */
2
+
3
+ .twoColumnInput {
4
+ display: flex;
5
+ gap: 20px;
6
+ align-items: center;
7
+ }
8
+
9
+ .column {
10
+ flex: 1;
11
+ }
12
+
13
+ .leftColumn {
14
+ flex: 1.2; /* Slightly wider for required label */
15
+ }
16
+
17
+ .rightColumn {
18
+ flex: 1;
19
+ }
20
+
21
+ /* Responsive adjustments */
22
+ @media (max-width: 768px) {
23
+ .twoColumnInput {
24
+ flex-direction: column;
25
+ gap: 12px;
26
+ }
27
+
28
+ .leftColumn,
29
+ .rightColumn {
30
+ flex: 1;
31
+ }
32
+ }
@@ -0,0 +1,139 @@
1
+ import React from "react";
2
+ import { PlussCore } from "../feature.config";
3
+ const { Text, Button } = PlussCore.Components;
4
+ import full from "../images/full.png";
5
+ import styles from "./ViewFull.module.css";
6
+
7
+ const ViewFull = ({ listing }) => {
8
+ // If no listing data provided, show placeholder
9
+ if (!listing || !listing.fields) {
10
+ return <img className={styles.viewFull__image} src={full} alt="full" />;
11
+ }
12
+
13
+ // Get the title field
14
+ const title = listing.fields["mandatory-title"] || "Untitled";
15
+
16
+ // Find all CTA fields
17
+ const ctaFields = Object.keys(listing.fields).filter((fieldId) => {
18
+ const fieldValue = listing.fields[fieldId];
19
+ return (
20
+ fieldValue &&
21
+ typeof fieldValue === "object" &&
22
+ fieldValue.label &&
23
+ fieldValue.url
24
+ );
25
+ });
26
+
27
+ // Find all text fields
28
+ const textFields = Object.keys(listing.fields)
29
+ .filter((fieldId) => {
30
+ const fieldValue = listing.fields[fieldId];
31
+ return typeof fieldValue === "string" && fieldValue.length > 0;
32
+ })
33
+ .filter((fieldId) => fieldId !== "mandatory-title"); // Exclude title field
34
+
35
+ // Find all image fields
36
+ const imageFields = Object.keys(listing.fields).filter((fieldId) => {
37
+ const fieldValue = listing.fields[fieldId];
38
+ return typeof fieldValue === "string" && fieldValue.startsWith("https://");
39
+ });
40
+
41
+ // Find all file fields
42
+ const fileFields = Object.keys(listing.fields).filter((fieldId) => {
43
+ const fieldValue = listing.fields[fieldId];
44
+ // Handle both single file object and array of files
45
+ if (Array.isArray(fieldValue)) {
46
+ return fieldValue.length > 0 && fieldValue.some((f) => f.url && !f.label);
47
+ } else if (fieldValue && typeof fieldValue === "object") {
48
+ return fieldValue.url && !fieldValue.label; // Not a CTA field (CTA has both url and label)
49
+ }
50
+ return false;
51
+ });
52
+
53
+ // Helper function to normalize file data
54
+ const normalizeFiles = (fileData) => {
55
+ if (Array.isArray(fileData)) {
56
+ return fileData.filter((f) => f && f.url && !f.label);
57
+ } else if (
58
+ fileData &&
59
+ typeof fileData === "object" &&
60
+ fileData.url &&
61
+ !fileData.label
62
+ ) {
63
+ return [fileData];
64
+ }
65
+ return [];
66
+ };
67
+
68
+ return (
69
+ <div className={styles.viewFull}>
70
+ {/* Title */}
71
+ <Text type="h2" className={styles.viewFull__title}>
72
+ {title}
73
+ </Text>
74
+
75
+ {/* Image fields */}
76
+ {imageFields.map((fieldId) => (
77
+ <div key={fieldId} className={styles.viewFull__imageContainer}>
78
+ <img
79
+ src={listing.fields[fieldId]}
80
+ alt={fieldId}
81
+ className={styles.viewFull__imageField}
82
+ />
83
+ </div>
84
+ ))}
85
+
86
+ {/* Text fields */}
87
+ {textFields.map((fieldId) => (
88
+ <Text key={fieldId} type="body" className={styles.viewFull__textField}>
89
+ {listing.fields[fieldId]}
90
+ </Text>
91
+ ))}
92
+
93
+ {/* File fields */}
94
+ {fileFields.map((fieldId) => {
95
+ const files = normalizeFiles(listing.fields[fieldId]);
96
+ return (
97
+ <div key={fieldId} className={styles.viewFull__fileSection}>
98
+ {files.map((file, index) => (
99
+ <div
100
+ key={`${fieldId}-${index}`}
101
+ className={styles.viewFull__fileItem}
102
+ >
103
+ <a
104
+ href={file.url}
105
+ target="_blank"
106
+ rel="noopener noreferrer"
107
+ className={styles.viewFull__fileLink}
108
+ >
109
+ <Text type="body" className={styles.viewFull__fileName}>
110
+ {file.name || file.originalName}
111
+ </Text>
112
+ </a>
113
+ </div>
114
+ ))}
115
+ </div>
116
+ );
117
+ })}
118
+
119
+ {/* CTA fields */}
120
+ {ctaFields.map((fieldId) => {
121
+ const ctaField = listing.fields[fieldId];
122
+ return (
123
+ <Button
124
+ key={fieldId}
125
+ buttonType="primary"
126
+ onClick={() => {
127
+ window.open(ctaField.url, "_blank", "noopener,noreferrer");
128
+ }}
129
+ className={styles.viewFull__ctaButton}
130
+ >
131
+ {ctaField.label}
132
+ </Button>
133
+ );
134
+ })}
135
+ </div>
136
+ );
137
+ };
138
+
139
+ export default ViewFull;
@@ -0,0 +1,71 @@
1
+ /* ViewFull BEM CSS Module */
2
+
3
+ /* Block */
4
+ .viewFull {
5
+ padding: 20px;
6
+ }
7
+
8
+ /* Elements */
9
+ .viewFull__image {
10
+ width: 100%;
11
+ height: 100%;
12
+ object-fit: contain;
13
+ }
14
+
15
+ .viewFull__title {
16
+ margin-bottom: 20px;
17
+ }
18
+
19
+ .viewFull__imageContainer {
20
+ margin-bottom: 20px;
21
+ text-align: center;
22
+ }
23
+
24
+ .viewFull__imageField {
25
+ max-width: 100%;
26
+ max-height: 300px;
27
+ object-fit: contain;
28
+ }
29
+
30
+ .viewFull__textField {
31
+ margin-bottom: 12px;
32
+ line-height: 1.4;
33
+ }
34
+
35
+ .viewFull__fileSection {
36
+ margin-bottom: 20px;
37
+ }
38
+
39
+ .viewFull__fileItem {
40
+ margin-bottom: 8px;
41
+ padding: 12px;
42
+ border: 1px solid var(--border-line-grey);
43
+ border-radius: 4px;
44
+ background-color: #fff;
45
+ transition: all 0.2s ease-in-out;
46
+ }
47
+
48
+ .viewFull__fileItem:hover {
49
+ border-color: var(--colour-branding-action);
50
+ box-shadow: 0 0 4px var(--colour-branding-action-alpha20);
51
+ }
52
+
53
+ .viewFull__fileLink {
54
+ text-decoration: none;
55
+ color: var(--text-dark);
56
+ display: flex;
57
+ align-items: center;
58
+ }
59
+
60
+ .viewFull__fileLink:hover {
61
+ color: var(--colour-branding-action);
62
+ }
63
+
64
+ .viewFull__fileName {
65
+ font-weight: 500;
66
+ }
67
+
68
+ .viewFull__ctaButton {
69
+ margin-top: 20px;
70
+ margin-right: 10px;
71
+ }
@@ -0,0 +1,62 @@
1
+ import React from "react";
2
+ import { PlussCore } from "../feature.config";
3
+ const { Text, Button } = PlussCore.Components;
4
+ import widget from "../images/widget.png";
5
+ import styles from "./ViewWidget.module.css";
6
+
7
+ const ViewWidget = ({ listing }) => {
8
+ // If no listing data provided, show placeholder
9
+ if (!listing || !listing.fields) {
10
+ return <img className={styles.image} src={widget} alt="widget" />;
11
+ }
12
+
13
+ // Get the title field
14
+ const title = listing.fields["mandatory-title"] || "Untitled";
15
+
16
+ // Find all CTA fields and render the first one
17
+ const ctaFields = Object.keys(listing.fields).filter((fieldId) => {
18
+ // Check if field value has label and url (CTA structure)
19
+ const fieldValue = listing.fields[fieldId];
20
+ return (
21
+ fieldValue &&
22
+ typeof fieldValue === "object" &&
23
+ fieldValue.label &&
24
+ fieldValue.url
25
+ );
26
+ });
27
+
28
+ // If no CTA fields, just show title
29
+ if (ctaFields.length === 0) {
30
+ return (
31
+ <div className={styles.root}>
32
+ <Text type="body" className={styles.title}>
33
+ {title}
34
+ </Text>
35
+ </div>
36
+ );
37
+ }
38
+
39
+ // Render CTA button
40
+ const ctaFieldId = ctaFields[0];
41
+ const ctaField = listing.fields[ctaFieldId];
42
+
43
+ return (
44
+ <div className={styles.root}>
45
+ <Text type="body" className={styles.title}>
46
+ {title}
47
+ </Text>
48
+ <Button
49
+ buttonType="primary"
50
+ onClick={() => {
51
+ // Open URL in new window
52
+ window.open(ctaField.url, "_blank", "noopener,noreferrer");
53
+ }}
54
+ className={styles.ctaButton}
55
+ >
56
+ {ctaField.label}
57
+ </Button>
58
+ </div>
59
+ );
60
+ };
61
+
62
+ export default ViewWidget;
@@ -0,0 +1,28 @@
1
+ /* ViewWidget CSS Module */
2
+
3
+ /* Block */
4
+ .root {
5
+ padding: 8px;
6
+ text-align: center;
7
+ min-height: 80px;
8
+ display: flex;
9
+ flex-direction: column;
10
+ justify-content: center;
11
+ align-items: center;
12
+ }
13
+
14
+ /* Elements */
15
+ .image {
16
+ width: 80px;
17
+ }
18
+
19
+ .title {
20
+ font-size: 12px;
21
+ margin-bottom: 8px;
22
+ }
23
+
24
+ .ctaButton {
25
+ font-size: 11px;
26
+ padding: 4px 8px;
27
+ min-width: auto;
28
+ }
@@ -0,0 +1,135 @@
1
+ /*
2
+ * Icon categories and definitions for the feature builder
3
+ * Curated for retirement village community management
4
+ * All icons are compatible with Font Awesome 6.4.0 (plugin) and 4.7.0 (main app)
5
+ */
6
+ export const iconCategories = [
7
+ {
8
+ name: "Community & Living",
9
+ icons: [
10
+ "home",
11
+ "users",
12
+ "user",
13
+ "heart",
14
+ "star",
15
+ "flag",
16
+ "hands-helping",
17
+ "handshake",
18
+ "people-carry",
19
+ "child",
20
+ ],
21
+ },
22
+ {
23
+ name: "Household & Comfort",
24
+ icons: [
25
+ "couch",
26
+ "bed",
27
+ "tv",
28
+ "bath",
29
+ "shower",
30
+ "toilet",
31
+ "sink",
32
+ "chair",
33
+ "table",
34
+ "fire",
35
+ "fan",
36
+ "temperature-high",
37
+ ],
38
+ },
39
+ {
40
+ name: "Health & Wellness",
41
+ icons: [
42
+ "shield",
43
+ "heart",
44
+ "user-nurse",
45
+ "stethoscope",
46
+ "heart-pulse",
47
+ "wheelchair",
48
+ "crutch",
49
+ "prescription-bottle",
50
+ "pills",
51
+ "syringe",
52
+ "thermometer",
53
+ ],
54
+ },
55
+ {
56
+ name: "Activities & Events",
57
+ icons: [
58
+ "calendar",
59
+ "clock",
60
+ "bell",
61
+ "music",
62
+ "book",
63
+ "image",
64
+ "camera",
65
+ "chess",
66
+ "dice",
67
+ "theater-masks",
68
+ "palette",
69
+ "graduation-cap",
70
+ "trophy",
71
+ ],
72
+ },
73
+ {
74
+ name: "Sports & Recreation",
75
+ icons: [
76
+ "basketball-ball",
77
+ "football-ball",
78
+ "volleyball-ball",
79
+ "table-tennis",
80
+ "bowling-ball",
81
+ "swimming-pool",
82
+ "bicycle",
83
+ "walking",
84
+ "hiking",
85
+ "umbrella-beach",
86
+ ],
87
+ },
88
+ {
89
+ name: "Dining & Food",
90
+ icons: [
91
+ "utensils",
92
+ "coffee",
93
+ "wine-glass",
94
+ "glass-water",
95
+ "pizza-slice",
96
+ "apple-alt",
97
+ "carrot",
98
+ "bread-slice",
99
+ "ice-cream",
100
+ "cocktail",
101
+ ],
102
+ },
103
+ {
104
+ name: "Transportation",
105
+ icons: ["car", "bus", "train", "bicycle", "truck"],
106
+ },
107
+ {
108
+ name: "Files & Information",
109
+ icons: ["file", "file-lines", "folder", "book-open", "clipboard"],
110
+ },
111
+ {
112
+ name: "Communication",
113
+ icons: ["phone", "envelope", "map-marker", "location-dot"],
114
+ },
115
+ {
116
+ name: "Garden & Outdoor",
117
+ icons: ["tree", "leaf", "seedling", "sun", "cloud-sun", "bug"],
118
+ },
119
+ {
120
+ name: "Maintenance & Safety",
121
+ icons: ["wrench", "hammer", "tools", "lock"],
122
+ },
123
+ ];
124
+
125
+ // Flatten all icons for easy iteration, removing duplicates
126
+ export const allIcons = [
127
+ ...new Set(
128
+ iconCategories.reduce((acc, category) => {
129
+ return [...acc, ...category.icons];
130
+ }, []),
131
+ ),
132
+ ];
133
+
134
+ // Default icons for current selection
135
+ export const defaultIcon = "star";