@webbycrown/webbycommerce 1.2.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/README.md +21 -3
  2. package/admin/app.js +3 -0
  3. package/admin/jsconfig.json +20 -0
  4. package/admin/src/components/ApiCollectionsContent.jsx +4626 -0
  5. package/admin/src/components/CompareContent.jsx +300 -0
  6. package/admin/src/components/ConfigureContent.jsx +407 -0
  7. package/admin/src/components/Initializer.jsx +64 -0
  8. package/admin/src/components/LoginRegisterContent.jsx +280 -0
  9. package/admin/src/components/PluginIcon.jsx +6 -0
  10. package/admin/src/components/ShippingTypeContent.jsx +230 -0
  11. package/admin/src/components/SmtpContent.jsx +316 -0
  12. package/admin/src/components/WishlistContent.jsx +273 -0
  13. package/admin/src/index.js +81 -0
  14. package/admin/src/pages/ApiCollections.jsx +169 -0
  15. package/admin/src/pages/Configure.jsx +55 -0
  16. package/admin/src/pages/Settings.jsx +93 -0
  17. package/admin/src/pluginId.js +4 -0
  18. package/{dist/_chunks/en-CiQ97iC8.js → admin/src/translations/en.json} +712 -574
  19. package/bin/setup.js +50 -3
  20. package/package.json +14 -13
  21. package/server/bootstrap.js +3 -0
  22. package/server/register.js +3 -0
  23. package/server/src/bootstrap.js +3826 -0
  24. package/server/src/components/content-block.json +37 -0
  25. package/server/src/components/shipping-zone-location.json +27 -0
  26. package/server/src/config/index.js +7 -0
  27. package/server/src/content-types/address/index.js +7 -0
  28. package/server/src/content-types/address/schema.json +74 -0
  29. package/server/src/content-types/cart/index.js +61 -0
  30. package/server/src/content-types/cart-item/index.js +79 -0
  31. package/server/src/content-types/compare.js +73 -0
  32. package/server/src/content-types/coupon/index.js +7 -0
  33. package/server/src/content-types/coupon/schema.json +67 -0
  34. package/server/src/content-types/index.js +42 -0
  35. package/server/src/content-types/order/index.js +7 -0
  36. package/server/src/content-types/order/schema.json +121 -0
  37. package/server/src/content-types/payment-transaction/index.js +7 -0
  38. package/server/src/content-types/payment-transaction/schema.json +73 -0
  39. package/server/src/content-types/product/index.js +7 -0
  40. package/server/src/content-types/product/schema.json +104 -0
  41. package/server/src/content-types/product-attribute/index.js +7 -0
  42. package/server/src/content-types/product-attribute/schema.json +80 -0
  43. package/server/src/content-types/product-attribute-value/index.js +7 -0
  44. package/server/src/content-types/product-attribute-value/schema.json +52 -0
  45. package/server/src/content-types/product-category/index.js +7 -0
  46. package/server/src/content-types/product-category/schema.json +54 -0
  47. package/server/src/content-types/product-tag/index.js +7 -0
  48. package/server/src/content-types/product-tag/schema.json +38 -0
  49. package/server/src/content-types/product-variation/index.js +7 -0
  50. package/server/src/content-types/product-variation/schema.json +74 -0
  51. package/server/src/content-types/shipping-method/index.js +7 -0
  52. package/server/src/content-types/shipping-method/schema.json +91 -0
  53. package/server/src/content-types/shipping-rate/index.js +7 -0
  54. package/server/src/content-types/shipping-rate/schema.json +73 -0
  55. package/server/src/content-types/shipping-rule/index.js +7 -0
  56. package/server/src/content-types/shipping-rule/schema.json +84 -0
  57. package/server/src/content-types/shipping-zone/index.js +7 -0
  58. package/server/src/content-types/shipping-zone/schema.json +57 -0
  59. package/server/src/content-types/wishlist.js +66 -0
  60. package/server/src/controllers/address.js +374 -0
  61. package/server/src/controllers/auth.js +1409 -0
  62. package/server/src/controllers/cart.js +337 -0
  63. package/server/src/controllers/category.js +388 -0
  64. package/server/src/controllers/compare.js +246 -0
  65. package/server/src/controllers/controller.js +168 -0
  66. package/server/src/controllers/ecommerce.js +20 -0
  67. package/server/src/controllers/index.js +34 -0
  68. package/server/src/controllers/order.js +1100 -0
  69. package/server/src/controllers/payment.js +243 -0
  70. package/server/src/controllers/product.js +1006 -0
  71. package/server/src/controllers/productTag.js +370 -0
  72. package/server/src/controllers/productVariation.js +181 -0
  73. package/server/src/controllers/shipping.js +1046 -0
  74. package/server/src/controllers/wishlist.js +332 -0
  75. package/server/src/destroy.js +6 -0
  76. package/server/src/index.js +26 -0
  77. package/server/src/middlewares/index.js +4 -0
  78. package/server/src/policies/index.js +4 -0
  79. package/server/src/register.js +67 -0
  80. package/server/src/routes/index.js +1130 -0
  81. package/server/src/services/cart.js +531 -0
  82. package/server/src/services/compare.js +300 -0
  83. package/server/src/services/index.js +16 -0
  84. package/server/src/services/service.js +19 -0
  85. package/server/src/services/shipping.js +513 -0
  86. package/server/src/services/wishlist.js +238 -0
  87. package/server/src/utils/check-ecommerce-permission.js +204 -0
  88. package/server/src/utils/extend-user-schema.js +161 -0
  89. package/server/src/utils/seed-data.js +639 -0
  90. package/server/src/utils/send-email.js +98 -0
  91. package/strapi-server.js +1 -6
  92. package/dist/_chunks/Settings-Bg2JyQ4c.js +0 -31518
  93. package/dist/_chunks/Settings-BonPzbwr.mjs +0 -31499
  94. package/dist/_chunks/en-DE15m4xZ.mjs +0 -574
  95. package/dist/_chunks/index-BWVy9o1d.mjs +0 -128
  96. package/dist/_chunks/index-NRuOdjd7.js +0 -127
  97. package/dist/admin/index.js +0 -3
  98. package/dist/admin/index.mjs +0 -4
  99. package/dist/robots.txt +0 -3
  100. package/dist/server/index.js +0 -27336
  101. package/dist/uploads/.gitkeep +0 -0
  102. package/dist/uploads/accessories_category_2a5631094b.jpeg +0 -0
  103. package/dist/uploads/beauty_personal_care_category_57f8a8f1e3.jpeg +0 -0
  104. package/dist/uploads/books_category_a9a253eada.jpeg +0 -0
  105. package/dist/uploads/classic_cotton_tshirt_1_cd713425f6.png +0 -0
  106. package/dist/uploads/clothing_category_d5c60ef07b.jpeg +0 -0
  107. package/dist/uploads/daviddoe_strapi_adbcd41787.jpeg +0 -0
  108. package/dist/uploads/electronics_category_fc3e5ef571.jpeg +0 -0
  109. package/dist/uploads/ergonomic_office_chair_1_c751cffb07.png +0 -0
  110. package/dist/uploads/home_garden_category_4f6eb3f8d6.jpeg +0 -0
  111. package/dist/uploads/istockphoto_1188462138_612x612_11f295b9c0.jpg +0 -0
  112. package/dist/uploads/istockphoto_1188462138_612x612_396fb272fd.jpg +0 -0
  113. package/dist/uploads/large_daviddoe_strapi_adbcd41787.jpeg +0 -0
  114. package/dist/uploads/leather_travel_backpack_1_238bc1ae4d.png +0 -0
  115. package/dist/uploads/mechanical_keyboard_pro_1_0cd391a6ac.png +0 -0
  116. package/dist/uploads/medium_classic_cotton_tshirt_1_cd713425f6.png +0 -0
  117. package/dist/uploads/medium_daviddoe_strapi_adbcd41787.jpeg +0 -0
  118. package/dist/uploads/medium_ergonomic_office_chair_1_c751cffb07.png +0 -0
  119. package/dist/uploads/medium_leather_travel_backpack_1_238bc1ae4d.png +0 -0
  120. package/dist/uploads/medium_mechanical_keyboard_pro_1_0cd391a6ac.png +0 -0
  121. package/dist/uploads/medium_smart_watch_series_5_1_cdc2511fb7.png +0 -0
  122. package/dist/uploads/medium_smartphone_x_pro_1_c3f0cbd080.png +0 -0
  123. package/dist/uploads/medium_the_great_gatsby_special_1_2e7c76d997.png +0 -0
  124. package/dist/uploads/medium_wireless_headphones_1_fa75cd50c3.png +0 -0
  125. package/dist/uploads/medium_yoga_mat_premium_1_01f9a3b5fa.png +0 -0
  126. package/dist/uploads/predictive_maintenance_icons_industry_automation_600nw_2685943461_e18a8aa3b0.webp +0 -0
  127. package/dist/uploads/small_classic_cotton_tshirt_1_cd713425f6.png +0 -0
  128. package/dist/uploads/small_daviddoe_strapi_adbcd41787.jpeg +0 -0
  129. package/dist/uploads/small_ergonomic_office_chair_1_c751cffb07.png +0 -0
  130. package/dist/uploads/small_leather_travel_backpack_1_238bc1ae4d.png +0 -0
  131. package/dist/uploads/small_mechanical_keyboard_pro_1_0cd391a6ac.png +0 -0
  132. package/dist/uploads/small_smart_watch_series_5_1_cdc2511fb7.png +0 -0
  133. package/dist/uploads/small_smartphone_x_pro_1_c3f0cbd080.png +0 -0
  134. package/dist/uploads/small_the_great_gatsby_special_1_2e7c76d997.png +0 -0
  135. package/dist/uploads/small_wireless_headphones_1_fa75cd50c3.png +0 -0
  136. package/dist/uploads/small_yoga_mat_premium_1_01f9a3b5fa.png +0 -0
  137. package/dist/uploads/smart_watch_series_5_1_cdc2511fb7.png +0 -0
  138. package/dist/uploads/smartphone_x_pro_1_c3f0cbd080.png +0 -0
  139. package/dist/uploads/the_great_gatsby_special_1_2e7c76d997.png +0 -0
  140. package/dist/uploads/thumbnail_accessories_category_2a5631094b.jpeg +0 -0
  141. package/dist/uploads/thumbnail_beauty_personal_care_category_57f8a8f1e3.jpeg +0 -0
  142. package/dist/uploads/thumbnail_books_category_a9a253eada.jpeg +0 -0
  143. package/dist/uploads/thumbnail_classic_cotton_tshirt_1_cd713425f6.png +0 -0
  144. package/dist/uploads/thumbnail_clothing_category_d5c60ef07b.jpeg +0 -0
  145. package/dist/uploads/thumbnail_daviddoe_strapi_adbcd41787.jpeg +0 -0
  146. package/dist/uploads/thumbnail_electronics_category_fc3e5ef571.jpeg +0 -0
  147. package/dist/uploads/thumbnail_ergonomic_office_chair_1_c751cffb07.png +0 -0
  148. package/dist/uploads/thumbnail_home_garden_category_4f6eb3f8d6.jpeg +0 -0
  149. package/dist/uploads/thumbnail_istockphoto_1188462138_612x612_11f295b9c0.jpg +0 -0
  150. package/dist/uploads/thumbnail_istockphoto_1188462138_612x612_396fb272fd.jpg +0 -0
  151. package/dist/uploads/thumbnail_leather_travel_backpack_1_238bc1ae4d.png +0 -0
  152. package/dist/uploads/thumbnail_mechanical_keyboard_pro_1_0cd391a6ac.png +0 -0
  153. package/dist/uploads/thumbnail_predictive_maintenance_icons_industry_automation_600nw_2685943461_e18a8aa3b0.webp +0 -0
  154. package/dist/uploads/thumbnail_smart_watch_series_5_1_cdc2511fb7.png +0 -0
  155. package/dist/uploads/thumbnail_smartphone_x_pro_1_c3f0cbd080.png +0 -0
  156. package/dist/uploads/thumbnail_the_great_gatsby_special_1_2e7c76d997.png +0 -0
  157. package/dist/uploads/thumbnail_wireless_headphones_1_fa75cd50c3.png +0 -0
  158. package/dist/uploads/thumbnail_yoga_mat_premium_1_01f9a3b5fa.png +0 -0
  159. package/dist/uploads/webby-commerce.png +0 -0
  160. package/dist/uploads/wireless_headphones_1_fa75cd50c3.png +0 -0
  161. package/dist/uploads/yoga_mat_premium_1_01f9a3b5fa.png +0 -0
  162. /package/{dist → server/src}/data/demo-data.json +0 -0
@@ -0,0 +1,316 @@
1
+ 'use strict';
2
+
3
+ import React, { useEffect, useState } from 'react';
4
+ import { useIntl } from 'react-intl';
5
+ import { Box, Flex, Typography, TextInput, Button } from '@strapi/design-system';
6
+ import { useFetchClient } from '@strapi/admin/strapi-admin';
7
+
8
+ import { PLUGIN_ID } from '../pluginId';
9
+
10
+ const SmtpContent = () => {
11
+ const { formatMessage } = useIntl();
12
+ const fetchClient = useFetchClient();
13
+
14
+ const [smtpSettings, setSmtpSettings] = useState({
15
+ host: '',
16
+ port: '587',
17
+ secure: false,
18
+ username: '',
19
+ password: '',
20
+ from: '',
21
+ fromName: '',
22
+ rejectUnauthorized: true,
23
+ });
24
+ const [isLoading, setIsLoading] = useState(false);
25
+ const [isSaving, setIsSaving] = useState(false);
26
+ const [feedback, setFeedback] = useState(null);
27
+
28
+ const title = formatMessage({
29
+ id: `${PLUGIN_ID}.settings.smtp.title`,
30
+ defaultMessage: 'SMTP Configuration',
31
+ });
32
+
33
+ const description = formatMessage({
34
+ id: `${PLUGIN_ID}.settings.smtp.description`,
35
+ defaultMessage:
36
+ 'Configure SMTP settings to send OTP emails. If not configured, Strapi\'s default email plugin will be used.',
37
+ });
38
+
39
+ useEffect(() => {
40
+ let isMounted = true;
41
+ const loadSettings = async () => {
42
+ try {
43
+ setIsLoading(true);
44
+ const { data } = await fetchClient.get(`/webbycommerce/settings`);
45
+ const smtp = data?.smtp || {};
46
+ if (!isMounted) return;
47
+
48
+ setSmtpSettings({
49
+ host: smtp.host || '',
50
+ port: smtp.port || '587',
51
+ secure: smtp.secure === true,
52
+ username: smtp.username || '',
53
+ password: smtp.password || '',
54
+ from: smtp.from || '',
55
+ fromName: smtp.fromName || '',
56
+ rejectUnauthorized: smtp.rejectUnauthorized !== false,
57
+ });
58
+ } catch (error) {
59
+ if (isMounted) {
60
+ setFeedback({
61
+ type: 'error',
62
+ message: formatMessage({
63
+ id: `${PLUGIN_ID}.settings.smtp.load.error`,
64
+ defaultMessage: 'Failed to load SMTP settings.',
65
+ }),
66
+ });
67
+ }
68
+ } finally {
69
+ if (isMounted) {
70
+ setIsLoading(false);
71
+ }
72
+ }
73
+ };
74
+
75
+ loadSettings();
76
+
77
+ return () => {
78
+ isMounted = false;
79
+ };
80
+ }, [fetchClient, formatMessage]);
81
+
82
+ const handleSave = async () => {
83
+ try {
84
+ setIsSaving(true);
85
+ setFeedback(null);
86
+
87
+ const { data } = await fetchClient.put(`/webbycommerce/settings`, {
88
+ smtp: smtpSettings,
89
+ });
90
+
91
+ setFeedback({
92
+ type: 'success',
93
+ message: formatMessage({
94
+ id: `${PLUGIN_ID}.settings.smtp.save.success`,
95
+ defaultMessage: 'SMTP settings updated successfully.',
96
+ }),
97
+ });
98
+ } catch (error) {
99
+ setFeedback({
100
+ type: 'error',
101
+ message: formatMessage({
102
+ id: `${PLUGIN_ID}.settings.smtp.save.error`,
103
+ defaultMessage: 'Failed to save SMTP settings.',
104
+ }),
105
+ });
106
+ } finally {
107
+ setIsSaving(false);
108
+ }
109
+ };
110
+
111
+ return (
112
+ <Box paddingTop={6}>
113
+ <Typography variant="beta" textColor="neutral800">
114
+ {title}
115
+ </Typography>
116
+
117
+ <Box marginTop={2}>
118
+ <Typography variant="pi" textColor="neutral600">
119
+ {description}
120
+ </Typography>
121
+ </Box>
122
+
123
+ <Box marginTop={6}>
124
+ <Box
125
+ background="neutral0"
126
+ hasRadius
127
+ shadow="filterShadow"
128
+ padding={4}
129
+ style={{ maxWidth: '640px' }}
130
+ >
131
+ <Typography variant="delta" textColor="neutral800">
132
+ {formatMessage({
133
+ id: `${PLUGIN_ID}.settings.configure.smtp.title`,
134
+ defaultMessage: 'SMTP Configuration (for OTP emails)',
135
+ })}
136
+ </Typography>
137
+ <Box marginTop={1}>
138
+ <Typography variant="pi" textColor="neutral600">
139
+ {formatMessage({
140
+ id: `${PLUGIN_ID}.settings.configure.smtp.description`,
141
+ defaultMessage:
142
+ 'Configure SMTP settings to send OTP emails. If not configured, Strapi\'s default email plugin will be used.',
143
+ })}
144
+ </Typography>
145
+ </Box>
146
+
147
+ <Box marginTop={4}>
148
+ <Flex direction="column" gap={4} alignItems="start">
149
+ <TextInput
150
+ name="smtp-host"
151
+ label={formatMessage({
152
+ id: `${PLUGIN_ID}.settings.configure.smtp.host.label`,
153
+ defaultMessage: 'SMTP Host',
154
+ })}
155
+ placeholder={formatMessage({
156
+ id: `${PLUGIN_ID}.settings.configure.smtp.host.placeholder`,
157
+ defaultMessage: 'e.g. smtp.gmail.com',
158
+ })}
159
+ value={smtpSettings.host}
160
+ onChange={(event) =>
161
+ setSmtpSettings({ ...smtpSettings, host: event.target.value })
162
+ }
163
+ disabled={isLoading || isSaving}
164
+ />
165
+
166
+ <Flex gap={3} alignItems="flex-end">
167
+ <Box style={{ flex: 1 }}>
168
+ <TextInput
169
+ name="smtp-port"
170
+ label={formatMessage({
171
+ id: `${PLUGIN_ID}.settings.configure.smtp.port.label`,
172
+ defaultMessage: 'SMTP Port',
173
+ })}
174
+ placeholder="587"
175
+ value={smtpSettings.port}
176
+ onChange={(event) =>
177
+ setSmtpSettings({ ...smtpSettings, port: event.target.value })
178
+ }
179
+ disabled={isLoading || isSaving}
180
+ type="number"
181
+ />
182
+ </Box>
183
+ <Box style={{ flex: 1 }}>
184
+ <Box marginBottom={2}>
185
+ <Typography variant="pi" textColor="neutral800" fontWeight="semiBold">
186
+ {formatMessage({
187
+ id: `${PLUGIN_ID}.settings.configure.smtp.secure.label`,
188
+ defaultMessage: 'Secure (TLS/SSL)',
189
+ })}
190
+ </Typography>
191
+ </Box>
192
+ <Flex alignItems="center" gap={2}>
193
+ <input
194
+ type="checkbox"
195
+ id="smtp-secure"
196
+ checked={smtpSettings.secure}
197
+ onChange={(event) =>
198
+ setSmtpSettings({
199
+ ...smtpSettings,
200
+ secure: event.target.checked,
201
+ })
202
+ }
203
+ disabled={isLoading || isSaving}
204
+ style={{
205
+ width: '16px',
206
+ height: '16px',
207
+ accentColor: '#4945ff',
208
+ cursor: isLoading || isSaving ? 'not-allowed' : 'pointer',
209
+ }}
210
+ />
211
+ <Typography variant="pi" textColor="neutral600">
212
+ {formatMessage({
213
+ id: `${PLUGIN_ID}.settings.configure.smtp.secure.hint`,
214
+ defaultMessage: 'Enable for port 465 (SSL), disable for port 587 (TLS)',
215
+ })}
216
+ </Typography>
217
+ </Flex>
218
+ </Box>
219
+ </Flex>
220
+
221
+ <TextInput
222
+ name="smtp-username"
223
+ label={formatMessage({
224
+ id: `${PLUGIN_ID}.settings.configure.smtp.username.label`,
225
+ defaultMessage: 'SMTP Username',
226
+ })}
227
+ placeholder={formatMessage({
228
+ id: `${PLUGIN_ID}.settings.configure.smtp.username.placeholder`,
229
+ defaultMessage: 'Your email address',
230
+ })}
231
+ value={smtpSettings.username}
232
+ onChange={(event) =>
233
+ setSmtpSettings({ ...smtpSettings, username: event.target.value })
234
+ }
235
+ disabled={isLoading || isSaving}
236
+ />
237
+
238
+ <TextInput
239
+ name="smtp-password"
240
+ label={formatMessage({
241
+ id: `${PLUGIN_ID}.settings.configure.smtp.password.label`,
242
+ defaultMessage: 'SMTP Password',
243
+ })}
244
+ placeholder={formatMessage({
245
+ id: `${PLUGIN_ID}.settings.configure.smtp.password.placeholder`,
246
+ defaultMessage: 'Your email password or app password',
247
+ })}
248
+ value={smtpSettings.password}
249
+ onChange={(event) =>
250
+ setSmtpSettings({ ...smtpSettings, password: event.target.value })
251
+ }
252
+ disabled={isLoading || isSaving}
253
+ type="password"
254
+ />
255
+
256
+ <TextInput
257
+ name="smtp-from"
258
+ label={formatMessage({
259
+ id: `${PLUGIN_ID}.settings.configure.smtp.from.label`,
260
+ defaultMessage: 'From Email',
261
+ })}
262
+ placeholder={formatMessage({
263
+ id: `${PLUGIN_ID}.settings.configure.smtp.from.placeholder`,
264
+ defaultMessage: 'noreply@example.com',
265
+ })}
266
+ value={smtpSettings.from}
267
+ onChange={(event) =>
268
+ setSmtpSettings({ ...smtpSettings, from: event.target.value })
269
+ }
270
+ disabled={isLoading || isSaving}
271
+ />
272
+
273
+ <TextInput
274
+ name="smtp-from-name"
275
+ label={formatMessage({
276
+ id: `${PLUGIN_ID}.settings.configure.smtp.fromName.label`,
277
+ defaultMessage: 'From Name',
278
+ })}
279
+ placeholder={formatMessage({
280
+ id: `${PLUGIN_ID}.settings.configure.smtp.fromName.placeholder`,
281
+ defaultMessage: 'Strapi Advanced Ecommerce',
282
+ })}
283
+ value={smtpSettings.fromName}
284
+ onChange={(event) =>
285
+ setSmtpSettings({ ...smtpSettings, fromName: event.target.value })
286
+ }
287
+ disabled={isLoading || isSaving}
288
+ />
289
+ </Flex>
290
+ </Box>
291
+ </Box>
292
+
293
+ <Box marginTop={6}>
294
+ <Flex gap={3} alignItems="center">
295
+ <Button onClick={handleSave} loading={isSaving} disabled={isLoading || isSaving}>
296
+ {formatMessage({
297
+ id: `${PLUGIN_ID}.settings.smtp.save`,
298
+ defaultMessage: 'Save SMTP settings',
299
+ })}
300
+ </Button>
301
+ {feedback && (
302
+ <Typography
303
+ variant="pi"
304
+ textColor={feedback.type === 'error' ? 'danger600' : 'success600'}
305
+ >
306
+ {feedback.message}
307
+ </Typography>
308
+ )}
309
+ </Flex>
310
+ </Box>
311
+ </Box>
312
+ </Box>
313
+ );
314
+ };
315
+
316
+ export default SmtpContent;
@@ -0,0 +1,273 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import {
3
+ Layout,
4
+ HeaderLayout,
5
+ ContentLayout,
6
+ ActionLayout,
7
+ } from '@strapi/design-system/Layout';
8
+ import {
9
+ Box,
10
+ Typography,
11
+ Button,
12
+ Table,
13
+ Thead,
14
+ Tbody,
15
+ Tr,
16
+ Td,
17
+ Th,
18
+ Avatar,
19
+ Badge,
20
+ Flex,
21
+ IconButton,
22
+ } from '@strapi/design-system';
23
+ import {
24
+ Eye,
25
+ Pencil,
26
+ Trash,
27
+ Plus,
28
+ } from '@strapi/icons';
29
+ import { useIntl } from 'react-intl';
30
+ import { useFetchClient } from '@strapi/admin/strapi-admin';
31
+ import { useNotification } from '@strapi/admin/strapi-admin';
32
+
33
+ const WishlistContent = () => {
34
+ const { formatMessage } = useIntl();
35
+ const { get, del } = useFetchClient();
36
+ const { toggleNotification } = useNotification();
37
+
38
+ const [wishlists, setWishlists] = useState([]);
39
+ const [loading, setLoading] = useState(true);
40
+ const [selectedWishlist, setSelectedWishlist] = useState(null);
41
+
42
+ useEffect(() => {
43
+ fetchWishlists();
44
+ }, []);
45
+
46
+ const fetchWishlists = async () => {
47
+ try {
48
+ setLoading(true);
49
+ const response = await get('/webbycommerce/wishlists');
50
+ setWishlists(response.data.data || []);
51
+ } catch (error) {
52
+ console.error('Error fetching wishlists:', error);
53
+ toggleNotification({
54
+ type: 'warning',
55
+ message: 'Failed to fetch wishlists',
56
+ });
57
+ } finally {
58
+ setLoading(false);
59
+ }
60
+ };
61
+
62
+ const handleDeleteWishlist = async (wishlistId) => {
63
+ if (!confirm('Are you sure you want to delete this wishlist?')) {
64
+ return;
65
+ }
66
+
67
+ try {
68
+ await del(`/webbycommerce/wishlists/${wishlistId}`);
69
+ setWishlists(wishlists.filter(w => w.id !== wishlistId));
70
+ toggleNotification({
71
+ type: 'success',
72
+ message: 'Wishlist deleted successfully',
73
+ });
74
+ } catch (error) {
75
+ console.error('Error deleting wishlist:', error);
76
+ toggleNotification({
77
+ type: 'warning',
78
+ message: 'Failed to delete wishlist',
79
+ });
80
+ }
81
+ };
82
+
83
+ const formatDate = (dateString) => {
84
+ return new Date(dateString).toLocaleDateString();
85
+ };
86
+
87
+ return (
88
+ <Layout>
89
+ <HeaderLayout
90
+ title="Wishlist Management"
91
+ subtitle="Manage user wishlists and favorite products"
92
+ as="h2"
93
+ />
94
+ <ActionLayout
95
+ startActions={
96
+ <Button
97
+ onClick={fetchWishlists}
98
+ loading={loading}
99
+ variant="secondary"
100
+ >
101
+ Refresh
102
+ </Button>
103
+ }
104
+ />
105
+ <ContentLayout>
106
+ <Box
107
+ background="neutral0"
108
+ hasRadius
109
+ shadow="filterShadow"
110
+ padding={6}
111
+ >
112
+ {loading ? (
113
+ <Box padding={4}>
114
+ <Typography>Loading wishlists...</Typography>
115
+ </Box>
116
+ ) : wishlists.length === 0 ? (
117
+ <Box padding={4}>
118
+ <Typography>No wishlists found.</Typography>
119
+ </Box>
120
+ ) : (
121
+ <Table colCount={5} rowCount={wishlists.length}>
122
+ <Thead>
123
+ <Tr>
124
+ <Th>
125
+ <Typography variant="sigma">User</Typography>
126
+ </Th>
127
+ <Th>
128
+ <Typography variant="sigma">Products</Typography>
129
+ </Th>
130
+ <Th>
131
+ <Typography variant="sigma">Public</Typography>
132
+ </Th>
133
+ <Th>
134
+ <Typography variant="sigma">Created</Typography>
135
+ </Th>
136
+ <Th>
137
+ <Typography variant="sigma">Actions</Typography>
138
+ </Th>
139
+ </Tr>
140
+ </Thead>
141
+ <Tbody>
142
+ {wishlists.map((wishlist) => (
143
+ <Tr key={wishlist.id}>
144
+ <Td>
145
+ <Flex>
146
+ <Avatar
147
+ src={null}
148
+ alt={wishlist.userEmail}
149
+ initials={wishlist.userEmail?.charAt(0).toUpperCase()}
150
+ />
151
+ <Box marginLeft={2}>
152
+ <Typography>{wishlist.userEmail}</Typography>
153
+ <Typography variant="pi" textColor="neutral600">
154
+ ID: {wishlist.userId}
155
+ </Typography>
156
+ </Box>
157
+ </Flex>
158
+ </Td>
159
+ <Td>
160
+ <Badge>{wishlist.products?.length || 0} products</Badge>
161
+ </Td>
162
+ <Td>
163
+ <Badge color={wishlist.isPublic ? 'success' : 'neutral'}>
164
+ {wishlist.isPublic ? 'Public' : 'Private'}
165
+ </Badge>
166
+ </Td>
167
+ <Td>
168
+ <Typography>{formatDate(wishlist.createdAt)}</Typography>
169
+ </Td>
170
+ <Td>
171
+ <Flex gap={1}>
172
+ <IconButton
173
+ onClick={() => setSelectedWishlist(wishlist)}
174
+ label="View wishlist"
175
+ icon={<Eye />}
176
+ />
177
+ <IconButton
178
+ onClick={() => handleDeleteWishlist(wishlist.id)}
179
+ label="Delete wishlist"
180
+ icon={<Trash />}
181
+ />
182
+ </Flex>
183
+ </Td>
184
+ </Tr>
185
+ ))}
186
+ </Tbody>
187
+ </Table>
188
+ )}
189
+ </Box>
190
+
191
+ {/* Wishlist Detail Modal/View */}
192
+ {selectedWishlist && (
193
+ <Box
194
+ background="neutral0"
195
+ hasRadius
196
+ shadow="filterShadow"
197
+ padding={6}
198
+ marginTop={4}
199
+ >
200
+ <Typography variant="beta" marginBottom={4}>
201
+ Wishlist Details - {selectedWishlist.userEmail}
202
+ </Typography>
203
+
204
+ {selectedWishlist.name && (
205
+ <Typography variant="epsilon" marginBottom={2}>
206
+ Name: {selectedWishlist.name}
207
+ </Typography>
208
+ )}
209
+
210
+ {selectedWishlist.description && (
211
+ <Typography variant="pi" marginBottom={4}>
212
+ Description: {selectedWishlist.description}
213
+ </Typography>
214
+ )}
215
+
216
+ <Typography variant="delta" marginBottom={3}>
217
+ Products ({selectedWishlist.products?.length || 0}):
218
+ </Typography>
219
+
220
+ {selectedWishlist.products?.length > 0 ? (
221
+ <Box>
222
+ {selectedWishlist.products.map((product) => (
223
+ <Flex
224
+ key={product.id}
225
+ alignItems="center"
226
+ padding={3}
227
+ background="neutral100"
228
+ hasRadius
229
+ marginBottom={2}
230
+ >
231
+ {product.images?.[0] && (
232
+ <Box marginRight={3}>
233
+ <img
234
+ src={product.images[0].url}
235
+ alt={product.name}
236
+ style={{ width: 50, height: 50, objectFit: 'cover', borderRadius: 4 }}
237
+ />
238
+ </Box>
239
+ )}
240
+ <Box flex={1}>
241
+ <Typography variant="omega">{product.name}</Typography>
242
+ <Typography variant="pi" textColor="neutral600">
243
+ ${product.price || 'N/A'}
244
+ </Typography>
245
+ </Box>
246
+ <Badge color={product.stockQuantity > 0 ? 'success' : 'danger'}>
247
+ {product.stockQuantity > 0 ? 'In Stock' : 'Out of Stock'}
248
+ </Badge>
249
+ </Flex>
250
+ ))}
251
+ </Box>
252
+ ) : (
253
+ <Typography variant="pi" textColor="neutral600">
254
+ No products in this wishlist
255
+ </Typography>
256
+ )}
257
+
258
+ <Flex marginTop={4} justifyContent="flex-end">
259
+ <Button
260
+ onClick={() => setSelectedWishlist(null)}
261
+ variant="tertiary"
262
+ >
263
+ Close
264
+ </Button>
265
+ </Flex>
266
+ </Box>
267
+ )}
268
+ </ContentLayout>
269
+ </Layout>
270
+ );
271
+ };
272
+
273
+ export default WishlistContent;
@@ -0,0 +1,81 @@
1
+ import { PLUGIN_ID } from './pluginId';
2
+ import Initializer from './components/Initializer';
3
+ import PluginIcon from './components/PluginIcon';
4
+
5
+ // Fix for Strapi 5.x: Provide checkUserHasPermissions globally
6
+ // This is a workaround for a known Strapi 5.x RBAC issue
7
+ if (typeof globalThis !== 'undefined') {
8
+ if (!globalThis.checkUserHasPermissions) {
9
+ globalThis.checkUserHasPermissions = async () => true;
10
+ }
11
+ }
12
+
13
+ export default {
14
+ register(app) {
15
+ // Ensure checkUserHasPermissions is available in app context
16
+ if (app && typeof app.checkUserHasPermissions === 'undefined') {
17
+ app.checkUserHasPermissions = async () => true;
18
+ }
19
+
20
+ app.registerPlugin({
21
+ id: PLUGIN_ID,
22
+ initializer: Initializer,
23
+ // Use a function for isReady to ensure proper initialization order
24
+ // This helps prevent conflicts with Strapi's core (tours, etc.)
25
+ isReady: () => true,
26
+ name: 'WebbyCommerce',
27
+ });
28
+
29
+ // Settings page: Advanced Ecommerce -> Configure (single page with tabs)
30
+ app.addSettingsLink(
31
+ {
32
+ id: 'webbycommerce',
33
+ intlLabel: {
34
+ id: `${PLUGIN_ID}.settings.section`,
35
+ defaultMessage: 'WebbyCommerce',
36
+ },
37
+ icon: PluginIcon,
38
+ },
39
+ {
40
+ id: `${PLUGIN_ID}.settings.configure`,
41
+ intlLabel: {
42
+ id: `${PLUGIN_ID}.settings.configure.title`,
43
+ defaultMessage: 'Configure',
44
+ },
45
+ to: `${PLUGIN_ID}`,
46
+ Component: () => import('./pages/Settings'),
47
+ }
48
+ );
49
+ },
50
+
51
+ bootstrap(app) {
52
+ // Bootstrap logic if needed
53
+ // Ensure checkUserHasPermissions is available
54
+ if (app && typeof app.checkUserHasPermissions === 'undefined') {
55
+ app.checkUserHasPermissions = async () => true;
56
+ }
57
+ },
58
+
59
+ async registerTrads({ locales }) {
60
+ return Promise.all(
61
+ locales.map(async (locale) => {
62
+ if (locale === 'en') {
63
+ try {
64
+ const { default: data } = await import('./translations/en.json');
65
+ return { data, locale };
66
+ } catch {
67
+ return { data: {}, locale };
68
+ }
69
+ }
70
+
71
+ try {
72
+ const { default: data } = await import(`./translations/${locale}.json`);
73
+ return { data, locale };
74
+ } catch {
75
+ return { data: {}, locale };
76
+ }
77
+ })
78
+ );
79
+ },
80
+ };
81
+