@feedmepos/mf-order-setting 0.0.76 → 0.0.77

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 (81) hide show
  1. package/dist/{KioskDevicesView-H7stMDFu.js → KioskDevicesView-Dgs5_UoK.js} +1 -1
  2. package/dist/{KioskDevicesView.vue_vue_type_script_setup_true_lang-DgpV4oKE.js → KioskDevicesView.vue_vue_type_script_setup_true_lang-B4DXPgAV.js} +3 -3
  3. package/dist/KioskSettingView-BUcPNVTc.js +681 -0
  4. package/dist/{KioskView-Cxcc1UL1.js → KioskView-DelVMMj4.js} +137 -127
  5. package/dist/{OrderSettingsView-Ds2L_rbu.js → OrderSettingsView-DE2U7vqW.js} +9232 -8262
  6. package/dist/{app-BDlPjU4y.js → app-BIvt4NBq.js} +1476 -1302
  7. package/dist/app.js +1 -1
  8. package/dist/{dayjs.min-OJf1trOq.js → dayjs.min-B58_jBg3.js} +1 -1
  9. package/dist/frontend/mf-order/src/api/index.d.ts +3 -1
  10. package/dist/frontend/mf-order/src/api/remoteOrder/index.d.ts +65 -2
  11. package/dist/frontend/mf-order/src/app.d.ts +174 -0
  12. package/dist/frontend/mf-order/src/composables/useInternalUser.d.ts +3 -0
  13. package/dist/frontend/mf-order/src/main.d.ts +174 -0
  14. package/dist/frontend/mf-order/src/modules/order-setting/kiosk/interface.d.ts +4 -0
  15. package/dist/frontend/mf-order/src/stores/order-setting/index.d.ts +15 -3
  16. package/dist/frontend/mf-order/src/views/order-settings/delivery/integrated-delivery/AdditionalStores.vue.d.ts +16 -0
  17. package/dist/frontend/mf-order/src/views/order-settings/delivery/integrated-delivery/ExternalSetting.vue.d.ts +2 -2
  18. package/dist/frontend/mf-order/src/views/order-settings/delivery/integrated-delivery/FoodpandaAdditionalStores.vue.d.ts +10 -0
  19. package/dist/frontend/mf-order/src/views/order-settings/delivery/integrated-delivery/IntegratedDelivery.vue.d.ts +15 -4
  20. package/dist/frontend/mf-order/src/views/order-settings/delivery/integrated-delivery/IntegrationIdentityDetails.vue.d.ts +10 -0
  21. package/dist/frontend/mf-order/src/views/order-settings/delivery/integrated-delivery/ShopeefoodAdditionalStores.vue.d.ts +10 -0
  22. package/dist/frontend/mf-order/tsconfig.app.tsbuildinfo +1 -1
  23. package/dist/index-CLS-FSH-.js +762 -0
  24. package/dist/{index-gGOpbnY3.js → index-CahsmFL1.js} +1 -1
  25. package/dist/{lineman-settings.dto-Cny_fF4n.js → lineman-settings.dto-DBDThIb0.js} +8999 -8874
  26. package/dist/package/entity/delivery/delivery.enum.d.ts +1 -1
  27. package/dist/package/entity/incoming-order/incoming-order.do.d.ts +4 -4
  28. package/dist/package/entity/kiosk/marketing/marketing.dto.d.ts +11 -0
  29. package/dist/package/entity/order-platform/base-integration.dto.d.ts +179 -0
  30. package/dist/package/entity/order-platform/deliveroo/deliveroo-dto.d.ts +2 -2
  31. package/dist/package/entity/order-platform/deliveroo/deliveroo-setting.do.d.ts +2 -2
  32. package/dist/package/entity/order-platform/external/order/external-order.dto.d.ts +2 -2
  33. package/dist/package/entity/order-platform/external/setting/external-setting.do.d.ts +4 -4
  34. package/dist/package/entity/order-platform/external/setting/external-setting.dto.d.ts +4 -4
  35. package/dist/package/entity/order-platform/foodpanda/foodpanda-menu.dto.d.ts +1 -1
  36. package/dist/package/entity/order-platform/foodpanda/foodpanda-settings.do.d.ts +105 -4
  37. package/dist/package/entity/order-platform/foodpanda/foodpanda-settings.dto.d.ts +30 -11
  38. package/dist/package/entity/order-platform/gofood/gofood-settings.do.d.ts +2 -2
  39. package/dist/package/entity/order-platform/gofood/gofood-settings.dto.d.ts +2 -2
  40. package/dist/package/entity/order-platform/grabfood/grabfood-order.do.d.ts +10 -10
  41. package/dist/package/entity/order-platform/grabfood/grabfood-settings.do.d.ts +134 -6
  42. package/dist/package/entity/order-platform/grabfood/grabfood.dto.d.ts +42 -20
  43. package/dist/package/entity/order-platform/lineman/lineman-settings.do.d.ts +2 -2
  44. package/dist/package/entity/order-platform/lineman/lineman-settings.dto.d.ts +2 -2
  45. package/dist/package/entity/order-platform/shopeefood/shopeefood-settings.do.d.ts +102 -4
  46. package/dist/package/entity/order-platform/shopeefood/shopeefood-settings.dto.d.ts +66 -5
  47. package/dist/package/entity/order-platform/tng/tng-settings.do.d.ts +2 -2
  48. package/dist/package/entity/order-platform/tng/tng-settings.dto.d.ts +4 -4
  49. package/dist/package/entity/order-setting/dine-in/dine-in.do.d.ts +2 -2
  50. package/dist/package/entity/order-setting/dine-in/dine-in.dto.d.ts +2 -2
  51. package/dist/package/entity/order-setting/kiosk/kiosk.do.d.ts +6 -0
  52. package/dist/package/entity/order-setting/kiosk/kiosk.dto.d.ts +6 -0
  53. package/dist/package/entity/order-setting/order-setting.do.d.ts +14 -4
  54. package/dist/package/entity/order-setting/order-setting.dto.d.ts +28 -8
  55. package/dist/package/entity/restaurant/restaurant.dto.d.ts +10 -0
  56. package/dist/package/entity/websocket/websocket.dto.d.ts +24 -0
  57. package/dist/style.css +1 -1
  58. package/package.json +1 -1
  59. package/src/api/index.ts +8 -2
  60. package/src/api/remoteOrder/index.ts +200 -4
  61. package/src/composables/useInternalUser.ts +14 -0
  62. package/src/locales/en-US.json +43 -0
  63. package/src/locales/ja-JP.json +43 -0
  64. package/src/locales/th-TH.json +90 -45
  65. package/src/locales/zh-CN.json +43 -0
  66. package/src/modules/order-setting/kiosk/interface.ts +29 -19
  67. package/src/stores/order-setting/mapper.ts +65 -55
  68. package/src/views/kiosk/settings/KioskSettingView.vue +33 -0
  69. package/src/views/order-settings/delivery/DeliverySetting.vue +0 -1
  70. package/src/views/order-settings/delivery/delivery.data.ts +3 -0
  71. package/src/views/order-settings/delivery/integrated-delivery/AdditionalStores.vue +472 -0
  72. package/src/views/order-settings/delivery/integrated-delivery/FoodpandaAdditionalStores.vue +314 -0
  73. package/src/views/order-settings/delivery/integrated-delivery/FoodpandaSetting.vue +27 -8
  74. package/src/views/order-settings/delivery/integrated-delivery/GrabfoodSetting.vue +31 -12
  75. package/src/views/order-settings/delivery/integrated-delivery/IntegratedDelivery.vue +23 -11
  76. package/src/views/order-settings/delivery/integrated-delivery/IntegrationIdentityDetails.vue +144 -0
  77. package/src/views/order-settings/delivery/integrated-delivery/ShopeefoodAdditionalStores.vue +335 -0
  78. package/src/views/order-settings/delivery/integrated-delivery/ShopeefoodSetting.vue +29 -8
  79. package/src/views/order-settings/queue/QueueSetting.vue +1 -1
  80. package/dist/KioskSettingView-fF6k9s_q.js +0 -658
  81. package/dist/index-Bid9lO2x.js +0 -665
@@ -0,0 +1,472 @@
1
+ <script setup lang="ts">
2
+ import { ref, reactive, computed, onMounted, watch } from 'vue'
3
+ import Day from 'dayjs'
4
+ import { useI18n } from '@feedmepos/mf-common'
5
+ import { useMenuStore } from '@/stores/menu/menu'
6
+ import { remoteOrderApi, type GrabfoodStoreView } from '@/api/remoteOrder'
7
+ import { useLoading } from '@/composables/loading'
8
+ import { useSnackbarFunctions } from '@/components/snackbar'
9
+ import { useDialog } from '@feedmepos/ui-library'
10
+ import IntegrationIdentityDetails from './IntegrationIdentityDetails.vue'
11
+ import { SvcCompanyApiError } from '@/api'
12
+ import { useInternalUser } from '@/composables/useInternalUser'
13
+
14
+ /**
15
+ * Multi-store: list of the ADDITIONAL GrabFood stores under this restaurant. The
16
+ * primary store is shown in the main GrabFood settings card above, not repeated
17
+ * here. Add / edit (catalog + enable) / remove all operate on additional stores;
18
+ * Add / Edit open a nested side sheet.
19
+ */
20
+ const props = defineProps<{ restaurantId: string; primaryEnabled: boolean }>()
21
+ const emits = defineEmits<{ (event: 'syncMenu', merchantID?: string): void }>()
22
+
23
+ const { t } = useI18n()
24
+ const menuStore = useMenuStore()
25
+ const { startAsyncCall, startAsyncCallWithErr } = useLoading()
26
+ const { showSuccess, showError } = useSnackbarFunctions()
27
+ const Dialog = useDialog()
28
+ const REMOTE_CLOSE_FAILED_CODE = 'GRABFOOD_REMOTE_CLOSE_FAILED'
29
+ const { isInternal } = useInternalUser()
30
+
31
+ const stores = ref<GrabfoodStoreView[]>([])
32
+ const editing = ref<null | {
33
+ mode: 'new' | 'edit'
34
+ integrationId: string | null
35
+ routeId: string
36
+ merchantID: string
37
+ }>(null)
38
+ const isSaving = ref(false)
39
+ const form = reactive<{ merchantID: string; catalogId: string | null; enable: boolean }>({
40
+ merchantID: '',
41
+ catalogId: null,
42
+ enable: true
43
+ })
44
+ // Snapshot of the store's saved values, captured when the Edit sheet opens. Lets us
45
+ // detect unsaved edits so Activate (Self-Serve) requires a Save first.
46
+ const originalForm = reactive<{ merchantID: string; catalogId: string | null; enable: boolean }>({
47
+ merchantID: '',
48
+ catalogId: null,
49
+ enable: true
50
+ })
51
+ const isDirty = computed(
52
+ () =>
53
+ editing.value?.mode === 'edit' &&
54
+ (form.merchantID !== originalForm.merchantID ||
55
+ form.catalogId !== originalForm.catalogId ||
56
+ form.enable !== originalForm.enable)
57
+ )
58
+
59
+ const catalogOptions = computed(() => menuStore.catalogOptions ?? [])
60
+ function catalogLabel(catalogId?: string | null) {
61
+ if (!catalogId) return '—'
62
+ return catalogOptions.value.find((catalog) => catalog.value === catalogId)?.label ?? catalogId
63
+ }
64
+
65
+ // Dates arrive as ISO strings; render them the same way the main GrabFood card
66
+ // (IntegratedDelivery) does. Null → em dash.
67
+ function formatDate(d?: string | null) {
68
+ return d ? Day(d).format('DD/MM/YYYY hh:mm a') : '—'
69
+ }
70
+
71
+ // The primary store is already shown in the main GrabFood settings card above, so
72
+ // this section lists only the ADDITIONAL stores. (save()'s uniqueness check still
73
+ // uses the full `stores` list so a new merchantID can't collide with the primary.)
74
+ const additionalStores = computed(() => stores.value.filter((s) => !s.isPrimary))
75
+ const showSyncAction = computed(
76
+ () => !props.primaryEnabled && additionalStores.value.some((store) => store.enable === true)
77
+ )
78
+
79
+ function syncMenu() {
80
+ const enabledStore = additionalStores.value.find((store) => store.enable === true)
81
+ emits('syncMenu', enabledStore?.merchantID)
82
+ }
83
+
84
+ // FmSideSheet emits update:model-value(false) when dismissed (backdrop / X) — treat
85
+ // that as Cancel so `editing` resets and the form closes.
86
+ function onSheetToggle(open: boolean) {
87
+ if (!open) cancelEdit()
88
+ }
89
+
90
+ async function load() {
91
+ if (!props.restaurantId) return
92
+ await startAsyncCallWithErr(async () => {
93
+ stores.value = await remoteOrderApi.integratedDelivery.listGrabfoodStores(props.restaurantId)
94
+ })
95
+ }
96
+ onMounted(load)
97
+ watch(
98
+ () => props.restaurantId,
99
+ () => load()
100
+ )
101
+
102
+ function openAdd() {
103
+ editing.value = { mode: 'new', integrationId: null, routeId: '', merchantID: '' }
104
+ form.merchantID = ''
105
+ form.catalogId = null
106
+ form.enable = true
107
+ }
108
+ function openEdit(s: GrabfoodStoreView) {
109
+ editing.value = {
110
+ mode: 'edit',
111
+ integrationId: s.integrationId,
112
+ routeId: s.routeId,
113
+ merchantID: s.merchantID
114
+ }
115
+ form.merchantID = s.merchantID
116
+ form.catalogId = s.catalogId ?? null
117
+ form.enable = s.enable ?? false
118
+ originalForm.merchantID = form.merchantID
119
+ originalForm.catalogId = form.catalogId
120
+ originalForm.enable = form.enable
121
+ }
122
+ function cancelEdit() {
123
+ editing.value = null
124
+ }
125
+
126
+ async function save() {
127
+ if (isSaving.value) return
128
+
129
+ const merchantID = form.merchantID.trim()
130
+ if (!merchantID) {
131
+ showError(t('order.merchantIdRequired'))
132
+ return
133
+ }
134
+ // Client-side uniqueness (backend is the source of truth): a collision is any
135
+ // OTHER store already using this merchantID. On edit, the store being edited is
136
+ // excluded — keeping its id (or self) is allowed, but renaming onto another
137
+ // store's id is blocked.
138
+ const editingId = editing.value?.mode === 'edit' ? editing.value.merchantID : null
139
+ if (stores.value.some((s) => s.merchantID === merchantID && s.merchantID !== editingId)) {
140
+ showError(t('order.merchantIdAlreadyExists'))
141
+ return
142
+ }
143
+ isSaving.value = true
144
+ try {
145
+ await startAsyncCallWithErr(async () => {
146
+ if (editing.value?.mode === 'new') {
147
+ await remoteOrderApi.integratedDelivery.addGrabfoodStore(props.restaurantId, {
148
+ merchantID,
149
+ catalogId: form.catalogId,
150
+ enable: form.enable
151
+ })
152
+ showSuccess(t('order.storeAdded'))
153
+ } else if (editing.value) {
154
+ // routeId is the client-side copy of this store's required integrationId.
155
+ const payload =
156
+ form.enable === false &&
157
+ merchantID === originalForm.merchantID &&
158
+ form.catalogId === originalForm.catalogId
159
+ ? { merchantID, enable: false }
160
+ : { merchantID, catalogId: form.catalogId, enable: form.enable }
161
+ await remoteOrderApi.integratedDelivery.updateGrabfoodStore(
162
+ props.restaurantId,
163
+ editing.value.routeId,
164
+ payload
165
+ )
166
+ showSuccess(t('order.storeUpdated'))
167
+ }
168
+ editing.value = null
169
+ await load()
170
+ })
171
+ } finally {
172
+ isSaving.value = false
173
+ }
174
+ }
175
+
176
+ // Remove flow: a center confirm dialog (useDialog) gates the destructive call, so
177
+ // the row's buttons no longer shuffle in place.
178
+ function promptRemove(s: GrabfoodStoreView) {
179
+ if (s.enable) {
180
+ showError(t('order.disableBeforeRemove'))
181
+ return
182
+ }
183
+ Dialog.open({
184
+ title: t('order.removeStoreConfirm', { merchantID: s.merchantID }),
185
+ primaryActions: { text: t('order.removeStore'), variant: 'destructive', close: true },
186
+ secondaryActions: { text: t('order.cancel'), variant: 'tertiary', close: true }
187
+ }).onPrimary(() => {
188
+ void confirmRemove(s)
189
+ })
190
+ }
191
+
192
+ function promptForceRemove(s: GrabfoodStoreView) {
193
+ Dialog.open({
194
+ title: t('order.forceRemoveStoreConfirm', { merchantID: s.merchantID }),
195
+ message: t('order.forceRemoveStoreWarning'),
196
+ dismissAway: false,
197
+ primaryActions: { text: t('order.forceRemoveStore'), variant: 'destructive', close: true },
198
+ secondaryActions: { text: t('order.cancel'), variant: 'tertiary', close: true }
199
+ }).onPrimary(() => {
200
+ void confirmRemove(s, true)
201
+ })
202
+ }
203
+
204
+ async function confirmRemove(s: GrabfoodStoreView, forceLocal = false) {
205
+ try {
206
+ await startAsyncCall(async () => {
207
+ await remoteOrderApi.integratedDelivery.removeGrabfoodStore(
208
+ props.restaurantId,
209
+ s.routeId,
210
+ forceLocal
211
+ )
212
+ showSuccess(t('order.storeRemoved'))
213
+ await load()
214
+ })
215
+ } catch (error) {
216
+ if (
217
+ !forceLocal &&
218
+ error instanceof SvcCompanyApiError &&
219
+ error.code === REMOTE_CLOSE_FAILED_CODE
220
+ ) {
221
+ promptForceRemove(s)
222
+ return
223
+ }
224
+ showError(`${error}`)
225
+ }
226
+ }
227
+
228
+ // Self-Serve Activation, opened from a store's Edit side sheet. Each additional
229
+ // store activates with its persisted FeedMe-generated integrationId; the backend
230
+ // sends that value in GrabFood's API-only partnerMerchantID field.
231
+ async function activate() {
232
+ const routeId = editing.value?.routeId
233
+ if (!routeId) return
234
+ await startAsyncCallWithErr(async () => {
235
+ const url = await remoteOrderApi.integratedDelivery.startSelfServeFlow(
236
+ props.restaurantId,
237
+ routeId
238
+ )
239
+ if (url) {
240
+ window.open(url, '_blank')
241
+ editing.value = null
242
+ await load()
243
+ } else showError('Failed to get GrabFood activation URL')
244
+ })
245
+ }
246
+ </script>
247
+
248
+ <template>
249
+ <div class="mt-10 store-section">
250
+ <div class="flex flex-row items-center mb-1">
251
+ <div class="flex-grow fm-typo-en-title-sm-600">{{ t('order.additionalStores') }}</div>
252
+ <div class="store-card__actions">
253
+ <FmButton
254
+ v-if="showSyncAction"
255
+ variant="primary"
256
+ :label="t('order.syncMenu')"
257
+ @click="syncMenu"
258
+ />
259
+ <FmButton
260
+ v-if="isInternal"
261
+ variant="secondary"
262
+ :label="t('order.addStore')"
263
+ @click="openAdd"
264
+ />
265
+ </div>
266
+ </div>
267
+ <div class="text-fm-color-neutral-gray-400 fm-typo-cn-body-md-400 mb-3">
268
+ {{ t('order.storesHint') }}
269
+ </div>
270
+
271
+ <div
272
+ v-if="additionalStores.length"
273
+ class="text-fm-color-neutral-gray-400 fm-typo-cn-body-md-400 mb-3"
274
+ >
275
+ {{ t('order.syncAllStoresHint') }}
276
+ </div>
277
+
278
+ <FmCard v-for="s in additionalStores" :key="s.routeId" variant="outlined" class="store-card">
279
+ <div class="store-card__top">
280
+ <div class="store-card__title fm-typo-en-title-sm-600">{{ s.merchantID }}</div>
281
+ <div class="store-card__actions">
282
+ <FmButton variant="secondary" :label="t('order.edit')" @click="openEdit(s)" />
283
+ <FmButton
284
+ v-if="isInternal"
285
+ variant="secondary"
286
+ :label="t('order.removeStore')"
287
+ @click="promptRemove(s)"
288
+ />
289
+ </div>
290
+ </div>
291
+ <div
292
+ v-if="isInternal && s.enable"
293
+ class="text-fm-color-neutral-gray-400 fm-typo-cn-body-md-400 mb-3"
294
+ >
295
+ {{ t('order.disableBeforeRemove') }}
296
+ </div>
297
+ <div class="store-card__fields fm-typo-cn-body-md-400">
298
+ <div class="store-field">
299
+ <span class="store-field__label text-fm-color-neutral-gray-400">{{
300
+ t('order.enabled')
301
+ }}</span>
302
+ <span>{{ s.enable ? '✓' : '✕' }}</span>
303
+ </div>
304
+ <div class="store-field">
305
+ <span class="store-field__label text-fm-color-neutral-gray-400">{{
306
+ t('order.catalog')
307
+ }}</span>
308
+ <span>{{ catalogLabel(s.catalogId) }}</span>
309
+ </div>
310
+ <div class="store-field">
311
+ <span class="store-field__label text-fm-color-neutral-gray-400">{{
312
+ t('order.status')
313
+ }}</span>
314
+ <span>{{ s.status ?? '—' }}</span>
315
+ </div>
316
+ <div class="store-field">
317
+ <span class="store-field__label text-fm-color-neutral-gray-400">{{
318
+ t('order.menuSyncError')
319
+ }}</span>
320
+ <span :class="{ 'text-fm-color-typo-error': s.menuSyncError }">{{
321
+ s.menuSyncError ?? '—'
322
+ }}</span>
323
+ </div>
324
+ <div class="store-field">
325
+ <span class="store-field__label text-fm-color-neutral-gray-400">{{
326
+ t('order.lastSync')
327
+ }}</span>
328
+ <span>{{ formatDate(s.lastSynchronize) }}</span>
329
+ </div>
330
+ <div class="store-field">
331
+ <span class="store-field__label text-fm-color-neutral-gray-400">{{
332
+ t('order.updatedAt')
333
+ }}</span>
334
+ <span>{{ formatDate(s.updatedAt) }}</span>
335
+ </div>
336
+ </div>
337
+ <IntegrationIdentityDetails
338
+ :integration-id="s.integrationId"
339
+ :provider-id="s.providerId ?? s.merchantID"
340
+ :provider-label="t('order.merchantId')"
341
+ />
342
+ </FmCard>
343
+
344
+ <FmSideSheet
345
+ :header="editing?.mode === 'new' ? t('order.addStore') : t('order.editStore')"
346
+ dismiss-away
347
+ class="w-full fm-typo-en-title-md-600"
348
+ :maxWidth="600"
349
+ :model-value="!!editing"
350
+ @update:model-value="onSheetToggle"
351
+ >
352
+ <div class="store-sheet-body">
353
+ <FmTextField
354
+ v-model="form.merchantID"
355
+ :label="t('order.merchantId')"
356
+ :disabled="!isInternal"
357
+ />
358
+ <FmSelect
359
+ v-model="form.catalogId"
360
+ :label="t('order.catalog')"
361
+ :placeholder="t('order.selectCatalog')"
362
+ :items="catalogOptions"
363
+ />
364
+ <FmSwitch v-model="form.enable" :label="t('order.enabled')" label-placement="right" />
365
+ <IntegrationIdentityDetails
366
+ v-if="editing?.mode === 'edit'"
367
+ :integration-id="editing.integrationId"
368
+ :provider-id="form.merchantID"
369
+ :provider-label="t('order.merchantId')"
370
+ />
371
+ <div v-if="editing?.mode === 'edit'" class="store-sheet-activate">
372
+ <FmButton
373
+ variant="primary"
374
+ :label="t('order.activate')"
375
+ :disabled="isDirty"
376
+ @click="activate()"
377
+ />
378
+ <div
379
+ v-if="isDirty"
380
+ class="store-sheet-activate__hint text-fm-color-neutral-gray-400 fm-typo-cn-body-md-400"
381
+ >
382
+ {{ t('order.saveBeforeActivate') }}
383
+ </div>
384
+ </div>
385
+ </div>
386
+ <template #side-sheet-footer>
387
+ <div class="flex gap-8 items-center justify-start">
388
+ <FmButton
389
+ variant="primary"
390
+ size="lg"
391
+ :label="editing?.mode === 'new' ? t('order.addStore') : t('order.save')"
392
+ :loading="isSaving"
393
+ :disabled="isSaving"
394
+ @click="save"
395
+ />
396
+ <FmButton variant="secondary" size="lg" :label="t('order.cancel')" @click="cancelEdit" />
397
+ </div>
398
+ </template>
399
+ </FmSideSheet>
400
+ </div>
401
+ </template>
402
+
403
+ <style scoped>
404
+ /* Visual divider separating the primary store (shown in the main GrabFood card above)
405
+ from the additional stores listed in this section. */
406
+ .store-section {
407
+ border-top: 1px solid #e5e7eb;
408
+ padding-top: 1.5rem;
409
+ }
410
+ .store-card {
411
+ padding: 1.25rem;
412
+ margin-bottom: 1rem;
413
+ }
414
+ .store-card__top {
415
+ display: flex;
416
+ align-items: flex-start;
417
+ justify-content: space-between;
418
+ gap: 1rem;
419
+ margin-bottom: 1.25rem;
420
+ }
421
+ .store-card__title {
422
+ min-width: 0;
423
+ word-break: break-all;
424
+ }
425
+ .store-card__actions {
426
+ display: flex;
427
+ flex-wrap: wrap;
428
+ justify-content: flex-end;
429
+ gap: 0.5rem;
430
+ flex-shrink: 0;
431
+ }
432
+ .store-card__fields {
433
+ display: flex;
434
+ flex-direction: column;
435
+ gap: 0.625rem;
436
+ }
437
+ /* Two-column label/value grid. The grid container owns the column widths, so the
438
+ label never collapses regardless of utility-class width quirks. */
439
+ .store-field {
440
+ display: grid;
441
+ grid-template-columns: 9rem minmax(0, 1fr);
442
+ align-items: start;
443
+ column-gap: 1rem;
444
+ row-gap: 0.125rem;
445
+ }
446
+ .store-field > span:last-child {
447
+ word-break: break-word;
448
+ }
449
+ /* Edit/Add side-sheet body: even vertical rhythm between the form fields. */
450
+ .store-sheet-body {
451
+ display: flex;
452
+ flex-direction: column;
453
+ gap: 1rem;
454
+ padding-top: 0.5rem;
455
+ }
456
+ /* Activate (Self-Serve) sits below the form fields in the Edit side sheet,
457
+ separated from the inputs above it. */
458
+ .store-sheet-activate {
459
+ padding-top: 1.25rem;
460
+ border-top: 1px solid #e5e7eb;
461
+ }
462
+ /* Shown when there are unsaved edits — Activate is disabled until the user saves. */
463
+ .store-sheet-activate__hint {
464
+ margin-top: 0.5rem;
465
+ }
466
+ /* Narrow container (e.g. mobile drawer): stack value under label. */
467
+ @media (max-width: 460px) {
468
+ .store-field {
469
+ grid-template-columns: 1fr;
470
+ }
471
+ }
472
+ </style>