// Dashbread Pro & Add-ons — plans and Pro Module Add-ons from the catalog bundled with this version of // Dashbread (Dashbread Pro swaps in live prices from dashbread.com), with this site's current plan from the // license state Pro supplies. Core never holds a license key and contacts no other site. import { useEffect, useState } from 'react'; import { __, _x, sprintf } from '@wordpress/i18n'; import { cfg } from '../api.js'; import { Badge, Button, Card, Icon, Notice, Spinner } from '../ui.jsx'; async function call(path, opts = {}) { const res = await fetch(cfg.rest + path, { credentials: 'same-origin', headers: { 'X-WP-Nonce': cfg.nonce, 'Content-Type': 'application/json' }, ...opts }); const data = await res.json().catch(() => ({})); if (!res.ok) throw new Error(data?.message || sprintf( /* translators: %s: HTTP status code */ __('Request failed (%s)', 'dashbread'), res.status )); return data; } // dashbread.com sells in US dollars; shown the way the site's language writes dollars ("$299", "299 $"). const money = (c) => { if (c == null) return ''; try { return new Intl.NumberFormat(document.documentElement.lang || undefined, { style: 'currency', currency: 'USD', minimumFractionDigits: 0, maximumFractionDigits: 0 }).format(c / 100); } catch (e) { return '$' + Math.round(c / 100); } }; const term = (t) => (t === 'year' ? __('/year', 'dashbread') : t === 'month' ? __('/month', 'dashbread') : t === 'one_time' ? ' ' + __('one-time', 'dashbread') : ''); const sites = (p) => (/unlimited|lifetime|bundle/.test(p.slug) ? __('Unlimited sites', 'dashbread') : /5-site/.test(p.slug) ? __('5 sites', 'dashbread') : p.tier === 'core' ? __('Free forever', 'dashbread') : __('1 site', 'dashbread')); const tierOf = (price) => (price >= 12900 ? 'Tier A' : price >= 7900 ? 'Tier B' : 'Tier C'); // Visible labels for the tier keys above (the keys themselves stay untranslated). const TIER_LABELS = { 'Tier A': __('Tier A', 'dashbread'), 'Tier B': __('Tier B', 'dashbread'), 'Tier C': __('Tier C', 'dashbread') }; function Info({ p, onClose, d }) { useEffect(() => { const k = (e) => e.key === 'Escape' && onClose(); document.addEventListener('keydown', k); return () => document.removeEventListener('keydown', k); }, []); return (
e.stopPropagation()}>

{p.name}

{p.image && } {p.tagline &&

{p.tagline}

}
{p.price === 0 ? _x('Free', 'price', 'dashbread') : money(p.price)}{p.price ? term(p.term) : ''}{p.requires && · {p.requires}}
{p.description &&
} {p.includes.length > 0 &&
    {p.includes.map((i, n) =>
  • {i.label}
  • )}
}
{p.tier !== 'core' && p.status === 'active' && !p.entitled && } {p.status === 'coming_soon' && {__('Coming soon', 'dashbread')}}
); } export default function Extend({ data }) { const [d, setD] = useState(null); const [err, setErr] = useState(''); const [info, setInfo] = useState(null); const [busy, setBusy] = useState(false); useEffect(() => { call('admin/store').then(setD).catch((e) => setErr(e.message)); }, []); if (!d) return err ? {err} : ; const refresh = async () => { setBusy(true); try { setD(await call('admin/store/refresh', { method: 'POST' })); } catch (e) { setErr(e.message); } setBusy(false); }; const cur = d.current; const allOn = cur.key === '__all'; const hasPro = allOn || d.entitlements.includes('pro'); const groups = []; const byTier = {}; d.addons.forEach((a) => { const t = tierOf(a.price || 0); (byTier[t] = byTier[t] || []).push(a); }); ['Tier A', 'Tier B', 'Tier C'].forEach((t) => byTier[t] && groups.push([t, byTier[t]])); const bundle = d.plans.find((p) => p.slug === 'dashbread-pmas-bundle'); const alaCarte = d.addons.reduce((s, a) => s + (a.price || 0), 0); return (
{err && setErr('')}>{err}} {d.bundled &&

{d.bundled_date ? sprintf( /* translators: %s: date the bundled price list was made */ __('Prices as of %s.', 'dashbread'), d.bundled_date ) : __('Prices as of this release.', 'dashbread')} {__('Current prices are on', 'dashbread')} dashbread.com.

}

{__('Add-ons & upgrades', 'dashbread')}

{__('Your plan:', 'dashbread')} {cur.label}{d.dev && {__('dev entitlements', 'dashbread')}}

{d.pro ? __('Unlock Pro and the Pro Module Add-ons for this site. Everything is licensed on dashbread.com; buying updates this site automatically.', 'dashbread') : __('Unlock Pro and the Pro Module Add-ons for this site. Everything is licensed on dashbread.com; buying updates this site automatically once Dashbread Pro is installed.', 'dashbread')}

{d.license_url ? : } {d.pro && }

{__('Plans', 'dashbread')}

{d.plans.map((p) => { const isCur = cur.key === p.slug || (p.tier === 'core' && cur.key === 'free'); const rank = d.rank[p.slug] ?? 0; const included = !isCur && rank < cur.rank; return (
{p.name}{p.slug === 'dashbread-pro-5-site' ? ' — ' + __('5 sites', 'dashbread') : p.slug === 'dashbread-pro-unlimited' ? ' — ' + __('unlimited sites', 'dashbread') : p.slug === 'dashbread-pro' ? ' — ' + __('1 site', 'dashbread') : ''}{isCur && {_x('Current', 'current plan', 'dashbread')}}
{p.price === 0 ? _x('Free', 'price', 'dashbread') : money(p.price)}{p.price ? term(p.term) : ''}
{sites(p)}
{isCur ? {__('Your current plan', 'dashbread')} : included ? {__('Included in your plan', 'dashbread')} : p.tier === 'core' ? {__('Installed', 'dashbread')} : }
); })}

{__('Pro Module Add-ons', 'dashbread')}

{bundle ? sprintf( /* translators: %s: bundle price per year, e.g. $299 */ __('Buy à la carte, or get them all with the bundle (%s/year).', 'dashbread'), money(bundle.price) ) : __('Buy à la carte, or get them all with the bundle.', 'dashbread')} {sprintf( /* translators: %s: total yearly price of all add-ons */ __('À la carte total: %s/year.', 'dashbread'), money(alaCarte) )} {__('Add-ons require Pro; nothing to install — they switch on when your license grants them.', 'dashbread')}

{groups.map(([tier, list]) => (
{TIER_LABELS[tier]}
{list.map((a) => (
{a.name}{a.status !== 'active' ? {__('Coming soon', 'dashbread')} : a.entitled ? {__('Active', 'dashbread')} : null}

{a.tagline}

{money(a.price)}{__('/year', 'dashbread')} {__('Guide', 'dashbread')} {a.status !== 'active' ? null : a.entitled ? {__('Included', 'dashbread')} : }
))}
))}

{__('Full apps that run inside the shell — courses, help desks, work orders — ship as their own plugins and register pages, widgets, flyout tabs and graph sources through Core\'s hooks. Building one? See Help › For developers.', 'dashbread')}

{data.pages.length > 0 &&
    {data.pages.map((p) =>
  • {p.title}{p.path}{__('Active', 'dashbread')}
  • )}
}
{info && setInfo(null)} />}
); }