diff --git a/backend/src/routes/auth.ts b/backend/src/routes/auth.ts index 74abc23..94fbf39 100644 --- a/backend/src/routes/auth.ts +++ b/backend/src/routes/auth.ts @@ -57,4 +57,26 @@ export async function authRoutes(app: FastifyInstance) { .get(payload.sub) return { user } }) + + const profileUpdateSchema = z.object({ + displayName: z.string().max(128).nullable().optional(), + email: z.string().email().max(256).nullable().optional(), + avatarDataUrl: z.string().max(2_000_000).nullable().optional(), + }) + + app.put('/api/auth/me', { onRequest: [app.authenticate] }, async (req, reply) => { + const payload = req.user as { sub: number; username: string } + const parsed = profileUpdateSchema.safeParse(req.body) + if (!parsed.success) { + return reply.code(400).send({ error: parsed.error.issues[0]?.message ?? 'Invalid input' }) + } + const { displayName, email, avatarDataUrl } = parsed.data + if (displayName !== undefined) db.prepare('UPDATE users SET display_name = ? WHERE id = ?').run(displayName, payload.sub) + if (email !== undefined) db.prepare('UPDATE users SET email = ? WHERE id = ?').run(email, payload.sub) + if (avatarDataUrl !== undefined) db.prepare('UPDATE users SET avatar_data_url = ? WHERE id = ?').run(avatarDataUrl, payload.sub) + const user = db + .prepare('SELECT id, username, display_name, email, avatar_data_url FROM users WHERE id = ?') + .get(payload.sub) + return { user } + }) } diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 2e72817..b2023a1 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -1,3 +1,4 @@ +import { useEffect, useState } from 'react' import { useLocation, Link } from 'react-router-dom' import { LayoutGrid, @@ -8,6 +9,7 @@ import { ChevronLeft, ChevronRight, } from 'lucide-react' +import { api, type Integration } from '../lib/api' interface SidebarProps { collapsed: boolean @@ -25,6 +27,16 @@ const navItems = [ export default function Sidebar({ collapsed, onToggle }: SidebarProps) { const width = collapsed ? 64 : 200 const location = useLocation() + const [integrations, setIntegrations] = useState(null) + + useEffect(() => { + api.listIntegrations().then(({ integrations }) => setIntegrations(integrations)) + }, []) + + const errored = integrations?.filter((i) => i.status === 'error').length ?? 0 + const statusOk = errored === 0 + const statusColor = statusOk ? '#2ECC71' : '#E74C3C' + const statusLabel = integrations === null ? 'Checking…' : statusOk ? 'All Systems Operational' : `${errored} Issue${errored > 1 ? 's' : ''} Detected` return (