Skip to content

Commit 76155f2

Browse files
committed
feat: increase banner
1 parent c22a54b commit 76155f2

4 files changed

Lines changed: 430 additions & 21 deletions

File tree

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { ref, computed, onMounted, type Ref, type ComputedRef } from 'vue'
2+
import { useContainersStore } from '@/stores/containers'
3+
import { useEdition } from '@/composables/useEdition'
4+
5+
export type Tier = 0 | 1 | 2 | 3
6+
7+
const COOLDOWN_MS = 30 * 24 * 60 * 60 * 1000
8+
9+
export function tierFromCount(count: number): Tier {
10+
if (count < 10) return 0
11+
if (count <= 24) return 1
12+
if (count <= 49) return 2
13+
return 3
14+
}
15+
16+
function storageKey(tier: Tier): string {
17+
return `pb:banner:pro-tier-${tier}`
18+
}
19+
20+
export interface ProBannerHandle {
21+
tier: Readonly<Ref<Tier | null>>
22+
count: Readonly<Ref<number | null>>
23+
visible: ComputedRef<boolean>
24+
dismiss: () => void
25+
}
26+
27+
export function useProBanner(): ProBannerHandle {
28+
const containers = useContainersStore()
29+
const { isEnterprise } = useEdition()
30+
31+
const tier = ref<Tier | null>(null)
32+
const count = ref<number | null>(null)
33+
const dismissed = ref(false)
34+
35+
onMounted(() => {
36+
const c = containers.containerCount
37+
const groups = containers.groups
38+
39+
if (c === 0 && groups.length === 0) return
40+
41+
tier.value = tierFromCount(c)
42+
count.value = c
43+
44+
if (tier.value !== 0) {
45+
const raw = localStorage.getItem(storageKey(tier.value))
46+
const ts = Number(raw)
47+
dismissed.value = Number.isFinite(ts) && Date.now() - ts < COOLDOWN_MS
48+
}
49+
})
50+
51+
const visible = computed(
52+
() => !isEnterprise.value && tier.value !== null && tier.value !== 0 && !dismissed.value,
53+
)
54+
55+
function dismiss(): void {
56+
if (tier.value === null || tier.value === 0) return
57+
dismissed.value = true
58+
localStorage.setItem(storageKey(tier.value), String(Date.now()))
59+
}
60+
61+
return { tier, count, visible, dismiss }
62+
}

frontend/src/layouts/DefaultLayout.vue

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
} from '@/composables/useDetailSlideOver'
2020
import { provideConfirm } from '@/composables/useConfirm'
2121
import { useEdition } from '@/composables/useEdition'
22-
import { useTimedDismissal } from '@/composables/useTimedDismissal'
22+
import { useProBanner } from '@/composables/useProBanner'
2323
import {
2424
Activity,
2525
ArrowRight,
@@ -112,12 +112,7 @@ const licenseLabel = computed(() => {
112112
}
113113
})
114114
115-
const { visible: supportBannerVisible, dismiss: dismissSupportBanner } = useTimedDismissal({
116-
storageKey: 'pb:banner:support-prompt',
117-
cooldownMs: 30 * 24 * 60 * 60 * 1000,
118-
})
119-
120-
const showSupportBanner = computed(() => !isEnterprise.value && supportBannerVisible.value)
115+
const proBanner = useProBanner()
121116
122117
const mobileMenuOpen = ref(false)
123118
@@ -362,25 +357,45 @@ const mainNav = computed(() =>
362357
</RouterLink>
363358
</template>
364359
</AlertBanner>
365-
<!-- Support the project banner (CE only) -->
360+
<!-- Pro tier banner (CE only, segmented by container count) -->
366361
<AlertBanner
367-
v-if="showSupportBanner"
362+
v-if="proBanner.visible.value"
368363
severity="info"
369-
label="SUPPORT"
364+
label="PRO"
370365
dismissible
371366
class="shrink-0"
372-
@dismiss="dismissSupportBanner"
367+
@dismiss="proBanner.dismiss()"
373368
>
374-
Support Maintenant's development by purchasing a Pro license — it's what keeps this project
375-
sustainable.
369+
<template v-if="proBanner.tier.value === 1">
370+
Running Maintenant in production? Pro adds Slack/Teams/Email alerts, CVE scanning and
371+
incident management — 29€/mo.
372+
</template>
373+
<template v-else-if="proBanner.tier.value === 2">
374+
You're monitoring {{ proBanner.count.value }} containers across production. Pro adds the
375+
alerting layer your scale needs — Slack, escalation, incidents, custom public status page,
376+
29€/mo.
377+
</template>
378+
<template v-else-if="proBanner.tier.value === 3">
379+
Running 50+ containers in production. Pro adds incident management, alert escalation and
380+
Slack routing. Want to discuss your setup with the founder?
381+
</template>
376382
<template #action>
377-
<RouterLink
378-
to="/pro-edition"
379-
class="license-action license-action--info inline-flex items-center gap-1 rounded border px-2 py-0.5 text-[11px] font-semibold transition-colors"
380-
>
381-
Get Pro
382-
<ArrowRight :size="12" />
383-
</RouterLink>
383+
<template v-if="proBanner.tier.value === 1 || proBanner.tier.value === 2">
384+
<RouterLink
385+
to="/pro-edition"
386+
class="license-action license-action--info inline-flex items-center gap-1 rounded border px-2 py-0.5 text-[11px] font-semibold transition-colors"
387+
>
388+
Get Pro →
389+
</RouterLink>
390+
</template>
391+
<template v-else-if="proBanner.tier.value === 3">
392+
<a
393+
href="mailto:benjamin@kolapsis.com?subject=Maintenant%20-%2050%2B%20containers%20setup"
394+
class="license-action license-action--info inline-flex items-center gap-1 rounded border px-2 py-0.5 text-[11px] font-semibold transition-colors"
395+
>
396+
Reply →
397+
</a>
398+
</template>
384399
</template>
385400
</AlertBanner>
386401
<AppHeader />

0 commit comments

Comments
 (0)