@antelopejs/dms-marketing 0.6.1 → 0.7.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/dist/config.d.ts +3 -0
- package/dist/config.js +11 -0
- package/dist/config.js.map +1 -1
- package/dist/data-api/websites.d.ts +10 -4
- package/dist/data-api/websites.js +79 -3
- package/dist/data-api/websites.js.map +1 -1
- package/dist/db/models/page_snapshots.model.d.ts +2 -0
- package/dist/db/models/page_snapshots.model.js +6 -0
- package/dist/db/models/page_snapshots.model.js.map +1 -1
- package/dist/db/models/websites.model.d.ts +1 -0
- package/dist/db/models/websites.model.js +9 -0
- package/dist/db/models/websites.model.js.map +1 -1
- package/dist/db/tables/page_snapshots.table.d.ts +1 -0
- package/dist/db/tables/page_snapshots.table.js +4 -0
- package/dist/db/tables/page_snapshots.table.js.map +1 -1
- package/dist/db/tables/websites.table.d.ts +2 -0
- package/dist/db/tables/websites.table.js +4 -0
- package/dist/db/tables/websites.table.js.map +1 -1
- package/dist/frontend-module.d.ts +6 -0
- package/dist/frontend-module.js +46 -0
- package/dist/frontend-module.js.map +1 -0
- package/dist/index.js +3 -11
- package/dist/index.js.map +1 -1
- package/dist/pages/acquisition/page.d.ts +1 -0
- package/dist/pages/acquisition/page.js +9 -4
- package/dist/pages/acquisition/page.js.map +1 -1
- package/dist/pages/blocks.d.ts +11 -2
- package/dist/pages/blocks.js +43 -5
- package/dist/pages/blocks.js.map +1 -1
- package/dist/pages/funnel/page.d.ts +1 -0
- package/dist/pages/funnel/page.js +1 -0
- package/dist/pages/funnel/page.js.map +1 -1
- package/dist/pages/funnels/page.d.ts +1 -0
- package/dist/pages/funnels/page.js +4 -2
- package/dist/pages/funnels/page.js.map +1 -1
- package/dist/pages/overview/page.d.ts +1 -0
- package/dist/pages/overview/page.js +5 -6
- package/dist/pages/overview/page.js.map +1 -1
- package/dist/pages/pages/page.d.ts +1 -0
- package/dist/pages/pages/page.js +1 -0
- package/dist/pages/pages/page.js.map +1 -1
- package/dist/pages/settings/form.js +1 -0
- package/dist/pages/settings/form.js.map +1 -1
- package/dist/pages/websites/page.d.ts +2 -3
- package/dist/pages/websites/page.js +63 -5
- package/dist/pages/websites/page.js.map +1 -1
- package/dist/routes/dashboard-tracking.js +5 -1
- package/dist/routes/dashboard-tracking.js.map +1 -1
- package/dist/routes/funnels.d.ts +2 -0
- package/dist/routes/funnels.js +6 -5
- package/dist/routes/funnels.js.map +1 -1
- package/dist/routes/public-endpoints.js +1 -1
- package/dist/routes/public-endpoints.js.map +1 -1
- package/dist/routes/snapshot.d.ts +1 -1
- package/dist/routes/snapshot.js +30 -13
- package/dist/routes/snapshot.js.map +1 -1
- package/dist/routes/stats.d.ts +10 -4
- package/dist/routes/stats.js +77 -23
- package/dist/routes/stats.js.map +1 -1
- package/dist/routes/websites.d.ts +5 -1
- package/dist/routes/websites.js +31 -13
- package/dist/routes/websites.js.map +1 -1
- package/dist/services/acquisition-report.d.ts +1 -0
- package/dist/services/acquisition-report.js +1 -0
- package/dist/services/acquisition-report.js.map +1 -1
- package/dist/services/collect.js +7 -2
- package/dist/services/collect.js.map +1 -1
- package/dist/services/dashboard-tracking.d.ts +7 -14
- package/dist/services/dashboard-tracking.js +42 -26
- package/dist/services/dashboard-tracking.js.map +1 -1
- package/dist/services/funnels-table.d.ts +3 -2
- package/dist/services/funnels-table.js +9 -9
- package/dist/services/funnels-table.js.map +1 -1
- package/dist/services/heatmap.d.ts +6 -0
- package/dist/services/heatmap.js +82 -17
- package/dist/services/heatmap.js.map +1 -1
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +1 -0
- package/dist/services/index.js.map +1 -1
- package/dist/services/prune.js +6 -1
- package/dist/services/prune.js.map +1 -1
- package/dist/services/realtime.d.ts +3 -0
- package/dist/services/realtime.js +49 -0
- package/dist/services/realtime.js.map +1 -0
- package/dist/services/request-body.d.ts +1 -0
- package/dist/services/request-body.js +18 -0
- package/dist/services/request-body.js.map +1 -1
- package/dist/services/sessionize.d.ts +1 -0
- package/dist/services/sessionize.js +9 -0
- package/dist/services/sessionize.js.map +1 -1
- package/dist/services/snapshots.d.ts +28 -1
- package/dist/services/snapshots.js +147 -26
- package/dist/services/snapshots.js.map +1 -1
- package/dist/services/tenant-website.d.ts +11 -0
- package/dist/services/tenant-website.js +27 -5
- package/dist/services/tenant-website.js.map +1 -1
- package/dist/services/website-report.d.ts +5 -0
- package/dist/services/website-report.js +13 -1
- package/dist/services/website-report.js.map +1 -1
- package/dist/services/workspace-scope.d.ts +31 -0
- package/dist/services/workspace-scope.js +72 -0
- package/dist/services/workspace-scope.js.map +1 -0
- package/dist/types/constants.d.ts +7 -0
- package/dist/types/constants.js +8 -1
- package/dist/types/constants.js.map +1 -1
- package/dist/types/events.d.ts +25 -25
- package/dist/types/snapshots.d.ts +29 -29
- package/dist/types/snapshots.js +1 -1
- package/dist/types/snapshots.js.map +1 -1
- package/dist/utils/funnel-steps-type.d.ts +13 -13
- package/frontend-vue/app/components/ChannelsCard.vue +19 -2
- package/frontend-vue/app/components/Context.vue +50 -376
- package/frontend-vue/app/components/FunnelBuilder.vue +28 -22
- package/frontend-vue/app/components/FunnelFigure.vue +15 -2
- package/frontend-vue/app/components/FunnelReport.vue +3 -0
- package/frontend-vue/app/components/FunnelTemplates.vue +15 -13
- package/frontend-vue/app/components/InstallGuide.vue +1 -1
- package/frontend-vue/app/components/PagePreview.vue +330 -74
- package/frontend-vue/app/components/PagesExplorer.vue +230 -85
- package/frontend-vue/app/components/TopListTabs.vue +51 -18
- package/frontend-vue/app/components/WebsiteCard.vue +394 -0
- package/frontend-vue/app/components/WebsiteCreate.vue +159 -0
- package/frontend-vue/app/components/WebsitePicker.vue +210 -0
- package/frontend-vue/app/components/WebsiteSettings.vue +539 -0
- package/frontend-vue/app/components/WorkspaceScope.vue +119 -0
- package/frontend-vue/app/composables/useContextWebsite.ts +105 -0
- package/frontend-vue/app/composables/useCopyDraft.ts +47 -0
- package/frontend-vue/app/composables/useMarketingApi.ts +92 -4
- package/frontend-vue/app/composables/useMarketingReads.ts +32 -23
- package/frontend-vue/app/composables/useMarketingRoutes.ts +25 -2
- package/frontend-vue/app/composables/useRecordCrumb.ts +17 -0
- package/frontend-vue/app/composables/useSnapshotFrame.ts +35 -1
- package/frontend-vue/app/utils/anchor-drift.ts +58 -0
- package/frontend-vue/app/utils/drift-dismissals.ts +20 -0
- package/frontend-vue/app/utils/fill-height.ts +141 -0
- package/frontend-vue/app/utils/format.ts +21 -2
- package/frontend-vue/app/utils/funnel-draft.ts +22 -0
- package/frontend-vue/app/utils/heatmap.ts +17 -0
- package/frontend-vue/app/utils/page-snapshot.ts +75 -0
- package/frontend-vue/app/utils/snapshot-options.ts +148 -0
- package/frontend-vue/app/utils/snapshotScrollRoot.ts +93 -0
- package/frontend-vue/app/utils/website-card.ts +93 -0
- package/frontend-vue/app/utils/website-purge.ts +95 -0
- package/frontend-vue/app/utils/website-settings.ts +121 -0
- package/frontend-vue/app/utils/website-state.ts +93 -0
- package/frontend-vue/app/utils/workspace-scope.ts +51 -0
- package/frontend-vue/i18n/locales/marketing-en-GB.json +112 -36
- package/frontend-vue/i18n/locales/marketing-fr-FR.json +112 -36
- package/frontend-vue/package.json +1 -1
- package/frontend-vue/tests/anchor-drift.test.ts +59 -0
- package/frontend-vue/tests/build-flags.test.ts +38 -0
- package/frontend-vue/tests/context-bar.test.ts +226 -0
- package/frontend-vue/tests/copy-draft.test.ts +64 -0
- package/frontend-vue/tests/drift-dismissals.test.ts +12 -0
- package/frontend-vue/tests/fill-height.test.ts +110 -0
- package/frontend-vue/tests/format.test.ts +14 -0
- package/frontend-vue/tests/funnel-draft.test.ts +37 -0
- package/frontend-vue/tests/heatmap.test.ts +25 -0
- package/frontend-vue/tests/install-guide.test.ts +25 -0
- package/frontend-vue/tests/marketing-reads.test.ts +20 -6
- package/frontend-vue/tests/page-snapshot.test.ts +62 -0
- package/frontend-vue/tests/record-crumb.test.ts +43 -0
- package/frontend-vue/tests/registration.test.ts +4 -1
- package/frontend-vue/tests/routes.test.ts +21 -0
- package/frontend-vue/tests/save-funnel.test.ts +47 -0
- package/frontend-vue/tests/server-parity.test.ts +3 -0
- package/frontend-vue/tests/snapshot-options.test.ts +148 -0
- package/frontend-vue/tests/snapshot-scroll-root.test.ts +84 -0
- package/frontend-vue/tests/stubs/frontend-module.ts +5 -1
- package/frontend-vue/tests/top-list-tabs.test.ts +158 -0
- package/frontend-vue/tests/website-card.test.ts +119 -0
- package/frontend-vue/tests/website-purge.test.ts +70 -0
- package/frontend-vue/tests/website-settings.test.ts +133 -0
- package/frontend-vue/tests/website-state.test.ts +96 -0
- package/frontend-vue/tests/workspace-scope-control.test.ts +113 -0
- package/frontend-vue/tests/workspace-scope.test.ts +55 -0
- package/package.json +5 -4
- package/skills/dms-marketing/SKILL.md +47 -6
- package/static/snapshot.js +539 -48
- package/static/tracker.js +154 -5
- package/frontend-vue/app/components/WebsitesGrid.vue +0 -812
|
@@ -1,812 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { computed, ref } from 'vue'
|
|
3
|
-
import { useDmsRouter, useI18n } from '#dms/frontend-module'
|
|
4
|
-
import {
|
|
5
|
-
useMarketingApi,
|
|
6
|
-
type MarketingWebsiteCard,
|
|
7
|
-
} from '../composables/useMarketingApi'
|
|
8
|
-
import { installLink, overviewLink } from '../composables/useMarketingRoutes'
|
|
9
|
-
import {
|
|
10
|
-
useWebsiteCards,
|
|
11
|
-
useWebsiteDeletionImpactRead,
|
|
12
|
-
} from '../composables/useMarketingReads'
|
|
13
|
-
import { useMarketingNow } from '../composables/useMarketingNow'
|
|
14
|
-
import {
|
|
15
|
-
formatNumber,
|
|
16
|
-
formatRelativeTime,
|
|
17
|
-
formatShortDate,
|
|
18
|
-
initials,
|
|
19
|
-
} from '../utils/format'
|
|
20
|
-
import { allowHostConfirmation, hostConflictOf } from '../utils/host-conflict'
|
|
21
|
-
import {
|
|
22
|
-
MAX_WEBSITE_DOMAIN_LENGTH,
|
|
23
|
-
MAX_WEBSITE_NAME_LENGTH,
|
|
24
|
-
fractionDigits,
|
|
25
|
-
} from '../constants'
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* The Websites page: one card per site with its state, volumes, accepted
|
|
29
|
-
* hosts and capture options; a site still waiting for its first pageview
|
|
30
|
-
* leads to the install guide. Hosts the guard refused show on any card, with
|
|
31
|
-
* the fix. The pencil opens a drawer with the site's General, Capture and
|
|
32
|
-
* Danger zone tabs.
|
|
33
|
-
*/
|
|
34
|
-
const props = withDefaults(
|
|
35
|
-
defineProps<{ fetchUrl?: string; componentId?: string; pageId?: string }>(),
|
|
36
|
-
{ fetchUrl: undefined, componentId: undefined, pageId: undefined },
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
type DrawerTab = 'general' | 'capture' | 'danger'
|
|
40
|
-
|
|
41
|
-
const CLOCK_TICK_MS = 15_000
|
|
42
|
-
|
|
43
|
-
const { t, locale } = useI18n()
|
|
44
|
-
const router = useDmsRouter()
|
|
45
|
-
const api = useMarketingApi()
|
|
46
|
-
const toast = useToast()
|
|
47
|
-
// A field-tied error from the save shows under its field, the rest as a toast.
|
|
48
|
-
const fieldErrors = useFieldErrors({
|
|
49
|
-
fields: {
|
|
50
|
-
name: 'website-name',
|
|
51
|
-
domain: 'website-domain',
|
|
52
|
-
extraDomains: 'website-hosts',
|
|
53
|
-
},
|
|
54
|
-
})
|
|
55
|
-
const { confirm } = useConfirm()
|
|
56
|
-
|
|
57
|
-
void props
|
|
58
|
-
|
|
59
|
-
const cardsRead = useWebsiteCards()
|
|
60
|
-
const cards = cardsRead.data
|
|
61
|
-
/** The cards could not be read; shown once a retry is over. */
|
|
62
|
-
const failed = computed(
|
|
63
|
-
() => !!cardsRead.error.value && cardsRead.status.value !== 'pending',
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
/** The cards again: a retry after a failed read. */
|
|
67
|
-
const load = (): Promise<void> => cardsRead.refresh()
|
|
68
|
-
|
|
69
|
-
/** The website a deletion is asked about. */
|
|
70
|
-
const deletionTarget = ref('')
|
|
71
|
-
const readDeletionImpact = useWebsiteDeletionImpactRead(deletionTarget)
|
|
72
|
-
|
|
73
|
-
const now = useMarketingNow(CLOCK_TICK_MS)
|
|
74
|
-
/** The browser's zone, which a server render writes the dates in. */
|
|
75
|
-
const readerZone = readerTimeZone()
|
|
76
|
-
|
|
77
|
-
function stateLine(card: MarketingWebsiteCard): string {
|
|
78
|
-
if (
|
|
79
|
-
(card.state === 'live' || card.state === 'imported') &&
|
|
80
|
-
card.lastActivityAt
|
|
81
|
-
) {
|
|
82
|
-
return t(`page.marketing.context.${card.state}`, {
|
|
83
|
-
ago: formatRelativeTime(card.lastActivityAt, now.value, locale.value),
|
|
84
|
-
})
|
|
85
|
-
}
|
|
86
|
-
return t(`page.marketing.context.state.${card.state}`)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const STATE_DOTS = {
|
|
90
|
-
live: 'bg-success',
|
|
91
|
-
imported: 'bg-info',
|
|
92
|
-
waiting: 'bg-warning',
|
|
93
|
-
paused: 'bg-(--ui-text-dimmed)',
|
|
94
|
-
} as const
|
|
95
|
-
|
|
96
|
-
async function openOverview(card: MarketingWebsiteCard): Promise<void> {
|
|
97
|
-
await api.selectContextWebsite(card.id)
|
|
98
|
-
await router.push(overviewLink(card.id))
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// --- Card actions ----------------------------------------------------------------
|
|
102
|
-
|
|
103
|
-
/** The card action running, as `<site id>:<what>`, for its spinner. */
|
|
104
|
-
const busy = ref<string | null>(null)
|
|
105
|
-
|
|
106
|
-
async function runCardAction(
|
|
107
|
-
key: string,
|
|
108
|
-
action: () => Promise<unknown>,
|
|
109
|
-
successTitle: string,
|
|
110
|
-
errorTitle: string,
|
|
111
|
-
): Promise<void> {
|
|
112
|
-
busy.value = key
|
|
113
|
-
try {
|
|
114
|
-
await action()
|
|
115
|
-
toast.add({ color: 'success', title: successTitle })
|
|
116
|
-
// Their state and counts are the backend's: the page reads them again,
|
|
117
|
-
// the card's spinner on until it shows them.
|
|
118
|
-
await refreshPageBlocks()
|
|
119
|
-
} catch (error) {
|
|
120
|
-
const conflict = hostConflictOf(error)
|
|
121
|
-
if (conflict) {
|
|
122
|
-
toast.add({
|
|
123
|
-
color: 'error',
|
|
124
|
-
title: t(errorTitle),
|
|
125
|
-
description: t('page.marketing.errors.duplicate_host_named', {
|
|
126
|
-
...conflict,
|
|
127
|
-
}),
|
|
128
|
-
})
|
|
129
|
-
} else {
|
|
130
|
-
fieldErrors.toastError(error, { toastTitle: errorTitle })
|
|
131
|
-
}
|
|
132
|
-
} finally {
|
|
133
|
-
busy.value = null
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
async function allowHost(card: MarketingWebsiteCard, hostname: string) {
|
|
138
|
-
if (!(await confirm(allowHostConfirmation(t, hostname)))) {
|
|
139
|
-
return
|
|
140
|
-
}
|
|
141
|
-
await runCardAction(
|
|
142
|
-
`${card.id}:${hostname}`,
|
|
143
|
-
() => api.allowHost(card.id, hostname),
|
|
144
|
-
t('page.marketing.install.allowed', { host: hostname }),
|
|
145
|
-
'page.marketing.install.allow_error',
|
|
146
|
-
)
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function resume(card: MarketingWebsiteCard) {
|
|
150
|
-
return runCardAction(
|
|
151
|
-
`${card.id}:resume`,
|
|
152
|
-
() => api.updateWebsite(card.id, { trackingEnabled: true }),
|
|
153
|
-
t('page.marketing.websites.resumed'),
|
|
154
|
-
'page.marketing.websites.save_error',
|
|
155
|
-
)
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// --- Drawer --------------------------------------------------------------------
|
|
159
|
-
|
|
160
|
-
const editing = ref<MarketingWebsiteCard | null>(null)
|
|
161
|
-
const drawerOpen = ref(false)
|
|
162
|
-
const tab = ref<DrawerTab>('general')
|
|
163
|
-
const draftName = ref('')
|
|
164
|
-
const draftDomain = ref('')
|
|
165
|
-
const draftHosts = ref<string[]>([])
|
|
166
|
-
const draftTracking = ref(true)
|
|
167
|
-
const draftSnapshots = ref(false)
|
|
168
|
-
const draftMask = ref(false)
|
|
169
|
-
const saving = ref(false)
|
|
170
|
-
|
|
171
|
-
function edit(
|
|
172
|
-
card: MarketingWebsiteCard,
|
|
173
|
-
initialTab: DrawerTab = 'general',
|
|
174
|
-
): void {
|
|
175
|
-
fieldErrors.clear()
|
|
176
|
-
editing.value = card
|
|
177
|
-
tab.value = initialTab
|
|
178
|
-
draftName.value = card.name
|
|
179
|
-
draftDomain.value = card.domain
|
|
180
|
-
draftHosts.value = [...card.extraDomains]
|
|
181
|
-
draftTracking.value = card.state !== 'paused'
|
|
182
|
-
draftSnapshots.value = card.snapshotsEnabled
|
|
183
|
-
draftMask.value = card.snapshotMaskText
|
|
184
|
-
drawerOpen.value = true
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// The content stays while the drawer slides out; it goes once it is gone.
|
|
188
|
-
function onDrawerClosed(): void {
|
|
189
|
-
editing.value = null
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const tabItems = computed(() => [
|
|
193
|
-
{ value: 'general', label: t('page.marketing.websites.drawer.general') },
|
|
194
|
-
{ value: 'capture', label: t('page.marketing.websites.drawer.capture') },
|
|
195
|
-
{ value: 'danger', label: t('page.marketing.websites.drawer.danger') },
|
|
196
|
-
])
|
|
197
|
-
|
|
198
|
-
/** Turning snapshots off, or changing the mask, discards the captures. */
|
|
199
|
-
const discardsSnapshots = computed(
|
|
200
|
-
() =>
|
|
201
|
-
editing.value !== null &&
|
|
202
|
-
editing.value.snapshots > 0 &&
|
|
203
|
-
(draftSnapshots.value !== editing.value.snapshotsEnabled ||
|
|
204
|
-
draftMask.value !== editing.value.snapshotMaskText),
|
|
205
|
-
)
|
|
206
|
-
|
|
207
|
-
/** Empty required fields say so under themselves, on the General tab. */
|
|
208
|
-
async function requireFields(): Promise<boolean> {
|
|
209
|
-
const blank = (['domain', 'name'] as const).filter(
|
|
210
|
-
(field) => !{ name: draftName, domain: draftDomain }[field].value.trim(),
|
|
211
|
-
)
|
|
212
|
-
if (blank.length > 0) {
|
|
213
|
-
tab.value = 'general'
|
|
214
|
-
}
|
|
215
|
-
for (const field of blank) {
|
|
216
|
-
await fieldErrors.setError(field, t('dms.field_errors.required'))
|
|
217
|
-
}
|
|
218
|
-
return blank.length === 0
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
async function save(): Promise<void> {
|
|
222
|
-
const card = editing.value
|
|
223
|
-
if (!card || !(await requireFields())) {
|
|
224
|
-
return
|
|
225
|
-
}
|
|
226
|
-
saving.value = true
|
|
227
|
-
try {
|
|
228
|
-
await api.updateWebsite(card.id, {
|
|
229
|
-
name: draftName.value.trim(),
|
|
230
|
-
domain: draftDomain.value.trim(),
|
|
231
|
-
extraDomains: draftHosts.value.map((host) => host.trim()).filter(Boolean),
|
|
232
|
-
trackingEnabled: draftTracking.value,
|
|
233
|
-
snapshotsEnabled: draftSnapshots.value,
|
|
234
|
-
snapshotMaskText: draftMask.value,
|
|
235
|
-
})
|
|
236
|
-
toast.add({ color: 'success', title: t('page.marketing.websites.saved') })
|
|
237
|
-
drawerOpen.value = false
|
|
238
|
-
refreshPageBlocks()
|
|
239
|
-
} catch (error) {
|
|
240
|
-
// A refused field lives on the General tab: show it there.
|
|
241
|
-
const conflict = hostConflictOf(error)
|
|
242
|
-
if (conflict) {
|
|
243
|
-
tab.value = 'general'
|
|
244
|
-
await fieldErrors.setError(
|
|
245
|
-
conflict.field === 'extraDomains' ? 'extraDomains' : 'domain',
|
|
246
|
-
t('page.marketing.errors.duplicate_host_named', { ...conflict }),
|
|
247
|
-
)
|
|
248
|
-
return
|
|
249
|
-
}
|
|
250
|
-
if (await fieldErrors.applyApiError(error)) {
|
|
251
|
-
tab.value = 'general'
|
|
252
|
-
return
|
|
253
|
-
}
|
|
254
|
-
fieldErrors.toastError(error, {
|
|
255
|
-
toastTitle: 'page.marketing.websites.save_error',
|
|
256
|
-
})
|
|
257
|
-
} finally {
|
|
258
|
-
saving.value = false
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
async function remove(card: MarketingWebsiteCard): Promise<void> {
|
|
263
|
-
let impact
|
|
264
|
-
try {
|
|
265
|
-
deletionTarget.value = card.id
|
|
266
|
-
impact = await readDeletionImpact()
|
|
267
|
-
} catch (error) {
|
|
268
|
-
fieldErrors.toastError(error, {
|
|
269
|
-
toastTitle: 'page.marketing.websites.delete.impact_error',
|
|
270
|
-
})
|
|
271
|
-
return
|
|
272
|
-
}
|
|
273
|
-
// Every stored day goes, so the counts read "since the oldest one".
|
|
274
|
-
const since =
|
|
275
|
-
impact.since === null ? null : formatShortDate(impact.since, locale.value)
|
|
276
|
-
const sinceLabel = (key: 'sessions' | 'pageviews') =>
|
|
277
|
-
since === null
|
|
278
|
-
? t(`page.marketing.websites.delete.${key}`)
|
|
279
|
-
: t(`page.marketing.websites.delete.${key}_since`, { date: since })
|
|
280
|
-
await confirm({
|
|
281
|
-
title: t('page.marketing.websites.delete.title', { name: card.name }),
|
|
282
|
-
description: t('page.marketing.websites.delete.description'),
|
|
283
|
-
color: 'error',
|
|
284
|
-
icon: 'i-ph-trash',
|
|
285
|
-
confirmLabel: t('page.marketing.websites.delete.confirm'),
|
|
286
|
-
// A hostname is not case-sensitive: neither is the typed check.
|
|
287
|
-
confirmText: card.domain,
|
|
288
|
-
confirmTextCaseInsensitive: true,
|
|
289
|
-
impact: [
|
|
290
|
-
{
|
|
291
|
-
icon: 'i-ph-users',
|
|
292
|
-
label: sinceLabel('sessions'),
|
|
293
|
-
count: formatNumber(impact.sessions, locale.value),
|
|
294
|
-
},
|
|
295
|
-
{
|
|
296
|
-
icon: 'i-ph-eye',
|
|
297
|
-
label: sinceLabel('pageviews'),
|
|
298
|
-
count: formatNumber(impact.pageviews, locale.value),
|
|
299
|
-
},
|
|
300
|
-
{
|
|
301
|
-
icon: 'i-ph-funnel',
|
|
302
|
-
label: t('page.marketing.websites.delete.funnels'),
|
|
303
|
-
count: impact.funnels,
|
|
304
|
-
},
|
|
305
|
-
{
|
|
306
|
-
icon: 'i-ph-camera',
|
|
307
|
-
label: t('page.marketing.websites.delete.snapshots'),
|
|
308
|
-
count: impact.snapshots,
|
|
309
|
-
},
|
|
310
|
-
],
|
|
311
|
-
onConfirm: async () => {
|
|
312
|
-
await api.deleteWebsite(card.id)
|
|
313
|
-
toast.add({
|
|
314
|
-
color: 'success',
|
|
315
|
-
title: t('page.marketing.websites.delete.done', { name: card.name }),
|
|
316
|
-
})
|
|
317
|
-
drawerOpen.value = false
|
|
318
|
-
// Its card goes; the others are unchanged, so nothing is read again.
|
|
319
|
-
cards.value = cards.value?.filter((other) => other.id !== card.id) ?? null
|
|
320
|
-
},
|
|
321
|
-
})
|
|
322
|
-
}
|
|
323
|
-
</script>
|
|
324
|
-
|
|
325
|
-
<template>
|
|
326
|
-
<div class="flex flex-col gap-4">
|
|
327
|
-
<DmsEmptyState
|
|
328
|
-
v-if="failed"
|
|
329
|
-
variant="error"
|
|
330
|
-
class="dms-card"
|
|
331
|
-
:title="t('page.marketing.websites.error')"
|
|
332
|
-
>
|
|
333
|
-
<template #actions>
|
|
334
|
-
<UButton
|
|
335
|
-
size="sm"
|
|
336
|
-
color="neutral"
|
|
337
|
-
variant="outline"
|
|
338
|
-
icon="i-ph-arrow-clockwise"
|
|
339
|
-
:label="t('page.marketing.common.retry')"
|
|
340
|
-
@click="load"
|
|
341
|
-
/>
|
|
342
|
-
</template>
|
|
343
|
-
</DmsEmptyState>
|
|
344
|
-
|
|
345
|
-
<div v-else-if="!cards" class="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
|
|
346
|
-
<USkeleton v-for="index in 3" :key="index" class="h-72 rounded-xl" />
|
|
347
|
-
</div>
|
|
348
|
-
|
|
349
|
-
<DmsMarketingFirstRun v-else-if="cards.length === 0" />
|
|
350
|
-
|
|
351
|
-
<template v-else>
|
|
352
|
-
<!-- The page's "collection paused" Banner block, when the module is paused. -->
|
|
353
|
-
<slot />
|
|
354
|
-
|
|
355
|
-
<div class="grid items-start gap-4 md:grid-cols-2 xl:grid-cols-3">
|
|
356
|
-
<article
|
|
357
|
-
v-for="card in cards"
|
|
358
|
-
:key="card.id"
|
|
359
|
-
class="flex flex-col overflow-hidden rounded-xl border bg-(--ui-bg)"
|
|
360
|
-
:class="
|
|
361
|
-
card.state === 'waiting'
|
|
362
|
-
? 'border-dashed border-warning/60'
|
|
363
|
-
: 'dms-card p-0'
|
|
364
|
-
"
|
|
365
|
-
>
|
|
366
|
-
<header class="flex items-start gap-3 px-4 pt-4">
|
|
367
|
-
<span
|
|
368
|
-
class="grid size-10 shrink-0 place-items-center rounded-lg bg-primary/15 font-mono text-sm font-semibold text-(--ui-color-primary-800) dark:text-primary"
|
|
369
|
-
>
|
|
370
|
-
{{ initials(card.name) }}
|
|
371
|
-
</span>
|
|
372
|
-
<span class="min-w-0 flex-1">
|
|
373
|
-
<span class="block truncate font-semibold text-highlighted">
|
|
374
|
-
{{ card.name }}
|
|
375
|
-
</span>
|
|
376
|
-
<span class="block truncate font-mono text-xs text-dimmed">
|
|
377
|
-
{{ card.domain }}
|
|
378
|
-
</span>
|
|
379
|
-
</span>
|
|
380
|
-
<UButton
|
|
381
|
-
icon="i-ph-pencil-simple"
|
|
382
|
-
size="xs"
|
|
383
|
-
color="neutral"
|
|
384
|
-
variant="ghost"
|
|
385
|
-
:aria-label="
|
|
386
|
-
t('page.marketing.websites.edit_named', { name: card.name })
|
|
387
|
-
"
|
|
388
|
-
@click="edit(card)"
|
|
389
|
-
/>
|
|
390
|
-
</header>
|
|
391
|
-
<p
|
|
392
|
-
class="flex items-center gap-2 px-4 pt-3 pb-3 font-mono text-xs text-muted"
|
|
393
|
-
>
|
|
394
|
-
<span class="size-2 rounded-full" :class="STATE_DOTS[card.state]" />
|
|
395
|
-
{{ stateLine(card) }}
|
|
396
|
-
</p>
|
|
397
|
-
|
|
398
|
-
<div
|
|
399
|
-
v-if="card.state === 'paused' || card.rejected.length > 0"
|
|
400
|
-
class="flex flex-col gap-2 px-4 pb-3"
|
|
401
|
-
>
|
|
402
|
-
<DmsBanner
|
|
403
|
-
v-if="card.state === 'paused'"
|
|
404
|
-
tone="info"
|
|
405
|
-
size="sm"
|
|
406
|
-
icon="i-ph-pause-circle"
|
|
407
|
-
:title="t('page.marketing.websites.paused_description')"
|
|
408
|
-
>
|
|
409
|
-
<template #actions>
|
|
410
|
-
<UButton
|
|
411
|
-
size="xs"
|
|
412
|
-
color="neutral"
|
|
413
|
-
variant="outline"
|
|
414
|
-
icon="i-ph-play"
|
|
415
|
-
:loading="busy === `${card.id}:resume`"
|
|
416
|
-
:label="t('page.marketing.websites.resume')"
|
|
417
|
-
@click="resume(card)"
|
|
418
|
-
/>
|
|
419
|
-
</template>
|
|
420
|
-
</DmsBanner>
|
|
421
|
-
<DmsBanner
|
|
422
|
-
v-for="refused in card.rejected"
|
|
423
|
-
:key="refused.hostname"
|
|
424
|
-
tone="warning"
|
|
425
|
-
size="sm"
|
|
426
|
-
icon="i-ph-prohibit"
|
|
427
|
-
:title="
|
|
428
|
-
t(
|
|
429
|
-
'page.marketing.websites.refused',
|
|
430
|
-
{ count: refused.count, host: refused.hostname },
|
|
431
|
-
refused.count,
|
|
432
|
-
)
|
|
433
|
-
"
|
|
434
|
-
>
|
|
435
|
-
<template #actions>
|
|
436
|
-
<UButton
|
|
437
|
-
size="xs"
|
|
438
|
-
color="neutral"
|
|
439
|
-
variant="outline"
|
|
440
|
-
:loading="busy === `${card.id}:${refused.hostname}`"
|
|
441
|
-
:label="t('page.marketing.install.allow')"
|
|
442
|
-
@click="allowHost(card, refused.hostname)"
|
|
443
|
-
/>
|
|
444
|
-
</template>
|
|
445
|
-
</DmsBanner>
|
|
446
|
-
</div>
|
|
447
|
-
|
|
448
|
-
<template v-if="card.state !== 'waiting'">
|
|
449
|
-
<div class="border-y border-default">
|
|
450
|
-
<!-- The window the three counts cover, whatever period the
|
|
451
|
-
other pages show. -->
|
|
452
|
-
<p
|
|
453
|
-
class="px-4 pt-1.5 text-right font-mono text-[10px] text-dimmed"
|
|
454
|
-
>
|
|
455
|
-
{{ t('page.marketing.websites.stats.window') }}
|
|
456
|
-
</p>
|
|
457
|
-
<dl class="grid grid-cols-3">
|
|
458
|
-
<div
|
|
459
|
-
v-for="stat in ['sessions', 'pages', 'funnels'] as const"
|
|
460
|
-
:key="stat"
|
|
461
|
-
class="border-default px-4 pt-0.5 pb-3 not-first:border-l"
|
|
462
|
-
>
|
|
463
|
-
<dt>
|
|
464
|
-
<DmsEyebrow
|
|
465
|
-
:label="t(`page.marketing.websites.stats.${stat}`)"
|
|
466
|
-
/>
|
|
467
|
-
</dt>
|
|
468
|
-
<dd
|
|
469
|
-
class="mt-1 font-mono text-lg font-semibold tabular-nums text-highlighted"
|
|
470
|
-
>
|
|
471
|
-
{{ formatNumber(card[stat], locale) }}
|
|
472
|
-
</dd>
|
|
473
|
-
</div>
|
|
474
|
-
</dl>
|
|
475
|
-
</div>
|
|
476
|
-
<dl class="flex flex-col gap-2 px-4 py-3 text-sm">
|
|
477
|
-
<div class="flex items-start justify-between gap-3">
|
|
478
|
-
<dt
|
|
479
|
-
class="flex shrink-0 items-center gap-2 whitespace-nowrap text-muted"
|
|
480
|
-
>
|
|
481
|
-
<UIcon name="i-ph-link" class="size-3.5" />
|
|
482
|
-
{{ t('page.marketing.websites.also_accepts') }}
|
|
483
|
-
</dt>
|
|
484
|
-
<dd class="flex min-w-0 flex-wrap justify-end gap-1">
|
|
485
|
-
<span
|
|
486
|
-
v-for="host in card.extraDomains"
|
|
487
|
-
:key="host"
|
|
488
|
-
class="max-w-full truncate rounded bg-elevated px-1.5 py-0.5 font-mono text-[11px] text-toned"
|
|
489
|
-
>
|
|
490
|
-
{{ host }}
|
|
491
|
-
</span>
|
|
492
|
-
<span
|
|
493
|
-
v-if="card.extraDomains.length === 0"
|
|
494
|
-
class="text-xs text-dimmed"
|
|
495
|
-
>
|
|
496
|
-
{{ t('page.marketing.websites.subdomains_only') }}
|
|
497
|
-
</span>
|
|
498
|
-
</dd>
|
|
499
|
-
</div>
|
|
500
|
-
<div class="flex items-center justify-between gap-3">
|
|
501
|
-
<dt
|
|
502
|
-
class="flex shrink-0 items-center gap-2 whitespace-nowrap text-muted"
|
|
503
|
-
>
|
|
504
|
-
<UIcon name="i-ph-camera" class="size-3.5" />
|
|
505
|
-
{{ t('page.marketing.websites.snapshots') }}
|
|
506
|
-
</dt>
|
|
507
|
-
<dd>
|
|
508
|
-
<DmsStatusPill
|
|
509
|
-
size="sm"
|
|
510
|
-
:mono="false"
|
|
511
|
-
:tone="card.snapshotsEnabled ? 'success' : 'neutral'"
|
|
512
|
-
:label="
|
|
513
|
-
card.snapshotsEnabled
|
|
514
|
-
? t(
|
|
515
|
-
card.snapshotMaskText
|
|
516
|
-
? 'page.marketing.websites.snapshots_masked'
|
|
517
|
-
: 'page.marketing.websites.snapshots_on',
|
|
518
|
-
)
|
|
519
|
-
: t('page.marketing.websites.snapshots_off')
|
|
520
|
-
"
|
|
521
|
-
/>
|
|
522
|
-
</dd>
|
|
523
|
-
</div>
|
|
524
|
-
<div class="flex items-center justify-between gap-3">
|
|
525
|
-
<dt
|
|
526
|
-
class="flex shrink-0 items-center gap-2 whitespace-nowrap text-muted"
|
|
527
|
-
>
|
|
528
|
-
<UIcon name="i-ph-cursor-click" class="size-3.5" />
|
|
529
|
-
{{ t('page.marketing.websites.sampling') }}
|
|
530
|
-
</dt>
|
|
531
|
-
<dd class="text-right font-mono text-xs text-highlighted">
|
|
532
|
-
{{
|
|
533
|
-
formatPercentage(card.sampleRate, locale, fractionDigits(0))
|
|
534
|
-
}}
|
|
535
|
-
·
|
|
536
|
-
{{ t('page.marketing.websites.sampling_default') }}
|
|
537
|
-
</dd>
|
|
538
|
-
</div>
|
|
539
|
-
</dl>
|
|
540
|
-
<footer
|
|
541
|
-
class="mt-auto flex items-center justify-between gap-2 border-t border-default px-4 py-3"
|
|
542
|
-
>
|
|
543
|
-
<UButton
|
|
544
|
-
icon="i-ph-code"
|
|
545
|
-
size="sm"
|
|
546
|
-
color="neutral"
|
|
547
|
-
variant="outline"
|
|
548
|
-
:label="t('page.marketing.websites.snippet_button')"
|
|
549
|
-
:aria-label="
|
|
550
|
-
t('page.marketing.websites.snippet_named', {
|
|
551
|
-
name: card.name,
|
|
552
|
-
})
|
|
553
|
-
"
|
|
554
|
-
:to="installLink(card.id)"
|
|
555
|
-
/>
|
|
556
|
-
<UButton
|
|
557
|
-
trailing-icon="i-ph-arrow-right"
|
|
558
|
-
size="sm"
|
|
559
|
-
color="neutral"
|
|
560
|
-
variant="ghost"
|
|
561
|
-
:label="t('page.marketing.websites.open_overview')"
|
|
562
|
-
:aria-label="
|
|
563
|
-
t('page.marketing.websites.open_overview_named', {
|
|
564
|
-
name: card.name,
|
|
565
|
-
})
|
|
566
|
-
"
|
|
567
|
-
@click="openOverview(card)"
|
|
568
|
-
/>
|
|
569
|
-
</footer>
|
|
570
|
-
</template>
|
|
571
|
-
|
|
572
|
-
<template v-else>
|
|
573
|
-
<p class="px-4 text-sm text-muted">
|
|
574
|
-
{{
|
|
575
|
-
t(
|
|
576
|
-
card.rejected.length > 0
|
|
577
|
-
? 'page.marketing.websites.waiting_refused'
|
|
578
|
-
: 'page.marketing.websites.waiting_description',
|
|
579
|
-
{
|
|
580
|
-
date: formatShortDate(card.createdAt, locale, readerZone),
|
|
581
|
-
},
|
|
582
|
-
)
|
|
583
|
-
}}
|
|
584
|
-
</p>
|
|
585
|
-
<footer class="mt-auto px-4 py-4">
|
|
586
|
-
<UButton
|
|
587
|
-
icon="i-ph-pulse"
|
|
588
|
-
size="sm"
|
|
589
|
-
:label="t('page.marketing.websites.finish_install')"
|
|
590
|
-
:aria-label="
|
|
591
|
-
t('page.marketing.websites.finish_install_named', {
|
|
592
|
-
name: card.name,
|
|
593
|
-
})
|
|
594
|
-
"
|
|
595
|
-
:to="installLink(card.id)"
|
|
596
|
-
/>
|
|
597
|
-
</footer>
|
|
598
|
-
</template>
|
|
599
|
-
</article>
|
|
600
|
-
</div>
|
|
601
|
-
</template>
|
|
602
|
-
|
|
603
|
-
<p
|
|
604
|
-
v-if="cards && cards.length > 0"
|
|
605
|
-
class="flex items-center gap-2 text-xs text-dimmed"
|
|
606
|
-
>
|
|
607
|
-
<UIcon name="i-ph-shield-check" class="size-4" />
|
|
608
|
-
{{ t('page.marketing.websites.privacy') }}
|
|
609
|
-
</p>
|
|
610
|
-
|
|
611
|
-
<USlideover
|
|
612
|
-
v-model:open="drawerOpen"
|
|
613
|
-
:ui="{ content: 'max-w-lg', title: 'min-w-0' }"
|
|
614
|
-
@after:leave="onDrawerClosed"
|
|
615
|
-
>
|
|
616
|
-
<template #title>
|
|
617
|
-
<span v-if="editing" class="flex min-w-0 flex-col gap-2">
|
|
618
|
-
<DmsEyebrow :label="t('page.marketing.websites.drawer.eyebrow')" />
|
|
619
|
-
<span class="flex min-w-0 items-center gap-3">
|
|
620
|
-
<span
|
|
621
|
-
class="grid size-9 shrink-0 place-items-center rounded-lg bg-primary/15 font-mono text-sm font-semibold text-(--ui-color-primary-800) dark:text-primary"
|
|
622
|
-
>
|
|
623
|
-
{{ initials(editing.name) }}
|
|
624
|
-
</span>
|
|
625
|
-
<span class="truncate text-xl font-semibold text-highlighted">
|
|
626
|
-
{{ editing.name }}
|
|
627
|
-
</span>
|
|
628
|
-
</span>
|
|
629
|
-
</span>
|
|
630
|
-
</template>
|
|
631
|
-
<template #description>
|
|
632
|
-
<span v-if="editing" class="font-mono text-xs break-all text-dimmed">
|
|
633
|
-
{{ editing.id }} ·
|
|
634
|
-
{{
|
|
635
|
-
t('page.marketing.websites.drawer.added', {
|
|
636
|
-
date: formatShortDate(editing.createdAt, locale),
|
|
637
|
-
})
|
|
638
|
-
}}
|
|
639
|
-
</span>
|
|
640
|
-
</template>
|
|
641
|
-
<template #body>
|
|
642
|
-
<div v-if="editing" class="flex flex-col gap-5">
|
|
643
|
-
<DmsSegmented
|
|
644
|
-
v-model="tab"
|
|
645
|
-
:items="tabItems"
|
|
646
|
-
size="sm"
|
|
647
|
-
class="self-start"
|
|
648
|
-
/>
|
|
649
|
-
<template v-if="tab === 'general'">
|
|
650
|
-
<DmsFieldRow
|
|
651
|
-
layout="stack"
|
|
652
|
-
spacing="list"
|
|
653
|
-
:inset="false"
|
|
654
|
-
label="$page.marketing.websites.fields.name"
|
|
655
|
-
label-for="website-name"
|
|
656
|
-
required
|
|
657
|
-
>
|
|
658
|
-
<UInput
|
|
659
|
-
id="website-name"
|
|
660
|
-
v-model="draftName"
|
|
661
|
-
class="w-full"
|
|
662
|
-
:maxlength="MAX_WEBSITE_NAME_LENGTH"
|
|
663
|
-
v-bind="fieldErrors.aria('name')"
|
|
664
|
-
@update:model-value="fieldErrors.clear('name')"
|
|
665
|
-
/>
|
|
666
|
-
<DmsFieldError
|
|
667
|
-
:id="fieldErrors.errorId('name')"
|
|
668
|
-
:message="fieldErrors.errors.name"
|
|
669
|
-
/>
|
|
670
|
-
</DmsFieldRow>
|
|
671
|
-
<DmsFieldRow
|
|
672
|
-
layout="stack"
|
|
673
|
-
spacing="list"
|
|
674
|
-
:inset="false"
|
|
675
|
-
label="$page.marketing.websites.fields.domain"
|
|
676
|
-
description="$page.marketing.websites.fields.domain_hint"
|
|
677
|
-
label-for="website-domain"
|
|
678
|
-
required
|
|
679
|
-
>
|
|
680
|
-
<UInput
|
|
681
|
-
id="website-domain"
|
|
682
|
-
v-model="draftDomain"
|
|
683
|
-
class="w-full font-mono"
|
|
684
|
-
:maxlength="MAX_WEBSITE_DOMAIN_LENGTH"
|
|
685
|
-
v-bind="fieldErrors.aria('domain')"
|
|
686
|
-
@update:model-value="fieldErrors.clear('domain')"
|
|
687
|
-
/>
|
|
688
|
-
<DmsFieldError
|
|
689
|
-
:id="fieldErrors.errorId('domain')"
|
|
690
|
-
:message="fieldErrors.errors.domain"
|
|
691
|
-
/>
|
|
692
|
-
</DmsFieldRow>
|
|
693
|
-
<DmsFieldRow
|
|
694
|
-
layout="stack"
|
|
695
|
-
spacing="list"
|
|
696
|
-
:inset="false"
|
|
697
|
-
label="$page.marketing.websites.fields.hosts"
|
|
698
|
-
description="$page.marketing.websites.fields.hosts_hint"
|
|
699
|
-
label-for="website-hosts"
|
|
700
|
-
>
|
|
701
|
-
<UInputTags
|
|
702
|
-
id="website-hosts"
|
|
703
|
-
v-model="draftHosts"
|
|
704
|
-
class="w-full font-mono"
|
|
705
|
-
:placeholder="'staging.example.com'"
|
|
706
|
-
v-bind="fieldErrors.aria('extraDomains')"
|
|
707
|
-
@update:model-value="fieldErrors.clear('extraDomains')"
|
|
708
|
-
/>
|
|
709
|
-
<DmsFieldError
|
|
710
|
-
:id="fieldErrors.errorId('extraDomains')"
|
|
711
|
-
:message="fieldErrors.errors.extraDomains"
|
|
712
|
-
/>
|
|
713
|
-
</DmsFieldRow>
|
|
714
|
-
<DmsFieldRow
|
|
715
|
-
spacing="list"
|
|
716
|
-
:inset="false"
|
|
717
|
-
label="$page.marketing.websites.fields.tracking"
|
|
718
|
-
description="$page.marketing.websites.fields.tracking_hint"
|
|
719
|
-
label-for="website-tracking"
|
|
720
|
-
>
|
|
721
|
-
<USwitch id="website-tracking" v-model="draftTracking" />
|
|
722
|
-
</DmsFieldRow>
|
|
723
|
-
<DmsFormRequiredLegend />
|
|
724
|
-
</template>
|
|
725
|
-
<template v-else-if="tab === 'capture'">
|
|
726
|
-
<DmsFieldRow
|
|
727
|
-
spacing="list"
|
|
728
|
-
:inset="false"
|
|
729
|
-
label="$page.marketing.websites.fields.snapshots"
|
|
730
|
-
description="$page.marketing.websites.fields.snapshots_hint"
|
|
731
|
-
label-for="website-snapshots"
|
|
732
|
-
>
|
|
733
|
-
<USwitch id="website-snapshots" v-model="draftSnapshots" />
|
|
734
|
-
</DmsFieldRow>
|
|
735
|
-
<DmsFieldRow
|
|
736
|
-
spacing="list"
|
|
737
|
-
:inset="false"
|
|
738
|
-
label="$page.marketing.websites.fields.mask"
|
|
739
|
-
description="$page.marketing.websites.fields.mask_hint"
|
|
740
|
-
label-for="website-mask"
|
|
741
|
-
:disabled="!draftSnapshots"
|
|
742
|
-
>
|
|
743
|
-
<USwitch
|
|
744
|
-
id="website-mask"
|
|
745
|
-
v-model="draftMask"
|
|
746
|
-
:disabled="!draftSnapshots"
|
|
747
|
-
/>
|
|
748
|
-
</DmsFieldRow>
|
|
749
|
-
<DmsBanner
|
|
750
|
-
v-if="discardsSnapshots"
|
|
751
|
-
tone="warning"
|
|
752
|
-
size="sm"
|
|
753
|
-
:title="
|
|
754
|
-
t(
|
|
755
|
-
'page.marketing.websites.fields.discard',
|
|
756
|
-
{ count: editing.snapshots },
|
|
757
|
-
editing.snapshots,
|
|
758
|
-
)
|
|
759
|
-
"
|
|
760
|
-
/>
|
|
761
|
-
</template>
|
|
762
|
-
<template v-else>
|
|
763
|
-
<div
|
|
764
|
-
class="flex flex-col gap-3 rounded-lg border border-error/40 p-4"
|
|
765
|
-
>
|
|
766
|
-
<span class="text-sm font-semibold text-highlighted">
|
|
767
|
-
{{ t('page.marketing.websites.delete.section') }}
|
|
768
|
-
</span>
|
|
769
|
-
<span class="text-sm text-muted">
|
|
770
|
-
{{ t('page.marketing.websites.delete.section_description') }}
|
|
771
|
-
</span>
|
|
772
|
-
<UButton
|
|
773
|
-
class="self-start"
|
|
774
|
-
color="error"
|
|
775
|
-
variant="outline"
|
|
776
|
-
icon="i-ph-trash"
|
|
777
|
-
:label="t('page.marketing.websites.delete.action')"
|
|
778
|
-
@click="remove(editing)"
|
|
779
|
-
/>
|
|
780
|
-
</div>
|
|
781
|
-
</template>
|
|
782
|
-
</div>
|
|
783
|
-
</template>
|
|
784
|
-
<template #footer>
|
|
785
|
-
<div
|
|
786
|
-
v-if="tab !== 'danger'"
|
|
787
|
-
class="flex w-full items-center justify-between"
|
|
788
|
-
>
|
|
789
|
-
<UButton
|
|
790
|
-
color="neutral"
|
|
791
|
-
variant="ghost"
|
|
792
|
-
:label="t('page.marketing.common.cancel')"
|
|
793
|
-
@click="drawerOpen = false"
|
|
794
|
-
/>
|
|
795
|
-
<UButton
|
|
796
|
-
:loading="saving"
|
|
797
|
-
:label="t('page.marketing.common.save_changes')"
|
|
798
|
-
@click="save"
|
|
799
|
-
/>
|
|
800
|
-
</div>
|
|
801
|
-
<UButton
|
|
802
|
-
v-else
|
|
803
|
-
class="ml-auto"
|
|
804
|
-
color="neutral"
|
|
805
|
-
variant="outline"
|
|
806
|
-
:label="t('page.marketing.websites.drawer.close')"
|
|
807
|
-
@click="drawerOpen = false"
|
|
808
|
-
/>
|
|
809
|
-
</template>
|
|
810
|
-
</USlideover>
|
|
811
|
-
</div>
|
|
812
|
-
</template>
|