@saasicat/ui-vue 0.24.2 → 0.26.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/README.md +32 -8
- package/dist/{catalog-DJcfm5N2.d.cts → catalog-Dch1Ryw0.d.cts} +353 -69
- package/dist/{catalog-DJcfm5N2.d.ts → catalog-Dch1Ryw0.d.ts} +353 -69
- package/dist/{chunk-F7NRM6KI.js → chunk-BPF2BMCQ.js} +748 -24
- package/dist/{chunk-D74FFCCP.js → chunk-NEXTZZRQ.js} +95 -177
- package/dist/{chunk-TQQMKWN6.js → chunk-O3J3ITF2.js} +119 -7
- package/dist/client/index.cjs +758 -83
- package/dist/client/index.d.cts +414 -9
- package/dist/client/index.d.ts +414 -9
- package/dist/client/index.js +84 -12
- package/dist/index.cjs +1325 -294
- package/dist/index.d.cts +205 -15
- package/dist/index.d.ts +205 -15
- package/dist/index.js +566 -240
- package/dist/quasar/index.cjs +574 -62
- package/dist/quasar/index.d.cts +61 -6
- package/dist/quasar/index.d.ts +61 -6
- package/dist/quasar/index.js +83 -19
- package/dist/{use-sa-theme-OUBlaqgl.d.ts → resource-registry-D7Xjs-5f.d.cts} +157 -7
- package/dist/{use-sa-theme-B5t7oXph.d.cts → resource-registry-TaR7rtq0.d.ts} +157 -7
- package/package.json +4 -3
- package/src/client/admin-error.ts +552 -0
- package/src/client/admin-resource-client.ts +9 -12
- package/src/client/batch-column-fetcher.ts +2 -2
- package/src/client/boot-loader.ts +12 -1
- package/src/client/http/axios-http-client.ts +383 -0
- package/src/client/http/fetch-http-client.ts +89 -0
- package/src/client/http/index.ts +6 -0
- package/src/client/http-json.ts +82 -16
- package/src/client/i18n/messages/discovery.ts +0 -6
- package/src/client/i18n/messages/errors.ts +44 -0
- package/src/client/i18n/messages/marketing.ts +0 -6
- package/src/client/i18n/messages/promos.ts +0 -2
- package/src/client/i18n/messages/shell.ts +12 -0
- package/src/client/i18n/messages.ts +3 -0
- package/src/client/index.ts +6 -0
- package/src/client/manifest-loader.ts +65 -15
- package/src/client/resources/audit.resource.ts +25 -0
- package/src/client/resources/define-resource.ts +166 -0
- package/src/client/resources/index.ts +42 -0
- package/src/client/resources/list-resource.ts +176 -0
- package/src/client/resources/plans.resource.ts +169 -0
- package/src/client/resources/resource-request.ts +117 -0
- package/src/client/resources/tenants.resource.ts +27 -0
- package/src/client/types.ts +10 -3
- package/src/components/BundleVersionPublishDialog.vue +1 -1
- package/src/components/MarketingPromotionsTab.vue +263 -241
- package/src/components/ThemeSwitcher.vue +85 -0
- package/src/components/admin-page/AdminAccordion.vue +125 -0
- package/src/components/bundle-editor/BundleCreatePanel.vue +1 -1
- package/src/components/dialogs/PromoCodeDialogFields.vue +20 -20
- package/src/components/plan/PlanCycleToggle.vue +1 -1
- package/src/components/plan-detail/PlanVersionsPanel.vue +5 -2
- package/src/index.ts +5 -0
- package/src/pages-standard/AdminLayout.vue +57 -0
- package/src/pages-standard/AdminManifestErrorPage.vue +4 -3
- package/src/pages-standard/BundlesPage.vue +5 -35
- package/src/pages-standard/DiscoveryPage.vue +8 -1
- package/src/pages-standard/MarketingCatalogPage.vue +14 -3
- package/src/pages-standard/SuperAdminLoginPage.vue +23 -2
- package/src/pages-standard/SuperAdminSetupWizard.vue +37 -3
- package/src/pages-standard/bundles-page/BundleAccordionList.vue +30 -32
- package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +80 -88
- package/src/pages-standard/discovery-page/DiscoveryQuotaCard.vue +73 -79
- package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +44 -6
- package/src/pages-standard/marketing-catalog/MarketingCatalogPreview.vue +8 -2
- package/src/pages-tenant/PackageSnapshotPanel.vue +31 -2
- package/src/quasar/confirm.ts +83 -0
- package/src/quasar/create-super-admin-app.ts +84 -10
- package/src/quasar/dark-bridge.ts +47 -10
- package/src/quasar/index.ts +1 -0
- package/src/ui/theme/components/accordion.css +118 -0
- package/src/ui/theme/index.css +1 -0
- package/src/vue/create-admin-routes.ts +9 -1
- package/src/vue/platform-loaders.ts +12 -2
- package/src/vue/resource-registry.ts +211 -0
- package/src/vue/super-admin-context.ts +20 -5
- package/src/vue/ui-confirm.ts +76 -0
- package/src/vue/use-api-list.ts +50 -45
- package/src/vue/use-async-action.ts +170 -0
- package/src/vue/use-async-data.ts +83 -0
- package/src/vue/use-bundles.ts +34 -7
- package/src/vue/use-catalog-entries.ts +37 -12
- package/src/vue/use-discovery.ts +27 -6
- package/src/vue/use-marketing-projections.ts +29 -7
- package/src/vue/use-plans.ts +36 -8
- package/src/vue/use-promotions.ts +27 -7
- package/src/vue/use-resource-list.ts +272 -0
- package/src/vue/use-sa-theme.ts +34 -5
- package/src/vue/use-tenant-subscription-bundles.ts +22 -3
|
@@ -37,9 +37,10 @@
|
|
|
37
37
|
<div class="mc-promo-today" :style="{ left: `${todayX}%` }">
|
|
38
38
|
<span>{{ msg.promotionsTab.today }}</span>
|
|
39
39
|
</div>
|
|
40
|
-
<
|
|
40
|
+
<button
|
|
41
41
|
v-for="bar in bars"
|
|
42
42
|
:key="bar.id"
|
|
43
|
+
type="button"
|
|
43
44
|
class="mc-promo-bar"
|
|
44
45
|
:class="bar.status"
|
|
45
46
|
:style="{
|
|
@@ -49,10 +50,11 @@
|
|
|
49
50
|
background: bar.color,
|
|
50
51
|
}"
|
|
51
52
|
:title="`${bar.label} · ${bar.from} → ${bar.to}`"
|
|
52
|
-
|
|
53
|
+
:aria-expanded="expandedId === bar.id"
|
|
54
|
+
@click="toggleExpanded(bar.id)"
|
|
53
55
|
>
|
|
54
56
|
<span class="mc-promo-bar-label">{{ bar.label }}</span>
|
|
55
|
-
</
|
|
57
|
+
</button>
|
|
56
58
|
</div>
|
|
57
59
|
</div>
|
|
58
60
|
|
|
@@ -63,250 +65,251 @@
|
|
|
63
65
|
|
|
64
66
|
<!-- List -->
|
|
65
67
|
<div class="mc-promo-list">
|
|
66
|
-
<
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
<div class="mc-promo-row
|
|
74
|
-
<
|
|
75
|
-
<div class="mc-promo-row-
|
|
76
|
-
<
|
|
77
|
-
<
|
|
78
|
-
{{
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
68
|
+
<AdminAccordion
|
|
69
|
+
v-for="p in sortedPromotions"
|
|
70
|
+
:key="p.id"
|
|
71
|
+
:open="expandedId === p.id"
|
|
72
|
+
@update:open="toggleExpanded(p.id)"
|
|
73
|
+
>
|
|
74
|
+
<template #header>
|
|
75
|
+
<div class="mc-promo-row">
|
|
76
|
+
<span class="mc-promo-color" :style="{ background: p.color }" />
|
|
77
|
+
<div class="mc-promo-row-main">
|
|
78
|
+
<div class="mc-promo-row-title">{{ p.internalLabel }}</div>
|
|
79
|
+
<div class="mc-promo-row-sub">
|
|
80
|
+
<span class="mc-promo-typechip">{{ typeChip(p) }}</span>
|
|
81
|
+
<span v-if="p.appliesTo.length === 0" class="mc-promo-muted">
|
|
82
|
+
{{ msg.promotionsTab.noPlans }}
|
|
83
|
+
</span>
|
|
84
|
+
<span v-for="k in p.appliesTo" :key="k" class="mc-promo-planchip">
|
|
85
|
+
{{ k }}
|
|
86
|
+
</span>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
<div class="mc-promo-row-when">
|
|
90
|
+
{{ p.validFrom }} → {{ p.validTo }}
|
|
91
|
+
<span class="mc-promo-cycle">
|
|
92
|
+
{{ cycleLabel(p.billingCycle) }}
|
|
93
|
+
<template v-if="p.onlyLocales">
|
|
94
|
+
· {{ msg.promotionsTab.onlyPrefix }}
|
|
95
|
+
{{ p.onlyLocales.join(', ').toUpperCase() }}
|
|
96
|
+
</template>
|
|
82
97
|
</span>
|
|
83
98
|
</div>
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
{{ p.validFrom }} → {{ p.validTo }}
|
|
87
|
-
<span class="mc-promo-cycle">
|
|
88
|
-
{{ cycleLabel(p.billingCycle) }}
|
|
89
|
-
<template v-if="p.onlyLocales">
|
|
90
|
-
· {{ msg.promotionsTab.onlyPrefix }}
|
|
91
|
-
{{ p.onlyLocales.join(', ').toUpperCase() }}
|
|
92
|
-
</template>
|
|
99
|
+
<span class="mc-promo-status" :class="statusOf(p)">
|
|
100
|
+
{{ statusLabel(statusOf(p)) }}
|
|
93
101
|
</span>
|
|
94
102
|
</div>
|
|
95
|
-
|
|
96
|
-
{{ statusLabel(statusOf(p)) }}
|
|
97
|
-
</span>
|
|
98
|
-
</div>
|
|
103
|
+
</template>
|
|
99
104
|
|
|
100
105
|
<!-- Editor -->
|
|
101
|
-
<div
|
|
102
|
-
<div class="mc-promo-editor-
|
|
103
|
-
<
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
<
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
</div>
|
|
126
|
-
|
|
127
|
-
<label class="mc-promo-label">{{ msg.promotionsTab.valueLabel }}</label>
|
|
128
|
-
<div class="mc-promo-valrow">
|
|
129
|
-
<template v-if="p.type === 'percent' || p.type === 'amount'">
|
|
130
|
-
<input
|
|
131
|
-
class="mc-promo-input mc-promo-input--sm"
|
|
132
|
-
type="number"
|
|
133
|
-
:value="numValue(p)"
|
|
134
|
-
@change="patch(p, { value: numInput($event) })"
|
|
135
|
-
/>
|
|
136
|
-
<span class="mc-promo-muted">
|
|
137
|
-
{{
|
|
138
|
-
p.type === 'percent'
|
|
139
|
-
? msg.promotionsTab.percentUnit
|
|
140
|
-
: msg.promotionsTab.amountUnit
|
|
141
|
-
}}
|
|
142
|
-
</span>
|
|
143
|
-
</template>
|
|
144
|
-
<template v-else-if="p.type === 'intro'">
|
|
145
|
-
<span class="mc-promo-muted">
|
|
146
|
-
{{ msg.promotionsTab.introForPrefix }}
|
|
147
|
-
</span>
|
|
148
|
-
<input
|
|
149
|
-
class="mc-promo-input mc-promo-input--sm"
|
|
150
|
-
type="number"
|
|
151
|
-
:value="introMonths(p)"
|
|
152
|
-
@change="patchIntro(p, 'months', numInput($event))"
|
|
153
|
-
/>
|
|
154
|
-
<span class="mc-promo-muted">
|
|
155
|
-
{{ msg.promotionsTab.introMonthsUnit }}
|
|
156
|
-
</span>
|
|
157
|
-
<input
|
|
158
|
-
class="mc-promo-input mc-promo-input--sm"
|
|
159
|
-
type="number"
|
|
160
|
-
:value="introPrice(p)"
|
|
161
|
-
@change="patchIntro(p, 'price', numInput($event))"
|
|
162
|
-
/>
|
|
163
|
-
<span class="mc-promo-muted">
|
|
164
|
-
{{ msg.promotionsTab.introPriceUnit }}
|
|
165
|
-
</span>
|
|
166
|
-
</template>
|
|
167
|
-
<template v-else>
|
|
168
|
-
<span class="mc-promo-muted">
|
|
169
|
-
{{ msg.promotionsTab.freeMonthsPrefix }}
|
|
170
|
-
</span>
|
|
171
|
-
<input
|
|
172
|
-
class="mc-promo-input mc-promo-input--sm"
|
|
173
|
-
type="number"
|
|
174
|
-
:value="numValue(p)"
|
|
175
|
-
@change="patch(p, { value: numInput($event) })"
|
|
176
|
-
/>
|
|
177
|
-
<span class="mc-promo-muted">
|
|
178
|
-
{{ msg.promotionsTab.freeMonthsUnit }}
|
|
179
|
-
</span>
|
|
180
|
-
</template>
|
|
181
|
-
</div>
|
|
106
|
+
<div class="mc-promo-editor-grid">
|
|
107
|
+
<div class="mc-promo-editor-col">
|
|
108
|
+
<label class="mc-promo-label">
|
|
109
|
+
{{ msg.promotionsTab.internalLabelLabel }}
|
|
110
|
+
</label>
|
|
111
|
+
<input
|
|
112
|
+
class="mc-promo-input"
|
|
113
|
+
:value="p.internalLabel"
|
|
114
|
+
@change="patch(p, { internalLabel: inputVal($event) })"
|
|
115
|
+
/>
|
|
116
|
+
|
|
117
|
+
<label class="mc-promo-label">{{ common.type }}</label>
|
|
118
|
+
<div class="mc-promo-typegrid">
|
|
119
|
+
<button
|
|
120
|
+
v-for="t in typeOptions"
|
|
121
|
+
:key="t.id"
|
|
122
|
+
type="button"
|
|
123
|
+
class="mc-promo-typeopt"
|
|
124
|
+
:class="{ active: p.type === t.id }"
|
|
125
|
+
@click="changeType(p, t.id)"
|
|
126
|
+
>
|
|
127
|
+
{{ t.label }}
|
|
128
|
+
</button>
|
|
129
|
+
</div>
|
|
182
130
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
131
|
+
<label class="mc-promo-label">{{ msg.promotionsTab.valueLabel }}</label>
|
|
132
|
+
<div class="mc-promo-valrow">
|
|
133
|
+
<template v-if="p.type === 'percent' || p.type === 'amount'">
|
|
134
|
+
<input
|
|
135
|
+
class="mc-promo-input mc-promo-input--sm"
|
|
136
|
+
type="number"
|
|
137
|
+
:value="numValue(p)"
|
|
138
|
+
@change="patch(p, { value: numInput($event) })"
|
|
139
|
+
/>
|
|
140
|
+
<span class="mc-promo-muted">
|
|
141
|
+
{{
|
|
142
|
+
p.type === 'percent'
|
|
143
|
+
? msg.promotionsTab.percentUnit
|
|
144
|
+
: msg.promotionsTab.amountUnit
|
|
145
|
+
}}
|
|
146
|
+
</span>
|
|
147
|
+
</template>
|
|
148
|
+
<template v-else-if="p.type === 'intro'">
|
|
149
|
+
<span class="mc-promo-muted">
|
|
150
|
+
{{ msg.promotionsTab.introForPrefix }}
|
|
151
|
+
</span>
|
|
187
152
|
<input
|
|
188
|
-
class="mc-promo-input"
|
|
189
|
-
type="
|
|
190
|
-
:value="p
|
|
191
|
-
@change="
|
|
153
|
+
class="mc-promo-input mc-promo-input--sm"
|
|
154
|
+
type="number"
|
|
155
|
+
:value="introMonths(p)"
|
|
156
|
+
@change="patchIntro(p, 'months', numInput($event))"
|
|
192
157
|
/>
|
|
193
|
-
<span class="mc-promo-muted"
|
|
158
|
+
<span class="mc-promo-muted">
|
|
159
|
+
{{ msg.promotionsTab.introMonthsUnit }}
|
|
160
|
+
</span>
|
|
194
161
|
<input
|
|
195
|
-
class="mc-promo-input"
|
|
196
|
-
type="
|
|
197
|
-
:value="p
|
|
198
|
-
@change="
|
|
162
|
+
class="mc-promo-input mc-promo-input--sm"
|
|
163
|
+
type="number"
|
|
164
|
+
:value="introPrice(p)"
|
|
165
|
+
@change="patchIntro(p, 'price', numInput($event))"
|
|
199
166
|
/>
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
:
|
|
212
|
-
@
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
167
|
+
<span class="mc-promo-muted">
|
|
168
|
+
{{ msg.promotionsTab.introPriceUnit }}
|
|
169
|
+
</span>
|
|
170
|
+
</template>
|
|
171
|
+
<template v-else>
|
|
172
|
+
<span class="mc-promo-muted">
|
|
173
|
+
{{ msg.promotionsTab.freeMonthsPrefix }}
|
|
174
|
+
</span>
|
|
175
|
+
<input
|
|
176
|
+
class="mc-promo-input mc-promo-input--sm"
|
|
177
|
+
type="number"
|
|
178
|
+
:value="numValue(p)"
|
|
179
|
+
@change="patch(p, { value: numInput($event) })"
|
|
180
|
+
/>
|
|
181
|
+
<span class="mc-promo-muted">
|
|
182
|
+
{{ msg.promotionsTab.freeMonthsUnit }}
|
|
183
|
+
</span>
|
|
184
|
+
</template>
|
|
185
|
+
</div>
|
|
217
186
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
187
|
+
<label class="mc-promo-label">
|
|
188
|
+
{{ common.validity }}
|
|
189
|
+
</label>
|
|
190
|
+
<div class="mc-promo-valrow">
|
|
221
191
|
<input
|
|
222
|
-
class="mc-promo-input
|
|
223
|
-
type="
|
|
224
|
-
:value="p.
|
|
225
|
-
@change="patch(p, {
|
|
192
|
+
class="mc-promo-input"
|
|
193
|
+
type="date"
|
|
194
|
+
:value="p.validFrom"
|
|
195
|
+
@change="patch(p, { validFrom: inputVal($event) })"
|
|
196
|
+
/>
|
|
197
|
+
<span class="mc-promo-muted">→</span>
|
|
198
|
+
<input
|
|
199
|
+
class="mc-promo-input"
|
|
200
|
+
type="date"
|
|
201
|
+
:value="p.validTo"
|
|
202
|
+
@change="patch(p, { validTo: inputVal($event) })"
|
|
226
203
|
/>
|
|
227
204
|
</div>
|
|
228
205
|
|
|
229
|
-
<
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
<
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
</button>
|
|
245
|
-
</div>
|
|
246
|
-
|
|
247
|
-
<label class="mc-promo-label">
|
|
248
|
-
{{ msg.promotionsTab.localeRestrictionLabel }}
|
|
249
|
-
</label>
|
|
250
|
-
<div class="mc-promo-typegrid">
|
|
251
|
-
<button
|
|
252
|
-
type="button"
|
|
253
|
-
class="mc-promo-typeopt"
|
|
254
|
-
:class="{ active: !p.onlyLocales }"
|
|
255
|
-
@click="patch(p, { onlyLocales: null })"
|
|
256
|
-
>
|
|
257
|
-
{{ msg.promotionsTab.allLocales }}
|
|
258
|
-
</button>
|
|
259
|
-
<button
|
|
260
|
-
v-for="l in activeLocales"
|
|
261
|
-
:key="l"
|
|
262
|
-
type="button"
|
|
263
|
-
class="mc-promo-typeopt"
|
|
264
|
-
:class="{ active: p.onlyLocales?.includes(l) }"
|
|
265
|
-
@click="toggleLocale(p, l)"
|
|
266
|
-
>
|
|
267
|
-
{{ msg.promotionsTab.onlyPrefix }} {{ l.toUpperCase() }}
|
|
268
|
-
</button>
|
|
269
|
-
</div>
|
|
206
|
+
<label class="mc-promo-label">
|
|
207
|
+
{{ msg.promotionsTab.billingCycleLabel }}
|
|
208
|
+
</label>
|
|
209
|
+
<div class="mc-promo-typegrid">
|
|
210
|
+
<button
|
|
211
|
+
v-for="c in cycleOptions"
|
|
212
|
+
:key="c.id"
|
|
213
|
+
type="button"
|
|
214
|
+
class="mc-promo-typeopt"
|
|
215
|
+
:class="{ active: p.billingCycle === c.id }"
|
|
216
|
+
@click="patch(p, { billingCycle: c.id })"
|
|
217
|
+
>
|
|
218
|
+
{{ c.label }}
|
|
219
|
+
</button>
|
|
220
|
+
</div>
|
|
270
221
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
/>
|
|
282
|
-
<input
|
|
283
|
-
class="mc-promo-input"
|
|
284
|
-
:placeholder="msg.promotionsTab.fineprintPlaceholder"
|
|
285
|
-
:value="p.i18n?.[l]?.fineprint || ''"
|
|
286
|
-
@change="patchI18n(p, l, 'fineprint', inputVal($event))"
|
|
287
|
-
/>
|
|
288
|
-
</div>
|
|
222
|
+
<label class="mc-promo-label">
|
|
223
|
+
{{ msg.promotionsTab.priorityLabel }}
|
|
224
|
+
</label>
|
|
225
|
+
<input
|
|
226
|
+
class="mc-promo-input mc-promo-input--sm"
|
|
227
|
+
type="number"
|
|
228
|
+
:value="p.priority"
|
|
229
|
+
@change="patch(p, { priority: numInput($event) })"
|
|
230
|
+
/>
|
|
231
|
+
</div>
|
|
289
232
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
233
|
+
<div class="mc-promo-editor-col">
|
|
234
|
+
<label class="mc-promo-label">
|
|
235
|
+
{{ msg.promotionsTab.appliesToLabel }}
|
|
236
|
+
</label>
|
|
237
|
+
<div class="mc-promo-planlist">
|
|
238
|
+
<button
|
|
239
|
+
v-for="pl in plans"
|
|
240
|
+
:key="pl.key"
|
|
241
|
+
type="button"
|
|
242
|
+
class="mc-promo-planopt"
|
|
243
|
+
:class="{ active: p.appliesTo.includes(pl.key) }"
|
|
244
|
+
@click="toggleApply(p, pl.key)"
|
|
245
|
+
>
|
|
246
|
+
{{ pl.label }}
|
|
247
|
+
<code>{{ pl.key }}</code>
|
|
248
|
+
</button>
|
|
249
|
+
</div>
|
|
302
250
|
|
|
303
|
-
|
|
304
|
-
|
|
251
|
+
<label class="mc-promo-label">
|
|
252
|
+
{{ msg.promotionsTab.localeRestrictionLabel }}
|
|
253
|
+
</label>
|
|
254
|
+
<div class="mc-promo-typegrid">
|
|
255
|
+
<button
|
|
256
|
+
type="button"
|
|
257
|
+
class="mc-promo-typeopt"
|
|
258
|
+
:class="{ active: !p.onlyLocales }"
|
|
259
|
+
@click="patch(p, { onlyLocales: null })"
|
|
260
|
+
>
|
|
261
|
+
{{ msg.promotionsTab.allLocales }}
|
|
262
|
+
</button>
|
|
263
|
+
<button
|
|
264
|
+
v-for="l in activeLocales"
|
|
265
|
+
:key="l"
|
|
266
|
+
type="button"
|
|
267
|
+
class="mc-promo-typeopt"
|
|
268
|
+
:class="{ active: p.onlyLocales?.includes(l) }"
|
|
269
|
+
@click="toggleLocale(p, l)"
|
|
270
|
+
>
|
|
271
|
+
{{ msg.promotionsTab.onlyPrefix }} {{ l.toUpperCase() }}
|
|
305
272
|
</button>
|
|
306
273
|
</div>
|
|
274
|
+
|
|
275
|
+
<label class="mc-promo-label">
|
|
276
|
+
{{ msg.promotionsTab.translationsLabel }}
|
|
277
|
+
</label>
|
|
278
|
+
<div v-for="l in activeLocales" :key="l" class="mc-promo-i18n-block">
|
|
279
|
+
<span class="mc-promo-i18n-code">{{ l.toUpperCase() }}</span>
|
|
280
|
+
<input
|
|
281
|
+
class="mc-promo-input"
|
|
282
|
+
:placeholder="msg.promotionsTab.badgePlaceholder"
|
|
283
|
+
:value="p.i18n?.[l]?.badge || ''"
|
|
284
|
+
@change="patchI18n(p, l, 'badge', inputVal($event))"
|
|
285
|
+
/>
|
|
286
|
+
<input
|
|
287
|
+
class="mc-promo-input"
|
|
288
|
+
:placeholder="msg.promotionsTab.fineprintPlaceholder"
|
|
289
|
+
:value="p.i18n?.[l]?.fineprint || ''"
|
|
290
|
+
@change="patchI18n(p, l, 'fineprint', inputVal($event))"
|
|
291
|
+
/>
|
|
292
|
+
</div>
|
|
293
|
+
|
|
294
|
+
<label class="mc-promo-label">{{ msg.promotionsTab.colorLabel }}</label>
|
|
295
|
+
<div class="mc-promo-colors">
|
|
296
|
+
<button
|
|
297
|
+
v-for="c in COLORS"
|
|
298
|
+
:key="c"
|
|
299
|
+
type="button"
|
|
300
|
+
class="mc-promo-colorbtn"
|
|
301
|
+
:class="{ active: p.color === c }"
|
|
302
|
+
:style="{ background: c }"
|
|
303
|
+
@click="patch(p, { color: c })"
|
|
304
|
+
/>
|
|
305
|
+
</div>
|
|
306
|
+
|
|
307
|
+
<button class="mc-promo-delete" type="button" @click="onRemove(p)">
|
|
308
|
+
{{ msg.promotionsTab.delete }}
|
|
309
|
+
</button>
|
|
307
310
|
</div>
|
|
308
311
|
</div>
|
|
309
|
-
</
|
|
312
|
+
</AdminAccordion>
|
|
310
313
|
</div>
|
|
311
314
|
</div>
|
|
312
315
|
</template>
|
|
@@ -322,6 +325,7 @@ import {
|
|
|
322
325
|
type PromotionType,
|
|
323
326
|
type UpdatePromotionData,
|
|
324
327
|
} from '@saasicat/types';
|
|
328
|
+
import AdminAccordion from './admin-page/AdminAccordion.vue';
|
|
325
329
|
import { formatMessage } from '../client/i18n/format.js';
|
|
326
330
|
import { useSaMessages, useSuperAdminI18n } from '../vue/use-super-admin-i18n.js';
|
|
327
331
|
|
|
@@ -350,6 +354,30 @@ const { intlLocale } = useSuperAdminI18n();
|
|
|
350
354
|
const busy = computed(() => props.busy ?? false);
|
|
351
355
|
const expandedId = ref<string | null>(null);
|
|
352
356
|
|
|
357
|
+
// sa-disclosure-exempt(toggles `expandedId`, writes `aria-expanded`):
|
|
358
|
+
// the timeline bar is a second trigger for the accordion below it
|
|
359
|
+
//
|
|
360
|
+
// The disclosure itself IS an `AdminAccordion` — one per promotion, in the list
|
|
361
|
+
// below, with the `<button>` and the `aria-expanded` that come with it. What
|
|
362
|
+
// this file adds is a second way into the same row: the bar that charts it on
|
|
363
|
+
// the timeline. A Gantt segment positioned by date cannot be an accordion
|
|
364
|
+
// header, so there is nothing here to migrate onto one.
|
|
365
|
+
//
|
|
366
|
+
// The rule sees the click write `expandedId` and the accordion's `:open` read
|
|
367
|
+
// it, and from the source it cannot tell a second trigger from a hand-rolled
|
|
368
|
+
// disclosure. What its story is actually about — "four of the eight were a
|
|
369
|
+
// `<div>` with a click handler" — is fixed rather than exempted here: the bar
|
|
370
|
+
// is a `<button>` that reports whether the row it charts is open. It carries no
|
|
371
|
+
// `aria-controls`, because the body's id is generated inside `AdminAccordion`
|
|
372
|
+
// and never leaves it; the row's own header owns that relationship.
|
|
373
|
+
|
|
374
|
+
// One expression for "open this one, close whatever was open". The row and the
|
|
375
|
+
// timeline bar above it both open the same editor, and they used to say so in
|
|
376
|
+
// two copies of the same ternary.
|
|
377
|
+
function toggleExpanded(id: string): void {
|
|
378
|
+
expandedId.value = expandedId.value === id ? null : id;
|
|
379
|
+
}
|
|
380
|
+
|
|
353
381
|
const typeOptions = computed<Array<{ id: PromotionType; label: string }>>(() => [
|
|
354
382
|
{ id: 'percent', label: msg.value.promotionsTab.typePercent },
|
|
355
383
|
{ id: 'amount', label: msg.value.promotionsTab.typeAmount },
|
|
@@ -661,15 +689,21 @@ async function onRemove(p: PromotionRow): Promise<void> {
|
|
|
661
689
|
color: var(--sa-color-negative-strong);
|
|
662
690
|
font-weight: 700;
|
|
663
691
|
}
|
|
692
|
+
/* A `<button>` wearing a bar: the browser's own button chrome would draw a
|
|
693
|
+
* border and a background over the promotion colour the row is identified by. */
|
|
664
694
|
.mc-promo-bar {
|
|
665
695
|
position: absolute;
|
|
666
696
|
height: 20px;
|
|
697
|
+
border: 0;
|
|
698
|
+
padding: 0;
|
|
667
699
|
border-radius: 5px;
|
|
668
700
|
cursor: pointer;
|
|
669
701
|
display: flex;
|
|
670
702
|
align-items: center;
|
|
671
703
|
overflow: hidden;
|
|
672
704
|
opacity: 0.92;
|
|
705
|
+
font: inherit;
|
|
706
|
+
text-align: left;
|
|
673
707
|
}
|
|
674
708
|
.mc-promo-bar.expired {
|
|
675
709
|
opacity: 0.45;
|
|
@@ -694,20 +728,15 @@ async function onRemove(p: PromotionRow): Promise<void> {
|
|
|
694
728
|
flex-direction: column;
|
|
695
729
|
gap: 6px;
|
|
696
730
|
}
|
|
731
|
+
/* Only what a PROMOTION puts in the header. The surface, border, radius, the
|
|
732
|
+
* padding, the open border and the hover feedback are `AdminAccordion`'s — this
|
|
733
|
+
* row used to state its own, and the editor below it stated a second, matching
|
|
734
|
+
* set so the two would read as one block. They were separated by the list's 6px
|
|
735
|
+
* gap all along. */
|
|
697
736
|
.mc-promo-row {
|
|
698
737
|
display: flex;
|
|
699
738
|
align-items: center;
|
|
700
739
|
gap: 12px;
|
|
701
|
-
background: var(--sa-color-bg-surface);
|
|
702
|
-
border: 1px solid var(--sa-color-border);
|
|
703
|
-
border-radius: 8px;
|
|
704
|
-
padding: 10px 12px;
|
|
705
|
-
cursor: pointer;
|
|
706
|
-
}
|
|
707
|
-
.mc-promo-row.expanded {
|
|
708
|
-
border-color: var(--sa-color-accent);
|
|
709
|
-
border-bottom-left-radius: 0;
|
|
710
|
-
border-bottom-right-radius: 0;
|
|
711
740
|
}
|
|
712
741
|
.mc-promo-color {
|
|
713
742
|
width: 10px;
|
|
@@ -777,13 +806,6 @@ async function onRemove(p: PromotionRow): Promise<void> {
|
|
|
777
806
|
background: var(--sa-color-border-soft);
|
|
778
807
|
color: var(--sa-color-fg-subtle);
|
|
779
808
|
}
|
|
780
|
-
.mc-promo-editor {
|
|
781
|
-
border: 1px solid var(--sa-color-accent);
|
|
782
|
-
border-top: 0;
|
|
783
|
-
border-radius: 0 0 8px 8px;
|
|
784
|
-
background: var(--sa-color-bg-sunken);
|
|
785
|
-
padding: 14px;
|
|
786
|
-
}
|
|
787
809
|
.mc-promo-editor-grid {
|
|
788
810
|
display: grid;
|
|
789
811
|
grid-template-columns: 1fr 1fr;
|