Skip to content

Commit dbff058

Browse files
authored
Fix any type usage in the saas/ folder (Stirling-Tools#5934)
# Description of Changes Ages ago I made Stirling-Tools#4835 to try and fix all the `any` type usage in the system but never got it finished, and there were just too many to review and ensure it still worked. There's even more now. My new tactic is to fix folder by folder. This fixes the `any` typing in the `saas/` folder, and also enables `no-unnecessary-type-assertion`, which really helps reduce pointless `as` casts that AI generates when the type is already known. I hope to expand both of these to the rest of the folders soon, but one folder is better than none.
1 parent 1722733 commit dbff058

22 files changed

Lines changed: 123 additions & 112 deletions

frontend/eslint.config.mjs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,20 @@ export default defineConfig(
5959
],
6060
},
6161
},
62+
// Folders that have been cleaned up and are now conformant - stricter rules enforced here
63+
{
64+
files: ['src/saas/**/*.{js,mjs,jsx,ts,tsx}'],
65+
languageOptions: {
66+
parserOptions: {
67+
project: true,
68+
tsconfigRootDir: import.meta.dirname,
69+
},
70+
},
71+
rules: {
72+
'@typescript-eslint/no-explicit-any': 'error',
73+
'@typescript-eslint/no-unnecessary-type-assertion': 'error',
74+
},
75+
},
6276
// Config for browser scripts
6377
{
6478
files: srcGlobs,

frontend/src/global.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ declare module 'assets/material-symbols-icons.json' {
1212
export default value;
1313
}
1414

15+
declare global {
16+
interface Window {
17+
__STIRLING_PDF_BASE_URL__?: string;
18+
}
19+
}
20+
1521
declare module 'axios' {
1622
export interface AxiosRequestConfig<_D = unknown> {
1723
suppressErrorToast?: boolean;

frontend/src/saas/auth/UseSession.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
114114
setSubscription(subscriptionInfo)
115115

116116
console.debug('[Auth Debug] Credits fetched successfully:', credits)
117-
} catch (error: any) {
117+
} catch (error: unknown) {
118118
console.debug('[Auth Debug] Failed to fetch credits:', error)
119119
// Don't set error state for credit fetching failures to avoid disrupting auth flow
120120
// Credits might not be available in all deployments
@@ -149,7 +149,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
149149
setIsPro(isProUser)
150150
console.debug('[Auth Debug] Pro status fetched:', isProUser)
151151
}
152-
} catch (error: any) {
152+
} catch (error: unknown) {
153153
console.debug('[Auth Debug] Failed to fetch pro status:', error)
154154
setIsPro(false) // Default to false if there's an error
155155
}
@@ -206,7 +206,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
206206
} else {
207207
setTrialStatus(null)
208208
}
209-
} catch (error: any) {
209+
} catch (error: unknown) {
210210
console.debug('[Auth Debug] Failed to fetch trial status:', error)
211211
setTrialStatus(null)
212212
}
@@ -243,7 +243,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
243243
setProfilePictureUrl(data.signedUrl)
244244
console.debug('[Auth Debug] Profile picture URL fetched successfully')
245245
}
246-
} catch (error: any) {
246+
} catch (error: unknown) {
247247
console.debug('[Auth Debug] Failed to fetch profile picture:', error)
248248
setProfilePictureUrl(null)
249249
}
@@ -267,7 +267,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
267267
const metadata = await getProfilePictureMetadata(currentSession.user.id)
268268
setProfilePictureMetadata(metadata)
269269
console.debug('[Auth Debug] Profile picture metadata fetched:', metadata)
270-
} catch (error: any) {
270+
} catch (error: unknown) {
271271
console.debug('[Auth Debug] Failed to fetch profile picture metadata:', error)
272272
setProfilePictureMetadata(null)
273273
}

frontend/src/saas/auth/supabase.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export const signInAnonymously = async () => {
8383
// Account linking functions
8484
export const linkEmailIdentity = async (email: string, password?: string) => {
8585
try {
86-
const updateData: any = { email }
86+
const updateData: { email: string; password?: string } = { email }
8787
if (password) {
8888
updateData.password = password
8989
}
@@ -143,7 +143,7 @@ export const linkOAuthIdentity = async (provider: 'google' | 'github' | 'apple'
143143
}
144144

145145
// Helper function to check if user is anonymous
146-
export const isUserAnonymous = (user: any) => {
146+
export const isUserAnonymous = (user: { is_anonymous?: boolean }) => {
147147
return user?.is_anonymous === true
148148
}
149149

frontend/src/saas/components/shared/ManageBillingButton.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,16 @@ export function ManageBillingButton({
3838
setLoading(true);
3939
setErr(null);
4040
try {
41-
const { data, error } = await supabase.functions.invoke('manage-billing', {
41+
const { data, error } = await supabase.functions.invoke<{ url: string; error?: string }>('manage-billing', {
4242
body: {
4343
name: 'Functions',
4444
return_url: returnUrl},
4545
})
4646
if (error) throw error;
47-
if (!data || 'error' in data) throw new Error((data as any)?.error ?? 'No portal URL');
48-
window.location.href = (data as any).url;
49-
} catch (e: any) {
50-
setErr(e.message ?? 'Could not open billing portal');
47+
if (!data || 'error' in data) throw new Error(data?.error ?? 'No portal URL');
48+
window.location.href = data.url;
49+
} catch (e: unknown) {
50+
setErr(e instanceof Error ? e.message : 'Could not open billing portal');
5151
} finally {
5252
setLoading(false);
5353
}

frontend/src/saas/components/shared/StripeCheckoutSaas.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ interface StripeCheckoutProps {
2424
currency?: string;
2525
isTrialConversion?: boolean;
2626
// Proprietary-specific props (for compatibility)
27-
planGroup?: any;
27+
planGroup?: unknown;
2828
minimumSeats?: number;
2929
onLicenseActivated?: (licenseInfo: {licenseType: string; enabled: boolean; maxUsers: number; hasKey: boolean}) => void;
3030
hostedCheckoutSuccess?: {

frontend/src/saas/components/shared/charts/utils/d3Utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ export function createScale(domain: [number, number], range: [number, number]) {
162162
* @param wait The wait time in milliseconds
163163
* @returns Debounced function
164164
*/
165-
export function debounce<T extends (...args: any[]) => any>(
165+
export function debounce<T extends (...args: unknown[]) => unknown>(
166166
func: T,
167167
wait: number
168168
): (...args: Parameters<T>) => void {

frontend/src/saas/components/shared/config/configSections/Overview.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,8 @@ const Overview: React.FC<OverviewProps> = ({ onLogoutClick }) => {
149149

150150
// Clear success message after 3 seconds
151151
setTimeout(() => setSuccess(null), 3000);
152-
} catch (error: any) {
153-
setProfileError(error.message || 'Failed to switch to custom picture');
152+
} catch (error: unknown) {
153+
setProfileError(error instanceof Error ? error.message : 'Failed to switch to custom picture');
154154
} finally {
155155
setProfileUploading(false);
156156
}
@@ -181,8 +181,8 @@ const Overview: React.FC<OverviewProps> = ({ onLogoutClick }) => {
181181
setSuccess('Account upgraded successfully! You can now sign in with your email.');
182182
setEmail('');
183183
setPassword('');
184-
} catch (err: any) {
185-
setUpgradeError(err?.message || 'Failed to upgrade account');
184+
} catch (err: unknown) {
185+
setUpgradeError(err instanceof Error ? err.message : 'Failed to upgrade account');
186186
} finally {
187187
setIsLoading(false);
188188
}
@@ -404,7 +404,7 @@ const Overview: React.FC<OverviewProps> = ({ onLogoutClick }) => {
404404
style={{ width: 16, height: 16 }}
405405
/>
406406
}
407-
onClick={() => handleOAuthUpgrade(provider.id as any)}
407+
onClick={() => handleOAuthUpgrade(provider.id as 'github' | 'google' | 'apple' | 'azure')}
408408
disabled={isLoading}
409409
>
410410
{provider.label}

frontend/src/saas/components/shared/config/configSections/PasswordSecurity.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ const PasswordSecurity: React.FC = () => {
5555
setOpened(false);
5656
setDidUpdate(false);
5757
}, 2000);
58-
} catch (e: any) {
59-
setError(e?.message || 'Failed to change password');
58+
} catch (e: unknown) {
59+
setError(e instanceof Error ? e.message : 'Failed to change password');
6060
} finally {
6161
setIsLoading(false);
6262
}

frontend/src/saas/components/shared/config/configSections/apiKeys/hooks/useApiKey.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import { useCallback, useEffect, useState } from "react";
2+
import { isAxiosError } from "axios";
23
import apiClient from "@app/services/apiClient";
34
import { useAuth } from "@app/auth/UseSession";
45
import { isUserAnonymous } from "@app/auth/supabase";
56

7+
type ApiKeyResponse = string | { apiKey?: string };
8+
69
export function useApiKey() {
710
const { session, loading, user } = useAuth();
811
const isAnonymous = Boolean(user && isUserAnonymous(user));
@@ -17,24 +20,21 @@ export function useApiKey() {
1720
setError(null);
1821
try {
1922
// Backend is POST for get and update
20-
const res = await apiClient.post("/api/v1/user/get-api-key");
21-
const value = typeof res.data === "string" ? res.data : res.data?.apiKey;
23+
const res = await apiClient.post<ApiKeyResponse>("/api/v1/user/get-api-key");
24+
const value = typeof res.data === "string" ? res.data : res.data.apiKey;
2225
if (typeof value === "string") setApiKey(value);
23-
} catch (e: any) {
26+
} catch (e: unknown) {
2427
// If not found, try to create one by calling update endpoint
25-
if (e?.response?.status === 404) {
28+
if (isAxiosError(e) && e.response?.status === 404) {
2629
try {
27-
const createRes = await apiClient.post("/api/v1/user/update-api-key");
28-
const created =
29-
typeof createRes.data === "string"
30-
? createRes.data
31-
: createRes.data?.apiKey;
30+
const createRes = await apiClient.post<ApiKeyResponse>("/api/v1/user/update-api-key");
31+
const created = typeof createRes.data === "string" ? createRes.data : createRes.data.apiKey;
3232
if (typeof created === "string") setApiKey(created);
33-
} catch (createErr: any) {
34-
setError(createErr);
33+
} catch (createErr: unknown) {
34+
setError(createErr instanceof Error ? createErr : new Error(String(createErr)));
3535
}
3636
} else {
37-
setError(e);
37+
setError(e instanceof Error ? e : new Error(String(e)));
3838
}
3939
} finally {
4040
setIsLoading(false);
@@ -46,11 +46,11 @@ export function useApiKey() {
4646
setIsRefreshing(true);
4747
setError(null);
4848
try {
49-
const res = await apiClient.post("/api/v1/user/update-api-key");
50-
const value = typeof res.data === "string" ? res.data : res.data?.apiKey;
49+
const res = await apiClient.post<ApiKeyResponse>("/api/v1/user/update-api-key");
50+
const value = typeof res.data === "string" ? res.data : res.data.apiKey;
5151
if (typeof value === "string") setApiKey(value);
52-
} catch (e: any) {
53-
setError(e);
52+
} catch (e: unknown) {
53+
setError(e instanceof Error ? e : new Error(String(e)));
5454
} finally {
5555
setIsRefreshing(false);
5656
}

0 commit comments

Comments
 (0)