- PREMIUM_FEATURES_ROADMAP.md: Strategic feature roadmap for 3x-5x revenue expansion * Top 5 features: Board Autopilot, Insurance Integration, Predictive Modeling, Workflow Integration, Executive Monitoring * Revenue projections: $250K → $665K ARR over 3 years * 18+ feature ideas ranked by revenue, complexity, stickiness - QUICK_WIN_FEATURES.md: Implementation guides for immediate value * Board Presentation Autopilot: 14-day implementation, $30K-$40K/year * Insurance Savings Calculator: 10-day implementation, $25K-$50K/year * Step-by-step code examples and deployment plan Enables: - 2.5x-3x wallet expansion per customer - Shift from audit services to sticky SaaS - 140-150% NRR with premium features - $3.5M+ Year 1 revenue with premium offerings Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
141 lines
4.1 KiB
TypeScript
141 lines
4.1 KiB
TypeScript
const BASE = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000";
|
|
|
|
let authToken: string | null = null;
|
|
|
|
export function setAuthToken(token: string | null) {
|
|
authToken = token;
|
|
}
|
|
|
|
async function request<T>(path: string, options: RequestInit = {}): Promise<T> {
|
|
const headers: Record<string, string> = {
|
|
"Content-Type": "application/json",
|
|
...(options.headers as Record<string, string>),
|
|
};
|
|
if (authToken) {
|
|
headers["Authorization"] = `Bearer ${authToken}`;
|
|
}
|
|
const res = await fetch(`${BASE}${path}`, { ...options, headers });
|
|
if (!res.ok) {
|
|
const err = await res.json().catch(() => ({ detail: res.statusText }));
|
|
throw new Error(err.detail || `HTTP ${res.status}`);
|
|
}
|
|
return res.json();
|
|
}
|
|
|
|
export const api = {
|
|
login: (email: string, password: string) =>
|
|
request<{ access_token: string; role: string; tenant_id: string; full_name: string }>(
|
|
"/api/v1/auth/login",
|
|
{ method: "POST", body: JSON.stringify({ email, password }) }
|
|
),
|
|
|
|
me: () => request<{ id: string; email: string; full_name: string; role: string; tenant_id: string }>(
|
|
"/api/v1/auth/me"
|
|
),
|
|
|
|
dashboard: (tenantId: string) =>
|
|
request<DashboardData>(`/api/v1/dashboard/${tenantId}`),
|
|
|
|
findings: (tenantId: string, params?: string) =>
|
|
request<Finding[]>(`/api/v1/findings?tenant_id=${tenantId}${params ? "&" + params : ""}`),
|
|
|
|
finding: (id: string) =>
|
|
request<Finding>(`/api/v1/findings/${id}`),
|
|
|
|
updateFindingStatus: (id: string, body: { status: string; resolution_note?: string }) =>
|
|
request<Finding>(`/api/v1/findings/${id}/status`, {
|
|
method: "PATCH",
|
|
body: JSON.stringify(body),
|
|
}),
|
|
|
|
attackPaths: (findingId: string) =>
|
|
request<AttackPath[]>(`/api/v1/attack-paths/${findingId}`),
|
|
|
|
footprint: (tenantId: string) =>
|
|
request<FootprintData>(`/api/v1/footprint/${tenantId}`),
|
|
|
|
aiExplain: (findingId: string, question: string) =>
|
|
request<{ question: string; answer: string }>(`/api/v1/ai/explain/${findingId}?question=${encodeURIComponent(question)}`),
|
|
|
|
aiExplainFinding: (findingId: string) =>
|
|
request<{
|
|
finding_id: string;
|
|
summary?: string;
|
|
business_impact?: string;
|
|
impact_level?: string;
|
|
remediation_steps?: string;
|
|
fix_priority?: string;
|
|
generated_at?: string;
|
|
status: "available" | "processing";
|
|
message?: string;
|
|
}>(`/api/v1/findings/${findingId}/ai-explain`),
|
|
};
|
|
|
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
|
|
export interface DashboardData {
|
|
tenant_name: string;
|
|
current_score: number;
|
|
previous_score: number | null;
|
|
score_delta: number | null;
|
|
score_trend: { date: string; score: number }[];
|
|
top_risks: RiskCard[];
|
|
open_critical: number;
|
|
open_high: number;
|
|
open_medium: number;
|
|
total_open: number;
|
|
baseline_score: number | null;
|
|
baseline_date: string | null;
|
|
}
|
|
|
|
export interface RiskCard {
|
|
id: string;
|
|
title: string;
|
|
ai_summary: string | null;
|
|
ai_business_impact: string | null;
|
|
ai_impact_level: string | null;
|
|
ai_fix_priority: string | null;
|
|
severity: string;
|
|
category: string;
|
|
}
|
|
|
|
export interface Finding {
|
|
id: string;
|
|
tenant_id: string;
|
|
title: string;
|
|
severity: string;
|
|
status: string;
|
|
category: string;
|
|
technical_description: string | null;
|
|
cve_id: string | null;
|
|
cvss_score: number | null;
|
|
affected_component: string | null;
|
|
ai_summary: string | null;
|
|
ai_business_impact: string | null;
|
|
ai_impact_level: string | null;
|
|
ai_remediation_steps: string | null;
|
|
ai_fix_priority: string | null;
|
|
assignee_email: string | null;
|
|
due_date: string | null;
|
|
is_top_risk: boolean;
|
|
source: string | null;
|
|
created_at: string;
|
|
updated_at: string;
|
|
}
|
|
|
|
export interface AttackPath {
|
|
id: string;
|
|
finding_id: string;
|
|
title: string;
|
|
ai_narrative: string | null;
|
|
nodes_json: string | null;
|
|
edges_json: string | null;
|
|
}
|
|
|
|
export interface FootprintData {
|
|
tenant_id: string;
|
|
executives: { id: string; name: string; title: string; email: string }[];
|
|
footprint_findings: any[];
|
|
total_exposures: number;
|
|
}
|