credit abis

This commit is contained in:
root
2026-03-27 00:14:49 +07:00
parent f89fcbee1c
commit 937033ea07
6 changed files with 265 additions and 75 deletions

View File

@@ -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'] ?? '');

View File

@@ -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 {

View File

@@ -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):

View File

@@ -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; ?>

View File

@@ -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">

View File

@@ -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,12 +218,9 @@ $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) -->
@@ -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>