Add free/paid feature gating (Role Editor, Activity Log, DB Explorer,

Analytics, White Label, remote site switcher are paid)

- New License class is the single gate every paid feature checks —
  today it's a placeholder (any non-empty license_key counts as active)
  since there's no license server yet, but every gated feature already
  funnels through it so swapping in real remote validation later won't
  touch anything else.
- Role Editor, Activity Log, and Database Explorer stay registered/
  capability-checked as before, but render an UpgradeNotice screen
  instead of the real feature when unlicensed (and skip loading the
  real JS bundle/REST data). Menu Creator, Admin Notices, Comments,
  and Plugins stay free.
- Analytics' tracking hooks and REST endpoint, and White Label's
  favicon/rename/text-replace hooks, simply don't register without a
  license. Settings fields for both stay visible/editable either way.
- Remote site switcher gated alongside its existing capability check.
- Sidebar shows a small lock badge on locked Aurora nav items; the
  Dashboard's Analytics tab shows the same upgrade prompt when locked.
- Fixed an unrelated bug surfaced while testing Role Editor: RolesData's
  REST callback called get_editable_roles() without loading
  wp-admin/includes/user.php first, which REST requests don't get for
  free (only real wp-admin page loads do) — 500'd every time.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
nixpanel
2026-07-03 19:59:00 -05:00
co-authored by Claude Sonnet 5
parent 73591e0ca4
commit 6d502604d7
15 changed files with 244 additions and 22 deletions
+10
View File
@@ -0,0 +1,10 @@
import { createApp } from 'vue';
import '@/shell/shell-frame.css';
import UpgradeNotice from '@/pages/_shared/UpgradeNotice.vue';
const mountEl = document.querySelector('[data-aurora-locked-feature]');
if (mountEl) {
const data = JSON.parse(mountEl.dataset.auroraLockedFeature || '{}');
createApp(UpgradeNotice, { feature: data.feature, description: data.description }).mount(mountEl);
}
+81
View File
@@ -0,0 +1,81 @@
<script setup>
defineProps({
feature: { type: String, required: true },
description: { type: String, default: '' },
});
</script>
<template>
<div class="upgrade-notice">
<div class="upgrade-notice__card">
<span class="upgrade-notice__icon">
<span class="dashicons dashicons-lock" />
</span>
<h1 class="upgrade-notice__title">{{ feature }} is a Pro feature</h1>
<p class="upgrade-notice__text">
{{ description || `Upgrade to Aurora Admin Pro to unlock ${feature}.` }}
</p>
<a class="upgrade-notice__cta" href="admin.php?page=aurora-admin#license">
Enter a license key
</a>
</div>
</div>
</template>
<style scoped>
.upgrade-notice {
display: flex;
align-items: center;
justify-content: center;
min-height: 400px;
padding: 40px 20px;
}
.upgrade-notice__card {
max-width: 380px;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
}
.upgrade-notice__icon {
width: 56px;
height: 56px;
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
background: var(--aurora-accent-soft);
color: var(--aurora-accent);
margin-bottom: 4px;
}
.upgrade-notice__icon .dashicons {
font-size: 26px;
width: 26px;
height: 26px;
}
.upgrade-notice__title {
font-size: 1.15rem;
font-weight: 700;
margin: 0;
color: var(--aurora-text);
}
.upgrade-notice__text {
font-size: 0.875rem;
color: var(--aurora-text-muted);
margin: 0;
}
.upgrade-notice__cta {
margin-top: 8px;
padding: 8px 18px;
border-radius: 8px;
background: var(--aurora-accent);
color: var(--aurora-accent-text);
font-size: 0.875rem;
font-weight: 600;
text-decoration: none;
}
.upgrade-notice__cta:hover {
opacity: 0.9;
}
</style>
+8 -1
View File
@@ -13,13 +13,14 @@ import ActiveUsers from './cards/ActiveUsers.vue';
import DeviceUsage from './cards/DeviceUsage.vue';
import Engagement from './cards/Engagement.vue';
import TopList from './cards/TopList.vue';
import UpgradeNotice from '../_shared/UpgradeNotice.vue';
const props = defineProps({
serverData: { type: Object, required: true },
});
const { data, loading, load } = useDashboardData(props.serverData);
const { data: analytics, load: loadAnalytics } = useAnalyticsData(props.serverData);
const { data: analytics, error: analyticsError, load: loadAnalytics } = useAnalyticsData(props.serverData);
// Date range — default last 7 days, with simple presets.
const today = new Date();
@@ -107,6 +108,12 @@ onMounted(() => reload(range.value[0], range.value[1]));
</div>
</div>
<UpgradeNotice
v-else-if="analyticsError"
feature="Analytics"
description="Upgrade to Aurora Admin Pro to unlock first-party visitor analytics for this dashboard."
/>
<div v-else class="dash__grid">
<div class="dash__cell" style="grid-column: span 8">
<PageViews :data="analytics ? analytics.pageViews : {}" />
@@ -91,7 +91,7 @@ const openFontList = () => {
</script>
<template>
<div class="field">
<div class="field" :id="field.key === 'license_key' ? 'license' : null">
<div class="field__info">
<div class="field__label">{{ field.label }}</div>
<p v-if="field.description" class="field__desc">{{ field.description }}</p>
+3 -3
View File
@@ -42,7 +42,7 @@ export const settingsCategories = [
type: 'text',
label: 'Remote Site Switcher Capability',
description:
'WordPress capability required to see the remote site switcher in the toolbar. Defaults to "manage_options" (admin users). Users with this capability or below will see the switcher.',
'Pro feature — requires a license. WordPress capability required to see the remote site switcher in the toolbar. Defaults to "manage_options" (admin users). Users with this capability or below will see the switcher.',
placeholder: 'manage_options',
},
{
@@ -111,7 +111,7 @@ export const settingsCategories = [
{
id: 'whitelabel',
title: 'White Label',
subtitle: 'Rebrand and customize interface text and icons',
subtitle: 'Rebrand and customize interface text and icons (Pro feature — requires a license)',
icon: 'dashicons-tag',
fields: [
{
@@ -198,7 +198,7 @@ export const settingsCategories = [
{
id: 'analytics',
title: 'Analytics',
subtitle: 'Built-in analytics and tracking settings',
subtitle: 'Built-in analytics and tracking settings (Pro feature — requires a license)',
icon: 'dashicons-chart-bar',
fields: [
{
+15
View File
@@ -185,6 +185,9 @@ const isAuroraActive = (item) => normalize(item.url) === current.value;
:class="{ 'sidebar__sublink--active': isAuroraActive(item) }"
>
{{ item.title }}
<span v-if="item.locked" class="sidebar__pro-badge" title="Pro feature">
<span class="dashicons dashicons-lock" />
</span>
</a>
</li>
</ul>
@@ -382,6 +385,18 @@ const isAuroraActive = (item) => normalize(item.url) === current.value;
font-weight: 600;
}
.sidebar__pro-badge {
display: flex;
align-items: center;
color: var(--aurora-text-muted);
flex-shrink: 0;
}
.sidebar__pro-badge .dashicons {
font-size: 13px;
width: 13px;
height: 13px;
}
/* --- Aurora branded nav (right under the native menu's own divider) ---- */
.sidebar__aurora {
padding-bottom: 4px;