@fleetbase/storefront-engine 0.3.31 → 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.
@@ -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-decorators';
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-fgray-800 dark:bg-gray-800 p-4">
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.isLoading}}
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 isLoading = false;
15
- @tracked buttonTitle = null;
16
+ @tracked buttonTitle = 'Select Category';
16
17
 
17
- constructor() {
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 = this.args.network;
20
- this.category = this.args.category;
21
- this.setButtonTitle(this.category);
22
- this.loadCategories(this.category);
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
- @action loadCategories(parentCategory) {
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
- this.isLoading = true;
49
- this.store
50
- .query('category', queryParams)
51
- .then((categories) => {
52
- this.categories = categories.toArray();
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.selectedCategory = category;
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-decorators';
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 { timeout } from 'ember-concurrency';
7
- import { task } from 'ember-concurrency-decorators';
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 loading state.
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 isLoading = false;
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
- this.fetch
523
- .post(`networks/${this.network.id}/remove-stores`, { stores: [store.id] }, { namespace: 'storefront/int/v1' })
524
- .then(() => {
525
- this.stores.removeObject(store);
526
- modal.done();
527
- })
528
- .catch((error) => {
529
- modal.stopLoading();
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
- // // load the network categories
27
- // const categories = await this.store.query('category', { owner_uuid: network.id, parents_only: true, for: 'storefront_network' });
28
-
29
- // // set the categories loaded
30
- // controller.categories = categories.toArray();
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
- <NetworkCategoryPicker @network={{this.network}} @onCreateNewCategory={{this.createNewCategory}} @onSelect={{this.selectCategory}} @wrapperClass="my-4 ml-9" />
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-center justify-between">
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fleetbase/storefront-api",
3
- "version": "0.3.31",
3
+ "version": "0.4.0",
4
4
  "description": "Headless Commerce & Marketplace Extension for Fleetbase",
5
5
  "keywords": [
6
6
  "fleetbase-extension",
package/extension.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "Storefront",
3
- "version": "0.3.31",
3
+ "version": "0.4.0",
4
4
  "description": "Headless Commerce & Marketplace Extension for Fleetbase",
5
5
  "repository": "https://github.com/fleetbase/storefront",
6
6
  "license": "AGPL-3.0-or-later",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleetbase/storefront-engine",
3
- "version": "0.3.31",
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": "latest",
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",
@@ -110,7 +110,7 @@ class NetworkController extends StorefrontController
110
110
  */
111
111
  public function removeStores(string $id, NetworkActionRequest $request)
112
112
  {
113
- $stores = collect($request->input('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
- * Remove stores to a network.
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
  *
@@ -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
  /**
@@ -169,6 +169,7 @@ Route::prefix(config('storefront.api.routing.prefix', 'storefront'))->namespace(
169
169
  function ($router, $controller) {
170
170
  $router->delete('{id}/remove-category', $controller('deleteCategory'));
171
171
  $router->post('{id}/set-store-category', $controller('addStoreToCategory'));
172
+ $router->post('{id}/remove-store-category', $controller('removeStoreCategory'));
172
173
  $router->post('{id}/add-stores', $controller('addStores'));
173
174
  $router->post('{id}/remove-stores', $controller('removeStores'));
174
175
  $router->post('{id}/invite', $controller('sendInvites'));
@@ -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