fix(ui): wire and restyle dashboard notifications dropdown
This commit is contained in:
parent
1effa1cb6f
commit
d7ddd3d9e0
2 changed files with 42 additions and 79 deletions
|
|
@ -3,8 +3,9 @@
|
|||
* Used for pages that need actual backend connectivity
|
||||
* (My Profile, My Portfolio, Verification) instead of the preview mock.
|
||||
*/
|
||||
import { For, JSX, Show, createMemo, createSignal, onMount } from "solid-js";
|
||||
import { For, JSX, createMemo } from "solid-js";
|
||||
import { AiChatWidget } from "./AiChatWidget";
|
||||
import NotificationBell from "./NotificationBell";
|
||||
import {
|
||||
User,
|
||||
Briefcase,
|
||||
|
|
@ -101,36 +102,6 @@ export default function DashboardShell(props: Props) {
|
|||
return k.charAt(0).toUpperCase() + k.slice(1).toLowerCase();
|
||||
});
|
||||
|
||||
const [unreadCount, setUnreadCount] = createSignal(0);
|
||||
|
||||
// Fetch unread notification count
|
||||
const fetchUnreadCount = async () => {
|
||||
try {
|
||||
const token =
|
||||
typeof window !== "undefined"
|
||||
? window.sessionStorage.getItem("nxtgauge_access_token") || ""
|
||||
: "";
|
||||
if (!token) return;
|
||||
const res = await fetch("/api/me/notifications/unread-count", {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
credentials: "include",
|
||||
});
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
setUnreadCount(data.unread_count || 0);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to fetch unread count:", e);
|
||||
}
|
||||
};
|
||||
|
||||
// Start polling on mount
|
||||
onMount(() => {
|
||||
fetchUnreadCount();
|
||||
const interval = setInterval(fetchUnreadCount, 30000);
|
||||
return () => clearInterval(interval);
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
|
|
@ -266,35 +237,7 @@ export default function DashboardShell(props: Props) {
|
|||
{titleCase(props.activeSidebar)}
|
||||
</p>
|
||||
<div style={{ display: "flex", "align-items": "center", gap: "12px" }}>
|
||||
<button
|
||||
type="button"
|
||||
style={{
|
||||
position: "relative",
|
||||
border: "none",
|
||||
background: "transparent",
|
||||
cursor: "pointer",
|
||||
display: "flex",
|
||||
"align-items": "center",
|
||||
"justify-content": "center",
|
||||
padding: 0,
|
||||
}}
|
||||
>
|
||||
<Bell size={18} style={{ color: "#9CA3AF" }} />
|
||||
<Show when={unreadCount() > 0}>
|
||||
<span
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "-2px",
|
||||
right: "-2px",
|
||||
width: "8px",
|
||||
height: "8px",
|
||||
background: "#FF5E13",
|
||||
"border-radius": "50%",
|
||||
border: "1px solid white",
|
||||
}}
|
||||
></span>
|
||||
</Show>
|
||||
</button>
|
||||
<NotificationBell />
|
||||
<div
|
||||
style={{
|
||||
width: "32px",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import { createSignal, createEffect, onCleanup, Show } from "solid-js";
|
||||
import { api } from "~/lib/api";
|
||||
|
||||
const ORANGE = "#FF5E13";
|
||||
const NAVY = "#0D0D2A";
|
||||
|
||||
export default function NotificationBell() {
|
||||
const [unreadCount, setUnreadCount] = createSignal(0);
|
||||
const [showDropdown, setShowDropdown] = createSignal(false);
|
||||
|
|
@ -84,7 +87,7 @@ export default function NotificationBell() {
|
|||
<div class="relative">
|
||||
<button
|
||||
onClick={toggleDropdown}
|
||||
class="relative p-2 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-full transition-colors"
|
||||
class="relative h-8 w-8 rounded-full border border-[#E5E7EB] bg-white text-[#6B7280] hover:text-[#111827] hover:border-[#D1D5DB] transition-colors flex items-center justify-center"
|
||||
aria-label="Notifications"
|
||||
>
|
||||
<svg
|
||||
|
|
@ -104,7 +107,10 @@ export default function NotificationBell() {
|
|||
|
||||
{/* Unread Badge */}
|
||||
<Show when={unreadCount() > 0}>
|
||||
<span class="absolute top-0 right-0 inline-flex items-center justify-center px-2 py-1 text-xs font-bold leading-none text-white transform translate-x-1/4 -translate-y-1/4 bg-orange-500 rounded-full">
|
||||
<span
|
||||
class="absolute -top-1 -right-1 min-w-[16px] h-4 px-1 inline-flex items-center justify-center text-[10px] font-bold leading-none text-white rounded-full"
|
||||
style={{ background: ORANGE }}
|
||||
>
|
||||
{unreadCount() > 99 ? "99+" : unreadCount()}
|
||||
</span>
|
||||
</Show>
|
||||
|
|
@ -117,33 +123,46 @@ export default function NotificationBell() {
|
|||
<div class="fixed inset-0 z-40" onClick={() => setShowDropdown(false)} />
|
||||
|
||||
{/* Dropdown Panel */}
|
||||
<div class="absolute right-0 mt-2 w-80 bg-white rounded-xl shadow-lg border z-50 overflow-hidden">
|
||||
<div class="flex justify-between items-center p-4 border-b">
|
||||
<h3 class="font-semibold">Notifications</h3>
|
||||
<div class="absolute right-0 mt-2 w-[360px] max-w-[calc(100vw-24px)] bg-white rounded-2xl border border-[#E5E7EB] shadow-[0_10px_30px_rgba(2,6,23,0.08)] z-50 overflow-hidden">
|
||||
<div class="flex justify-between items-center px-4 py-3 border-b border-[#E5E7EB] bg-[#FCFCFD]">
|
||||
<div class="flex items-center gap-2">
|
||||
<h3 class="text-sm font-semibold" style={{ color: NAVY }}>Notifications</h3>
|
||||
<Show when={unreadCount() > 0}>
|
||||
<span class="text-[11px] font-semibold px-2 py-0.5 rounded-full bg-[#FFF3EE] text-[#C2410C]">
|
||||
{unreadCount()} unread
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
<Show when={unreadCount() > 0}>
|
||||
<button
|
||||
onClick={markAllAsRead}
|
||||
class="text-sm text-orange-600 hover:text-orange-700"
|
||||
class="text-xs font-semibold hover:opacity-90"
|
||||
style={{ color: ORANGE }}
|
||||
>
|
||||
Mark all read
|
||||
</button>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<div class="max-h-96 overflow-y-auto">
|
||||
<div class="max-h-[420px] overflow-y-auto">
|
||||
<Show
|
||||
when={notifications().length > 0}
|
||||
fallback={
|
||||
<div class="p-8 text-center text-gray-500">
|
||||
<p class="text-4xl mb-2">🔔</p>
|
||||
<p>No notifications yet</p>
|
||||
<div class="px-6 py-10 text-center">
|
||||
<div class="mx-auto mb-3 w-10 h-10 rounded-full bg-[#F3F4F6] flex items-center justify-center text-[#9CA3AF]">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width={1.8} d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
|
||||
</svg>
|
||||
</div>
|
||||
<p class="text-sm font-semibold text-[#374151]">No notifications yet</p>
|
||||
<p class="text-xs text-[#6B7280] mt-1">We will show updates here when they arrive.</p>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{notifications().map((notification) => (
|
||||
<div
|
||||
class={`p-4 border-b hover:bg-gray-50 cursor-pointer transition-colors ${
|
||||
!notification.is_read ? "bg-orange-50" : ""
|
||||
class={`px-4 py-3 border-b border-[#F1F5F9] hover:bg-[#F8FAFC] cursor-pointer transition-colors ${
|
||||
!notification.is_read ? "bg-[#FFF7ED]" : "bg-white"
|
||||
}`}
|
||||
onClick={() => {
|
||||
if (!notification.is_read) {
|
||||
|
|
@ -153,20 +172,20 @@ export default function NotificationBell() {
|
|||
>
|
||||
<div class="flex items-start gap-3">
|
||||
{/* Unread Dot */}
|
||||
<div class="mt-1.5">
|
||||
<div class="mt-1.5 shrink-0">
|
||||
<div
|
||||
class={`w-2 h-2 rounded-full ${
|
||||
!notification.is_read ? "bg-orange-500" : "bg-transparent"
|
||||
!notification.is_read ? "bg-[#F97316]" : "bg-transparent"
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="font-medium text-sm text-gray-900 line-clamp-1">
|
||||
<p class="font-semibold text-sm text-[#111827] line-clamp-1">
|
||||
{notification.title}
|
||||
</p>
|
||||
<p class="text-sm text-gray-600 line-clamp-2 mt-0.5">{notification.body}</p>
|
||||
<p class="text-xs text-gray-400 mt-1">
|
||||
<p class="text-sm text-[#4B5563] line-clamp-2 mt-0.5">{notification.body}</p>
|
||||
<p class="text-[11px] text-[#9CA3AF] mt-1.5">
|
||||
{formatTime(notification.created_at)}
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -176,10 +195,11 @@ export default function NotificationBell() {
|
|||
</Show>
|
||||
</div>
|
||||
|
||||
<div class="p-3 border-t bg-gray-50">
|
||||
<div class="p-3 border-t border-[#E5E7EB] bg-[#FCFCFD]">
|
||||
<a
|
||||
href="/dashboard/notifications"
|
||||
class="block text-center text-sm text-orange-600 hover:text-orange-700 font-medium"
|
||||
class="block text-center text-sm font-semibold hover:opacity-90"
|
||||
style={{ color: ORANGE }}
|
||||
onClick={() => setShowDropdown(false)}
|
||||
>
|
||||
View all notifications
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue