@plusscommunities/pluss-feature-builder-web-d 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.
- package/.babelrc +4 -0
- package/dist/index.cjs.js +7792 -0
- package/package.json +54 -0
- package/rollup.config.js +68 -0
- package/src/actions/featureBuilderStringsActions.js +88 -0
- package/src/actions/featureDefinitionsIndex.js +258 -0
- package/src/actions/formActions.js +311 -0
- package/src/actions/index.js +12 -0
- package/src/actions/listingActions.js +350 -0
- package/src/actions/wizardActions.js +240 -0
- package/src/components/ActivityCardExample.jsx +86 -0
- package/src/components/ActivityCardExample.module.css +130 -0
- package/src/components/BackgroundLoader.jsx +33 -0
- package/src/components/BackgroundLoader.module.css +46 -0
- package/src/components/BaseFieldConfig.jsx +305 -0
- package/src/components/BaseFieldConfig.module.css +42 -0
- package/src/components/CenteredContainer.jsx +29 -0
- package/src/components/CenteredContainer.module.css +171 -0
- package/src/components/DeleteConfirmationPopup.jsx +95 -0
- package/src/components/DeleteConfirmationPopup.module.css +12 -0
- package/src/components/ErrorBoundary.jsx +134 -0
- package/src/components/ErrorBoundary.module.css +77 -0
- package/src/components/ErrorMessage.jsx +85 -0
- package/src/components/ErrorMessage.module.css +116 -0
- package/src/components/ExampleDisplay.jsx +26 -0
- package/src/components/ExampleDisplay.module.css +3 -0
- package/src/components/FeatureBuilderSidebar.jsx +84 -0
- package/src/components/FeatureBuilderSuccessPopup.jsx +55 -0
- package/src/components/FeatureBuilderSuccessPopup.module.css +43 -0
- package/src/components/FeatureBuilderWelcomePopup.jsx +51 -0
- package/src/components/FeatureBuilderWelcomePopup.module.css +21 -0
- package/src/components/FeatureListingCard.jsx +104 -0
- package/src/components/FeatureListingCard.module.css +62 -0
- package/src/components/Fields.jsx +460 -0
- package/src/components/Fields.module.css +159 -0
- package/src/components/IconLoader.jsx +153 -0
- package/src/components/IconLoader.module.css +92 -0
- package/src/components/IconSelector.jsx +112 -0
- package/src/components/IconSelector.module.css +197 -0
- package/src/components/ListingEditor.jsx +406 -0
- package/src/components/ListingEditor.module.css +14 -0
- package/src/components/ListingSuccessPopup.jsx +52 -0
- package/src/components/LoadingScreen.jsx +54 -0
- package/src/components/LoadingScreen.module.css +103 -0
- package/src/components/LoadingState.jsx +40 -0
- package/src/components/LoadingState.module.css +18 -0
- package/src/components/PreviewFull.js +24 -0
- package/src/components/PreviewFull.module.css +11 -0
- package/src/components/PreviewGrid.js +14 -0
- package/src/components/PreviewWidget.js +27 -0
- package/src/components/PreviewWidget.module.css +15 -0
- package/src/components/SidebarLayout.jsx +292 -0
- package/src/components/SidebarLayout.module.css +145 -0
- package/src/components/SkeletonLoader.jsx +128 -0
- package/src/components/SkeletonLoader.module.css +295 -0
- package/src/components/SortButtonGroup.jsx +34 -0
- package/src/components/SortButtonGroup.module.css +51 -0
- package/src/components/ToastContainer.jsx +98 -0
- package/src/components/ToastContainer.module.css +156 -0
- package/src/components/ToggleSwitch.js +40 -0
- package/src/components/ToggleSwitch.module.css +48 -0
- package/src/components/TwoColumnInput.jsx +29 -0
- package/src/components/TwoColumnInput.module.css +32 -0
- package/src/components/ViewFull.js +139 -0
- package/src/components/ViewFull.module.css +71 -0
- package/src/components/ViewWidget.js +62 -0
- package/src/components/ViewWidget.module.css +28 -0
- package/src/components/iconCategories.js +135 -0
- package/src/components/iconImports.js +409 -0
- package/src/components/index.js +61 -0
- package/src/components/listing/FileListItem.jsx +86 -0
- package/src/components/listing/GalleryDisplay.jsx +331 -0
- package/src/components/listing/GalleryDisplay.module.css +309 -0
- package/src/components/listing/ListingCTAInput.jsx +82 -0
- package/src/components/listing/ListingDescriptionInput.jsx +73 -0
- package/src/components/listing/ListingField.jsx +101 -0
- package/src/components/listing/ListingField.module.css +106 -0
- package/src/components/listing/ListingFileInput.jsx +255 -0
- package/src/components/listing/ListingFileInput.module.css +192 -0
- package/src/components/listing/ListingForm.jsx +90 -0
- package/src/components/listing/ListingForm.module.css +38 -0
- package/src/components/listing/ListingGalleryInput.jsx +236 -0
- package/src/components/listing/ListingGalleryInput.module.css +131 -0
- package/src/components/listing/ListingImageInput.jsx +153 -0
- package/src/components/listing/ListingTextInput.jsx +72 -0
- package/src/feature.config.js +130 -0
- package/src/helper/index.js +135 -0
- package/src/hooks/useFeatureDefinitionLoader.js +62 -0
- 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 +38 -0
- package/src/pages/CreateListingPage.jsx +49 -0
- package/src/pages/EditListingPage.jsx +58 -0
- package/src/reducers/featureBuilderReducer.js +744 -0
- package/src/screens/CreateListing.module.css +45 -0
- package/src/screens/Form.module.css +734 -0
- package/src/screens/FormFieldsStep.jsx +689 -0
- package/src/screens/FormLayoutStep.jsx +445 -0
- package/src/screens/FormOverviewStep.jsx +396 -0
- package/src/screens/ListingScreen.jsx +478 -0
- package/src/screens/ListingScreen.module.css +333 -0
- package/src/selectors/featureBuilderSelectors.js +529 -0
- package/src/types/index.js +91 -0
- package/src/utils/textUtils.js +89 -0
- package/src/validators/galleryValidators.js +345 -0
- package/src/values.config.a.js +49 -0
- package/src/values.config.b.js +49 -0
- package/src/values.config.c.js +49 -0
- package/src/values.config.d.js +49 -0
- package/src/values.config.js +49 -0
- package/src/webapi/featureDefinitionActions.js +0 -0
- package/src/webapi/featuresActions.js +90 -0
- package/src/webapi/helper.js +4 -0
- package/src/webapi/index.js +12 -0
- package/src/webapi/listingActions.js +176 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wizard Actions for Feature Builder
|
|
3
|
+
* Manages wizard state including navigation, step validation, and mode switching
|
|
4
|
+
* Provides validation logic for overview, fields, and layout steps
|
|
5
|
+
* Coordinates with form state and step progression
|
|
6
|
+
*
|
|
7
|
+
* @namespace wizardActions
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Import selectors for form state access
|
|
11
|
+
import {
|
|
12
|
+
selectFormTitle,
|
|
13
|
+
selectFormIcon,
|
|
14
|
+
selectFormDisplayName,
|
|
15
|
+
selectFormLayout,
|
|
16
|
+
selectFormFields,
|
|
17
|
+
} from "../selectors/featureBuilderSelectors";
|
|
18
|
+
|
|
19
|
+
// Wizard action types
|
|
20
|
+
export const SET_WIZARD_MODE = "SET_WIZARD_MODE";
|
|
21
|
+
export const SET_NAVIGATION_STATE = "SET_NAVIGATION_STATE";
|
|
22
|
+
export const UPDATE_STEP_VALIDATION = "UPDATE_STEP_VALIDATION";
|
|
23
|
+
export const MARK_STEP_COMPLETE = "MARK_STEP_COMPLETE";
|
|
24
|
+
export const RESET_WIZARD_STATE = "RESET_WIZARD_STATE";
|
|
25
|
+
export const VALIDATE_AND_UPDATE_STEP = "VALIDATE_AND_UPDATE_STEP";
|
|
26
|
+
|
|
27
|
+
// Mode setters
|
|
28
|
+
export const setWizardMode = (mode) => ({
|
|
29
|
+
type: SET_WIZARD_MODE,
|
|
30
|
+
payload: mode,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Navigation actions
|
|
34
|
+
export const setNavigationState = (navigationState) => ({
|
|
35
|
+
type: SET_NAVIGATION_STATE,
|
|
36
|
+
payload: navigationState,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export const setCurrentStep = (step, previousStep = null) => ({
|
|
40
|
+
type: SET_NAVIGATION_STATE,
|
|
41
|
+
payload: {
|
|
42
|
+
currentStep: step,
|
|
43
|
+
previousStep,
|
|
44
|
+
canGoBack: step !== "welcome",
|
|
45
|
+
canGoForward: true,
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
export const goToStep = (step) => (dispatch, getState) => {
|
|
50
|
+
const state = getState()[require("../values.config").reducerKey];
|
|
51
|
+
const currentStep =
|
|
52
|
+
state &&
|
|
53
|
+
state.wizard &&
|
|
54
|
+
state.wizard.navigation &&
|
|
55
|
+
state.wizard.navigation.currentStep;
|
|
56
|
+
|
|
57
|
+
// Clear form submission state when changing steps
|
|
58
|
+
const { clearFormSubmissionState } = require("./formActions");
|
|
59
|
+
dispatch(clearFormSubmissionState());
|
|
60
|
+
|
|
61
|
+
dispatch(setCurrentStep(step, currentStep));
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const updateStepValidation = (step, isValid, errors = {}) => ({
|
|
65
|
+
type: UPDATE_STEP_VALIDATION,
|
|
66
|
+
payload: {
|
|
67
|
+
step,
|
|
68
|
+
isValid,
|
|
69
|
+
errors,
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
export const validateAndUpdateStep = (step) => (dispatch, getState) => {
|
|
74
|
+
// Use existing selectors to get form data
|
|
75
|
+
const state = getState();
|
|
76
|
+
const title = selectFormTitle(state);
|
|
77
|
+
const icon = selectFormIcon(state);
|
|
78
|
+
const displayName = selectFormDisplayName(state);
|
|
79
|
+
const layout = selectFormLayout(state);
|
|
80
|
+
const fields = selectFormFields(state);
|
|
81
|
+
|
|
82
|
+
const form = { title, icon, displayName, layout, fields };
|
|
83
|
+
|
|
84
|
+
const { isValid, errors } = getFormValidation(form, step);
|
|
85
|
+
|
|
86
|
+
dispatch(updateStepValidation(step, isValid, errors));
|
|
87
|
+
|
|
88
|
+
// If valid, mark as complete
|
|
89
|
+
if (isValid) {
|
|
90
|
+
dispatch({
|
|
91
|
+
type: MARK_STEP_COMPLETE,
|
|
92
|
+
payload: step,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return { isValid, errors };
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// Step completion
|
|
100
|
+
export const markStepComplete = (step) => ({
|
|
101
|
+
type: MARK_STEP_COMPLETE,
|
|
102
|
+
payload: step,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Reset wizard state
|
|
106
|
+
export const resetWizardState = () => ({
|
|
107
|
+
type: RESET_WIZARD_STATE,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const getFormValidation = (form, step) => {
|
|
111
|
+
// Return validation results for undefined form (prevent crashes)
|
|
112
|
+
if (!form) {
|
|
113
|
+
switch (step) {
|
|
114
|
+
case "overview":
|
|
115
|
+
return {
|
|
116
|
+
isValid: false,
|
|
117
|
+
errors: {
|
|
118
|
+
title: "Title is required",
|
|
119
|
+
displayName: "Display name is required",
|
|
120
|
+
icon: "Icon is required",
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
case "fields":
|
|
124
|
+
return {
|
|
125
|
+
isValid: false,
|
|
126
|
+
errors: {
|
|
127
|
+
missingTitle: "Title field is required",
|
|
128
|
+
missingImage: "Feature image field is required",
|
|
129
|
+
fieldLabels: "Some fields are missing labels",
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
case "layout":
|
|
133
|
+
return {
|
|
134
|
+
isValid: false,
|
|
135
|
+
errors: {
|
|
136
|
+
layoutType: "Layout type is required",
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
default:
|
|
140
|
+
return { isValid: false, errors: {} };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
switch (step) {
|
|
145
|
+
case "overview": {
|
|
146
|
+
const hasTitle = form.title && form.title.trim().length > 0;
|
|
147
|
+
const hasDisplayName =
|
|
148
|
+
form.displayName && form.displayName.trim().length > 0;
|
|
149
|
+
const hasIcon = form.icon && form.icon.length > 0;
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
isValid: hasTitle && hasDisplayName && hasIcon,
|
|
153
|
+
errors: {
|
|
154
|
+
title: !hasTitle ? "Title is required" : null,
|
|
155
|
+
displayName: !hasDisplayName ? "Display name is required" : null,
|
|
156
|
+
icon: !hasIcon ? "Icon is required" : null,
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
case "fields": {
|
|
161
|
+
const hasTitleField =
|
|
162
|
+
form.fields &&
|
|
163
|
+
form.fields.some((field) => field.id === "mandatory-title");
|
|
164
|
+
const hasImageField =
|
|
165
|
+
form.fields &&
|
|
166
|
+
form.fields.some((field) => field.id === "mandatory-feature-image");
|
|
167
|
+
|
|
168
|
+
// Check each field for missing labels and create field-specific errors
|
|
169
|
+
const fieldErrors = {};
|
|
170
|
+
let allFieldsHaveLabels = true;
|
|
171
|
+
|
|
172
|
+
if (form.fields) {
|
|
173
|
+
form.fields.forEach((field) => {
|
|
174
|
+
if (
|
|
175
|
+
(field.type === "text" ||
|
|
176
|
+
field.type === "description" ||
|
|
177
|
+
field.type === "title" ||
|
|
178
|
+
field.type === "image" ||
|
|
179
|
+
field.type === "gallery" ||
|
|
180
|
+
field.type === "feature-image" ||
|
|
181
|
+
field.type === "file" ||
|
|
182
|
+
field.type === "cta") &&
|
|
183
|
+
field.values
|
|
184
|
+
) {
|
|
185
|
+
if (!field.values.label || field.values.label.trim().length === 0) {
|
|
186
|
+
fieldErrors[field.id] = "Field label is required";
|
|
187
|
+
allFieldsHaveLabels = false;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
isValid: hasTitleField && hasImageField && allFieldsHaveLabels,
|
|
195
|
+
errors: {
|
|
196
|
+
missingTitle: !hasTitleField ? "Title field is required" : null,
|
|
197
|
+
missingImage: !hasImageField
|
|
198
|
+
? "Feature image field is required"
|
|
199
|
+
: null,
|
|
200
|
+
...fieldErrors,
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
case "layout": {
|
|
205
|
+
const hasLayoutType =
|
|
206
|
+
form.layout && form.layout.type && form.layout.type.length > 0;
|
|
207
|
+
return {
|
|
208
|
+
isValid: hasLayoutType,
|
|
209
|
+
errors: {
|
|
210
|
+
layoutType: !hasLayoutType ? "Layout type is required" : null,
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
default:
|
|
215
|
+
return { isValid: true, errors: {} };
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
export const setWizardModeAndSave = (mode) => (dispatch) => {
|
|
220
|
+
dispatch(setWizardMode(mode));
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
export const setCurrentStepAndSave =
|
|
224
|
+
(step, previousStep = null) =>
|
|
225
|
+
(dispatch) => {
|
|
226
|
+
dispatch(setCurrentStep(step, previousStep));
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
export const updateStepValidationAndSave =
|
|
230
|
+
(step, isValid, errors = {}) =>
|
|
231
|
+
(dispatch) => {
|
|
232
|
+
dispatch(updateStepValidation(step, isValid, errors));
|
|
233
|
+
if (isValid) {
|
|
234
|
+
dispatch(markStepComplete(step));
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
export const markStepCompleteAndSave = (step) => (dispatch) => {
|
|
239
|
+
dispatch(markStepComplete(step));
|
|
240
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Button } from "../components";
|
|
3
|
+
import { PlussCore } from "../feature.config";
|
|
4
|
+
const { Text } = PlussCore.Components;
|
|
5
|
+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
6
|
+
import { faUserCircle, faClock } from "@fortawesome/free-solid-svg-icons";
|
|
7
|
+
import { capitalizeTextWithFallback } from "../utils/textUtils";
|
|
8
|
+
import styles from "./ActivityCardExample.module.css";
|
|
9
|
+
|
|
10
|
+
const ActivityCardExample = ({ displayName }) => {
|
|
11
|
+
// Handle "a/an" grammar correctly
|
|
12
|
+
const getArticle = (name) => {
|
|
13
|
+
if (!name) return "a";
|
|
14
|
+
const firstChar = name.toLowerCase().charAt(0);
|
|
15
|
+
// Handle vowel sounds and special cases
|
|
16
|
+
return ["a", "e", "i", "o", "u"].includes(firstChar) ? "an" : "a";
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Handle button click - show that it's a display-only button
|
|
20
|
+
const handleButtonClick = () => {
|
|
21
|
+
alert(
|
|
22
|
+
"This is just a preview of how your button will look. In the actual application, this button would create a new " +
|
|
23
|
+
(displayName || "item") +
|
|
24
|
+
".",
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const exampleTitle = displayName
|
|
29
|
+
? `${capitalizeTextWithFallback(displayName, "")} Example`
|
|
30
|
+
: "Example Item";
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div className={styles.activityCardExample}>
|
|
34
|
+
<Text type="formLabel" className={styles.title}>
|
|
35
|
+
Activity Feed Example
|
|
36
|
+
</Text>
|
|
37
|
+
|
|
38
|
+
{/* Additional info */}
|
|
39
|
+
<div className={styles.additionalInfo}>
|
|
40
|
+
<Text type="help" className={styles.infoText}>
|
|
41
|
+
This shows how your single page name will appear in real activity
|
|
42
|
+
feeds and on buttons throughout the application.
|
|
43
|
+
</Text>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div className={styles.activityCard}>
|
|
47
|
+
{/* User Avatar */}
|
|
48
|
+
<div className={styles.profilePic}>
|
|
49
|
+
<FontAwesomeIcon icon={faUserCircle} className={styles.avatarIcon} />
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
{/* Activity Content */}
|
|
53
|
+
<div className={styles.activityContent}>
|
|
54
|
+
<div className={styles.activityText}>
|
|
55
|
+
<span className={styles.highlightedUser}>John Smith</span>
|
|
56
|
+
commented on {getArticle(displayName)}
|
|
57
|
+
<span className={styles.highlightedFeature}>
|
|
58
|
+
{capitalizeTextWithFallback(displayName, "item")}
|
|
59
|
+
</span>
|
|
60
|
+
:
|
|
61
|
+
<span className={styles.exampleTitle}>{exampleTitle}</span>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<div className={styles.activityTime}>
|
|
65
|
+
<FontAwesomeIcon icon={faClock} className={styles.timeIcon} /> 2
|
|
66
|
+
minutes ago
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
{/* Primary Add Button */}
|
|
72
|
+
<div className={styles.buttonContainer}>
|
|
73
|
+
<Button
|
|
74
|
+
buttonType="primary"
|
|
75
|
+
leftIcon="plus"
|
|
76
|
+
isActive
|
|
77
|
+
onClick={handleButtonClick}
|
|
78
|
+
>
|
|
79
|
+
New {capitalizeTextWithFallback(displayName, "Item")}
|
|
80
|
+
</Button>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export default ActivityCardExample;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/* Activity Card Example Component Styles */
|
|
2
|
+
.activityCardExample {
|
|
3
|
+
margin-top: 16px;
|
|
4
|
+
margin-bottom: 24px;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.title {
|
|
8
|
+
margin-bottom: 8px;
|
|
9
|
+
font-weight: 600;
|
|
10
|
+
color: var(--active-text);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.activityCard {
|
|
14
|
+
display: flex;
|
|
15
|
+
align-items: flex-start;
|
|
16
|
+
gap: 12px;
|
|
17
|
+
padding: 16px;
|
|
18
|
+
background: var(--bg-white);
|
|
19
|
+
border: 1px solid var(--linegrey);
|
|
20
|
+
border-radius: 8px;
|
|
21
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
|
22
|
+
transition: box-shadow 0.2s ease;
|
|
23
|
+
margin-top: 12px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.activityCard:hover {
|
|
27
|
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* Profile Picture */
|
|
31
|
+
.profilePic {
|
|
32
|
+
width: 34px;
|
|
33
|
+
height: 34px;
|
|
34
|
+
display: flex;
|
|
35
|
+
align-items: center;
|
|
36
|
+
justify-content: center;
|
|
37
|
+
background: var(--bg-bluegrey);
|
|
38
|
+
border-radius: 50%;
|
|
39
|
+
flex-shrink: 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.avatarIcon {
|
|
43
|
+
font-size: 20px;
|
|
44
|
+
color: var(--text-light);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* Activity Content */
|
|
48
|
+
.activityContent {
|
|
49
|
+
flex: 1;
|
|
50
|
+
min-width: 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.activityText {
|
|
54
|
+
font-size: 14px;
|
|
55
|
+
line-height: 1.4;
|
|
56
|
+
color: var(--active-text);
|
|
57
|
+
margin-bottom: 4px;
|
|
58
|
+
word-wrap: break-word;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.highlightedUser {
|
|
62
|
+
font-weight: 600;
|
|
63
|
+
color: var(--colour-blue);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.highlightedFeature {
|
|
67
|
+
font-weight: 600;
|
|
68
|
+
color: var(--colour-blue);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.exampleTitle {
|
|
72
|
+
color: var(--text-mid);
|
|
73
|
+
font-style: italic;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* Activity Time */
|
|
77
|
+
.activityTime {
|
|
78
|
+
display: flex;
|
|
79
|
+
align-items: center;
|
|
80
|
+
gap: 4px;
|
|
81
|
+
font-size: 12px;
|
|
82
|
+
color: var(--text-light);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.timeIcon {
|
|
86
|
+
font-size: 10px;
|
|
87
|
+
color: var(--text-light);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* Additional Info */
|
|
91
|
+
.additionalInfo {
|
|
92
|
+
margin-bottom: 8px;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.infoText {
|
|
96
|
+
font-size: 11px;
|
|
97
|
+
color: var(--text-light);
|
|
98
|
+
line-height: 1.3;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* Primary Add Button */
|
|
102
|
+
.buttonContainer {
|
|
103
|
+
margin-top: 20px;
|
|
104
|
+
display: flex;
|
|
105
|
+
justify-content: flex-start;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* Responsive design */
|
|
109
|
+
@media (max-width: 768px) {
|
|
110
|
+
.activityCard {
|
|
111
|
+
padding: 12px;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.profilePic {
|
|
115
|
+
width: 30px;
|
|
116
|
+
height: 30px;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.avatarIcon {
|
|
120
|
+
font-size: 18px;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.activityText {
|
|
124
|
+
font-size: 13px;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.activityTime {
|
|
128
|
+
font-size: 11px;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
3
|
+
import { faSync } from "@fortawesome/free-solid-svg-icons";
|
|
4
|
+
import styles from "./BackgroundLoader.module.css";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Background Loader component for overlay loading states
|
|
8
|
+
* Displays a spinning icon with text overlay for async operations
|
|
9
|
+
* Conditionally renders based on show prop
|
|
10
|
+
*
|
|
11
|
+
* @param {Object} props - Component props
|
|
12
|
+
* @param {string} [props.text="Updating..."] - Text to display alongside spinner
|
|
13
|
+
* @param {boolean} [props.show=true] - Whether the loader should be visible
|
|
14
|
+
* @returns {React.ReactElement|null} Background loader overlay or null if hidden
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* <BackgroundLoader text="Saving data..." show={isLoading} />
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* <BackgroundLoader />
|
|
21
|
+
*/
|
|
22
|
+
const BackgroundLoader = ({ text = "Updating...", show = true }) => {
|
|
23
|
+
if (!show) return null;
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div className={styles.backgroundLoader}>
|
|
27
|
+
<FontAwesomeIcon icon={faSync} spin className={styles.icon} />
|
|
28
|
+
<span className={styles.text}>{text}</span>
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export default BackgroundLoader;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
.backgroundLoader {
|
|
2
|
+
display: flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
gap: 0.5rem;
|
|
5
|
+
padding: 0.5rem 1rem;
|
|
6
|
+
background: rgba(255, 255, 255, 0.95);
|
|
7
|
+
border: 1px solid #e9ecef;
|
|
8
|
+
border-radius: 20px;
|
|
9
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
10
|
+
font-size: 0.875rem;
|
|
11
|
+
color: var(--text-bluegrey, #6c7a90);
|
|
12
|
+
position: fixed;
|
|
13
|
+
top: 1rem;
|
|
14
|
+
right: 1rem;
|
|
15
|
+
z-index: 1000;
|
|
16
|
+
animation: slideIn 0.3s ease-out;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.icon {
|
|
20
|
+
color: var(--colour-branding-main, #4a57b7);
|
|
21
|
+
font-size: 0.875rem;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.text {
|
|
25
|
+
font-weight: 500;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@keyframes slideIn {
|
|
29
|
+
from {
|
|
30
|
+
opacity: 0;
|
|
31
|
+
transform: translateX(20px);
|
|
32
|
+
}
|
|
33
|
+
to {
|
|
34
|
+
opacity: 1;
|
|
35
|
+
transform: translateX(0);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@media (max-width: 768px) {
|
|
40
|
+
.backgroundLoader {
|
|
41
|
+
top: auto;
|
|
42
|
+
bottom: 1rem;
|
|
43
|
+
right: 1rem;
|
|
44
|
+
font-size: 0.75rem;
|
|
45
|
+
}
|
|
46
|
+
}
|