b912ff22bc
- SSO session auth gating on all protected pages - dashboard.php: account section (profile form + workspace panel), onboarding prompt modal, overview bar extracted to CSS classes, dashboard.css linked in page head - api/profile.php: save/dismiss endpoint for optional profile fields - assets/css/dashboard.css: account grid, dash-account-panel, dash-profile-form, profile-prompt-backdrop modal, overview bar classes, dash-section-kicker, dash-tier-badge base styles - includes/bootstrap.php: dbnToolsMainUserProfile, dbnToolsProfileNeedsPrompt, dbnToolsRequirePageAuth - scripts/sql/004_user_profile_fields.sql: nullable phone, address, and profile_prompt_dismissed_at columns Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
192 lines
10 KiB
PHP
192 lines
10 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
/**
|
|
* Dashboard chrome (minimal). Used by /dashboard/* pages.
|
|
*
|
|
* Page contract:
|
|
* $dashboardPage string — slug for active-state ('index'|'documents'|'document'|'upload'|'chat'|'settings')
|
|
* $dashboardTitle string — H1 for the content area
|
|
* $dashboardLead string? — optional sub-title sentence
|
|
* $extraScripts string[]?— optional extra script srcs (defer-loaded)
|
|
*
|
|
* Lazy-provisions the tenant on first hit; exposes ids to JS as window.DBN_DASHBOARD.
|
|
*/
|
|
|
|
require_once __DIR__ . '/bootstrap.php';
|
|
|
|
if (!dbnToolsIsAuthenticated()) {
|
|
dbnToolsRequirePageAuth($_SERVER['REQUEST_URI'] ?? '/dashboard/');
|
|
}
|
|
|
|
try {
|
|
$dashboardTenant = dbnToolsEnsureDashboardTenant();
|
|
} catch (DbnToolsHttpException $e) {
|
|
http_response_code($e->status);
|
|
echo '<!doctype html><meta charset="utf-8"><title>Dashboard unavailable</title>'
|
|
. '<p style="font-family:sans-serif;max-width:540px;margin:4rem auto;">'
|
|
. htmlspecialchars($e->getMessage())
|
|
. ' <a href="/dashboard/">Try again</a></p>';
|
|
exit;
|
|
}
|
|
|
|
$uiLang = dbnToolsCurrentLanguage();
|
|
$dashboardPage = $dashboardPage ?? 'index';
|
|
$dashboardTitle = $dashboardTitle ?? 'Dashboard';
|
|
$dashboardLead = $dashboardLead ?? '';
|
|
$langPath = strtok((string)($_SERVER['REQUEST_URI'] ?? '/dashboard/'), '?') ?: '/dashboard/';
|
|
|
|
$dashAuthUser = dbnToolsAuthenticatedUser();
|
|
$dashUserDisplay = '';
|
|
if ($dashAuthUser !== null) {
|
|
$email = (string)($dashAuthUser['email'] ?? '');
|
|
$dashUserDisplay = strstr($email, '@', true) ?: $email;
|
|
}
|
|
|
|
$dashboardNav = [
|
|
'index' => ['url' => '/dashboard/', 'label' => dbnToolsT('dash_nav_overview', $uiLang), 'sub' => 'Overview'],
|
|
'documents' => ['url' => '/dashboard/documents.php', 'label' => dbnToolsT('dash_nav_documents', $uiLang), 'sub' => 'Documents'],
|
|
'upload' => ['url' => '/dashboard/upload.php', 'label' => dbnToolsT('dash_nav_upload', $uiLang), 'sub' => 'Upload'],
|
|
'chat' => ['url' => '/dashboard/chat.php', 'label' => dbnToolsT('dash_nav_ask', $uiLang), 'sub' => 'Ask'],
|
|
'settings' => ['url' => '/dashboard/settings.php', 'label' => dbnToolsT('dash_nav_settings', $uiLang), 'sub' => 'Settings'],
|
|
];
|
|
?>
|
|
<!doctype html>
|
|
<html lang="<?= htmlspecialchars($uiLang) ?>">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title><?= htmlspecialchars($dashboardTitle) ?> · <?= htmlspecialchars(dbnToolsT('dash_page_subtitle', $uiLang)) ?> · Do Better Norge</title>
|
|
<link rel="stylesheet" href="../assets/css/tools.css">
|
|
<link rel="stylesheet" href="../assets/css/dashboard.css">
|
|
<link rel="stylesheet" href="../assets/css/dbn-tools-redesign.css">
|
|
</head>
|
|
<body data-authenticated="true" data-dashboard-page="<?= htmlspecialchars($dashboardPage) ?>">
|
|
<script>
|
|
window.DBN_TOOLS_AUTHENTICATED = true;
|
|
window.DBN_TOOLS_LANG = <?= json_encode($uiLang, JSON_UNESCAPED_UNICODE) ?>;
|
|
window.DBN_DASHBOARD = {
|
|
clientId: <?= (int)$dashboardTenant['client_id'] ?>,
|
|
clientUserId: <?= (int)$dashboardTenant['client_user_id'] ?>,
|
|
corpusId: <?= (int)$dashboardTenant['corpus_id'] ?>,
|
|
apiBase: '/api/dashboard'
|
|
};
|
|
window.DBN_I18N = <?= json_encode([
|
|
'locale' => dbnToolsT('js_locale', $uiLang),
|
|
'status_ready' => dbnToolsT('js_status_ready', $uiLang),
|
|
'status_pending' => dbnToolsT('js_status_pending', $uiLang),
|
|
'status_processing' => dbnToolsT('js_status_processing', $uiLang),
|
|
'status_error' => dbnToolsT('js_status_error', $uiLang),
|
|
'th_title' => dbnToolsT('js_th_title', $uiLang),
|
|
'th_status' => dbnToolsT('js_th_status', $uiLang),
|
|
'th_chunks' => dbnToolsT('js_th_chunks', $uiLang),
|
|
'th_added' => dbnToolsT('js_th_added', $uiLang),
|
|
'th_category' => dbnToolsT('js_th_category', $uiLang),
|
|
'loading' => dbnToolsT('js_loading', $uiLang),
|
|
'loading_docs' => dbnToolsT('js_loading_docs', $uiLang),
|
|
'empty_docs' => dbnToolsT('js_empty_docs', $uiLang),
|
|
'empty_docs_link' => dbnToolsT('js_empty_docs_link', $uiLang),
|
|
'empty_filter' => dbnToolsT('js_empty_filter', $uiLang),
|
|
'error_loading' => dbnToolsT('js_error_loading', $uiLang),
|
|
'kpi_docs' => dbnToolsT('js_kpi_docs', $uiLang),
|
|
'kpi_chunks' => dbnToolsT('js_kpi_chunks', $uiLang),
|
|
'kpi_ready' => dbnToolsT('js_kpi_ready', $uiLang),
|
|
'kpi_last' => dbnToolsT('js_kpi_last', $uiLang),
|
|
'kpi_of_quota' => dbnToolsT('js_kpi_of_quota', $uiLang),
|
|
'kpi_searchable' => dbnToolsT('js_kpi_searchable', $uiLang),
|
|
'kpi_of_total' => dbnToolsT('js_kpi_of_total', $uiLang),
|
|
'kpi_date_label' => dbnToolsT('js_kpi_date_label', $uiLang),
|
|
'get_started' => dbnToolsT('js_get_started', $uiLang),
|
|
'see_all' => dbnToolsT('js_see_all', $uiLang),
|
|
'recent_activity' => dbnToolsT('js_recent_activity', $uiLang),
|
|
'upload_docs_btn' => dbnToolsT('js_upload_docs_btn', $uiLang),
|
|
'ask_btn' => dbnToolsT('js_ask_btn', $uiLang),
|
|
'browse_btn' => dbnToolsT('js_browse_btn', $uiLang),
|
|
'pager_showing' => dbnToolsT('js_pager_showing', $uiLang),
|
|
'delete_selected' => dbnToolsT('js_delete_selected', $uiLang),
|
|
'delete_n_selected' => dbnToolsT('js_delete_n_selected', $uiLang),
|
|
'delete_docs_confirm'=> dbnToolsT('js_delete_docs_confirm', $uiLang),
|
|
'prev' => dbnToolsT('js_prev', $uiLang),
|
|
'next' => dbnToolsT('js_next', $uiLang),
|
|
'filter_all_status' => dbnToolsT('js_filter_all_status', $uiLang),
|
|
'filter_placeholder'=> dbnToolsT('js_filter_placeholder', $uiLang),
|
|
'chat_thinking' => dbnToolsT('js_chat_thinking', $uiLang),
|
|
'chat_save' => dbnToolsT('js_chat_save', $uiLang),
|
|
'chat_copy' => dbnToolsT('js_chat_copy', $uiLang),
|
|
'chat_copied' => dbnToolsT('js_chat_copied', $uiLang),
|
|
'chat_passages_meta'=> dbnToolsT('js_chat_passages_meta', $uiLang),
|
|
'chat_save_unavail' => dbnToolsT('js_chat_save_unavail', $uiLang),
|
|
'tab_preview' => dbnToolsT('js_tab_preview', $uiLang),
|
|
'tab_chunks' => dbnToolsT('js_tab_chunks', $uiLang),
|
|
'tab_related' => dbnToolsT('js_tab_related', $uiLang),
|
|
'tab_edit' => dbnToolsT('js_tab_edit', $uiLang),
|
|
'words' => dbnToolsT('js_words', $uiLang),
|
|
'passages_label' => dbnToolsT('js_passages_label', $uiLang),
|
|
'added_at' => dbnToolsT('js_added_at', $uiLang),
|
|
'tags_label' => dbnToolsT('js_tags_label', $uiLang),
|
|
'source_url_label' => dbnToolsT('js_source_url_label', $uiLang),
|
|
'back' => dbnToolsT('js_back', $uiLang),
|
|
'delete_btn' => dbnToolsT('js_delete_btn', $uiLang),
|
|
'save_changes' => dbnToolsT('js_save_changes', $uiLang),
|
|
'saving' => dbnToolsT('js_saving', $uiLang),
|
|
'saved_at' => dbnToolsT('js_saved_at', $uiLang),
|
|
'delete_doc_confirm'=> dbnToolsT('js_delete_doc_confirm', $uiLang),
|
|
'loading_related' => dbnToolsT('js_loading_related', $uiLang),
|
|
'no_citations' => dbnToolsT('js_no_citations', $uiLang),
|
|
'graph_unavailable' => dbnToolsT('js_graph_unavailable', $uiLang),
|
|
'no_chunks' => dbnToolsT('js_no_chunks', $uiLang),
|
|
'content_empty' => dbnToolsT('js_content_empty', $uiLang),
|
|
'doc_not_found' => dbnToolsT('js_doc_not_found', $uiLang),
|
|
'missing_doc_id' => dbnToolsT('js_missing_doc_id', $uiLang),
|
|
'upload_select_file'=> dbnToolsT('js_upload_select_file', $uiLang),
|
|
'upload_indexing' => dbnToolsT('js_upload_indexing', $uiLang),
|
|
'upload_saving' => dbnToolsT('js_upload_saving', $uiLang),
|
|
'upload_queuing' => dbnToolsT('js_upload_queuing', $uiLang),
|
|
'upload_drop_hint' => dbnToolsT('js_upload_drop_hint', $uiLang),
|
|
'upload_indexed' => dbnToolsT('js_upload_indexed', $uiLang),
|
|
'upload_open_doc' => dbnToolsT('js_upload_open_doc', $uiLang),
|
|
'upload_queued' => dbnToolsT('js_upload_queued', $uiLang),
|
|
'upload_follow_docs'=> dbnToolsT('js_upload_follow_docs', $uiLang),
|
|
'upload_bg_done' => dbnToolsT('js_upload_bg_done', $uiLang),
|
|
'field_title' => dbnToolsT('js_field_title', $uiLang),
|
|
'field_category' => dbnToolsT('js_field_category', $uiLang),
|
|
'field_tags' => dbnToolsT('js_field_tags', $uiLang),
|
|
'field_lang' => dbnToolsT('js_field_lang', $uiLang),
|
|
'field_author' => dbnToolsT('js_field_author', $uiLang),
|
|
], JSON_UNESCAPED_UNICODE) ?>;
|
|
</script>
|
|
|
|
<?php include __DIR__ . '/nav.php'; ?>
|
|
|
|
<div class="dbn-context-bar" role="note">
|
|
<span class="dbn-context-bar__tag"><?= htmlspecialchars(dbnToolsT('context_bar_tag', $uiLang)) ?></span>
|
|
<nav class="dbn-context-bar__links" aria-label="About">
|
|
<a href="/why-ours.php<?= $uiLang !== 'en' ? '?lang=' . urlencode($uiLang) : '' ?>"><?= htmlspecialchars(dbnToolsT('context_bar_why', $uiLang)) ?></a>
|
|
<a href="/pricing.php<?= $uiLang !== 'en' ? '?lang=' . urlencode($uiLang) : '' ?>"><?= htmlspecialchars(dbnToolsT('context_bar_pricing', $uiLang)) ?></a>
|
|
<a href="/mcp-tool.php<?= $uiLang !== 'en' ? '?lang=' . urlencode($uiLang) : '' ?>"><?= htmlspecialchars(dbnToolsT('context_bar_mcp', $uiLang)) ?></a>
|
|
<a href="/privacy.php<?= $uiLang !== 'en' ? '?lang=' . urlencode($uiLang) : '' ?>"><?= htmlspecialchars(dbnToolsT('context_bar_privacy', $uiLang)) ?></a>
|
|
</nav>
|
|
</div>
|
|
|
|
<div class="dash-shell">
|
|
|
|
<div class="dash-layout">
|
|
<nav class="dash-sidebar" aria-label="Dashboard sections">
|
|
<?php foreach ($dashboardNav as $slug => $item): ?>
|
|
<a href="<?= htmlspecialchars($item['url']) ?>"
|
|
class="dash-sidebar__item<?= $slug === $dashboardPage ? ' is-active' : '' ?>"
|
|
<?= $slug === $dashboardPage ? 'aria-current="page"' : '' ?>>
|
|
<strong><?= htmlspecialchars($item['label']) ?></strong>
|
|
<small><?= htmlspecialchars($item['sub']) ?></small>
|
|
</a>
|
|
<?php endforeach; ?>
|
|
</nav>
|
|
|
|
<main class="dash-main" id="dashMain">
|
|
<header class="dash-main__head">
|
|
<h1><?= htmlspecialchars($dashboardTitle) ?></h1>
|
|
<?php if ($dashboardLead !== ''): ?>
|
|
<p class="dash-main__lead"><?= htmlspecialchars($dashboardLead) ?></p>
|
|
<?php endif; ?>
|
|
</header>
|
|
<div class="dash-main__body">
|