Skip to content

Commit 37c4fb1

Browse files
[6.x] Redesign trial mode banner (#12239)
* wip * refactor licensing messaging, add global header badge * wip ... - Move to a vue component instead of having logic in the root App.vue - Closing the modal or navigating to the licenses page will trigger a snooze - License manager alert only cares about when licenses are invalid - License request failues are handled on the pro badge by turning it yellow and putting the error in a tooltip. - Tooltip component becomes "disabled" and just renders the slot if there's no text. * Snooze is 5 minutes on production, a full day in dev. * No longer need hasLicenseBanner. The component won't be rendered at all if it doesnt need to be. * Make modal non-dismissible. It was so easy to accidentally close the modal by clicking outside of it and now you wont see it again for a whole day. * If on production, the pro badge says "Unlicensed" in red instead of "Trial Mode" in green. * Improve badge-as-link dark hover colors --------- Co-authored-by: Jason Varga <jason@pixelfear.com>
1 parent 34e695b commit 37c4fb1

11 files changed

Lines changed: 167 additions & 143 deletions

File tree

resources/js/bootstrap/App.vue

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import SitesEditForm from '../components/sites/EditForm.vue';
3434
import CommandPalette from '../components/command-palette/CommandPalette.vue';
3535
import ItemActions from '../components/actions/ItemActions.vue';
3636
import BulkActions from '../components/actions/BulkActions.vue';
37+
import LicensingAlert from '../components/LicensingAlert.vue';
3738
3839
import { defineAsyncComponent } from 'vue';
3940
import { ConfigProvider } from 'reka-ui';
@@ -77,14 +78,13 @@ export default {
7778
ConfigProvider,
7879
ItemActions,
7980
BulkActions,
81+
LicensingAlert,
8082
},
8183
8284
data() {
8385
return {
8486
navOpen: false,
85-
showBanner: true,
8687
appendedComponents: Statamic.$components.components,
87-
isLicensingBannerSnoozed: localStorage.getItem(`statamic.snooze_license_banner`) > new Date().valueOf(),
8888
copyToClipboardModalUrl: null,
8989
};
9090
},
@@ -113,8 +113,6 @@ export default {
113113
114114
this.fixAutofocus();
115115
116-
this.showBanner = !this.isLicensingBannerSnoozed && Statamic.$config.get('hasLicenseBanner');
117-
118116
this.$toast.registerInterceptor(this.$axios);
119117
this.$toast.displayInitialToasts();
120118
},
@@ -147,13 +145,6 @@ export default {
147145
localStorage.setItem('statamic.nav', this.navOpen ? 'open' : 'closed');
148146
},
149147
150-
151-
152-
hideBanner() {
153-
this.showBanner = false;
154-
localStorage.setItem(`statamic.snooze_license_banner`, new Date(Date.now() + 5 * 60 * 1000).valueOf());
155-
},
156-
157148
fixAutofocus() {
158149
// Fix autofocus issues in Safari and Firefox
159150
setTimeout(() => {
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<script setup>
2+
import { Modal, Description, Button } from '@/components/ui';
3+
import { computed, ref } from 'vue';
4+
5+
const props = defineProps({
6+
message: String,
7+
testing: Boolean,
8+
manageUrl: String,
9+
});
10+
11+
const key = 'statamic.snooze_license_banner';
12+
const open = ref(localStorage.getItem(key) < new Date().valueOf());
13+
const snoozeMinutes = computed(() => props.testing ? (24 * 60) : 5);
14+
const snoozeMilliseconds = computed(() => snoozeMinutes.value * 60 * 1000);
15+
16+
function snooze() {
17+
open.value = false;
18+
localStorage.setItem(key, new Date(Date.now() + snoozeMilliseconds.value).valueOf());
19+
}
20+
21+
function manageLicenses() {
22+
snooze();
23+
window.location = props.manageUrl;
24+
}
25+
</script>
26+
27+
<template>
28+
<Modal
29+
:title="__('Licensing Alert')"
30+
:open="open"
31+
@update:open="snooze"
32+
icon="alert-alarm-bell"
33+
class="[&_[data-ui-heading]]:text-red-700! [&_svg]:text-red-700! dark:[&_[data-ui-heading]]:text-red-400! dark:[&_svg]:text-red-400!'"
34+
:dismissible="false"
35+
>
36+
<div class="flex items-center justify-between">
37+
<Description :text="message" />
38+
</div>
39+
<template #footer>
40+
<div class="flex items-center justify-end space-x-3 pt-3 pb-1">
41+
<Button @click="snooze" :text="__('Snooze')" variant="ghost" tabindex="-1" />
42+
<Button v-if="manageUrl" @click="manageLicenses" :text="__('Manage Licenses')" />
43+
</div>
44+
</template>
45+
</Modal>
46+
</template>

resources/js/components/ui/Badge.vue

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -36,25 +36,22 @@ const badgeClasses = computed(() => {
3636
black: 'bg-gray-900 border-black text-white dark:bg-gray-300/6 dark:text-gray-300 [a]:hover:bg-black/90 [button]:hover:bg-black/90',
3737
blue: 'bg-blue-100/80 border-blue-300 text-blue-700 dark:bg-blue-300/6 dark:text-blue-300 [a]:hover:bg-blue-200/60 [button]:hover:bg-blue-200/60',
3838
cyan: 'bg-cyan-100/80 border-cyan-400 text-cyan-700 dark:bg-cyan-300/6 dark:text-cyan-300 [a]:hover:bg-cyan-200/60 [button]:hover:bg-cyan-200/60',
39-
default:
40-
'bg-gray-100/80 border-gray-300 dark:bg-gray-800 dark:text-gray-100 text-gray-700 [a]:hover:bg-gray-200/50 dark:[a]:hover:bg-gray-700/50 [button]:hover:bg-gray-200/50',
41-
emerald:
42-
'bg-emerald-100/80 border-emerald-400 text-emerald-700 dark:bg-emerald-300/6 dark:text-emerald-300 [a]:hover:bg-emerald-200/60 [button]:hover:bg-emerald-200/60',
43-
fuchsia:
44-
'bg-fuchsia-100/80 border-fuchsia-300 text-fuchsia-700 dark:bg-fuschia-300/6 dark:text-fuschia-300 [a]:hover:bg-fuchsia-200/60 [button]:hover:bg-fuchsia-200/60',
45-
green: 'bg-green-100/80 border-green-400 text-green-700 dark:bg-green-300/6 dark:text-green-300 [a]:hover:bg-green-200/60 [button]:hover:bg-green-200/60',
46-
indigo: 'bg-indigo-100/80 border-indigo-300 text-indigo-700 dark:bg-indigo-300/6 dark:text-indigo-300 [a]:hover:bg-indigo-200/60 [button]:hover:bg-indigo-200/60',
47-
lime: 'bg-lime-100 border-lime-400 text-lime-700 dark:bg-lime-300/6 dark:text-lime-300 [a]:hover:bg-lime-200/80 [button]:hover:bg-lime-200/80',
48-
orange: 'bg-orange-100 border-orange-400 text-orange-700 dark:bg-orange-300/6 dark:text-orange-300 [a]:hover:bg-orange-200/60 [button]:hover:bg-orange-200/60',
49-
pink: 'bg-pink-100/80 border-pink-300 text-pink-800 dark:bg-pink-300/6 dark:text-pink-300 [a]:hover:bg-pink-200/60 [button]:hover:bg-pink-200/60',
50-
purple: 'bg-purple-100/80 border-purple-300 text-purple-800 dark:bg-purple-300/6 dark:text-purple-300 [a]:hover:bg-purple-200/60 [button]:hover:bg-purple-200/60',
51-
red: 'bg-red-100/80 border-red-400/80 text-red-700 dark:bg-red-300/6 dark:text-red-300 [a]:hover:bg-red-200/60 [button]:hover:bg-red-200/60',
52-
rose: 'bg-rose-100/80 border-rose-300 text-rose-800 dark:bg-rose-300/6 dark:text-rose-300 [a]:hover:bg-rose-200/60 [button]:hover:bg-rose-200/60',
53-
sky: 'bg-sky-100/80 border-sky-300 text-sky-700 dark:bg-sky-300/6 dark:text-sky-300 [a]:hover:bg-sky-200/60 [button]:hover:bg-sky-200/60',
54-
teal: 'bg-teal-100 border-teal-400 text-teal-700 dark:bg-teal-300/6 dark:text-teal-300 [a]:hover:bg-teal-200/70 [button]:hover:bg-teal-200/70',
55-
violet: 'bg-violet-100/80 border-violet-300 text-violet-700 dark:bg-violet-300/6 dark:text-violet-300 [a]:hover:bg-violet-200/60 [button]:hover:bg-violet-200/60',
56-
white: 'bg-white border-gray-300 text-gray-700 dark:bg-gray-300/6 dark:text-gray-300 [a]:hover:bg-gray-200/30 [button]:hover:bg-gray-200/30',
57-
yellow: 'bg-yellow-100 border-yellow-400 text-yellow-700 dark:bg-yellow-300/6 dark:text-yellow-300 [a]:hover:bg-yellow-200/80 [button]:hover:bg-yellow-200/80',
39+
default: 'bg-gray-100/80 border-gray-300 dark:bg-gray-800 dark:text-gray-100 text-gray-700 [a]:hover:bg-gray-200/50 dark:[a]:hover:bg-gray-700/50 [button]:hover:bg-gray-200/50',
40+
emerald: 'bg-emerald-100/80 border-emerald-400 text-emerald-700 dark:bg-emerald-300/6 dark:text-emerald-300 [a]:hover:bg-emerald-200/60 [button]:hover:bg-emerald-200/60',
41+
fuchsia: 'bg-fuchsia-100/80 border-fuchsia-300 text-fuchsia-700 dark:bg-fuschia-300/6 dark:text-fuschia-300 [a]:hover:bg-fuchsia-200/60 [button]:hover:bg-fuchsia-200/60 dark:[a]:hover:bg-fuchsia-300/15',
42+
green: 'bg-green-100/80 border-green-400 text-green-700 dark:bg-green-300/6 dark:text-green-300 [a]:hover:bg-green-200/60 [button]:hover:bg-green-200/60 dark:[a]:hover:bg-green-300/15',
43+
indigo: 'bg-indigo-100/80 border-indigo-300 text-indigo-700 dark:bg-indigo-300/6 dark:text-indigo-300 [a]:hover:bg-indigo-200/60 [button]:hover:bg-indigo-200/60 dark:[a]:hover:bg-indigo-300/15',
44+
lime: 'bg-lime-100 border-lime-400 text-lime-700 dark:bg-lime-300/6 dark:text-lime-300 [a]:hover:bg-lime-200/80 [button]:hover:bg-lime-200/80 dark:[a]:hover:bg-lime-300/15',
45+
orange: 'bg-orange-100 border-orange-400 text-orange-700 dark:bg-orange-300/6 dark:text-orange-300 [a]:hover:bg-orange-200/60 [button]:hover:bg-orange-200/60 dark:[a]:hover:bg-orange-300/15',
46+
pink: 'bg-pink-100/80 border-pink-300 text-pink-800 dark:bg-pink-300/6 dark:text-pink-300 [a]:hover:bg-pink-200/60 [button]:hover:bg-pink-200/60 dark:[a]:hover:bg-pink-300/15',
47+
purple: 'bg-purple-100/80 border-purple-300 text-purple-800 dark:bg-purple-300/6 dark:text-purple-300 [a]:hover:bg-purple-200/60 [button]:hover:bg-purple-200/60 dark:[a]:hover:bg-purple-300/15',
48+
red: 'bg-red-100/80 border-red-400/80 text-red-700 dark:bg-red-300/6 dark:text-red-300 [a]:hover:bg-red-200/60 [button]:hover:bg-red-200/60 dark:[a]:hover:bg-red-300/15',
49+
rose: 'bg-rose-100/80 border-rose-300 text-rose-800 dark:bg-rose-300/6 dark:text-rose-300 [a]:hover:bg-rose-200/60 [button]:hover:bg-rose-200/60 dark:[a]:hover:bg-rose-300/15',
50+
sky: 'bg-sky-100/80 border-sky-300 text-sky-700 dark:bg-sky-300/6 dark:text-sky-300 [a]:hover:bg-sky-200/60 [button]:hover:bg-sky-200/60 dark:[a]:hover:bg-sky-300/15',
51+
teal: 'bg-teal-100 border-teal-400 text-teal-700 dark:bg-teal-300/6 dark:text-teal-300 [a]:hover:bg-teal-200/70 [button]:hover:bg-teal-200/70 dark:[a]:hover:bg-teal-300/15',
52+
violet: 'bg-violet-100/80 border-violet-300 text-violet-700 dark:bg-violet-300/6 dark:text-violet-300 [a]:hover:bg-violet-200/60 [button]:hover:bg-violet-200/60 dark:[a]:hover:bg-violet-300/15',
53+
white: 'bg-white border-gray-300 text-gray-700 dark:bg-gray-300/6 dark:text-gray-300 [a]:hover:bg-gray-200/30 [button]:hover:bg-gray-200/30 dark:[a]:hover:bg-gray-300/15',
54+
yellow: 'bg-yellow-100 border-yellow-400 text-yellow-700 dark:bg-yellow-300/6 dark:text-yellow-300 [a]:hover:bg-yellow-200/80 [button]:hover:bg-yellow-200/80 dark:[a]:hover:bg-yellow-300/15',
5855
},
5956
variant: {
6057
default: 'border dark:border-none shadow-ui-sm',

resources/js/components/ui/Modal/Modal.vue

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import { cva } from 'cva';
33
import { hasComponent } from '@/composables/has-component.js';
44
import { DialogContent, DialogOverlay, DialogPortal, DialogRoot, DialogTitle, DialogTrigger } from 'reka-ui';
55
import { computed, getCurrentInstance, ref, watch } from 'vue';
6+
import { Icon } from '@/components/ui';
67
78
const emit = defineEmits(['update:open']);
89
910
const props = defineProps({
1011
title: { type: String, default: '' },
12+
icon: { type: [String, null], default: null },
1113
open: { type: Boolean, default: false },
1214
dismissible: { type: Boolean, default: true },
1315
});
@@ -70,8 +72,9 @@ function preventIfNotDismissible(event) {
7072
@escape-key-down="preventIfNotDismissible"
7173
>
7274
<div class="relative space-y-3 rounded-xl border border-gray-400/60 bg-white p-4 shadow-[0_1px_16px_-2px_rgba(63,63,71,0.2)] dark:border-none dark:bg-gray-800 dark:shadow-[0_10px_15px_rgba(0,0,0,.5)] dark:inset-shadow-2xs dark:inset-shadow-white/15" >
73-
<DialogTitle v-if="!hasModalTitleComponent" data-ui-modal-title class="font-medium">
74-
{{ title }}
75+
<DialogTitle v-if="!hasModalTitleComponent" data-ui-modal-title class="flex items-center gap-2">
76+
<Icon :name="icon" v-if="icon" class="size-4" />
77+
<ui-heading :text="title" size="lg" class="font-medium" />
7578
</DialogTitle>
7679
<slot />
7780
</div>

resources/js/components/ui/Tooltip.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const triggerAttrs = computed(() => ({
1919
</script>
2020

2121
<template>
22-
<TooltipProvider :ariaLabel="tooltipText" :delay-duration="delay">
22+
<TooltipProvider v-if="tooltipText" :ariaLabel="tooltipText" :delay-duration="delay">
2323
<TooltipRoot>
2424
<TooltipTrigger v-bind="triggerAttrs">
2525
<slot />
@@ -46,4 +46,5 @@ const triggerAttrs = computed(() => ({
4646
</TooltipPortal>
4747
</TooltipRoot>
4848
</TooltipProvider>
49+
<slot v-else />
4950
</template>

resources/lang/en/messages.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -173,14 +173,14 @@
173173
'licensing_error_unlicensed' => 'Unlicensed',
174174
'licensing_incorrect_key_format_body' => 'Your site key format is incorrect. Site keys are 16-character alphanumeric strings. Get your current key from your account at <a href="https://statamic.com" class="underline">statamic.com</a>. Do not use legacy UUID license keys.',
175175
'licensing_incorrect_key_format_heading' => 'Incorrect site key format',
176-
'licensing_production_alert' => 'This site is using Statamic Pro and commercial addons. Please purchase appropriate licenses.',
177-
'licensing_production_alert_addons' => 'This site is using commercial addons. Please purchase appropriate licenses.',
178-
'licensing_production_alert_renew_statamic' => 'Using this version of Statamic Pro requires a license renewal.',
179-
'licensing_production_alert_statamic' => 'This site is using Statamic Pro. Please purchase a license.',
180-
'licensing_sync_instructions' => 'Data from statamic.com is synced once per hour. Force a sync to see any changes you\'ve made.',
181-
'licensing_trial_mode_alert' => 'This site is using Statamic Pro and commercial addons. Valid licenses are required before launch.',
182-
'licensing_trial_mode_alert_addons' => 'This site is using commercial addons. Valid licenses are required before launch.',
183-
'licensing_trial_mode_alert_statamic' => 'This site is using Statamic Pro. A valid license is required before launch.',
176+
'licensing_production_alert' => 'This site is running Statamic Pro and commercial addons on a production domain. To continue using these features, please ensure you have valid licenses.',
177+
'licensing_production_alert_addons' => 'This site is using commercial addons on a production domain. To continue using these features, please ensure you have valid licenses.',
178+
'licensing_production_alert_renew_statamic' => 'Your Statamic Pro license needs to be renewed to use this version. Please update your license or downgrade your Statamic version to continue.',
179+
'licensing_production_alert_statamic' => 'This site is running Statamic Pro on a production domain. To continue using Pro features, please ensure you have a valid license.',
180+
'licensing_sync_instructions' => 'License data syncs from statamic.com hourly. Use the sync button to check for any recent changes you\'ve made.',
181+
'licensing_trial_mode_alert' => 'This site is using Statamic Pro and commercial addons in trial mode. Valid licenses will be required when you\'re ready to launch.',
182+
'licensing_trial_mode_alert_addons' => 'This site is using commercial addons in trial mode. Valid licenses will be required when you\'re ready to launch.',
183+
'licensing_trial_mode_alert_statamic' => 'Thanks for trying Statamic Pro! This site is currently in trial mode — please enter a license before your site goes live.',
184184
'licensing_utility_description' => 'View and resolve licensing details.',
185185
'max_depth_instructions' => 'Set the max page nesting level.',
186186
'max_items_instructions' => 'Set a maximum number of selectable items.',

resources/views/layout.blade.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ class="@yield('content-class') pt-14"
2222
:class="{
2323
'nav-closed': ! navOpen,
2424
'nav-open': navOpen,
25-
'showing-license-banner': showBanner
2625
}"
2726
>
2827
<main id="main" class="flex bg-body-bg dark:bg-dark-body-bg dark:border-t rounded-t-2xl dark:border-dark-body-border fixed top-14 inset-x-0 bottom-0 min-h-[calc(100vh-3.5rem)]">

resources/views/partials/global-header.blade.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
use function Statamic\trans as __;
33
@endphp
44

5+
@inject('licenses', 'Statamic\Licensing\LicenseManager')
6+
57
<header class="h-14 bg-global-header-bg dark:bg-dark-global-header-bg flex justify-between space-x-2 items-center text-white px-4 fixed overflow-x-auto top-0 inset-x-0 z-[3]">
68
<a class="c-skip-link z-(--z-index-header) px-4 py-2 bg-blue-800 text-sm top-2.5 left-2.25 fixed opacity-0 -translate-y-24 focus:translate-y-0 focus:opacity-100 rounded-md" href="#main">
79
{{ __('Skip to sidebar') }}
@@ -30,7 +32,24 @@
3032
{{ $customLogoText ?? config('app.name') }}
3133
</a>
3234
@if (Statamic::pro())
33-
<ui-badge size="sm" variant="flat" text="Pro" class="hidden sm:block select-none dark:bg-black/20!" />
35+
@if ($licenses->valid())
36+
<ui-badge size="sm" variant="flat" text="{{ __('Pro') }}" class="hidden sm:block select-none dark:bg-black/20!" />
37+
@else
38+
<ui-tooltip :text="{{ $licenses->requestFailed() ? "'".$licenses->requestFailureMessage()."'" : 'null' }}">
39+
<ui-badge
40+
@if ($licenses->requestFailed())
41+
color="yellow"
42+
icon="alert-warning-exclamation-mark"
43+
@elseif ($licenses->isOnPublicDomain())
44+
color="red"
45+
@else
46+
color="green"
47+
@endif
48+
href="{{ cp_route('utilities.licensing') }}"
49+
text="{{ __('Pro') }}{{ $licenses->isOnPublicDomain() ? __('statamic::messages.licensing_error_unlicensed') : __('Trial Mode') }}"
50+
></ui-badge>
51+
</ui-tooltip>
52+
@endif
3453
@endif
3554
</div>
3655
@endif

0 commit comments

Comments
 (0)