Add monetization spine + Build Your Own Case (Min Sak)

- Stripe: StripeClient.php, checkout/portal/webhook endpoints, idempotent event handling
- FreeTier: tier-aware credits (free/light/pro/pro_plus), bonus_balance, hourly caps per tier
- pricing.php + billing.php: 4-tier cards, 3 topups, Customer Portal, balance breakdown
- Min Sak: CaseStore.php, AzureDocIntelligence.php, AzureSearchAdmin.php — per-user hybrid RAG
- api/case/: upload, list, delete, ingest-callback (HMAC-auth'd from n8n)
- award-survey-credits: inter-site HMAC endpoint for dobetternorge.no survey bonus
- dashboard.php: tier badge, balance breakdown card, Min Sak CTA, survey CTA
- KorrespondAgent + all 3 other agents: use_my_case toggle wired to dbnToolsCaseContext()
- bootstrap.php: dbnToolsCaseContext(), dbnToolsIntersiteSecret(), dbnToolsCurrentTier()

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-20 20:52:54 +02:00
parent ed5489d174
commit ba9cddf9a1
30 changed files with 2804 additions and 133 deletions
+58
View File
@@ -2,6 +2,7 @@
declare(strict_types=1);
require_once __DIR__ . '/includes/bootstrap.php';
require_once __DIR__ . '/includes/FreeTier.php';
if (!dbnToolsIsAuthenticated()) {
header('Location: /?return=' . urlencode($_SERVER['REQUEST_URI'] ?? '/dashboard.php'));
@@ -13,6 +14,19 @@ $tools = dbnToolsLaunchedTools($uiLang);
$workbench = dbnToolsWorkbenchMeta($uiLang);
$langPath = '/dashboard.php';
// Tier + balance for SSO users (CaveauAI sessions get no panel)
$dashIsSso = dbnToolsIsFreeTier();
$dashTier = $dashIsSso ? FreeTier::tier((int)$_SESSION['dbn_tools_sso_uid']) : 'caveau';
$dashDetail = $dashIsSso ? FreeTier::balanceDetail((int)$_SESSION['dbn_tools_sso_uid']) : null;
$tierLabels = [
'free' => ['Gratis', '#f3f4f6', '#374151'],
'light' => ['Light', '#ddd6fe', '#5b21b6'],
'pro' => ['Pro', '#bfdbfe', '#1e40af'],
'pro_plus' => ['Pro+ Familie', '#fde68a', '#92400e'],
];
$tierLabel = $tierLabels[$dashTier] ?? ['CaveauAI', '#d1fae5', '#065f46'];
$showSurveyCta = $dashIsSso && empty($dashDetail['survey_completed_at']);
require_once __DIR__ . '/includes/tool-svgs.php';
?>
<!doctype html>
@@ -53,10 +67,54 @@ window.DBN_TOOLS_LANG = <?= json_encode($uiLang, JSON_UNESCAPED_UNICODE) ?>;
<?php endforeach; ?>
</nav>
<span id="healthPill" class="status-pill"><?= htmlspecialchars(dbnToolsT('session_active', $uiLang)) ?></span>
<?php if ($dashIsSso): ?>
<a href="/billing.php" class="tier-pill" style="background: <?= htmlspecialchars($tierLabel[1]) ?>; color: <?= htmlspecialchars($tierLabel[2]) ?>; padding: 4px 12px; border-radius: 999px; font-size: 0.82rem; font-weight: 600; text-decoration: none;">
<?= htmlspecialchars($tierLabel[0]) ?>
</a>
<?php endif; ?>
<button id="healthButton" class="secondary-button" type="button"><?= htmlspecialchars(dbnToolsT('health', $uiLang)) ?></button>
</div>
</header>
<?php if ($dashIsSso && $dashDetail): ?>
<section class="dashboard-status-row" style="display:grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap:1rem; margin: 1.5rem 0;">
<div class="status-card" style="background:#fff; border:1px solid #e5e7eb; border-radius:10px; padding:1.1rem 1.25rem;">
<p style="margin:0; color:#6b7280; font-size:0.85rem; text-transform:uppercase; letter-spacing:0.06em;">Tilgjengelige kreditter</p>
<p style="margin:0.35rem 0 0; font-size:1.8rem; font-weight:700; color:#00205B;">
<?php $eff = (int)$dashDetail['balance'] + (int)$dashDetail['bonus_balance']; ?>
<?php if ($dashTier === 'pro_plus'): ?>∞<?php else: ?><?= number_format($eff, 0, ',', ' ') ?><?php endif; ?>
</p>
<p style="margin:0; color:#6b7280; font-size:0.85rem;"><?= (int)$dashDetail['balance'] ?> månedlige · <?= (int)$dashDetail['bonus_balance'] ?> bonus · <a href="/billing.php">Detaljer</a></p>
</div>
<?php if (in_array($dashTier, ['light','pro','pro_plus'], true)): ?>
<a class="status-card" href="/min-sak.php" style="background:#fff; border:1px solid #e5e7eb; border-radius:10px; padding:1.1rem 1.25rem; text-decoration:none; color:inherit;">
<p style="margin:0; color:#6b7280; font-size:0.85rem; text-transform:uppercase; letter-spacing:0.06em;">Min sak</p>
<p style="margin:0.35rem 0 0; font-size:1.4rem; font-weight:700; color:#00205B;">Bygg din egen sak →</p>
<?php
$used = (int)$dashDetail['storage_used_bytes'];
$quota = (int)$dashDetail['storage_quota_bytes'];
$usedMb = $used > 0 ? round($used / 1048576, 1) : 0;
$quotaMb = $quota > 0 ? round($quota / 1048576, 0) : 0;
?>
<p style="margin:0; color:#6b7280; font-size:0.85rem;"><?= $usedMb ?> MB / <?= $quotaMb ?> MB brukt</p>
</a>
<?php else: ?>
<a class="status-card" href="/pricing.php" style="background:linear-gradient(135deg,#00205B,#003478); color:#fff; border-radius:10px; padding:1.1rem 1.25rem; text-decoration:none;">
<p style="margin:0; opacity:0.85; font-size:0.85rem; text-transform:uppercase; letter-spacing:0.06em;">Bygg din egen sak</p>
<p style="margin:0.35rem 0 0; font-size:1.4rem; font-weight:700;">Last opp dokumenter →</p>
<p style="margin:0; opacity:0.85; font-size:0.85rem;">Tilgjengelig fra Light €9/mo</p>
</a>
<?php endif; ?>
<?php if ($showSurveyCta): ?>
<a class="status-card" href="https://dobetternorge.no/survey.php" style="background:#fef3c7; color:#92400e; border-radius:10px; padding:1.1rem 1.25rem; text-decoration:none;">
<p style="margin:0; font-size:0.85rem; text-transform:uppercase; letter-spacing:0.06em; opacity:0.85;">Tjen 25 ekstra kreditter</p>
<p style="margin:0.35rem 0 0; font-size:1.2rem; font-weight:700;">Ta vår 5-spørsmåls undersøkelse →</p>
<p style="margin:0; font-size:0.85rem; opacity:0.85;">Ingen salgspitch — bare research</p>
</a>
<?php endif; ?>
</section>
<?php endif; ?>
<section class="manifesto dashboard-manifesto">
<div class="manifesto-copy">
<p class="manifesto-eyebrow"><?= htmlspecialchars(dbnToolsT('dashboard_eyebrow', $uiLang)) ?></p>