Files
dobetternorge-tools/dashboard.php
T
daveadmin e09ee62c62 Apply Do Better Norge tools brand redesign (CSS + all tool pages)
New dbn-tools-redesign.css with warm paper surface, navy tools nav, gold
accent, and per-tool themes via body[data-active-tool]. Updated all 21 tool
PHP pages, shared layout/nav/footer includes, and advocate route (new).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 11:26:39 +02:00

257 lines
14 KiB
PHP

<?php
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'));
exit;
}
$uiLang = dbnToolsCurrentLanguage();
$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'],
'plus' => ['Plus', '#ddd6fe', '#5b21b6'],
'pro' => ['Pro Familie', '#bfdbfe', '#1e40af'],
];
$tierLabel = $tierLabels[$dashTier] ?? ['CaveauAI', '#d1fae5', '#065f46'];
$showSurveyCta = $dashIsSso && empty($dashDetail['survey_completed_at']);
require_once __DIR__ . '/includes/tool-svgs.php';
?>
<!doctype html>
<html lang="<?= htmlspecialchars($uiLang) ?>">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?= htmlspecialchars(dbnToolsT('dashboard_title', $uiLang)) ?> — Do Better Norge</title>
<meta name="robots" content="noindex, nofollow">
<meta name="theme-color" content="#00205B">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Crimson+Pro:wght@400;600;700&family=IBM+Plex+Sans:wght@400;500;600;700&display=swap">
<link rel="stylesheet" href="assets/css/tools.css">
<link rel="stylesheet" href="assets/css/dbn-tools-redesign.css">
</head>
<body data-authenticated="true" class="lt-app">
<script>
window.DBN_TOOLS_AUTHENTICATED = true;
window.DBN_TOOLS_LANG = <?= json_encode($uiLang, JSON_UNESCAPED_UNICODE) ?>;
</script>
<?php include __DIR__ . '/includes/nav.php'; ?>
<main id="appShell" class="app-shell dashboard-shell">
<?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;"><?= htmlspecialchars(dbnToolsT('credits_available', $uiLang)) ?></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']; ?>
<?= number_format($eff, 0, ',', ' ') ?>
</p>
<p style="margin:0; color:#6b7280; font-size:0.85rem;"><?= (int)$dashDetail['balance'] ?> <?= htmlspecialchars(dbnToolsT('credits_monthly', $uiLang)) ?> · <?= (int)$dashDetail['bonus_balance'] ?> <?= htmlspecialchars(dbnToolsT('credits_bonus', $uiLang)) ?> · <a href="/billing.php"><?= htmlspecialchars(dbnToolsT('details_link', $uiLang)) ?></a></p>
</div>
<?php if (in_array($dashTier, ['plus','pro'], 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;"><?= htmlspecialchars(dbnToolsT('my_case', $uiLang)) ?></p>
<p style="margin:0.35rem 0 0; font-size:1.4rem; font-weight:700; color:#00205B;"><?= htmlspecialchars(dbnToolsT('build_your_case', $uiLang)) ?> →</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</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;"><?= htmlspecialchars(dbnToolsT('build_your_case', $uiLang)) ?></p>
<p style="margin:0.35rem 0 0; font-size:1.4rem; font-weight:700;"><?= htmlspecialchars(dbnToolsT('upload_documents', $uiLang)) ?> →</p>
<p style="margin:0; opacity:0.85; font-size:0.85rem;"><?= htmlspecialchars(dbnToolsT('upgrade_from_plus', $uiLang)) ?></p>
</a>
<?php endif; ?>
<!-- Corpus summary widget — populated via fetch('/api/corpus-summary.php') -->
<a id="corpusSummaryCard" class="status-card" href="/dashboard/" style="background:#fff; border:1px solid #e5e7eb; border-radius:10px; padding:1.1rem 1.25rem; text-decoration:none; color:inherit; display:block;">
<p style="margin:0; color:#6b7280; font-size:0.85rem; text-transform:uppercase; letter-spacing:0.06em;"><?= htmlspecialchars(dbnToolsT('my_corpus', $uiLang)) ?></p>
<p id="corpusDocCount" style="margin:0.35rem 0 0; font-size:1.4rem; font-weight:700; color:#00205B;">—</p>
<p id="corpusUpdated" style="margin:0; color:#6b7280; font-size:0.85rem;"><?= htmlspecialchars(dbnToolsT('open_corpus', $uiLang)) ?> →</p>
</a>
<?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;"><?= htmlspecialchars(dbnToolsT('earn_credits_eyebrow', $uiLang)) ?></p>
<p style="margin:0.35rem 0 0; font-size:1.2rem; font-weight:700;"><?= htmlspecialchars(dbnToolsT('survey_btn', $uiLang)) ?> →</p>
<p style="margin:0; font-size:0.85rem; opacity:0.85;"><?= htmlspecialchars(dbnToolsT('survey_cta_text', $uiLang)) ?></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>
<h2 class="manifesto-title"><?= htmlspecialchars(dbnToolsT('manifesto_title', $uiLang)) ?></h2>
<p class="manifesto-sub"><?= htmlspecialchars(dbnToolsT('dashboard_sub', $uiLang)) ?></p>
</div>
<div class="manifesto-stats" aria-label="Headline statistics">
<div class="manifesto-stat"><strong>23</strong><span><?= htmlspecialchars(dbnToolsT('stat_echr', $uiLang)) ?></span></div>
<div class="manifesto-stat"><strong>64%</strong><span><?= htmlspecialchars(dbnToolsT('stat_loss', $uiLang)) ?></span></div>
<div class="manifesto-stat"><strong>1,731</strong><span><?= htmlspecialchars(dbnToolsT('stat_tribunal', $uiLang)) ?></span></div>
<div class="manifesto-stat"><strong>20+</strong><span><?= htmlspecialchars(dbnToolsT('stat_pending', $uiLang)) ?></span></div>
</div>
</section>
<div class="disclaimer" role="note"><?= htmlspecialchars(dbnToolsT('disclaimer', $uiLang)) ?></div>
<section class="tool-dashboard-grid" aria-label="Available tools">
<a class="dashboard-tool-card dashboard-tool-card--workbench" href="<?= htmlspecialchars($workbench['url']) ?>">
<span class="dashboard-tool-card__icon"><?= htmlspecialchars($workbench['icon']) ?></span>
<span class="dashboard-tool-card__badge"><?= htmlspecialchars($workbench['badge']) ?></span>
<h2><?= htmlspecialchars($workbench['label']) ?></h2>
<p><?= htmlspecialchars($workbench['description']) ?></p>
<strong><?= htmlspecialchars(dbnToolsT('enter_workbench', $uiLang)) ?></strong>
</a>
<?php foreach ($tools as $slug => $item): ?>
<a class="dashboard-tool-card" href="<?= htmlspecialchars($item['url']) ?>">
<span class="dashboard-tool-card__icon"><?= htmlspecialchars($item['icon']) ?></span>
<span class="dashboard-tool-card__badge"><?= htmlspecialchars($item['badge']) ?></span>
<h2><?= htmlspecialchars($item['label']) ?></h2>
<p><?= htmlspecialchars($item['description']) ?></p>
<strong><?= htmlspecialchars(dbnToolsT('open_tool', $uiLang)) ?></strong>
</a>
<?php endforeach; ?>
</section>
<!-- Welcome modal — shown once per browser until dismissed with "don't show again" -->
<div id="welcomeModal" class="wlc-backdrop" role="dialog" aria-modal="true" aria-labelledby="wlcTitle" hidden>
<div class="wlc-card">
<div class="wlc-header">
<p class="wlc-eyebrow"><?= htmlspecialchars(dbnToolsT('brand_line', $uiLang)) ?></p>
<h2 class="wlc-title" id="wlcTitle">Welcome to Do Better Norge Legal Tools</h2>
<p class="wlc-sub">Here&rsquo;s a quick look at what each tool does &mdash; click any card on the dashboard to go straight in.</p>
</div>
<div class="wlc-workbench-tip">
<span class="wlc-tip-icon" aria-hidden="true">💡</span>
<span><strong>Start with the Case Workbench</strong> &mdash; frame your situation first, then use the tools below with context already loaded.</span>
</div>
<div class="wlc-tools-grid">
<?php
$welcomeTips = [
'transcribe' => 'Upload a hearing recording to get an accurate, speaker-separated transcript',
'timeline' => 'Paste case notes to instantly map all key dates and Barnevernet milestones',
'redact' => 'Strip names and ID numbers before sharing any document with third parties',
'korrespond' => 'Draft authority letters in Norwegian + your language with verified citations',
'barnevernet' => 'Upload child-welfare documents to flag procedural violations and red flags',
'advocate' => 'Generate a fully-cited brief for your position from ECHR and Lovdata',
'deep-research' => 'Ask a complex legal question and get a multi-angle cited research brief',
'discrepancy' => 'Upload two doc versions to surface deleted facts or new allegations',
'corpus' => 'Browse the 220 K+ indexed legal passages behind every AI answer',
'citations' => 'Trace how cases cite each other to find supporting precedents',
];
$toolIcons = [
'transcribe' => '🎙',
'timeline' => '📅',
'redact' => '🔒',
'korrespond' => '✉️',
'barnevernet' => '🔍',
'advocate' => '⚖️',
'deep-research' => '🔬',
'discrepancy' => '🔎',
'corpus' => '📚',
'citations' => '🔗',
];
foreach ($tools as $slug => $item):
$tip = $welcomeTips[$slug] ?? $item['description'];
$icon = $toolIcons[$slug] ?? '🛠';
?>
<a class="wlc-tool-item" href="<?= htmlspecialchars($item['url']) ?>">
<span class="wlc-tool-icon" aria-hidden="true"><?= $icon ?></span>
<span class="wlc-tool-name"><?= htmlspecialchars($item['label']) ?></span>
<span class="wlc-tool-tip"><?= htmlspecialchars($tip) ?></span>
</a>
<?php endforeach; ?>
</div>
<div class="wlc-footer">
<label class="wlc-no-show">
<input type="checkbox" id="wlcDontShow" checked>
Don&rsquo;t show this again
</label>
<button id="wlcGetStarted" class="wlc-btn-start" type="button">Get started &rarr;</button>
</div>
</div>
</div>
</main>
<?php require_once __DIR__ . '/includes/footer.php'; ?>
<script src="assets/js/tools.js" defer></script>
<script>
(function () {
var STORAGE_KEY = 'dbn-welcome-v1-seen';
var modal = document.getElementById('welcomeModal');
var btnStart = document.getElementById('wlcGetStarted');
var chkDontShow = document.getElementById('wlcDontShow');
function closeModal(saveFlag) {
modal.hidden = true;
document.body.style.overflow = '';
if (saveFlag) {
try { localStorage.setItem(STORAGE_KEY, '1'); } catch (e) {}
}
}
if (modal && !localStorage.getItem(STORAGE_KEY)) {
modal.hidden = false;
document.body.style.overflow = 'hidden';
}
if (btnStart) {
btnStart.addEventListener('click', function () {
closeModal(chkDontShow && chkDontShow.checked);
});
}
if (modal) {
modal.addEventListener('click', function (e) {
if (e.target === modal) closeModal(false);
});
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && !modal.hidden) closeModal(false);
});
}
}());
</script>
<?php if ($dashIsSso): ?>
<script>
(function () {
var card = document.getElementById('corpusSummaryCard');
var countEl = document.getElementById('corpusDocCount');
var updEl = document.getElementById('corpusUpdated');
if (!card || !countEl) return;
fetch('/api/corpus-summary.php')
.then(function (r) { return r.ok ? r.json() : null; })
.then(function (data) {
if (!data) return;
countEl.textContent = data.doc_count !== undefined
? data.doc_count.toLocaleString() + ' docs'
: '—';
if (data.last_updated && updEl) {
var d = new Date(data.last_updated);
updEl.textContent = d.toLocaleDateString(undefined, { day: 'numeric', month: 'short', year: 'numeric' });
}
})
.catch(function () {});
}());
</script>
<?php endif; ?>
</body>
</html>