@plusscommunities/pluss-feature-builder-web-a 1.0.2-beta.0 → 1.0.2-beta.2
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 +574 -580
- package/package.json +1 -1
- package/src/actions/wizardActions.js +5 -2
- package/src/screens/FormOverviewStep.jsx +0 -1
package/dist/index.cjs.js
CHANGED
|
@@ -2117,438 +2117,16 @@ const Text$8 = Components$4.Text;
|
|
|
2117
2117
|
|
|
2118
2118
|
function ownKeys$8(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
2119
2119
|
function _objectSpread$8(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$8(Object(t), !0).forEach(function (r) { _defineProperty__default["default"](e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$8(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
2120
|
-
const SET_NAVIGATION_STATE$1 = "SET_NAVIGATION_STATE";
|
|
2121
|
-
const UPDATE_STEP_VALIDATION$1 = "UPDATE_STEP_VALIDATION";
|
|
2122
|
-
const MARK_STEP_COMPLETE$1 = "MARK_STEP_COMPLETE";
|
|
2123
|
-
const setCurrentStep = function (step) {
|
|
2124
|
-
let previousStep = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
|
|
2125
|
-
return {
|
|
2126
|
-
type: SET_NAVIGATION_STATE$1,
|
|
2127
|
-
payload: {
|
|
2128
|
-
currentStep: step,
|
|
2129
|
-
previousStep,
|
|
2130
|
-
canGoBack: step !== "welcome",
|
|
2131
|
-
canGoForward: true
|
|
2132
|
-
}
|
|
2133
|
-
};
|
|
2134
|
-
};
|
|
2135
|
-
const goToStep = step => (dispatch, getState) => {
|
|
2136
|
-
const state = getState()[require("../values.config").reducerKey];
|
|
2137
|
-
const currentStep = state && state.wizard && state.wizard.navigation && state.wizard.navigation.currentStep;
|
|
2138
|
-
|
|
2139
|
-
// Clear form submission state when changing steps
|
|
2140
|
-
const {
|
|
2141
|
-
clearFormSubmissionState
|
|
2142
|
-
} = require("./formActions");
|
|
2143
|
-
dispatch(clearFormSubmissionState());
|
|
2144
|
-
dispatch(setCurrentStep(step, currentStep));
|
|
2145
|
-
};
|
|
2146
|
-
const updateStepValidation = function (step, isValid) {
|
|
2147
|
-
let errors = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
2148
|
-
return {
|
|
2149
|
-
type: UPDATE_STEP_VALIDATION$1,
|
|
2150
|
-
payload: {
|
|
2151
|
-
step,
|
|
2152
|
-
isValid,
|
|
2153
|
-
errors
|
|
2154
|
-
}
|
|
2155
|
-
};
|
|
2156
|
-
};
|
|
2157
|
-
const validateAndUpdateStep = step => (dispatch, getState) => {
|
|
2158
|
-
// Use existing selectors to get form data
|
|
2159
|
-
const state = getState();
|
|
2160
|
-
const title = selectFormTitle(state);
|
|
2161
|
-
const icon = selectFormIcon(state);
|
|
2162
|
-
const displayName = selectFormDisplayName(state);
|
|
2163
|
-
const layout = selectFormLayout(state);
|
|
2164
|
-
const fields = selectFormFields(state);
|
|
2165
|
-
const form = {
|
|
2166
|
-
title,
|
|
2167
|
-
icon,
|
|
2168
|
-
displayName,
|
|
2169
|
-
layout,
|
|
2170
|
-
fields
|
|
2171
|
-
};
|
|
2172
|
-
const {
|
|
2173
|
-
isValid,
|
|
2174
|
-
errors
|
|
2175
|
-
} = getFormValidation(form, step);
|
|
2176
|
-
dispatch(updateStepValidation(step, isValid, errors));
|
|
2177
|
-
|
|
2178
|
-
// If valid, mark as complete
|
|
2179
|
-
if (isValid) {
|
|
2180
|
-
dispatch({
|
|
2181
|
-
type: MARK_STEP_COMPLETE$1,
|
|
2182
|
-
payload: step
|
|
2183
|
-
});
|
|
2184
|
-
}
|
|
2185
|
-
return {
|
|
2186
|
-
isValid,
|
|
2187
|
-
errors
|
|
2188
|
-
};
|
|
2189
|
-
};
|
|
2190
|
-
const getFormValidation = (form, step) => {
|
|
2191
|
-
// Return validation results for undefined form (prevent crashes)
|
|
2192
|
-
if (!form) {
|
|
2193
|
-
switch (step) {
|
|
2194
|
-
case "overview":
|
|
2195
|
-
return {
|
|
2196
|
-
isValid: false,
|
|
2197
|
-
errors: {
|
|
2198
|
-
title: "Title is required",
|
|
2199
|
-
displayName: "Display name is required",
|
|
2200
|
-
icon: "Icon is required"
|
|
2201
|
-
}
|
|
2202
|
-
};
|
|
2203
|
-
case "fields":
|
|
2204
|
-
return {
|
|
2205
|
-
isValid: false,
|
|
2206
|
-
errors: {
|
|
2207
|
-
missingTitle: "Title field is required",
|
|
2208
|
-
missingImage: "Feature image field is required",
|
|
2209
|
-
fieldLabels: "Some fields are missing labels"
|
|
2210
|
-
}
|
|
2211
|
-
};
|
|
2212
|
-
case "layout":
|
|
2213
|
-
return {
|
|
2214
|
-
isValid: false,
|
|
2215
|
-
errors: {
|
|
2216
|
-
layoutType: "Layout type is required"
|
|
2217
|
-
}
|
|
2218
|
-
};
|
|
2219
|
-
default:
|
|
2220
|
-
return {
|
|
2221
|
-
isValid: false,
|
|
2222
|
-
errors: {}
|
|
2223
|
-
};
|
|
2224
|
-
}
|
|
2225
|
-
}
|
|
2226
|
-
switch (step) {
|
|
2227
|
-
case "overview":
|
|
2228
|
-
{
|
|
2229
|
-
const hasTitle = form.title && form.title.trim().length > 0;
|
|
2230
|
-
const hasDisplayName = form.displayName && form.displayName.trim().length > 0;
|
|
2231
|
-
const hasIcon = form.icon && form.icon.length > 0;
|
|
2232
|
-
return {
|
|
2233
|
-
isValid: hasTitle && hasDisplayName && hasIcon,
|
|
2234
|
-
errors: {
|
|
2235
|
-
title: !hasTitle ? "Title is required" : null,
|
|
2236
|
-
displayName: !hasDisplayName ? "Display name is required" : null,
|
|
2237
|
-
icon: !hasIcon ? "Icon is required" : null
|
|
2238
|
-
}
|
|
2239
|
-
};
|
|
2240
|
-
}
|
|
2241
|
-
case "fields":
|
|
2242
|
-
{
|
|
2243
|
-
const hasTitleField = form.fields && form.fields.some(field => field.id === "mandatory-title");
|
|
2244
|
-
const hasImageField = form.fields && form.fields.some(field => field.id === "mandatory-feature-image");
|
|
2245
|
-
|
|
2246
|
-
// Check each field for missing labels and create field-specific errors
|
|
2247
|
-
const fieldErrors = {};
|
|
2248
|
-
let allFieldsHaveLabels = true;
|
|
2249
|
-
if (form.fields) {
|
|
2250
|
-
form.fields.forEach(field => {
|
|
2251
|
-
if ((field.type === "text" || field.type === "description" || field.type === "title" || field.type === "image" || field.type === "gallery" || field.type === "feature-image" || field.type === "file" || field.type === "cta") && field.values) {
|
|
2252
|
-
if (!field.values.label || field.values.label.trim().length === 0) {
|
|
2253
|
-
fieldErrors[field.id] = "Field label is required";
|
|
2254
|
-
allFieldsHaveLabels = false;
|
|
2255
|
-
}
|
|
2256
|
-
}
|
|
2257
|
-
});
|
|
2258
|
-
}
|
|
2259
|
-
return {
|
|
2260
|
-
isValid: hasTitleField && hasImageField && allFieldsHaveLabels,
|
|
2261
|
-
errors: _objectSpread$8({
|
|
2262
|
-
missingTitle: !hasTitleField ? "Title field is required" : null,
|
|
2263
|
-
missingImage: !hasImageField ? "Feature image field is required" : null
|
|
2264
|
-
}, fieldErrors)
|
|
2265
|
-
};
|
|
2266
|
-
}
|
|
2267
|
-
case "layout":
|
|
2268
|
-
{
|
|
2269
|
-
const hasLayoutType = form.layout && form.layout.type && form.layout.type.length > 0;
|
|
2270
|
-
return {
|
|
2271
|
-
isValid: hasLayoutType,
|
|
2272
|
-
errors: {
|
|
2273
|
-
layoutType: !hasLayoutType ? "Layout type is required" : null
|
|
2274
|
-
}
|
|
2275
|
-
};
|
|
2276
|
-
}
|
|
2277
|
-
default:
|
|
2278
|
-
return {
|
|
2279
|
-
isValid: true,
|
|
2280
|
-
errors: {}
|
|
2281
|
-
};
|
|
2282
|
-
}
|
|
2283
|
-
};
|
|
2284
|
-
const setCurrentStepAndSave = function (step) {
|
|
2285
|
-
let previousStep = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
|
|
2286
|
-
return dispatch => {
|
|
2287
|
-
dispatch(setCurrentStep(step, previousStep));
|
|
2288
|
-
};
|
|
2289
|
-
};
|
|
2290
|
-
|
|
2291
|
-
var css$d = ".fullWidthContent {\n\tmax-width: 100%;\n\tmargin-left: auto;\n\tmargin-right: auto;\n\tpadding: 2rem 2rem 3rem 2rem; /* Add extra bottom padding */\n}\n\n/* Full-width container that allows scrollbar at edge */\n.fullWidthContainer {\n\tdisplay: flex;\n\tflex-direction: column;\n\tflex: 1;\n\tmin-height: 0; /* Allow content to determine height */\n\twidth: 100%; /* Take full width to ensure scrollbar is at edge */\n}\n\n/* Content container to keep content centered */\n.contentContainer {\n\tdisplay: flex;\n\tflex-direction: column;\n\tflex: 1;\n\tmin-height: 0;\n\tmax-width: 960px;\n\tmargin: 0 auto;\n\tpadding: 64px 32px;\n\twidth: 100%;\n\tbox-sizing: border-box;\n}\n\n/* Legacy container class for backward compatibility */\n.container{\n\tdisplay: flex;\n\tflex-direction: column;\n\tflex: 1;\n\tmin-height: 0; /* Allow content to determine height */\n max-width: 960px;\n margin: 0 auto;\n padding: 64px 32px;\n}\n\n/* Responsive adjustments for content container */\n@media (max-width: 768px) {\n\t.contentContainer {\n\t\tpadding: 1.5rem 1.5rem 2.5rem 1.5rem; /* Slightly reduced but still adequate */\n\t}\n\t.container {\n\t\tpadding: 1.5rem 1.5rem 2.5rem 1.5rem; /* Legacy container support */\n\t}\n}\n\n@media (max-width: 480px) {\n\t.contentContainer {\n\t\tpadding: 1rem 1rem 2rem 1rem; /* Maintain padding on small screens */\n\t}\n\t.container {\n\t\tpadding: 1rem 1rem 2rem 1rem; /* Legacy container support */\n\t}\n}\n\n/* Enhanced sidebar navigation for progress indication */\n:global(.hub-sideBar) {\n\t/* Make sidebar more prominent during creation mode */\n}\n\n:global(.sideNav-item.isCurrent) {\n\tbackground: var(--colour-purple, #6e79c5) !important;\n\tcolor: var(--colour-white, #ffffff) !important;\n\tfont-weight: 600 !important;\n\tborder-left: 4px solid var(--colour-branding-dark, #364196) !important;\n}\n\n:global(.sideNav-item.isCurrent) i {\n\tcolor: var(--colour-white, #ffffff) !important;\n}\n\n:global(.sideNav-item.isCompleted) {\n\tcolor: var(--colour-branding-dark, #364196) !important;\n\tposition: relative;\n}\n\n:global(.sideNav-item.isCompleted::after) {\n\tcontent: \"✓\";\n\tposition: absolute;\n\tright: 1rem;\n\ttop: 50%;\n\ttransform: translateY(-50%);\n\tcolor: var(--colour-branding-dark, #364196);\n\tfont-weight: bold;\n}\n\n:global(.sideNav-item.isDisabled) {\n\topacity: 0.4;\n\tcursor: not-allowed;\n}\n\n/* Enhanced progress section */\n:global(.hub-sideBar-section) {\n\tborder-bottom: 2px solid var(--colour-branding-main-fade, rgba(74, 87, 183, 0.15));\n\tpadding-bottom: 1rem;\n\tmargin-bottom: 1rem;\n}\n\n:global(.hub-sideBar-section:last-child) {\n\tborder-bottom: none;\n}\n\n/* Delete button styling - bold red for development */\n.deleteButton {\n\tcolor: #dc3545 !important;\n\tfont-weight: bold !important;\n}\n\n:global(.sideNav-item.deleteButton) {\n\tcolor: #dc3545 !important;\n\tfont-weight: bold !important;\n}\n\n:global(.sideNav-item.deleteButton:hover) {\n\tbackground-color: rgba(220, 53, 69, 0.1) !important;\n}\n\n:global(.sideNav-item.deleteButton i) {\n\tcolor: #dc3545 !important;\n}\n\n/* Development delete button */\n.devDeleteButton {\n\tposition: absolute;\n\tbottom: 20px;\n\tleft: 20px;\n\tbackground-color: #ff0000;\n\tcolor: #ffffff;\n\tborder: none;\n\tpadding: 8px 12px;\n\tborder-radius: 4px;\n\tcursor: pointer;\n\tfont-size: 12px;\n\tfont-weight: bold;\n\tz-index: 1000;\n\tbox-shadow: 0 2px 4px rgba(0,0,0,0.3);\n}\n\n.devDeleteButton:hover {\n\tbackground-color: #cc0000;\n}\n";
|
|
2292
|
-
n(css$d,{});
|
|
2293
|
-
|
|
2294
|
-
const {
|
|
2295
|
-
Helper: Helper$2,
|
|
2296
|
-
Session: Session$2
|
|
2297
|
-
} = PlussCore__namespace;
|
|
2298
|
-
const {
|
|
2299
|
-
getUrl: getUrl$2
|
|
2300
|
-
} = Helper$2;
|
|
2301
|
-
const {
|
|
2302
|
-
authedFunction: authedFunction$2
|
|
2303
|
-
} = Session$2;
|
|
2304
|
-
|
|
2305
|
-
/**
|
|
2306
|
-
* Sidebar Layout component for feature builder wizard
|
|
2307
|
-
* Provides navigation sidebar with step progression, completion tracking
|
|
2308
|
-
* Includes delete functionality for feature definitions
|
|
2309
|
-
* Manages step accessibility based on wizard mode and validation state
|
|
2310
|
-
*
|
|
2311
|
-
* @param {Object} props - Component props
|
|
2312
|
-
* @param {React.ReactNode} props.children - Child components to render in main content area
|
|
2313
|
-
* @param {Object} props.history - React Router history object for navigation
|
|
2314
|
-
* @returns {React.ReactElement} Layout component with sidebar navigation
|
|
2315
|
-
*
|
|
2316
|
-
* @example
|
|
2317
|
-
* <SidebarLayout history={historyObject}>
|
|
2318
|
-
* <YourMainContent />
|
|
2319
|
-
* </SidebarLayout>
|
|
2320
|
-
*/
|
|
2321
|
-
const SideBarInner = props => {
|
|
2322
|
-
const {
|
|
2323
|
-
children,
|
|
2324
|
-
history
|
|
2325
|
-
} = props;
|
|
2326
|
-
const dispatch = reactRedux.useDispatch();
|
|
2327
|
-
|
|
2328
|
-
// Get wizard state
|
|
2329
|
-
const mode = reactRedux.useSelector(selectWizardMode);
|
|
2330
|
-
const isEditMode = reactRedux.useSelector(selectIsEditMode);
|
|
2331
|
-
const isCreateMode = reactRedux.useSelector(selectIsCreateMode);
|
|
2332
|
-
const currentStep = reactRedux.useSelector(selectCurrentStep);
|
|
2333
|
-
reactRedux.useSelector(selectStepProgress);
|
|
2334
|
-
const definition = reactRedux.useSelector(selectDefinition);
|
|
2335
|
-
const definitionId = reactRedux.useSelector(selectDefinitionId);
|
|
2336
|
-
const auth = reactRedux.useSelector(state => state.auth);
|
|
2337
|
-
|
|
2338
|
-
// Delete functionality state
|
|
2339
|
-
const [showDeleteConfirm, setShowDeleteConfirm] = React.useState(false);
|
|
2340
|
-
const [isDeleting, setIsDeleting] = React.useState(false);
|
|
2341
|
-
const goTo = url => history.push(url);
|
|
2342
|
-
const isSelected = url => {
|
|
2343
|
-
return history.location.pathname === url;
|
|
2344
|
-
};
|
|
2345
|
-
|
|
2346
|
-
// Define step configuration with dynamic URL based on mode
|
|
2347
|
-
const getStepUrl = stepKey => {
|
|
2348
|
-
// Always use /definition/ in the URL since that's how routes are registered
|
|
2349
|
-
return "/feature-builder/definition/".concat(stepKey);
|
|
2350
|
-
};
|
|
2351
|
-
const steps = [{
|
|
2352
|
-
key: "overview",
|
|
2353
|
-
text: "Feature Overview",
|
|
2354
|
-
icon: "info-circle",
|
|
2355
|
-
url: getStepUrl("overview")
|
|
2356
|
-
}, {
|
|
2357
|
-
key: "fields",
|
|
2358
|
-
text: "Configure Fields",
|
|
2359
|
-
icon: "edit",
|
|
2360
|
-
url: getStepUrl("fields")
|
|
2361
|
-
}, {
|
|
2362
|
-
key: "layout",
|
|
2363
|
-
text: "Choose Layout",
|
|
2364
|
-
icon: "columns",
|
|
2365
|
-
url: getStepUrl("layout")
|
|
2366
|
-
}];
|
|
2367
|
-
|
|
2368
|
-
// Build sidebar items based on mode
|
|
2369
|
-
const buildSidebarItems = () => {
|
|
2370
|
-
return steps.map((step, index) => {
|
|
2371
|
-
const isCompleted = selectIsStepComplete(step.key);
|
|
2372
|
-
const isAccessible = selectIsStepAccessible(step.key);
|
|
2373
|
-
|
|
2374
|
-
// Add step number to text for better clarity
|
|
2375
|
-
const stepText = "".concat(index + 1, ". ").concat(step.text);
|
|
2376
|
-
const itemProps = {
|
|
2377
|
-
type: "navItem",
|
|
2378
|
-
text: stepText,
|
|
2379
|
-
icon: step.icon,
|
|
2380
|
-
selected: isSelected(step.url),
|
|
2381
|
-
onclick: isAccessible ? () => {
|
|
2382
|
-
goTo(step.url);
|
|
2383
|
-
dispatch(goToStep(step.key));
|
|
2384
|
-
} : null,
|
|
2385
|
-
isFontAwesome: true,
|
|
2386
|
-
// Enhanced completion indicator
|
|
2387
|
-
completed: isCompleted,
|
|
2388
|
-
// Allow navigation to completed steps even in create mode
|
|
2389
|
-
disabled: mode === "create" && !isAccessible
|
|
2390
|
-
};
|
|
2391
|
-
return itemProps;
|
|
2392
|
-
});
|
|
2393
|
-
};
|
|
2394
|
-
|
|
2395
|
-
// Determine sidebar title - always use "Build Your Feature" now
|
|
2396
|
-
const getSidebarTitle = () => {
|
|
2397
|
-
return "Build Your Feature";
|
|
2398
|
-
};
|
|
2399
|
-
|
|
2400
|
-
// Simple help text
|
|
2401
|
-
const getHelpText = () => {
|
|
2402
|
-
return "Get help with feature builder";
|
|
2403
|
-
};
|
|
2404
|
-
|
|
2405
|
-
// Build sidebar sections - simplified without progress section
|
|
2406
|
-
const sidebarSections = [{
|
|
2407
|
-
title: getSidebarTitle(),
|
|
2408
|
-
items: buildSidebarItems()
|
|
2409
|
-
}];
|
|
2410
|
-
|
|
2411
|
-
// Delete functionality handlers
|
|
2412
|
-
const handleDeleteClick = () => {
|
|
2413
|
-
setShowDeleteConfirm(true);
|
|
2414
|
-
};
|
|
2415
|
-
const handleConfirmDelete = async () => {
|
|
2416
|
-
if (!definition) return;
|
|
2417
|
-
setIsDeleting(true);
|
|
2418
|
-
try {
|
|
2419
|
-
await authedFunction$2({
|
|
2420
|
-
method: "POST",
|
|
2421
|
-
url: getUrl$2("feature-builder", "definition/update/delete"),
|
|
2422
|
-
data: {
|
|
2423
|
-
id: definitionId,
|
|
2424
|
-
site: auth.site
|
|
2425
|
-
}
|
|
2426
|
-
});
|
|
2427
|
-
|
|
2428
|
-
// Redirect to welcome screen after deletion
|
|
2429
|
-
history.push("/feature-builder/definition/overview");
|
|
2430
|
-
} finally {
|
|
2431
|
-
setIsDeleting(false);
|
|
2432
|
-
setShowDeleteConfirm(false);
|
|
2433
|
-
}
|
|
2434
|
-
};
|
|
2435
|
-
const handleCancelDelete = () => {
|
|
2436
|
-
setShowDeleteConfirm(false);
|
|
2437
|
-
};
|
|
2438
|
-
|
|
2439
|
-
// Add effect to manually attach click handlers since HubSidebar might not use onclick properly
|
|
2440
|
-
React.useEffect(() => {
|
|
2441
|
-
const attachClickHandlers = () => {
|
|
2442
|
-
const stepsWithUrls = [{
|
|
2443
|
-
key: "overview",
|
|
2444
|
-
url: getStepUrl("overview")
|
|
2445
|
-
}, {
|
|
2446
|
-
key: "fields",
|
|
2447
|
-
url: getStepUrl("fields")
|
|
2448
|
-
}, {
|
|
2449
|
-
key: "layout",
|
|
2450
|
-
url: getStepUrl("layout")
|
|
2451
|
-
}];
|
|
2452
|
-
stepsWithUrls.forEach(step => {
|
|
2453
|
-
const navItem = Array.from(document.querySelectorAll(".sideNav-item")).find(item => item.textContent && item.textContent.includes(step.key === "overview" ? "Feature Overview" : step.key === "fields" ? "Configure Fields" : "Choose Layout"));
|
|
2454
|
-
if (navItem) {
|
|
2455
|
-
// Remove any existing click listeners
|
|
2456
|
-
navItem.onclick = null;
|
|
2457
|
-
|
|
2458
|
-
// Check if this step is accessible
|
|
2459
|
-
const isAccessible = selectIsStepAccessible(step.key)({
|
|
2460
|
-
[values.reducerKey]: {
|
|
2461
|
-
wizard: {
|
|
2462
|
-
mode: mode,
|
|
2463
|
-
navigation: {
|
|
2464
|
-
currentStep: currentStep
|
|
2465
|
-
}
|
|
2466
|
-
}
|
|
2467
|
-
}
|
|
2468
|
-
});
|
|
2469
|
-
|
|
2470
|
-
// Add our click handler only if accessible
|
|
2471
|
-
if (isAccessible) {
|
|
2472
|
-
navItem.onclick = event => {
|
|
2473
|
-
event.preventDefault();
|
|
2474
|
-
event.stopPropagation();
|
|
2475
|
-
history.push(step.url);
|
|
2476
|
-
dispatch(goToStep(step.key));
|
|
2477
|
-
};
|
|
2478
|
-
}
|
|
2479
|
-
|
|
2480
|
-
// Make sure it's styled appropriately
|
|
2481
|
-
navItem.style.cursor = isAccessible ? "pointer" : "not-allowed";
|
|
2482
|
-
navItem.style.pointerEvents = "auto";
|
|
2483
|
-
navItem.style.opacity = isAccessible ? "1" : "0.5";
|
|
2484
|
-
}
|
|
2485
|
-
});
|
|
2486
|
-
};
|
|
2487
|
-
|
|
2488
|
-
// Initial attachment
|
|
2489
|
-
attachClickHandlers();
|
|
2490
|
-
|
|
2491
|
-
// Re-attach after a short delay to ensure HubSidebar has rendered
|
|
2492
|
-
const timeoutId = setTimeout(attachClickHandlers, 100);
|
|
2493
|
-
|
|
2494
|
-
// Also try to re-attach when DOM changes (observe for mutations)
|
|
2495
|
-
const observer = new MutationObserver(() => {
|
|
2496
|
-
setTimeout(attachClickHandlers, 50);
|
|
2497
|
-
});
|
|
2498
|
-
|
|
2499
|
-
// Start observing the document body for changes
|
|
2500
|
-
observer.observe(document.body, {
|
|
2501
|
-
childList: true,
|
|
2502
|
-
subtree: true
|
|
2503
|
-
});
|
|
2504
|
-
return () => {
|
|
2505
|
-
clearTimeout(timeoutId);
|
|
2506
|
-
observer.disconnect();
|
|
2507
|
-
};
|
|
2508
|
-
}, [history, dispatch, isEditMode, isCreateMode, currentStep]);
|
|
2509
|
-
return /*#__PURE__*/React__default["default"].createElement("div", {
|
|
2510
|
-
className: "hub-wrapperContainer"
|
|
2511
|
-
}, /*#__PURE__*/React__default["default"].createElement(HubSidebar, {
|
|
2512
|
-
sections: sidebarSections,
|
|
2513
|
-
helpGuide: {
|
|
2514
|
-
text: getHelpText(),
|
|
2515
|
-
url: "https://www.plusscommunities.com/user-guide"
|
|
2516
|
-
}
|
|
2517
|
-
}), /*#__PURE__*/React__default["default"].createElement("button", {
|
|
2518
|
-
onClick: handleDeleteClick,
|
|
2519
|
-
className: css$d.devDeleteButton
|
|
2520
|
-
}, "DELETE FEATURE DEF"), /*#__PURE__*/React__default["default"].createElement("div", {
|
|
2521
|
-
className: "hub-contentWrapper"
|
|
2522
|
-
}, /*#__PURE__*/React__default["default"].createElement("div", {
|
|
2523
|
-
className: css$d.fullWidthContainer
|
|
2524
|
-
}, /*#__PURE__*/React__default["default"].createElement("div", {
|
|
2525
|
-
className: css$d.contentContainer
|
|
2526
|
-
}, children))), /*#__PURE__*/React__default["default"].createElement(DeleteConfirmationPopup, {
|
|
2527
|
-
isOpen: showDeleteConfirm,
|
|
2528
|
-
listing: definition,
|
|
2529
|
-
onConfirm: handleConfirmDelete,
|
|
2530
|
-
onCancel: handleCancelDelete,
|
|
2531
|
-
isDeleting: isDeleting,
|
|
2532
|
-
deleteType: "featureDefinition"
|
|
2533
|
-
}));
|
|
2534
|
-
};
|
|
2535
|
-
const SidebarLayout = reactRouter.withRouter(SideBarInner);
|
|
2536
|
-
|
|
2537
|
-
var css$c = ".content {\n\tflex: 1 1 auto;\n\tdisplay: flex;\n\tflex-flow: column nowrap;\n\tpadding: 2rem 0 3rem 0; /* Add proper top/bottom padding */\n\tmin-height: 100%; /* Ensure full height for proper spacing */\n}\n\n/* New page wrapper for centered container approach */\n.pageWrapper {\n\tpadding: 2rem 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tjustify-content: flex-start;\n}\n\n@media (min-width: 768px) {\n\t.pageWrapper {\n\t\tpadding: 3rem 0;\n\t}\n}\n\n/* Form container styling when using CenteredContainer */\n.formContainer {\n\tbackground-color: var(--bg-white);\n}\n\n/* Section styling */\n.section {\n\tmargin-bottom: 2rem;\n\t\tpadding: 0 0 2em;\n}\n\n.section--no-border {\n\t\tborder-bottom: none;\n}\n\n.subtitle {\n\tcolor: var(--text-bluegrey, #6c7a90);\n}\n\n.sectionHeader {\n\t\tdisplay: flex;\n\t\tjustify-content: space-between;\n}\n\n/* Add Field Button styling */\n.addFieldButton {\n\tbackground: var(--colour-branding-action, #5c90df);\n\tcolor: white;\n\tborder: none;\n\tpadding: 0.75rem 1.5rem;\n\tborder-radius: 6px;\n\tfont-size: 1rem;\n\tcursor: pointer;\n\tmargin-bottom: 2rem;\n\ttransition: all 0.2s ease;\n}\n\n.addFieldButton:hover {\n\tbackground: var(--colour-branding-action-hover, #364196);\n\ttransform: translateY(-1px);\n\tbox-shadow: 0 2px 4px rgba(92, 144, 223, 0.3);\n}\n\n.addFieldButton:focus {\n\toutline: 2px solid var(--colour-branding-action, #5c90df);\n\toutline-offset: 2px;\n}\n\n.addFieldButton:active {\n\ttransform: translateY(0);\n}\n\n/* Refresh button styling */\n.refreshButton {\n\tmargin-left: 10px;\n\tpadding: 2px 8px;\n\tfont-size: 12px;\n\tbackground: var(--colour-dusk, #536280);\n\tcolor: white;\n\tborder: none;\n\tborder-radius: 4px;\n\tcursor: pointer;\n\ttransition: background-color 0.2s ease;\n}\n\n.refreshButton:hover {\n\tbackground: var(--colour-dusk-hover, #485968);\n}\n\n/* Error message container */\n.errorMessageContainer {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 0.5rem;\n}\n\n/* Add Field Container - matches field structure */\n.addFieldContainer {\n\tdisplay: flex;\n\tmargin-top: 32px;\n}\n\n.fieldNumberContainer {\n\tdisplay: flex;\n\twidth: 40px;\n}\n\n/* Add Field Section */\n.addFieldSection {\n\tdisplay: flex;\n\tgap: 1rem;\n\talign-items: center;\n\tjustify-content: flex-start; /* Align to start to match field content */\n\tmargin-top: 0; /* Remove top margin since it's in the container */\n}\n\n/* Desktop: horizontal layout for field selector and button */\n@media (min-width: 769px) {\n\t.addFieldSection {\n\t\tflex-direction: row;\n\t\talign-items: baseline;\n\t\tgap: 2rem;\n\t\talign-items: flex-start;\n\t\tjustify-content: flex-start; /* Align to start to match field content */\n\t}\n}\n\n/* Help Section for Popup */\n.helpSection {\n\tbackground-color: var(--colour-branding-action-superlight);\n\tborder: 1px solid var(--colour-branding-inactive);\n\tborder-radius: 8px;\n\tpadding: 1.5rem;\n\tmargin-bottom: 1.5rem;\n}\n\n.helpTitle {\n\tcolor: var(--text-dark);\n\tfont-weight: 600;\n\tmargin-bottom: 0.75rem;\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 0.5rem;\n\tfont-size: 1.25rem;\n}\n\n.helpText {\n\tcolor: var(--colour-branding-action);\n\tline-height: 1.6;\n\tfont-size: 1.25rem;\n}\n\n/* Field Type Cards for Popup */\n.fieldTypeCards {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 1rem; /* Spacing between vertical cards */\n\tpadding: 1rem 0;\n}\n\n/* Responsive layout: maintain vertical layout on all screen sizes */\n@media (min-width: 768px) {\n\t.fieldTypeCards {\n\t\tgap: 1.5rem; /* Slightly larger gap on desktop */\n\t}\n}\n\n.fieldTypeCard {\n\tdisplay: flex;\n\tflex-direction: row;\n\talign-items: flex-start;\n\tpadding: 1.75rem 1.25rem; /* More vertical padding */\n\tborder: 1px solid var(--border-line-grey);\n\tborder-radius: 8px;\n\tbackground-color: var(--bg-white);\n\tcursor: pointer;\n\ttransition: all 0.2s ease;\n\tbox-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);\n\tmin-height: 120px; /* Adjust height for horizontal layout */\n\tgap: 1rem; /* Space between icon and content */\n}\n\n.fieldTypeCard:hover {\n\tborder-color: var(--text-light);\n\tbackground-color: var(--bg-bluegrey);\n\tbox-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);\n}\n\n.fieldTypeCard:focus {\n\toutline: 2px solid var(--text-light);\n\toutline-offset: 2px;\n}\n\n.fieldTypeCard:active {\n\ttransform: scale(0.98);\n\ttransition: transform 0.1s ease;\n}\n\n.fieldTypeCardIcon {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 48px;\n\theight: 48px;\n\tmargin-bottom: 0; /* Remove bottom margin for horizontal layout */\n\tfont-size: 20px;\n\tflex-shrink: 0;\n\tborder: 1px solid var(--border-line-grey); /* Subtle border */\n\tborder-radius: 8px;\n\tbackground-color: var(--bg-bluegrey);\n\tpadding: 8px;\n}\n\n\n.fieldTypeCardContent {\n\tflex: 1;\n\twidth: 100%;\n\tmin-width: 0; /* Prevent content from overflowing */\n}\n\n.fieldTypeCardTitle {\n\tmargin: 0 0 0.25rem 0;\n\tfont-weight: 600;\n\tfont-size: 1.4rem; /* Increased for better readability */\n}\n\n.fieldTypeCardDescription {\n\tmargin: 0 0 0.75rem 0;\n\tfont-size: 1.6rem; /* Increased for better readability */\n\tline-height: 1.5; /* Improved line height for better readability */\n}\n\n.fieldTypeCardUseCase {\n\tmargin: 0;\n\tfont-size: 1.4rem;\n\tfont-style: italic;\n\tcolor: var(--text-light);\n\tpadding-top: 0.5rem;\n\tborder-top: 1px solid var(--border-line-grey);\n}\n\n/* Field selector and button container - horizontal layout */\n.addFieldSection> div:first-child {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 0.5rem;\n\tflex: 1;\n}\n\n/* Container for add field button alignment */\n.addFieldButtonContainer {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: flex-end;\n}\n\n/* Empty State */\n.emptyState {\n\tpadding: 2rem;\n\ttext-align: center;\n\tbackground-color: var(--bg-bluegrey, #f4f7f9);\n\tborder-radius: 8px;\n\tborder: 1px dashed var(--border-line-grey, #dbddf1);\n\tmargin-bottom: 1rem;\n}\n\n.emptyStateText {\n\tcolor: var(--text-bluegrey, #6c7a90);\n\tfont-style: italic;\n}\n\n.fieldTypeSelector {\n\tdisplay: flex;\n\tflex-direction: column;\n\tmax-width: 500px; /* Increased from 300px to accommodate longer text */\n\twidth: 100%;\n}\n\n@media (max-width: 768px) {\n\t.addFieldSection {\n\t\talign-items: stretch;\n\t}\n\t\n\t.fieldTypeSelector {\n\t\tmax-width: none;\n\t}\n}\n\n.grid__four {\n max-width: 1200px;\n\tdisplay: grid;\n\tgrid-template-columns: repeat(2, minmax(250px, 1fr));\n margin: 2rem 0;\n\tgap: 2rem;\n\tjustify-items: center;\n}\n\n@media (max-width: 768px) {\n\t.grid__four {\n\t\tgrid-template-columns: 1fr;\n\t\tgap: 2rem;\n\t}\n}\n\n/* Navigation styles */\n.navigation {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tgap: 1rem;\n\tmargin-top: 3rem; /* Increase from 2rem */\n\tpadding-top: 2rem;\n\tpadding-bottom: 2rem; /* Add bottom padding */\n\tborder-top: 1px solid #e9ecef;\n}\n\n/* Single button alignment - align to right for overview step save button */\n.navigation> :only-child {\n\tmargin-left: auto;\n\tmargin-right: 0;\n}\n\n/* Two button alignment - one left, one right */\n.navigation> :first-child:not(:only-child) {\n\tmargin-right: auto;\n}\n\n.navigation> :last-child:not(:only-child) {\n\tmargin-left: auto;\n}\n\n/* Special case for overview step - align save button to right */\n.overviewStep .navigation> :only-child {\n\tmargin-left: auto;\n\tmargin-right: 0;\n}\n\n.previousButton {\n\tbackground: #6c757d;\n\tcolor: white;\n\tborder: none;\n\tpadding: 0.75rem 1.5rem;\n\tborder-radius: 6px;\n\tfont-size: 1rem;\n\tcursor: pointer;\n\ttransition: all 0.2s ease;\n}\n\n.previousButton:hover {\n\tbackground: #5a6268;\n}\n\n.saveButton {\n\tbackground: var(--colour-purple, #6e79c5);\n\tcolor: white;\n\tborder: none;\n\tpadding: 0.75rem 2rem;\n\tborder-radius: 6px;\n\tfont-size: 1rem;\n\tfont-weight: 500;\n\tcursor: pointer;\n\ttransition: all 0.2s ease;\n}\n\n.saveButton:hover:not(:disabled) {\n\tbackground: var(--colour-purple-hover, #5a66b3);\n\ttransform: translateY(-1px);\n\tbox-shadow: 0 2px 4px rgba(110, 121, 197, 0.3);\n}\n\n.saveButton:focus {\n\toutline: 2px solid var(--colour-purple, #6e79c5);\n\toutline-offset: 2px;\n}\n\n.saveButton:disabled {\n\tbackground: #6c757d;\n\tcursor: not-allowed;\n\topacity: 0.65;\n\ttransform: none;\n\tbox-shadow: none;\n}\n\n.nextButton {\n\tbackground: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n\tcolor: white;\n\tborder: none;\n\tpadding: 0.75rem 2rem;\n\tborder-radius: 6px;\n\tfont-size: 1rem;\n\tfont-weight: 500;\n\tcursor: pointer;\n\ttransition: all 0.2s ease;\n}\n\n.nextButton:hover:not(:disabled) {\n\ttransform: translateY(-2px);\n\tbox-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);\n}\n\n.nextButton:disabled {\n\tbackground: #6c757d;\n\tcursor: not-allowed;\n\topacity: 0.65;\n\ttransform: none;\n\tbox-shadow: none;\n}\n\n\n\n.errorMessage {\n\tcolor: var(--colour-red);\n\tmargin: 0.25rem 0;\n\tpadding: 0.25rem 0;\n\tfont-size: 0.875rem;\n\tline-height: 1.4;\n}\n\n/* Layout option styles */\n.layoutOption {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\ttext-align: center;\n\tcursor: pointer;\n\ttransition: all 0.2s ease;\n\tbackground-color: transparent;\n}\n\n.layoutOptionImage {\n\twidth: 250px;\n\theight: 250px;\n\tborder-radius: 12px;\n\toverflow: hidden;\n\tmargin-bottom: 1rem;\n\tborder: 2px solid var(--border-line-grey, #dbddf1);\n\ttransition: all 0.2s ease;\n}\n\n.layoutOption.selected .layoutOptionImage {\n\tborder-color: var(--border-line-grey, #dbddf1);\n\tbox-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n}\n\n.layoutOptionImg {\n\twidth: 100%;\n\theight: 100%;\n\tobject-fit: cover;\n}\n\n.layoutOptionContent {\n\tmax-width: 250px;\n}\n\n.layoutOptionTitle {\n\tfont-size: 1.6rem;\n\tfont-weight: 600;\n\tmargin: 0 0 0.5rem 0;\n\tline-height: 1.3;\n\tcolor: #333;\n}\n\n.layoutOptionDescription {\n\tfont-size: 1.2rem;\n\tmargin: 0;\n\tline-height: 1.3;\n\tcolor: #6c757d;\n}\n\n.layoutOption.hasError .layoutOptionImage {\n\tborder-color: var(--colour-red, #dc3545);\n}\n\n/* Field error styles */\n.fieldError {\n\tcolor: #dc3545;\n\tfont-size: 1.6rem;\n\tfont-weight: 500;\n\tmargin-top: 0.5rem;\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 0.25rem;\n}\n\n.errorIcon {\n\tfont-size: 1rem;\n}\n\n/* Field wrapper styles are now handled in Fields.module.css */\n\n/* Field type indicator styles are now handled in Fields.module.css */\n\n@keyframes errorShake {\n\t0%, 100% { transform: translateX(0); }\n\t25% { transform: translateX(-3px); }\n\t75% { transform: translateX(3px); }\n}\n\n@keyframes errorPulse {\n\t0% { \n\t\tbox-shadow: 0 1px 3px rgba(192, 39, 67, 0.1), 0 1px 2px rgba(192, 39, 67, 0.08);\n\t}\n\t50% { \n\t\tbox-shadow: 0 2px 6px rgba(192, 39, 67, 0.2), 0 1px 3px rgba(192, 39, 67, 0.15);\n\t}\n\t100% { \n\t\tbox-shadow: 0 1px 3px rgba(192, 39, 67, 0.1), 0 1px 2px rgba(192, 39, 67, 0.08);\n\t}\n}\n\n.successMessage {\n\tbackground: var(--colour-branding-secondary-light);\n\tborder: 1px solid var(--colour-branding-secondary);\n\tborder-radius: 8px;\n\tpadding: 1rem;\n\tmargin-top: 1rem;\n\tcolor: var(--colour-green);\n\tfont-weight: 500;\n}\n\n/* Mode-aware styling */\n.createMode {\n\tcolor: var(--colour-purple, #6e79c5);\n}\n\n.editMode {\n\tcolor: var(--colour-branding-dark, #364196);\n}\n\n/* Responsive Design */\n@media (max-width: 768px) {\n\t.content {\n\t\tpadding: 1.5rem 0 2.5rem 0; /* Maintain proper padding on mobile */\n\t}\n\t\n\t.section {\n\t\tmargin-bottom: 1.5rem;\n\t}\n\t\n\t/* Mobile: vertical layout for field selector and button */\n\t.addFieldSection {\n\t\tflex-direction: column;\n\t\talign-items: stretch; /* Stretch to fill available space */\n\t}\n\t\n\t/* Button should not be full width on mobile */\n\t.addFieldSection Button {\n\t\twidth: auto; /* Let button use its natural width */\n\t\tpadding: 1rem 1.5rem; /* Keep proper padding but not full width */\n\t}\n\t\n\t.addFieldButtonContainer {\n\t\talign-items: stretch;\n\t\tjustify-content: stretch;\n\t\tmin-height: auto;\n\t}\n\t\n\t.navigation {\n\t\tflex-direction: column;\n\t\tgap: 0.75rem;\n\t\tjustify-content: flex-start; /* Align to start on mobile */\n\t\tmargin-top: 2rem; /* Slightly reduce on mobile */\n\t\tpadding-top: 1.5rem;\n\t\tpadding-bottom: 1.5rem; /* Maintain bottom padding */\n\t}\n\t\n\t/* Mobile button alignment - single button aligns right, two buttons stack */\n\t.navigation> :only-child {\n\t\tmargin-left: auto;\n\t\tmargin-right: 0;\n\t}\n\t\n\t.navigation> :first-child:not(:only-child),\n\t.navigation> :last-child:not(:only-child) {\n\t\tmargin-left: 0;\n\t\tmargin-right: 0;\n\t}\n\t\n\t.previousButton,\n\t.nextButton,\n\t.saveButton {\n\t\twidth: 100%;\n\t\tpadding: 1rem;\n\t}\n}\n\n@media (max-width: 480px) {\n\t.content {\n\t\tpadding: 1rem 0 2rem 0; /* Still maintain padding on small screens */\n\t}\n\t\n\t.section {\n\t\tmargin-bottom: 1rem;\n\t}\n\t\n\t.navigation {\n\t\tmargin-top: 1.5rem; /* Further reduce on very small screens */\n\t\tpadding-top: 1rem;\n\t\tpadding-bottom: 1.5rem;\n\t\tjustify-content: flex-start; /* Align to start on small screens */\n\t}\n\t\n\t/* Small screen button alignment */\n\t.navigation> :only-child {\n\t\tmargin-left: auto;\n\t\tmargin-right: 0;\n\t}\n\t\n\t.navigation> :first-child:not(:only-child),\n\t.navigation> :last-child:not(:only-child) {\n\t\tmargin-left: 0;\n\t\tmargin-right: 0;\n\t}\n\t\n\t.errorMessageContainer {\n\t\tflex-direction: column;\n\t\talign-items: flex-start;\n\t\tgap: 0.75rem;\n\t}\n\t\n\t.refreshButton {\n\t\talign-self: flex-end;\n\t}\n}\n\n/* Full width content when sidebar is hidden */\n:global(.hub-contentWrapper.fullWidthContent) {\n\tmax-width: 1000px;\n\tmargin: 0 auto;\n\tpadding: 2rem;\n}\n\n.hub-contentWrapper--col {\n flex-flow: column;\n}\n\n/* Validation error message - displayed at top of form */\n.validationErrorMessage {\n\tbackground-color: var(--colour-branding-secondary-light);\n\tborder: 1px solid var(--colour-branding-secondary);\n\tborder-radius: 6px;\n\tcolor: var(--colour-red);\n\tpadding: 12px 16px;\n\tfont-weight: 500;\n\tfont-size: 14px;\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\t\tmargin: 24px 32px;\n}\n\n.validationErrorMessage::before {\n\tcontent: \"⚠\";\n\tfont-size: 16px;\n\tcolor: var(--colour-red);\n}\n\n/* Loading overlay for forms - deprecated, use SkeletonLoader instead */\n/*\n.loadingOverlay {\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\tright: 0;\n\tbottom: 0;\n\tbackground: rgba(255, 255, 255, 0.95);\n\tbackdrop-filter: blur(2px);\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tz-index: 10;\n\tborder-radius: 8px;\n\tanimation: fadeIn 0.2s ease-in-out;\n}\n\n@keyframes fadeIn {\n\tfrom { opacity: 0; }\n\tto { opacity: 1; }\n}\n*/\n\n/* Form header with buttons */\n.formHeader {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\talign-items: flex-start;\n\tmargin-bottom: 16px;\n}\n\n/* Loading container for fields */\n.fieldsLoadingContainer {\n\ttext-align: center;\n\tpadding: 60px 20px;\n}\n\n/* Help note styling */\n.helpNote {\n\tfont-size: 13px;\n\tcolor: #6c757d;\n}\n\n/* Loading container for overview */\n.overviewLoadingContainer {\n\ttext-align: center;\n\tpadding: 60px 20px;\n}\n\n/* Form layout header */\n.formLayoutHeader {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\talign-items: flex-start;\n\tmargin-bottom: 16px;\n}\n\n/* Section margins */\n.gridIconSection {\n\tmargin-bottom: 3rem;\n}\n\n.layoutSection {\n\tmargin-bottom: 2rem;\n}\n\n/* Grid icon loading state */\n.gridIconLoading {\n\tgrid-column: 1 / -1;\n}\n\n/* Hide upload button in grid icon section */\n.gridIconSection :global(.iconLoader__buttonOverlay button:first-child) {\n\tdisplay: none !important;\n}\n";
|
|
2538
|
-
n(css$c,{});
|
|
2539
|
-
|
|
2540
|
-
function ownKeys$7(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
2541
|
-
function _objectSpread$7(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$7(Object(t), !0).forEach(function (r) { _defineProperty__default["default"](e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$7(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
2542
2120
|
const {
|
|
2543
|
-
Helper: Helper$
|
|
2544
|
-
Session: Session$
|
|
2121
|
+
Helper: Helper$2,
|
|
2122
|
+
Session: Session$2
|
|
2545
2123
|
} = PlussCore__namespace;
|
|
2546
2124
|
const {
|
|
2547
|
-
getUrl: getUrl$
|
|
2548
|
-
} = Helper$
|
|
2125
|
+
getUrl: getUrl$2
|
|
2126
|
+
} = Helper$2;
|
|
2549
2127
|
const {
|
|
2550
|
-
authedFunction: authedFunction$
|
|
2551
|
-
} = Session$
|
|
2128
|
+
authedFunction: authedFunction$2
|
|
2129
|
+
} = Session$2;
|
|
2552
2130
|
const featureDefinitionActions = {
|
|
2553
2131
|
/**
|
|
2554
2132
|
* Get the single feature definition by ID
|
|
@@ -2559,9 +2137,9 @@ const featureDefinitionActions = {
|
|
|
2559
2137
|
id,
|
|
2560
2138
|
site
|
|
2561
2139
|
};
|
|
2562
|
-
return authedFunction$
|
|
2140
|
+
return authedFunction$2({
|
|
2563
2141
|
method: "GET",
|
|
2564
|
-
url: getUrl$
|
|
2142
|
+
url: getUrl$2("feature-builder", "definition/get/single", query)
|
|
2565
2143
|
});
|
|
2566
2144
|
},
|
|
2567
2145
|
/**
|
|
@@ -2575,9 +2153,9 @@ const featureDefinitionActions = {
|
|
|
2575
2153
|
*
|
|
2576
2154
|
*/
|
|
2577
2155
|
create: async (id, site, featureDefinition) => {
|
|
2578
|
-
return authedFunction$
|
|
2156
|
+
return authedFunction$2({
|
|
2579
2157
|
method: "POST",
|
|
2580
|
-
url: getUrl$
|
|
2158
|
+
url: getUrl$2("feature-builder", "definition/update/create"),
|
|
2581
2159
|
data: {
|
|
2582
2160
|
id,
|
|
2583
2161
|
site,
|
|
@@ -2610,12 +2188,12 @@ const featureDefinitionActions = {
|
|
|
2610
2188
|
*/
|
|
2611
2189
|
edit: async (featureDefinitionData, site) => {
|
|
2612
2190
|
// Ensure site is included in the request body
|
|
2613
|
-
const dataWithSite = _objectSpread$
|
|
2191
|
+
const dataWithSite = _objectSpread$8({
|
|
2614
2192
|
site: site
|
|
2615
2193
|
}, featureDefinitionData);
|
|
2616
|
-
return authedFunction$
|
|
2194
|
+
return authedFunction$2({
|
|
2617
2195
|
method: "POST",
|
|
2618
|
-
url: getUrl$
|
|
2196
|
+
url: getUrl$2("feature-builder", "definition/update/edit"),
|
|
2619
2197
|
data: dataWithSite
|
|
2620
2198
|
});
|
|
2621
2199
|
},
|
|
@@ -2629,9 +2207,9 @@ const featureDefinitionActions = {
|
|
|
2629
2207
|
*
|
|
2630
2208
|
*/
|
|
2631
2209
|
delete: async (id, site) => {
|
|
2632
|
-
return authedFunction$
|
|
2210
|
+
return authedFunction$2({
|
|
2633
2211
|
method: "POST",
|
|
2634
|
-
url: getUrl$
|
|
2212
|
+
url: getUrl$2("feature-builder", "definition/update/delete"),
|
|
2635
2213
|
data: {
|
|
2636
2214
|
id,
|
|
2637
2215
|
site
|
|
@@ -2640,8 +2218,8 @@ const featureDefinitionActions = {
|
|
|
2640
2218
|
}
|
|
2641
2219
|
};
|
|
2642
2220
|
|
|
2643
|
-
function ownKeys$
|
|
2644
|
-
function _objectSpread$
|
|
2221
|
+
function ownKeys$7(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
2222
|
+
function _objectSpread$7(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$7(Object(t), !0).forEach(function (r) { _defineProperty__default["default"](e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$7(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
2645
2223
|
|
|
2646
2224
|
// IMPORTANT: Using local UPDATE_STRINGS action type to make extension self-contained
|
|
2647
2225
|
// The main app's StringsReducer will handle this action type the same way
|
|
@@ -2650,12 +2228,12 @@ const updateFeatureBuilderString = title => (dispatch, getState) => {
|
|
|
2650
2228
|
var _getState$strings;
|
|
2651
2229
|
const currentStrings = ((_getState$strings = getState().strings) === null || _getState$strings === void 0 ? void 0 : _getState$strings.config) || {};
|
|
2652
2230
|
const titleCased = toTitleCase(title) || values.textMenuTitle;
|
|
2653
|
-
const updatedStrings = _objectSpread$
|
|
2654
|
-
sideNav: _objectSpread$
|
|
2231
|
+
const updatedStrings = _objectSpread$7(_objectSpread$7({}, currentStrings), {}, {
|
|
2232
|
+
sideNav: _objectSpread$7(_objectSpread$7({}, currentStrings.sideNav), {}, {
|
|
2655
2233
|
[values.featureKey]: titleCased,
|
|
2656
2234
|
[values.menuKey]: "Manage ".concat(titleCased)
|
|
2657
2235
|
}),
|
|
2658
|
-
permission: _objectSpread$
|
|
2236
|
+
permission: _objectSpread$7(_objectSpread$7({}, currentStrings.permission), {}, {
|
|
2659
2237
|
[values.permissionFeatureBuilderDefinition]: "Manage custom feature ".concat(titleCased),
|
|
2660
2238
|
[values.permissionFeatureBuilderContent]: "Manage ".concat(titleCased, " content")
|
|
2661
2239
|
})
|
|
@@ -2668,8 +2246,8 @@ const updateFeatureBuilderString = title => (dispatch, getState) => {
|
|
|
2668
2246
|
const updateFeatureBuilderIcon = icon => (dispatch, getState) => {
|
|
2669
2247
|
var _getState$strings2;
|
|
2670
2248
|
const currentStrings = ((_getState$strings2 = getState().strings) === null || _getState$strings2 === void 0 ? void 0 : _getState$strings2.config) || {};
|
|
2671
|
-
const updatedStrings = _objectSpread$
|
|
2672
|
-
sideNav: _objectSpread$
|
|
2249
|
+
const updatedStrings = _objectSpread$7(_objectSpread$7({}, currentStrings), {}, {
|
|
2250
|
+
sideNav: _objectSpread$7(_objectSpread$7({}, currentStrings.sideNav), {}, {
|
|
2673
2251
|
[values.featureKey + "-icon"]: icon,
|
|
2674
2252
|
[values.menuKey + "-icon"]: icon
|
|
2675
2253
|
})
|
|
@@ -2825,167 +2403,586 @@ const addField = function () {
|
|
|
2825
2403
|
// Generate a unique ID for the new field
|
|
2826
2404
|
const fieldId = "custom-field-".concat(Date.now(), "-").concat(Math.random().toString(36).substr(2, 9));
|
|
2827
2405
|
return {
|
|
2828
|
-
type: actionsTypes.ADD_FIELD,
|
|
2406
|
+
type: actionsTypes.ADD_FIELD,
|
|
2407
|
+
payload: {
|
|
2408
|
+
id: fieldId,
|
|
2409
|
+
type: fieldType
|
|
2410
|
+
}
|
|
2411
|
+
};
|
|
2412
|
+
};
|
|
2413
|
+
const deleteField = id => {
|
|
2414
|
+
return {
|
|
2415
|
+
type: actionsTypes.DELETE_FIELD,
|
|
2416
|
+
payload: id
|
|
2417
|
+
};
|
|
2418
|
+
};
|
|
2419
|
+
const updateFieldById = (id, updatedField) => {
|
|
2420
|
+
return {
|
|
2421
|
+
type: actionsTypes.UPDATE_FIELD,
|
|
2422
|
+
payload: {
|
|
2423
|
+
id,
|
|
2424
|
+
updatedField
|
|
2425
|
+
}
|
|
2426
|
+
};
|
|
2427
|
+
};
|
|
2428
|
+
|
|
2429
|
+
/**
|
|
2430
|
+
* Action creator to set a description field as the summary field
|
|
2431
|
+
* Ensures only one description field can be marked as summary by
|
|
2432
|
+
* automatically unsetting all other description fields when a new one is selected
|
|
2433
|
+
*
|
|
2434
|
+
* @param {string} fieldId - ID of the description field to set as summary
|
|
2435
|
+
* @returns {Object} Redux action object for summary field selection
|
|
2436
|
+
*
|
|
2437
|
+
* @example
|
|
2438
|
+
* dispatch(setSummaryField('field-description-123'));
|
|
2439
|
+
*/
|
|
2440
|
+
const setSummaryField = fieldId => {
|
|
2441
|
+
return {
|
|
2442
|
+
type: actionsTypes.SET_SUMMARY_FIELD,
|
|
2443
|
+
payload: fieldId
|
|
2444
|
+
};
|
|
2445
|
+
};
|
|
2446
|
+
const setLayoutType = layoutType => {
|
|
2447
|
+
return {
|
|
2448
|
+
type: actionsTypes.SET_LAYOUT_TYPE,
|
|
2449
|
+
payload: layoutType
|
|
2450
|
+
};
|
|
2451
|
+
};
|
|
2452
|
+
const submitFormRequest = () => {
|
|
2453
|
+
return {
|
|
2454
|
+
type: actionsTypes.SUBMIT_FORM_REQUEST
|
|
2455
|
+
};
|
|
2456
|
+
};
|
|
2457
|
+
const submitFormSuccess = () => {
|
|
2458
|
+
return {
|
|
2459
|
+
type: actionsTypes.SUBMIT_FORM_SUCCESS
|
|
2460
|
+
};
|
|
2461
|
+
};
|
|
2462
|
+
const submitFormFailure = error => {
|
|
2463
|
+
return {
|
|
2464
|
+
type: actionsTypes.SUBMIT_FORM_FAILURE,
|
|
2465
|
+
payload: error
|
|
2466
|
+
};
|
|
2467
|
+
};
|
|
2468
|
+
const clearFormSubmissionState = () => {
|
|
2469
|
+
return {
|
|
2470
|
+
type: actionsTypes.CLEAR_FORM_SUBMISSION_STATE
|
|
2471
|
+
};
|
|
2472
|
+
};
|
|
2473
|
+
|
|
2474
|
+
/**
|
|
2475
|
+
* Submits the complete feature form to the server
|
|
2476
|
+
* Handles form validation, API submission, and error handling
|
|
2477
|
+
*
|
|
2478
|
+
* @returns {Function} Async thunk function for Redux
|
|
2479
|
+
* @throws {Error} When form validation fails or API submission encounters error
|
|
2480
|
+
*/
|
|
2481
|
+
function submitForm() {
|
|
2482
|
+
return async (dispatch, getState) => {
|
|
2483
|
+
const state = getState()[values.reducerKey];
|
|
2484
|
+
const form = state && state.form;
|
|
2485
|
+
if (!form) {
|
|
2486
|
+
dispatch(submitFormFailure(new Error("Form data is missing. Please refresh the page and try again.")));
|
|
2487
|
+
return;
|
|
2488
|
+
}
|
|
2489
|
+
dispatch(submitFormRequest());
|
|
2490
|
+
try {
|
|
2491
|
+
// Get site from auth store
|
|
2492
|
+
const site = getState().auth.site;
|
|
2493
|
+
if (!site) {
|
|
2494
|
+
throw new Error("Authentication error: Site context not found. Please refresh and login again.");
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
// Use mode from fetch instead of checking definition ID
|
|
2498
|
+
const definitionState = state && state.definition;
|
|
2499
|
+
const mode = definitionState && definitionState.mode; // Use stored mode from fetch
|
|
2500
|
+
const definitionId = definitionState && definitionState.id;
|
|
2501
|
+
if (mode === "edit") {
|
|
2502
|
+
// Always update when in edit mode
|
|
2503
|
+
const updatedDefinition = {
|
|
2504
|
+
id: definitionId,
|
|
2505
|
+
site: site,
|
|
2506
|
+
// Include site from auth store
|
|
2507
|
+
featureDefinition: {
|
|
2508
|
+
// Wrap in expected structure for backend
|
|
2509
|
+
title: form.title,
|
|
2510
|
+
icon: form.icon,
|
|
2511
|
+
displayName: form.displayName,
|
|
2512
|
+
layout: form.layout,
|
|
2513
|
+
fields: form.fields
|
|
2514
|
+
}
|
|
2515
|
+
};
|
|
2516
|
+
await featureDefinitionActions.edit(updatedDefinition, site);
|
|
2517
|
+
} else {
|
|
2518
|
+
// Always create when in create mode (or mode is undefined/null)
|
|
2519
|
+
if (!values.featureId || !site) {
|
|
2520
|
+
throw new Error("Authentication error: Missing required context (featureId or site).");
|
|
2521
|
+
}
|
|
2522
|
+
await featureDefinitionActions.create(values.featureId, site,
|
|
2523
|
+
// Use actual site from auth store
|
|
2524
|
+
{
|
|
2525
|
+
title: form.title,
|
|
2526
|
+
icon: form.icon,
|
|
2527
|
+
displayName: form.displayName,
|
|
2528
|
+
layout: form.layout,
|
|
2529
|
+
fields: form.fields
|
|
2530
|
+
});
|
|
2531
|
+
}
|
|
2532
|
+
dispatch(submitFormSuccess());
|
|
2533
|
+
} catch (err) {
|
|
2534
|
+
// Handle different types of errors
|
|
2535
|
+
let errorToDisplay = err;
|
|
2536
|
+
if (err.response) {
|
|
2537
|
+
// API error (400, 401, 404, 500, etc.)
|
|
2538
|
+
const {
|
|
2539
|
+
status,
|
|
2540
|
+
data
|
|
2541
|
+
} = err.response;
|
|
2542
|
+
if (status === 400 && data && data.error) {
|
|
2543
|
+
errorToDisplay = new Error("Validation error: ".concat(data.error));
|
|
2544
|
+
} else if (status === 401) {
|
|
2545
|
+
errorToDisplay = new Error("You are not authorized to perform this action");
|
|
2546
|
+
} else if (status === 404) {
|
|
2547
|
+
errorToDisplay = new Error("Feature definition not found");
|
|
2548
|
+
} else if (status >= 500) {
|
|
2549
|
+
errorToDisplay = new Error("Server error. Please try again later.");
|
|
2550
|
+
} else {
|
|
2551
|
+
errorToDisplay = new Error(data && data.error || "Request failed with status ".concat(status));
|
|
2552
|
+
}
|
|
2553
|
+
} else if (err.request) {
|
|
2554
|
+
// Network error (no response received)
|
|
2555
|
+
errorToDisplay = new Error("Network error. Please check your connection and try again.");
|
|
2556
|
+
} else if (err.message) {
|
|
2557
|
+
// Other JavaScript errors
|
|
2558
|
+
errorToDisplay = err;
|
|
2559
|
+
} else {
|
|
2560
|
+
// Unknown error
|
|
2561
|
+
errorToDisplay = new Error("An unexpected error occurred while saving");
|
|
2562
|
+
}
|
|
2563
|
+
dispatch(submitFormFailure(errorToDisplay));
|
|
2564
|
+
}
|
|
2565
|
+
};
|
|
2566
|
+
}
|
|
2567
|
+
|
|
2568
|
+
function ownKeys$6(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
2569
|
+
function _objectSpread$6(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$6(Object(t), !0).forEach(function (r) { _defineProperty__default["default"](e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$6(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
2570
|
+
const SET_NAVIGATION_STATE$1 = "SET_NAVIGATION_STATE";
|
|
2571
|
+
const UPDATE_STEP_VALIDATION$1 = "UPDATE_STEP_VALIDATION";
|
|
2572
|
+
const MARK_STEP_COMPLETE$1 = "MARK_STEP_COMPLETE";
|
|
2573
|
+
const setCurrentStep = function (step) {
|
|
2574
|
+
let previousStep = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
|
|
2575
|
+
return {
|
|
2576
|
+
type: SET_NAVIGATION_STATE$1,
|
|
2829
2577
|
payload: {
|
|
2830
|
-
|
|
2831
|
-
|
|
2578
|
+
currentStep: step,
|
|
2579
|
+
previousStep,
|
|
2580
|
+
canGoBack: step !== "welcome",
|
|
2581
|
+
canGoForward: true
|
|
2832
2582
|
}
|
|
2833
2583
|
};
|
|
2834
2584
|
};
|
|
2835
|
-
const
|
|
2585
|
+
const goToStep = step => (dispatch, getState) => {
|
|
2586
|
+
const state = getState()[values.reducerKey];
|
|
2587
|
+
const currentStep = state && state.wizard && state.wizard.navigation && state.wizard.navigation.currentStep;
|
|
2588
|
+
|
|
2589
|
+
// Clear form submission state when changing steps
|
|
2590
|
+
dispatch(clearFormSubmissionState());
|
|
2591
|
+
dispatch(setCurrentStep(step, currentStep));
|
|
2592
|
+
};
|
|
2593
|
+
const updateStepValidation = function (step, isValid) {
|
|
2594
|
+
let errors = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
2836
2595
|
return {
|
|
2837
|
-
type:
|
|
2838
|
-
payload:
|
|
2596
|
+
type: UPDATE_STEP_VALIDATION$1,
|
|
2597
|
+
payload: {
|
|
2598
|
+
step,
|
|
2599
|
+
isValid,
|
|
2600
|
+
errors
|
|
2601
|
+
}
|
|
2839
2602
|
};
|
|
2840
2603
|
};
|
|
2841
|
-
const
|
|
2604
|
+
const validateAndUpdateStep = step => (dispatch, getState) => {
|
|
2605
|
+
// Use existing selectors to get form data
|
|
2606
|
+
const state = getState();
|
|
2607
|
+
const title = selectFormTitle(state);
|
|
2608
|
+
const icon = selectFormIcon(state);
|
|
2609
|
+
const displayName = selectFormDisplayName(state);
|
|
2610
|
+
const layout = selectFormLayout(state);
|
|
2611
|
+
const fields = selectFormFields(state);
|
|
2612
|
+
const form = {
|
|
2613
|
+
title,
|
|
2614
|
+
icon,
|
|
2615
|
+
displayName,
|
|
2616
|
+
layout,
|
|
2617
|
+
fields
|
|
2618
|
+
};
|
|
2619
|
+
const {
|
|
2620
|
+
isValid,
|
|
2621
|
+
errors
|
|
2622
|
+
} = getFormValidation(form, step);
|
|
2623
|
+
dispatch(updateStepValidation(step, isValid, errors));
|
|
2624
|
+
|
|
2625
|
+
// If valid, mark as complete
|
|
2626
|
+
if (isValid) {
|
|
2627
|
+
dispatch({
|
|
2628
|
+
type: MARK_STEP_COMPLETE$1,
|
|
2629
|
+
payload: step
|
|
2630
|
+
});
|
|
2631
|
+
}
|
|
2842
2632
|
return {
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2633
|
+
isValid,
|
|
2634
|
+
errors
|
|
2635
|
+
};
|
|
2636
|
+
};
|
|
2637
|
+
const getFormValidation = (form, step) => {
|
|
2638
|
+
// Return validation results for undefined form (prevent crashes)
|
|
2639
|
+
if (!form) {
|
|
2640
|
+
switch (step) {
|
|
2641
|
+
case "overview":
|
|
2642
|
+
return {
|
|
2643
|
+
isValid: false,
|
|
2644
|
+
errors: {
|
|
2645
|
+
title: "Title is required",
|
|
2646
|
+
displayName: "Display name is required",
|
|
2647
|
+
icon: "Icon is required"
|
|
2648
|
+
}
|
|
2649
|
+
};
|
|
2650
|
+
case "fields":
|
|
2651
|
+
return {
|
|
2652
|
+
isValid: false,
|
|
2653
|
+
errors: {
|
|
2654
|
+
missingTitle: "Title field is required",
|
|
2655
|
+
missingImage: "Feature image field is required",
|
|
2656
|
+
fieldLabels: "Some fields are missing labels"
|
|
2657
|
+
}
|
|
2658
|
+
};
|
|
2659
|
+
case "layout":
|
|
2660
|
+
return {
|
|
2661
|
+
isValid: false,
|
|
2662
|
+
errors: {
|
|
2663
|
+
layoutType: "Layout type is required"
|
|
2664
|
+
}
|
|
2665
|
+
};
|
|
2666
|
+
default:
|
|
2667
|
+
return {
|
|
2668
|
+
isValid: false,
|
|
2669
|
+
errors: {}
|
|
2670
|
+
};
|
|
2847
2671
|
}
|
|
2672
|
+
}
|
|
2673
|
+
switch (step) {
|
|
2674
|
+
case "overview":
|
|
2675
|
+
{
|
|
2676
|
+
const hasTitle = form.title && form.title.trim().length > 0;
|
|
2677
|
+
const hasDisplayName = form.displayName && form.displayName.trim().length > 0;
|
|
2678
|
+
const hasIcon = form.icon && form.icon.length > 0;
|
|
2679
|
+
return {
|
|
2680
|
+
isValid: hasTitle && hasDisplayName && hasIcon,
|
|
2681
|
+
errors: {
|
|
2682
|
+
title: !hasTitle ? "Title is required" : null,
|
|
2683
|
+
displayName: !hasDisplayName ? "Display name is required" : null,
|
|
2684
|
+
icon: !hasIcon ? "Icon is required" : null
|
|
2685
|
+
}
|
|
2686
|
+
};
|
|
2687
|
+
}
|
|
2688
|
+
case "fields":
|
|
2689
|
+
{
|
|
2690
|
+
const hasTitleField = form.fields && form.fields.some(field => field.id === "mandatory-title");
|
|
2691
|
+
const hasImageField = form.fields && form.fields.some(field => field.id === "mandatory-feature-image");
|
|
2692
|
+
|
|
2693
|
+
// Check each field for missing labels and create field-specific errors
|
|
2694
|
+
const fieldErrors = {};
|
|
2695
|
+
let allFieldsHaveLabels = true;
|
|
2696
|
+
if (form.fields) {
|
|
2697
|
+
form.fields.forEach(field => {
|
|
2698
|
+
if ((field.type === "text" || field.type === "description" || field.type === "title" || field.type === "image" || field.type === "gallery" || field.type === "feature-image" || field.type === "file" || field.type === "cta") && field.values) {
|
|
2699
|
+
if (!field.values.label || field.values.label.trim().length === 0) {
|
|
2700
|
+
fieldErrors[field.id] = "Field label is required";
|
|
2701
|
+
allFieldsHaveLabels = false;
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
});
|
|
2705
|
+
}
|
|
2706
|
+
return {
|
|
2707
|
+
isValid: hasTitleField && hasImageField && allFieldsHaveLabels,
|
|
2708
|
+
errors: _objectSpread$6({
|
|
2709
|
+
missingTitle: !hasTitleField ? "Title field is required" : null,
|
|
2710
|
+
missingImage: !hasImageField ? "Feature image field is required" : null
|
|
2711
|
+
}, fieldErrors)
|
|
2712
|
+
};
|
|
2713
|
+
}
|
|
2714
|
+
case "layout":
|
|
2715
|
+
{
|
|
2716
|
+
const hasLayoutType = form.layout && form.layout.type && form.layout.type.length > 0;
|
|
2717
|
+
return {
|
|
2718
|
+
isValid: hasLayoutType,
|
|
2719
|
+
errors: {
|
|
2720
|
+
layoutType: !hasLayoutType ? "Layout type is required" : null
|
|
2721
|
+
}
|
|
2722
|
+
};
|
|
2723
|
+
}
|
|
2724
|
+
default:
|
|
2725
|
+
return {
|
|
2726
|
+
isValid: true,
|
|
2727
|
+
errors: {}
|
|
2728
|
+
};
|
|
2729
|
+
}
|
|
2730
|
+
};
|
|
2731
|
+
const setCurrentStepAndSave = function (step) {
|
|
2732
|
+
let previousStep = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
|
|
2733
|
+
return dispatch => {
|
|
2734
|
+
dispatch(setCurrentStep(step, previousStep));
|
|
2848
2735
|
};
|
|
2849
2736
|
};
|
|
2850
2737
|
|
|
2738
|
+
var css$d = ".fullWidthContent {\n\tmax-width: 100%;\n\tmargin-left: auto;\n\tmargin-right: auto;\n\tpadding: 2rem 2rem 3rem 2rem; /* Add extra bottom padding */\n}\n\n/* Full-width container that allows scrollbar at edge */\n.fullWidthContainer {\n\tdisplay: flex;\n\tflex-direction: column;\n\tflex: 1;\n\tmin-height: 0; /* Allow content to determine height */\n\twidth: 100%; /* Take full width to ensure scrollbar is at edge */\n}\n\n/* Content container to keep content centered */\n.contentContainer {\n\tdisplay: flex;\n\tflex-direction: column;\n\tflex: 1;\n\tmin-height: 0;\n\tmax-width: 960px;\n\tmargin: 0 auto;\n\tpadding: 64px 32px;\n\twidth: 100%;\n\tbox-sizing: border-box;\n}\n\n/* Legacy container class for backward compatibility */\n.container{\n\tdisplay: flex;\n\tflex-direction: column;\n\tflex: 1;\n\tmin-height: 0; /* Allow content to determine height */\n max-width: 960px;\n margin: 0 auto;\n padding: 64px 32px;\n}\n\n/* Responsive adjustments for content container */\n@media (max-width: 768px) {\n\t.contentContainer {\n\t\tpadding: 1.5rem 1.5rem 2.5rem 1.5rem; /* Slightly reduced but still adequate */\n\t}\n\t.container {\n\t\tpadding: 1.5rem 1.5rem 2.5rem 1.5rem; /* Legacy container support */\n\t}\n}\n\n@media (max-width: 480px) {\n\t.contentContainer {\n\t\tpadding: 1rem 1rem 2rem 1rem; /* Maintain padding on small screens */\n\t}\n\t.container {\n\t\tpadding: 1rem 1rem 2rem 1rem; /* Legacy container support */\n\t}\n}\n\n/* Enhanced sidebar navigation for progress indication */\n:global(.hub-sideBar) {\n\t/* Make sidebar more prominent during creation mode */\n}\n\n:global(.sideNav-item.isCurrent) {\n\tbackground: var(--colour-purple, #6e79c5) !important;\n\tcolor: var(--colour-white, #ffffff) !important;\n\tfont-weight: 600 !important;\n\tborder-left: 4px solid var(--colour-branding-dark, #364196) !important;\n}\n\n:global(.sideNav-item.isCurrent) i {\n\tcolor: var(--colour-white, #ffffff) !important;\n}\n\n:global(.sideNav-item.isCompleted) {\n\tcolor: var(--colour-branding-dark, #364196) !important;\n\tposition: relative;\n}\n\n:global(.sideNav-item.isCompleted::after) {\n\tcontent: \"✓\";\n\tposition: absolute;\n\tright: 1rem;\n\ttop: 50%;\n\ttransform: translateY(-50%);\n\tcolor: var(--colour-branding-dark, #364196);\n\tfont-weight: bold;\n}\n\n:global(.sideNav-item.isDisabled) {\n\topacity: 0.4;\n\tcursor: not-allowed;\n}\n\n/* Enhanced progress section */\n:global(.hub-sideBar-section) {\n\tborder-bottom: 2px solid var(--colour-branding-main-fade, rgba(74, 87, 183, 0.15));\n\tpadding-bottom: 1rem;\n\tmargin-bottom: 1rem;\n}\n\n:global(.hub-sideBar-section:last-child) {\n\tborder-bottom: none;\n}\n\n/* Delete button styling - bold red for development */\n.deleteButton {\n\tcolor: #dc3545 !important;\n\tfont-weight: bold !important;\n}\n\n:global(.sideNav-item.deleteButton) {\n\tcolor: #dc3545 !important;\n\tfont-weight: bold !important;\n}\n\n:global(.sideNav-item.deleteButton:hover) {\n\tbackground-color: rgba(220, 53, 69, 0.1) !important;\n}\n\n:global(.sideNav-item.deleteButton i) {\n\tcolor: #dc3545 !important;\n}\n\n/* Development delete button */\n.devDeleteButton {\n\tposition: absolute;\n\tbottom: 20px;\n\tleft: 20px;\n\tbackground-color: #ff0000;\n\tcolor: #ffffff;\n\tborder: none;\n\tpadding: 8px 12px;\n\tborder-radius: 4px;\n\tcursor: pointer;\n\tfont-size: 12px;\n\tfont-weight: bold;\n\tz-index: 1000;\n\tbox-shadow: 0 2px 4px rgba(0,0,0,0.3);\n}\n\n.devDeleteButton:hover {\n\tbackground-color: #cc0000;\n}\n";
|
|
2739
|
+
n(css$d,{});
|
|
2740
|
+
|
|
2741
|
+
const {
|
|
2742
|
+
Helper: Helper$1,
|
|
2743
|
+
Session: Session$1
|
|
2744
|
+
} = PlussCore__namespace;
|
|
2745
|
+
const {
|
|
2746
|
+
getUrl: getUrl$1
|
|
2747
|
+
} = Helper$1;
|
|
2748
|
+
const {
|
|
2749
|
+
authedFunction: authedFunction$1
|
|
2750
|
+
} = Session$1;
|
|
2751
|
+
|
|
2851
2752
|
/**
|
|
2852
|
-
*
|
|
2853
|
-
*
|
|
2854
|
-
*
|
|
2753
|
+
* Sidebar Layout component for feature builder wizard
|
|
2754
|
+
* Provides navigation sidebar with step progression, completion tracking
|
|
2755
|
+
* Includes delete functionality for feature definitions
|
|
2756
|
+
* Manages step accessibility based on wizard mode and validation state
|
|
2855
2757
|
*
|
|
2856
|
-
* @param {
|
|
2857
|
-
* @
|
|
2758
|
+
* @param {Object} props - Component props
|
|
2759
|
+
* @param {React.ReactNode} props.children - Child components to render in main content area
|
|
2760
|
+
* @param {Object} props.history - React Router history object for navigation
|
|
2761
|
+
* @returns {React.ReactElement} Layout component with sidebar navigation
|
|
2858
2762
|
*
|
|
2859
2763
|
* @example
|
|
2860
|
-
*
|
|
2764
|
+
* <SidebarLayout history={historyObject}>
|
|
2765
|
+
* <YourMainContent />
|
|
2766
|
+
* </SidebarLayout>
|
|
2861
2767
|
*/
|
|
2862
|
-
const
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2768
|
+
const SideBarInner = props => {
|
|
2769
|
+
const {
|
|
2770
|
+
children,
|
|
2771
|
+
history
|
|
2772
|
+
} = props;
|
|
2773
|
+
const dispatch = reactRedux.useDispatch();
|
|
2774
|
+
|
|
2775
|
+
// Get wizard state
|
|
2776
|
+
const mode = reactRedux.useSelector(selectWizardMode);
|
|
2777
|
+
const isEditMode = reactRedux.useSelector(selectIsEditMode);
|
|
2778
|
+
const isCreateMode = reactRedux.useSelector(selectIsCreateMode);
|
|
2779
|
+
const currentStep = reactRedux.useSelector(selectCurrentStep);
|
|
2780
|
+
reactRedux.useSelector(selectStepProgress);
|
|
2781
|
+
const definition = reactRedux.useSelector(selectDefinition);
|
|
2782
|
+
const definitionId = reactRedux.useSelector(selectDefinitionId);
|
|
2783
|
+
const auth = reactRedux.useSelector(state => state.auth);
|
|
2784
|
+
|
|
2785
|
+
// Delete functionality state
|
|
2786
|
+
const [showDeleteConfirm, setShowDeleteConfirm] = React.useState(false);
|
|
2787
|
+
const [isDeleting, setIsDeleting] = React.useState(false);
|
|
2788
|
+
const goTo = url => history.push(url);
|
|
2789
|
+
const isSelected = url => {
|
|
2790
|
+
return history.location.pathname === url;
|
|
2791
|
+
};
|
|
2792
|
+
|
|
2793
|
+
// Define step configuration with dynamic URL based on mode
|
|
2794
|
+
const getStepUrl = stepKey => {
|
|
2795
|
+
// Always use /definition/ in the URL since that's how routes are registered
|
|
2796
|
+
return "/feature-builder/definition/".concat(stepKey);
|
|
2797
|
+
};
|
|
2798
|
+
const steps = [{
|
|
2799
|
+
key: "overview",
|
|
2800
|
+
text: "Feature Overview",
|
|
2801
|
+
icon: "info-circle",
|
|
2802
|
+
url: getStepUrl("overview")
|
|
2803
|
+
}, {
|
|
2804
|
+
key: "fields",
|
|
2805
|
+
text: "Configure Fields",
|
|
2806
|
+
icon: "edit",
|
|
2807
|
+
url: getStepUrl("fields")
|
|
2808
|
+
}, {
|
|
2809
|
+
key: "layout",
|
|
2810
|
+
text: "Choose Layout",
|
|
2811
|
+
icon: "columns",
|
|
2812
|
+
url: getStepUrl("layout")
|
|
2813
|
+
}];
|
|
2814
|
+
|
|
2815
|
+
// Build sidebar items based on mode
|
|
2816
|
+
const buildSidebarItems = () => {
|
|
2817
|
+
return steps.map((step, index) => {
|
|
2818
|
+
const isCompleted = selectIsStepComplete(step.key);
|
|
2819
|
+
const isAccessible = selectIsStepAccessible(step.key);
|
|
2820
|
+
|
|
2821
|
+
// Add step number to text for better clarity
|
|
2822
|
+
const stepText = "".concat(index + 1, ". ").concat(step.text);
|
|
2823
|
+
const itemProps = {
|
|
2824
|
+
type: "navItem",
|
|
2825
|
+
text: stepText,
|
|
2826
|
+
icon: step.icon,
|
|
2827
|
+
selected: isSelected(step.url),
|
|
2828
|
+
onclick: isAccessible ? () => {
|
|
2829
|
+
goTo(step.url);
|
|
2830
|
+
dispatch(goToStep(step.key));
|
|
2831
|
+
} : null,
|
|
2832
|
+
isFontAwesome: true,
|
|
2833
|
+
// Enhanced completion indicator
|
|
2834
|
+
completed: isCompleted,
|
|
2835
|
+
// Allow navigation to completed steps even in create mode
|
|
2836
|
+
disabled: mode === "create" && !isAccessible
|
|
2837
|
+
};
|
|
2838
|
+
return itemProps;
|
|
2839
|
+
});
|
|
2866
2840
|
};
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
payload: layoutType
|
|
2841
|
+
|
|
2842
|
+
// Determine sidebar title - always use "Build Your Feature" now
|
|
2843
|
+
const getSidebarTitle = () => {
|
|
2844
|
+
return "Build Your Feature";
|
|
2872
2845
|
};
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2846
|
+
|
|
2847
|
+
// Simple help text
|
|
2848
|
+
const getHelpText = () => {
|
|
2849
|
+
return "Get help with feature builder";
|
|
2877
2850
|
};
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2851
|
+
|
|
2852
|
+
// Build sidebar sections - simplified without progress section
|
|
2853
|
+
const sidebarSections = [{
|
|
2854
|
+
title: getSidebarTitle(),
|
|
2855
|
+
items: buildSidebarItems()
|
|
2856
|
+
}];
|
|
2857
|
+
|
|
2858
|
+
// Delete functionality handlers
|
|
2859
|
+
const handleDeleteClick = () => {
|
|
2860
|
+
setShowDeleteConfirm(true);
|
|
2882
2861
|
};
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2862
|
+
const handleConfirmDelete = async () => {
|
|
2863
|
+
if (!definition) return;
|
|
2864
|
+
setIsDeleting(true);
|
|
2865
|
+
try {
|
|
2866
|
+
await authedFunction$1({
|
|
2867
|
+
method: "POST",
|
|
2868
|
+
url: getUrl$1("feature-builder", "definition/update/delete"),
|
|
2869
|
+
data: {
|
|
2870
|
+
id: definitionId,
|
|
2871
|
+
site: auth.site
|
|
2872
|
+
}
|
|
2873
|
+
});
|
|
2874
|
+
|
|
2875
|
+
// Redirect to welcome screen after deletion
|
|
2876
|
+
history.push("/feature-builder/definition/overview");
|
|
2877
|
+
} finally {
|
|
2878
|
+
setIsDeleting(false);
|
|
2879
|
+
setShowDeleteConfirm(false);
|
|
2880
|
+
}
|
|
2888
2881
|
};
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
return {
|
|
2892
|
-
type: actionsTypes.CLEAR_FORM_SUBMISSION_STATE
|
|
2882
|
+
const handleCancelDelete = () => {
|
|
2883
|
+
setShowDeleteConfirm(false);
|
|
2893
2884
|
};
|
|
2894
|
-
};
|
|
2895
2885
|
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
const site = getState().auth.site;
|
|
2915
|
-
if (!site) {
|
|
2916
|
-
throw new Error("Authentication error: Site context not found. Please refresh and login again.");
|
|
2917
|
-
}
|
|
2886
|
+
// Add effect to manually attach click handlers since HubSidebar might not use onclick properly
|
|
2887
|
+
React.useEffect(() => {
|
|
2888
|
+
const attachClickHandlers = () => {
|
|
2889
|
+
const stepsWithUrls = [{
|
|
2890
|
+
key: "overview",
|
|
2891
|
+
url: getStepUrl("overview")
|
|
2892
|
+
}, {
|
|
2893
|
+
key: "fields",
|
|
2894
|
+
url: getStepUrl("fields")
|
|
2895
|
+
}, {
|
|
2896
|
+
key: "layout",
|
|
2897
|
+
url: getStepUrl("layout")
|
|
2898
|
+
}];
|
|
2899
|
+
stepsWithUrls.forEach(step => {
|
|
2900
|
+
const navItem = Array.from(document.querySelectorAll(".sideNav-item")).find(item => item.textContent && item.textContent.includes(step.key === "overview" ? "Feature Overview" : step.key === "fields" ? "Configure Fields" : "Choose Layout"));
|
|
2901
|
+
if (navItem) {
|
|
2902
|
+
// Remove any existing click listeners
|
|
2903
|
+
navItem.onclick = null;
|
|
2918
2904
|
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2905
|
+
// Check if this step is accessible
|
|
2906
|
+
const isAccessible = selectIsStepAccessible(step.key)({
|
|
2907
|
+
[values.reducerKey]: {
|
|
2908
|
+
wizard: {
|
|
2909
|
+
mode: mode,
|
|
2910
|
+
navigation: {
|
|
2911
|
+
currentStep: currentStep
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
});
|
|
2916
|
+
|
|
2917
|
+
// Add our click handler only if accessible
|
|
2918
|
+
if (isAccessible) {
|
|
2919
|
+
navItem.onclick = event => {
|
|
2920
|
+
event.preventDefault();
|
|
2921
|
+
event.stopPropagation();
|
|
2922
|
+
history.push(step.url);
|
|
2923
|
+
dispatch(goToStep(step.key));
|
|
2924
|
+
};
|
|
2936
2925
|
}
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
throw new Error("Authentication error: Missing required context (featureId or site).");
|
|
2943
|
-
}
|
|
2944
|
-
await featureDefinitionActions.create(values.featureId, site,
|
|
2945
|
-
// Use actual site from auth store
|
|
2946
|
-
{
|
|
2947
|
-
title: form.title,
|
|
2948
|
-
icon: form.icon,
|
|
2949
|
-
displayName: form.displayName,
|
|
2950
|
-
layout: form.layout,
|
|
2951
|
-
fields: form.fields
|
|
2952
|
-
});
|
|
2953
|
-
}
|
|
2954
|
-
dispatch(submitFormSuccess());
|
|
2955
|
-
} catch (err) {
|
|
2956
|
-
// Handle different types of errors
|
|
2957
|
-
let errorToDisplay = err;
|
|
2958
|
-
if (err.response) {
|
|
2959
|
-
// API error (400, 401, 404, 500, etc.)
|
|
2960
|
-
const {
|
|
2961
|
-
status,
|
|
2962
|
-
data
|
|
2963
|
-
} = err.response;
|
|
2964
|
-
if (status === 400 && data && data.error) {
|
|
2965
|
-
errorToDisplay = new Error("Validation error: ".concat(data.error));
|
|
2966
|
-
} else if (status === 401) {
|
|
2967
|
-
errorToDisplay = new Error("You are not authorized to perform this action");
|
|
2968
|
-
} else if (status === 404) {
|
|
2969
|
-
errorToDisplay = new Error("Feature definition not found");
|
|
2970
|
-
} else if (status >= 500) {
|
|
2971
|
-
errorToDisplay = new Error("Server error. Please try again later.");
|
|
2972
|
-
} else {
|
|
2973
|
-
errorToDisplay = new Error(data && data.error || "Request failed with status ".concat(status));
|
|
2926
|
+
|
|
2927
|
+
// Make sure it's styled appropriately
|
|
2928
|
+
navItem.style.cursor = isAccessible ? "pointer" : "not-allowed";
|
|
2929
|
+
navItem.style.pointerEvents = "auto";
|
|
2930
|
+
navItem.style.opacity = isAccessible ? "1" : "0.5";
|
|
2974
2931
|
}
|
|
2975
|
-
}
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2932
|
+
});
|
|
2933
|
+
};
|
|
2934
|
+
|
|
2935
|
+
// Initial attachment
|
|
2936
|
+
attachClickHandlers();
|
|
2937
|
+
|
|
2938
|
+
// Re-attach after a short delay to ensure HubSidebar has rendered
|
|
2939
|
+
const timeoutId = setTimeout(attachClickHandlers, 100);
|
|
2940
|
+
|
|
2941
|
+
// Also try to re-attach when DOM changes (observe for mutations)
|
|
2942
|
+
const observer = new MutationObserver(() => {
|
|
2943
|
+
setTimeout(attachClickHandlers, 50);
|
|
2944
|
+
});
|
|
2945
|
+
|
|
2946
|
+
// Start observing the document body for changes
|
|
2947
|
+
observer.observe(document.body, {
|
|
2948
|
+
childList: true,
|
|
2949
|
+
subtree: true
|
|
2950
|
+
});
|
|
2951
|
+
return () => {
|
|
2952
|
+
clearTimeout(timeoutId);
|
|
2953
|
+
observer.disconnect();
|
|
2954
|
+
};
|
|
2955
|
+
}, [history, dispatch, isEditMode, isCreateMode, currentStep]);
|
|
2956
|
+
return /*#__PURE__*/React__default["default"].createElement("div", {
|
|
2957
|
+
className: "hub-wrapperContainer"
|
|
2958
|
+
}, /*#__PURE__*/React__default["default"].createElement(HubSidebar, {
|
|
2959
|
+
sections: sidebarSections,
|
|
2960
|
+
helpGuide: {
|
|
2961
|
+
text: getHelpText(),
|
|
2962
|
+
url: "https://www.plusscommunities.com/user-guide"
|
|
2986
2963
|
}
|
|
2987
|
-
}
|
|
2988
|
-
|
|
2964
|
+
}), /*#__PURE__*/React__default["default"].createElement("button", {
|
|
2965
|
+
onClick: handleDeleteClick,
|
|
2966
|
+
className: css$d.devDeleteButton
|
|
2967
|
+
}, "DELETE FEATURE DEF"), /*#__PURE__*/React__default["default"].createElement("div", {
|
|
2968
|
+
className: "hub-contentWrapper"
|
|
2969
|
+
}, /*#__PURE__*/React__default["default"].createElement("div", {
|
|
2970
|
+
className: css$d.fullWidthContainer
|
|
2971
|
+
}, /*#__PURE__*/React__default["default"].createElement("div", {
|
|
2972
|
+
className: css$d.contentContainer
|
|
2973
|
+
}, children))), /*#__PURE__*/React__default["default"].createElement(DeleteConfirmationPopup, {
|
|
2974
|
+
isOpen: showDeleteConfirm,
|
|
2975
|
+
listing: definition,
|
|
2976
|
+
onConfirm: handleConfirmDelete,
|
|
2977
|
+
onCancel: handleCancelDelete,
|
|
2978
|
+
isDeleting: isDeleting,
|
|
2979
|
+
deleteType: "featureDefinition"
|
|
2980
|
+
}));
|
|
2981
|
+
};
|
|
2982
|
+
const SidebarLayout = reactRouter.withRouter(SideBarInner);
|
|
2983
|
+
|
|
2984
|
+
var css$c = ".content {\n\tflex: 1 1 auto;\n\tdisplay: flex;\n\tflex-flow: column nowrap;\n\tpadding: 2rem 0 3rem 0; /* Add proper top/bottom padding */\n\tmin-height: 100%; /* Ensure full height for proper spacing */\n}\n\n/* New page wrapper for centered container approach */\n.pageWrapper {\n\tpadding: 2rem 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tjustify-content: flex-start;\n}\n\n@media (min-width: 768px) {\n\t.pageWrapper {\n\t\tpadding: 3rem 0;\n\t}\n}\n\n/* Form container styling when using CenteredContainer */\n.formContainer {\n\tbackground-color: var(--bg-white);\n}\n\n/* Section styling */\n.section {\n\tmargin-bottom: 2rem;\n\t\tpadding: 0 0 2em;\n}\n\n.section--no-border {\n\t\tborder-bottom: none;\n}\n\n.subtitle {\n\tcolor: var(--text-bluegrey, #6c7a90);\n}\n\n.sectionHeader {\n\t\tdisplay: flex;\n\t\tjustify-content: space-between;\n}\n\n/* Add Field Button styling */\n.addFieldButton {\n\tbackground: var(--colour-branding-action, #5c90df);\n\tcolor: white;\n\tborder: none;\n\tpadding: 0.75rem 1.5rem;\n\tborder-radius: 6px;\n\tfont-size: 1rem;\n\tcursor: pointer;\n\tmargin-bottom: 2rem;\n\ttransition: all 0.2s ease;\n}\n\n.addFieldButton:hover {\n\tbackground: var(--colour-branding-action-hover, #364196);\n\ttransform: translateY(-1px);\n\tbox-shadow: 0 2px 4px rgba(92, 144, 223, 0.3);\n}\n\n.addFieldButton:focus {\n\toutline: 2px solid var(--colour-branding-action, #5c90df);\n\toutline-offset: 2px;\n}\n\n.addFieldButton:active {\n\ttransform: translateY(0);\n}\n\n/* Refresh button styling */\n.refreshButton {\n\tmargin-left: 10px;\n\tpadding: 2px 8px;\n\tfont-size: 12px;\n\tbackground: var(--colour-dusk, #536280);\n\tcolor: white;\n\tborder: none;\n\tborder-radius: 4px;\n\tcursor: pointer;\n\ttransition: background-color 0.2s ease;\n}\n\n.refreshButton:hover {\n\tbackground: var(--colour-dusk-hover, #485968);\n}\n\n/* Error message container */\n.errorMessageContainer {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 0.5rem;\n}\n\n/* Add Field Container - matches field structure */\n.addFieldContainer {\n\tdisplay: flex;\n\tmargin-top: 32px;\n}\n\n.fieldNumberContainer {\n\tdisplay: flex;\n\twidth: 40px;\n}\n\n/* Add Field Section */\n.addFieldSection {\n\tdisplay: flex;\n\tgap: 1rem;\n\talign-items: center;\n\tjustify-content: flex-start; /* Align to start to match field content */\n\tmargin-top: 0; /* Remove top margin since it's in the container */\n}\n\n/* Desktop: horizontal layout for field selector and button */\n@media (min-width: 769px) {\n\t.addFieldSection {\n\t\tflex-direction: row;\n\t\talign-items: baseline;\n\t\tgap: 2rem;\n\t\talign-items: flex-start;\n\t\tjustify-content: flex-start; /* Align to start to match field content */\n\t}\n}\n\n/* Help Section for Popup */\n.helpSection {\n\tbackground-color: var(--colour-branding-action-superlight);\n\tborder: 1px solid var(--colour-branding-inactive);\n\tborder-radius: 8px;\n\tpadding: 1.5rem;\n\tmargin-bottom: 1.5rem;\n}\n\n.helpTitle {\n\tcolor: var(--text-dark);\n\tfont-weight: 600;\n\tmargin-bottom: 0.75rem;\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 0.5rem;\n\tfont-size: 1.25rem;\n}\n\n.helpText {\n\tcolor: var(--colour-branding-action);\n\tline-height: 1.6;\n\tfont-size: 1.25rem;\n}\n\n/* Field Type Cards for Popup */\n.fieldTypeCards {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 1rem; /* Spacing between vertical cards */\n\tpadding: 1rem 0;\n}\n\n/* Responsive layout: maintain vertical layout on all screen sizes */\n@media (min-width: 768px) {\n\t.fieldTypeCards {\n\t\tgap: 1.5rem; /* Slightly larger gap on desktop */\n\t}\n}\n\n.fieldTypeCard {\n\tdisplay: flex;\n\tflex-direction: row;\n\talign-items: flex-start;\n\tpadding: 1.75rem 1.25rem; /* More vertical padding */\n\tborder: 1px solid var(--border-line-grey);\n\tborder-radius: 8px;\n\tbackground-color: var(--bg-white);\n\tcursor: pointer;\n\ttransition: all 0.2s ease;\n\tbox-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);\n\tmin-height: 120px; /* Adjust height for horizontal layout */\n\tgap: 1rem; /* Space between icon and content */\n}\n\n.fieldTypeCard:hover {\n\tborder-color: var(--text-light);\n\tbackground-color: var(--bg-bluegrey);\n\tbox-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);\n}\n\n.fieldTypeCard:focus {\n\toutline: 2px solid var(--text-light);\n\toutline-offset: 2px;\n}\n\n.fieldTypeCard:active {\n\ttransform: scale(0.98);\n\ttransition: transform 0.1s ease;\n}\n\n.fieldTypeCardIcon {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 48px;\n\theight: 48px;\n\tmargin-bottom: 0; /* Remove bottom margin for horizontal layout */\n\tfont-size: 20px;\n\tflex-shrink: 0;\n\tborder: 1px solid var(--border-line-grey); /* Subtle border */\n\tborder-radius: 8px;\n\tbackground-color: var(--bg-bluegrey);\n\tpadding: 8px;\n}\n\n\n.fieldTypeCardContent {\n\tflex: 1;\n\twidth: 100%;\n\tmin-width: 0; /* Prevent content from overflowing */\n}\n\n.fieldTypeCardTitle {\n\tmargin: 0 0 0.25rem 0;\n\tfont-weight: 600;\n\tfont-size: 1.4rem; /* Increased for better readability */\n}\n\n.fieldTypeCardDescription {\n\tmargin: 0 0 0.75rem 0;\n\tfont-size: 1.6rem; /* Increased for better readability */\n\tline-height: 1.5; /* Improved line height for better readability */\n}\n\n.fieldTypeCardUseCase {\n\tmargin: 0;\n\tfont-size: 1.4rem;\n\tfont-style: italic;\n\tcolor: var(--text-light);\n\tpadding-top: 0.5rem;\n\tborder-top: 1px solid var(--border-line-grey);\n}\n\n/* Field selector and button container - horizontal layout */\n.addFieldSection> div:first-child {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 0.5rem;\n\tflex: 1;\n}\n\n/* Container for add field button alignment */\n.addFieldButtonContainer {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: flex-end;\n}\n\n/* Empty State */\n.emptyState {\n\tpadding: 2rem;\n\ttext-align: center;\n\tbackground-color: var(--bg-bluegrey, #f4f7f9);\n\tborder-radius: 8px;\n\tborder: 1px dashed var(--border-line-grey, #dbddf1);\n\tmargin-bottom: 1rem;\n}\n\n.emptyStateText {\n\tcolor: var(--text-bluegrey, #6c7a90);\n\tfont-style: italic;\n}\n\n.fieldTypeSelector {\n\tdisplay: flex;\n\tflex-direction: column;\n\tmax-width: 500px; /* Increased from 300px to accommodate longer text */\n\twidth: 100%;\n}\n\n@media (max-width: 768px) {\n\t.addFieldSection {\n\t\talign-items: stretch;\n\t}\n\t\n\t.fieldTypeSelector {\n\t\tmax-width: none;\n\t}\n}\n\n.grid__four {\n max-width: 1200px;\n\tdisplay: grid;\n\tgrid-template-columns: repeat(2, minmax(250px, 1fr));\n margin: 2rem 0;\n\tgap: 2rem;\n\tjustify-items: center;\n}\n\n@media (max-width: 768px) {\n\t.grid__four {\n\t\tgrid-template-columns: 1fr;\n\t\tgap: 2rem;\n\t}\n}\n\n/* Navigation styles */\n.navigation {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tgap: 1rem;\n\tmargin-top: 3rem; /* Increase from 2rem */\n\tpadding-top: 2rem;\n\tpadding-bottom: 2rem; /* Add bottom padding */\n\tborder-top: 1px solid #e9ecef;\n}\n\n/* Single button alignment - align to right for overview step save button */\n.navigation> :only-child {\n\tmargin-left: auto;\n\tmargin-right: 0;\n}\n\n/* Two button alignment - one left, one right */\n.navigation> :first-child:not(:only-child) {\n\tmargin-right: auto;\n}\n\n.navigation> :last-child:not(:only-child) {\n\tmargin-left: auto;\n}\n\n/* Special case for overview step - align save button to right */\n.overviewStep .navigation> :only-child {\n\tmargin-left: auto;\n\tmargin-right: 0;\n}\n\n.previousButton {\n\tbackground: #6c757d;\n\tcolor: white;\n\tborder: none;\n\tpadding: 0.75rem 1.5rem;\n\tborder-radius: 6px;\n\tfont-size: 1rem;\n\tcursor: pointer;\n\ttransition: all 0.2s ease;\n}\n\n.previousButton:hover {\n\tbackground: #5a6268;\n}\n\n.saveButton {\n\tbackground: var(--colour-purple, #6e79c5);\n\tcolor: white;\n\tborder: none;\n\tpadding: 0.75rem 2rem;\n\tborder-radius: 6px;\n\tfont-size: 1rem;\n\tfont-weight: 500;\n\tcursor: pointer;\n\ttransition: all 0.2s ease;\n}\n\n.saveButton:hover:not(:disabled) {\n\tbackground: var(--colour-purple-hover, #5a66b3);\n\ttransform: translateY(-1px);\n\tbox-shadow: 0 2px 4px rgba(110, 121, 197, 0.3);\n}\n\n.saveButton:focus {\n\toutline: 2px solid var(--colour-purple, #6e79c5);\n\toutline-offset: 2px;\n}\n\n.saveButton:disabled {\n\tbackground: #6c757d;\n\tcursor: not-allowed;\n\topacity: 0.65;\n\ttransform: none;\n\tbox-shadow: none;\n}\n\n.nextButton {\n\tbackground: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n\tcolor: white;\n\tborder: none;\n\tpadding: 0.75rem 2rem;\n\tborder-radius: 6px;\n\tfont-size: 1rem;\n\tfont-weight: 500;\n\tcursor: pointer;\n\ttransition: all 0.2s ease;\n}\n\n.nextButton:hover:not(:disabled) {\n\ttransform: translateY(-2px);\n\tbox-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);\n}\n\n.nextButton:disabled {\n\tbackground: #6c757d;\n\tcursor: not-allowed;\n\topacity: 0.65;\n\ttransform: none;\n\tbox-shadow: none;\n}\n\n\n\n.errorMessage {\n\tcolor: var(--colour-red);\n\tmargin: 0.25rem 0;\n\tpadding: 0.25rem 0;\n\tfont-size: 0.875rem;\n\tline-height: 1.4;\n}\n\n/* Layout option styles */\n.layoutOption {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\ttext-align: center;\n\tcursor: pointer;\n\ttransition: all 0.2s ease;\n\tbackground-color: transparent;\n}\n\n.layoutOptionImage {\n\twidth: 250px;\n\theight: 250px;\n\tborder-radius: 12px;\n\toverflow: hidden;\n\tmargin-bottom: 1rem;\n\tborder: 2px solid var(--border-line-grey, #dbddf1);\n\ttransition: all 0.2s ease;\n}\n\n.layoutOption.selected .layoutOptionImage {\n\tborder-color: var(--border-line-grey, #dbddf1);\n\tbox-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n}\n\n.layoutOptionImg {\n\twidth: 100%;\n\theight: 100%;\n\tobject-fit: cover;\n}\n\n.layoutOptionContent {\n\tmax-width: 250px;\n}\n\n.layoutOptionTitle {\n\tfont-size: 1.6rem;\n\tfont-weight: 600;\n\tmargin: 0 0 0.5rem 0;\n\tline-height: 1.3;\n\tcolor: #333;\n}\n\n.layoutOptionDescription {\n\tfont-size: 1.2rem;\n\tmargin: 0;\n\tline-height: 1.3;\n\tcolor: #6c757d;\n}\n\n.layoutOption.hasError .layoutOptionImage {\n\tborder-color: var(--colour-red, #dc3545);\n}\n\n/* Field error styles */\n.fieldError {\n\tcolor: #dc3545;\n\tfont-size: 1.6rem;\n\tfont-weight: 500;\n\tmargin-top: 0.5rem;\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 0.25rem;\n}\n\n.errorIcon {\n\tfont-size: 1rem;\n}\n\n/* Field wrapper styles are now handled in Fields.module.css */\n\n/* Field type indicator styles are now handled in Fields.module.css */\n\n@keyframes errorShake {\n\t0%, 100% { transform: translateX(0); }\n\t25% { transform: translateX(-3px); }\n\t75% { transform: translateX(3px); }\n}\n\n@keyframes errorPulse {\n\t0% { \n\t\tbox-shadow: 0 1px 3px rgba(192, 39, 67, 0.1), 0 1px 2px rgba(192, 39, 67, 0.08);\n\t}\n\t50% { \n\t\tbox-shadow: 0 2px 6px rgba(192, 39, 67, 0.2), 0 1px 3px rgba(192, 39, 67, 0.15);\n\t}\n\t100% { \n\t\tbox-shadow: 0 1px 3px rgba(192, 39, 67, 0.1), 0 1px 2px rgba(192, 39, 67, 0.08);\n\t}\n}\n\n.successMessage {\n\tbackground: var(--colour-branding-secondary-light);\n\tborder: 1px solid var(--colour-branding-secondary);\n\tborder-radius: 8px;\n\tpadding: 1rem;\n\tmargin-top: 1rem;\n\tcolor: var(--colour-green);\n\tfont-weight: 500;\n}\n\n/* Mode-aware styling */\n.createMode {\n\tcolor: var(--colour-purple, #6e79c5);\n}\n\n.editMode {\n\tcolor: var(--colour-branding-dark, #364196);\n}\n\n/* Responsive Design */\n@media (max-width: 768px) {\n\t.content {\n\t\tpadding: 1.5rem 0 2.5rem 0; /* Maintain proper padding on mobile */\n\t}\n\t\n\t.section {\n\t\tmargin-bottom: 1.5rem;\n\t}\n\t\n\t/* Mobile: vertical layout for field selector and button */\n\t.addFieldSection {\n\t\tflex-direction: column;\n\t\talign-items: stretch; /* Stretch to fill available space */\n\t}\n\t\n\t/* Button should not be full width on mobile */\n\t.addFieldSection Button {\n\t\twidth: auto; /* Let button use its natural width */\n\t\tpadding: 1rem 1.5rem; /* Keep proper padding but not full width */\n\t}\n\t\n\t.addFieldButtonContainer {\n\t\talign-items: stretch;\n\t\tjustify-content: stretch;\n\t\tmin-height: auto;\n\t}\n\t\n\t.navigation {\n\t\tflex-direction: column;\n\t\tgap: 0.75rem;\n\t\tjustify-content: flex-start; /* Align to start on mobile */\n\t\tmargin-top: 2rem; /* Slightly reduce on mobile */\n\t\tpadding-top: 1.5rem;\n\t\tpadding-bottom: 1.5rem; /* Maintain bottom padding */\n\t}\n\t\n\t/* Mobile button alignment - single button aligns right, two buttons stack */\n\t.navigation> :only-child {\n\t\tmargin-left: auto;\n\t\tmargin-right: 0;\n\t}\n\t\n\t.navigation> :first-child:not(:only-child),\n\t.navigation> :last-child:not(:only-child) {\n\t\tmargin-left: 0;\n\t\tmargin-right: 0;\n\t}\n\t\n\t.previousButton,\n\t.nextButton,\n\t.saveButton {\n\t\twidth: 100%;\n\t\tpadding: 1rem;\n\t}\n}\n\n@media (max-width: 480px) {\n\t.content {\n\t\tpadding: 1rem 0 2rem 0; /* Still maintain padding on small screens */\n\t}\n\t\n\t.section {\n\t\tmargin-bottom: 1rem;\n\t}\n\t\n\t.navigation {\n\t\tmargin-top: 1.5rem; /* Further reduce on very small screens */\n\t\tpadding-top: 1rem;\n\t\tpadding-bottom: 1.5rem;\n\t\tjustify-content: flex-start; /* Align to start on small screens */\n\t}\n\t\n\t/* Small screen button alignment */\n\t.navigation> :only-child {\n\t\tmargin-left: auto;\n\t\tmargin-right: 0;\n\t}\n\t\n\t.navigation> :first-child:not(:only-child),\n\t.navigation> :last-child:not(:only-child) {\n\t\tmargin-left: 0;\n\t\tmargin-right: 0;\n\t}\n\t\n\t.errorMessageContainer {\n\t\tflex-direction: column;\n\t\talign-items: flex-start;\n\t\tgap: 0.75rem;\n\t}\n\t\n\t.refreshButton {\n\t\talign-self: flex-end;\n\t}\n}\n\n/* Full width content when sidebar is hidden */\n:global(.hub-contentWrapper.fullWidthContent) {\n\tmax-width: 1000px;\n\tmargin: 0 auto;\n\tpadding: 2rem;\n}\n\n.hub-contentWrapper--col {\n flex-flow: column;\n}\n\n/* Validation error message - displayed at top of form */\n.validationErrorMessage {\n\tbackground-color: var(--colour-branding-secondary-light);\n\tborder: 1px solid var(--colour-branding-secondary);\n\tborder-radius: 6px;\n\tcolor: var(--colour-red);\n\tpadding: 12px 16px;\n\tfont-weight: 500;\n\tfont-size: 14px;\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\t\tmargin: 24px 32px;\n}\n\n.validationErrorMessage::before {\n\tcontent: \"⚠\";\n\tfont-size: 16px;\n\tcolor: var(--colour-red);\n}\n\n/* Loading overlay for forms - deprecated, use SkeletonLoader instead */\n/*\n.loadingOverlay {\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\tright: 0;\n\tbottom: 0;\n\tbackground: rgba(255, 255, 255, 0.95);\n\tbackdrop-filter: blur(2px);\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tz-index: 10;\n\tborder-radius: 8px;\n\tanimation: fadeIn 0.2s ease-in-out;\n}\n\n@keyframes fadeIn {\n\tfrom { opacity: 0; }\n\tto { opacity: 1; }\n}\n*/\n\n/* Form header with buttons */\n.formHeader {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\talign-items: flex-start;\n\tmargin-bottom: 16px;\n}\n\n/* Loading container for fields */\n.fieldsLoadingContainer {\n\ttext-align: center;\n\tpadding: 60px 20px;\n}\n\n/* Help note styling */\n.helpNote {\n\tfont-size: 13px;\n\tcolor: #6c757d;\n}\n\n/* Loading container for overview */\n.overviewLoadingContainer {\n\ttext-align: center;\n\tpadding: 60px 20px;\n}\n\n/* Form layout header */\n.formLayoutHeader {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\talign-items: flex-start;\n\tmargin-bottom: 16px;\n}\n\n/* Section margins */\n.gridIconSection {\n\tmargin-bottom: 3rem;\n}\n\n.layoutSection {\n\tmargin-bottom: 2rem;\n}\n\n/* Grid icon loading state */\n.gridIconLoading {\n\tgrid-column: 1 / -1;\n}\n\n/* Hide upload button in grid icon section */\n.gridIconSection :global(.iconLoader__buttonOverlay button:first-child) {\n\tdisplay: none !important;\n}\n";
|
|
2985
|
+
n(css$c,{});
|
|
2989
2986
|
|
|
2990
2987
|
/*
|
|
2991
2988
|
* Icon categories and definitions for the feature builder
|
|
@@ -3306,9 +3303,6 @@ const FormOverviewStepInner = props => {
|
|
|
3306
3303
|
|
|
3307
3304
|
// Clear submission state after showing toast (only for non-edit cases)
|
|
3308
3305
|
if (!isEditMode) {
|
|
3309
|
-
const {
|
|
3310
|
-
clearFormSubmissionState
|
|
3311
|
-
} = require("../actions/formActions");
|
|
3312
3306
|
dispatch(clearFormSubmissionState());
|
|
3313
3307
|
}
|
|
3314
3308
|
}
|