feat: wire frontend pages to tRPC APIs
- Add hooks (useAuth, useSubscription, useNotifications) for real API data - Add auth service (login/signup) with password hashing and session support - Replace stub auth with real tRPC calls in login/signup/onboarding pages - Replace mock dashboard data with real API data from hooks - Create service pages: DarkWatch, VoicePrint, SpamShield, HomeTitle, RemoveBrokers, Settings - Update Navbar, TopBar, Sidebar with real user data and correct routes - Add passwordHash field to users schema for credential auth - Fix tests to work with real hooks (mock tRPC/hooks)
This commit is contained in:
103
web/src/routes/(webapp)/darkwatch.tsx
Normal file
103
web/src/routes/(webapp)/darkwatch.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
import { createSignal, createResource, For, Show } from "solid-js";
|
||||
import { Title } from "@solidjs/meta";
|
||||
import { Sidebar, TopBar } from "~/components/dashboard";
|
||||
import { Button, Input, Card, Badge } from "~/components/ui";
|
||||
import { api } from "~/lib/api";
|
||||
|
||||
export default function DarkWatchPage() {
|
||||
const [sidebarOpen, setSidebarOpen] = createSignal(false);
|
||||
const [itemValue, setItemValue] = createSignal("");
|
||||
const [watchlist, { refetch: refetchWatchlist }] = createResource(
|
||||
() => api.darkwatch.getWatchlist.query(),
|
||||
{ initialValue: [] },
|
||||
);
|
||||
const [exposures] = createResource(
|
||||
() => api.darkwatch.getExposures.query({ page: 1, limit: 20 }),
|
||||
);
|
||||
|
||||
async function addItem() {
|
||||
const val = itemValue().trim();
|
||||
if (!val) return;
|
||||
const type = val.includes("@") ? "EMAIL" : "PHONE";
|
||||
await api.darkwatch.addWatchlistItem.mutate({ type, value: val });
|
||||
setItemValue("");
|
||||
refetchWatchlist();
|
||||
}
|
||||
|
||||
async function removeItem(itemId: string) {
|
||||
await api.darkwatch.removeWatchlistItem.mutate({ itemId });
|
||||
refetchWatchlist();
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="flex h-[calc(100vh-4rem)] bg-[var(--color-bg)]">
|
||||
<Title>DarkWatch — ShieldAI</Title>
|
||||
<Sidebar open={sidebarOpen()} onClose={() => setSidebarOpen(false)} />
|
||||
<div class="flex-1 flex flex-col min-w-0">
|
||||
<TopBar onMenuToggle={() => setSidebarOpen(v => !v)} />
|
||||
<main class="flex-1 overflow-y-auto p-6">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<h1 class="text-2xl font-bold text-[var(--color-text-primary)] mb-6">DarkWatch</h1>
|
||||
|
||||
<Card class="mb-6 p-4">
|
||||
<h2 class="text-sm font-semibold text-[var(--color-text-primary)] mb-3">Add Watchlist Item</h2>
|
||||
<div class="flex gap-2">
|
||||
<Input
|
||||
placeholder="Email or phone number"
|
||||
value={itemValue()}
|
||||
onInput={(e) => setItemValue(e.currentTarget.value)}
|
||||
/>
|
||||
<Button onClick={addItem}>Add</Button>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card class="mb-6">
|
||||
<div class="px-4 py-3 border-b border-[var(--color-border)]/50">
|
||||
<h2 class="text-sm font-semibold text-[var(--color-text-primary)]">Watchlist</h2>
|
||||
</div>
|
||||
<div class="divide-y divide-[var(--color-border)]/50">
|
||||
<For each={watchlist()}>
|
||||
{(item: Record<string, unknown>) => (
|
||||
<div class="px-4 py-3 flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-[var(--color-text-primary)]">{String(item.value ?? "")}</p>
|
||||
<p class="text-xs text-[var(--color-text-tertiary)]">{String(item.type ?? "")}</p>
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" onClick={() => removeItem(String(item.id))}>
|
||||
Remove
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<div class="px-4 py-3 border-b border-[var(--color-border)]/50">
|
||||
<h2 class="text-sm font-semibold text-[var(--color-text-primary)]">Recent Exposures</h2>
|
||||
</div>
|
||||
<div class="divide-y divide-[var(--color-border)]/50">
|
||||
<For each={(exposures()?.items ?? []).slice(0, 10)}>
|
||||
{(exp: Record<string, unknown>) => (
|
||||
<div class="px-4 py-3">
|
||||
<p class="text-sm font-medium text-[var(--color-text-primary)]">{String(exp.title ?? "")}</p>
|
||||
<p class="text-xs text-[var(--color-text-secondary)]">{String(exp.description ?? "")}</p>
|
||||
<Badge variant={(String(exp.severity ?? "") === "HIGH" || String(exp.severity ?? "") === "CRITICAL") ? "error" : "warning"}>
|
||||
{String(exp.severity ?? "")}
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
<Show when={!exposures()?.items?.length}>
|
||||
<div class="px-4 py-8 text-center text-sm text-[var(--color-text-tertiary)]">
|
||||
No exposures found
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user