@fleetbase/storefront-engine 0.3.30 → 0.4.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/addon/components/customer-panel/orders.js +1 -1
- package/addon/components/modals/share-network.hbs +1 -1
- package/addon/components/network-category-picker.hbs +2 -1
- package/addon/components/network-category-picker.js +52 -22
- package/addon/components/widget/storefront-key-metrics.js +1 -1
- package/addon/controllers/customers/index.js +2 -2
- package/addon/controllers/networks/index/network/orders.js +1 -2
- package/addon/controllers/networks/index/network/stores.js +69 -64
- package/addon/controllers/networks/index.js +1 -2
- package/addon/controllers/products/index/category.js +1 -2
- package/addon/controllers/products/index/index.js +1 -2
- package/addon/routes/networks/index/network/stores.js +6 -5
- package/addon/styles/storefront-engine.css +7 -0
- package/addon/templates/networks/index/network/stores.hbs +8 -1
- package/addon/templates/networks/index.hbs +1 -1
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +2 -3
- package/server/config/database.connections.php +2 -1
- package/server/src/Http/Controllers/NetworkController.php +20 -3
- package/server/src/Http/Controllers/v1/ProductController.php +309 -1
- package/server/src/Http/Requests/CreateProductRequest.php +46 -0
- package/server/src/Http/Requests/UpdateProductRequest.php +7 -0
- package/server/src/Http/Resources/CatalogCategory.php +3 -2
- package/server/src/Http/Resources/Category.php +3 -1
- package/server/src/Http/Resources/Customer.php +2 -1
- package/server/src/Http/Resources/Product.php +2 -0
- package/server/src/Models/Network.php +2 -1
- package/server/src/Models/Store.php +2 -1
- package/server/src/routes.php +3 -0
- package/translations/en-us.yaml +3 -0
|
@@ -3,7 +3,7 @@ import { tracked } from '@glimmer/tracking';
|
|
|
3
3
|
import { inject as service } from '@ember/service';
|
|
4
4
|
import { action, get } from '@ember/object';
|
|
5
5
|
import { debug } from '@ember/debug';
|
|
6
|
-
import { task } from 'ember-concurrency
|
|
6
|
+
import { task } from 'ember-concurrency';
|
|
7
7
|
|
|
8
8
|
export default class CustomerPanelOrdersComponent extends Component {
|
|
9
9
|
@service store;
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
</div>
|
|
30
30
|
</div>
|
|
31
31
|
|
|
32
|
-
<div class="rounded-md border dark:border-
|
|
32
|
+
<div class="rounded-md border dark:border-gray-800 dark:bg-gray-800 p-4">
|
|
33
33
|
<h4 class="dark:text-gray-50 mb-4 font-semibold">{{t "storefront.component.modals.share-network.get-link"}}</h4>
|
|
34
34
|
<div class="input-group mb-0i">
|
|
35
35
|
<Toggle
|
|
@@ -8,11 +8,12 @@
|
|
|
8
8
|
@buttonClass={{concat "w-full truncate w-48" " " @buttonClass}}
|
|
9
9
|
@buttonWrapperClass={{concat "w-full" " " @buttonWrapperClass}}
|
|
10
10
|
@wrapperClass={{@wrapperClass}}
|
|
11
|
+
{{did-update this.updateArgs @category}}
|
|
11
12
|
as |dd|
|
|
12
13
|
>
|
|
13
14
|
<div role="menu" class="store-selector-dropdown-menu next-dd-menu py-1">
|
|
14
15
|
<div role="group" class="px-1 overflow-y-scroll max-h-72">
|
|
15
|
-
{{#if this.
|
|
16
|
+
{{#if this.loadCategories.isRunning}}
|
|
16
17
|
<div class="text-sm flex flex-row items-center px-3 py-0.5 border-0 my-1">
|
|
17
18
|
<Spinner class="mr-2i" />
|
|
18
19
|
<span class="dark:text-gray-100 test-sm">{{t "storefront.common.loading"}}</span>
|
|
@@ -2,6 +2,8 @@ import Component from '@glimmer/component';
|
|
|
2
2
|
import { tracked } from '@glimmer/tracking';
|
|
3
3
|
import { inject as service } from '@ember/service';
|
|
4
4
|
import { action } from '@ember/object';
|
|
5
|
+
import { debug } from '@ember/debug';
|
|
6
|
+
import { task } from 'ember-concurrency';
|
|
5
7
|
import isModel from '@fleetbase/ember-core/utils/is-model';
|
|
6
8
|
|
|
7
9
|
export default class NetworkCategoryPickerComponent extends Component {
|
|
@@ -11,20 +13,27 @@ export default class NetworkCategoryPickerComponent extends Component {
|
|
|
11
13
|
@tracked categories = [];
|
|
12
14
|
@tracked selectedCategory;
|
|
13
15
|
@tracked network;
|
|
14
|
-
@tracked
|
|
15
|
-
@tracked buttonTitle = null;
|
|
16
|
+
@tracked buttonTitle = 'Select Category';
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
context = {
|
|
19
|
+
loadCategories: this.loadCategories,
|
|
20
|
+
loadParentCategories: this.loadParentCategories,
|
|
21
|
+
onSelectCategory: this.onSelectCategory,
|
|
22
|
+
onCreateNewCategory: this.onCreateNewCategory,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
constructor(owner, { network, category, onReady }) {
|
|
18
26
|
super(...arguments);
|
|
19
|
-
this.network =
|
|
20
|
-
this.category
|
|
21
|
-
|
|
22
|
-
|
|
27
|
+
this.network = network;
|
|
28
|
+
this.setCategory(category);
|
|
29
|
+
|
|
30
|
+
if (typeof onReady === 'function') {
|
|
31
|
+
onReady(this.context);
|
|
32
|
+
}
|
|
23
33
|
}
|
|
24
34
|
|
|
25
35
|
setButtonTitle(selectedCategory) {
|
|
26
36
|
let buttonTitle = this.args.buttonTitle ?? 'Select Category';
|
|
27
|
-
|
|
28
37
|
if (selectedCategory) {
|
|
29
38
|
buttonTitle = selectedCategory.name;
|
|
30
39
|
}
|
|
@@ -32,7 +41,7 @@ export default class NetworkCategoryPickerComponent extends Component {
|
|
|
32
41
|
this.buttonTitle = buttonTitle;
|
|
33
42
|
}
|
|
34
43
|
|
|
35
|
-
@
|
|
44
|
+
@task *loadCategories(parentCategory) {
|
|
36
45
|
const queryParams = {
|
|
37
46
|
owner_uuid: this.network.id,
|
|
38
47
|
parents_only: parentCategory ? false : true,
|
|
@@ -45,26 +54,20 @@ export default class NetworkCategoryPickerComponent extends Component {
|
|
|
45
54
|
queryParams.with_parent = true;
|
|
46
55
|
}
|
|
47
56
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
.finally(() => {
|
|
55
|
-
this.isLoading = false;
|
|
56
|
-
});
|
|
57
|
+
try {
|
|
58
|
+
const categories = yield this.store.query('category', queryParams);
|
|
59
|
+
this.categories = categories.toArray();
|
|
60
|
+
} catch (error) {
|
|
61
|
+
debug(`Unable to load categories : ${error.message}`)
|
|
62
|
+
}
|
|
57
63
|
}
|
|
58
64
|
|
|
59
65
|
@action onSelectCategory(category) {
|
|
60
|
-
this.
|
|
61
|
-
this.setButtonTitle(category);
|
|
66
|
+
this.setCategory(category);
|
|
62
67
|
|
|
63
68
|
if (typeof this.args.onSelect === 'function') {
|
|
64
69
|
this.args.onSelect(category);
|
|
65
70
|
}
|
|
66
|
-
|
|
67
|
-
this.loadCategories(category);
|
|
68
71
|
}
|
|
69
72
|
|
|
70
73
|
@action onCreateNewCategory() {
|
|
@@ -80,4 +83,31 @@ export default class NetworkCategoryPickerComponent extends Component {
|
|
|
80
83
|
|
|
81
84
|
this.onSelectCategory(null);
|
|
82
85
|
}
|
|
86
|
+
|
|
87
|
+
@action updateArgs(el, [category]) {
|
|
88
|
+
this.setCategory(category);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async setCategoryById(categoryId) {
|
|
92
|
+
const category = this.store.peekRecord('category', categoryId);
|
|
93
|
+
if (category) {
|
|
94
|
+
this.setCategory(category);
|
|
95
|
+
} else {
|
|
96
|
+
// load from server if possible
|
|
97
|
+
const categoryRecord = await this.store.findRecord('category', categoryId);
|
|
98
|
+
if (categoryRecord) {
|
|
99
|
+
this.onSelectCategory(categoryRecord);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
setCategory(category) {
|
|
105
|
+
if (typeof category === 'string') {
|
|
106
|
+
return this.setCategoryById(category);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
this.selectedCategory = category;
|
|
110
|
+
this.setButtonTitle(category);
|
|
111
|
+
this.loadCategories.perform(category);
|
|
112
|
+
}
|
|
83
113
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import Component from '@glimmer/component';
|
|
2
2
|
import { tracked } from '@glimmer/tracking';
|
|
3
3
|
import { inject as service } from '@ember/service';
|
|
4
|
-
import { task } from 'ember-concurrency
|
|
4
|
+
import { task } from 'ember-concurrency';
|
|
5
5
|
|
|
6
6
|
export default class WidgetStorefrontKeyMetricsComponent extends Component {
|
|
7
7
|
/**
|
|
@@ -2,9 +2,9 @@ import { inject as service } from '@ember/service';
|
|
|
2
2
|
import { isBlank } from '@ember/utils';
|
|
3
3
|
import BaseController from '@fleetbase/storefront-engine/controllers/base-controller';
|
|
4
4
|
import { tracked } from '@glimmer/tracking';
|
|
5
|
-
import { timeout } from 'ember-concurrency';
|
|
6
|
-
import { task } from 'ember-concurrency-decorators';
|
|
5
|
+
import { timeout, task } from 'ember-concurrency';
|
|
7
6
|
import { action } from '@ember/object';
|
|
7
|
+
|
|
8
8
|
export default class CustomersIndexController extends BaseController {
|
|
9
9
|
/**
|
|
10
10
|
* Inject the `notifications` service
|
|
@@ -2,8 +2,7 @@ import Controller from '@ember/controller';
|
|
|
2
2
|
import { inject as service } from '@ember/service';
|
|
3
3
|
import { tracked } from '@glimmer/tracking';
|
|
4
4
|
import { isBlank } from '@ember/utils';
|
|
5
|
-
import { timeout } from 'ember-concurrency';
|
|
6
|
-
import { task } from 'ember-concurrency-decorators';
|
|
5
|
+
import { timeout, task } from 'ember-concurrency';
|
|
7
6
|
import { action } from '@ember/object';
|
|
8
7
|
|
|
9
8
|
export default class NetworksIndexNetworkOrdersController extends Controller {
|
|
@@ -3,67 +3,19 @@ import { tracked } from '@glimmer/tracking';
|
|
|
3
3
|
import { inject as service } from '@ember/service';
|
|
4
4
|
import { action, set } from '@ember/object';
|
|
5
5
|
import { isBlank } from '@ember/utils';
|
|
6
|
-
import {
|
|
7
|
-
import { task } from 'ember-concurrency
|
|
6
|
+
import { debug } from '@ember/debug';
|
|
7
|
+
import { timeout, task } from 'ember-concurrency';
|
|
8
8
|
import createShareableLink from '../../../../utils/create-shareable-link';
|
|
9
9
|
import isEmail from '@fleetbase/ember-core/utils/is-email';
|
|
10
10
|
import isModel from '@fleetbase/ember-core/utils/is-model';
|
|
11
11
|
|
|
12
12
|
export default class NetworksIndexNetworkStoresController extends Controller {
|
|
13
|
-
/**
|
|
14
|
-
* Inject the `notifications` service
|
|
15
|
-
*
|
|
16
|
-
* @var {Service}
|
|
17
|
-
* @memberof NetworksIndexNetworkStoresController
|
|
18
|
-
*/
|
|
19
13
|
@service notifications;
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Inject the `intl` service
|
|
23
|
-
*
|
|
24
|
-
* @var {Service}
|
|
25
|
-
* @memberof NetworksIndexNetworkStoresController
|
|
26
|
-
*/
|
|
27
14
|
@service intl;
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Inject the `modals-manager` service
|
|
31
|
-
*
|
|
32
|
-
* @var {Service}
|
|
33
|
-
* @memberof NetworksIndexNetworkStoresController
|
|
34
|
-
*/
|
|
35
15
|
@service modalsManager;
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Inject the `crud` service
|
|
39
|
-
*
|
|
40
|
-
* @var {Service}
|
|
41
|
-
* @memberof NetworksIndexNetworkStoresController
|
|
42
|
-
*/
|
|
43
16
|
@service crud;
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Inject the `fetch` service
|
|
47
|
-
*
|
|
48
|
-
* @var {Service}
|
|
49
|
-
* @memberof NetworksIndexNetworkStoresController
|
|
50
|
-
*/
|
|
51
17
|
@service fetch;
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Inject the `store` service
|
|
55
|
-
*
|
|
56
|
-
* @var {Service}
|
|
57
|
-
* @memberof NetworksIndexNetworkStoresController
|
|
58
|
-
*/
|
|
59
18
|
@service store;
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Inject the `hostRouter` service
|
|
63
|
-
*
|
|
64
|
-
* @var {Service}
|
|
65
|
-
* @memberof NetworksIndexNetworkStoresController
|
|
66
|
-
*/
|
|
67
19
|
@service hostRouter;
|
|
68
20
|
|
|
69
21
|
/**
|
|
@@ -122,12 +74,25 @@ export default class NetworksIndexNetworkStoresController extends Controller {
|
|
|
122
74
|
@tracked network;
|
|
123
75
|
|
|
124
76
|
/**
|
|
125
|
-
* The
|
|
77
|
+
* The stores loaded.
|
|
78
|
+
*
|
|
79
|
+
* @memberof NetworksIndexNetworkStoresController
|
|
80
|
+
*/
|
|
81
|
+
@tracked stores = [];
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The category picker component context.
|
|
85
|
+
*
|
|
86
|
+
* @memberof NetworksIndexNetworkStoresController
|
|
87
|
+
*/
|
|
88
|
+
@tracked categoryPicker;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The current category model instance.
|
|
126
92
|
*
|
|
127
|
-
* @var {Boolean}
|
|
128
93
|
* @memberof NetworksIndexNetworkStoresController
|
|
129
94
|
*/
|
|
130
|
-
@tracked
|
|
95
|
+
@tracked categoryModel;
|
|
131
96
|
|
|
132
97
|
/**
|
|
133
98
|
* All columns applicable for network stores
|
|
@@ -144,6 +109,7 @@ export default class NetworksIndexNetworkStoresController extends Controller {
|
|
|
144
109
|
filterable: true,
|
|
145
110
|
filterComponent: 'filter/string',
|
|
146
111
|
showOnlineIndicator: true,
|
|
112
|
+
cellClassNames: 'network-store-name-column',
|
|
147
113
|
},
|
|
148
114
|
{
|
|
149
115
|
label: this.intl.t('storefront.common.id'),
|
|
@@ -208,6 +174,11 @@ export default class NetworksIndexNetworkStoresController extends Controller {
|
|
|
208
174
|
label: this.intl.t('storefront.networks.index.network.stores.assign-category'),
|
|
209
175
|
fn: this.assignStoreToCategory,
|
|
210
176
|
},
|
|
177
|
+
{
|
|
178
|
+
label: this.intl.t('storefront.networks.index.network.stores.remove-category'),
|
|
179
|
+
fn: this.removeStoreCategory,
|
|
180
|
+
isVisible: (store) => store.category,
|
|
181
|
+
},
|
|
211
182
|
{
|
|
212
183
|
separator: true,
|
|
213
184
|
},
|
|
@@ -247,6 +218,10 @@ export default class NetworksIndexNetworkStoresController extends Controller {
|
|
|
247
218
|
this.storeQuery = value;
|
|
248
219
|
}
|
|
249
220
|
|
|
221
|
+
@action setCategoryPickerContext(context) {
|
|
222
|
+
this.categoryPicker = context;
|
|
223
|
+
}
|
|
224
|
+
|
|
250
225
|
/**
|
|
251
226
|
* Selects a category and assigns its ID to the current category property.
|
|
252
227
|
* If the selected category is null, the category property is set to null.
|
|
@@ -255,6 +230,8 @@ export default class NetworksIndexNetworkStoresController extends Controller {
|
|
|
255
230
|
* @param {CategoryModel|null} selectedCategory - The selected category object containing the ID.
|
|
256
231
|
*/
|
|
257
232
|
@action selectCategory(selectedCategory) {
|
|
233
|
+
this.categoryModel = selectedCategory;
|
|
234
|
+
|
|
258
235
|
if (selectedCategory) {
|
|
259
236
|
this.category = selectedCategory.id;
|
|
260
237
|
} else {
|
|
@@ -285,6 +262,36 @@ export default class NetworksIndexNetworkStoresController extends Controller {
|
|
|
285
262
|
});
|
|
286
263
|
}
|
|
287
264
|
|
|
265
|
+
/**
|
|
266
|
+
* Displays a confirmation modal to remove a specified store from the network.
|
|
267
|
+
* Allows the user to confirm the removal.
|
|
268
|
+
*
|
|
269
|
+
* @action
|
|
270
|
+
* @param {StoreModel} store - The store object to be removed.
|
|
271
|
+
*/
|
|
272
|
+
@action async removeStoreCategory(store) {
|
|
273
|
+
this.modalsManager.confirm({
|
|
274
|
+
title: this.intl.t('storefront.networks.index.network.stores.remove-store-category'),
|
|
275
|
+
body: this.intl.t('storefront.networks.index.network.stores.remove-store-category-body', { storeName: store.name, categoryName: store.category?.get('name') }),
|
|
276
|
+
acceptButtonIcon: 'check',
|
|
277
|
+
acceptButtonIconPrefix: 'fas',
|
|
278
|
+
declineButtonIcon: 'times',
|
|
279
|
+
declineButtonIconPrefix: 'fas',
|
|
280
|
+
confirm: async (modal) => {
|
|
281
|
+
modal.startLoading();
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
await this.fetch.post(`networks/${this.network.id}/remove-store-category`, { store: store.id }, { namespace: 'storefront/int/v1' });
|
|
285
|
+
await this.hostRouter.refresh();
|
|
286
|
+
modal.done();
|
|
287
|
+
} catch (error) {
|
|
288
|
+
modal.stopLoading();
|
|
289
|
+
this.notifications.serverError(error);
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
288
295
|
/**
|
|
289
296
|
* Displays a modal to assign a store to a category or create a new category.
|
|
290
297
|
* Allows the user to select a category, create a new one, or confirm the assignment.
|
|
@@ -516,19 +523,17 @@ export default class NetworksIndexNetworkStoresController extends Controller {
|
|
|
516
523
|
acceptButtonIconPrefix: 'fas',
|
|
517
524
|
declineButtonIcon: 'times',
|
|
518
525
|
declineButtonIconPrefix: 'fas',
|
|
519
|
-
confirm: (modal) => {
|
|
526
|
+
confirm: async (modal) => {
|
|
520
527
|
modal.startLoading();
|
|
521
528
|
|
|
522
|
-
|
|
523
|
-
.post(`networks/${this.network.id}/remove-stores`, { stores: [store.id] }, { namespace: 'storefront/int/v1' })
|
|
524
|
-
.
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
.
|
|
529
|
-
|
|
530
|
-
this.notifications.serverError(error);
|
|
531
|
-
});
|
|
529
|
+
try {
|
|
530
|
+
await this.fetch.post(`networks/${this.network.id}/remove-stores`, { stores: [store.id] }, { namespace: 'storefront/int/v1' });
|
|
531
|
+
await this.hostRouter.refresh();
|
|
532
|
+
modal.done();
|
|
533
|
+
} catch (error) {
|
|
534
|
+
modal.stopLoading();
|
|
535
|
+
this.notifications.serverError(error);
|
|
536
|
+
}
|
|
532
537
|
},
|
|
533
538
|
});
|
|
534
539
|
}
|
|
@@ -4,8 +4,7 @@ import { inject as service } from '@ember/service';
|
|
|
4
4
|
import { tracked } from '@glimmer/tracking';
|
|
5
5
|
import { action } from '@ember/object';
|
|
6
6
|
import { isBlank } from '@ember/utils';
|
|
7
|
-
import { timeout } from 'ember-concurrency';
|
|
8
|
-
import { task } from 'ember-concurrency-decorators';
|
|
7
|
+
import { timeout, task } from 'ember-concurrency';
|
|
9
8
|
|
|
10
9
|
export default class NetworksIndexController extends BaseController {
|
|
11
10
|
/**
|
|
@@ -4,8 +4,7 @@ import { inject as service } from '@ember/service';
|
|
|
4
4
|
import { action } from '@ember/object';
|
|
5
5
|
import { dasherize } from '@ember/string';
|
|
6
6
|
import { isBlank } from '@ember/utils';
|
|
7
|
-
import { timeout } from 'ember-concurrency';
|
|
8
|
-
import { task } from 'ember-concurrency-decorators';
|
|
7
|
+
import { timeout, task } from 'ember-concurrency';
|
|
9
8
|
|
|
10
9
|
export default class ProductsIndexCategoryController extends BaseController {
|
|
11
10
|
@service intl;
|
|
@@ -3,8 +3,7 @@ import { inject as service } from '@ember/service';
|
|
|
3
3
|
import { tracked } from '@glimmer/tracking';
|
|
4
4
|
import { action } from '@ember/object';
|
|
5
5
|
import { isBlank } from '@ember/utils';
|
|
6
|
-
import { timeout } from 'ember-concurrency';
|
|
7
|
-
import { task } from 'ember-concurrency-decorators';
|
|
6
|
+
import { timeout, task } from 'ember-concurrency';
|
|
8
7
|
|
|
9
8
|
export default class ProductsIndexIndexController extends BaseController {
|
|
10
9
|
@service filters;
|
|
@@ -3,6 +3,7 @@ import { inject as service } from '@ember/service';
|
|
|
3
3
|
|
|
4
4
|
export default class NetworksIndexNetworkStoresRoute extends Route {
|
|
5
5
|
@service store;
|
|
6
|
+
@service hostRouter;
|
|
6
7
|
|
|
7
8
|
queryParams = {
|
|
8
9
|
category: { refreshModel: true },
|
|
@@ -23,10 +24,10 @@ export default class NetworksIndexNetworkStoresRoute extends Route {
|
|
|
23
24
|
// set the network to controller
|
|
24
25
|
controller.network = this.network;
|
|
25
26
|
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
// set the cateogry if set
|
|
28
|
+
const { category: categoryId } = this.paramsFor(this.routeName);
|
|
29
|
+
if (categoryId) {
|
|
30
|
+
controller.category = categoryId;
|
|
31
|
+
}
|
|
31
32
|
}
|
|
32
33
|
}
|
|
@@ -77,3 +77,10 @@ body[data-theme='dark'] .ui-tabs.overlay-content-panel > ul .ui-tab.active,
|
|
|
77
77
|
body[data-theme='dark'] .ui-tabs.overlay-content-panel > nav .ui-tab.active {
|
|
78
78
|
background-color: #202a37;
|
|
79
79
|
}
|
|
80
|
+
|
|
81
|
+
/** hotfix online indicator overflowing store name */
|
|
82
|
+
td.network-store-name-column > div > svg {
|
|
83
|
+
margin-left: -0.7rem;
|
|
84
|
+
height: 0.5rem;
|
|
85
|
+
width: 0.5rem;
|
|
86
|
+
}
|
|
@@ -19,6 +19,13 @@
|
|
|
19
19
|
</Layout::Section::Header>
|
|
20
20
|
|
|
21
21
|
<Layout::Section::Body>
|
|
22
|
-
<
|
|
22
|
+
<div class="flex flex-row items-center space-x-2">
|
|
23
|
+
<NetworkCategoryPicker @network={{this.network}} @category={{this.category}} @onCreateNewCategory={{this.createNewCategory}} @onSelect={{this.selectCategory}} @onReady={{this.setCategoryPickerContext}} @wrapperClass="w-64 my-4 ml-9" />
|
|
24
|
+
{{#if this.categoryModel}}
|
|
25
|
+
<Button @icon="arrow-left" @size="xs" @onClick={{this.categoryPicker.loadParentCategories}} />
|
|
26
|
+
<Button @icon="cog" @text="Edit Category" @size="xs" @onClick={{fn this.editCategory this.categoryModel}} />
|
|
27
|
+
<Button @icon="trash" @text="Delete Category" @size="xs" @type="danger" @onClick={{fn this.deleteCategory this.categoryModel}} />
|
|
28
|
+
{{/if}}
|
|
29
|
+
</div>
|
|
23
30
|
<Table @rows={{@model}} @columns={{this.columns}} @selectable={{true}} @canSelectAll={{true}} @onSetup={{fn (mut this.table)}} @tfoot={{false}} @selectAllColumnWidth={{20}} />
|
|
24
31
|
</Layout::Section::Body>
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
<Image src={{network.logo_url}} class="w-32" alt={{network.name}} @fallbackSrc="https://flb-assets.s3.ap-southeast-1.amazonaws.com/static/image-file-icon.png" />
|
|
13
13
|
</div>
|
|
14
14
|
<div class="border-t dark:border-gray-700 px-4 py-2">
|
|
15
|
-
<div class="flex items-
|
|
15
|
+
<div class="flex items-start justify-between">
|
|
16
16
|
<div>
|
|
17
17
|
<LinkTo @route="networks.index.network" @model={{network}} class="font-bold text-lg text-gray-700 dark:text-gray-100">{{network.name}}</LinkTo>
|
|
18
18
|
</div>
|
package/composer.json
CHANGED
package/extension.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fleetbase/storefront-engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Headless Commerce & Marketplace Extension for Fleetbase",
|
|
5
5
|
"fleetbase": {
|
|
6
6
|
"route": "storefront",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"@babel/core": "^7.23.2",
|
|
47
47
|
"@fleetbase/ember-core": "latest",
|
|
48
48
|
"@fleetbase/ember-ui": "latest",
|
|
49
|
-
"@fleetbase/fleetops-data": "
|
|
49
|
+
"@fleetbase/fleetops-data": "^0.1.19",
|
|
50
50
|
"@fortawesome/ember-fontawesome": "^2.0.0",
|
|
51
51
|
"@fortawesome/fontawesome-svg-core": "6.4.0",
|
|
52
52
|
"@fortawesome/free-brands-svg-icons": "6.4.0",
|
|
@@ -79,7 +79,6 @@
|
|
|
79
79
|
"ember-cli-sri": "^2.1.1",
|
|
80
80
|
"ember-cli-terser": "^4.0.2",
|
|
81
81
|
"ember-concurrency": "^3.1.1",
|
|
82
|
-
"ember-concurrency-decorators": "^2.0.3",
|
|
83
82
|
"ember-data": "^4.12.5",
|
|
84
83
|
"ember-engines": "^0.9.0",
|
|
85
84
|
"ember-load-initializers": "^2.1.2",
|
|
@@ -7,7 +7,8 @@ $database = env('DB_DATABASE', 'fleetbase');
|
|
|
7
7
|
$username = env('DB_USERNAME', 'fleetbase');
|
|
8
8
|
$password = env('DB_PASSWORD', '');
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
$databaseUrl = getenv('DATABASE_URL');
|
|
11
|
+
if (!empty($databaseUrl)) {
|
|
11
12
|
$url = Utils::parseUrl($databaseUrl);
|
|
12
13
|
|
|
13
14
|
$host = $url['host'];
|
|
@@ -110,7 +110,7 @@ class NetworkController extends StorefrontController
|
|
|
110
110
|
*/
|
|
111
111
|
public function removeStores(string $id, NetworkActionRequest $request)
|
|
112
112
|
{
|
|
113
|
-
$stores =
|
|
113
|
+
$stores = $request->array('stores');
|
|
114
114
|
|
|
115
115
|
// delete each
|
|
116
116
|
foreach ($stores as $storeId) {
|
|
@@ -121,7 +121,7 @@ class NetworkController extends StorefrontController
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
/**
|
|
124
|
-
*
|
|
124
|
+
* Add a store to a network category
|
|
125
125
|
*
|
|
126
126
|
* @return \Illuminate\Http\Response
|
|
127
127
|
*/
|
|
@@ -132,7 +132,6 @@ class NetworkController extends StorefrontController
|
|
|
132
132
|
|
|
133
133
|
// get network store instance
|
|
134
134
|
$networkStore = NetworkStore::where(['network_uuid' => $id, 'store_uuid' => $store])->first();
|
|
135
|
-
|
|
136
135
|
if ($networkStore) {
|
|
137
136
|
$networkStore->update(['category_uuid' => $category]);
|
|
138
137
|
}
|
|
@@ -140,6 +139,24 @@ class NetworkController extends StorefrontController
|
|
|
140
139
|
return response()->json(['status' => 'ok']);
|
|
141
140
|
}
|
|
142
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Remove stores to a network.
|
|
144
|
+
*
|
|
145
|
+
* @return \Illuminate\Http\Response
|
|
146
|
+
*/
|
|
147
|
+
public function removeStoreCategory(string $id, NetworkActionRequest $request)
|
|
148
|
+
{
|
|
149
|
+
$store = $request->input('store');
|
|
150
|
+
|
|
151
|
+
// get network store instance
|
|
152
|
+
$networkStore = NetworkStore::where(['network_uuid' => $id, 'store_uuid' => $store])->first();
|
|
153
|
+
if ($networkStore) {
|
|
154
|
+
$networkStore->update(['category_uuid' => null]);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return response()->json(['status' => 'ok']);
|
|
158
|
+
}
|
|
159
|
+
|
|
143
160
|
/**
|
|
144
161
|
* Remove stores to a network.
|
|
145
162
|
*
|
|
@@ -5,17 +5,325 @@ namespace Fleetbase\Storefront\Http\Controllers\v1;
|
|
|
5
5
|
use Fleetbase\FleetOps\Http\Resources\v1\DeletedResource;
|
|
6
6
|
use Fleetbase\Http\Controllers\Controller;
|
|
7
7
|
use Fleetbase\Models\Category;
|
|
8
|
+
use Fleetbase\Storefront\Http\Requests\CreateProductRequest;
|
|
9
|
+
use Fleetbase\Storefront\Http\Requests\UpdateProductRequest;
|
|
8
10
|
use Fleetbase\Storefront\Http\Resources\Product as StorefrontProduct;
|
|
11
|
+
use Fleetbase\Storefront\Models\AddonCategory;
|
|
9
12
|
use Fleetbase\Storefront\Models\Product;
|
|
13
|
+
use Fleetbase\Storefront\Models\ProductAddon;
|
|
14
|
+
use Fleetbase\Storefront\Models\ProductAddonCategory;
|
|
15
|
+
use Fleetbase\Storefront\Models\ProductVariant;
|
|
16
|
+
use Fleetbase\Storefront\Models\ProductVariantOption;
|
|
17
|
+
use Fleetbase\Storefront\Models\Store;
|
|
18
|
+
use Fleetbase\Support\Utils;
|
|
10
19
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
|
11
20
|
use Illuminate\Http\Request;
|
|
12
21
|
|
|
13
22
|
class ProductController extends Controller
|
|
14
23
|
{
|
|
24
|
+
/**
|
|
25
|
+
* Create a new Storefront product.
|
|
26
|
+
*
|
|
27
|
+
* @return void
|
|
28
|
+
*/
|
|
29
|
+
public function create(CreateProductRequest $request)
|
|
30
|
+
{
|
|
31
|
+
// Collect product details input
|
|
32
|
+
$input = $request->only([
|
|
33
|
+
'name',
|
|
34
|
+
'description',
|
|
35
|
+
'tags',
|
|
36
|
+
'meta',
|
|
37
|
+
'sku',
|
|
38
|
+
'price',
|
|
39
|
+
'currency',
|
|
40
|
+
'sale_price',
|
|
41
|
+
'addons',
|
|
42
|
+
'variants',
|
|
43
|
+
'is_service',
|
|
44
|
+
'is_bookable',
|
|
45
|
+
'is_available',
|
|
46
|
+
'is_on_sale',
|
|
47
|
+
'is_recommended',
|
|
48
|
+
'can_pickup',
|
|
49
|
+
'youtube_urls',
|
|
50
|
+
'status',
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
// Set product store
|
|
54
|
+
$input['store_uuid'] = session('storefront_store');
|
|
55
|
+
|
|
56
|
+
// Set product relations
|
|
57
|
+
$input['company_uuid'] = session('company');
|
|
58
|
+
$input['created_by_uuid'] = session('user');
|
|
59
|
+
|
|
60
|
+
// Prepare arrayable data
|
|
61
|
+
$input['tags'] = Utils::arrayFrom(data_get($input, 'tags', []));
|
|
62
|
+
$input['youtube_urls'] = Utils::arrayFrom(data_get($input, 'youtube_urls', []));
|
|
63
|
+
|
|
64
|
+
// Prepare money
|
|
65
|
+
$input['price'] = Utils::numbersOnly(data_get($input, 'price', 0));
|
|
66
|
+
$input['sale_price'] = Utils::numbersOnly(data_get($input, 'sale_price', 0));
|
|
67
|
+
|
|
68
|
+
// Set currency
|
|
69
|
+
$input['currency'] = data_get($input, 'currency', session('storefront_currency', 'USD'));
|
|
70
|
+
|
|
71
|
+
// Resolve category
|
|
72
|
+
if ($request->filled('category')) {
|
|
73
|
+
$categoryInput = $request->input('category');
|
|
74
|
+
|
|
75
|
+
if (Utils::isPublicId($categoryInput)) {
|
|
76
|
+
$category = Category::where([
|
|
77
|
+
'company_uuid' => session('company'),
|
|
78
|
+
'owner_uuid' => session('storefront_store'),
|
|
79
|
+
'public_id' => $categoryInput,
|
|
80
|
+
'for' => 'storefront_product',
|
|
81
|
+
])->first();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Create new product if data is array
|
|
85
|
+
if (is_array($categoryInput) && isset($categoryInput['name'])) {
|
|
86
|
+
$category = Category::create([
|
|
87
|
+
'company_uuid' => session('company'),
|
|
88
|
+
'owner_uuid' => session('storefront_store'),
|
|
89
|
+
'owner_type' => Utils::getMutationType('storefront:store'),
|
|
90
|
+
'name' => data_get($categoryInput, 'name'),
|
|
91
|
+
'description' => data_get($categoryInput, 'description'),
|
|
92
|
+
'tags' => Utils::arrayFrom(data_get($categoryInput, 'tags', [])),
|
|
93
|
+
'for' => 'storefront_product',
|
|
94
|
+
]);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Set the cateogry for the product
|
|
98
|
+
if ($category instanceof Category) {
|
|
99
|
+
$input['category_uuid'] = $category->uuid;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Create product
|
|
104
|
+
$product = Product::create($input);
|
|
105
|
+
|
|
106
|
+
// Resolve addon categories
|
|
107
|
+
if ($request->filled('addon_categories') && $request->isArray('addon_categories')) {
|
|
108
|
+
$request->collect('addon_categories')->each(function ($addonCategoryInput) use ($product) {
|
|
109
|
+
// Resolve existing addon category from ID
|
|
110
|
+
if (Utils::isPublicId($addonCategoryInput)) {
|
|
111
|
+
$addonCategory = AddonCategory::where('public_id', $addonCategoryInput)->first();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Create new addon cateogry with addons
|
|
115
|
+
if (is_array($addonCategoryInput)) {
|
|
116
|
+
$addonCategory = AddonCategory::create([
|
|
117
|
+
'company_uuid' => session('company'),
|
|
118
|
+
'name' => data_get($addonCategoryInput, 'name'),
|
|
119
|
+
'description' => data_get($addonCategoryInput, 'description'),
|
|
120
|
+
'tags' => Utils::arrayFrom(data_get($addonCategoryInput, 'tags', [])),
|
|
121
|
+
]);
|
|
122
|
+
|
|
123
|
+
if (isset($addonCategoryInput['addons']) && is_array($addonCategoryInput['addons'])) {
|
|
124
|
+
collect($addonCategoryInput['addons'])->each(function ($addonInput) use ($addonCategory) {
|
|
125
|
+
if (is_string($addonInput)) {
|
|
126
|
+
return ProductAddon::create([
|
|
127
|
+
'category_uuid' => $addonCategory->uuid,
|
|
128
|
+
'created_by_uuid' => session('user'),
|
|
129
|
+
'name' => $addonInput,
|
|
130
|
+
]);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (is_array($addonInput)) {
|
|
134
|
+
return ProductAddon::create([
|
|
135
|
+
'category_uuid' => $addonCategory->uuid,
|
|
136
|
+
'created_by_uuid' => session('user'),
|
|
137
|
+
'name' => data_get($addonInput, 'name'),
|
|
138
|
+
'price' => Utils::numbersOnly(data_get($addonInput, 'price', 0)),
|
|
139
|
+
'sale_price' => Utils::numbersOnly(data_get($addonInput, 'sale_price', 0)),
|
|
140
|
+
'is_on_sale' => Utils::castBoolean(data_get($addonInput, 'is_on_sale', false)),
|
|
141
|
+
]);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Create product addon category
|
|
148
|
+
if ($addonCategory instanceof AddonCategory) {
|
|
149
|
+
ProductAddonCategory::create([
|
|
150
|
+
'product_uuid' => $product->uuid,
|
|
151
|
+
'category_uuid' => $addonCategory->uuid,
|
|
152
|
+
'excluded_addons' => Utils::arrayFrom(data_get($addonCategoryInput, 'excluded_addons', [])),
|
|
153
|
+
'max_selectable' => data_get($addonCategoryInput, 'max_selectable'),
|
|
154
|
+
'is_required' => Utils::castBoolean(data_get($addonCategoryInput, 'is_required')),
|
|
155
|
+
]);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Resolve variants
|
|
161
|
+
if ($request->filled('variants') && $request->isArray('variants')) {
|
|
162
|
+
$request->collect('variants')->each(function ($variantInput) use ($product) {
|
|
163
|
+
// Create new variants for product
|
|
164
|
+
if (is_array($variantInput)) {
|
|
165
|
+
$productVariant = ProductVariant::create([
|
|
166
|
+
'product_uuid' => $product->uuid,
|
|
167
|
+
'name' => data_get($variantInput, 'name'),
|
|
168
|
+
'description' => data_get($variantInput, 'description'),
|
|
169
|
+
'meta' => data_get($variantInput, 'meta', []),
|
|
170
|
+
'is_required' => Utils::castBoolean(data_get($variantInput, 'is_required')),
|
|
171
|
+
'is_multiselect' => Utils::castBoolean(data_get($variantInput, 'is_multiselect')),
|
|
172
|
+
'min' => data_get($variantInput, 'min', 0),
|
|
173
|
+
'max' => data_get($variantInput, 'max', 1),
|
|
174
|
+
]);
|
|
175
|
+
|
|
176
|
+
if (isset($variantInput['options']) && is_array($variantInput['options'])) {
|
|
177
|
+
collect($variantInput['options'])->each(function ($variantOptionInput) use ($productVariant) {
|
|
178
|
+
if (is_string($variantOptionInput)) {
|
|
179
|
+
ProductVariantOption::create([
|
|
180
|
+
'product_variant_uuid' => $productVariant->uuid,
|
|
181
|
+
'name' => $variantOptionInput,
|
|
182
|
+
]);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (is_array($variantOptionInput)) {
|
|
186
|
+
ProductVariantOption::create([
|
|
187
|
+
'product_variant_uuid' => $productVariant->uuid,
|
|
188
|
+
'name' => data_get($variantOptionInput, 'name'),
|
|
189
|
+
'description' => data_get($variantOptionInput, 'description'),
|
|
190
|
+
'additional_cost' => Utils::numbersOnly(data_get($variantOptionInput, 'additional_cost', 0)),
|
|
191
|
+
]);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return new StorefrontProduct($product);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Updates a Storefront Product.
|
|
204
|
+
*
|
|
205
|
+
* @param string $id
|
|
206
|
+
*
|
|
207
|
+
* @return \Fleetbase\Storefront\Http\Resources\StorefrontProduct
|
|
208
|
+
*/
|
|
209
|
+
public function update($id, UpdateProductRequest $request)
|
|
210
|
+
{
|
|
211
|
+
// Try to resolve the product by public_id or uuid (custom method)
|
|
212
|
+
try {
|
|
213
|
+
$product = Product::findRecordOrFail($id);
|
|
214
|
+
} catch (ModelNotFoundException $exception) {
|
|
215
|
+
return response()->json([
|
|
216
|
+
'error' => 'Product not found.',
|
|
217
|
+
], 404);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Validate input
|
|
221
|
+
$input = $request->validated();
|
|
222
|
+
|
|
223
|
+
// Sanitize/transform
|
|
224
|
+
$input['tags'] = Utils::arrayFrom(data_get($input, 'tags', []));
|
|
225
|
+
$input['youtube_urls'] = Utils::arrayFrom(data_get($input, 'youtube_urls', []));
|
|
226
|
+
$input['price'] = Utils::numbersOnly(data_get($input, 'price', 0));
|
|
227
|
+
$input['sale_price'] = Utils::numbersOnly(data_get($input, 'sale_price', 0));
|
|
228
|
+
|
|
229
|
+
// Resolve or create category
|
|
230
|
+
if (isset($input['category'])) {
|
|
231
|
+
$categoryInput = $input['category'];
|
|
232
|
+
$category = null;
|
|
233
|
+
|
|
234
|
+
if (Utils::isPublicId($categoryInput)) {
|
|
235
|
+
$category = Category::where([
|
|
236
|
+
'company_uuid' => session('company'),
|
|
237
|
+
'owner_uuid' => session('storefront_store'),
|
|
238
|
+
'public_id' => $categoryInput,
|
|
239
|
+
'for' => 'storefront_product',
|
|
240
|
+
])->first();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (is_array($categoryInput) && isset($categoryInput['name'])) {
|
|
244
|
+
$category = Category::create([
|
|
245
|
+
'company_uuid' => session('company'),
|
|
246
|
+
'owner_uuid' => session('storefront_store'),
|
|
247
|
+
'owner_type' => Utils::getMutationType('storefront:store'),
|
|
248
|
+
'name' => data_get($categoryInput, 'name'),
|
|
249
|
+
'description' => data_get($categoryInput, 'description'),
|
|
250
|
+
'tags' => Utils::arrayFrom(data_get($categoryInput, 'tags', [])),
|
|
251
|
+
'for' => 'storefront_product',
|
|
252
|
+
]);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if ($category instanceof Category) {
|
|
256
|
+
$input['category_uuid'] = $category->uuid;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Update the product
|
|
261
|
+
$product->update($input);
|
|
262
|
+
|
|
263
|
+
// Update sync addon categories
|
|
264
|
+
if ($request->filled('addon_categories') && $request->isArray('addon_categories')) {
|
|
265
|
+
$addonCategories = $request->collect('addon_categories')->map(function ($addonCategory) {
|
|
266
|
+
// Resolve category public_id to UUID
|
|
267
|
+
if (isset($addonCategory['category'])) {
|
|
268
|
+
$category = AddonCategory::where('public_id', $addonCategory['category'])->first();
|
|
269
|
+
if ($category) {
|
|
270
|
+
$addonCategory['category_uuid'] = $category->uuid;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Resolve ProductAddonCategory public_id if present (e.g. for updates)
|
|
275
|
+
if (isset($addonCategory['id']) && Utils::isPublicId($addonCategory['id'])) {
|
|
276
|
+
$pac = ProductAddonCategory::where('public_id', $addonCategory['id'])->first();
|
|
277
|
+
if ($pac) {
|
|
278
|
+
$addonCategory['uuid'] = $pac->uuid;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return $addonCategory;
|
|
283
|
+
})->toArray();
|
|
284
|
+
|
|
285
|
+
$product->setAddonCategories($addonCategories);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// update sync product variants and options
|
|
289
|
+
if ($request->filled('variants') && $request->isArray('variants')) {
|
|
290
|
+
$variants = $request->collect('variants')->map(function ($variant) {
|
|
291
|
+
// Resolve variant public_id to UUID
|
|
292
|
+
if (isset($variant['id']) && Utils::isPublicId($variant['id'])) {
|
|
293
|
+
$variantModel = ProductVariant::where('public_id', $variant['id'])->first();
|
|
294
|
+
if ($variantModel) {
|
|
295
|
+
$variant['uuid'] = $variantModel->uuid;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Resolve variant option IDs if using public_id
|
|
300
|
+
if (isset($variant['options']) && is_array($variant['options'])) {
|
|
301
|
+
$variant['options'] = collect($variant['options'])->map(function ($option) {
|
|
302
|
+
if (isset($option['id']) && Utils::isPublicId($option['id'])) {
|
|
303
|
+
$optionModel = ProductVariantOption::where('public_id', $option['id'])->first();
|
|
304
|
+
if ($optionModel) {
|
|
305
|
+
$option['uuid'] = $optionModel->uuid;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return $option;
|
|
310
|
+
})->toArray();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return $variant;
|
|
314
|
+
})->toArray();
|
|
315
|
+
|
|
316
|
+
$product->setProductVariants($variants);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Return resource
|
|
320
|
+
return new StorefrontProduct($product);
|
|
321
|
+
}
|
|
322
|
+
|
|
15
323
|
/**
|
|
16
324
|
* Query for Storefront Product resources.
|
|
17
325
|
*
|
|
18
|
-
* @return \Fleetbase\Http\Resources\
|
|
326
|
+
* @return \Fleetbase\Http\Resources\ProductCollection
|
|
19
327
|
*/
|
|
20
328
|
public function query(Request $request)
|
|
21
329
|
{
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\Storefront\Http\Requests;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\Http\Requests\FleetbaseRequest;
|
|
6
|
+
use Illuminate\Validation\Rule;
|
|
7
|
+
|
|
8
|
+
class CreateProductRequest extends FleetbaseRequest
|
|
9
|
+
{
|
|
10
|
+
/**
|
|
11
|
+
* Determine if the user is authorized to make this request.
|
|
12
|
+
*/
|
|
13
|
+
public function authorize(): bool
|
|
14
|
+
{
|
|
15
|
+
return session('storefront_key') || request()->session()->has('api_credential');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Get the validation rules that apply to the request.
|
|
20
|
+
*/
|
|
21
|
+
public function rules(): array
|
|
22
|
+
{
|
|
23
|
+
return [
|
|
24
|
+
'name' => 'required|string|max:255',
|
|
25
|
+
'description' => 'nullable|string',
|
|
26
|
+
'tags' => 'nullable|array',
|
|
27
|
+
'meta' => 'nullable|array',
|
|
28
|
+
'sku' => 'nullable|string|max:100',
|
|
29
|
+
'price' => [Rule::requiredIf(fn () => $this->isMethod('POST')), 'numeric', 'min:0'],
|
|
30
|
+
'sale_price' => 'nullable|numeric|min:0',
|
|
31
|
+
'currency' => 'nullable|string|size:3',
|
|
32
|
+
'addons' => 'nullable|array',
|
|
33
|
+
'variants' => 'nullable|array',
|
|
34
|
+
'is_service' => 'nullable|boolean',
|
|
35
|
+
'is_bookable' => 'nullable|boolean',
|
|
36
|
+
'is_available' => 'nullable|boolean',
|
|
37
|
+
'is_on_sale' => 'nullable|boolean',
|
|
38
|
+
'is_recommended' => 'nullable|boolean',
|
|
39
|
+
'can_pickup' => 'nullable|boolean',
|
|
40
|
+
'youtube_urls' => 'nullable|array',
|
|
41
|
+
'status' => 'nullable|string|in:draft,active,archived',
|
|
42
|
+
'category' => 'nullable',
|
|
43
|
+
'addon_categories' => 'nullable|array',
|
|
44
|
+
];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -4,6 +4,7 @@ namespace Fleetbase\Storefront\Http\Resources;
|
|
|
4
4
|
|
|
5
5
|
use Fleetbase\Http\Resources\FleetbaseResource;
|
|
6
6
|
use Fleetbase\Support\Http;
|
|
7
|
+
use Fleetbase\Support\Utils;
|
|
7
8
|
|
|
8
9
|
class CatalogCategory extends FleetbaseResource
|
|
9
10
|
{
|
|
@@ -26,8 +27,8 @@ class CatalogCategory extends FleetbaseResource
|
|
|
26
27
|
'owner_uuid' => $this->when(Http::isInternalRequest(), $this->owner_uuid),
|
|
27
28
|
'name' => $this->name,
|
|
28
29
|
'description' => $this->description,
|
|
29
|
-
'tags' => $this
|
|
30
|
-
'meta' => $this
|
|
30
|
+
'tags' => data_get($this, 'tags', []),
|
|
31
|
+
'meta' => data_get($this, 'meta', Utils::createObject()),
|
|
31
32
|
'products' => CatalogProduct::collection($this->products ?? []),
|
|
32
33
|
'for' => $this->for,
|
|
33
34
|
'order' => $this->order,
|
|
@@ -4,6 +4,7 @@ namespace Fleetbase\Storefront\Http\Resources;
|
|
|
4
4
|
|
|
5
5
|
use Fleetbase\Http\Resources\FleetbaseResource;
|
|
6
6
|
use Fleetbase\Support\Http;
|
|
7
|
+
use Fleetbase\Support\Utils;
|
|
7
8
|
|
|
8
9
|
class Category extends FleetbaseResource
|
|
9
10
|
{
|
|
@@ -28,7 +29,7 @@ class Category extends FleetbaseResource
|
|
|
28
29
|
return $parentCategory->public_id;
|
|
29
30
|
}
|
|
30
31
|
),
|
|
31
|
-
'tags' => $this->tags
|
|
32
|
+
'tags' => Utils::arrayFrom($this->tags),
|
|
32
33
|
'translations' => $this->translations ?? [],
|
|
33
34
|
'products' => $this->when($request->has('with_products') || $request->inArray('with', 'products'), $this->products ? Product::collection($this->products) : []),
|
|
34
35
|
'subcategories' => $this->when(
|
|
@@ -40,6 +41,7 @@ class Category extends FleetbaseResource
|
|
|
40
41
|
$this->subCategories->toArray()
|
|
41
42
|
)
|
|
42
43
|
),
|
|
44
|
+
'meta' => data_get($this, 'meta', Utils::createObject()),
|
|
43
45
|
'order' => $this->order,
|
|
44
46
|
'slug' => $this->slug,
|
|
45
47
|
'created_at' => $this->created_at,
|
|
@@ -5,6 +5,7 @@ namespace Fleetbase\Storefront\Http\Resources;
|
|
|
5
5
|
use Fleetbase\FleetOps\Http\Resources\v1\Place;
|
|
6
6
|
use Fleetbase\Http\Resources\FleetbaseResource;
|
|
7
7
|
use Fleetbase\Support\Http;
|
|
8
|
+
use Fleetbase\Support\Utils;
|
|
8
9
|
use Illuminate\Support\Str;
|
|
9
10
|
|
|
10
11
|
class Customer extends FleetbaseResource
|
|
@@ -33,7 +34,7 @@ class Customer extends FleetbaseResource
|
|
|
33
34
|
'address' => data_get($this, 'place.address'),
|
|
34
35
|
'addresses' => $this->whenLoaded('places', Place::collection($this->places)),
|
|
35
36
|
'token' => $this->when($this->token, $this->token),
|
|
36
|
-
'meta' => $this
|
|
37
|
+
'meta' => data_get($this, 'meta', Utils::createObject()),
|
|
37
38
|
'slug' => $this->slug,
|
|
38
39
|
'created_at' => $this->created_at,
|
|
39
40
|
'updated_at' => $this->updated_at,
|
|
@@ -4,6 +4,7 @@ namespace Fleetbase\Storefront\Http\Resources;
|
|
|
4
4
|
|
|
5
5
|
use Fleetbase\Http\Resources\FleetbaseResource;
|
|
6
6
|
use Fleetbase\Support\Http;
|
|
7
|
+
use Fleetbase\Support\Utils;
|
|
7
8
|
use Illuminate\Support\Arr;
|
|
8
9
|
use Illuminate\Support\Collection;
|
|
9
10
|
use Illuminate\Support\Str;
|
|
@@ -42,6 +43,7 @@ class Product extends FleetbaseResource
|
|
|
42
43
|
'is_available' => $this->is_available,
|
|
43
44
|
'tags' => $this->tags ?? [],
|
|
44
45
|
'status' => $this->status,
|
|
46
|
+
'meta' => data_get($this, 'meta', Utils::createObject()),
|
|
45
47
|
'slug' => $this->slug,
|
|
46
48
|
'translations' => $this->translations ?? [],
|
|
47
49
|
'addon_categories' => $this->mapAddonCategories($this->addonCategories),
|
|
@@ -156,7 +156,8 @@ class Network extends StorefrontModel
|
|
|
156
156
|
{
|
|
157
157
|
return $this->belongsToMany(Store::class, 'network_stores', 'network_uuid', 'store_uuid')
|
|
158
158
|
->using(NetworkStore::class)
|
|
159
|
-
->withPivot('category_uuid')
|
|
159
|
+
->withPivot(['category_uuid', 'deleted_at'])
|
|
160
|
+
->wherePivotNull('deleted_at');
|
|
160
161
|
}
|
|
161
162
|
|
|
162
163
|
/**
|
|
@@ -278,7 +278,8 @@ class Store extends StorefrontModel
|
|
|
278
278
|
{
|
|
279
279
|
return $this->belongsToMany(Network::class, 'network_stores', 'store_uuid', 'network_uuid')
|
|
280
280
|
->using(NetworkStore::class)
|
|
281
|
-
->withPivot('category_uuid')
|
|
281
|
+
->withPivot(['category_uuid', 'deleted_at'])
|
|
282
|
+
->wherePivotNull('deleted_at');
|
|
282
283
|
}
|
|
283
284
|
|
|
284
285
|
/**
|
package/server/src/routes.php
CHANGED
|
@@ -73,6 +73,8 @@ Route::prefix(config('storefront.api.routing.prefix', 'storefront'))->namespace(
|
|
|
73
73
|
$router->group(['prefix' => 'products'], function () use ($router) {
|
|
74
74
|
$router->get('/', 'ProductController@query');
|
|
75
75
|
$router->get('{id}', 'ProductController@find');
|
|
76
|
+
$router->post('/', 'ProductController@create');
|
|
77
|
+
$router->put('{id}', 'ProductController@update');
|
|
76
78
|
});
|
|
77
79
|
|
|
78
80
|
// storefront/v1/food-trucks
|
|
@@ -167,6 +169,7 @@ Route::prefix(config('storefront.api.routing.prefix', 'storefront'))->namespace(
|
|
|
167
169
|
function ($router, $controller) {
|
|
168
170
|
$router->delete('{id}/remove-category', $controller('deleteCategory'));
|
|
169
171
|
$router->post('{id}/set-store-category', $controller('addStoreToCategory'));
|
|
172
|
+
$router->post('{id}/remove-store-category', $controller('removeStoreCategory'));
|
|
170
173
|
$router->post('{id}/add-stores', $controller('addStores'));
|
|
171
174
|
$router->post('{id}/remove-stores', $controller('removeStores'));
|
|
172
175
|
$router->post('{id}/invite', $controller('sendInvites'));
|
package/translations/en-us.yaml
CHANGED
|
@@ -409,6 +409,7 @@ storefront:
|
|
|
409
409
|
view-store-details: View Store Details
|
|
410
410
|
edit-store: Edit Store
|
|
411
411
|
assign-category: Assign Category
|
|
412
|
+
remove-category: Remove Category
|
|
412
413
|
remove-store-from-network: Remove Store from Network
|
|
413
414
|
delete-network-category: Delete network category?
|
|
414
415
|
deleting-category-move-all-stores-inside-on-top-level: Deleting this category will move all stores inside to the top level, are you sure you want to delete this category?
|
|
@@ -429,6 +430,8 @@ storefront:
|
|
|
429
430
|
send-invitations: Send Invitations
|
|
430
431
|
invitation-sent-recipients: Invitations sent to recipients!
|
|
431
432
|
invalid-emails-provided-error: Invalid emails provided!
|
|
433
|
+
remove-store-category: Remove store category
|
|
434
|
+
remove-store-category-body: You are about to remove {storeName} from the category {categoryName}, do you wish to proceed?
|
|
432
435
|
orders:
|
|
433
436
|
index:
|
|
434
437
|
internal-id: Internal ID
|