@fleetbase/storefront-engine 0.3.13 → 0.3.14

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 (44) hide show
  1. package/addon/components/add-product-as-entity-button.js +3 -3
  2. package/addon/components/modals/create-first-store.hbs +3 -1
  3. package/addon/components/widget/storefront-key-metrics.hbs +1 -1
  4. package/addon/controllers/application.js +1 -1
  5. package/addon/controllers/products/index/category/edit.js +1 -0
  6. package/addon/controllers/products/index/category/new.js +6 -2
  7. package/addon/routes/application.js +14 -3
  8. package/addon/routes/customers/index/edit.js +14 -1
  9. package/addon/routes/customers/index/view.js +11 -0
  10. package/addon/routes/customers/index.js +11 -0
  11. package/addon/routes/networks/index/network.js +11 -0
  12. package/addon/routes/networks/index.js +11 -0
  13. package/addon/routes/orders/index/edit.js +14 -1
  14. package/addon/routes/orders/index/new.js +14 -1
  15. package/addon/routes/orders/index/view.js +10 -0
  16. package/addon/routes/orders/index.js +11 -0
  17. package/addon/routes/products/index/category/edit.js +11 -0
  18. package/addon/routes/products/index/category/new.js +15 -1
  19. package/addon/routes/products/index/category.js +12 -1
  20. package/addon/routes/products/index/index.js +11 -0
  21. package/addon/routes/products/index.js +11 -0
  22. package/addon/routes/settings/index.js +11 -0
  23. package/addon/services/storefront.js +35 -74
  24. package/addon/templates/application.hbs +31 -5
  25. package/addon/templates/customers/index.hbs +1 -1
  26. package/addon/templates/networks/index.hbs +4 -3
  27. package/addon/templates/orders/index.hbs +2 -2
  28. package/addon/templates/products/index/category/new.hbs +326 -245
  29. package/addon/templates/products/index/category.hbs +6 -9
  30. package/addon/templates/products/index/index.hbs +2 -2
  31. package/addon/templates/products/index.hbs +11 -5
  32. package/addon/templates/settings/index.hbs +1 -0
  33. package/composer.json +3 -3
  34. package/extension.json +1 -1
  35. package/package.json +5 -4
  36. package/server/src/Auth/Schemas/Storefront.php +97 -3
  37. package/server/src/Http/Controllers/v1/CartController.php +1 -1
  38. package/server/src/Http/Controllers/v1/CustomerController.php +1 -1
  39. package/server/src/Http/Resources/Product.php +5 -5
  40. package/server/src/Models/Cart.php +1 -1
  41. package/server/src/Models/Network.php +3 -3
  42. package/server/src/Models/Store.php +4 -4
  43. package/server/src/Support/Metrics.php +8 -8
  44. package/server/src/Support/QPay.php +4 -4
@@ -13,293 +13,374 @@
13
13
  @text={{this.overlayActionButtonTitle}}
14
14
  @onClick={{perform this.saveProduct}}
15
15
  @isLoading={{not this.saveProduct.isIdle}}
16
+ @permission={{this.abilityPermission}}
16
17
  />
17
18
  </Overlay::Header>
18
19
 
19
20
  <Overlay::Body @increaseInnerBodyHeightBy="0" @wrapperClass="new-order-overlay-body px-4 space-y-4 pt-4">
20
- <InputGroup @name="Product Name" @value={{this.product.name}} @helpText="Enter your product's name" />
21
- <InputGroup @name="Product Description" @helpText="Enter a description of your product">
22
- <Textarea @value={{this.product.description}} class="form-input w-full" placeholder="Enter a description of your product...." rows={{4}} />
23
- </InputGroup>
24
- <InputGroup @name="Product Tags">
25
- <TagInput
26
- class="form-input"
27
- @placeholder="Add tags"
28
- @allowSpacesInTags={{true}}
29
- @tags={{this.product.tags}}
30
- @addTag={{this.addTag}}
31
- @removeTagAtIndex={{this.removeTag}}
32
- as |tag|
33
- >
34
- {{tag}}
35
- </TagInput>
36
- </InputGroup>
37
- <InputGroup @name="Product SKU" @value={{this.product.sku}} @helpText="Enter product SKU if applicable" />
38
- <div class="grid grid-cols-2 gap-2">
39
- <InputGroup @name="Price" @helpText="Enter a price users will pay to purchase this product">
40
- <MoneyInput
41
- class="w-full"
42
- @currency={{if this.product.currency this.product.currency this.activeStore.currency}}
43
- @value={{this.product.price}}
44
- @canSelectCurrency={{false}}
45
- @onCurrencyChange={{fn (mut this.product.currency)}}
46
- />
21
+ {{#let (cannot this.abilityPermission) as |unauthorized|}}
22
+ <InputGroup @name="Product Name" @value={{this.product.name}} @helpText="Enter your product's name" @disabled={{unauthorized}} />
23
+ <InputGroup @name="Product Description" @helpText="Enter a description of your product">
24
+ <Textarea @value={{this.product.description}} class="form-input w-full" placeholder="Enter a description of your product...." disabled={{unauthorized}} rows={{4}} />
47
25
  </InputGroup>
48
- <InputGroup @name="Sale Price" @helpText="Optionally add a sale price for the product if the product is put on sale">
49
- <MoneyInput
50
- class="w-full"
51
- @currency={{if this.product.currency this.product.currency this.activeStore.currency}}
52
- @value={{this.product.sale_price}}
53
- @canSelectCurrency={{false}}
54
- @onCurrencyChange={{fn (mut this.product.currency)}}
55
- />
26
+ <InputGroup @name="Product Tags">
27
+ <TagInput
28
+ class="form-input"
29
+ @placeholder="Add tags"
30
+ @allowSpacesInTags={{true}}
31
+ @tags={{this.product.tags}}
32
+ @addTag={{this.addTag}}
33
+ @removeTagAtIndex={{this.removeTag}}
34
+ @disabled={{unauthorized}}
35
+ as |tag|
36
+ >
37
+ {{tag}}
38
+ </TagInput>
56
39
  </InputGroup>
57
- </div>
40
+ <InputGroup @name="Product SKU" @value={{this.product.sku}} @helpText="Enter product SKU if applicable" @disabled={{unauthorized}} />
41
+ <div class="grid grid-cols-2 gap-2">
42
+ <InputGroup @name="Price" @helpText="Enter a price users will pay to purchase this product">
43
+ <MoneyInput
44
+ class="w-full"
45
+ @currency={{if this.product.currency this.product.currency this.activeStore.currency}}
46
+ @value={{this.product.price}}
47
+ @canSelectCurrency={{false}}
48
+ @onCurrencyChange={{fn (mut this.product.currency)}}
49
+ @disabled={{unauthorized}}
50
+ />
51
+ </InputGroup>
52
+ <InputGroup @name="Sale Price" @helpText="Optionally add a sale price for the product if the product is put on sale">
53
+ <MoneyInput
54
+ class="w-full"
55
+ @currency={{if this.product.currency this.product.currency this.activeStore.currency}}
56
+ @value={{this.product.sale_price}}
57
+ @canSelectCurrency={{false}}
58
+ @onCurrencyChange={{fn (mut this.product.currency)}}
59
+ @disabled={{unauthorized}}
60
+ />
61
+ </InputGroup>
62
+ </div>
58
63
 
59
- <ContentPanel
60
- @title="Metadata"
61
- @open={{this.product.meta_array.length}}
62
- @actionButtons={{this.metadataButtons}}
63
- @pad={{true}}
64
- @panelBodyWrapperClass="px-0 py-4"
65
- @panelBodyClass="bg-white dark:bg-gray-800"
66
- >
67
- {{#each this.product.meta_array as |metaField index|}}
68
- <div class="px-4 py-3 border-b border-gray-200 dark:border-gray-700">
69
- <div class="input-group">
70
- <div class="flex justify-between items-center mb-1">
71
- <Input class="form-input border-0 px-2 py-1 m-0 bg-white dark:bg-gray-900 shadow-none" @value={{metaField.label}} placeholder={{metaField.label}} />
72
- <a href="javascript:;" class="text-xs" tabindex="-1" {{on "click" (fn this.removeMetaField index)}}>
73
- <FaIcon @icon="trash" @size="sm" class="mr-1" />
74
- Remove
75
- </a>
64
+ <ContentPanel
65
+ @title="Metadata"
66
+ @open={{this.product.meta_array.length}}
67
+ @actionButtons={{this.metadataButtons}}
68
+ @pad={{true}}
69
+ @panelBodyWrapperClass="px-0 py-4"
70
+ @panelBodyClass="bg-white dark:bg-gray-800"
71
+ >
72
+ {{#each this.product.meta_array as |metaField index|}}
73
+ <div class="px-4 py-3 border-b border-gray-200 dark:border-gray-700">
74
+ <div class="input-group">
75
+ <div class="flex justify-between items-center mb-1">
76
+ <Input
77
+ class="form-input border-0 px-2 py-1 m-0 bg-white dark:bg-gray-900 shadow-none"
78
+ @value={{metaField.label}}
79
+ disabled={{unauthorized}}
80
+ placeholder={{metaField.label}}
81
+ />
82
+ <a href="javascript:;" class="text-xs" tabindex="-1" disabled={{unauthorized}} {{on "click" (fn this.removeMetaField index)}}>
83
+ <FaIcon @icon="trash" @size="sm" class="mr-1" />
84
+ Remove
85
+ </a>
86
+ </div>
87
+ <Input class="w-full form-input" @value={{metaField.value}} placeholder={{metaField.label}} disabled={{unauthorized}} />
76
88
  </div>
77
- <Input class="w-full form-input" @value={{metaField.value}} placeholder={{metaField.label}} />
78
89
  </div>
79
- </div>
80
- {{/each}}
81
- </ContentPanel>
82
-
83
- <InputGroup>
84
- <TranslationsEditor @value={{this.product.translations}} @defaultKeys={{array "name" "description"}} @onChange={{fn (mut this.product.translations)}} />
85
- </InputGroup>
90
+ {{/each}}
91
+ </ContentPanel>
86
92
 
87
- <div class="store-boolean-settings">
88
- <div class="input-group mb-0">
89
- <Checkbox @value={{this.product.is_service}} @onToggle={{fn (mut this.product.is_service)}}>
90
- <FaIcon @icon="house-leave" class="text-indigo-400 mx-2" /><span class="dark:text-gray-100 text-sm">This is a service</span>
91
- </Checkbox>
92
- </div>
93
+ <InputGroup>
94
+ <TranslationsEditor
95
+ @value={{this.product.translations}}
96
+ @defaultKeys={{array "name" "description"}}
97
+ @onChange={{fn (mut this.product.translations)}}
98
+ @disabled={{unauthorized}}
99
+ />
100
+ </InputGroup>
93
101
 
94
- {{#if this.product.is_service}}
102
+ <div class="store-boolean-settings">
95
103
  <div class="input-group mb-0">
96
- <Checkbox @value={{this.product.is_bookable}} @onToggle={{fn (mut this.product.is_bookable)}}>
97
- <FaIcon @icon="calendar-star" class="text-yellow-400 mx-2" /><span class="dark:text-gray-100 text-sm">This service is bookable</span>
104
+ <Checkbox @value={{this.product.is_service}} @onToggle={{fn (mut this.product.is_service)}} @disabled={{unauthorized}}>
105
+ <FaIcon @icon="house-leave" class="text-indigo-400 mx-2" /><span class="dark:text-gray-100 text-sm">This is a service</span>
98
106
  </Checkbox>
99
107
  </div>
100
- {{/if}}
101
108
 
102
- <div class="input-group mb-0">
103
- <Checkbox @value={{this.product.is_on_sale}} @onToggle={{fn (mut this.product.is_on_sale)}}>
104
- <FaIcon @icon="badge-percent" class="text-blue-400 mx-2" /><span class="dark:text-gray-100 text-sm">This product is on sale</span>
105
- </Checkbox>
106
- </div>
109
+ {{#if this.product.is_service}}
110
+ <div class="input-group mb-0">
111
+ <Checkbox @value={{this.product.is_bookable}} @onToggle={{fn (mut this.product.is_bookable)}} @disabled={{unauthorized}}>
112
+ <FaIcon @icon="calendar-star" class="text-yellow-400 mx-2" /><span class="dark:text-gray-100 text-sm">This service is bookable</span>
113
+ </Checkbox>
114
+ </div>
115
+ {{/if}}
107
116
 
108
- <div class="input-group">
109
- <Checkbox @value={{this.product.is_recommended}} @onToggle={{fn (mut this.product.is_recommended)}}>
110
- <FaIcon @icon="check" class="text-green-400 mx-2" /><span class="dark:text-gray-100 text-sm">This product is recommended</span>
111
- </Checkbox>
112
- </div>
117
+ <div class="input-group mb-0">
118
+ <Checkbox @value={{this.product.is_on_sale}} @onToggle={{fn (mut this.product.is_on_sale)}} @disabled={{unauthorized}}>
119
+ <FaIcon @icon="badge-percent" class="text-blue-400 mx-2" /><span class="dark:text-gray-100 text-sm">This product is on sale</span>
120
+ </Checkbox>
121
+ </div>
113
122
 
114
- <div class="input-group">
115
- <Checkbox @value={{this.product.is_available}} @onToggle={{fn (mut this.product.is_available)}}>
116
- <FaIcon @icon="eye" class="text-blue-400 mx-2" /><span class="dark:text-gray-100 text-sm">This product is available</span>
117
- </Checkbox>
118
- </div>
119
- </div>
123
+ <div class="input-group">
124
+ <Checkbox @value={{this.product.is_recommended}} @onToggle={{fn (mut this.product.is_recommended)}} @disabled={{unauthorized}}>
125
+ <FaIcon @icon="check" class="text-green-400 mx-2" /><span class="dark:text-gray-100 text-sm">This product is recommended</span>
126
+ </Checkbox>
127
+ </div>
120
128
 
121
- <ContentPanel @title="Variants" @open={{true}} @pad={{false}} @panelBodyWrapperClass="px-0 py-4" @panelBodyClass="bg-white dark:bg-gray-800">
122
- <div class="content-panel-body p-4">
123
- <div class="flex items-center justify-end">
124
- <Button @text="New Variant" @icon="plus" @iconPrefix="fas" @onClick={{this.createProductVariant}} />
129
+ <div class="input-group">
130
+ <Checkbox @value={{this.product.is_available}} @onToggle={{fn (mut this.product.is_available)}} @disabled={{unauthorized}}>
131
+ <FaIcon @icon="eye" class="text-blue-400 mx-2" /><span class="dark:text-gray-100 text-sm">This product is available</span>
132
+ </Checkbox>
125
133
  </div>
126
134
  </div>
127
- <Tabs as |Tab|>
128
- {{#each this.product.variants as |variant|}}
129
- <Tab @title={{variant.name}}>
130
- <div class="px-4 py-3">
131
- <div class="flex items-center justify-end mb-3">
132
- <Button class="mr-2" @type="danger" @text="Remove Variant" @icon="trash" @onClick={{fn this.removeProductVariant variant}} />
133
- <Button class="mr-2" @type="warning" @text="Edit Variant" @icon="pencil" @onClick={{fn this.editProductVariant variant}} />
134
- <Button @text={{concat "New " (lowercase variant.name) " option"}} @icon="plus" @iconPrefix="fas" @onClick={{fn this.addVariantOption variant}} />
135
- </div>
136
- <div class="space-y-2">
137
- {{#each variant.options as |variantOption index|}}
138
- <div class="grid grid-cols-9 gap-1 px-3 py-2 rounded-md dark:bg-gray-900 bg-gray-50">
139
- <div class="col-span-2">
140
- <Input @type="text" @value={{variantOption.name}} class="form-input w-full" placeholder="Option Name" />
141
- </div>
142
- <div class="col-span-4">
143
- <Input @type="text" @value={{variantOption.description}} class="form-input w-full" placeholder="Option Description" />
144
- </div>
145
- <div class="col-span-2">
146
- <MoneyInput
147
- class="w-full"
148
- @currency={{if this.product.currency this.product.currency this.activeStore.currency}}
149
- @canSelectCurrency={{false}}
150
- @value={{variantOption.additional_cost}}
151
- />
152
- </div>
153
- <div class="flex items-center justify-center text-center text-sm">
154
- <a href="javascript:;" {{on "click" (fn this.removeVariantOption variant index)}}>Remove</a>
155
- </div>
156
- </div>
157
- {{/each}}
158
- </div>
159
- </div>
160
- </Tab>
161
- {{/each}}
162
- </Tabs>
163
- </ContentPanel>
164
135
 
165
- <ContentPanel @title="Add-Ons" @open={{true}} @pad={{false}} @panelBodyWrapperClass="px-0 py-4" @panelBodyClass="bg-white dark:bg-gray-800">
166
- <div class="px-4">
167
- <div class="flex items-center justify-end mb-3">
168
- <Button @text="Select Addon Categories" @icon="plus" @iconPrefix="fas" @onClick={{perform this.promptSelectAddonCategories}} />
136
+ <ContentPanel @title="Variants" @open={{true}} @pad={{false}} @panelBodyWrapperClass="px-0 py-4" @panelBodyClass="bg-white dark:bg-gray-800">
137
+ <div class="content-panel-body p-4">
138
+ <div class="flex items-center justify-end">
139
+ <Button @text="New Variant" @icon="plus" @iconPrefix="fas" @onClick={{this.createProductVariant}} @permission="storefront create product-variant" />
140
+ </div>
169
141
  </div>
170
- <div class="space-y-2">
171
- {{#each this.product.addon_categories as |addonCategory index|}}
172
- <div>
173
- <div class="flex items-center rounded-md shadow-sm px-3 py-2 font-semibold bg-gray-200 dark:bg-gray-900 dark:text-gray-100 mb-2">
174
- <div class="flex-1 flex items-center">
175
- <FaIcon @icon="pencil" class="mr-1 dark:text-gray-100" />
176
- <div class="w-full px-2 m-0 border-none bg-transparent dark:text-gray-100">
177
- {{addonCategory.name}}
178
- </div>
142
+ <Tabs as |Tab|>
143
+ {{#each this.product.variants as |variant|}}
144
+ <Tab @title={{variant.name}}>
145
+ <div class="px-4 py-3">
146
+ <div class="flex items-center justify-end mb-3">
147
+ <Button
148
+ class="mr-2"
149
+ @type="danger"
150
+ @text="Remove Variant"
151
+ @icon="trash"
152
+ @onClick={{fn this.removeProductVariant variant}}
153
+ @permission="storefront delete product-variant"
154
+ />
155
+ <Button
156
+ class="mr-2"
157
+ @type="warning"
158
+ @text="Edit Variant"
159
+ @icon="pencil"
160
+ @onClick={{fn this.editProductVariant variant}}
161
+ @permission="storefront update product-variant"
162
+ />
163
+ <Button
164
+ @text={{concat "New " (lowercase variant.name) " option"}}
165
+ @icon="plus"
166
+ @iconPrefix="fas"
167
+ @onClick={{fn this.addVariantOption variant}}
168
+ @permission="storefront create product-variant-option"
169
+ />
179
170
  </div>
180
- <div class="flex items-center">
181
- <a href="javascript:;" class="destroy-action opacity-50 hover:opacity-100 text-sm" {{on "click" (fn this.removeAddonCategory index)}}>Remove</a>
171
+ <div class="space-y-2">
172
+ {{#each variant.options as |variantOption index|}}
173
+ <div class="grid grid-cols-9 gap-1 px-3 py-2 rounded-md dark:bg-gray-900 bg-gray-50">
174
+ <div class="col-span-2">
175
+ <Input
176
+ @type="text"
177
+ @value={{variantOption.name}}
178
+ class="form-input w-full"
179
+ placeholder="Option Name"
180
+ disabled={{cannot "storefront update product-variant-option"}}
181
+ />
182
+ </div>
183
+ <div class="col-span-4">
184
+ <Input
185
+ @type="text"
186
+ @value={{variantOption.description}}
187
+ class="form-input w-full"
188
+ placeholder="Option Description"
189
+ disabled={{cannot "storefront update product-variant-option"}}
190
+ />
191
+ </div>
192
+ <div class="col-span-2">
193
+ <MoneyInput
194
+ class="w-full"
195
+ @currency={{if this.product.currency this.product.currency this.activeStore.currency}}
196
+ @canSelectCurrency={{false}}
197
+ @value={{variantOption.additional_cost}}
198
+ @disabled={{cannot "storefront update product-variant-option"}}
199
+ />
200
+ </div>
201
+ <div class="flex items-center justify-center text-center text-sm">
202
+ <a
203
+ href="javascript:;"
204
+ {{on "click" (fn this.removeVariantOption variant index)}}
205
+ disabled={{cannot "storefront delete product-variant-option"}}
206
+ >Remove</a>
207
+ </div>
208
+ </div>
209
+ {{/each}}
182
210
  </div>
183
211
  </div>
184
- <div class="space-y-2 mb-2">
185
- {{#each addonCategory.category.addons as |addon|}}
186
- <div class="grid grid-cols-10 gap-1 px-3 py-2 rounded-md border border-gray-200 dark:border-gray-900 dark:text-gray-100 dark:bg-gray-800 bg-gray-200">
187
- <div class="flex items-center">
188
- <Checkbox @value={{not (in-array addon.id addonCategory.excluded_addons)}} @onToggle={{fn this.excludeAddon index addon}} />
189
- </div>
190
- <div class="col-span-3 flex items-center">
191
- {{addon.name}}
192
- </div>
193
- <div class="col-span-4">
194
- {{addon.description}}
195
- </div>
196
- <div class="col-span-2">
197
- {{format-currency addon.price}}
212
+ </Tab>
213
+ {{/each}}
214
+ </Tabs>
215
+ </ContentPanel>
216
+
217
+ <ContentPanel @title="Add-Ons" @open={{true}} @pad={{false}} @panelBodyWrapperClass="px-0 py-4" @panelBodyClass="bg-white dark:bg-gray-800">
218
+ <div class="px-4">
219
+ <div class="flex items-center justify-end mb-3">
220
+ <Button
221
+ @text="Select Addon Categories"
222
+ @icon="plus"
223
+ @iconPrefix="fas"
224
+ @onClick={{perform this.promptSelectAddonCategories}}
225
+ @permission="storefront list product-addon"
226
+ />
227
+ </div>
228
+ <div class="space-y-2">
229
+ {{#each this.product.addon_categories as |addonCategory index|}}
230
+ <div>
231
+ <div class="flex items-center rounded-md shadow-sm px-3 py-2 font-semibold bg-gray-200 dark:bg-gray-900 dark:text-gray-100 mb-2">
232
+ <div class="flex-1 flex items-center">
233
+ <FaIcon @icon="pencil" class="mr-1 dark:text-gray-100" />
234
+ <div class="w-full px-2 m-0 border-none bg-transparent dark:text-gray-100">
235
+ {{addonCategory.name}}
198
236
  </div>
199
237
  </div>
200
- {{/each}}
238
+ <div class="flex items-center">
239
+ <a
240
+ href="javascript:;"
241
+ class="destroy-action opacity-50 hover:opacity-100 text-sm"
242
+ {{on "click" (fn this.removeAddonCategory index)}}
243
+ disabled={{cannot "storefront delete product-addon"}}
244
+ >Remove</a>
245
+ </div>
246
+ </div>
247
+ <div class="space-y-2 mb-2">
248
+ {{#each addonCategory.category.addons as |addon|}}
249
+ <div class="grid grid-cols-10 gap-1 px-3 py-2 rounded-md border border-gray-200 dark:border-gray-900 dark:text-gray-100 dark:bg-gray-800 bg-gray-200">
250
+ <div class="flex items-center">
251
+ <Checkbox
252
+ @value={{not (in-array addon.id addonCategory.excluded_addons)}}
253
+ @onToggle={{fn this.excludeAddon index addon}}
254
+ disabled={{cannot "storefront update product-addon"}}
255
+ />
256
+ </div>
257
+ <div class="col-span-3 flex items-center">
258
+ {{addon.name}}
259
+ </div>
260
+ <div class="col-span-4">
261
+ {{addon.description}}
262
+ </div>
263
+ <div class="col-span-2">
264
+ {{format-currency addon.price}}
265
+ </div>
266
+ </div>
267
+ {{/each}}
268
+ </div>
201
269
  </div>
202
- </div>
203
- {{/each}}
270
+ {{/each}}
271
+ </div>
204
272
  </div>
205
- </div>
206
- </ContentPanel>
273
+ </ContentPanel>
207
274
 
208
- <ContentPanel @title="Availability" @open={{this.product.hours.length}} @pad={{false}} @panelBodyWrapperClass="px-0 py-4" @panelBodyClass="bg-white dark:bg-gray-800">
209
- <div class="px-4 py-3">
210
- <ScheduleManager @subject={{this.product}} @subjectKey="product_uuid" @hourModelType="product-hour" class="grid grid-cols-1 gap-4 lg:grid-cols-2 lg:gap-2" />
211
- </div>
212
- </ContentPanel>
275
+ <ContentPanel @title="Availability" @open={{this.product.hours.length}} @pad={{false}} @panelBodyWrapperClass="px-0 py-4" @panelBodyClass="bg-white dark:bg-gray-800">
276
+ <div class="px-4 py-3">
277
+ <ScheduleManager
278
+ @subject={{this.product}}
279
+ @subjectKey="product_uuid"
280
+ @hourModelType="product-hour"
281
+ @disabled={{unauthorized}}
282
+ class="grid grid-cols-1 gap-4 lg:grid-cols-2 lg:gap-2"
283
+ />
284
+ </div>
285
+ </ContentPanel>
213
286
 
214
- <ContentPanel @title="Images & Videos" @open={{this.product.files.length}} @pad={{false}} @panelBodyWrapperClass="px-0 py-4" @panelBodyClass="bg-white dark:bg-gray-800">
215
- <div class="px-6 space-y-4">
216
- {{#if this.isUploading}}
217
- <div
218
- class="min-h-56 dropzone w-full rounded-lg px-4 py-8 min-h bg-gray-50 dark:bg-gray-900 bg-opacity-25 text-gray-900 dark:text-white text-center flex flex-col items-center justify-center border-2 border-dashed border-gray-200 dark:border-indigo-500"
219
- >
220
- <div class="flex items-center justify-center py-5">
221
- <Spinner class="text-sm dar:text-gray-100" @loadingMessage={{t "component.dropzone.uploading"}} />
287
+ <ContentPanel @title="Images & Videos" @open={{this.product.files.length}} @pad={{false}} @panelBodyWrapperClass="px-0 py-4" @panelBodyClass="bg-white dark:bg-gray-800">
288
+ <div class="px-6 space-y-4">
289
+ {{#if this.isUploading}}
290
+ <div
291
+ class="min-h-56 dropzone w-full rounded-lg px-4 py-8 min-h bg-gray-50 dark:bg-gray-900 bg-opacity-25 text-gray-900 dark:text-white text-center flex flex-col items-center justify-center border-2 border-dashed border-gray-200 dark:border-indigo-500"
292
+ >
293
+ <div class="flex items-center justify-center py-5">
294
+ <Spinner class="text-sm dar:text-gray-100" @loadingMessage={{t "component.dropzone.uploading"}} />
295
+ </div>
222
296
  </div>
223
- </div>
224
- {{else}}
225
- {{#let (file-queue name="files" onFileAdded=this.queueFile accept=(join "," this.acceptedFileTypes)) as |queue|}}
226
- <FileDropzone @queue={{queue}} class="dropzone file-dropzone" as |dropzone|>
227
- {{#if dropzone.active}}
228
- {{#if dropzone.valid}}
229
- {{t "component.dropzone.drop-to-upload"}}
297
+ {{else}}
298
+ {{#let (file-queue name="files" onFileAdded=this.queueFile accept=(join "," this.acceptedFileTypes)) as |queue|}}
299
+ <FileDropzone @queue={{queue}} @disabled={{unauthorized}} class="dropzone file-dropzone" as |dropzone|>
300
+ {{#if dropzone.active}}
301
+ {{#if dropzone.valid}}
302
+ {{t "component.dropzone.drop-to-upload"}}
303
+ {{else}}
304
+ {{t "component.dropzone.invalid"}}
305
+ {{/if}}
306
+ {{else if queue.files.length}}
307
+ <div class="my-2">
308
+ <FaIcon @icon="photo-video" class="text-indigo-500 mr-2" />
309
+ {{t "component.dropzone.files-ready-for-upload" numOfFiles=(pluralize queue.files.length (t "component.dropzone.file"))}}
310
+ </div>
311
+ <div class="my-2">({{queue.progress}}%)</div>
230
312
  {{else}}
231
- {{t "component.dropzone.invalid"}}
313
+ <h4 class="font-semibold mb-8">
314
+ <FaIcon @icon="photo-video" @size="2x" class="text-indigo-500 mr-2" />
315
+ {{t "component.dropzone.upload-images-videos"}}
316
+ </h4>
317
+ <div>
318
+ {{#if dropzone.supported}}
319
+ <p class="text-base font-semibold my-5">{{t "component.dropzone.dropzone-supported-images-videos"}}</p>
320
+ {{/if}}
321
+ <FileUpload
322
+ @name="files"
323
+ @for="files"
324
+ @accept={{join "," this.acceptedFileTypes}}
325
+ @multiple={{true}}
326
+ @onFileAdded={{this.queueFile}}
327
+ @disabled={{unauthorized}}
328
+ >
329
+ <a tabindex={{0}} class="btn btn-magic cursor-pointer ml-1">{{t "component.dropzone.or-select-button-text"}}</a>
330
+ </FileUpload>
331
+ </div>
232
332
  {{/if}}
233
- {{else if queue.files.length}}
234
- <div class="my-2">
235
- <FaIcon @icon="photo-video" class="text-indigo-500 mr-2" />
236
- {{t "component.dropzone.files-ready-for-upload" numOfFiles=(pluralize queue.files.length (t "component.dropzone.file"))}}
333
+ </FileDropzone>
334
+ {{/let}}
335
+ {{#if this.uploadQueue}}
336
+ <div class="mx-4">
337
+ <div class="flex items-center justify-between mb-4">
338
+ <span class="leading-6 dark:text-gray-100">{{t "component.dropzone.upload-queue"}}</span>
237
339
  </div>
238
- <div class="my-2">({{queue.progress}}%)</div>
239
- {{else}}
240
- <h4 class="font-semibold mb-8">
241
- <FaIcon @icon="photo-video" @size="2x" class="text-indigo-500 mr-2" />
242
- {{t "component.dropzone.upload-images-videos"}}
243
- </h4>
244
- <div>
245
- {{#if dropzone.supported}}
246
- <p class="text-base font-semibold my-5">{{t "component.dropzone.dropzone-supported-images-videos"}}</p>
247
- {{/if}}
248
- <FileUpload @name="files" @for="files" @accept={{join "," this.acceptedFileTypes}} @multiple={{true}} @onFileAdded={{this.queueFile}}>
249
- <a tabindex={{0}} class="btn btn-magic cursor-pointer ml-1">{{t "component.dropzone.or-select-button-text"}}</a>
250
- </FileUpload>
340
+ <div class="space-y-2 mb-4">
341
+ {{#each this.uploadQueue as |file|}}
342
+ <div class="flex items-center justify-between bg-blue-100 border border-blue-800 dark:border-blue-800 py-1.5 shadow-sm rounded-lg px-4">
343
+ <div class="text-xs text-blue-900 truncate">{{truncate-filename file.name 50}}</div>
344
+ <div class="flex items-center text-sm">
345
+ <Spinner class="text-blue-900 mr-2" />
346
+ <span class="font-bold text-blue-900">{{round file.progress}}%</span>
347
+ </div>
348
+ </div>
349
+ {{/each}}
251
350
  </div>
252
- {{/if}}
253
- </FileDropzone>
254
- {{/let}}
255
- {{#if this.uploadQueue}}
256
- <div class="mx-4">
257
- <div class="flex items-center justify-between mb-4">
258
- <span class="leading-6 dark:text-gray-100">{{t "component.dropzone.upload-queue"}}</span>
259
351
  </div>
260
- <div class="space-y-2 mb-4">
261
- {{#each this.uploadQueue as |file|}}
262
- <div class="flex items-center justify-between bg-blue-100 border border-blue-800 dark:border-blue-800 py-1.5 shadow-sm rounded-lg px-4">
263
- <div class="text-xs text-blue-900 truncate">{{truncate-filename file.name 50}}</div>
264
- <div class="flex items-center text-sm">
265
- <Spinner class="text-blue-900 mr-2" />
266
- <span class="font-bold text-blue-900">{{round file.progress}}%</span>
352
+ {{/if}}
353
+ <div>
354
+ <div class="grid grid-cols-2 md:grid-cols-4 gap-2 md:gap-4">
355
+ {{#each this.product.files as |file|}}
356
+ <FileRecord @file={{file}} @fileIconClass={{if (eq this.product.primary_image_uuid file.id) "border-blue-400"}} @onDelete={{this.removeFile}}>
357
+ <div class="flex items-center justify-evenly">
358
+ <Button
359
+ @icon="magic"
360
+ @text="Make Primary"
361
+ @size="xs"
362
+ @textClass="text-xs truncate"
363
+ @onClick={{fn this.makePrimaryFile file}}
364
+ @disabled={{or unauthorized (eq this.product.primary_image_uuid file.id)}}
365
+ />
267
366
  </div>
268
- </div>
367
+ </FileRecord>
269
368
  {{/each}}
270
369
  </div>
271
370
  </div>
272
371
  {{/if}}
273
- <div>
274
- <div class="grid grid-cols-2 md:grid-cols-4 gap-2 md:gap-4">
275
- {{#each this.product.files as |file|}}
276
- <FileRecord @file={{file}} @fileIconClass={{if (eq this.product.primary_image_uuid file.id) "border-blue-400"}} @onDelete={{this.removeFile}}>
277
- <div class="flex items-center justify-evenly">
278
- <Button
279
- @icon="magic"
280
- @text="Make Primary"
281
- @size="xs"
282
- @textClass="text-xs truncate"
283
- @onClick={{fn this.makePrimaryFile file}}
284
- @disabled={{eq this.product.primary_image_uuid file.id}}
285
- />
286
- </div>
287
- </FileRecord>
288
- {{/each}}
289
- </div>
290
- </div>
291
- {{/if}}
292
- </div>
293
- </ContentPanel>
372
+ </div>
373
+ </ContentPanel>
294
374
 
295
- <ContentPanel @title="Youtube Videos" @open={{this.product.youtube_urls.length}} @wrapperClass="mb-12" @panelBodyWrapperClass="p-4" @panelBodyClass="bg-white dark:bg-gray-800">
296
- <p class="text-sm">Add YouTube video urls to assosciate with this product or service.</p>
297
- <div class="input-group">
298
- <ArrayInput @data={{this.product.youtube_urls}} @placeholder="Enter youtube video url" @onDataChanged={{fn (mut this.product.youtube_urls)}}>
299
- <InputLabel @labelText="Youtube Video URLs" @helpText="Input product youtube video urls if applicable." />
300
- </ArrayInput>
301
- </div>
302
- </ContentPanel>
303
- <Spacer @height="300px" />
375
+ <ContentPanel @title="Youtube Videos" @open={{this.product.youtube_urls.length}} @wrapperClass="mb-12" @panelBodyWrapperClass="p-4" @panelBodyClass="bg-white dark:bg-gray-800">
376
+ <p class="text-sm">Add YouTube video urls to assosciate with this product or service.</p>
377
+ <div class="input-group">
378
+ <ArrayInput @data={{this.product.youtube_urls}} @placeholder="Enter youtube video url" @onDataChanged={{fn (mut this.product.youtube_urls)}} @disabled={{unauthorized}}>
379
+ <InputLabel @labelText="Youtube Video URLs" @helpText="Input product youtube video urls if applicable." />
380
+ </ArrayInput>
381
+ </div>
382
+ </ContentPanel>
383
+ <Spacer @height="300px" />
384
+ {{/let}}
304
385
  </Overlay::Body>
305
386
  </Overlay>