1198 lines
75 KiB
PHP
1198 lines
75 KiB
PHP
<?php
|
|
/**
|
|
* Fociz Persoonlijk Profiel & Koppelingen Portaal (Voor alle gebruikers: Beheerders & Collega's)
|
|
*/
|
|
|
|
require_once __DIR__ . '/includes/auth.php';
|
|
require_once __DIR__ . '/includes/integrations/microsoft.php';
|
|
require_once __DIR__ . '/includes/integrations/jira.php';
|
|
|
|
// Iedere ingelogde gebruiker heeft toegang tot zijn/haar eigen profiel
|
|
$user = require_auth();
|
|
$userId = (int)$user['id'];
|
|
$db = get_db_connection();
|
|
|
|
$message = null;
|
|
$messageType = 'success';
|
|
|
|
if (!empty($_GET['success'])) {
|
|
$message = $_GET['success'];
|
|
$messageType = 'success';
|
|
} elseif (!empty($_GET['error'])) {
|
|
$message = $_GET['error'];
|
|
$messageType = 'error';
|
|
}
|
|
|
|
// POST Verwerking VÓÓR headers zodat redirects en mutaties soepel verlopen
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$action = $_POST['action'] ?? '';
|
|
|
|
// 1. Outlook koppelen via OAuth
|
|
if ($action === 'connect_outlook') {
|
|
$appConfig = microsoft_get_app_config();
|
|
if (!$appConfig['is_configured']) {
|
|
$message = ($user['role'] === 'ADMIN')
|
|
? "Configureer eerst de Application (Client) ID en het Client Secret in het Systeembeheer."
|
|
: "Microsoft 365 is nog niet ingesteld door de beheerder van uw organisatie. Vraag uw beheerder om de koppeling te activeren.";
|
|
$messageType = 'error';
|
|
} else {
|
|
header("Location: /auth/oauth_microsoft.php");
|
|
exit;
|
|
}
|
|
}
|
|
|
|
// 2. Outlook ontkoppelen (alleen persoonlijke tokens van deze gebruiker wissen)
|
|
elseif ($action === 'disconnect_outlook') {
|
|
microsoft_disconnect($userId);
|
|
header('Location: /profile.php?success=' . urlencode('Uw Microsoft Outlook agenda is succesvol ontkoppeld.'));
|
|
exit;
|
|
}
|
|
|
|
// 2b. Outlook synchronisatievoorkeuren opslaan
|
|
elseif ($action === 'save_outlook_settings') {
|
|
$syncInterval = (int)($_POST['sync_interval_seconds'] ?? 30);
|
|
|
|
$stmtExisting = $db->prepare("SELECT settings FROM integrations WHERE user_id = ? AND provider = 'OUTLOOK'");
|
|
$stmtExisting->execute([$userId]);
|
|
$existing = $stmtExisting->fetch();
|
|
$settings = json_decode($existing['settings'] ?? '{}', true) ?: [];
|
|
$settings['sync_interval_seconds'] = $syncInterval;
|
|
|
|
$stmtUp = $db->prepare("UPDATE integrations SET settings = ? WHERE user_id = ? AND provider = 'OUTLOOK'");
|
|
$stmtUp->execute([json_encode($settings), $userId]);
|
|
|
|
$message = "Uw Microsoft Outlook synchronisatievoorkeur is succesvol opgeslagen.";
|
|
$messageType = 'success';
|
|
}
|
|
// 3. Jira Opslaan & Testen
|
|
elseif ($action === 'save_jira') {
|
|
$companyDomain = jira_get_company_domain();
|
|
$rawDomain = trim($_POST['jira_domain'] ?? '');
|
|
$jiraDomain = !empty($rawDomain) ? jira_normalize_domain($rawDomain) : $companyDomain;
|
|
$jiraEmail = trim($_POST['jira_email'] ?? '');
|
|
$jiraToken = trim($_POST['jira_api_token'] ?? '');
|
|
|
|
// Haal bestaande instellingen op
|
|
$stmtExisting = $db->prepare("SELECT * FROM integrations WHERE user_id = ? AND provider = 'JIRA'");
|
|
$stmtExisting->execute([$userId]);
|
|
$existing = $stmtExisting->fetch();
|
|
$existingSettings = json_decode($existing['settings'] ?? '{}', true) ?: [];
|
|
|
|
if (($jiraToken === '' || $jiraToken === '••••••••••••••••') && !empty($existingSettings['jira_api_token']) && $existingSettings['jira_api_token'] !== '••••••••••••••••') {
|
|
$jiraToken = $existingSettings['jira_api_token'];
|
|
}
|
|
|
|
$accountName = $existingSettings['account_name'] ?? '';
|
|
$accountId = $existingSettings['account_id'] ?? '';
|
|
|
|
if (!empty($jiraDomain) && !empty($jiraEmail) && !empty($jiraToken)) {
|
|
$testRes = jira_test_connection($jiraDomain, $jiraEmail, $jiraToken);
|
|
if (!$testRes['success']) {
|
|
$message = "Jira Verbindingsfout: " . $testRes['error'];
|
|
$messageType = 'error';
|
|
} else {
|
|
$accountName = $testRes['user']['displayName'] ?? 'Jira Gebruiker';
|
|
$accountId = $testRes['user']['accountId'] ?? '';
|
|
$message = "Verbinding met Jira geslaagd! Ingelogd als: {$accountName}";
|
|
$messageType = 'success';
|
|
}
|
|
} else {
|
|
$message = "Jira instellingen opgeslagen.";
|
|
$messageType = 'success';
|
|
}
|
|
|
|
$newSettings = [
|
|
'jira_domain' => $jiraDomain,
|
|
'jira_email' => $jiraEmail,
|
|
'jira_api_token' => $jiraToken,
|
|
'account_name' => $accountName,
|
|
'account_id' => $accountId,
|
|
];
|
|
|
|
$stmt = $db->prepare("
|
|
INSERT INTO integrations (user_id, provider, is_enabled, is_demo_mode, settings, last_sync_at)
|
|
VALUES (?, 'JIRA', 1, 0, ?, NOW())
|
|
ON DUPLICATE KEY UPDATE
|
|
is_demo_mode = 0,
|
|
settings = VALUES(settings),
|
|
last_sync_at = NOW()
|
|
");
|
|
$stmt->execute([$userId, json_encode($newSettings)]);
|
|
unset($_SESSION["jira_issues_user_{$userId}"]);
|
|
}
|
|
|
|
// 4. Jira ontkoppelen
|
|
elseif ($action === 'disconnect_jira') {
|
|
jira_disconnect($userId);
|
|
header('Location: /profile.php?success=' . urlencode('Atlassian Jira is succesvol ontkoppeld.'));
|
|
exit;
|
|
}
|
|
|
|
// 5. Notificatievoorkeuren opslaan
|
|
elseif ($action === 'save_notifications') {
|
|
$settings = [
|
|
'enabled' => isset($_POST['notif_enabled']) ? 1 : 0,
|
|
'lead_time_minutes' => (int)($_POST['lead_time_minutes'] ?? 5),
|
|
'repeat_count' => (int)($_POST['repeat_count'] ?? 2),
|
|
'repeat_interval_minutes' => (int)($_POST['repeat_interval_minutes'] ?? 2),
|
|
'sound_enabled' => isset($_POST['sound_enabled']) ? 1 : 0,
|
|
];
|
|
update_user_notification_settings($userId, $settings);
|
|
$message = "Uw persoonlijke notificatievoorkeuren zijn succesvol opgeslagen.";
|
|
$messageType = 'success';
|
|
}
|
|
|
|
// 6. Profielnaam bijwerken
|
|
elseif ($action === 'update_profile') {
|
|
$name = trim($_POST['name'] ?? '');
|
|
if (strlen($name) < 2) {
|
|
$message = "Voer een geldige naam in van minimaal 2 tekens.";
|
|
$messageType = 'error';
|
|
} else {
|
|
$stmt = $db->prepare("UPDATE users SET name = ? WHERE id = ?");
|
|
$stmt->execute([$name, $userId]);
|
|
$_SESSION['user_name'] = $name;
|
|
$user['name'] = $name;
|
|
$message = "Uw profielnaam is succesvol bijgewerkt.";
|
|
$messageType = 'success';
|
|
}
|
|
}
|
|
|
|
// 7. Wachtwoord wijzigen
|
|
elseif ($action === 'change_password') {
|
|
$currentPassword = $_POST['current_password'] ?? '';
|
|
$newPassword = $_POST['new_password'] ?? '';
|
|
$confirmPassword = $_POST['confirm_password'] ?? '';
|
|
|
|
$stmtU = $db->prepare("SELECT password_hash FROM users WHERE id = ?");
|
|
$stmtU->execute([$userId]);
|
|
$storedHash = $stmtU->fetchColumn();
|
|
|
|
if (!password_verify($currentPassword, $storedHash)) {
|
|
$message = "Uw huidige wachtwoord is niet correct ingevoerd.";
|
|
$messageType = 'error';
|
|
} elseif (strlen($newPassword) < 8) {
|
|
$message = "Het nieuwe wachtwoord moet minimaal 8 tekens lang zijn.";
|
|
$messageType = 'error';
|
|
} elseif ($newPassword !== $confirmPassword) {
|
|
$message = "Het nieuwe wachtwoord en de bevestiging komen niet overeen.";
|
|
$messageType = 'error';
|
|
} else {
|
|
$newHash = password_hash($newPassword, PASSWORD_DEFAULT);
|
|
$stmtUp = $db->prepare("UPDATE users SET password_hash = ? WHERE id = ?");
|
|
$stmtUp->execute([$newHash, $userId]);
|
|
$message = "Uw wachtwoord is succesvol gewijzigd.";
|
|
$messageType = 'success';
|
|
}
|
|
}
|
|
|
|
// 8. Standaard avatar kiezen uit bibliotheek
|
|
elseif ($action === 'select_preset_avatar') {
|
|
$preset = trim($_POST['avatar_preset'] ?? '');
|
|
if (preg_match('/^\/assets\/avatars\/avatar-(0[1-9]|1[0-9]|2[0-4])\.svg$/', $preset)) {
|
|
$stmtUp = $db->prepare("UPDATE users SET avatar_url = ? WHERE id = ?");
|
|
$stmtUp->execute([$preset, $userId]);
|
|
$_SESSION['user_avatar_url'] = $preset;
|
|
$user['avatar_url'] = $preset;
|
|
$message = "Uw avatar is succesvol bijgewerkt!";
|
|
$messageType = 'success';
|
|
} else {
|
|
$message = "Ongeldige avatar geselecteerd.";
|
|
$messageType = 'error';
|
|
}
|
|
}
|
|
|
|
// 9. Eigen profielfoto uploaden
|
|
elseif ($action === 'upload_custom_avatar') {
|
|
if (!isset($_FILES['avatar_file']) || $_FILES['avatar_file']['error'] !== UPLOAD_ERR_OK) {
|
|
$message = "Selecteer een geldige afbeelding om te uploaden.";
|
|
$messageType = 'error';
|
|
} else {
|
|
$file = $_FILES['avatar_file'];
|
|
$maxSize = 4 * 1024 * 1024; // 4MB
|
|
if ($file['size'] > $maxSize) {
|
|
$message = "De afbeelding is te groot (maximaal 4MB toegestaan).";
|
|
$messageType = 'error';
|
|
} else {
|
|
$allowedTypes = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];
|
|
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
|
$mime = finfo_file($finfo, $file['tmp_name']);
|
|
finfo_close($finfo);
|
|
|
|
if (!in_array($mime, $allowedTypes)) {
|
|
$message = "Alleen JPG, PNG, WebP of GIF afbeeldingen zijn toegestaan.";
|
|
$messageType = 'error';
|
|
} else {
|
|
$ext = match($mime) {
|
|
'image/jpeg' => 'jpg',
|
|
'image/png' => 'png',
|
|
'image/webp' => 'webp',
|
|
'image/gif' => 'gif',
|
|
default => 'jpg'
|
|
};
|
|
|
|
$uploadDir = __DIR__ . '/assets/uploads/avatars';
|
|
if (!is_dir($uploadDir)) {
|
|
@mkdir($uploadDir, 0755, true);
|
|
}
|
|
|
|
$filename = "avatar_{$userId}_" . time() . "_" . bin2hex(random_bytes(4)) . ".{$ext}";
|
|
$targetPath = $uploadDir . '/' . $filename;
|
|
|
|
if (move_uploaded_file($file['tmp_name'], $targetPath)) {
|
|
$webPath = "/assets/uploads/avatars/{$filename}";
|
|
$stmtUp = $db->prepare("UPDATE users SET avatar_url = ? WHERE id = ?");
|
|
$stmtUp->execute([$webPath, $userId]);
|
|
$_SESSION['user_avatar_url'] = $webPath;
|
|
$user['avatar_url'] = $webPath;
|
|
$message = "Uw profielfoto is succesvol geüpload en ingesteld!";
|
|
$messageType = 'success';
|
|
} else {
|
|
$message = "Er is een fout opgetreden bij het verplaatsen van het geüploade bestand.";
|
|
$messageType = 'error';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 10. Avatar verwijderen
|
|
elseif ($action === 'remove_avatar') {
|
|
$stmtUp = $db->prepare("UPDATE users SET avatar_url = NULL WHERE id = ?");
|
|
$stmtUp->execute([$userId]);
|
|
unset($_SESSION['user_avatar_url']);
|
|
$user['avatar_url'] = null;
|
|
$message = "Uw avatar is teruggezet naar de standaard weergave met uw initialen.";
|
|
$messageType = 'success';
|
|
}
|
|
}
|
|
|
|
// Actuele gegevens ophalen
|
|
$stmtUser = $db->prepare("SELECT * FROM users WHERE id = ?");
|
|
$stmtUser->execute([$userId]);
|
|
$userProfile = $stmtUser->fetch();
|
|
|
|
// Outlook status ophalen
|
|
$stmtOutlook = $db->prepare("SELECT * FROM integrations WHERE user_id = ? AND provider = 'OUTLOOK'");
|
|
$stmtOutlook->execute([$userId]);
|
|
$outlookRow = $stmtOutlook->fetch();
|
|
$isOutlookConnected = !empty($outlookRow['access_token']) && empty($outlookRow['is_demo_mode']);
|
|
$outlookSettings = json_decode($outlookRow['settings'] ?? '{}', true) ?: [];
|
|
|
|
// Jira status ophalen
|
|
$stmtJira = $db->prepare("SELECT * FROM integrations WHERE user_id = ? AND provider = 'JIRA'");
|
|
$stmtJira->execute([$userId]);
|
|
$jiraRow = $stmtJira->fetch();
|
|
$jiraSettings = json_decode($jiraRow['settings'] ?? '{}', true) ?: [];
|
|
$isJiraConnected = !empty($jiraSettings['jira_api_token']);
|
|
$companyJiraDomain = jira_get_company_domain();
|
|
$activeJiraDomain = !empty($jiraSettings['jira_domain']) ? $jiraSettings['jira_domain'] : $companyJiraDomain;
|
|
|
|
// Centrale Azure App configuratie status
|
|
$appConfig = microsoft_get_app_config();
|
|
|
|
// Notificatie-instellingen
|
|
$notifSettings = get_user_notification_settings($userId);
|
|
|
|
// Thema ophalen
|
|
$userTheme = get_user_theme($userId);
|
|
|
|
$pageTitle = 'Mijn Profiel & Koppelingen — Fociz';
|
|
require_once __DIR__ . '/includes/header.php';
|
|
?>
|
|
|
|
<div class="space-y-8 max-w-5xl mx-auto pb-12">
|
|
<!-- Profiel Header Card -->
|
|
<div class="relative overflow-hidden fociz-card p-6 sm:p-8">
|
|
<div class="absolute -top-20 -right-20 w-72 h-72 bg-indigo-500/10 rounded-full blur-3xl pointer-events-none"></div>
|
|
|
|
<div class="relative z-10 flex flex-col sm:flex-row sm:items-center justify-between gap-6">
|
|
<div class="flex items-center space-x-4 sm:space-x-5">
|
|
<div class="w-16 h-16 sm:w-20 sm:h-20 rounded-2xl bg-gradient-to-tr from-indigo-600 to-sky-500 p-0.5 shadow-sm flex-shrink-0 overflow-hidden">
|
|
<?php if (!empty($userProfile['avatar_url'])): ?>
|
|
<img src="<?= htmlspecialchars($userProfile['avatar_url']) ?>" alt="Avatar" class="w-full h-full rounded-2xl object-cover bg-white dark:bg-[#0c0f17]">
|
|
<?php else: ?>
|
|
<div class="w-full h-full rounded-2xl bg-white dark:bg-[#0c0f17] flex items-center justify-center font-bold text-2xl sm:text-3xl text-indigo-600 dark:text-indigo-400 uppercase">
|
|
<?= strtoupper(substr($userProfile['name'], 0, 1)) ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<div class="space-y-1">
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
<h1 class="text-2xl sm:text-3xl font-black text-slate-900 dark:text-white tracking-tight">
|
|
<?= htmlspecialchars($userProfile['name']) ?>
|
|
</h1>
|
|
<?php if ($userProfile['role'] === 'ADMIN'): ?>
|
|
<span class="px-2.5 py-0.5 rounded-full bg-indigo-50 dark:bg-indigo-500/10 border border-indigo-200 dark:border-indigo-500/20 text-indigo-700 dark:text-indigo-300 text-xs font-semibold flex items-center space-x-1">
|
|
<i data-lucide="shield" class="w-3 h-3"></i>
|
|
<span>Beheerder</span>
|
|
</span>
|
|
<?php else: ?>
|
|
<span class="px-2.5 py-0.5 rounded-full bg-slate-100 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 text-slate-700 dark:text-slate-300 text-xs font-semibold flex items-center space-x-1">
|
|
<i data-lucide="user" class="w-3 h-3"></i>
|
|
<span>Collega</span>
|
|
</span>
|
|
<?php endif; ?>
|
|
|
|
<span class="px-2.5 py-0.5 rounded-full bg-teal-50 dark:bg-teal-500/10 border border-teal-200 dark:border-teal-500/20 text-teal-700 dark:text-teal-400 text-xs font-semibold flex items-center space-x-1">
|
|
<i data-lucide="shield-check" class="w-3 h-3"></i>
|
|
<span>2FA Beveiligd</span>
|
|
</span>
|
|
</div>
|
|
|
|
<p class="text-xs text-slate-500 dark:text-slate-400 flex items-center space-x-2">
|
|
<span><?= htmlspecialchars($userProfile['email']) ?></span>
|
|
<span>•</span>
|
|
<span>Lid sinds <?= date('d-m-Y', strtotime($userProfile['created_at'])) ?></span>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<?php if ($userProfile['role'] === 'ADMIN'): ?>
|
|
<div class="flex-shrink-0">
|
|
<a href="/admin.php" class="inline-flex items-center space-x-2 px-4 py-2.5 rounded-xl bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700 text-slate-800 dark:text-slate-200 border border-slate-200 dark:border-slate-700 text-xs font-semibold transition shadow-xs">
|
|
<i data-lucide="shield-alert" class="w-4 h-4 text-indigo-600 dark:text-indigo-400"></i>
|
|
<span>Systeembeheer Openen</span>
|
|
</a>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Meldingen Banner -->
|
|
<?php if ($message): ?>
|
|
<div class="p-4 rounded-xl flex items-start space-x-3 text-sm animate-in fade-in duration-200 <?= $messageType === 'success' ? 'bg-teal-50 dark:bg-teal-500/10 border border-teal-200 dark:border-teal-500/20 text-teal-800 dark:text-teal-200' : 'bg-rose-50 dark:bg-rose-500/10 border border-rose-200 dark:border-rose-500/20 text-rose-800 dark:text-rose-200' ?>">
|
|
<i data-lucide="<?= $messageType === 'success' ? 'check-circle-2' : 'alert-triangle' ?>" class="w-5 h-5 flex-shrink-0 mt-0.5 <?= $messageType === 'success' ? 'text-teal-600 dark:text-teal-400' : 'text-rose-600 dark:text-rose-400' ?>"></i>
|
|
<div class="flex-1 font-medium"><?= htmlspecialchars($message) ?></div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<!-- Sectie 0: Weergave & Thema Instellingen (Dark & Light mode) -->
|
|
<div class="fociz-card p-6 sm:p-7">
|
|
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3 pb-6 border-b border-slate-100 dark:border-slate-800/80">
|
|
<div class="flex items-center space-x-3.5">
|
|
<div class="w-12 h-12 rounded-2xl bg-indigo-50 dark:bg-indigo-500/10 border border-indigo-200 dark:border-indigo-500/20 flex items-center justify-center text-indigo-600 dark:text-indigo-400 font-bold shadow-xs">
|
|
<i data-lucide="palette" class="w-6 h-6"></i>
|
|
</div>
|
|
<div>
|
|
<div class="flex items-center space-x-2">
|
|
<h2 class="text-base sm:text-lg font-bold text-slate-900 dark:text-white tracking-tight">Weergave & Thema</h2>
|
|
<span id="themeStatusBadge" class="px-2.5 py-0.5 rounded-full bg-indigo-50 dark:bg-indigo-500/10 border border-indigo-200 dark:border-indigo-500/20 text-indigo-700 dark:text-indigo-400 text-[10px] font-semibold">
|
|
Actief: <?= ucfirst($userTheme) ?>
|
|
</span>
|
|
</div>
|
|
<p class="text-xs text-slate-500 dark:text-slate-400">Kies uw gewenste uiterlijk: rustig donker grafiet, kraakhelder licht of synchroniseer met uw apparaat.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-6 grid grid-cols-1 sm:grid-cols-3 gap-4">
|
|
<!-- Optie 1: Donker (Dark) -->
|
|
<button type="button" onclick="selectThemePreference('dark')" id="themeBtn-dark" class="theme-card relative text-left p-4 rounded-xl border-2 transition duration-150 flex flex-col justify-between group hover:shadow-md cursor-pointer bg-[#0c0f17] border-slate-800 text-white">
|
|
<div class="w-full h-24 rounded-lg bg-[#111622] p-2.5 border border-slate-800/80 mb-3 flex flex-col justify-between overflow-hidden shadow-inner">
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center space-x-1.5">
|
|
<span class="w-2 h-2 rounded-full bg-rose-400/80"></span>
|
|
<span class="w-2 h-2 rounded-full bg-amber-400/80"></span>
|
|
<span class="w-2 h-2 rounded-full bg-teal-400/80"></span>
|
|
</div>
|
|
<span class="text-[9px] font-mono text-slate-500">fociz.dark</span>
|
|
</div>
|
|
<div class="space-y-1.5">
|
|
<div class="h-2 w-3/4 bg-indigo-500/60 rounded"></div>
|
|
<div class="h-2 w-1/2 bg-slate-800 rounded"></div>
|
|
</div>
|
|
<div class="flex space-x-1">
|
|
<div class="h-3 w-8 bg-slate-800 rounded"></div>
|
|
<div class="h-3 w-10 bg-indigo-500/20 rounded"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<div class="flex items-center justify-between">
|
|
<span class="text-sm font-semibold text-white flex items-center space-x-1.5">
|
|
<i data-lucide="moon" class="w-4 h-4 text-indigo-400"></i>
|
|
<span>Donkere Modus</span>
|
|
</span>
|
|
<span id="themeCheck-dark" class="theme-check hidden w-5 h-5 rounded-full bg-indigo-600 text-white items-center justify-center shadow-xs">
|
|
<i data-lucide="check" class="w-3.5 h-3.5 stroke-[3]"></i>
|
|
</span>
|
|
</div>
|
|
<p class="text-[11px] text-slate-400 mt-1 leading-relaxed">
|
|
Diepe neutrale contrasten, rustig voor de ogen in de avond of donkere werkplek.
|
|
</p>
|
|
</div>
|
|
</button>
|
|
|
|
<!-- Optie 2: Licht (Light) -->
|
|
<button type="button" onclick="selectThemePreference('light')" id="themeBtn-light" class="theme-card relative text-left p-4 rounded-xl border-2 transition duration-150 flex flex-col justify-between group hover:shadow-md cursor-pointer bg-[#f8fafc] border-slate-200 text-slate-900">
|
|
<div class="w-full h-24 rounded-lg bg-white p-2.5 border border-slate-200/90 mb-3 flex flex-col justify-between overflow-hidden shadow-xs">
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center space-x-1.5">
|
|
<span class="w-2 h-2 rounded-full bg-rose-400/80"></span>
|
|
<span class="w-2 h-2 rounded-full bg-amber-400/80"></span>
|
|
<span class="w-2 h-2 rounded-full bg-teal-400/80"></span>
|
|
</div>
|
|
<span class="text-[9px] font-mono text-slate-400">fociz.light</span>
|
|
</div>
|
|
<div class="space-y-1.5">
|
|
<div class="h-2 w-3/4 bg-indigo-600/50 rounded"></div>
|
|
<div class="h-2 w-1/2 bg-slate-200 rounded"></div>
|
|
</div>
|
|
<div class="flex space-x-1">
|
|
<div class="h-3 w-8 bg-slate-100 rounded"></div>
|
|
<div class="h-3 w-10 bg-indigo-50 rounded"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<div class="flex items-center justify-between">
|
|
<span class="text-sm font-semibold text-slate-900 flex items-center space-x-1.5">
|
|
<i data-lucide="sun" class="w-4 h-4 text-amber-500"></i>
|
|
<span>Lichte Modus</span>
|
|
</span>
|
|
<span id="themeCheck-light" class="theme-check hidden w-5 h-5 rounded-full bg-indigo-600 text-white items-center justify-center shadow-xs">
|
|
<i data-lucide="check" class="w-3.5 h-3.5 stroke-[3]"></i>
|
|
</span>
|
|
</div>
|
|
<p class="text-[11px] text-slate-500 mt-1 leading-relaxed">
|
|
Kraakhelder, fris off-white en professioneel voor overdag en felle kantoorverlichting.
|
|
</p>
|
|
</div>
|
|
</button>
|
|
|
|
<!-- Optie 3: Systeem (Auto) -->
|
|
<button type="button" onclick="selectThemePreference('system')" id="themeBtn-system" class="theme-card relative text-left p-4 rounded-xl border-2 transition duration-150 flex flex-col justify-between group hover:shadow-md cursor-pointer bg-slate-50 dark:bg-[#111622] border-slate-200 dark:border-slate-800">
|
|
<div class="w-full h-24 rounded-lg border border-slate-200 dark:border-slate-800 mb-3 flex overflow-hidden shadow-xs">
|
|
<!-- Linker helft licht -->
|
|
<div class="w-1/2 bg-[#f8fafc] p-2 flex flex-col justify-between border-r border-slate-200">
|
|
<div class="flex space-x-1">
|
|
<span class="w-1.5 h-1.5 rounded-full bg-slate-300"></span>
|
|
<span class="w-1.5 h-1.5 rounded-full bg-slate-300"></span>
|
|
</div>
|
|
<div class="h-2 w-3/4 bg-indigo-600/40 rounded"></div>
|
|
<div class="h-2 w-1/2 bg-slate-200 rounded"></div>
|
|
</div>
|
|
<!-- Rechter helft donker -->
|
|
<div class="w-1/2 bg-[#0c0f17] p-2 flex flex-col justify-between">
|
|
<div class="flex justify-end space-x-1">
|
|
<span class="w-1.5 h-1.5 rounded-full bg-slate-700"></span>
|
|
<span class="w-1.5 h-1.5 rounded-full bg-slate-700"></span>
|
|
</div>
|
|
<div class="h-2 w-3/4 bg-indigo-500/50 rounded"></div>
|
|
<div class="h-2 w-1/2 bg-slate-800 rounded"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<div class="flex items-center justify-between">
|
|
<span class="text-sm font-semibold text-slate-900 dark:text-white flex items-center space-x-1.5">
|
|
<i data-lucide="laptop" class="w-4 h-4 text-sky-500"></i>
|
|
<span>Systeem Volgen</span>
|
|
</span>
|
|
<span id="themeCheck-system" class="theme-check hidden w-5 h-5 rounded-full bg-indigo-600 text-white items-center justify-center shadow-xs">
|
|
<i data-lucide="check" class="w-3.5 h-3.5 stroke-[3]"></i>
|
|
</span>
|
|
</div>
|
|
<p class="text-[11px] text-slate-500 dark:text-slate-400 mt-1 leading-relaxed">
|
|
Schakelt automatisch mee met uw Windows, macOS of browser voorkeur.
|
|
</p>
|
|
</div>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sectie 0b: Profielfoto & Avatar Beheer -->
|
|
<div class="fociz-card p-6 sm:p-7 space-y-6">
|
|
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 pb-6 border-b border-slate-100 dark:border-slate-800/80">
|
|
<div class="flex items-center space-x-3.5">
|
|
<div class="w-12 h-12 rounded-2xl bg-gradient-to-tr from-indigo-500 to-pink-500 p-0.5 shadow-xs flex-shrink-0 flex items-center justify-center text-white">
|
|
<i data-lucide="smile" class="w-6 h-6"></i>
|
|
</div>
|
|
<div>
|
|
<div class="flex items-center space-x-2">
|
|
<h2 class="text-base sm:text-lg font-bold text-slate-900 dark:text-white tracking-tight">Profielfoto & Avatar</h2>
|
|
<span class="px-2.5 py-0.5 rounded-full <?= !empty($userProfile['avatar_url']) ? 'bg-teal-50 dark:bg-teal-500/10 border border-teal-200 dark:border-teal-500/20 text-teal-700 dark:text-teal-400' : 'bg-slate-100 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 text-slate-600 dark:text-slate-400' ?> text-[10px] font-semibold">
|
|
<?= !empty($userProfile['avatar_url']) ? 'Aangepaste Avatar Actief' : 'Standaard Initiaal-Badge' ?>
|
|
</span>
|
|
</div>
|
|
<p class="text-xs text-slate-500 dark:text-slate-400">Kies een mini-avatar uit de 24 stijlvolle karakters of upload een eigen afbeelding.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<?php if (!empty($userProfile['avatar_url'])): ?>
|
|
<form method="POST" action="/profile.php" class="self-start sm:self-auto">
|
|
<input type="hidden" name="action" value="remove_avatar">
|
|
<button type="submit" onclick="return confirm('Avatar herstellen naar standaard initiaal-badge?')" class="inline-flex items-center space-x-1.5 px-3 py-1.5 rounded-xl text-xs font-semibold text-rose-600 dark:text-rose-400 bg-rose-50 dark:bg-rose-950/30 border border-rose-200 dark:border-rose-800/50 hover:bg-rose-100 dark:hover:bg-rose-900/50 transition cursor-pointer">
|
|
<i data-lucide="rotate-ccw" class="w-3.5 h-3.5"></i>
|
|
<span>Standaard Herstellen</span>
|
|
</button>
|
|
</form>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-12 gap-6">
|
|
<!-- Linker kolom: 24 Standaard Preset Avatars (8 cols) -->
|
|
<div class="lg:col-span-8 space-y-3">
|
|
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
|
|
<h3 class="text-xs font-bold text-slate-800 dark:text-slate-200 flex items-center space-x-1.5">
|
|
<i data-lucide="sparkles" class="w-3.5 h-3.5 text-indigo-500"></i>
|
|
<span>Kies een Karakter (24 Flitsende Avatars)</span>
|
|
</h3>
|
|
<span class="text-[11px] text-slate-400 dark:text-slate-500">Klik om direct toe te passen</span>
|
|
</div>
|
|
|
|
<!-- Categorie Filter Pills -->
|
|
<div class="flex flex-wrap items-center gap-1.5 pt-1">
|
|
<button type="button" onclick="filterAvatarCategory('all')" id="avCatBtn-all" class="av-cat-btn px-2.5 py-1 rounded-lg text-[11px] font-bold transition cursor-pointer bg-indigo-600 text-white shadow-xs">
|
|
Alle (24)
|
|
</button>
|
|
<button type="button" onclick="filterAvatarCategory('emoji')" id="avCatBtn-emoji" class="av-cat-btn px-2.5 py-1 rounded-lg text-[11px] font-semibold transition cursor-pointer bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-300">
|
|
⚡ Fun & Emojis (6)
|
|
</button>
|
|
<button type="button" onclick="filterAvatarCategory('bots')" id="avCatBtn-bots" class="av-cat-btn px-2.5 py-1 rounded-lg text-[11px] font-semibold transition cursor-pointer bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-300">
|
|
🤖 Cyber & Bots (6)
|
|
</button>
|
|
<button type="button" onclick="filterAvatarCategory('personas')" id="avCatBtn-personas" class="av-cat-btn px-2.5 py-1 rounded-lg text-[11px] font-semibold transition cursor-pointer bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-300">
|
|
✨ Moderne Personas (6)
|
|
</button>
|
|
<button type="button" onclick="filterAvatarCategory('heroes')" id="avCatBtn-heroes" class="av-cat-btn px-2.5 py-1 rounded-lg text-[11px] font-semibold transition cursor-pointer bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-300">
|
|
🎯 Adventurer & Heroes (6)
|
|
</button>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-4 sm:grid-cols-6 md:grid-cols-8 gap-3 p-4 rounded-2xl bg-slate-50 dark:bg-[#0c0f17] border border-slate-200 dark:border-slate-800">
|
|
<?php
|
|
$avatarPresets = [
|
|
['file' => 'avatar-01.svg', 'cat' => 'emoji', 'name' => 'Cool Star (Zonnebril)'],
|
|
['file' => 'avatar-02.svg', 'cat' => 'emoji', 'name' => 'Party Champion'],
|
|
['file' => 'avatar-03.svg', 'cat' => 'emoji', 'name' => 'Celebration Vibe'],
|
|
['file' => 'avatar-04.svg', 'cat' => 'emoji', 'name' => 'Laser Eyes Energy'],
|
|
['file' => 'avatar-05.svg', 'cat' => 'emoji', 'name' => 'Sunny Smiles'],
|
|
['file' => 'avatar-06.svg', 'cat' => 'emoji', 'name' => 'Fire Heart'],
|
|
['file' => 'avatar-07.svg', 'cat' => 'bots', 'name' => 'Cyber Tron'],
|
|
['file' => 'avatar-08.svg', 'cat' => 'bots', 'name' => 'Aero Bot'],
|
|
['file' => 'avatar-09.svg', 'cat' => 'bots', 'name' => 'Neon Volt'],
|
|
['file' => 'avatar-10.svg', 'cat' => 'bots', 'name' => 'Pixel Pulse'],
|
|
['file' => 'avatar-11.svg', 'cat' => 'bots', 'name' => 'Quantum AI'],
|
|
['file' => 'avatar-12.svg', 'cat' => 'bots', 'name' => 'Robo Racer'],
|
|
['file' => 'avatar-13.svg', 'cat' => 'personas', 'name' => 'Alex (Hipster)'],
|
|
['file' => 'avatar-14.svg', 'cat' => 'personas', 'name' => 'Maya (Tech Lead)'],
|
|
['file' => 'avatar-15.svg', 'cat' => 'personas', 'name' => 'Lucas (Designer)'],
|
|
['file' => 'avatar-16.svg', 'cat' => 'personas', 'name' => 'Elena (Developer)'],
|
|
['file' => 'avatar-17.svg', 'cat' => 'personas', 'name' => 'Sam (Creative)'],
|
|
['file' => 'avatar-18.svg', 'cat' => 'personas', 'name' => 'Zoe (Founder)'],
|
|
['file' => 'avatar-19.svg', 'cat' => 'heroes', 'name' => 'Jack (Adventurer)'],
|
|
['file' => 'avatar-20.svg', 'cat' => 'heroes', 'name' => 'Luna (Heroine)'],
|
|
['file' => 'avatar-21.svg', 'cat' => 'heroes', 'name' => 'Oliver (Explorer)'],
|
|
['file' => 'avatar-22.svg', 'cat' => 'heroes', 'name' => 'Mia (Rebel)'],
|
|
['file' => 'avatar-23.svg', 'cat' => 'heroes', 'name' => 'Leo (Cosmic)'],
|
|
['file' => 'avatar-24.svg', 'cat' => 'heroes', 'name' => 'Nova (Cyberpunk)'],
|
|
];
|
|
|
|
foreach ($avatarPresets as $av):
|
|
$avatarPath = "/assets/avatars/{$av['file']}";
|
|
$isSelected = ($userProfile['avatar_url'] === $avatarPath);
|
|
?>
|
|
<form method="POST" action="/profile.php" class="relative group avatar-item" data-category="<?= $av['cat'] ?>">
|
|
<input type="hidden" name="action" value="select_preset_avatar">
|
|
<input type="hidden" name="avatar_preset" value="<?= $avatarPath ?>">
|
|
<button type="submit" class="w-full aspect-square rounded-2xl p-1 transition-all duration-200 transform hover:scale-110 active:scale-95 hover:shadow-lg hover:shadow-indigo-500/25 focus:outline-none cursor-pointer <?= $isSelected ? 'ring-2 ring-indigo-600 dark:ring-indigo-400 bg-indigo-50 dark:bg-indigo-950/40 shadow-sm' : 'hover:bg-white dark:hover:bg-slate-800 border border-transparent' ?>" title="<?= htmlspecialchars($av['name']) ?>">
|
|
<img src="<?= $avatarPath ?>" alt="<?= htmlspecialchars($av['name']) ?>" class="w-full h-full rounded-xl object-contain pointer-events-none drop-shadow-xs">
|
|
<?php if ($isSelected): ?>
|
|
<span class="absolute -top-1 -right-1 w-5 h-5 rounded-full bg-indigo-600 text-white flex items-center justify-center shadow-xs border-2 border-white dark:border-[#0c0f17]">
|
|
<i data-lucide="check" class="w-3 h-3 stroke-[3]"></i>
|
|
</span>
|
|
<?php endif; ?>
|
|
</button>
|
|
</form>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Rechter kolom: Eigen Afbeelding Uploaden (4 cols) -->
|
|
<div class="lg:col-span-4 space-y-3">
|
|
<h3 class="text-xs font-bold text-slate-800 dark:text-slate-200 flex items-center space-x-1.5">
|
|
<i data-lucide="upload-cloud" class="w-3.5 h-3.5 text-sky-500"></i>
|
|
<span>Eigen Foto Uploaden</span>
|
|
</h3>
|
|
|
|
<form method="POST" action="/profile.php" enctype="multipart/form-data" class="p-4 rounded-2xl bg-slate-50 dark:bg-[#0c0f17] border border-slate-200 dark:border-slate-800 space-y-4">
|
|
<input type="hidden" name="action" value="upload_custom_avatar">
|
|
|
|
<div class="text-center p-5 border-2 border-dashed border-slate-300 dark:border-slate-700 rounded-xl hover:border-indigo-500 dark:hover:border-indigo-400 transition bg-white dark:bg-[#111622]">
|
|
<div class="w-10 h-10 mx-auto mb-2 rounded-xl bg-indigo-50 dark:bg-indigo-950/40 text-indigo-600 dark:text-indigo-400 flex items-center justify-center">
|
|
<i data-lucide="image" class="w-5 h-5"></i>
|
|
</div>
|
|
<label for="customAvatarFile" class="block text-xs font-semibold text-indigo-600 dark:text-indigo-400 hover:underline cursor-pointer mb-1">
|
|
Klik om bestand te kiezen
|
|
</label>
|
|
<p class="text-[10px] text-slate-500 dark:text-slate-400">JPG, PNG, WebP of GIF (max. 4MB)</p>
|
|
<input type="file" id="customAvatarFile" name="avatar_file" accept="image/png, image/jpeg, image/webp, image/gif" required class="hidden" onchange="previewUploadAvatar(this)">
|
|
<div id="uploadFileName" class="mt-2 text-[11px] font-mono text-slate-700 dark:text-slate-300 truncate hidden"></div>
|
|
</div>
|
|
|
|
<button type="submit" id="uploadAvatarBtn" class="w-full py-2.5 px-4 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-bold transition shadow-xs flex items-center justify-center space-x-2 cursor-pointer">
|
|
<i data-lucide="upload" class="w-4 h-4"></i>
|
|
<span>Foto Uploaden & Opslaan</span>
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sectie 1: Mijn Microsoft 365 Outlook Agenda -->
|
|
<div class="fociz-card p-6 sm:p-7">
|
|
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 pb-6 border-b border-slate-100 dark:border-slate-800/80">
|
|
<div class="flex items-center space-x-3.5">
|
|
<div class="w-12 h-12 rounded-2xl bg-sky-50 dark:bg-sky-500/10 border border-sky-200 dark:border-sky-500/20 flex items-center justify-center text-sky-600 dark:text-sky-400 font-bold shadow-xs">
|
|
<i data-lucide="calendar" class="w-6 h-6"></i>
|
|
</div>
|
|
<div>
|
|
<div class="flex items-center space-x-2">
|
|
<h2 class="text-base sm:text-lg font-bold text-slate-900 dark:text-white tracking-tight">Mijn Microsoft 365 (Agenda & E-mail)</h2>
|
|
<?php if ($isOutlookConnected): ?>
|
|
<span class="px-2.5 py-0.5 rounded-full bg-teal-50 dark:bg-teal-500/15 border border-teal-200 dark:border-teal-500/30 text-teal-700 dark:text-teal-400 text-[10px] font-semibold flex items-center space-x-1">
|
|
<span class="w-1.5 h-1.5 rounded-full bg-teal-500 animate-pulse"></span>
|
|
<span>Live Verbonden</span>
|
|
</span>
|
|
<?php else: ?>
|
|
<span class="px-2.5 py-0.5 rounded-full bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400 text-[10px] font-semibold">
|
|
Niet Gekoppeld
|
|
</span>
|
|
<?php endif; ?>
|
|
</div>
|
|
<p class="text-xs text-slate-500 dark:text-slate-400">Synchroniseer uw agenda-afspraken en zakelijke Outlook e-mails met Fociz</p>
|
|
</div>
|
|
</div>
|
|
|
|
<?php if ($isOutlookConnected): ?>
|
|
<div class="flex items-center space-x-2">
|
|
<a href="/auth/oauth_microsoft.php?reconsent=1" class="px-3 py-1.5 rounded-xl bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-300 border border-slate-200 dark:border-slate-700 text-xs font-medium transition" title="Machtigingen bijwerken bij Microsoft (inclusief Mail.ReadWrite)">
|
|
Machtigingen Bijwerken
|
|
</a>
|
|
<form method="POST" onsubmit="return confirm('Weet u zeker dat u uw Microsoft Outlook agenda wilt ontkoppelen?');">
|
|
<input type="hidden" name="action" value="disconnect_outlook">
|
|
<button type="submit" class="px-3 py-1.5 rounded-xl bg-rose-50 hover:bg-rose-100 dark:bg-rose-500/10 dark:hover:bg-rose-500/20 text-rose-600 dark:text-rose-400 border border-rose-200 dark:border-rose-500/20 text-xs font-medium transition cursor-pointer">
|
|
Ontkoppelen
|
|
</button>
|
|
</form>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<div class="mt-6">
|
|
<?php if ($isOutlookConnected): ?>
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
<div class="p-4 rounded-xl bg-slate-50/70 dark:bg-slate-900/60 border border-slate-200/80 dark:border-slate-800 space-y-1.5">
|
|
<span class="text-[10px] uppercase tracking-wider font-semibold text-slate-500 dark:text-slate-400 block">Gekoppeld Microsoft Account</span>
|
|
<span class="text-sm font-bold text-slate-900 dark:text-white block truncate"><?= htmlspecialchars($outlookSettings['account_name'] ?? 'Microsoft Gebruiker') ?></span>
|
|
<span class="text-xs text-indigo-600 dark:text-indigo-400 block truncate"><?= htmlspecialchars($outlookSettings['account_email'] ?? $userProfile['email']) ?></span>
|
|
</div>
|
|
|
|
<div class="p-4 rounded-xl bg-slate-50/70 dark:bg-slate-900/60 border border-slate-200/80 dark:border-slate-800 space-y-1.5">
|
|
<span class="text-[10px] uppercase tracking-wider font-semibold text-slate-500 dark:text-slate-400 block">Microsoft Teams Koppeling</span>
|
|
<span class="text-sm font-bold text-slate-900 dark:text-white flex items-center space-x-1.5">
|
|
<i data-lucide="video" class="w-4 h-4 text-sky-500"></i>
|
|
<span>Automatische Meeting Links</span>
|
|
</span>
|
|
<span class="text-xs text-slate-500 dark:text-slate-400 block">Vergaderlinks in uw agenda worden direct klikbaar in uw dagplanning</span>
|
|
</div>
|
|
</div>
|
|
|
|
<form method="POST" class="mt-4 p-4 rounded-xl bg-slate-50/70 dark:bg-slate-900/60 border border-slate-200/80 dark:border-slate-800 flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
|
<input type="hidden" name="action" value="save_outlook_settings">
|
|
<div class="space-y-1">
|
|
<label for="sync_interval_seconds" class="text-xs font-bold text-slate-900 dark:text-white flex items-center space-x-1.5">
|
|
<i data-lucide="refresh-cw" class="w-3.5 h-3.5 text-sky-500"></i>
|
|
<span>Automatische Agenda Synchronisatie</span>
|
|
</label>
|
|
<p class="text-[11px] text-slate-500 dark:text-slate-400 leading-relaxed">
|
|
Bepaalt hoe vaak het dagplanningscherm in de achtergrond controleert op gewijzigde of verwijderde Outlook afspraken.
|
|
</p>
|
|
</div>
|
|
<div class="flex items-center space-x-2">
|
|
<?php $currentInterval = (int)($outlookSettings['sync_interval_seconds'] ?? 30); ?>
|
|
<select id="sync_interval_seconds" name="sync_interval_seconds" class="px-3 py-2 rounded-xl border border-slate-300 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-900 dark:text-white text-xs focus:ring-2 focus:ring-sky-500 focus:outline-hidden">
|
|
<option value="15" <?= $currentInterval === 15 ? 'selected' : '' ?>>Elke 15 seconden (Real-time)</option>
|
|
<option value="30" <?= $currentInterval === 30 ? 'selected' : '' ?>>Elke 30 seconden (Aanbevolen)</option>
|
|
<option value="60" <?= $currentInterval === 60 ? 'selected' : '' ?>>Elke 60 seconden (1 minuut)</option>
|
|
<option value="120" <?= $currentInterval === 120 ? 'selected' : '' ?>>Elke 2 minuten</option>
|
|
<option value="0" <?= $currentInterval === 0 ? 'selected' : '' ?>>Handmatig (en bij vensterfocus)</option>
|
|
</select>
|
|
<button type="submit" class="px-4 py-2 rounded-xl bg-sky-600 hover:bg-sky-500 text-white font-semibold text-xs transition shadow-2xs whitespace-nowrap cursor-pointer">
|
|
Opslaan
|
|
</button>
|
|
</div>
|
|
</form>
|
|
<div class="mt-4 p-3.5 rounded-xl bg-teal-50 dark:bg-teal-500/10 border border-teal-200 dark:border-teal-500/20 text-teal-800 dark:text-teal-300 text-xs flex items-center justify-between">
|
|
<div class="flex items-center space-x-2">
|
|
<i data-lucide="check-circle" class="w-4 h-4 text-teal-600 dark:text-teal-400 flex-shrink-0"></i>
|
|
<span>Uw agenda synchroniseert live. Afspraken van vandaag verschijnen direct in uw <a href="/planner.php" class="underline font-semibold text-slate-900 dark:text-white">Dagplanning</a>.</span>
|
|
</div>
|
|
<a href="/planner.php" class="px-3 py-1 rounded-lg bg-teal-500/20 hover:bg-teal-500/30 text-teal-800 dark:text-teal-200 font-semibold text-xs transition flex-shrink-0">
|
|
Naar Dagplanning →
|
|
</a>
|
|
</div>
|
|
|
|
<?php else: ?>
|
|
<div class="p-6 rounded-xl bg-slate-50/70 dark:bg-slate-900/60 border border-slate-200/80 dark:border-slate-800 text-center space-y-4">
|
|
<div class="w-14 h-14 rounded-2xl bg-sky-50 dark:bg-sky-500/10 border border-sky-200 dark:border-sky-500/20 text-sky-600 dark:text-sky-400 flex items-center justify-center mx-auto">
|
|
<i data-lucide="calendar-plus" class="w-7 h-7"></i>
|
|
</div>
|
|
<div class="max-w-md mx-auto space-y-1">
|
|
<h3 class="text-base font-bold text-slate-900 dark:text-white">Verbind uw Microsoft Outlook Agenda</h3>
|
|
<p class="text-xs text-slate-500 dark:text-slate-400 leading-relaxed">
|
|
Krijg uw dagelijkse afspraken automatisch te zien in Fociz en ontvang herinneringen vóórdat uw vergaderingen beginnen.
|
|
</p>
|
|
</div>
|
|
|
|
<?php if ($appConfig['is_configured']): ?>
|
|
<div>
|
|
<form method="POST">
|
|
<input type="hidden" name="action" value="connect_outlook">
|
|
<button type="submit" class="inline-flex items-center space-x-2 px-6 py-3 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white font-semibold text-xs transition shadow-xs cursor-pointer">
|
|
<i data-lucide="log-in" class="w-4 h-4"></i>
|
|
<span>Koppel Mijn Outlook Agenda</span>
|
|
</button>
|
|
</form>
|
|
<span class="text-[11px] text-slate-500 block mt-2">U wordt veilig doorgestuurd naar Microsoft om in te loggen</span>
|
|
</div>
|
|
<?php else: ?>
|
|
<div class="p-3.5 rounded-xl bg-amber-50 dark:bg-amber-500/10 border border-amber-200 dark:border-amber-500/30 text-amber-800 dark:text-amber-300 text-xs max-w-lg mx-auto">
|
|
<i data-lucide="alert-circle" class="w-4 h-4 text-amber-500 inline mr-1 -mt-0.5"></i>
|
|
Microsoft 365 koppeling is nog niet geconfigureerd door de beheerder.
|
|
<?php if ($userProfile['role'] === 'ADMIN'): ?>
|
|
<a href="/admin.php" class="underline font-bold text-slate-900 dark:text-white ml-1">Stel Azure in via Systeembeheer →</a>
|
|
<?php else: ?>
|
|
Vraag uw beheerder om dit in te schakelen.
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sectie 2: Mijn Atlassian Jira Koppeling -->
|
|
<div class="fociz-card p-6 sm:p-7">
|
|
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 pb-6 border-b border-slate-100 dark:border-slate-800/80">
|
|
<div class="flex items-center space-x-3.5">
|
|
<div class="w-12 h-12 rounded-2xl bg-indigo-50 dark:bg-indigo-500/10 border border-indigo-200 dark:border-indigo-500/20 flex items-center justify-center text-indigo-600 dark:text-indigo-400 font-bold shadow-xs">
|
|
<i data-lucide="check-square" class="w-6 h-6"></i>
|
|
</div>
|
|
<div>
|
|
<div class="flex items-center space-x-2">
|
|
<h2 class="text-base sm:text-lg font-bold text-slate-900 dark:text-white tracking-tight">Mijn Atlassian Jira Koppeling</h2>
|
|
<?php if ($isJiraConnected): ?>
|
|
<span class="px-2.5 py-0.5 rounded-full bg-teal-50 dark:bg-teal-500/15 border border-teal-200 dark:border-teal-500/30 text-teal-700 dark:text-teal-400 text-[10px] font-semibold">
|
|
Verbonden
|
|
</span>
|
|
<?php else: ?>
|
|
<span class="px-2.5 py-0.5 rounded-full bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400 text-[10px] font-semibold">
|
|
Niet Gekoppeld
|
|
</span>
|
|
<?php endif; ?>
|
|
</div>
|
|
<p class="text-xs text-slate-500 dark:text-slate-400">Importeer uw toegewezen Jira issues direct naar uw dagelijkse takenlijst</p>
|
|
</div>
|
|
</div>
|
|
|
|
<?php if ($isJiraConnected): ?>
|
|
<form method="POST" onsubmit="return confirm('Weet u zeker dat u Jira wilt ontkoppelen?');">
|
|
<input type="hidden" name="action" value="disconnect_jira">
|
|
<button type="submit" class="px-3 py-1.5 rounded-xl bg-rose-50 hover:bg-rose-100 dark:bg-rose-500/10 dark:hover:bg-rose-500/20 text-rose-600 dark:text-rose-400 border border-rose-200 dark:border-rose-500/20 text-xs font-medium transition cursor-pointer">
|
|
Jira Ontkoppelen
|
|
</button>
|
|
</form>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<form method="POST" class="mt-6 space-y-4">
|
|
<input type="hidden" name="action" value="save_jira">
|
|
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1.5">
|
|
Jira Cloud Domein
|
|
</label>
|
|
<input type="text" name="jira_domain" value="<?= htmlspecialchars($activeJiraDomain) ?>" placeholder="https://uwbedrijf.atlassian.net" class="w-full bg-white dark:bg-slate-900/60 border border-slate-200 dark:border-slate-800 rounded-xl px-3.5 py-2.5 text-xs text-slate-900 dark:text-white placeholder-slate-400 dark:placeholder-slate-600 focus:outline-none focus:ring-1 focus:ring-indigo-500 font-mono">
|
|
<span class="text-[10px] text-slate-500 mt-1 block">Standaard ingevuld door beheerder indien beschikbaar</span>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1.5">
|
|
Uw Jira E-mailadres
|
|
</label>
|
|
<input type="email" name="jira_email" value="<?= htmlspecialchars($jiraSettings['jira_email'] ?? $userProfile['email']) ?>" placeholder="naam@bedrijf.nl" class="w-full bg-white dark:bg-slate-900/60 border border-slate-200 dark:border-slate-800 rounded-xl px-3.5 py-2.5 text-xs text-slate-900 dark:text-white placeholder-slate-400 dark:placeholder-slate-600 focus:outline-none focus:ring-1 focus:ring-indigo-500">
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<div class="flex items-center justify-between mb-1.5">
|
|
<label class="text-xs font-semibold text-slate-700 dark:text-slate-300">
|
|
Persoonlijk Atlassian API Token
|
|
</label>
|
|
<a href="https://id.atlassian.com/manage-profile/security/api-tokens" target="_blank" rel="noopener noreferrer" class="text-[10px] text-indigo-600 dark:text-indigo-400 hover:underline flex items-center space-x-1">
|
|
<span>API Token Aanmaken</span>
|
|
<i data-lucide="external-link" class="w-3 h-3"></i>
|
|
</a>
|
|
</div>
|
|
<div class="relative">
|
|
<input type="password" id="jiraApiTokenInput" name="jira_api_token" value="<?= !empty($jiraSettings['jira_api_token']) ? '••••••••••••••••' : '' ?>" placeholder="Plak hier uw Atlassian API token..." class="w-full bg-white dark:bg-slate-900/60 border border-slate-200 dark:border-slate-800 rounded-xl pl-3.5 pr-10 py-2.5 text-xs text-slate-900 dark:text-white placeholder-slate-400 dark:placeholder-slate-600 focus:outline-none focus:ring-1 focus:ring-indigo-500">
|
|
<button type="button" onclick="toggleJiraToken()" class="absolute inset-y-0 right-0 pr-3 flex items-center text-slate-400 hover:text-slate-600 dark:hover:text-slate-300 cursor-pointer">
|
|
<i data-lucide="eye" class="w-4 h-4" id="eyeJiraIcon"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="pt-2 flex items-center justify-between">
|
|
<?php if ($isJiraConnected && !empty($jiraSettings['account_name'])): ?>
|
|
<span class="text-xs text-teal-600 dark:text-teal-400 flex items-center space-x-1.5">
|
|
<i data-lucide="check" class="w-4 h-4"></i>
|
|
<span>Verbonden als: <strong><?= htmlspecialchars($jiraSettings['account_name']) ?></strong></span>
|
|
</span>
|
|
<?php else: ?>
|
|
<span class="text-[11px] text-slate-500 dark:text-slate-400">Verbinding wordt automatisch gevalideerd bij het opslaan</span>
|
|
<?php endif; ?>
|
|
|
|
<button type="submit" class="px-5 py-2.5 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-semibold transition shadow-xs cursor-pointer">
|
|
Jira Opslaan & Testen
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Sectie 3: Mijn Meldingen & Agenda Push Notificaties -->
|
|
<div class="fociz-card p-6 sm:p-7">
|
|
<div class="flex items-center space-x-3.5 pb-6 border-b border-slate-100 dark:border-slate-800/80">
|
|
<div class="w-12 h-12 rounded-2xl bg-indigo-50 dark:bg-indigo-500/10 border border-indigo-200 dark:border-indigo-500/20 flex items-center justify-center text-indigo-600 dark:text-indigo-400 font-bold shadow-xs">
|
|
<i data-lucide="bell" class="w-6 h-6"></i>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-base sm:text-lg font-bold text-slate-900 dark:text-white tracking-tight">Mijn Meldingen & Agenda Herinneringen</h2>
|
|
<p class="text-xs text-slate-500 dark:text-slate-400">Krijg realtime meldingen vanuit uw browser zodra een afspraak nadert</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-6 space-y-5">
|
|
<!-- Browser Permissie Banner in Profiel -->
|
|
<div id="profileNotifPermGranted" class="hidden p-4 rounded-xl bg-teal-50 dark:bg-teal-500/10 border border-teal-200 dark:border-teal-500/30 text-teal-800 dark:text-teal-300 flex flex-col sm:flex-row sm:items-center justify-between gap-3">
|
|
<div class="flex items-center space-x-2.5">
|
|
<i data-lucide="check-circle" class="w-5 h-5 text-teal-600 dark:text-teal-400 flex-shrink-0"></i>
|
|
<div>
|
|
<strong class="block text-xs text-slate-900 dark:text-white">Browser Pushberichten zijn Actief</strong>
|
|
<span class="text-[11px] text-teal-700 dark:text-teal-300/80">Uw browser heeft toestemming om bureaubladmeldingen weer te geven.</span>
|
|
</div>
|
|
</div>
|
|
<button type="button" onclick="focizTestNotification()" class="px-3 py-1.5 rounded-xl bg-teal-500/20 hover:bg-teal-500/30 text-teal-800 dark:text-teal-200 font-semibold text-xs transition flex items-center space-x-1.5 flex-shrink-0 cursor-pointer">
|
|
<i data-lucide="send" class="w-3.5 h-3.5"></i>
|
|
<span>Verzend Test Melding</span>
|
|
</button>
|
|
</div>
|
|
|
|
<div id="profileNotifPermRequest" class="p-4 rounded-xl bg-indigo-50 dark:bg-indigo-500/10 border border-indigo-200 dark:border-indigo-500/30 text-indigo-800 dark:text-indigo-200 flex flex-col sm:flex-row sm:items-center justify-between gap-3">
|
|
<div class="flex items-center space-x-2.5">
|
|
<i data-lucide="bell-ring" class="w-5 h-5 text-indigo-600 dark:text-indigo-400 flex-shrink-0"></i>
|
|
<div>
|
|
<strong class="block text-xs text-slate-900 dark:text-white">Toestemming van Browser Vereist</strong>
|
|
<span class="text-[11px] text-indigo-700 dark:text-indigo-300/80">Klik op de knop om pushmeldingen in te schakelen voor Fociz.</span>
|
|
</div>
|
|
</div>
|
|
<button type="button" onclick="focizRequestPermission()" class="px-4 py-2 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white font-semibold text-xs transition shadow-xs flex-shrink-0 cursor-pointer">
|
|
Toestemming Verlenen
|
|
</button>
|
|
</div>
|
|
|
|
<div id="profileNotifPermDenied" class="hidden p-4 rounded-xl bg-rose-50 dark:bg-rose-500/10 border border-rose-200 dark:border-rose-500/30 text-rose-800 dark:text-rose-300 text-xs leading-relaxed">
|
|
<i data-lucide="alert-triangle" class="w-4 h-4 text-rose-600 dark:text-rose-400 inline mr-1 -mt-0.5"></i>
|
|
Meldingen zijn momenteel geblokkeerd in uw browser. Klik links in de adresbalk op het slotje/site-instellingen en zet "Meldingen" op "Toestaan".
|
|
</div>
|
|
|
|
<form method="POST" class="space-y-4">
|
|
<input type="hidden" name="action" value="save_notifications">
|
|
|
|
<div class="flex items-center justify-between p-4 rounded-xl bg-slate-50/70 dark:bg-slate-900/60 border border-slate-200/80 dark:border-slate-800">
|
|
<div>
|
|
<span class="text-xs font-semibold text-slate-900 dark:text-white block">Meldingen Actief</span>
|
|
<span class="text-[11px] text-slate-500 dark:text-slate-400">Automatisch waarschuwen bij naderende afspraken</span>
|
|
</div>
|
|
<label class="relative inline-flex items-center cursor-pointer">
|
|
<input type="checkbox" name="notif_enabled" <?= !empty($notifSettings['enabled']) ? 'checked' : '' ?> class="sr-only peer">
|
|
<div class="w-11 h-6 bg-slate-200 dark:bg-slate-800 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-slate-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-indigo-600"></div>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
|
<div>
|
|
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1.5">
|
|
Vooraankondiging
|
|
</label>
|
|
<select name="lead_time_minutes" class="w-full bg-white dark:bg-slate-900/60 border border-slate-200 dark:border-slate-800 rounded-xl px-3.5 py-2.5 text-xs text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500">
|
|
<option value="0" <?= (int)$notifSettings['lead_time_minutes'] === 0 ? 'selected' : '' ?>>0 minuten (Precies op aanvang)</option>
|
|
<option value="2" <?= (int)$notifSettings['lead_time_minutes'] === 2 ? 'selected' : '' ?>>2 minuten van tevoren</option>
|
|
<option value="5" <?= (int)$notifSettings['lead_time_minutes'] === 5 ? 'selected' : '' ?>>5 minuten van tevoren (Aanbevolen)</option>
|
|
<option value="10" <?= (int)$notifSettings['lead_time_minutes'] === 10 ? 'selected' : '' ?>>10 minuten van tevoren</option>
|
|
<option value="15" <?= (int)$notifSettings['lead_time_minutes'] === 15 ? 'selected' : '' ?>>15 minuten van tevoren</option>
|
|
<option value="30" <?= (int)$notifSettings['lead_time_minutes'] === 30 ? 'selected' : '' ?>>30 minuten van tevoren</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1.5">
|
|
Aantal herhalingen
|
|
</label>
|
|
<select name="repeat_count" class="w-full bg-white dark:bg-slate-900/60 border border-slate-200 dark:border-slate-800 rounded-xl px-3.5 py-2.5 text-xs text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500">
|
|
<option value="1" <?= (int)$notifSettings['repeat_count'] === 1 ? 'selected' : '' ?>>1x (Alleen eerste melding)</option>
|
|
<option value="2" <?= (int)$notifSettings['repeat_count'] === 2 ? 'selected' : '' ?>>2x herhalen (Aanbevolen)</option>
|
|
<option value="3" <?= (int)$notifSettings['repeat_count'] === 3 ? 'selected' : '' ?>>3x herhalen</option>
|
|
<option value="5" <?= (int)$notifSettings['repeat_count'] === 5 ? 'selected' : '' ?>>5x herhalen</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1.5">
|
|
Herhalingsinterval
|
|
</label>
|
|
<select name="repeat_interval_minutes" class="w-full bg-white dark:bg-slate-900/60 border border-slate-200 dark:border-slate-800 rounded-xl px-3.5 py-2.5 text-xs text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500">
|
|
<option value="1" <?= (int)$notifSettings['repeat_interval_minutes'] === 1 ? 'selected' : '' ?>>Elke 1 minuut</option>
|
|
<option value="2" <?= (int)$notifSettings['repeat_interval_minutes'] === 2 ? 'selected' : '' ?>>Elke 2 minuten</option>
|
|
<option value="3" <?= (int)$notifSettings['repeat_interval_minutes'] === 3 ? 'selected' : '' ?>>Elke 3 minuten</option>
|
|
<option value="5" <?= (int)$notifSettings['repeat_interval_minutes'] === 5 ? 'selected' : '' ?>>Elke 5 minuten</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-center justify-between p-4 rounded-xl bg-slate-50/70 dark:bg-slate-900/60 border border-slate-200/80 dark:border-slate-800">
|
|
<div class="flex items-center space-x-2.5">
|
|
<i data-lucide="volume-2" class="w-4 h-4 text-indigo-600 dark:text-indigo-400"></i>
|
|
<div>
|
|
<span class="text-xs font-semibold text-slate-900 dark:text-white block">Subtiel Geluidssignaal</span>
|
|
<span class="text-[11px] text-slate-500 dark:text-slate-400">Speel een zachte chime af bij binnenkomst van de herinnering</span>
|
|
</div>
|
|
</div>
|
|
<label class="relative inline-flex items-center cursor-pointer">
|
|
<input type="checkbox" name="sound_enabled" <?= !empty($notifSettings['sound_enabled']) ? 'checked' : '' ?> class="sr-only peer">
|
|
<div class="w-11 h-6 bg-slate-200 dark:bg-slate-800 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-slate-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-indigo-600"></div>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="flex justify-end pt-2">
|
|
<button type="submit" class="px-5 py-2.5 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-semibold transition shadow-xs cursor-pointer">
|
|
Notificatievoorkeuren Opslaan
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sectie 4: Account & Beveiliging (Wachtwoord & 2FA) -->
|
|
<div class="space-y-6">
|
|
<!-- 2FA Beveiliging Card -->
|
|
<div class="p-5 rounded-2xl bg-teal-50/70 dark:bg-teal-950/20 border border-teal-200/80 dark:border-teal-800/40 flex flex-col sm:flex-row sm:items-center justify-between gap-4 shadow-xs">
|
|
<div class="flex items-center space-x-3.5">
|
|
<div class="w-12 h-12 rounded-2xl bg-teal-500/15 border border-teal-500/30 flex items-center justify-center text-teal-700 dark:text-teal-400 font-bold shadow-xs flex-shrink-0">
|
|
<i data-lucide="shield-check" class="w-6 h-6"></i>
|
|
</div>
|
|
<div>
|
|
<div class="flex items-center space-x-2">
|
|
<h3 class="text-sm font-bold text-slate-900 dark:text-white">Tweestapsverificatie (2FA) is Actief</h3>
|
|
<span class="px-2 py-0.5 rounded-full bg-teal-500/15 text-teal-800 dark:text-teal-300 font-semibold text-[10px] border border-teal-500/20">Beveiligd</span>
|
|
</div>
|
|
<p class="text-xs text-slate-600 dark:text-slate-400 mt-0.5">
|
|
Uw Fociz account is optimaal beveiligd met een Authenticator app (TOTP) voor maximale privacy- en dataveiligheid.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="text-xs font-mono font-medium text-teal-700 dark:text-teal-400 flex items-center space-x-1.5 flex-shrink-0 self-end sm:self-center">
|
|
<i data-lucide="lock" class="w-4 h-4"></i>
|
|
<span>Verplicht & Actief</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<!-- Profielnaam formulier -->
|
|
<div class="fociz-card p-6 sm:p-7 space-y-4">
|
|
<div class="flex items-center space-x-3 pb-4 border-b border-slate-100 dark:border-slate-800/80">
|
|
<i data-lucide="user" class="w-4 h-4 text-indigo-600 dark:text-indigo-400"></i>
|
|
<h3 class="text-sm font-bold text-slate-900 dark:text-white">Persoonlijke Gegevens</h3>
|
|
</div>
|
|
|
|
<form method="POST" class="space-y-4">
|
|
<input type="hidden" name="action" value="update_profile">
|
|
|
|
<div>
|
|
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1.5">
|
|
Weergavenaam
|
|
</label>
|
|
<input type="text" name="name" required value="<?= htmlspecialchars($userProfile['name']) ?>" class="w-full bg-slate-50/70 dark:bg-[#0c0f17]/60 border border-slate-300 dark:border-slate-800 rounded-xl px-3.5 py-2.5 text-xs text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500 shadow-2xs">
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1.5">
|
|
E-mailadres
|
|
</label>
|
|
<input type="email" readonly disabled value="<?= htmlspecialchars($userProfile['email']) ?>" class="w-full bg-slate-100/70 dark:bg-slate-900/40 border border-slate-200 dark:border-slate-800/60 rounded-xl px-3.5 py-2.5 text-xs text-slate-500 cursor-not-allowed shadow-2xs">
|
|
<span class="text-[10px] text-slate-500 mt-1 block">E-mailadres is gekoppeld aan uw account en 2FA.</span>
|
|
</div>
|
|
|
|
<div class="flex justify-end pt-2">
|
|
<button type="submit" class="px-4 py-2 rounded-xl bg-slate-900 hover:bg-slate-800 text-white dark:bg-slate-800 dark:hover:bg-slate-700 dark:text-white text-xs font-semibold transition border border-slate-900 dark:border-slate-700 shadow-xs cursor-pointer">
|
|
Naam Opslaan
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Wachtwoord Wijzigen Formulier -->
|
|
<div class="fociz-card p-6 sm:p-7 space-y-4">
|
|
<div class="flex items-center space-x-3 pb-4 border-b border-slate-100 dark:border-slate-800/80">
|
|
<i data-lucide="key" class="w-4 h-4 text-indigo-600 dark:text-indigo-400"></i>
|
|
<h3 class="text-sm font-bold text-slate-900 dark:text-white">Wachtwoord Wijzigen</h3>
|
|
</div>
|
|
|
|
<form method="POST" class="space-y-3">
|
|
<input type="hidden" name="action" value="change_password">
|
|
|
|
<div>
|
|
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">
|
|
Huidig Wachtwoord
|
|
</label>
|
|
<input type="password" name="current_password" required class="w-full bg-white dark:bg-slate-900/60 border border-slate-200 dark:border-slate-800 rounded-xl px-3.5 py-2 text-xs text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500">
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">
|
|
Nieuw Wachtwoord (min. 8 tekens)
|
|
</label>
|
|
<input type="password" name="new_password" required minlength="8" class="w-full bg-white dark:bg-slate-900/60 border border-slate-200 dark:border-slate-800 rounded-xl px-3.5 py-2 text-xs text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500">
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1">
|
|
Bevestig Nieuw Wachtwoord
|
|
</label>
|
|
<input type="password" name="confirm_password" required minlength="8" class="w-full bg-white dark:bg-slate-900/60 border border-slate-200 dark:border-slate-800 rounded-xl px-3.5 py-2 text-xs text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500">
|
|
</div>
|
|
|
|
<div class="flex justify-end pt-2">
|
|
<button type="submit" class="px-4 py-2 rounded-xl bg-slate-900 hover:bg-slate-800 text-white dark:bg-slate-800 dark:hover:bg-slate-700 dark:text-white text-xs font-semibold transition border border-slate-900 dark:border-slate-700 shadow-xs cursor-pointer">
|
|
Wachtwoord Wijzigen
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// --- Thema Selector Functionaliteit ---
|
|
function selectThemePreference(theme) {
|
|
if (typeof focizSetTheme === 'function') {
|
|
focizSetTheme(theme, true);
|
|
}
|
|
updateProfileThemeUI(theme);
|
|
}
|
|
|
|
function updateProfileThemeUI(theme) {
|
|
const themes = ['dark', 'light', 'system'];
|
|
themes.forEach(t => {
|
|
const btn = document.getElementById('themeBtn-' + t);
|
|
const check = document.getElementById('themeCheck-' + t);
|
|
if (!btn || !check) return;
|
|
|
|
if (t === theme) {
|
|
btn.classList.add('ring-2', 'ring-indigo-500', 'border-indigo-500');
|
|
check.classList.remove('hidden');
|
|
check.classList.add('flex');
|
|
} else {
|
|
btn.classList.remove('ring-2', 'ring-indigo-500', 'border-indigo-500');
|
|
check.classList.add('hidden');
|
|
check.classList.remove('flex');
|
|
}
|
|
});
|
|
|
|
const badge = document.getElementById('themeStatusBadge');
|
|
if (badge) {
|
|
const label = theme.charAt(0).toUpperCase() + theme.slice(1);
|
|
badge.innerText = 'Actief: ' + label;
|
|
}
|
|
}
|
|
|
|
function toggleJiraToken() {
|
|
const input = document.getElementById('jiraApiTokenInput');
|
|
const icon = document.getElementById('eyeJiraIcon');
|
|
if (!input || !icon) return;
|
|
if (input.type === 'password') {
|
|
input.type = 'text';
|
|
icon.setAttribute('data-lucide', 'eye-off');
|
|
} else {
|
|
input.type = 'password';
|
|
icon.setAttribute('data-lucide', 'eye');
|
|
}
|
|
if (window.lucide) lucide.createIcons();
|
|
}
|
|
|
|
function previewUploadAvatar(input) {
|
|
if (input.files && input.files[0]) {
|
|
const file = input.files[0];
|
|
const nameEl = document.getElementById('uploadFileName');
|
|
if (nameEl) {
|
|
nameEl.textContent = '📄 ' + file.name + ' (' + Math.round(file.size / 1024) + ' KB)';
|
|
nameEl.classList.remove('hidden');
|
|
}
|
|
}
|
|
}
|
|
|
|
function filterAvatarCategory(category) {
|
|
const items = document.querySelectorAll('.avatar-item');
|
|
items.forEach(el => {
|
|
const cat = el.getAttribute('data-category');
|
|
if (category === 'all' || cat === category) {
|
|
el.classList.remove('hidden');
|
|
} else {
|
|
el.classList.add('hidden');
|
|
}
|
|
});
|
|
|
|
const btns = document.querySelectorAll('.av-cat-btn');
|
|
btns.forEach(btn => {
|
|
btn.className = 'av-cat-btn px-2.5 py-1 rounded-lg text-[11px] font-semibold transition cursor-pointer bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-300';
|
|
});
|
|
const activeBtn = document.getElementById('avCatBtn-' + category);
|
|
if (activeBtn) {
|
|
activeBtn.className = 'av-cat-btn px-2.5 py-1 rounded-lg text-[11px] font-bold transition cursor-pointer bg-indigo-600 text-white shadow-xs';
|
|
}
|
|
}
|
|
|
|
// Sync banner status met browser permissie & activeer actuele thema kaart
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
// Initialiseer geselecteerde thema-kaart
|
|
const activeTheme = localStorage.getItem('fociz_theme') || '<?= $userTheme ?>' || 'dark';
|
|
updateProfileThemeUI(activeTheme);
|
|
|
|
// Permissie status
|
|
if (!("Notification" in window)) return;
|
|
const grantedEl = document.getElementById('profileNotifPermGranted');
|
|
const reqEl = document.getElementById('profileNotifPermRequest');
|
|
const deniedEl = document.getElementById('profileNotifPermDenied');
|
|
|
|
if (Notification.permission === 'granted') {
|
|
if (grantedEl) grantedEl.classList.remove('hidden');
|
|
if (reqEl) reqEl.classList.add('hidden');
|
|
if (deniedEl) deniedEl.classList.add('hidden');
|
|
} else if (Notification.permission === 'denied') {
|
|
if (grantedEl) grantedEl.classList.add('hidden');
|
|
if (reqEl) reqEl.classList.add('hidden');
|
|
if (deniedEl) deniedEl.classList.remove('hidden');
|
|
} else {
|
|
if (grantedEl) grantedEl.classList.add('hidden');
|
|
if (reqEl) reqEl.classList.remove('hidden');
|
|
if (deniedEl) deniedEl.classList.add('hidden');
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|