Dashboard account section, profile API, and CSS account panels
- 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>
This commit is contained in:
@@ -122,6 +122,33 @@ function dbnToolsIsAuthenticated(): bool
|
||||
&& (string)($_SESSION['dbn_tools_client_slug'] ?? '') === dbnToolsClientSlug();
|
||||
}
|
||||
|
||||
function dbnToolsSafeReturnPath(mixed $value, string $default = '/dashboard.php'): string
|
||||
{
|
||||
$return = trim((string)$value);
|
||||
if ($return === '' || !str_starts_with($return, '/') || str_starts_with($return, '//')) {
|
||||
return $default;
|
||||
}
|
||||
if (preg_match('/[\r\n]/', $return)) {
|
||||
return $default;
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
function dbnToolsMainLoginUrl(?string $returnPath = null): string
|
||||
{
|
||||
$return = dbnToolsSafeReturnPath($returnPath ?? ($_SERVER['REQUEST_URI'] ?? '/dashboard.php'), '/dashboard.php');
|
||||
return 'https://dobetternorge.no/tools-login.php?return=' . urlencode($return);
|
||||
}
|
||||
|
||||
function dbnToolsRequirePageAuth(?string $returnPath = null): void
|
||||
{
|
||||
if (dbnToolsIsAuthenticated()) {
|
||||
return;
|
||||
}
|
||||
header('Location: ' . dbnToolsMainLoginUrl($returnPath));
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a signed SSO token from dobetternorge.no.
|
||||
* Returns the decoded payload array or null on failure.
|
||||
@@ -148,6 +175,7 @@ function dbnToolsAuthenticatedUser(): ?array
|
||||
'user_id' => isset($_SESSION['dbn_tools_user_id']) ? (int)$_SESSION['dbn_tools_user_id'] : null,
|
||||
'client_id' => isset($_SESSION['dbn_tools_client_id']) ? (int)$_SESSION['dbn_tools_client_id'] : null,
|
||||
'email' => (string)($_SESSION['dbn_tools_user_email'] ?? ''),
|
||||
'name' => (string)($_SESSION['dbn_tools_user_name'] ?? ''),
|
||||
'role' => (string)($_SESSION['dbn_tools_user_role'] ?? ''),
|
||||
];
|
||||
}
|
||||
@@ -518,6 +546,43 @@ function dbnmDb(): PDO
|
||||
return $pdo;
|
||||
}
|
||||
|
||||
function dbnToolsMainUserProfile(int $userId): ?array
|
||||
{
|
||||
if ($userId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = dbnmDb()->prepare(
|
||||
'SELECT id, username, display_name, email, phone, address_line1, address_line2,
|
||||
postal_code, city, address_region, country, preferred_language,
|
||||
profile_prompt_dismissed_at
|
||||
FROM users
|
||||
WHERE id = ?
|
||||
LIMIT 1'
|
||||
);
|
||||
$stmt->execute([$userId]);
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
return is_array($row) ? $row : null;
|
||||
} catch (Throwable $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function dbnToolsProfileNeedsPrompt(?array $profile): bool
|
||||
{
|
||||
if (!$profile || !empty($profile['profile_prompt_dismissed_at'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (['display_name', 'phone', 'address_line1', 'postal_code', 'city', 'country'] as $field) {
|
||||
if (trim((string)($profile[$field] ?? '')) === '') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* True when the current session belongs to an SSO user (Google login).
|
||||
* All SSO sessions go through the credit + tier system (free, plus, pro).
|
||||
|
||||
Reference in New Issue
Block a user