@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,478 @@
|
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
|
2
|
+
import { useSelector, useDispatch } from "react-redux";
|
|
3
|
+
import { withRouter } from "react-router-dom";
|
|
4
|
+
import { Table } from "react-bootstrap";
|
|
5
|
+
import { PlussCore } from "../feature.config";
|
|
6
|
+
import {
|
|
7
|
+
fetchListing,
|
|
8
|
+
fetchListingSilent,
|
|
9
|
+
deleteListing,
|
|
10
|
+
undeleteListing,
|
|
11
|
+
setSortBy,
|
|
12
|
+
setShowDeleted,
|
|
13
|
+
} from "../actions/listingActions";
|
|
14
|
+
import { fetchFeatureDefinitions } from "../actions/featureDefinitionsIndex.js";
|
|
15
|
+
import { capitalizeTextWithFallback } from "../utils/textUtils";
|
|
16
|
+
import {
|
|
17
|
+
selectSortedListings,
|
|
18
|
+
selectActiveListings,
|
|
19
|
+
selectDeletedListings,
|
|
20
|
+
selectSortBy,
|
|
21
|
+
selectShowDeleted,
|
|
22
|
+
selectListingsIsLoading,
|
|
23
|
+
selectListingsError,
|
|
24
|
+
selectListingsIsInitiallyLoaded,
|
|
25
|
+
selectListingsIsRestoring,
|
|
26
|
+
selectDefinition,
|
|
27
|
+
selectDefinitionIsLoading,
|
|
28
|
+
selectDefinitionError,
|
|
29
|
+
selectHasDefinition,
|
|
30
|
+
} from "../selectors/featureBuilderSelectors";
|
|
31
|
+
import {
|
|
32
|
+
DeleteConfirmationPopup,
|
|
33
|
+
ErrorMessage,
|
|
34
|
+
LoadingState,
|
|
35
|
+
FeatureBuilderSidebar,
|
|
36
|
+
SortButtonGroup,
|
|
37
|
+
ToggleSwitch,
|
|
38
|
+
Header,
|
|
39
|
+
ErrorBoundary,
|
|
40
|
+
AddButton,
|
|
41
|
+
} from "../components";
|
|
42
|
+
|
|
43
|
+
import { values } from "../values.config.js";
|
|
44
|
+
|
|
45
|
+
import styles from "./ListingScreen.module.css";
|
|
46
|
+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
47
|
+
import { faPen, faTrash } from "@fortawesome/free-solid-svg-icons";
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
const { Button, Text } = PlussCore.Components;
|
|
51
|
+
|
|
52
|
+
const ListingScreen = (props) => {
|
|
53
|
+
const { history } = props;
|
|
54
|
+
const dispatch = useDispatch();
|
|
55
|
+
const auth = useSelector((state) => state.auth);
|
|
56
|
+
const sortedListings = useSelector(selectSortedListings);
|
|
57
|
+
const activeListings = useSelector(selectActiveListings);
|
|
58
|
+
const deletedListings = useSelector(selectDeletedListings);
|
|
59
|
+
const sortBy = useSelector(selectSortBy);
|
|
60
|
+
const showDeleted = useSelector(selectShowDeleted);
|
|
61
|
+
const isLoading = useSelector(selectListingsIsLoading);
|
|
62
|
+
const isInitiallyLoaded = useSelector(selectListingsIsInitiallyLoaded);
|
|
63
|
+
const error = useSelector(selectListingsError);
|
|
64
|
+
const isRestoring = useSelector(selectListingsIsRestoring);
|
|
65
|
+
const definition = useSelector(selectDefinition);
|
|
66
|
+
const displayName = definition?.displayName || definition?.title || "Feature";
|
|
67
|
+
const isDefinitionLoading = useSelector(selectDefinitionIsLoading);
|
|
68
|
+
const definitionError = useSelector(selectDefinitionError);
|
|
69
|
+
const hasDefinition = useSelector(selectHasDefinition);
|
|
70
|
+
|
|
71
|
+
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
|
72
|
+
const [listingToDelete, setListingToDelete] = useState(null);
|
|
73
|
+
const [deleteMessage, setDeleteMessage] = useState("");
|
|
74
|
+
const [isDeleting, setIsDeleting] = useState(false);
|
|
75
|
+
|
|
76
|
+
// Error handling state
|
|
77
|
+
const [actionError, setActionError] = useState("");
|
|
78
|
+
const [showActionError, setShowActionError] = useState(false);
|
|
79
|
+
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
// Always fetch feature definitions
|
|
82
|
+
dispatch(fetchFeatureDefinitions());
|
|
83
|
+
|
|
84
|
+
// Check data freshness - if no listings exist or data is stale, fetch with loading
|
|
85
|
+
const dataNeedsRefresh =
|
|
86
|
+
!isInitiallyLoaded ||
|
|
87
|
+
sortedListings.length === 0 ||
|
|
88
|
+
(sortedListings.length > 0 && !sortedListings[0].id);
|
|
89
|
+
|
|
90
|
+
if (dataNeedsRefresh) {
|
|
91
|
+
// Data is empty or invalid - show loading and fetch
|
|
92
|
+
dispatch(fetchListing());
|
|
93
|
+
} else {
|
|
94
|
+
// Data exists - refresh in background silently
|
|
95
|
+
dispatch(fetchListingSilent());
|
|
96
|
+
}
|
|
97
|
+
}, [dispatch, isInitiallyLoaded, sortedListings.length]);
|
|
98
|
+
|
|
99
|
+
// Helper function to show action errors
|
|
100
|
+
const showActionErrorMessage = (message) => {
|
|
101
|
+
setActionError(message);
|
|
102
|
+
setShowActionError(true);
|
|
103
|
+
// Auto-hide after 5 seconds
|
|
104
|
+
setTimeout(() => {
|
|
105
|
+
setShowActionError(false);
|
|
106
|
+
}, 5000);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Smart refresh with data freshness check
|
|
110
|
+
const handleSmartRefresh = () => {
|
|
111
|
+
// Force fresh data load regardless of current state
|
|
112
|
+
dispatch(fetchListing());
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const handleDeleteClick = (listing) => {
|
|
116
|
+
setListingToDelete(listing);
|
|
117
|
+
setShowDeleteConfirm(true);
|
|
118
|
+
setDeleteMessage("");
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const handleEditClick = (listing) => {
|
|
122
|
+
const id = listing.id || listing._id;
|
|
123
|
+
history.push(values.routeEditListing.replace(":id", id));
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const handleCreateClick = () => {
|
|
127
|
+
history.push(values.routeCreateListing);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const handleConfirmDelete = () => {
|
|
131
|
+
if (listingToDelete) {
|
|
132
|
+
setIsDeleting(true);
|
|
133
|
+
dispatch(deleteListing(listingToDelete.id))
|
|
134
|
+
.then(() => {
|
|
135
|
+
const title =
|
|
136
|
+
(listingToDelete.fields &&
|
|
137
|
+
listingToDelete.fields["mandatory-title"]) ||
|
|
138
|
+
"Item";
|
|
139
|
+
setDeleteMessage(
|
|
140
|
+
`${capitalizeTextWithFallback(title, "Item")} deleted. You can restore it anytime.`,
|
|
141
|
+
);
|
|
142
|
+
setShowDeleteConfirm(false);
|
|
143
|
+
setListingToDelete(null);
|
|
144
|
+
setIsDeleting(false);
|
|
145
|
+
// Clear message after 3 seconds
|
|
146
|
+
setTimeout(() => setDeleteMessage(""), 3000);
|
|
147
|
+
})
|
|
148
|
+
.catch((error) => {
|
|
149
|
+
const errorMessage = error.message || "Network error";
|
|
150
|
+
showActionErrorMessage(
|
|
151
|
+
`Can't delete listing right now. Check your internet connection and try again. (${errorMessage})`,
|
|
152
|
+
);
|
|
153
|
+
setShowDeleteConfirm(false);
|
|
154
|
+
setListingToDelete(null);
|
|
155
|
+
setIsDeleting(false);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const handleCancelDelete = () => {
|
|
161
|
+
setShowDeleteConfirm(false);
|
|
162
|
+
setListingToDelete(null);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const handleRestoreClick = (listing) => {
|
|
166
|
+
dispatch(undeleteListing(listing.id))
|
|
167
|
+
.then(() => {
|
|
168
|
+
const title =
|
|
169
|
+
(listing.fields && listing.fields["mandatory-title"]) || "Item";
|
|
170
|
+
setDeleteMessage(
|
|
171
|
+
`${capitalizeTextWithFallback(title, "Item")} restored and ready to use.`,
|
|
172
|
+
);
|
|
173
|
+
// Clear message after 3 seconds
|
|
174
|
+
setTimeout(() => setDeleteMessage(""), 3000);
|
|
175
|
+
})
|
|
176
|
+
.catch((error) => {
|
|
177
|
+
const errorMessage = error.message || "Network error";
|
|
178
|
+
showActionErrorMessage(
|
|
179
|
+
`Can't restore listing right now. Check your internet connection and try again. (${errorMessage})`,
|
|
180
|
+
);
|
|
181
|
+
});
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const handleSortChange = (sortType) => {
|
|
185
|
+
dispatch(setSortBy(sortType));
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const handleToggleDeleted = (showDeleted) => {
|
|
189
|
+
dispatch(setShowDeleted(showDeleted));
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const handleShowDeletedListings = () => {
|
|
193
|
+
dispatch(setShowDeleted(true));
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const renderTableRows = () => {
|
|
197
|
+
const source = sortedListings;
|
|
198
|
+
|
|
199
|
+
return source.map((listing) => {
|
|
200
|
+
const id = listing.id || listing._id;
|
|
201
|
+
const title =
|
|
202
|
+
(listing.fields && listing.fields["mandatory-title"]) || "Untitled";
|
|
203
|
+
const modifiedAt =
|
|
204
|
+
listing.editedAt ||
|
|
205
|
+
listing.updatedAt ||
|
|
206
|
+
listing.createdAt ||
|
|
207
|
+
new Date();
|
|
208
|
+
const isDeleted = !!listing.deletedAt;
|
|
209
|
+
|
|
210
|
+
return (
|
|
211
|
+
<tr
|
|
212
|
+
key={id}
|
|
213
|
+
className={isDeleted ? styles["listingScreen__row--deleted"] : ""}
|
|
214
|
+
>
|
|
215
|
+
<td>
|
|
216
|
+
{new Date(modifiedAt).toLocaleDateString("en-AU", {
|
|
217
|
+
day: "numeric",
|
|
218
|
+
month: "short",
|
|
219
|
+
year: "2-digit",
|
|
220
|
+
})}
|
|
221
|
+
</td>
|
|
222
|
+
<td>
|
|
223
|
+
<div className={styles.titleContainer}>
|
|
224
|
+
<span
|
|
225
|
+
onClick={() => handleEditClick(listing)}
|
|
226
|
+
className={
|
|
227
|
+
isDeleted ? styles["title--deleted"] : styles["titleLink"]
|
|
228
|
+
}
|
|
229
|
+
>
|
|
230
|
+
{title}
|
|
231
|
+
</span>
|
|
232
|
+
{isDeleted && <span className={styles.deletedTag}>Deleted</span>}
|
|
233
|
+
</div>
|
|
234
|
+
</td>
|
|
235
|
+
<td>
|
|
236
|
+
<div className={styles.actions}>
|
|
237
|
+
{!isDeleted && (
|
|
238
|
+
<span
|
|
239
|
+
onClick={() => handleEditClick(listing)}
|
|
240
|
+
className={styles.actionIcon}
|
|
241
|
+
title="Edit"
|
|
242
|
+
>
|
|
243
|
+
<FontAwesomeIcon icon={faPen} />
|
|
244
|
+
</span>
|
|
245
|
+
)}
|
|
246
|
+
|
|
247
|
+
{!isDeleted && (
|
|
248
|
+
<span
|
|
249
|
+
onClick={() => handleDeleteClick(listing)}
|
|
250
|
+
className={styles.actionIcon}
|
|
251
|
+
title="Delete"
|
|
252
|
+
>
|
|
253
|
+
<FontAwesomeIcon icon={faTrash} />
|
|
254
|
+
</span>
|
|
255
|
+
)}
|
|
256
|
+
{isDeleted && (
|
|
257
|
+
<Button
|
|
258
|
+
onClick={() => handleRestoreClick(listing)}
|
|
259
|
+
buttonType="outlinedAction"
|
|
260
|
+
disabled={isRestoring}
|
|
261
|
+
narrow
|
|
262
|
+
leftIcon={isRestoring ? "sync" : "undo"}
|
|
263
|
+
loading={isRestoring}
|
|
264
|
+
>
|
|
265
|
+
{isRestoring ? "Restoring..." : "Restore"}
|
|
266
|
+
</Button>
|
|
267
|
+
)}
|
|
268
|
+
</div>
|
|
269
|
+
</td>
|
|
270
|
+
</tr>
|
|
271
|
+
);
|
|
272
|
+
});
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
// Check for content management permission
|
|
276
|
+
if (
|
|
277
|
+
!PlussCore.Session.validateAccess(
|
|
278
|
+
auth.site,
|
|
279
|
+
values.permissionFeatureBuilderContent,
|
|
280
|
+
auth,
|
|
281
|
+
)
|
|
282
|
+
) {
|
|
283
|
+
return (
|
|
284
|
+
<div className="hub-wrapperContainer">
|
|
285
|
+
<FeatureBuilderSidebar />
|
|
286
|
+
<div className="hub-contentWrapper">
|
|
287
|
+
<div className={styles.welcomeContainer}>
|
|
288
|
+
<div className={styles.welcomeHeader}>
|
|
289
|
+
<Text type="h1" className={styles.welcomeTitle}>
|
|
290
|
+
Access Restricted
|
|
291
|
+
</Text>
|
|
292
|
+
<Text type="body" className={styles.welcomeSubtitle}>
|
|
293
|
+
You don't have permission to manage feature content. Please
|
|
294
|
+
contact your administrator if you need access.
|
|
295
|
+
</Text>
|
|
296
|
+
</div>
|
|
297
|
+
</div>
|
|
298
|
+
</div>
|
|
299
|
+
</div>
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Don't show full-screen loading, use skeleton states instead
|
|
304
|
+
|
|
305
|
+
return (
|
|
306
|
+
<ErrorBoundary
|
|
307
|
+
title="Unable to load listings"
|
|
308
|
+
message="If you continue to experience issues loading your listings, please try refreshing the page or contact support."
|
|
309
|
+
onRetry={handleSmartRefresh}
|
|
310
|
+
>
|
|
311
|
+
<div className="hub-wrapperContainer">
|
|
312
|
+
<FeatureBuilderSidebar />
|
|
313
|
+
<div className="hub-headerContentWrapper">
|
|
314
|
+
<Header />
|
|
315
|
+
<div className={styles.root}>
|
|
316
|
+
<div className={styles.header}>
|
|
317
|
+
<div className={styles.headerText}>
|
|
318
|
+
<Text type="h1">Manage Your {displayName} Listings</Text>
|
|
319
|
+
<Text type="bodyLarge">
|
|
320
|
+
View, edit, and manage all your {displayName.toLowerCase()}{" "}
|
|
321
|
+
listings
|
|
322
|
+
</Text>
|
|
323
|
+
</div>
|
|
324
|
+
{sortedListings.length > 0 && (
|
|
325
|
+
<div className={styles.headerActions}>
|
|
326
|
+
<div className={styles.sortSection}>
|
|
327
|
+
<Text type="help" className={styles.uppercase}>
|
|
328
|
+
SORT BY
|
|
329
|
+
</Text>
|
|
330
|
+
{SortButtonGroup && (
|
|
331
|
+
<SortButtonGroup
|
|
332
|
+
currentSort={sortBy}
|
|
333
|
+
onSortChange={handleSortChange}
|
|
334
|
+
/>
|
|
335
|
+
)}
|
|
336
|
+
</div>
|
|
337
|
+
<div className={styles.toggleContainer}>
|
|
338
|
+
<Text type="body">Show deleted {displayName} listing</Text>
|
|
339
|
+
<ToggleSwitch
|
|
340
|
+
isActive={showDeleted}
|
|
341
|
+
onChange={handleToggleDeleted}
|
|
342
|
+
/>
|
|
343
|
+
</div>
|
|
344
|
+
</div>
|
|
345
|
+
)}
|
|
346
|
+
</div>
|
|
347
|
+
|
|
348
|
+
{/* Action errors (delete, restore, toggle) */}
|
|
349
|
+
{showActionError && (
|
|
350
|
+
<ErrorMessage
|
|
351
|
+
message={actionError}
|
|
352
|
+
variant="error"
|
|
353
|
+
onClose={() => setShowActionError(false)}
|
|
354
|
+
/>
|
|
355
|
+
)}
|
|
356
|
+
|
|
357
|
+
{/* Success messages */}
|
|
358
|
+
{deleteMessage && deleteMessage.includes("success") && (
|
|
359
|
+
<ErrorMessage
|
|
360
|
+
message={deleteMessage}
|
|
361
|
+
variant="success"
|
|
362
|
+
onClose={() => setDeleteMessage("")}
|
|
363
|
+
/>
|
|
364
|
+
)}
|
|
365
|
+
|
|
366
|
+
{/* General listing errors */}
|
|
367
|
+
{error && (
|
|
368
|
+
<ErrorMessage
|
|
369
|
+
message={`Error loading listings: ${error}`}
|
|
370
|
+
variant="error"
|
|
371
|
+
/>
|
|
372
|
+
)}
|
|
373
|
+
|
|
374
|
+
{!hasDefinition && !isDefinitionLoading && (
|
|
375
|
+
<div className={styles.empty}>
|
|
376
|
+
<div className="emptyState" />
|
|
377
|
+
<p>
|
|
378
|
+
No feature definition exists. Please create a feature
|
|
379
|
+
definition first before creating listings.
|
|
380
|
+
</p>
|
|
381
|
+
<Button
|
|
382
|
+
buttonType="primary"
|
|
383
|
+
leftIcon="plus"
|
|
384
|
+
onClick={handleCreateClick}
|
|
385
|
+
isActive={true}
|
|
386
|
+
>
|
|
387
|
+
Add {displayName}
|
|
388
|
+
</Button>
|
|
389
|
+
</div>
|
|
390
|
+
)}
|
|
391
|
+
|
|
392
|
+
{definitionError && (
|
|
393
|
+
<ErrorMessage
|
|
394
|
+
message={`Error loading feature definition: ${definitionError}`}
|
|
395
|
+
variant="error"
|
|
396
|
+
/>
|
|
397
|
+
)}
|
|
398
|
+
|
|
399
|
+
{isLoading ? (
|
|
400
|
+
<div className={styles.loadingContainer}>
|
|
401
|
+
<LoadingState message="Loading data..." />
|
|
402
|
+
</div>
|
|
403
|
+
) : sortedListings.length > 0 ? (
|
|
404
|
+
<Table className="plussTable" striped bordered condensed hover>
|
|
405
|
+
<thead>
|
|
406
|
+
<tr key="header">
|
|
407
|
+
<th className={styles.dateColumn}>Last modified at</th>
|
|
408
|
+
<th>Title</th>
|
|
409
|
+
<th className={styles.actionsColumn}>Actions</th>
|
|
410
|
+
</tr>
|
|
411
|
+
</thead>
|
|
412
|
+
<tbody>{renderTableRows()}</tbody>
|
|
413
|
+
</Table>
|
|
414
|
+
) : hasDefinition ? (
|
|
415
|
+
// Smart empty state logic
|
|
416
|
+
activeListings.length === 0 &&
|
|
417
|
+
deletedListings.length > 0 &&
|
|
418
|
+
!showDeleted ? (
|
|
419
|
+
<div className={styles.empty}>
|
|
420
|
+
<div className="emptyState" />
|
|
421
|
+
<p>
|
|
422
|
+
You have {deletedListings.length} deleted{" "}
|
|
423
|
+
{displayName.toLowerCase()} listing
|
|
424
|
+
{deletedListings.length > 1 ? "s" : ""}.
|
|
425
|
+
</p>
|
|
426
|
+
<div className={styles.emptyActions}>
|
|
427
|
+
<Button
|
|
428
|
+
buttonType="primary"
|
|
429
|
+
leftIcon="plus"
|
|
430
|
+
onClick={handleCreateClick}
|
|
431
|
+
className={styles.emptySecondaryButton}
|
|
432
|
+
isActive
|
|
433
|
+
>
|
|
434
|
+
Add new {displayName}
|
|
435
|
+
</Button>
|
|
436
|
+
<Button
|
|
437
|
+
buttonType="secondary"
|
|
438
|
+
leftIcon="eye"
|
|
439
|
+
onClick={handleShowDeletedListings}
|
|
440
|
+
className={styles.emptyPrimaryButton}
|
|
441
|
+
>
|
|
442
|
+
Show deleted listing
|
|
443
|
+
{deletedListings.length > 1 ? "s" : ""}
|
|
444
|
+
</Button>
|
|
445
|
+
</div>
|
|
446
|
+
</div>
|
|
447
|
+
) : (
|
|
448
|
+
// Regular empty state
|
|
449
|
+
<div className={styles.empty}>
|
|
450
|
+
<div className="emptyState" />
|
|
451
|
+
<p>No {displayName.toLowerCase()} listings found.</p>
|
|
452
|
+
<Button
|
|
453
|
+
buttonType="primary"
|
|
454
|
+
leftIcon="plus"
|
|
455
|
+
onClick={handleCreateClick}
|
|
456
|
+
isActive
|
|
457
|
+
>
|
|
458
|
+
Add {displayName}
|
|
459
|
+
</Button>
|
|
460
|
+
</div>
|
|
461
|
+
)
|
|
462
|
+
) : null}
|
|
463
|
+
|
|
464
|
+
<DeleteConfirmationPopup
|
|
465
|
+
isOpen={showDeleteConfirm}
|
|
466
|
+
listing={listingToDelete}
|
|
467
|
+
onConfirm={handleConfirmDelete}
|
|
468
|
+
onCancel={handleCancelDelete}
|
|
469
|
+
isDeleting={isDeleting}
|
|
470
|
+
/>
|
|
471
|
+
</div>
|
|
472
|
+
</div>
|
|
473
|
+
</div>
|
|
474
|
+
</ErrorBoundary>
|
|
475
|
+
);
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
export default withRouter(ListingScreen);
|