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",
"postcss": "^8.5.6",
"tailwindcss": "^4.1.16",
"typescript": "~5.8.2",
"typescript": "7.0.2",
"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.grandTotalValue}>{formatCurrency(invoice.total)}</Text>
</View>
{invoice.amountPaid > 0 && (
{invoice.amountPaid !== undefined && invoice.amountPaid > 0 && (
<View style={styles.totalRow}>
<Text style={styles.totalLabel}>Amount Paid</Text>
<Text style={styles.totalValue}>{formatCurrency(invoice.amountPaid)}</Text>
+19 -10
View File
@@ -54,6 +54,15 @@ export interface MetricCard {
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
export async function getRevenueData(
startDate: Date,
@@ -92,7 +101,7 @@ export async function getRevenueData(
});
// Aggregate orders into periods
orders?.forEach(order => {
orders?.forEach((order: RevenueRow) => {
const orderDate = new Date(order.created_at);
let key: string;
@@ -198,7 +207,7 @@ export async function getCustomerGrowth(
});
// Count new customers per day
customers?.forEach(customer => {
customers?.forEach((customer: DatedRow) => {
const date = format(new Date(customer.created_at), 'yyyy-MM-dd');
const growth = dailyGrowth.get(date);
if (growth) {
@@ -228,7 +237,7 @@ export async function getGPUTypeBreakdown(
const breakdown = new Map<string, GPUTypeBreakdown>();
let totalRevenue = 0;
orders.forEach(order => {
orders.forEach((order: GPURow) => {
const type = order.gpu_type || 'Unknown';
const existing = breakdown.get(type) || {
gpuType: type,
@@ -322,7 +331,7 @@ export async function getQuotePerformance(
});
// 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 perf = performanceMap.get(date);
@@ -370,13 +379,13 @@ export async function getAverageDealValue(
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 byGPUType = new Map<string, number>();
const gpuTypeTotals = new Map<string, { total: number; count: number }>();
orders.forEach(order => {
orders.forEach((order: GPURow) => {
const type = order.gpu_type || 'Unknown';
const existing = gpuTypeTotals.get(type) || { total: 0, count: 0 };
existing.total += order.total;
@@ -449,8 +458,8 @@ export async function getKeyMetrics(period: '7d' | '30d' | '90d' | '1y' = '30d')
.gte('created_at', previousStartDate.toISOString())
.lte('created_at', previousEndDate.toISOString());
const currentRevenue = currentOrders?.reduce((sum, order) => sum + order.total, 0) || 0;
const previousRevenue = previousOrders?.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: number, order: TotalRow) => sum + order.total, 0) || 0;
const revenueChange = previousRevenue > 0 ? ((currentRevenue - previousRevenue) / previousRevenue) * 100 : 0;
const currentOrderCount = currentOrders?.length || 0;
@@ -524,8 +533,8 @@ export async function getResponseTimeMetrics(
['> 3 days', 0]
]);
rfqs.forEach(rfq => {
const quote = quotes.find(q => q.rfq_id === rfq.id);
rfqs.forEach((rfq: DatedRow) => {
const quote = quotes.find((q: QuoteLinkRow) => q.rfq_id === rfq.id);
if (quote) {
const rfqTime = new Date(rfq.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
const invoicedOrderIds = new Set(invoices.map(inv => inv.order_id));
return orders.filter(order => !invoicedOrderIds.has(order.id));
const invoicedOrderIds = new Set(invoices.map((inv: { order_id: string }) => inv.order_id));
return orders.filter((order: Order) => !invoicedOrderIds.has(order.id));
} catch (error) {
console.error('Error fetching orders without invoices:', error);
return [];
@@ -335,7 +335,7 @@ export const markInvoiceAsPaid = async (
.select('amount_due')
.eq('id', invoiceId)
.single()
.then(res => res.data?.amount_due || 0),
.then((res: { data: { amount_due: number } | null }) => res.data?.amount_due || 0),
amount_due: 0
})
.eq('id', invoiceId)
+2 -2
View File
@@ -24,7 +24,7 @@ const API_BASE_URL = import.meta.env.VITE_API_URL || '/api';
interface RequestOptions {
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
body?: any;
headers?: HeadersInit;
headers?: Record<string, string>;
retries?: number;
retryDelay?: number;
}
@@ -53,7 +53,7 @@ export async function apiRequest<T = any>(
// Get current session for authentication
const { data: { session } } = await supabase.auth.getSession();
const requestHeaders: HeadersInit = {
const requestHeaders: Record<string, string> = {
'Content-Type': 'application/json',
...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);
const quoteData = {
rfq_id: selectedRFQ || null,
cluster_request_id: selectedClusterRequest || null,
rfq_id: selectedRFQ || undefined,
cluster_request_id: selectedClusterRequest || undefined,
quote_number: generateQuoteNumber(),
items: quoteItems.filter(item => item.description),
subtotal,
@@ -433,7 +433,7 @@ ${revenueData.map(d => `${d.date},${d.revenue},${d.orders}`).join('\n')}`;
total,
payment_terms: paymentTerms,
valid_until: validUntil.toISOString(),
notes: quoteNotes || null,
notes: quoteNotes || undefined,
};
await createQuote(quoteData);
+2 -5
View File
@@ -60,13 +60,10 @@ const SignUp: React.FC = () => {
if (error) throw error;
if (data.user) {
// Check if email confirmation is required
if (data.user.identities && data.user.identities.length === 0) {
setError('Please check your email to confirm your account.');
} else {
// IAM owns email confirmation, and signUp redirects there rather than
// returning a user, so there is nothing to probe here.
navigate('/dashboard');
}
}
} catch (err: any) {
setError(err.message || 'An error occurred during sign up');
} finally {