diff --git a/dashboards/admin-web/README.md b/dashboards/admin-web/README.md index 5aa0e585..2c7611c1 100644 --- a/dashboards/admin-web/README.md +++ b/dashboards/admin-web/README.md @@ -37,10 +37,10 @@ curl -X POST "http://localhost:3001/api/seed?secret=" ### Default Logins -| Email | Password | Role | -| ------------------ | ----------- | ----------- | -| `admin@example.com` | `Admin123!` | Super Admin | -| `viewer@example.com` | `viewer123` | Viewer | +| Email | Password | Role | +| -------------------- | ----------- | ----------- | +| `admin@example.com` | `Admin123!` | Super Admin | +| `viewer@example.com` | `viewer123` | Viewer | ## Environment Variables diff --git a/dashboards/admin-web/src/__tests__/kill-switch.test.ts b/dashboards/admin-web/src/__tests__/kill-switch.test.ts index 24bbc824..023ba9b7 100644 --- a/dashboards/admin-web/src/__tests__/kill-switch.test.ts +++ b/dashboards/admin-web/src/__tests__/kill-switch.test.ts @@ -52,13 +52,15 @@ describe('GET /api/settings/kill-switch', () => { it('returns existing kill_switch flag state', async () => { mockListFlags.mockResolvedValue({ - flags: [{ - key: 'kill_switch', - enabled: true, - platforms: ['desktop', 'ios'], - description: 'Maintenance window', - updatedAt: '2026-02-16T00:00:00Z', - }], + flags: [ + { + key: 'kill_switch', + enabled: true, + platforms: ['desktop', 'ios'], + description: 'Maintenance window', + updatedAt: '2026-02-16T00:00:00Z', + }, + ], }); const res = await makeGet(); diff --git a/dashboards/admin-web/src/app/(dashboard)/extraction/page.tsx b/dashboards/admin-web/src/app/(dashboard)/extraction/page.tsx index b3bf10ea..3a67aa33 100644 --- a/dashboards/admin-web/src/app/(dashboard)/extraction/page.tsx +++ b/dashboards/admin-web/src/app/(dashboard)/extraction/page.tsx @@ -138,7 +138,7 @@ export default function ExtractionPage() { } }, [inputText, selectedTask]); - const currentTask = tasks.find((t) => t.id === selectedTask); + const currentTask = tasks.find(t => t.id === selectedTask); // Group extractions by class const groupedExtractions = result?.extractions.reduce( @@ -148,7 +148,7 @@ export default function ExtractionPage() { acc[cls].push(e); return acc; }, - {} as Record, + {} as Record ); return ( @@ -189,7 +189,7 @@ export default function ExtractionPage() { className="w-full min-h-[200px] rounded-lg border border-border bg-background p-3 text-sm resize-y focus:outline-none focus:ring-2 focus:ring-ring" placeholder="Paste a transcript, meeting notes, or any text to extract structured entities from..." value={inputText} - onChange={(e) => setInputText(e.target.value)} + onChange={e => setInputText(e.target.value)} />
@@ -290,7 +297,9 @@ export default function ProductsPage() { value={form.defaultPlan} onValueChange={v => setForm({ ...form, defaultPlan: v as 'free' | 'pro' })} > - + + + Free Pro @@ -322,7 +331,8 @@ export default function ProductsPage() {

Free

setForm({ ...form, deviceLimitFree: e.target.value })} /> @@ -330,7 +340,8 @@ export default function ProductsPage() {

Pro

setForm({ ...form, deviceLimitPro: e.target.value })} /> @@ -338,7 +349,8 @@ export default function ProductsPage() {

Enterprise

setForm({ ...form, deviceLimitEnterprise: e.target.value })} /> @@ -367,7 +379,8 @@ export default function ProductsPage() { Product "{onboardResult.productId}" onboarded successfully

- {onboardResult.plans} plans seeded{onboardResult.flags > 0 ? `, ${onboardResult.flags} flag(s) created` : ''} + {onboardResult.plans} plans seeded + {onboardResult.flags > 0 ? `, ${onboardResult.flags} flag(s) created` : ''}

@@ -403,7 +416,11 @@ export default function ProductsPage() { : 'bg-red-50 text-red-700 dark:bg-red-950/30 dark:text-red-400' } > - {p.status === 'active' ? : } + {p.status === 'active' ? ( + + ) : ( + + )} {p.status}
Device Limits
- Free: {p.deviceLimits.free} · Pro: {p.deviceLimits.pro} · Ent: {p.deviceLimits.enterprise} + Free: {p.deviceLimits.free} · Pro: {p.deviceLimits.pro} · Ent:{' '} + {p.deviceLimits.enterprise}
{p.websiteUrl && ( <> @@ -488,7 +506,9 @@ export default function ProductsPage() { value={editForm.status ?? 'active'} onValueChange={v => setEditForm({ ...editForm, status: v })} > - + + + Active Disabled @@ -503,7 +523,9 @@ export default function ProductsPage() { value={editForm.defaultPlan ?? 'free'} onValueChange={v => setEditForm({ ...editForm, defaultPlan: v })} > - + + + Free Pro @@ -513,7 +535,9 @@ export default function ProductsPage() {
setEditForm({ ...editForm, trialDays: e.target.value })} /> @@ -532,7 +556,8 @@ export default function ProductsPage() {

Free

setEditForm({ ...editForm, deviceLimitFree: e.target.value })} /> @@ -540,7 +565,8 @@ export default function ProductsPage() {

Pro

setEditForm({ ...editForm, deviceLimitPro: e.target.value })} /> @@ -548,9 +574,12 @@ export default function ProductsPage() {

Enterprise

setEditForm({ ...editForm, deviceLimitEnterprise: e.target.value })} + onChange={e => + setEditForm({ ...editForm, deviceLimitEnterprise: e.target.value }) + } />
diff --git a/dashboards/admin-web/src/app/(dashboard)/promos/page.tsx b/dashboards/admin-web/src/app/(dashboard)/promos/page.tsx index 1de1562b..781eebef 100644 --- a/dashboards/admin-web/src/app/(dashboard)/promos/page.tsx +++ b/dashboards/admin-web/src/app/(dashboard)/promos/page.tsx @@ -30,7 +30,13 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/select'; -import { apiListPromos, apiCreatePromo, apiDeletePromo, apiUpdatePromo, type ApiPromo } from '@/lib/api'; +import { + apiListPromos, + apiCreatePromo, + apiDeletePromo, + apiUpdatePromo, + type ApiPromo, +} from '@/lib/api'; function formatDate(iso: string) { return new Date(iso).toLocaleDateString('en-US', { @@ -69,7 +75,8 @@ export default function PromosPage() { const handleToggleActive = async (promo: ApiPromo) => { const { data } = await apiUpdatePromo(promo.id, { active: !promo.active }); - if (data) setPromos(prev => prev.map(p => p.id === promo.id ? { ...p, active: !p.active } : p)); + if (data) + setPromos(prev => prev.map(p => (p.id === promo.id ? { ...p, active: !p.active } : p))); }; const loadPromos = useCallback(async () => { diff --git a/dashboards/admin-web/src/app/(dashboard)/settings/page.tsx b/dashboards/admin-web/src/app/(dashboard)/settings/page.tsx index fa407a0f..6e7c4b5e 100644 --- a/dashboards/admin-web/src/app/(dashboard)/settings/page.tsx +++ b/dashboards/admin-web/src/app/(dashboard)/settings/page.tsx @@ -435,10 +435,13 @@ export default function SettingsPage() { Azure Configuration Azure secrets are managed via the{' '} - + Secrets Manager - - {' '}(Key Vault) + {' '} + (Key Vault)
@@ -449,8 +452,8 @@ export default function SettingsPage() { in Azure Key Vault and resolved at runtime. Use the{' '} Secrets Manager - - {' '}to view, rotate, or update them. + {' '} + to view, rotate, or update them.

@@ -473,7 +476,12 @@ export default function SettingsPage() { setSettings(s => ({ ...s, rateLimits: { ...s.rateLimits, globalPerMin: parseInt(e.target.value) || 0 } }))} + onChange={e => + setSettings(s => ({ + ...s, + rateLimits: { ...s.rateLimits, globalPerMin: parseInt(e.target.value) || 0 }, + })) + } />
@@ -481,7 +489,12 @@ export default function SettingsPage() { setSettings(s => ({ ...s, rateLimits: { ...s.rateLimits, perUserPerMin: parseInt(e.target.value) || 0 } }))} + onChange={e => + setSettings(s => ({ + ...s, + rateLimits: { ...s.rateLimits, perUserPerMin: parseInt(e.target.value) || 0 }, + })) + } />
@@ -491,7 +504,12 @@ export default function SettingsPage() { setSettings(s => ({ ...s, rateLimits: { ...s.rateLimits, maxTokenBurst: parseInt(e.target.value) || 0 } }))} + onChange={e => + setSettings(s => ({ + ...s, + rateLimits: { ...s.rateLimits, maxTokenBurst: parseInt(e.target.value) || 0 }, + })) + } />
@@ -499,7 +517,12 @@ export default function SettingsPage() { setSettings(s => ({ ...s, rateLimits: { ...s.rateLimits, abuseThreshold: parseInt(e.target.value) || 0 } }))} + onChange={e => + setSettings(s => ({ + ...s, + rateLimits: { ...s.rateLimits, abuseThreshold: parseInt(e.target.value) || 0 }, + })) + } />
@@ -513,7 +536,9 @@ export default function SettingsPage() { setSettings(s => ({ ...s, rateLimits: { ...s.rateLimits, autoSuspendOnAbuse: v } }))} + onCheckedChange={v => + setSettings(s => ({ ...s, rateLimits: { ...s.rateLimits, autoSuspendOnAbuse: v } })) + } />
@@ -525,7 +550,9 @@ export default function SettingsPage() {
setSettings(s => ({ ...s, rateLimits: { ...s.rateLimits, ipBlocklist: v } }))} + onCheckedChange={v => + setSettings(s => ({ ...s, rateLimits: { ...s.rateLimits, ipBlocklist: v } })) + } /> @@ -552,7 +579,12 @@ export default function SettingsPage() { setSettings(s => ({ ...s, notifications: { ...s.notifications, newUserSignup: v } }))} + onCheckedChange={v => + setSettings(s => ({ + ...s, + notifications: { ...s.notifications, newUserSignup: v }, + })) + } />
@@ -564,7 +596,12 @@ export default function SettingsPage() {
setSettings(s => ({ ...s, notifications: { ...s.notifications, usageThreshold: v } }))} + onCheckedChange={v => + setSettings(s => ({ + ...s, + notifications: { ...s.notifications, usageThreshold: v }, + })) + } />
@@ -576,7 +613,12 @@ export default function SettingsPage() {
setSettings(s => ({ ...s, notifications: { ...s.notifications, failedPayment: v } }))} + onCheckedChange={v => + setSettings(s => ({ + ...s, + notifications: { ...s.notifications, failedPayment: v }, + })) + } />
@@ -588,7 +630,12 @@ export default function SettingsPage() {
setSettings(s => ({ ...s, notifications: { ...s.notifications, securityAlerts: v } }))} + onCheckedChange={v => + setSettings(s => ({ + ...s, + notifications: { ...s.notifications, securityAlerts: v }, + })) + } /> @@ -620,7 +667,9 @@ export default function SettingsPage() { setSettings(s => ({ ...s, dataRetentionDays: parseInt(e.target.value) || 365 }))} + onChange={e => + setSettings(s => ({ ...s, dataRetentionDays: parseInt(e.target.value) || 365 })) + } />
diff --git a/dashboards/admin-web/src/app/(dashboard)/subscriptions/page.tsx b/dashboards/admin-web/src/app/(dashboard)/subscriptions/page.tsx index ebce6dab..2b2d30a4 100644 --- a/dashboards/admin-web/src/app/(dashboard)/subscriptions/page.tsx +++ b/dashboards/admin-web/src/app/(dashboard)/subscriptions/page.tsx @@ -71,10 +71,7 @@ export default function SubscriptionsPage() { useEffect(() => { async function load() { try { - const [plansRes, usersRes] = await Promise.allSettled([ - apiListPlans(), - apiListUsers(), - ]); + const [plansRes, usersRes] = await Promise.allSettled([apiListPlans(), apiListUsers()]); let loadedPlans: LocalPlan[] = []; if (plansRes.status === 'fulfilled' && plansRes.value.data?.plans?.length) { loadedPlans = plansRes.value.data.plans.filter(p => p.active).map(planDocToLocal); @@ -238,7 +235,9 @@ export default function SubscriptionsPage() { -
{totalActiveUsers > 0 ? formatCurrency(totalMRR / totalActiveUsers) : '$0.00'}
+
+ {totalActiveUsers > 0 ? formatCurrency(totalMRR / totalActiveUsers) : '$0.00'} +

ARPU

diff --git a/dashboards/admin-web/src/app/(dashboard)/users/page.tsx b/dashboards/admin-web/src/app/(dashboard)/users/page.tsx index 7df8c45b..e5354f2f 100644 --- a/dashboards/admin-web/src/app/(dashboard)/users/page.tsx +++ b/dashboards/admin-web/src/app/(dashboard)/users/page.tsx @@ -50,7 +50,13 @@ import { SelectValue, } from '@/components/ui/select'; import { mockUsers, formatNumber, formatCurrency, formatDate, type User } from '@/lib/mock-data'; -import { apiListUsers, apiUpdateUser, apiDeleteUser, apiCreateInvitation, type ApiUser } from '@/lib/api'; +import { + apiListUsers, + apiUpdateUser, + apiDeleteUser, + apiCreateInvitation, + type ApiUser, +} from '@/lib/api'; import { Label } from '@/components/ui/label'; import { useToast } from '@/components/ui/toast'; @@ -127,10 +133,15 @@ export default function UsersPage() { }); setInviteCreating(false); if (data) { - const userDashboardUrl = process.env.NEXT_PUBLIC_USER_DASHBOARD_URL || 'http://localhost:3002'; + const userDashboardUrl = + process.env.NEXT_PUBLIC_USER_DASHBOARD_URL || 'http://localhost:3002'; setInviteLink(`${userDashboardUrl}/login?ref=${encodeURIComponent(data.code)}`); } else { - toast({ title: 'Failed to create invite', description: error || 'Unknown error', variant: 'error' }); + toast({ + title: 'Failed to create invite', + description: error || 'Unknown error', + variant: 'error', + }); } }; @@ -160,7 +171,10 @@ export default function UsersPage() { setUsers(prev => prev.map(u => (u.id === user.id ? { ...u, status: newStatus as User['status'] } : u)) ); - toast({ title: `User ${newStatus === 'suspended' ? 'suspended' : 'activated'}`, variant: newStatus === 'suspended' ? 'warning' : 'success' }); + toast({ + title: `User ${newStatus === 'suspended' ? 'suspended' : 'activated'}`, + variant: newStatus === 'suspended' ? 'warning' : 'success', + }); } else { toast({ title: 'Action failed', description: error, variant: 'error' }); } @@ -232,13 +246,15 @@ export default function UsersPage() {

Users

Manage platform users and their subscriptions

- @@ -434,9 +450,24 @@ export default function UsersPage() { handleChangePlan(user.id, user.plan === 'free' ? 'pro' : user.plan === 'pro' ? 'enterprise' : 'free')} + onClick={() => + handleChangePlan( + user.id, + user.plan === 'free' + ? 'pro' + : user.plan === 'pro' + ? 'enterprise' + : 'free' + ) + } > - Cycle Plan ({user.plan} → {user.plan === 'free' ? 'pro' : user.plan === 'pro' ? 'enterprise' : 'free'}) + Cycle Plan ({user.plan} →{' '} + {user.plan === 'free' + ? 'pro' + : user.plan === 'pro' + ? 'enterprise' + : 'free'} + ) {inviteLink} - diff --git a/dashboards/admin-web/src/app/api/ops/secrets/[name]/route.ts b/dashboards/admin-web/src/app/api/ops/secrets/[name]/route.ts index 5ad98696..c4a613aa 100644 --- a/dashboards/admin-web/src/app/api/ops/secrets/[name]/route.ts +++ b/dashboards/admin-web/src/app/api/ops/secrets/[name]/route.ts @@ -13,10 +13,7 @@ function getSecretClient(): SecretClient { } /** GET /api/ops/secrets/[name] — read a specific secret value */ -export async function GET( - _req: NextRequest, - { params }: { params: Promise<{ name: string }> }, -) { +export async function GET(_req: NextRequest, { params }: { params: Promise<{ name: string }> }) { try { const { name } = await params; const client = getSecretClient(); @@ -41,10 +38,7 @@ export async function GET( } /** DELETE /api/ops/secrets/[name] — soft-delete a secret */ -export async function DELETE( - _req: NextRequest, - { params }: { params: Promise<{ name: string }> }, -) { +export async function DELETE(_req: NextRequest, { params }: { params: Promise<{ name: string }> }) { try { const { name } = await params; const client = getSecretClient(); @@ -55,7 +49,7 @@ export async function DELETE( } catch (err) { return NextResponse.json( { error: err instanceof Error ? err.message : String(err) }, - { status: 500 }, + { status: 500 } ); } } diff --git a/dashboards/admin-web/src/app/api/ops/secrets/route.ts b/dashboards/admin-web/src/app/api/ops/secrets/route.ts index 81891859..1e1adc9d 100644 --- a/dashboards/admin-web/src/app/api/ops/secrets/route.ts +++ b/dashboards/admin-web/src/app/api/ops/secrets/route.ts @@ -49,7 +49,7 @@ export async function GET() { } catch (err) { return NextResponse.json( { error: err instanceof Error ? err.message : String(err) }, - { status: 500 }, + { status: 500 } ); } } @@ -67,10 +67,7 @@ export async function POST(req: NextRequest) { }; if (!name || !value) { - return NextResponse.json( - { error: 'name and value are required' }, - { status: 400 }, - ); + return NextResponse.json({ error: 'name and value are required' }, { status: 400 }); } const client = getSecretClient(); @@ -89,7 +86,7 @@ export async function POST(req: NextRequest) { } catch (err) { return NextResponse.json( { error: err instanceof Error ? err.message : String(err) }, - { status: 500 }, + { status: 500 } ); } } diff --git a/dashboards/admin-web/src/app/api/ops/valkey/route.ts b/dashboards/admin-web/src/app/api/ops/valkey/route.ts index 389f14b4..6560d45b 100644 --- a/dashboards/admin-web/src/app/api/ops/valkey/route.ts +++ b/dashboards/admin-web/src/app/api/ops/valkey/route.ts @@ -34,7 +34,10 @@ function parseInfoValue(info: string, key: string): string | undefined { return line?.split(':').slice(1).join(':'); } -async function getPreview(client: ReturnType, key: string): Promise { +async function getPreview( + client: ReturnType, + key: string +): Promise { const [type, ttlSeconds] = await Promise.all([client.type(key), client.ttl(key)]); if (type === 'string') { @@ -76,7 +79,10 @@ async function getPreview(client: ReturnType, key: string): } if (type === 'zset') { - const [size, entries] = await Promise.all([client.zCard(key), client.zRangeWithScores(key, 0, 4)]); + const [size, entries] = await Promise.all([ + client.zCard(key), + client.zRangeWithScores(key, 0, 4), + ]); return { key, type, @@ -142,7 +148,10 @@ export async function GET(req: NextRequest) { } } catch (error) { const message = error instanceof Error ? error.message : 'Unable to inspect Valkey'; - return NextResponse.json({ error: message }, { status: message === 'Unauthorized' ? 401 : 500 }); + return NextResponse.json( + { error: message }, + { status: message === 'Unauthorized' ? 401 : 500 } + ); } } @@ -215,6 +224,9 @@ export async function POST(req: NextRequest) { } } catch (error) { const message = error instanceof Error ? error.message : 'Valkey write failed'; - return NextResponse.json({ error: message }, { status: message === 'Unauthorized' ? 401 : 500 }); + return NextResponse.json( + { error: message }, + { status: message === 'Unauthorized' ? 401 : 500 } + ); } } diff --git a/dashboards/admin-web/src/app/api/settings/kill-switch/route.ts b/dashboards/admin-web/src/app/api/settings/kill-switch/route.ts index ae47fe43..fc01f7ac 100644 --- a/dashboards/admin-web/src/app/api/settings/kill-switch/route.ts +++ b/dashboards/admin-web/src/app/api/settings/kill-switch/route.ts @@ -76,7 +76,10 @@ export async function PUT(req: NextRequest) { const reason = typeof body.reason === 'string' ? body.reason : ''; const platforms: PlatformFlags = body.platforms ?? { - desktop: true, ios: true, android: true, web: true, + desktop: true, + ios: true, + android: true, + web: true, }; const result = await listFlags(); diff --git a/dashboards/admin-web/src/app/api/telemetry/clusters/[id]/route.ts b/dashboards/admin-web/src/app/api/telemetry/clusters/[id]/route.ts index 67b3d151..7e34d5b8 100644 --- a/dashboards/admin-web/src/app/api/telemetry/clusters/[id]/route.ts +++ b/dashboards/admin-web/src/app/api/telemetry/clusters/[id]/route.ts @@ -8,10 +8,7 @@ function getJwt(req: NextRequest): string { return req.headers.get('authorization')?.replace('Bearer ', '') ?? ''; } -export async function PATCH( - req: NextRequest, - { params }: { params: Promise<{ id: string }> } -) { +export async function PATCH(req: NextRequest, { params }: { params: Promise<{ id: string }> }) { const jwt = getJwt(req); if (!jwt) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); diff --git a/dashboards/admin-web/src/app/api/telemetry/policies/[id]/route.ts b/dashboards/admin-web/src/app/api/telemetry/policies/[id]/route.ts index 8e4a3911..e0318f94 100644 --- a/dashboards/admin-web/src/app/api/telemetry/policies/[id]/route.ts +++ b/dashboards/admin-web/src/app/api/telemetry/policies/[id]/route.ts @@ -1,8 +1,5 @@ import { NextRequest, NextResponse } from 'next/server'; -import { - updateTelemetryPolicy, - deleteTelemetryPolicy, -} from '@/lib/platform-client'; +import { updateTelemetryPolicy, deleteTelemetryPolicy } from '@/lib/platform-client'; function getJwt(req: NextRequest): string { const cookie = req.headers.get('cookie') ?? ''; @@ -11,10 +8,7 @@ function getJwt(req: NextRequest): string { return req.headers.get('authorization')?.replace('Bearer ', '') ?? ''; } -export async function PUT( - req: NextRequest, - { params }: { params: Promise<{ id: string }> } -) { +export async function PUT(req: NextRequest, { params }: { params: Promise<{ id: string }> }) { const jwt = getJwt(req); if (!jwt) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); @@ -28,10 +22,7 @@ export async function PUT( } } -export async function DELETE( - req: NextRequest, - { params }: { params: Promise<{ id: string }> } -) { +export async function DELETE(req: NextRequest, { params }: { params: Promise<{ id: string }> }) { const jwt = getJwt(req); if (!jwt) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); diff --git a/dashboards/admin-web/src/app/api/telemetry/policies/route.ts b/dashboards/admin-web/src/app/api/telemetry/policies/route.ts index 809f2b77..549a6912 100644 --- a/dashboards/admin-web/src/app/api/telemetry/policies/route.ts +++ b/dashboards/admin-web/src/app/api/telemetry/policies/route.ts @@ -1,8 +1,5 @@ import { NextRequest, NextResponse } from 'next/server'; -import { - listTelemetryPolicies, - createTelemetryPolicy, -} from '@/lib/platform-client'; +import { listTelemetryPolicies, createTelemetryPolicy } from '@/lib/platform-client'; function getJwt(req: NextRequest): string { const cookie = req.headers.get('cookie') ?? ''; diff --git a/dashboards/admin-web/src/components/ui/alert.tsx b/dashboards/admin-web/src/components/ui/alert.tsx index ab761d17..42b1a352 100644 --- a/dashboards/admin-web/src/components/ui/alert.tsx +++ b/dashboards/admin-web/src/components/ui/alert.tsx @@ -1,58 +1,48 @@ -import * as React from "react" -import { cva, type VariantProps } from "class-variance-authority" -import { cn } from "@/lib/utils" +import * as React from 'react'; +import { cva, type VariantProps } from 'class-variance-authority'; +import { cn } from '@/lib/utils'; const alertVariants = cva( - "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", + 'relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground', { variants: { variant: { - default: "bg-background text-foreground", + default: 'bg-background text-foreground', destructive: - "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", + 'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive', }, }, defaultVariants: { - variant: "default", + variant: 'default', }, } -) +); const Alert = React.forwardRef< HTMLDivElement, React.HTMLAttributes & VariantProps >(({ className, variant, ...props }, ref) => ( -
-)) -Alert.displayName = "Alert" +
+)); +Alert.displayName = 'Alert'; -const AlertTitle = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)) -AlertTitle.displayName = "AlertTitle" +const AlertTitle = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +); +AlertTitle.displayName = 'AlertTitle'; const AlertDescription = React.forwardRef< HTMLParagraphElement, React.HTMLAttributes >(({ className, ...props }, ref) => ( -
-)) -AlertDescription.displayName = "AlertDescription" +
+)); +AlertDescription.displayName = 'AlertDescription'; -export { Alert, AlertTitle, AlertDescription } +export { Alert, AlertTitle, AlertDescription }; diff --git a/dashboards/admin-web/src/components/ui/checkbox.tsx b/dashboards/admin-web/src/components/ui/checkbox.tsx index 8d55ab33..795827ad 100644 --- a/dashboards/admin-web/src/components/ui/checkbox.tsx +++ b/dashboards/admin-web/src/components/ui/checkbox.tsx @@ -1,13 +1,12 @@ -'use client' +'use client'; -import * as React from 'react' -import { Check } from 'lucide-react' -import { cn } from '@/lib/utils' +import * as React from 'react'; +import { Check } from 'lucide-react'; +import { cn } from '@/lib/utils'; -export interface CheckboxProps - extends React.ButtonHTMLAttributes { - checked?: boolean - onCheckedChange?: (checked: boolean) => void +export interface CheckboxProps extends React.ButtonHTMLAttributes { + checked?: boolean; + onCheckedChange?: (checked: boolean) => void; } const Checkbox = React.forwardRef( @@ -28,7 +27,7 @@ const Checkbox = React.forwardRef( {checked && } ) -) -Checkbox.displayName = 'Checkbox' +); +Checkbox.displayName = 'Checkbox'; -export { Checkbox } +export { Checkbox }; diff --git a/dashboards/admin-web/src/components/ui/slider.tsx b/dashboards/admin-web/src/components/ui/slider.tsx index 0059f381..946d0ab7 100644 --- a/dashboards/admin-web/src/components/ui/slider.tsx +++ b/dashboards/admin-web/src/components/ui/slider.tsx @@ -11,10 +11,7 @@ const Slider = React.forwardRef< >(({ className, ...props }, ref) => ( diff --git a/dashboards/admin-web/src/lib/api.ts b/dashboards/admin-web/src/lib/api.ts index 6a1b6d40..4b08561b 100644 --- a/dashboards/admin-web/src/lib/api.ts +++ b/dashboards/admin-web/src/lib/api.ts @@ -597,7 +597,9 @@ export async function apiGetBroadcast(id: string) { return apiFetch(`/admin/broadcasts/${id}`); } -export async function apiCreateBroadcast(body: Omit) { +export async function apiCreateBroadcast( + body: Omit +) { return apiFetch('/admin/broadcasts', { method: 'POST', body: JSON.stringify(body), @@ -661,7 +663,16 @@ export interface ApiSurvey { description?: string; questions: { id: string; - type: 'single_choice' | 'multiple_choice' | 'rating' | 'nps' | 'text_short' | 'text_long' | 'dropdown' | 'scale' | 'ranking'; + type: + | 'single_choice' + | 'multiple_choice' + | 'rating' + | 'nps' + | 'text_short' + | 'text_long' + | 'dropdown' + | 'scale' + | 'ranking'; text: string; description?: string; required: boolean; @@ -676,7 +687,11 @@ export interface ApiSurvey { status: 'draft' | 'active' | 'paused' | 'closed'; startsAt?: string; endsAt?: string; - displayTrigger: { type: 'immediate' } | { type: 'delay_seconds'; seconds: number } | { type: 'event'; eventName: string } | { type: 'page_view'; pagePattern: string }; + displayTrigger: + | { type: 'immediate' } + | { type: 'delay_seconds'; seconds: number } + | { type: 'event'; eventName: string } + | { type: 'page_view'; pagePattern: string }; incentive?: { type: 'pro_days' | 'credits'; amount: number }; metrics: { impressions: number; @@ -733,7 +748,9 @@ export async function apiGetSurvey(id: string) { return apiFetch(`/admin/surveys/${id}`); } -export async function apiCreateSurvey(body: Omit) { +export async function apiCreateSurvey( + body: Omit +) { return apiFetch('/admin/surveys', { method: 'POST', body: JSON.stringify(body), @@ -759,12 +776,17 @@ export async function apiPauseSurvey(id: string) { return apiFetch<{ success: boolean }>(`/admin/surveys/${id}/pause`, { method: 'POST' }); } -export async function apiGetSurveyResponses(id: string, options?: { isComplete?: boolean; limit?: number; offset?: number }) { +export async function apiGetSurveyResponses( + id: string, + options?: { isComplete?: boolean; limit?: number; offset?: number } +) { const params = new URLSearchParams(); if (options?.isComplete !== undefined) params.set('isComplete', String(options.isComplete)); if (options?.limit) params.set('limit', String(options.limit)); if (options?.offset) params.set('offset', String(options.offset)); - return apiFetch<{ responses: ApiSurveyResponse[]; total: number }>(`/admin/surveys/${id}/responses?${params}`); + return apiFetch<{ responses: ApiSurveyResponse[]; total: number }>( + `/admin/surveys/${id}/responses?${params}` + ); } export async function apiGetSurveyRespondents(id: string) { diff --git a/dashboards/admin-web/src/lib/billing-client.ts b/dashboards/admin-web/src/lib/billing-client.ts index 832b7ecd..d9ee0e58 100644 --- a/dashboards/admin-web/src/lib/billing-client.ts +++ b/dashboards/admin-web/src/lib/billing-client.ts @@ -58,7 +58,9 @@ export async function updateSubscription( // ── Usage ─────────────────────────────────────────────────────── -export async function listUsage(options: { userId?: string; days?: number; limit?: number; productId?: string } = {}) { +export async function listUsage( + options: { userId?: string; days?: number; limit?: number; productId?: string } = {} +) { const params = new URLSearchParams(); if (options.userId) params.set('userId', options.userId); if (options.days) params.set('days', String(options.days)); diff --git a/dashboards/admin-web/src/lib/diagnostics-client.ts b/dashboards/admin-web/src/lib/diagnostics-client.ts index e8606fe9..3ef62d13 100644 --- a/dashboards/admin-web/src/lib/diagnostics-client.ts +++ b/dashboards/admin-web/src/lib/diagnostics-client.ts @@ -180,7 +180,9 @@ export function createDiagnosticsClient(config: DiagnosticsClientConfig) { if (options.limit) params.set('limit', options.limit.toString()); if (options.offset) params.set('offset', options.offset.toString()); - const result = await client.safeFetch(`/api/diagnostics/sessions?${params.toString()}`); + const result = await client.safeFetch( + `/api/diagnostics/sessions?${params.toString()}` + ); if (result.error) throw new Error(result.error); return result.data!; }, @@ -202,11 +204,14 @@ export function createDiagnosticsClient(config: DiagnosticsClientConfig) { }, async updateSession(sessionId: string, request: UpdateSessionRequest): Promise { - const result = await client.safeFetch(`/api/diagnostics/sessions/${sessionId}`, { - method: 'PATCH', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(request), - }); + const result = await client.safeFetch( + `/api/diagnostics/sessions/${sessionId}`, + { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(request), + } + ); if (result.error) throw new Error(result.error); return result.data!; }, @@ -267,7 +272,10 @@ export function createDiagnosticsClient(config: DiagnosticsClientConfig) { // Screenshots // ------------------------------------------------------------------------- - async getScreenshots(sessionId: string, productId: string): Promise< + async getScreenshots( + sessionId: string, + productId: string + ): Promise< Array<{ id: string; blobUrl: string; diff --git a/dashboards/admin-web/src/lib/docker-control.ts b/dashboards/admin-web/src/lib/docker-control.ts index d7cbfdf2..19799adb 100644 --- a/dashboards/admin-web/src/lib/docker-control.ts +++ b/dashboards/admin-web/src/lib/docker-control.ts @@ -54,7 +54,10 @@ export async function restartServiceContainer(serviceId: string): Promise<{ cont throw new Error('Service is not restartable from admin ops'); } - const response = await dockerRequest('POST', `/containers/${encodeURIComponent(container)}/restart?t=10`); + const response = await dockerRequest( + 'POST', + `/containers/${encodeURIComponent(container)}/restart?t=10` + ); if (![204, 304].includes(response.statusCode)) { throw new Error(response.body || `Docker restart failed with status ${response.statusCode}`); } diff --git a/dashboards/admin-web/src/lib/extraction-client.ts b/dashboards/admin-web/src/lib/extraction-client.ts index 97f9a65a..28b98244 100644 --- a/dashboards/admin-web/src/lib/extraction-client.ts +++ b/dashboards/admin-web/src/lib/extraction-client.ts @@ -48,7 +48,7 @@ export interface ExtractionTask { export async function extractText( text: string, taskId?: string, - modelId?: string, + modelId?: string ): Promise { try { return await extractionApi.fetch('/extract', { @@ -66,7 +66,7 @@ export async function extractTranscript(text: string): Promise, - modelId?: string, + modelId?: string ): Promise { try { const result = await extractionApi.fetch<{ results: ExtractResponse[] }>('/extract/batch', { @@ -102,7 +102,9 @@ export async function getTask(id: string): Promise { export async function getSidecarHealth(): Promise<{ status: string; sidecar?: unknown } | null> { try { - return await extractionApi.fetch<{ status: string; sidecar?: unknown }>('/extract/sidecar-health'); + return await extractionApi.fetch<{ status: string; sidecar?: unknown }>( + '/extract/sidecar-health' + ); } catch { return null; } diff --git a/dashboards/admin-web/src/lib/predictive-client.ts b/dashboards/admin-web/src/lib/predictive-client.ts index 574fd192..60f1d20f 100644 --- a/dashboards/admin-web/src/lib/predictive-client.ts +++ b/dashboards/admin-web/src/lib/predictive-client.ts @@ -60,7 +60,9 @@ export async function getProductHealthDetail(productId: string): Promise { - return predictiveApi.fetch(`/predictive/health/${productId}/trends?days=${days}`); + return predictiveApi.fetch( + `/predictive/health/${productId}/trends?days=${days}` + ); } // ── Churn Prediction ───────────────────────────────────────── @@ -89,7 +91,11 @@ export interface ChurnPrediction { }; } -export async function getChurnScore(userId: string, productId: string, horizon = 30): Promise { +export async function getChurnScore( + userId: string, + productId: string, + horizon = 30 +): Promise { return predictiveApi.fetch('/predictive/churn-score', { method: 'POST', body: JSON.stringify({ userId, productId, horizon: String(horizon) }), @@ -106,18 +112,22 @@ export interface AtRiskUser { predictionTimestamp: string; } -export async function getAtRiskUsers(options: { - productId?: string; - segment?: RiskSegment; - limit?: number; - offset?: number; -} = {}): Promise<{ users: AtRiskUser[]; total: number }> { +export async function getAtRiskUsers( + options: { + productId?: string; + segment?: RiskSegment; + limit?: number; + offset?: number; + } = {} +): Promise<{ users: AtRiskUser[]; total: number }> { const params = new URLSearchParams(); if (options.productId) params.set('productId', options.productId); if (options.segment) params.set('segment', options.segment); if (options.limit) params.set('limit', String(options.limit)); if (options.offset) params.set('offset', String(options.offset)); - return predictiveApi.fetch<{ users: AtRiskUser[]; total: number }>(`/predictive/at-risk-users?${params}`); + return predictiveApi.fetch<{ users: AtRiskUser[]; total: number }>( + `/predictive/at-risk-users?${params}` + ); } export interface UserRiskProfile extends ChurnPrediction { @@ -149,8 +159,12 @@ export async function getModelPerformance(): Promise { return predictiveApi.fetch('/predictive/model/performance'); } -export async function getFeatureImportance(): Promise> { - const res = await predictiveApi.fetch<{ features: Array<{ feature: string; importance: number }> }>('/predictive/model/features'); +export async function getFeatureImportance(): Promise< + Array<{ feature: string; importance: number }> +> { + const res = await predictiveApi.fetch<{ + features: Array<{ feature: string; importance: number }>; + }>('/predictive/model/features'); return res.features; } @@ -242,7 +256,10 @@ export async function getCampaignStats(id: string): Promise { return predictiveApi.fetch(`/predictive/campaigns/${id}/stats`); } -export async function triggerCampaign(id: string, testUserId?: string): Promise<{ triggered: number }> { +export async function triggerCampaign( + id: string, + testUserId?: string +): Promise<{ triggered: number }> { return predictiveApi.fetch<{ triggered: number }>(`/predictive/campaigns/${id}/trigger`, { method: 'POST', body: JSON.stringify(testUserId ? { testUserId } : {}),