Skip to content

Commit 6ad6d4b

Browse files
committed
add modified App.tsx, removed Debug.jsx modified queryClient.ts and SnippetContext.ts
1 parent a9bff5a commit 6ad6d4b

5 files changed

Lines changed: 154 additions & 30 deletions

File tree

client/src/App.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import CollectionDetail from "@/pages/CollectionDetail";
1616
import Tags from "@/pages/Tags";
1717
import Settings from "@/pages/Settings";
1818
import SharedSnippet from "@/pages/SharedSnippet";
19-
import { DebugEnv } from "./components/Debug";
2019

2120
function Router() {
2221
return (
@@ -138,7 +137,6 @@ export default function App() {
138137
<CollectionProvider>
139138
<TooltipProvider>
140139
<Router />
141-
<DebugEnv />
142140
{showDebug && <AuthDebug user={user} loading={loading} />}
143141
</TooltipProvider>
144142
</CollectionProvider>

client/src/components/Debug.jsx

Lines changed: 0 additions & 22 deletions
This file was deleted.

client/src/contexts/SnippetContext.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ export function SnippetProvider({ children }: { children: ReactNode }) {
9191
// Delete snippet mutation
9292
const deleteSnippetMutation = useMutation({
9393
mutationFn: async (id: number) => {
94-
await apiRequest("DELETE", `/api/snippets/${id}`);
94+
await apiRequest("DELETE", `/api/snippets/${id}`, undefined, { expectJson: false });
9595
},
9696
onSuccess: () => {
9797
queryClient.invalidateQueries({ queryKey: ["/api/snippets"] });

client/src/lib/queryClient.ts

Lines changed: 93 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { QueryClient, QueryFunction } from "@tanstack/react-query";
2+
import { auth } from "@/lib/firebase";
23

34
async function throwIfResNotOk(res: Response) {
45
if (!res.ok) {
@@ -7,18 +8,88 @@ async function throwIfResNotOk(res: Response) {
78
}
89
}
910

11+
interface ApiRequestOptions {
12+
expectJson?: boolean;
13+
}
14+
1015
export async function apiRequest<T = any>(
16+
method: string,
1117
url: string,
12-
options?: RequestInit
18+
data?: any,
19+
options: ApiRequestOptions = {}
1320
): Promise<T> {
21+
// Get the Firebase ID token if the user is signed in
22+
let token = null;
23+
try {
24+
// Get the current user and their ID token
25+
const currentUser = auth.currentUser;
26+
if (currentUser) {
27+
token = await currentUser.getIdToken();
28+
console.log("[apiRequest] Got Firebase ID token, length:", token.length);
29+
} else {
30+
console.log("[apiRequest] No current user found");
31+
}
32+
} catch (e) {
33+
console.error("[apiRequest] Error getting Firebase ID token:", e);
34+
token = null;
35+
}
36+
37+
// Build headers
38+
const headers: Record<string, string> = {};
39+
40+
// Only add Content-Type if we have data to send
41+
if (data) {
42+
headers["Content-Type"] = "application/json";
43+
}
44+
45+
// Add Authorization header if we have a token
46+
if (token) {
47+
headers["Authorization"] = `Bearer ${token}`;
48+
console.log("[apiRequest] Adding Authorization header to request");
49+
} else {
50+
console.log("[apiRequest] No token available, making unauthenticated request");
51+
}
52+
53+
console.log(`[apiRequest] Making ${method} request to ${url}`);
54+
1455
const res = await fetch(url, {
15-
headers: options?.body ? { "Content-Type": "application/json" } : {},
56+
method,
57+
headers,
58+
body: data ? JSON.stringify(data) : undefined,
1659
credentials: "include",
17-
...options
1860
});
1961

20-
await throwIfResNotOk(res);
21-
return await res.json();
62+
// Defensive: check for JSON response
63+
const contentType = res.headers.get("content-type");
64+
65+
// If we explicitly don't expect JSON or it's a 204 No Content, return early
66+
if (options.expectJson === false || res.status === 204) {
67+
if (!res.ok) {
68+
const text = (await res.text()) || res.statusText;
69+
throw new Error(`${res.status}: ${text}`);
70+
}
71+
console.log(`[apiRequest] ${method} ${url} completed successfully (no JSON response)`);
72+
return undefined as T;
73+
}
74+
75+
if (contentType && contentType.includes("application/json")) {
76+
const json = await res.json();
77+
if (!res.ok) {
78+
console.error(`[apiRequest] ${method} ${url} failed:`, json);
79+
throw new Error(json.message || `API error: ${res.status}`);
80+
}
81+
console.log(`[apiRequest] ${method} ${url} completed successfully`);
82+
return json;
83+
} else {
84+
// Not JSON, likely an error page
85+
if (!res.ok) {
86+
const text = await res.text();
87+
console.error(`[apiRequest] ${method} ${url} failed with non-JSON response:`, text.slice(0, 100));
88+
throw new Error(`${res.status}: Unexpected response from server: ${text.slice(0, 100)}`);
89+
}
90+
const text = await res.text();
91+
throw new Error("Unexpected response from server: " + text.slice(0, 100));
92+
}
2293
}
2394

2495
type UnauthorizedBehavior = "returnNull" | "throw";
@@ -27,7 +98,24 @@ export const getQueryFn: <T>(options: {
2798
}) => QueryFunction<T> =
2899
({ on401: unauthorizedBehavior }) =>
29100
async ({ queryKey }) => {
101+
// For queries, we also need to attach the auth token
102+
let token = null;
103+
try {
104+
const currentUser = auth.currentUser;
105+
if (currentUser) {
106+
token = await currentUser.getIdToken();
107+
}
108+
} catch (e) {
109+
console.error("[getQueryFn] Error getting Firebase ID token:", e);
110+
}
111+
112+
const headers: Record<string, string> = {};
113+
if (token) {
114+
headers["Authorization"] = `Bearer ${token}`;
115+
}
116+
30117
const res = await fetch(queryKey[0] as string, {
118+
headers,
31119
credentials: "include",
32120
});
33121

client/src/lib/safe-classname.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// CREATE THIS FILE: client/src/lib/safe-classname.ts
2+
3+
/**
4+
* Safely converts any className value to a string
5+
* Handles objects, arrays, and other non-string values
6+
*/
7+
export function safeClassName(className: any): string {
8+
if (typeof className === 'string') {
9+
return className;
10+
}
11+
12+
if (className === null || className === undefined) {
13+
return '';
14+
}
15+
16+
// Handle SVGAnimatedString (the main culprit)
17+
if (className && typeof className === 'object' && 'baseVal' in className) {
18+
return className.baseVal || '';
19+
}
20+
21+
// Handle arrays
22+
if (Array.isArray(className)) {
23+
return className.map(safeClassName).filter(Boolean).join(' ');
24+
}
25+
26+
// Handle objects (CSS modules, styled-components)
27+
if (typeof className === 'object') {
28+
return Object.keys(className)
29+
.filter(key => className[key])
30+
.join(' ');
31+
}
32+
33+
// Handle functions, numbers, booleans
34+
if (typeof className === 'function') {
35+
return '';
36+
}
37+
38+
return String(className);
39+
}
40+
41+
/**
42+
* Enhanced cn function that safely handles all className types
43+
*/
44+
import { type ClassValue, clsx } from "clsx";
45+
import { twMerge } from "tailwind-merge";
46+
47+
export function cn(...inputs: ClassValue[]): string {
48+
// First, make all inputs safe
49+
const safeInputs = inputs.map(input => {
50+
if (typeof input === 'string' || typeof input === 'undefined' || input === null) {
51+
return input;
52+
}
53+
return safeClassName(input);
54+
});
55+
56+
return twMerge(clsx(safeInputs));
57+
}
58+
59+
// Export the original cn as well for backwards compatibility
60+
export { clsx, twMerge };

0 commit comments

Comments
 (0)