@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
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
<div class="sa-setup-card">
|
|
4
4
|
<div class="sa-setup-head">
|
|
5
5
|
<div class="sa-setup-badge">{{ iconText }}</div>
|
|
6
|
-
<div>
|
|
6
|
+
<div class="sa-setup-headings">
|
|
7
7
|
<h1 class="sa-setup-title">{{ msg.setup.title }}</h1>
|
|
8
8
|
<div class="sa-setup-sub">{{ subtitleText }}</div>
|
|
9
9
|
</div>
|
|
10
10
|
<LocaleSwitcher class="sa-setup-locale" />
|
|
11
|
+
<ThemeSwitcher class="sa-setup-theme" />
|
|
11
12
|
</div>
|
|
12
13
|
|
|
13
14
|
<q-banner v-if="errorMessage" class="sa-setup-error" rounded>
|
|
@@ -162,6 +163,7 @@ import { HttpJsonError, postJson as httpPostJson } from '../client/http-json.js'
|
|
|
162
163
|
import { formatMessage } from '../client/i18n/format.js';
|
|
163
164
|
import { useSaMessages } from '../vue/use-super-admin-i18n.js';
|
|
164
165
|
import LocaleSwitcher from '../components/LocaleSwitcher.vue';
|
|
166
|
+
import ThemeSwitcher from '../components/ThemeSwitcher.vue';
|
|
165
167
|
|
|
166
168
|
interface Props {
|
|
167
169
|
/** Display name + badge abbreviation (override the app branding, e.g. from PublicBoot). */
|
|
@@ -174,6 +176,23 @@ const props = defineProps<Props>();
|
|
|
174
176
|
// shown again — the freshly created SuperAdmin then signs in.
|
|
175
177
|
const emit = defineEmits<{ done: [] }>();
|
|
176
178
|
|
|
179
|
+
// sa-disclosure-exempt(uses `<details>`):
|
|
180
|
+
// the platform's own disclosure, on the one screen that owns its own frame
|
|
181
|
+
//
|
|
182
|
+
// The `otpauth://` URI hides behind a `<details>`/`<summary>` above, and it
|
|
183
|
+
// stays there. `AdminAccordion` exists because four of the eight surfaces it
|
|
184
|
+
// replaced were a `<div>` with a click handler — no keyboard, nothing to
|
|
185
|
+
// announce. `<details>` has none of that problem: it is the platform's own
|
|
186
|
+
// disclosure, operable by keyboard, announced as one, and it needs no state
|
|
187
|
+
// from the page at all.
|
|
188
|
+
//
|
|
189
|
+
// What is left is a consistency argument, and it points the other way here.
|
|
190
|
+
// This wizard renders OUTSIDE `AdminLayout` — it is one of the screens the page
|
|
191
|
+
// shell exempts, a centred card on a first run when no admin exists yet — and
|
|
192
|
+
// the surface in question is a one-line "show me the URI" for someone whose
|
|
193
|
+
// authenticator app could not read the QR code. An accordion card with a badge
|
|
194
|
+
// frame, an accent open border and a sunken body would make it the loudest
|
|
195
|
+
// element on the screen, to reveal one line of text.
|
|
177
196
|
const msg = useSaMessages('shell');
|
|
178
197
|
const brand = useSuperAdminBrand();
|
|
179
198
|
const endpoints = useSuperAdminEndpoints();
|
|
@@ -297,14 +316,24 @@ async function submitConfirm(): Promise<void> {
|
|
|
297
316
|
gap: 12px;
|
|
298
317
|
margin-bottom: 20px;
|
|
299
318
|
}
|
|
300
|
-
.sa-setup-locale
|
|
301
|
-
|
|
319
|
+
.sa-setup-locale,
|
|
320
|
+
.sa-setup-theme {
|
|
302
321
|
align-self: flex-start;
|
|
303
322
|
color: var(--sa-color-fg-secondary);
|
|
304
323
|
}
|
|
324
|
+
/* One `auto` on the first of the pair moves both. */
|
|
325
|
+
.sa-setup-locale {
|
|
326
|
+
margin-left: auto;
|
|
327
|
+
}
|
|
305
328
|
.sa-setup-badge {
|
|
306
329
|
width: 44px;
|
|
307
330
|
height: 44px;
|
|
331
|
+
/* Same reasoning as `.sa-login-logo` on the login card: the mark is a fixed
|
|
332
|
+
square, so it is the wrong flex item to take the row's shortfall from.
|
|
333
|
+
Here it was squeezed at every width, 1440 included — 35.61px in the
|
|
334
|
+
platform's own fixture, 33.56px in the example app — because the two
|
|
335
|
+
switchers made the row's content wider than the card outright. */
|
|
336
|
+
flex: none;
|
|
308
337
|
border-radius: 10px;
|
|
309
338
|
display: flex;
|
|
310
339
|
align-items: center;
|
|
@@ -322,6 +351,11 @@ async function submitConfirm(): Promise<void> {
|
|
|
322
351
|
font-size: var(--sa-text-xl);
|
|
323
352
|
text-transform: uppercase;
|
|
324
353
|
}
|
|
354
|
+
/* The block that absorbs what the pinned badge no longer gives up — see
|
|
355
|
+
`.sa-login-brand__text`, which carries the same rule for the same reason. */
|
|
356
|
+
.sa-setup-headings {
|
|
357
|
+
min-width: 0;
|
|
358
|
+
}
|
|
325
359
|
.sa-setup-title {
|
|
326
360
|
margin: 0;
|
|
327
361
|
font-weight: 800;
|
|
@@ -1,44 +1,41 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="sa-bundles__list">
|
|
3
|
-
<
|
|
3
|
+
<AdminAccordion
|
|
4
4
|
v-for="bundle in filteredBundles"
|
|
5
5
|
:key="bundle.id"
|
|
6
6
|
class="sa-bd-card"
|
|
7
|
-
:
|
|
7
|
+
:open="openKey === bundle.id"
|
|
8
|
+
@update:open="emit('toggle', bundle)"
|
|
8
9
|
>
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
<
|
|
15
|
-
class="sa-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
10
|
+
<template #mark><q-icon name="inventory_2" size="18px" /></template>
|
|
11
|
+
|
|
12
|
+
<template #header>
|
|
13
|
+
<div class="sa-bd-card__head">
|
|
14
|
+
<div class="sa-bd-card__titlewrap">
|
|
15
|
+
<div class="sa-bd-card__titlerow">
|
|
16
|
+
<span class="sa-bd-card__key">{{ bundle.bundleKey }}</span>
|
|
17
|
+
<span
|
|
18
|
+
class="sa-bundle-chip"
|
|
19
|
+
:class="`sa-bundle-chip--${statusClass(bundle)}`"
|
|
20
|
+
:title="statusTooltip(bundle)"
|
|
21
|
+
>
|
|
22
|
+
{{ statusLabel(bundle) }}
|
|
23
|
+
</span>
|
|
24
|
+
<span
|
|
25
|
+
v-if="i18nLocaleCount(bundle) > 0"
|
|
26
|
+
class="sa-bundle-chip sa-bundle-chip--info"
|
|
27
|
+
>
|
|
28
|
+
{{ translationCount(bundle) }}
|
|
29
|
+
</span>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="sa-bd-card__name">{{ bundle.label }}</div>
|
|
32
|
+
<div class="sa-bd-card__desc">{{ bundle.description || '—' }}</div>
|
|
27
33
|
</div>
|
|
28
|
-
<div class="sa-bd-card__name">{{ bundle.label }}</div>
|
|
29
|
-
<div class="sa-bd-card__desc">{{ bundle.description || '—' }}</div>
|
|
30
34
|
</div>
|
|
31
|
-
|
|
32
|
-
name="chevron_right"
|
|
33
|
-
class="sa-bd-card__chev"
|
|
34
|
-
:class="{ open: openKey === bundle.id }"
|
|
35
|
-
/>
|
|
36
|
-
</div>
|
|
35
|
+
</template>
|
|
37
36
|
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
</div>
|
|
41
|
-
</div>
|
|
37
|
+
<slot name="detail" :bundle="bundle" />
|
|
38
|
+
</AdminAccordion>
|
|
42
39
|
|
|
43
40
|
<div v-if="bundlesTotal > 0 && filteredBundles.length === 0" class="sa-bd-empty-row">
|
|
44
41
|
{{ msg.list.emptyNoMatch }}
|
|
@@ -48,6 +45,7 @@
|
|
|
48
45
|
|
|
49
46
|
<script setup lang="ts">
|
|
50
47
|
import type { BundleRow } from '@saasicat/types';
|
|
48
|
+
import AdminAccordion from '../../components/admin-page/AdminAccordion.vue';
|
|
51
49
|
import {
|
|
52
50
|
bundleStatusMeta,
|
|
53
51
|
type BundleAggregateStatus,
|
|
@@ -1,71 +1,79 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
{{
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
2
|
+
<AdminAccordion class="sa-fc" :open="expanded" @update:open="emit('toggle')">
|
|
3
|
+
<template #mark><q-icon :name="iconValue || 'help_outline'" size="18px" /></template>
|
|
4
|
+
|
|
5
|
+
<template #header>
|
|
6
|
+
<div class="sa-fc__head">
|
|
7
|
+
<div class="sa-fc__main">
|
|
8
|
+
<div class="sa-fc__titlerow">
|
|
9
|
+
<code class="sa-fc__key">{{ feature.featureKey }}</code>
|
|
10
|
+
<span class="sa-fc__label">{{ labelValue || feature.featureKey }}</span>
|
|
11
|
+
<span v-if="newCapsCount > 0" class="sa-fc__flag sa-fc__flag--new">
|
|
12
|
+
{{ newCapsLabel }}
|
|
13
|
+
</span>
|
|
14
|
+
<span v-if="deprecatedCapsCount > 0" class="sa-fc__flag sa-fc__flag--dep">
|
|
15
|
+
{{ deprecatedCapsLabel }}
|
|
16
|
+
</span>
|
|
17
|
+
<span
|
|
18
|
+
v-if="feature.successorKey"
|
|
19
|
+
class="sa-fc__flag sa-fc__flag--succ"
|
|
20
|
+
:title="replacedByLabel"
|
|
21
|
+
>
|
|
22
|
+
{{ replacedByLabel }}
|
|
23
|
+
</span>
|
|
24
|
+
<span
|
|
25
|
+
v-if="feature.replaces.length"
|
|
26
|
+
class="sa-fc__flag sa-fc__flag--repl"
|
|
27
|
+
:title="replacesLabel"
|
|
28
|
+
>
|
|
29
|
+
{{ replacesLabel }}
|
|
30
|
+
</span>
|
|
31
|
+
</div>
|
|
32
|
+
<div class="sa-fc__sub">
|
|
33
|
+
<template v-if="owners.length">
|
|
34
|
+
<span class="sa-muted">{{ msg.owner }}</span>
|
|
35
|
+
{{ owners.join(', ') }}
|
|
36
|
+
<span class="sa-fc__dot">·</span>
|
|
37
|
+
</template>
|
|
38
|
+
{{ capabilities.length }}
|
|
39
|
+
{{
|
|
40
|
+
capabilities.length === 1
|
|
41
|
+
? msg.feature.capabilityOne
|
|
42
|
+
: msg.feature.capabilityMany
|
|
43
|
+
}}
|
|
44
|
+
<template v-if="tierValue">
|
|
45
|
+
<span class="sa-fc__dot">·</span>
|
|
46
|
+
<span class="sa-fc__tier">{{ tierValue }}</span>
|
|
47
|
+
</template>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<div class="sa-fc__coverage">
|
|
22
52
|
<span
|
|
23
|
-
v-
|
|
24
|
-
|
|
25
|
-
|
|
53
|
+
v-for="lng in targetLocales"
|
|
54
|
+
:key="lng"
|
|
55
|
+
class="sa-cov-pill"
|
|
56
|
+
:class="coverageClass(coverage(lng))"
|
|
26
57
|
>
|
|
27
|
-
{{
|
|
58
|
+
<span>{{ localeShort(lng) }}</span>
|
|
59
|
+
<span>{{ coveragePct(coverage(lng)) }}%</span>
|
|
28
60
|
</span>
|
|
29
61
|
</div>
|
|
30
|
-
<div class="sa-fc__sub">
|
|
31
|
-
<template v-if="owners.length">
|
|
32
|
-
<span class="sa-muted">{{ msg.owner }}</span>
|
|
33
|
-
{{ owners.join(', ') }}
|
|
34
|
-
<span class="sa-fc__dot">·</span>
|
|
35
|
-
</template>
|
|
36
|
-
{{ capabilities.length }}
|
|
37
|
-
{{
|
|
38
|
-
capabilities.length === 1
|
|
39
|
-
? msg.feature.capabilityOne
|
|
40
|
-
: msg.feature.capabilityMany
|
|
41
|
-
}}
|
|
42
|
-
<template v-if="tierValue">
|
|
43
|
-
<span class="sa-fc__dot">·</span>
|
|
44
|
-
<span class="sa-fc__tier">{{ tierValue }}</span>
|
|
45
|
-
</template>
|
|
46
|
-
</div>
|
|
47
|
-
</div>
|
|
48
|
-
|
|
49
|
-
<div class="sa-fc__coverage">
|
|
50
|
-
<span
|
|
51
|
-
v-for="lng in targetLocales"
|
|
52
|
-
:key="lng"
|
|
53
|
-
class="sa-cov-pill"
|
|
54
|
-
:class="coverageClass(coverage(lng))"
|
|
55
|
-
>
|
|
56
|
-
<span>{{ localeShort(lng) }}</span>
|
|
57
|
-
<span>{{ coveragePct(coverage(lng)) }}%</span>
|
|
58
|
-
</span>
|
|
59
62
|
</div>
|
|
63
|
+
</template>
|
|
60
64
|
|
|
65
|
+
<!-- Outside the trigger, not merely `@click.stop` inside it. The old
|
|
66
|
+
head was a `<div @click>` with this control nested in it, and the
|
|
67
|
+
only thing keeping a status change from also toggling the card was
|
|
68
|
+
a `.stop` somebody remembered. Structure beats memory. -->
|
|
69
|
+
<template #header-actions>
|
|
61
70
|
<DiscoveryStatusControl
|
|
62
71
|
:status="feature.discoveryStatus"
|
|
63
72
|
@set-status="(target) => emit('review', feature.featureKey, target)"
|
|
64
73
|
/>
|
|
65
|
-
|
|
66
|
-
</div>
|
|
74
|
+
</template>
|
|
67
75
|
|
|
68
|
-
<div
|
|
76
|
+
<div class="sa-fc__body">
|
|
69
77
|
<div v-if="feature.discoveryStatus === 'outdated'" class="sa-fc__banner warn">
|
|
70
78
|
<q-icon name="warning" size="16px" />
|
|
71
79
|
<span>
|
|
@@ -140,7 +148,7 @@
|
|
|
140
148
|
"
|
|
141
149
|
/>
|
|
142
150
|
</div>
|
|
143
|
-
</
|
|
151
|
+
</AdminAccordion>
|
|
144
152
|
</template>
|
|
145
153
|
|
|
146
154
|
<script setup lang="ts">
|
|
@@ -152,6 +160,7 @@ import type {
|
|
|
152
160
|
FeatureCatalogEntryRow,
|
|
153
161
|
UpdateCatalogEntryBaseData,
|
|
154
162
|
} from '@saasicat/types';
|
|
163
|
+
import AdminAccordion from '../../components/admin-page/AdminAccordion.vue';
|
|
155
164
|
import CatalogEntryTransPanel from './CatalogEntryTransPanel.vue';
|
|
156
165
|
import DiscoveryCapList from './DiscoveryCapList.vue';
|
|
157
166
|
import DiscoveryStatusControl from './DiscoveryStatusControl.vue';
|
|
@@ -268,26 +277,16 @@ function coverage(locale: string): number {
|
|
|
268
277
|
</script>
|
|
269
278
|
|
|
270
279
|
<style scoped>
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
.sa-fc.expanded {
|
|
278
|
-
border-color: var(--sa-color-scheduled-border);
|
|
279
|
-
box-shadow: 0 1px 3px var(--sa-shadow-tint-2);
|
|
280
|
-
}
|
|
280
|
+
/* Surface, radius, open border, head padding, chevron and body come from
|
|
281
|
+
* `AdminAccordion`. What is left is what a FEATURE row puts in that header.
|
|
282
|
+
*
|
|
283
|
+
* These seven rules were byte-identical to `DiscoveryQuotaCard`'s after a
|
|
284
|
+
* prefix rename — jscpd could not see it, because `minTokens: 50` is larger
|
|
285
|
+
* than any single rule here. The duplication was real and below the threshold. */
|
|
281
286
|
.sa-fc__head {
|
|
282
287
|
display: flex;
|
|
283
288
|
align-items: center;
|
|
284
|
-
gap:
|
|
285
|
-
padding: 10px 12px;
|
|
286
|
-
cursor: pointer;
|
|
287
|
-
}
|
|
288
|
-
.sa-fc__icon {
|
|
289
|
-
color: var(--sa-color-fg-secondary);
|
|
290
|
-
flex-shrink: 0;
|
|
289
|
+
gap: var(--sa-space-4);
|
|
291
290
|
}
|
|
292
291
|
.sa-fc__main {
|
|
293
292
|
flex: 1;
|
|
@@ -340,9 +339,13 @@ function coverage(locale: string): number {
|
|
|
340
339
|
color: var(--sa-color-fg-muted);
|
|
341
340
|
margin-top: 1px;
|
|
342
341
|
}
|
|
342
|
+
/* `-subtle`, not `-disabled`. Nothing here is disabled: this is the quietest
|
|
343
|
+
* rung of readable text, and the disabled rung is not on that ladder — it read
|
|
344
|
+
* 1.48:1 on the card's own surface, which the contrast check only saw once a
|
|
345
|
+
* fixture rendered a feature row for the first time. */
|
|
343
346
|
.sa-fc__dot {
|
|
344
347
|
margin: 0 4px;
|
|
345
|
-
color: var(--sa-color-fg-
|
|
348
|
+
color: var(--sa-color-fg-subtle);
|
|
346
349
|
}
|
|
347
350
|
.sa-fc__tier {
|
|
348
351
|
font-weight: 700;
|
|
@@ -355,18 +358,6 @@ function coverage(locale: string): number {
|
|
|
355
358
|
gap: 6px;
|
|
356
359
|
flex-shrink: 0;
|
|
357
360
|
}
|
|
358
|
-
.sa-fc__chev {
|
|
359
|
-
color: var(--sa-color-fg-subtle);
|
|
360
|
-
transition: transform 0.15s;
|
|
361
|
-
}
|
|
362
|
-
.sa-fc__chev.open {
|
|
363
|
-
transform: rotate(90deg);
|
|
364
|
-
}
|
|
365
|
-
.sa-fc__body {
|
|
366
|
-
border-top: 1px solid var(--sa-color-border-soft);
|
|
367
|
-
padding: 12px;
|
|
368
|
-
background: var(--sa-color-bg-sunken);
|
|
369
|
-
}
|
|
370
361
|
.sa-fc__banner {
|
|
371
362
|
display: flex;
|
|
372
363
|
align-items: center;
|
|
@@ -403,11 +394,12 @@ function coverage(locale: string): number {
|
|
|
403
394
|
color: var(--sa-color-fg-subtle);
|
|
404
395
|
margin-bottom: 8px;
|
|
405
396
|
}
|
|
397
|
+
/* Same swap, same reason: "2 · read-only" sat at 1.42:1 on the sunken column. */
|
|
406
398
|
.sa-fc__split-count {
|
|
407
399
|
font-weight: 600;
|
|
408
400
|
text-transform: none;
|
|
409
401
|
letter-spacing: 0;
|
|
410
|
-
color: var(--sa-color-fg-
|
|
402
|
+
color: var(--sa-color-fg-subtle);
|
|
411
403
|
margin-left: 6px;
|
|
412
404
|
}
|
|
413
405
|
.sa-fc__fields {
|
|
@@ -1,62 +1,76 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
>
|
|
20
|
-
{{
|
|
21
|
-
|
|
2
|
+
<AdminAccordion
|
|
3
|
+
class="sa-qc"
|
|
4
|
+
:class="{ warn: !quota.usageProvider }"
|
|
5
|
+
:open="expanded"
|
|
6
|
+
:mark-tone="quota.usageProvider ? 'accent' : 'negative'"
|
|
7
|
+
@update:open="emit('toggle')"
|
|
8
|
+
>
|
|
9
|
+
<!-- The glyph already says it; the tone lets the badge say it too. The
|
|
10
|
+
`:color` this replaces was a Quasar PALETTE name, which reaches past
|
|
11
|
+
the role layer — `negative` there is Quasar's, not the theme's. -->
|
|
12
|
+
<template #mark>
|
|
13
|
+
<q-icon :name="quota.usageProvider ? 'inventory_2' : 'error'" size="18px" />
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<template #header>
|
|
17
|
+
<div class="sa-qc__head">
|
|
18
|
+
<div class="sa-qc__main">
|
|
19
|
+
<div class="sa-qc__titlerow">
|
|
20
|
+
<span class="sa-qc__label">{{ labelValue || quota.quotaKey }}</span>
|
|
21
|
+
<code class="sa-qc__key">{{ quota.quotaKey }}</code>
|
|
22
|
+
<span class="sa-chip">{{ quota.enforcementMode }}</span>
|
|
23
|
+
<span
|
|
24
|
+
v-if="quota.successorKey"
|
|
25
|
+
class="sa-qc__flag sa-qc__flag--succ"
|
|
26
|
+
:title="replacedByLabel"
|
|
27
|
+
>
|
|
28
|
+
{{ replacedByLabel }}
|
|
29
|
+
</span>
|
|
30
|
+
<span
|
|
31
|
+
v-if="quota.replaces.length"
|
|
32
|
+
class="sa-qc__flag sa-qc__flag--repl"
|
|
33
|
+
:title="replacesLabel"
|
|
34
|
+
>
|
|
35
|
+
{{ replacesLabel }}
|
|
36
|
+
</span>
|
|
37
|
+
</div>
|
|
38
|
+
<div class="sa-qc__sub">
|
|
39
|
+
{{ msg.unit }} <code>{{ quota.unit }}</code> · {{ msg.quota.usageProvider }}
|
|
40
|
+
<code v-if="quota.usageProvider">{{ quota.usageProvider }}</code>
|
|
41
|
+
<span v-else class="sa-qc__missing">{{
|
|
42
|
+
msg.quota.usageProviderMissing
|
|
43
|
+
}}</span>
|
|
44
|
+
</div>
|
|
45
|
+
<div v-if="!quota.usageProvider" class="sa-qc__warning">
|
|
46
|
+
{{ msg.quota.noUsageProviderWarning }}
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<div class="sa-qc__coverage">
|
|
22
51
|
<span
|
|
23
|
-
v-
|
|
24
|
-
|
|
25
|
-
|
|
52
|
+
v-for="lng in targetLocales"
|
|
53
|
+
:key="lng"
|
|
54
|
+
class="sa-cov-pill"
|
|
55
|
+
:class="coverageClass(coverage(lng))"
|
|
26
56
|
>
|
|
27
|
-
{{
|
|
57
|
+
<span>{{ localeShort(lng) }}</span>
|
|
58
|
+
<span>{{ coveragePct(coverage(lng)) }}%</span>
|
|
28
59
|
</span>
|
|
29
60
|
</div>
|
|
30
|
-
<div class="sa-qc__sub">
|
|
31
|
-
{{ msg.unit }} <code>{{ quota.unit }}</code> · {{ msg.quota.usageProvider }}
|
|
32
|
-
<code v-if="quota.usageProvider">{{ quota.usageProvider }}</code>
|
|
33
|
-
<span v-else class="sa-qc__missing">{{ msg.quota.usageProviderMissing }}</span>
|
|
34
|
-
</div>
|
|
35
|
-
<div v-if="!quota.usageProvider" class="sa-qc__warning">
|
|
36
|
-
{{ msg.quota.noUsageProviderWarning }}
|
|
37
|
-
</div>
|
|
38
|
-
</div>
|
|
39
|
-
|
|
40
|
-
<div class="sa-qc__coverage">
|
|
41
|
-
<span
|
|
42
|
-
v-for="lng in targetLocales"
|
|
43
|
-
:key="lng"
|
|
44
|
-
class="sa-cov-pill"
|
|
45
|
-
:class="coverageClass(coverage(lng))"
|
|
46
|
-
>
|
|
47
|
-
<span>{{ localeShort(lng) }}</span>
|
|
48
|
-
<span>{{ coveragePct(coverage(lng)) }}%</span>
|
|
49
|
-
</span>
|
|
50
61
|
</div>
|
|
62
|
+
</template>
|
|
51
63
|
|
|
64
|
+
<!-- See DiscoveryFeatureCard: outside the trigger, not `@click.stop`
|
|
65
|
+
inside it. -->
|
|
66
|
+
<template #header-actions>
|
|
52
67
|
<DiscoveryStatusControl
|
|
53
68
|
:status="quota.discoveryStatus"
|
|
54
69
|
@set-status="(target) => emit('review', quota.quotaKey, target)"
|
|
55
70
|
/>
|
|
56
|
-
|
|
57
|
-
</div>
|
|
71
|
+
</template>
|
|
58
72
|
|
|
59
|
-
<div
|
|
73
|
+
<div class="sa-qc__body">
|
|
60
74
|
<div v-if="quota.discoveryStatus === 'outdated'" class="sa-qc__banner">
|
|
61
75
|
<q-icon name="warning" size="16px" />
|
|
62
76
|
<span>
|
|
@@ -74,7 +88,7 @@
|
|
|
74
88
|
"
|
|
75
89
|
/>
|
|
76
90
|
</div>
|
|
77
|
-
</
|
|
91
|
+
</AdminAccordion>
|
|
78
92
|
</template>
|
|
79
93
|
|
|
80
94
|
<script setup lang="ts">
|
|
@@ -85,6 +99,7 @@ import type {
|
|
|
85
99
|
QuotaCatalogEntryRow,
|
|
86
100
|
UpdateCatalogEntryBaseData,
|
|
87
101
|
} from '@saasicat/types';
|
|
102
|
+
import AdminAccordion from '../../components/admin-page/AdminAccordion.vue';
|
|
88
103
|
import CatalogEntryTransPanel from './CatalogEntryTransPanel.vue';
|
|
89
104
|
import DiscoveryStatusControl from './DiscoveryStatusControl.vue';
|
|
90
105
|
import { formatMessage } from '../../client/i18n/format.js';
|
|
@@ -153,31 +168,22 @@ function coverage(locale: string): number {
|
|
|
153
168
|
</script>
|
|
154
169
|
|
|
155
170
|
<style scoped>
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
171
|
+
/* Surface, radius, open border, head padding, chevron and body come from
|
|
172
|
+
* `AdminAccordion`. What is left is what a QUOTA row puts in that header — and
|
|
173
|
+
* `warn`, which is this card's own idea and not the accordion's.
|
|
174
|
+
*
|
|
175
|
+
* Two values changed by moving: the open border was
|
|
176
|
+
* `--sa-color-scheduled-border` plus a shadow here and `--sa-color-accent`
|
|
177
|
+
* everywhere else, and the head padding was 10/12 against 12/14 next door.
|
|
178
|
+
* Those differences were the finding, not the design. */
|
|
162
179
|
.sa-qc.warn {
|
|
163
180
|
border-color: var(--sa-color-negative-border);
|
|
164
|
-
|
|
165
|
-
.sa-qc.expanded {
|
|
166
|
-
border-color: var(--sa-color-scheduled-border);
|
|
167
|
-
box-shadow: 0 1px 3px var(--sa-shadow-tint-2);
|
|
181
|
+
background: var(--sa-color-negative-surface);
|
|
168
182
|
}
|
|
169
183
|
.sa-qc__head {
|
|
170
184
|
display: flex;
|
|
171
185
|
align-items: center;
|
|
172
|
-
gap:
|
|
173
|
-
padding: 10px 12px;
|
|
174
|
-
cursor: pointer;
|
|
175
|
-
}
|
|
176
|
-
.sa-qc.warn .sa-qc__head {
|
|
177
|
-
background: var(--sa-color-negative-surface);
|
|
178
|
-
}
|
|
179
|
-
.sa-qc__icon {
|
|
180
|
-
flex-shrink: 0;
|
|
186
|
+
gap: var(--sa-space-4);
|
|
181
187
|
}
|
|
182
188
|
.sa-qc__main {
|
|
183
189
|
flex: 1;
|
|
@@ -230,18 +236,6 @@ function coverage(locale: string): number {
|
|
|
230
236
|
gap: 6px;
|
|
231
237
|
flex-shrink: 0;
|
|
232
238
|
}
|
|
233
|
-
.sa-qc__chev {
|
|
234
|
-
color: var(--sa-color-fg-subtle);
|
|
235
|
-
transition: transform 0.15s;
|
|
236
|
-
}
|
|
237
|
-
.sa-qc__chev.open {
|
|
238
|
-
transform: rotate(90deg);
|
|
239
|
-
}
|
|
240
|
-
.sa-qc__body {
|
|
241
|
-
border-top: 1px solid var(--sa-color-border-soft);
|
|
242
|
-
padding: 12px;
|
|
243
|
-
background: var(--sa-color-bg-sunken);
|
|
244
|
-
}
|
|
245
239
|
.sa-qc__banner {
|
|
246
240
|
display: flex;
|
|
247
241
|
align-items: center;
|