2. Notifications System (the bell 🔔)

Let’s scaffold it clean + simple so you can expand later.

Database table
CREATE TABLE IF NOT EXISTS notifications (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  user_id INTEGER,
  message TEXT,
  type TEXT, -- e.g. "system", "billing", "project"
  read INT DEFAULT 0,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

Backend routes
// server/routes/notifications.ts
import { Router } from "express";
import { requireAuth, AuthedRequest } from "../auth/middleware";
import { db } from "../db";

const router = Router();

router.get("/api/notifications", requireAuth, (req: AuthedRequest, res) => {
  const rows = db.prepare("SELECT * FROM notifications WHERE user_id=? ORDER BY created_at DESC LIMIT 50")
                 .all(req.user!.id);
  res.json({ notifications: rows });
});

router.post("/api/notifications/read", requireAuth, (req: AuthedRequest, res) => {
  const { id } = req.body;
  db.prepare("UPDATE notifications SET read=1 WHERE id=? AND user_id=?").run(id, req.user!.id);
  res.json({ ok: true });
});

export default router;

Frontend: bell + dropdown
// src/components/NotificationsBell.tsx
import { useEffect, useState } from "react";

export default function NotificationsBell() {
  const [open, setOpen] = useState(false);
  const [items, setItems] = useState<any[]>([]);

  useEffect(() => {
    if (open) {
      fetch("/api/notifications", { credentials: "include" })
        .then(r=>r.json()).then(d=>setItems(d.notifications || []));
    }
  }, [open]);

  return (
    <div className="relative">
      <button className="relative" onClick={()=>setOpen(!open)}>
        <span className="material-icons">notifications</span>
        {items.some(n=>!n.read) && (
          <span className="absolute -top-1 -right-1 w-3 h-3 bg-red-500 rounded-full"></span>
        )}
      </button>
      {open && (
        <div className="absolute right-0 mt-2 w-80 bg-white border rounded-xl shadow-lg z-50 p-2 max-h-96 overflow-auto">
          {items.length ? items.map(n=>(
            <div key={n.id} className={`p-2 rounded-xl mb-1 ${n.read ? "bg-gray-50" : "bg-green-50"}`}>
              <div className="text-sm">{n.message}</div>
              <div className="text-xs text-gray-500">{new Date(n.created_at).toLocaleString()}</div>
            </div>
          )) : <div className="text-sm text-gray-500 p-4">No notifications yet</div>}
        </div>
      )}
    </div>
  );
}

Preferences (hook into Settings)

In Settings (gear page), expose toggles for:

✅ System updates

✅ Billing alerts

✅ Project activity

<label className="flex items-center gap-3">
  <input type="checkbox" checked={system} onChange={e=>setSystem(e.target.checked)} />
  System updates
</label>
<label className="flex items-center gap-3">
  <input type="checkbox" checked={billing} onChange={e=>setBilling(e.target.checked)} />
  Billing alerts
</label>
<label className="flex items-center gap-3">
  <input type="checkbox" checked={project} onChange={e=>setProject(e.target.checked)} />
  Project activity
</label>


⚡ Flow:

System inserts notifications into DB (e.g., “Your Pro plan is active!”).

Bell shows unread count badge.

Clicking bell opens dropdown → table.

Settings page lets them toggle categories (Phase 2 expansion).