credit abis
This commit is contained in:
@@ -122,6 +122,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && $user_id) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mark all notifications as read
|
||||||
|
if (isset($_POST['mark_all_read'])) {
|
||||||
|
$pdo->prepare("UPDATE notifications SET is_read=1 WHERE user_id=?")->execute([$user_id]);
|
||||||
|
header('Location: ?action=' . ($_POST['redirect_to'] ?? 'dashboard'));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
// Change password
|
// Change password
|
||||||
if (isset($_POST['change_password'])) {
|
if (isset($_POST['change_password'])) {
|
||||||
$cur = trim($_POST['current_password'] ?? '');
|
$cur = trim($_POST['current_password'] ?? '');
|
||||||
|
|||||||
@@ -30,11 +30,62 @@ a { text-decoration: none; color: var(--primary); }
|
|||||||
a:hover { text-decoration: underline; }
|
a:hover { text-decoration: underline; }
|
||||||
|
|
||||||
.app-container { display: flex; min-height: 100vh; }
|
.app-container { display: flex; min-height: 100vh; }
|
||||||
.sidebar { width: 240px; background: var(--surface); border-right: 1px solid var(--border); padding: 1.5rem; display: flex; flex-direction: column; gap: 1rem; position: sticky; top: 0; height: 100vh; overflow-y: auto; }
|
|
||||||
.sidebar h2 { font-size: 1.15rem; margin-bottom: 1.5rem; color: var(--primary); }
|
/* ── Sidebar ─────────────────────────────────────────────────────────────── */
|
||||||
.nav-link { padding: 0.6rem 0.9rem; border-radius: 0.5rem; text-decoration: none !important; color: var(--text); font-weight: 500; transition: background 0.2s; display: flex; justify-content: space-between; align-items: center; font-size: 0.9rem; }
|
.sidebar {
|
||||||
|
width: 240px; background: var(--surface); border-right: 1px solid var(--border);
|
||||||
|
padding: 1.25rem 1rem; display: flex; flex-direction: column; gap: 0.75rem;
|
||||||
|
position: sticky; top: 0; height: 100vh; overflow-y: auto; overflow-x: hidden;
|
||||||
|
transition: width 0.25s cubic-bezier(.4,0,.2,1); flex-shrink: 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.sidebar-brand {
|
||||||
|
display: flex; align-items: center; gap: 8px; font-size: 1.1rem;
|
||||||
|
font-weight: 700; color: var(--primary); margin-bottom: 0.75rem;
|
||||||
|
white-space: nowrap; overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toggle button pinned to right edge */
|
||||||
|
.sidebar-toggle-btn {
|
||||||
|
position: absolute; top: 1.5rem; right: -14px;
|
||||||
|
width: 28px; height: 28px; border-radius: 50%;
|
||||||
|
border: 1px solid var(--border); background: var(--surface);
|
||||||
|
color: var(--text-light); font-size: 0.65rem;
|
||||||
|
cursor: pointer; display: flex; align-items: center; justify-content: center;
|
||||||
|
z-index: 20; transition: background 0.15s, color 0.15s, border-color 0.15s;
|
||||||
|
box-shadow: 0 2px 6px rgba(0,0,0,0.12);
|
||||||
|
}
|
||||||
|
.sidebar-toggle-btn:hover { background: var(--primary); color: #fff; border-color: var(--primary); }
|
||||||
|
|
||||||
|
/* Nav link: icon always visible, label collapses */
|
||||||
|
.nav-link {
|
||||||
|
padding: 0.6rem 0.75rem; border-radius: 0.5rem;
|
||||||
|
text-decoration: none !important; color: var(--text); font-weight: 500;
|
||||||
|
transition: background 0.2s; display: flex; align-items: center;
|
||||||
|
gap: 0.65rem; font-size: 0.875rem; white-space: nowrap; overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
.nav-link:hover, .nav-link.active { background: rgba(var(--primary-rgb), 0.1); color: var(--primary); }
|
.nav-link:hover, .nav-link.active { background: rgba(var(--primary-rgb), 0.1); color: var(--primary); }
|
||||||
.nav-link .badge { background: #EF4444; color: white; padding: 2px 6px; border-radius: 12px; font-size: 0.75rem; }
|
.nav-icon { width: 18px; text-align: center; flex-shrink: 0; font-size: 0.95rem; }
|
||||||
|
.sidebar-label { overflow: hidden; transition: opacity 0.2s; opacity: 1; flex: 1; }
|
||||||
|
.nav-link .badge { background: #EF4444; color: white; padding: 2px 6px; border-radius: 12px; font-size: 0.72rem; margin-left: auto; }
|
||||||
|
|
||||||
|
/* ── Collapsed State ────────────────────────────────────────────────────── */
|
||||||
|
body.sidebar-collapsed .sidebar { width: 64px; }
|
||||||
|
body.sidebar-collapsed .sidebar-label { opacity: 0; width: 0; pointer-events: none; }
|
||||||
|
body.sidebar-collapsed .nav-link { justify-content: center; padding: 0.65rem; gap: 0; }
|
||||||
|
body.sidebar-collapsed .nav-link .badge { position: absolute; top: 2px; right: 2px; padding: 1px 4px; font-size: 0.6rem; }
|
||||||
|
body.sidebar-collapsed .sidebar-toggle-btn .fa-chevron-left { display: block; }
|
||||||
|
|
||||||
|
/* Tooltip on collapsed nav links */
|
||||||
|
body.sidebar-collapsed .nav-link { position: relative; }
|
||||||
|
body.sidebar-collapsed .nav-link::after {
|
||||||
|
content: attr(title); position: absolute; left: calc(100% + 12px); top: 50%;
|
||||||
|
transform: translateY(-50%); background: var(--text); color: var(--surface);
|
||||||
|
padding: 4px 10px; border-radius: 6px; font-size: 0.78rem; white-space: nowrap;
|
||||||
|
opacity: 0; pointer-events: none; transition: opacity 0.15s; z-index: 100;
|
||||||
|
}
|
||||||
|
body.sidebar-collapsed .nav-link:hover::after { opacity: 1; }
|
||||||
|
|
||||||
.main-content { flex: 1; padding: 1.5rem 2rem; overflow-y: auto; max-height: 100vh; }
|
.main-content { flex: 1; padding: 1.5rem 2rem; overflow-y: auto; max-height: 100vh; }
|
||||||
.header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; }
|
.header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; }
|
||||||
@@ -97,6 +148,16 @@ body.dark-mode .project-item:hover { background: rgba(255,255,255,0.03); }
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.user-avatar-btn:hover { border-color: var(--primary); box-shadow: 0 0 0 3px rgba(var(--primary-rgb),0.2); }
|
.user-avatar-btn:hover { border-color: var(--primary); box-shadow: 0 0 0 3px rgba(var(--primary-rgb),0.2); }
|
||||||
|
/* Notification count badge overlaid on avatar */
|
||||||
|
.avatar-notif-badge {
|
||||||
|
position: absolute; top: -3px; right: -3px;
|
||||||
|
background: #EF4444; color: #fff; font-size: 0.6rem; font-weight: 700;
|
||||||
|
min-width: 17px; height: 17px; border-radius: 10px; border: 2px solid var(--bg);
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
cursor: pointer; z-index: 10; padding: 0 3px; line-height: 1;
|
||||||
|
animation: badge-pop 0.3s cubic-bezier(.17,.67,.83,.67);
|
||||||
|
}
|
||||||
|
@keyframes badge-pop { 0%{transform:scale(0)}70%{transform:scale(1.2)}100%{transform:scale(1)} }
|
||||||
.user-dropdown-menu {
|
.user-dropdown-menu {
|
||||||
display: none; position: absolute; top: calc(100% + 8px); right: 0;
|
display: none; position: absolute; top: calc(100% + 8px); right: 0;
|
||||||
background: var(--surface); border: 1px solid var(--border); border-radius: 0.75rem;
|
background: var(--surface); border: 1px solid var(--border); border-radius: 0.75rem;
|
||||||
@@ -123,6 +184,19 @@ body.dark-mode .project-item:hover { background: rgba(255,255,255,0.03); }
|
|||||||
.user-dropdown-logout:hover { background: rgba(239,68,68,0.07) !important; }
|
.user-dropdown-logout:hover { background: rgba(239,68,68,0.07) !important; }
|
||||||
.user-dropdown-logout i { color: #EF4444 !important; }
|
.user-dropdown-logout i { color: #EF4444 !important; }
|
||||||
|
|
||||||
|
/* ── Activity log scrollbar fix ────────────────────────────────────────── */
|
||||||
|
.log-list {
|
||||||
|
display: flex; flex-direction: column; gap: 0.5rem;
|
||||||
|
overflow-y: auto; flex: 1; max-height: 360px;
|
||||||
|
padding-right: 8px; /* prevent scrollbar from eating time */
|
||||||
|
scrollbar-gutter: stable; /* modern browsers reserve scrollbar space */
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: var(--border) transparent;
|
||||||
|
}
|
||||||
|
.log-list::-webkit-scrollbar { width: 4px; }
|
||||||
|
.log-list::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
|
||||||
|
.log-list::-webkit-scrollbar-track { background: transparent; }
|
||||||
|
|
||||||
/* ── Notes / Google Keep Style ───────────────────────────────────────────── */
|
/* ── Notes / Google Keep Style ───────────────────────────────────────────── */
|
||||||
.notes-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 1rem; }
|
.notes-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 1rem; }
|
||||||
.note-card {
|
.note-card {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
// Dark Mode Toggle
|
// ── Dark Mode Toggle ──────────────────────────────────────────────────
|
||||||
const themeBtn = document.getElementById('theme-toggle');
|
const themeBtn = document.getElementById('theme-toggle');
|
||||||
const iconLight = document.getElementById('theme-icon-light');
|
const iconLight = document.getElementById('theme-icon-light');
|
||||||
const iconDark = document.getElementById('theme-icon-dark');
|
const iconDark = document.getElementById('theme-icon-dark');
|
||||||
@@ -16,7 +16,6 @@
|
|||||||
iconDark.style.display = 'block';
|
iconDark.style.display = 'block';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(themeBtn) {
|
if(themeBtn) {
|
||||||
updateThemeIcon();
|
updateThemeIcon();
|
||||||
themeBtn.addEventListener('click', () => {
|
themeBtn.addEventListener('click', () => {
|
||||||
@@ -27,7 +26,7 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toast Notifier
|
// ── Toast ─────────────────────────────────────────────────────────────
|
||||||
function showToast(msg) {
|
function showToast(msg) {
|
||||||
const t = document.getElementById('toast');
|
const t = document.getElementById('toast');
|
||||||
t.textContent = msg;
|
t.textContent = msg;
|
||||||
@@ -35,7 +34,24 @@
|
|||||||
setTimeout(() => t.classList.remove('show'), 3000);
|
setTimeout(() => t.classList.remove('show'), 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// User dropdown
|
// ── Sidebar Collapse ──────────────────────────────────────────────────
|
||||||
|
function toggleSidebar() {
|
||||||
|
const collapsed = document.body.classList.toggle('sidebar-collapsed');
|
||||||
|
localStorage.setItem('sidebar_collapsed', collapsed ? '1' : '0');
|
||||||
|
updateSidebarIcon();
|
||||||
|
}
|
||||||
|
function updateSidebarIcon() {
|
||||||
|
const icon = document.getElementById('sidebar-toggle-icon');
|
||||||
|
if(!icon) return;
|
||||||
|
if(document.body.classList.contains('sidebar-collapsed')) {
|
||||||
|
icon.className = 'fa fa-chevron-right';
|
||||||
|
} else {
|
||||||
|
icon.className = 'fa fa-chevron-left';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateSidebarIcon();
|
||||||
|
|
||||||
|
// ── User Dropdown ─────────────────────────────────────────────────────
|
||||||
function toggleUserDropdown() {
|
function toggleUserDropdown() {
|
||||||
const menu = document.getElementById('user-dropdown-menu');
|
const menu = document.getElementById('user-dropdown-menu');
|
||||||
menu.classList.toggle('open');
|
menu.classList.toggle('open');
|
||||||
@@ -49,14 +65,20 @@
|
|||||||
if (wrap && !wrap.contains(e.target)) closeUserDropdown();
|
if (wrap && !wrap.contains(e.target)) closeUserDropdown();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Close modals on overlay click
|
// ── Notifications ─────────────────────────────────────────────────────
|
||||||
|
function openNotifications() {
|
||||||
|
closeUserDropdown();
|
||||||
|
document.getElementById('notifications-modal').classList.add('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Close modals on overlay click ─────────────────────────────────────
|
||||||
document.querySelectorAll('.modal-overlay').forEach(mo => {
|
document.querySelectorAll('.modal-overlay').forEach(mo => {
|
||||||
mo.addEventListener('click', (e) => {
|
mo.addEventListener('click', (e) => {
|
||||||
if (e.target === mo) mo.classList.remove('active');
|
if (e.target === mo) mo.classList.remove('active');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Auto open change-pw modal if there was a pw msg
|
// ── Auto open change-pw modal if error ────────────────────────────────
|
||||||
<?php
|
<?php
|
||||||
$pw_msg_bottom = $_SESSION['pw_change_msg_bottom'] ?? null;
|
$pw_msg_bottom = $_SESSION['pw_change_msg_bottom'] ?? null;
|
||||||
if($pw_msg_bottom):
|
if($pw_msg_bottom):
|
||||||
|
|||||||
@@ -9,7 +9,10 @@
|
|||||||
<link rel="stylesheet" href="public/css/style.css">
|
<link rel="stylesheet" href="public/css/style.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script>if(localStorage.getItem('theme') === 'dark') document.body.classList.add('dark-mode');</script>
|
<script>
|
||||||
|
if(localStorage.getItem('theme') === 'dark') document.body.classList.add('dark-mode');
|
||||||
|
if(localStorage.getItem('sidebar_collapsed') === '1') document.body.classList.add('sidebar-collapsed');
|
||||||
|
</script>
|
||||||
<div id="toast"></div>
|
<div id="toast"></div>
|
||||||
|
|
||||||
<!-- Profile Picture Modal -->
|
<!-- Profile Picture Modal -->
|
||||||
@@ -82,41 +85,97 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="app-container">
|
<!-- Notifications Modal -->
|
||||||
<aside class="sidebar">
|
<?php
|
||||||
<h2 style="display:flex; align-items:center; gap:8px;">
|
// Fetch notifications for popup
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg>
|
$notif_list = [];
|
||||||
TaskManager
|
try {
|
||||||
</h2>
|
$stmt_n = $pdo->prepare("SELECT * FROM notifications WHERE user_id=? ORDER BY created_at DESC LIMIT 30");
|
||||||
<nav style="display:flex; flex-direction:column; gap:0.5rem;">
|
$stmt_n->execute([$user_id]);
|
||||||
<a href="?action=dashboard" class="nav-link <?= $action=='dashboard'?'active':'' ?>">
|
$notif_list = $stmt_n->fetchAll();
|
||||||
<span style="display:flex;align-items:center;gap:0.6rem;"><i class="fa fa-gauge-high" style="width:16px;"></i> Dashboard</span>
|
} catch(Exception $e) {}
|
||||||
</a>
|
?>
|
||||||
<a href="?action=tasks" class="nav-link <?= $action=='tasks'?'active':'' ?>">
|
<div class="modal-overlay" id="notifications-modal">
|
||||||
<span style="display:flex;align-items:center;gap:0.6rem;"><i class="fa fa-list-check" style="width:16px;"></i> Tasks</span>
|
<div class="modal-content" style="max-width:460px; max-height:80vh; display:flex; flex-direction:column; padding:0; overflow:hidden;">
|
||||||
</a>
|
<div style="display:flex; justify-content:space-between; align-items:center; padding:1.25rem 1.5rem; border-bottom:1px solid var(--border);">
|
||||||
<a href="?action=notes" class="nav-link <?= $action=='notes'?'active':'' ?>">
|
<h3 style="font-size:1.05rem; font-weight:700;"><i class="fa fa-bell" style="color:var(--primary);"></i> Notifications</h3>
|
||||||
<span style="display:flex;align-items:center;gap:0.6rem;"><i class="fa fa-note-sticky" style="width:16px;"></i> Notes</span>
|
<div style="display:flex; gap:0.5rem; align-items:center;">
|
||||||
</a>
|
|
||||||
<a href="?action=passwords" class="nav-link <?= $action=='passwords'?'active':'' ?>">
|
|
||||||
<span style="display:flex;align-items:center;gap:0.6rem;"><i class="fa fa-lock" style="width:16px;"></i> Passwords</span>
|
|
||||||
</a>
|
|
||||||
<a href="?action=projects" class="nav-link <?= $action=='projects'?'active':'' ?>">
|
|
||||||
<span style="display:flex;align-items:center;gap:0.6rem;"><i class="fa fa-folder-open" style="width:16px;"></i> Projects</span>
|
|
||||||
</a>
|
|
||||||
<a href="?action=notifications" class="nav-link <?= $action=='notifications'?'active':'' ?>">
|
|
||||||
<span style="display:flex;align-items:center;gap:0.6rem;"><i class="fa fa-bell" style="width:16px;"></i> Notifications</span>
|
|
||||||
<?php if($unread_notifications > 0): ?>
|
<?php if($unread_notifications > 0): ?>
|
||||||
<span class="badge"><?= $unread_notifications ?></span>
|
<form method="POST" id="mark-all-read-form" style="display:inline;">
|
||||||
|
<input type="hidden" name="mark_all_read" value="1">
|
||||||
|
<input type="hidden" name="redirect_to" value="<?= htmlspecialchars($action) ?>">
|
||||||
|
<button type="submit" class="btn btn-sm btn-secondary" style="font-size:0.75rem;">Mark all read</button>
|
||||||
|
</form>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
<button onclick="document.getElementById('notifications-modal').classList.remove('active')" style="background:none;border:none;color:var(--text-light);cursor:pointer;font-size:1.2rem;padding:4px;">×</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="overflow-y:auto; flex:1; padding:0.75rem 0;">
|
||||||
|
<?php if(empty($notif_list)): ?>
|
||||||
|
<div style="text-align:center; padding:2.5rem 1rem; color:var(--text-light);">
|
||||||
|
<i class="fa fa-bell-slash" style="font-size:2rem; display:block; margin-bottom:0.75rem;"></i>
|
||||||
|
No notifications yet.
|
||||||
|
</div>
|
||||||
|
<?php else: foreach($notif_list as $n):
|
||||||
|
$t = strtotime($n['created_at']);
|
||||||
|
$diff = time()-$t;
|
||||||
|
$ago = $diff<60?'just now':($diff<3600?floor($diff/60).'m ago':($diff<86400?floor($diff/3600).'h ago':floor($diff/86400).'d ago'));
|
||||||
|
?>
|
||||||
|
<div style="display:flex; align-items:flex-start; gap:0.75rem; padding:0.65rem 1.5rem; border-bottom:1px solid var(--border); transition:background 0.15s; <?= !$n['is_read']?'background:rgba(var(--primary-rgb),0.04);':'' ?>">
|
||||||
|
<div style="width:8px; height:8px; border-radius:50%; background:<?= !$n['is_read']?'var(--primary)':'transparent' ?>; margin-top:6px; flex-shrink:0;"></div>
|
||||||
|
<div style="flex:1; min-width:0;">
|
||||||
|
<div style="font-size:0.875rem; color:var(--text); <?= !$n['is_read']?'font-weight:500;':'' ?>"><?= htmlspecialchars($n['message']) ?></div>
|
||||||
|
<div style="font-size:0.72rem; color:var(--text-light); margin-top:3px;"><?= $ago ?></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="app-container">
|
||||||
|
<!-- Sidebar -->
|
||||||
|
<aside class="sidebar" id="sidebar">
|
||||||
|
<!-- Toggle button on the edge -->
|
||||||
|
<button class="sidebar-toggle-btn" id="sidebar-toggle-btn" onclick="toggleSidebar()" title="Collapse / Expand">
|
||||||
|
<i class="fa fa-chevron-left" id="sidebar-toggle-icon"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<h2 class="sidebar-brand">
|
||||||
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg>
|
||||||
|
<span class="sidebar-label sidebar-brand-text">TaskManager</span>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<nav style="display:flex; flex-direction:column; gap:0.25rem;">
|
||||||
|
<a href="?action=dashboard" class="nav-link <?= $action=='dashboard'?'active':'' ?>" title="Dashboard">
|
||||||
|
<i class="fa fa-gauge-high nav-icon"></i>
|
||||||
|
<span class="sidebar-label">Dashboard</span>
|
||||||
|
</a>
|
||||||
|
<a href="?action=tasks" class="nav-link <?= $action=='tasks'?'active':'' ?>" title="Tasks">
|
||||||
|
<i class="fa fa-list-check nav-icon"></i>
|
||||||
|
<span class="sidebar-label">Tasks</span>
|
||||||
|
</a>
|
||||||
|
<a href="?action=notes" class="nav-link <?= $action=='notes'?'active':'' ?>" title="Notes">
|
||||||
|
<i class="fa fa-note-sticky nav-icon"></i>
|
||||||
|
<span class="sidebar-label">Notes</span>
|
||||||
|
</a>
|
||||||
|
<a href="?action=passwords" class="nav-link <?= $action=='passwords'?'active':'' ?>" title="Passwords">
|
||||||
|
<i class="fa fa-lock nav-icon"></i>
|
||||||
|
<span class="sidebar-label">Passwords</span>
|
||||||
|
</a>
|
||||||
|
<a href="?action=projects" class="nav-link <?= $action=='projects'?'active':'' ?>" title="Projects">
|
||||||
|
<i class="fa fa-folder-open nav-icon"></i>
|
||||||
|
<span class="sidebar-label">Projects</span>
|
||||||
</a>
|
</a>
|
||||||
<?php if(!empty($_SESSION['is_admin'])): ?>
|
<?php if(!empty($_SESSION['is_admin'])): ?>
|
||||||
<a href="?action=admin" class="nav-link <?= $action=='admin'?'active':'' ?>">
|
<a href="?action=admin" class="nav-link <?= $action=='admin'?'active':'' ?>" title="Admin Panel">
|
||||||
<span style="display:flex;align-items:center;gap:0.6rem;"><i class="fa fa-shield-halved" style="width:16px;"></i> Admin Panel</span>
|
<i class="fa fa-shield-halved nav-icon"></i>
|
||||||
|
<span class="sidebar-label">Admin Panel</span>
|
||||||
</a>
|
</a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</nav>
|
</nav>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<main class="main-content">
|
<main class="main-content">
|
||||||
<!-- Top Bar -->
|
<!-- Top Bar -->
|
||||||
<div style="display:flex; justify-content:flex-end; align-items:center; gap:0.75rem; margin-bottom:1.5rem;">
|
<div style="display:flex; justify-content:flex-end; align-items:center; gap:0.75rem; margin-bottom:1.5rem;">
|
||||||
@@ -128,6 +187,8 @@
|
|||||||
|
|
||||||
<!-- User Avatar Dropdown -->
|
<!-- User Avatar Dropdown -->
|
||||||
<div class="user-dropdown-wrap" id="user-dropdown-wrap">
|
<div class="user-dropdown-wrap" id="user-dropdown-wrap">
|
||||||
|
<!-- Avatar with notification badge -->
|
||||||
|
<div style="position:relative; display:inline-flex;">
|
||||||
<button class="user-avatar-btn" id="user-avatar-btn" onclick="toggleUserDropdown()" title="<?= htmlspecialchars($current_user['username']) ?>">
|
<button class="user-avatar-btn" id="user-avatar-btn" onclick="toggleUserDropdown()" title="<?= htmlspecialchars($current_user['username']) ?>">
|
||||||
<?php
|
<?php
|
||||||
try { $pdo->exec("ALTER TABLE users ADD COLUMN profile_pic TEXT DEFAULT NULL"); } catch(Exception $e) {}
|
try { $pdo->exec("ALTER TABLE users ADD COLUMN profile_pic TEXT DEFAULT NULL"); } catch(Exception $e) {}
|
||||||
@@ -139,6 +200,11 @@
|
|||||||
<?= strtoupper(mb_substr($current_user['username'],0,1)) ?>
|
<?= strtoupper(mb_substr($current_user['username'],0,1)) ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</button>
|
</button>
|
||||||
|
<?php if($unread_notifications > 0): ?>
|
||||||
|
<span class="avatar-notif-badge" onclick="openNotifications()"><?= $unread_notifications > 9 ? '9+' : $unread_notifications ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="user-dropdown-menu" id="user-dropdown-menu">
|
<div class="user-dropdown-menu" id="user-dropdown-menu">
|
||||||
<div class="user-dropdown-header">
|
<div class="user-dropdown-header">
|
||||||
<div class="user-dropdown-name"><?= htmlspecialchars($current_user['username']) ?></div>
|
<div class="user-dropdown-name"><?= htmlspecialchars($current_user['username']) ?></div>
|
||||||
@@ -149,6 +215,14 @@
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<div class="user-dropdown-divider"></div>
|
<div class="user-dropdown-divider"></div>
|
||||||
|
<button class="user-dropdown-item" onclick="openNotifications()">
|
||||||
|
<i class="fa fa-bell"></i>
|
||||||
|
Notifications
|
||||||
|
<?php if($unread_notifications > 0): ?>
|
||||||
|
<span class="badge" style="margin-left:auto; font-size:0.65rem;"><?= $unread_notifications ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</button>
|
||||||
|
<div class="user-dropdown-divider"></div>
|
||||||
<button class="user-dropdown-item" onclick="document.getElementById('profile-pic-modal').classList.add('active'); closeUserDropdown();">
|
<button class="user-dropdown-item" onclick="document.getElementById('profile-pic-modal').classList.add('active'); closeUserDropdown();">
|
||||||
<i class="fa fa-image"></i> Set Profile Picture
|
<i class="fa fa-image"></i> Set Profile Picture
|
||||||
</button>
|
</button>
|
||||||
@@ -162,8 +236,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<?php if (isset($pw_msg) && $pw_msg === 'success'): ?>
|
<?php if (isset($pw_msg) && $pw_msg): ?>
|
||||||
<script>document.addEventListener('DOMContentLoaded',()=>{ document.getElementById('change-pw-modal').classList.add('active'); });</script>
|
|
||||||
<?php elseif(isset($pw_msg) && $pw_msg): ?>
|
|
||||||
<script>document.addEventListener('DOMContentLoaded',()=>{ document.getElementById('change-pw-modal').classList.add('active'); });</script>
|
<script>document.addEventListener('DOMContentLoaded',()=>{ document.getElementById('change-pw-modal').classList.add('active'); });</script>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|||||||
@@ -112,22 +112,25 @@ $notes = $stmt->fetchAll();
|
|||||||
$all_users = query("SELECT id, username FROM users WHERE id != ?", [$user_id])->fetchAll();
|
$all_users = query("SELECT id, username FROM users WHERE id != ?", [$user_id])->fetchAll();
|
||||||
$all_groups = query("SELECT * FROM groups")->fetchAll();
|
$all_groups = query("SELECT * FROM groups")->fetchAll();
|
||||||
|
|
||||||
// Define note colors
|
// Define note colors — Google Keep inspired
|
||||||
$note_colors = [
|
$note_colors = [
|
||||||
null => ['bg'=>'var(--surface)', 'label'=>'Default'],
|
'' => ['bg'=>'var(--surface)', 'label'=>'Default', 'hex'=>'#ffffff'],
|
||||||
'#fff9c4' => ['bg'=>'#fff9c4', 'label'=>'Yellow'],
|
'#fff9c4' => ['bg'=>'#fff9c4', 'label'=>'Yellow', 'hex'=>'#fff9c4'],
|
||||||
'#d4edda' => ['bg'=>'#d4edda', 'label'=>'Green'],
|
'#ccff90' => ['bg'=>'#ccff90', 'label'=>'Lime', 'hex'=>'#ccff90'],
|
||||||
'#cce5ff' => ['bg'=>'#cce5ff', 'label'=>'Blue'],
|
'#d4edda' => ['bg'=>'#d4edda', 'label'=>'Sage', 'hex'=>'#d4edda'],
|
||||||
'#f8d7da' => ['bg'=>'#f8d7da', 'label'=>'Pink'],
|
'#80cbc4' => ['bg'=>'#80cbc4', 'label'=>'Teal', 'hex'=>'#80cbc4'],
|
||||||
'#e2d9f3' => ['bg'=>'#e2d9f3', 'label'=>'Purple'],
|
'#b3e5fc' => ['bg'=>'#b3e5fc', 'label'=>'Sky', 'hex'=>'#b3e5fc'],
|
||||||
'#fde8d8' => ['bg'=>'#fde8d8', 'label'=>'Orange'],
|
'#cce5ff' => ['bg'=>'#cce5ff', 'label'=>'Blue', 'hex'=>'#cce5ff'],
|
||||||
'#f5f5f5' => ['bg'=>'#f5f5f5', 'label'=>'Gray'],
|
'#e2d9f3' => ['bg'=>'#e2d9f3', 'label'=>'Lavender', 'hex'=>'#e2d9f3'],
|
||||||
|
'#f8d7da' => ['bg'=>'#f8d7da', 'label'=>'Rose', 'hex'=>'#f8d7da'],
|
||||||
|
'#ef9a9a' => ['bg'=>'#ef9a9a', 'label'=>'Red', 'hex'=>'#ef9a9a'],
|
||||||
|
'#ffcc80' => ['bg'=>'#ffcc80', 'label'=>'Peach', 'hex'=>'#ffcc80'],
|
||||||
|
'#fde8d8' => ['bg'=>'#fde8d8', 'label'=>'Orange', 'hex'=>'#fde8d8'],
|
||||||
|
'#d7ccc8' => ['bg'=>'#d7ccc8', 'label'=>'Sand', 'hex'=>'#d7ccc8'],
|
||||||
];
|
];
|
||||||
// dark mode color overrides handled via JS
|
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="header" style="margin-bottom:1rem;">
|
<div style="display:flex; justify-content:flex-end; align-items:center; margin-bottom:1rem;">
|
||||||
<h1 style="font-size:1.5rem; font-weight:700;">📝 Notes</h1>
|
|
||||||
<button class="btn" onclick="document.getElementById('new-note-modal').classList.add('active')">
|
<button class="btn" onclick="document.getElementById('new-note-modal').classList.add('active')">
|
||||||
<i class="fa fa-plus"></i> New Note
|
<i class="fa fa-plus"></i> New Note
|
||||||
</button>
|
</button>
|
||||||
@@ -135,8 +138,11 @@ $note_colors = [
|
|||||||
|
|
||||||
<!-- ── New Note Modal ─────────────────────────────────────────────────────── -->
|
<!-- ── New Note Modal ─────────────────────────────────────────────────────── -->
|
||||||
<div class="modal-overlay" id="new-note-modal">
|
<div class="modal-overlay" id="new-note-modal">
|
||||||
<div class="modal-content" style="max-width:600px; max-height:90vh; overflow-y:auto;">
|
<div class="modal-content" style="max-width:580px; max-height:92vh; overflow-y:auto; padding:1.5rem;">
|
||||||
<h3 style="margin-bottom:1rem;">New Note</h3>
|
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:1rem;">
|
||||||
|
<h3 style="font-size:1rem; font-weight:700;">New Note</h3>
|
||||||
|
<button type="button" style="background:none;border:none;color:var(--text-light);cursor:pointer;font-size:1.2rem;" onclick="document.getElementById('new-note-modal').classList.remove('active')">×</button>
|
||||||
|
</div>
|
||||||
<form method="POST" enctype="multipart/form-data" id="new-note-form">
|
<form method="POST" enctype="multipart/form-data" id="new-note-form">
|
||||||
<input type="hidden" name="create_note" value="1">
|
<input type="hidden" name="create_note" value="1">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|||||||
@@ -44,6 +44,13 @@ if($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unshare a specific share record
|
||||||
|
if(isset($_POST['remove_password_share'])) {
|
||||||
|
$share_id = (int)$_POST['share_id'];
|
||||||
|
$chk = query("SELECT ps.id FROM password_shares ps JOIN passwords p ON ps.password_id=p.id WHERE ps.id=? AND p.owner_id=?", [$share_id, $user_id])->fetch();
|
||||||
|
if($chk) query("DELETE FROM password_shares WHERE id=?", [$share_id]);
|
||||||
|
}
|
||||||
|
|
||||||
if(isset($_POST['bulk_action']) && isset($_POST['selected_passwords'])) {
|
if(isset($_POST['bulk_action']) && isset($_POST['selected_passwords'])) {
|
||||||
$selected = $_POST['selected_passwords'];
|
$selected = $_POST['selected_passwords'];
|
||||||
$action = $_POST['bulk_action'];
|
$action = $_POST['bulk_action'];
|
||||||
@@ -187,7 +194,12 @@ $active_folder = $_GET['vault_folder'] ?? 'all';
|
|||||||
<label>Notes</label>
|
<label>Notes</label>
|
||||||
<textarea name="notes" id="pw-edit-notes" class="form-control" rows="2" placeholder="Optional notes..."></textarea>
|
<textarea name="notes" id="pw-edit-notes" class="form-control" rows="2" placeholder="Optional notes..."></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div style="display:flex; justify-content:space-between; gap:0.5rem; flex-wrap:wrap; margin-top:0.5rem;">
|
<!-- Shares management section (populated by JS) -->
|
||||||
|
<div id="pw-shares-section" style="display:none; border-top:1px solid var(--border); padding-top:0.75rem; margin-top:0.5rem;">
|
||||||
|
<label style="font-size:0.8rem; color:var(--text-light); display:block; margin-bottom:0.4rem;">Shared with</label>
|
||||||
|
<div id="pw-shares-list" style="display:flex; flex-wrap:wrap; gap:0.35rem;"></div>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex; justify-content:space-between; gap:0.5rem; flex-wrap:wrap; margin-top:0.75rem;">
|
||||||
<div id="pw-edit-delete-wrap" style="display:none;">
|
<div id="pw-edit-delete-wrap" style="display:none;">
|
||||||
<button type="button" class="btn btn-danger btn-sm" onclick="deletePassword()"><i class="fa fa-trash"></i> Delete</button>
|
<button type="button" class="btn btn-danger btn-sm" onclick="deletePassword()"><i class="fa fa-trash"></i> Delete</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -206,13 +218,10 @@ $active_folder = $_GET['vault_folder'] ?? 'all';
|
|||||||
<input type="hidden" name="password_id" id="delete-pw-id">
|
<input type="hidden" name="password_id" id="delete-pw-id">
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="header" style="margin-bottom:1rem;">
|
<div style="display:flex; justify-content:flex-end; gap:0.5rem; flex-wrap:wrap; margin-bottom:1rem;">
|
||||||
<h1 style="font-size:1.5rem; font-weight:700;">🔐 Password Vault</h1>
|
|
||||||
<div style="display:flex; gap:0.5rem; flex-wrap:wrap;">
|
|
||||||
<button class="btn btn-secondary" onclick="showAddFolderPrompt()"><i class="fa fa-folder-plus"></i> New Folder</button>
|
<button class="btn btn-secondary" onclick="showAddFolderPrompt()"><i class="fa fa-folder-plus"></i> New Folder</button>
|
||||||
<button class="btn" onclick="openAddPassword()"><i class="fa fa-plus"></i> Add Entry</button>
|
<button class="btn" onclick="openAddPassword()"><i class="fa fa-plus"></i> Add Entry</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Add folder form (hidden) -->
|
<!-- Add folder form (hidden) -->
|
||||||
<form method="POST" id="add-folder-form" style="display:none;">
|
<form method="POST" id="add-folder-form" style="display:none;">
|
||||||
@@ -224,8 +233,8 @@ $active_folder = $_GET['vault_folder'] ?? 'all';
|
|||||||
<input type="hidden" name="folder_id" id="del-folder-id">
|
<input type="hidden" name="folder_id" id="del-folder-id">
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="card" style="padding:0; overflow:hidden;">
|
<div style="background:var(--surface); border-radius:1rem; box-shadow:0 4px 6px -1px rgba(0,0,0,0.07); border:1px solid var(--border); overflow:hidden;">
|
||||||
<div class="vault-layout">
|
<div class="vault-layout" style="min-height:calc(100vh - 200px);">
|
||||||
<!-- Vault Sidebar (Folders) -->
|
<!-- Vault Sidebar (Folders) -->
|
||||||
<div class="vault-sidebar">
|
<div class="vault-sidebar">
|
||||||
<div style="font-size:0.7rem; text-transform:uppercase; letter-spacing:0.06em; color:var(--text-light); font-weight:700; padding:0.25rem 0.65rem; margin-bottom:0.25rem;">Folders</div>
|
<div style="font-size:0.7rem; text-transform:uppercase; letter-spacing:0.06em; color:var(--text-light); font-weight:700; padding:0.25rem 0.65rem; margin-bottom:0.25rem;">Folders</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user