Compile with TypeScript 7
Hanzo CI/CD / cicd (push) Failing after 4m30s
CI/CD / cicd (push) Failing after 4m31s

typescript 7.0.2 is the native compiler, so this is a pin and not a second
toolchain. tsc --noEmit on tsconfig.json: 15 diagnostics before and after, the
same pre-existing set on both compilers.

TypeScript 7 checks strictly by default, which surfaced 24 real gaps this
tsconfig had been hiding. No compiler setting moved to quiet them: the query
callbacks in adminAnalytics and adminInvoiceActions now name the row each select
projects, request headers are a Record<string, string> rather than a HeadersInit
that cannot be indexed, an optional amountPaid is checked before it is read, a
quote passes undefined rather than null for its optional columns, and sign-up no
longer probes an identities field that IAM never returns.

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
2026-09-16 22:04:48 -07:00
co-authored by Hanzo Dev
parent 18225c89a3
commit 984339eeaf
7 changed files with 32 additions and 26 deletions
+1 -1
View File
@@ -38,7 +38,7 @@
"node-mocks-http": "^1.15.1", "node-mocks-http": "^1.15.1",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"tailwindcss": "^4.1.16", "tailwindcss": "^4.1.16",
"typescript": "~5.8.2", "typescript": "7.0.2",
"vite": "^6.2.0" "vite": "^6.2.0"
} }
} }
+1 -1
View File
@@ -322,7 +322,7 @@ export const InvoiceDocument: React.FC<{ invoice: InvoiceData }> = ({ invoice })
<Text style={styles.grandTotalLabel}>Total</Text> <Text style={styles.grandTotalLabel}>Total</Text>
<Text style={styles.grandTotalValue}>{formatCurrency(invoice.total)}</Text> <Text style={styles.grandTotalValue}>{formatCurrency(invoice.total)}</Text>
</View> </View>
{invoice.amountPaid > 0 && ( {invoice.amountPaid !== undefined && invoice.amountPaid > 0 && (
<View style={styles.totalRow}> <View style={styles.totalRow}>
<Text style={styles.totalLabel}>Amount Paid</Text> <Text style={styles.totalLabel}>Amount Paid</Text>
<Text style={styles.totalValue}>{formatCurrency(invoice.amountPaid)}</Text> <Text style={styles.totalValue}>{formatCurrency(invoice.amountPaid)}</Text>
+19 -10
View File
@@ -54,6 +54,15 @@ export interface MetricCard {
suffix?: string; suffix?: string;
} }
// The query client hands back untyped rows, so each callback below names the
// columns its own select projects.
interface RevenueRow { created_at: string; total: number; gpu_type: string | null }
interface GPURow { gpu_type: string | null; total: number }
interface TotalRow { total: number }
interface StatusRow { created_at: string; status: string }
interface DatedRow { id: string; created_at: string }
interface QuoteLinkRow { rfq_id: string; created_at: string }
// Get revenue data grouped by time period // Get revenue data grouped by time period
export async function getRevenueData( export async function getRevenueData(
startDate: Date, startDate: Date,
@@ -92,7 +101,7 @@ export async function getRevenueData(
}); });
// Aggregate orders into periods // Aggregate orders into periods
orders?.forEach(order => { orders?.forEach((order: RevenueRow) => {
const orderDate = new Date(order.created_at); const orderDate = new Date(order.created_at);
let key: string; let key: string;
@@ -198,7 +207,7 @@ export async function getCustomerGrowth(
}); });
// Count new customers per day // Count new customers per day
customers?.forEach(customer => { customers?.forEach((customer: DatedRow) => {
const date = format(new Date(customer.created_at), 'yyyy-MM-dd'); const date = format(new Date(customer.created_at), 'yyyy-MM-dd');
const growth = dailyGrowth.get(date); const growth = dailyGrowth.get(date);
if (growth) { if (growth) {
@@ -228,7 +237,7 @@ export async function getGPUTypeBreakdown(
const breakdown = new Map<string, GPUTypeBreakdown>(); const breakdown = new Map<string, GPUTypeBreakdown>();
let totalRevenue = 0; let totalRevenue = 0;
orders.forEach(order => { orders.forEach((order: GPURow) => {
const type = order.gpu_type || 'Unknown'; const type = order.gpu_type || 'Unknown';
const existing = breakdown.get(type) || { const existing = breakdown.get(type) || {
gpuType: type, gpuType: type,
@@ -322,7 +331,7 @@ export async function getQuotePerformance(
}); });
// Aggregate quotes by day and status // Aggregate quotes by day and status
quotes.forEach(quote => { quotes.forEach((quote: StatusRow) => {
const date = format(new Date(quote.created_at), 'yyyy-MM-dd'); const date = format(new Date(quote.created_at), 'yyyy-MM-dd');
const perf = performanceMap.get(date); const perf = performanceMap.get(date);
@@ -370,13 +379,13 @@ export async function getAverageDealValue(
return { overall: 0, byGPUType: new Map() }; return { overall: 0, byGPUType: new Map() };
} }
const totalRevenue = orders.reduce((sum, order) => sum + order.total, 0); const totalRevenue = orders.reduce((sum: number, order: GPURow) => sum + order.total, 0);
const overall = totalRevenue / orders.length; const overall = totalRevenue / orders.length;
const byGPUType = new Map<string, number>(); const byGPUType = new Map<string, number>();
const gpuTypeTotals = new Map<string, { total: number; count: number }>(); const gpuTypeTotals = new Map<string, { total: number; count: number }>();
orders.forEach(order => { orders.forEach((order: GPURow) => {
const type = order.gpu_type || 'Unknown'; const type = order.gpu_type || 'Unknown';
const existing = gpuTypeTotals.get(type) || { total: 0, count: 0 }; const existing = gpuTypeTotals.get(type) || { total: 0, count: 0 };
existing.total += order.total; existing.total += order.total;
@@ -449,8 +458,8 @@ export async function getKeyMetrics(period: '7d' | '30d' | '90d' | '1y' = '30d')
.gte('created_at', previousStartDate.toISOString()) .gte('created_at', previousStartDate.toISOString())
.lte('created_at', previousEndDate.toISOString()); .lte('created_at', previousEndDate.toISOString());
const currentRevenue = currentOrders?.reduce((sum, order) => sum + order.total, 0) || 0; const currentRevenue = currentOrders?.reduce((sum: number, order: TotalRow) => sum + order.total, 0) || 0;
const previousRevenue = previousOrders?.reduce((sum, order) => sum + order.total, 0) || 0; const previousRevenue = previousOrders?.reduce((sum: number, order: TotalRow) => sum + order.total, 0) || 0;
const revenueChange = previousRevenue > 0 ? ((currentRevenue - previousRevenue) / previousRevenue) * 100 : 0; const revenueChange = previousRevenue > 0 ? ((currentRevenue - previousRevenue) / previousRevenue) * 100 : 0;
const currentOrderCount = currentOrders?.length || 0; const currentOrderCount = currentOrders?.length || 0;
@@ -524,8 +533,8 @@ export async function getResponseTimeMetrics(
['> 3 days', 0] ['> 3 days', 0]
]); ]);
rfqs.forEach(rfq => { rfqs.forEach((rfq: DatedRow) => {
const quote = quotes.find(q => q.rfq_id === rfq.id); const quote = quotes.find((q: QuoteLinkRow) => q.rfq_id === rfq.id);
if (quote) { if (quote) {
const rfqTime = new Date(rfq.created_at).getTime(); const rfqTime = new Date(rfq.created_at).getTime();
const quoteTime = new Date(quote.created_at).getTime(); const quoteTime = new Date(quote.created_at).getTime();
+3 -3
View File
@@ -309,8 +309,8 @@ export const getOrdersWithoutInvoices = async (): Promise<Order[]> => {
} }
// Filter orders that don't have invoices // Filter orders that don't have invoices
const invoicedOrderIds = new Set(invoices.map(inv => inv.order_id)); const invoicedOrderIds = new Set(invoices.map((inv: { order_id: string }) => inv.order_id));
return orders.filter(order => !invoicedOrderIds.has(order.id)); return orders.filter((order: Order) => !invoicedOrderIds.has(order.id));
} catch (error) { } catch (error) {
console.error('Error fetching orders without invoices:', error); console.error('Error fetching orders without invoices:', error);
return []; return [];
@@ -335,7 +335,7 @@ export const markInvoiceAsPaid = async (
.select('amount_due') .select('amount_due')
.eq('id', invoiceId) .eq('id', invoiceId)
.single() .single()
.then(res => res.data?.amount_due || 0), .then((res: { data: { amount_due: number } | null }) => res.data?.amount_due || 0),
amount_due: 0 amount_due: 0
}) })
.eq('id', invoiceId) .eq('id', invoiceId)
+2 -2
View File
@@ -24,7 +24,7 @@ const API_BASE_URL = import.meta.env.VITE_API_URL || '/api';
interface RequestOptions { interface RequestOptions {
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
body?: any; body?: any;
headers?: HeadersInit; headers?: Record<string, string>;
retries?: number; retries?: number;
retryDelay?: number; retryDelay?: number;
} }
@@ -53,7 +53,7 @@ export async function apiRequest<T = any>(
// Get current session for authentication // Get current session for authentication
const { data: { session } } = await supabase.auth.getSession(); const { data: { session } } = await supabase.auth.getSession();
const requestHeaders: HeadersInit = { const requestHeaders: Record<string, string> = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
...headers, ...headers,
}; };
+3 -3
View File
@@ -424,8 +424,8 @@ ${revenueData.map(d => `${d.date},${d.revenue},${d.orders}`).join('\n')}`;
validUntil.setDate(validUntil.getDate() + quoteValidDays); validUntil.setDate(validUntil.getDate() + quoteValidDays);
const quoteData = { const quoteData = {
rfq_id: selectedRFQ || null, rfq_id: selectedRFQ || undefined,
cluster_request_id: selectedClusterRequest || null, cluster_request_id: selectedClusterRequest || undefined,
quote_number: generateQuoteNumber(), quote_number: generateQuoteNumber(),
items: quoteItems.filter(item => item.description), items: quoteItems.filter(item => item.description),
subtotal, subtotal,
@@ -433,7 +433,7 @@ ${revenueData.map(d => `${d.date},${d.revenue},${d.orders}`).join('\n')}`;
total, total,
payment_terms: paymentTerms, payment_terms: paymentTerms,
valid_until: validUntil.toISOString(), valid_until: validUntil.toISOString(),
notes: quoteNotes || null, notes: quoteNotes || undefined,
}; };
await createQuote(quoteData); await createQuote(quoteData);
+3 -6
View File
@@ -60,12 +60,9 @@ const SignUp: React.FC = () => {
if (error) throw error; if (error) throw error;
if (data.user) { if (data.user) {
// Check if email confirmation is required // IAM owns email confirmation, and signUp redirects there rather than
if (data.user.identities && data.user.identities.length === 0) { // returning a user, so there is nothing to probe here.
setError('Please check your email to confirm your account.'); navigate('/dashboard');
} else {
navigate('/dashboard');
}
} }
} catch (err: any) { } catch (err: any) {
setError(err.message || 'An error occurred during sign up'); setError(err.message || 'An error occurred during sign up');