@@ -8,6 +8,8 @@ import { Label } from "@/components/ui/label";
88import { useToast } from "@/hooks/use-toast" ;
99import { queryClient } from "@/lib/queryClient" ;
1010import { UploadCloud , Download , AlertCircle } from "lucide-react" ;
11+ import { getAuth } from "firebase/auth" ;
12+ import { useAuthContext } from "@/contexts/AuthContext" ;
1113
1214interface ImportExportDialogProps {
1315 open : boolean ;
@@ -20,6 +22,7 @@ export default function ImportExportDialog({
2022} : ImportExportDialogProps ) {
2123 const { toast } = useToast ( ) ;
2224 const queryClient = useQueryClient ( ) ;
25+ const { user } = useAuthContext ( ) ;
2326 const [ activeTab , setActiveTab ] = useState ( "import" ) ;
2427 const [ importData , setImportData ] = useState ( "" ) ;
2528 const [ isLoading , setIsLoading ] = useState ( false ) ;
@@ -31,6 +34,37 @@ export default function ImportExportDialog({
3134 setIsLoading ( true ) ;
3235 setError ( null ) ;
3336
37+ // Ensure user is authenticated
38+ if ( ! user ) {
39+ setError ( "You must be logged in to import snippets." ) ;
40+ setIsLoading ( false ) ;
41+ return ;
42+ }
43+
44+ // Get Firebase auth instance and current user
45+ const auth = getAuth ( ) ;
46+ const firebaseUser = auth . currentUser ;
47+
48+ // Check if Firebase user exists
49+ if ( ! firebaseUser ) {
50+ console . error ( "Firebase user not found but local user exists" ) ;
51+ setError ( "Authentication error. Please try logging in again." ) ;
52+ setIsLoading ( false ) ;
53+ return ;
54+ }
55+
56+ // Get id token
57+ let token ;
58+ try {
59+ token = await firebaseUser . getIdToken ( true ) ;
60+ console . log ( "Got Firebase ID token for API request" ) ;
61+ } catch ( tokenError ) {
62+ console . error ( "Failed to get ID token:" , tokenError ) ;
63+ setError ( "Authentication token error. Please refresh the page and try again." ) ;
64+ setIsLoading ( false ) ;
65+ return ;
66+ }
67+
3468 // Validate JSON format
3569 let snippetsData ;
3670 try {
@@ -42,25 +76,51 @@ export default function ImportExportDialog({
4276 }
4377
4478 // Ensure snippets is an array
45- const snippets = Array . isArray ( snippetsData ) ? snippetsData : [ snippetsData ] ;
79+ const rawSnippets = Array . isArray ( snippetsData ) ? snippetsData : [ snippetsData ] ;
4680
47- // Send to API
81+ // Clean the snippets to remove any ID fields and auto-generated fields
82+ const cleanSnippets = rawSnippets . map ( snippet => {
83+ // Only keep the fields we want to import
84+ return {
85+ title : snippet . title || "Untitled Snippet" ,
86+ code : snippet . code || "" ,
87+ language : snippet . language || "text" ,
88+ description : snippet . description || "" ,
89+ tags : Array . isArray ( snippet . tags ) ? snippet . tags : [ ] ,
90+ // User ID will be set on the server side from the auth token
91+ } ;
92+ } ) ;
93+
94+ console . log ( `Importing ${ cleanSnippets . length } snippets...` ) ;
95+
96+ // Send to API with auth token
4897 const response = await fetch ( "/api/snippets/import" , {
4998 method : "POST" ,
5099 headers : {
51- "Content-Type" : "application/json"
100+ "Content-Type" : "application/json" ,
101+ "Authorization" : `Bearer ${ token } `
52102 } ,
53- body : JSON . stringify ( { snippets } )
103+ body : JSON . stringify ( { snippets : cleanSnippets } )
54104 } ) ;
55105
106+ // Handle non-2xx responses
56107 if ( ! response . ok ) {
57- throw new Error ( "Failed to import snippets" ) ;
108+ const errorText = await response . text ( ) ;
109+ console . error ( "Import response error:" , response . status , errorText ) ;
110+ throw new Error ( `Server returned ${ response . status } : ${ response . statusText || errorText } ` ) ;
58111 }
59112
60113 const result = await response . json ( ) ;
114+ console . log ( "Import successful:" , result ) ;
115+
116+ // Refresh snippets data - try different query keys
117+ queryClient . invalidateQueries ( { queryKey : [ 'snippets' ] } ) ;
118+ queryClient . invalidateQueries ( { queryKey : [ '/api/snippets' ] } ) ;
61119
62- // Refresh snippets data
63- queryClient . invalidateQueries ( { queryKey : [ "/api/snippets" ] } ) ;
120+ // Force a complete refresh after a short delay
121+ setTimeout ( ( ) => {
122+ window . location . reload ( ) ;
123+ } , 1000 ) ;
64124
65125 // Show success message
66126 toast ( {
@@ -70,25 +130,75 @@ export default function ImportExportDialog({
70130
71131 // Close dialog
72132 onOpenChange ( false ) ;
73- } catch ( err ) {
74- setError ( "Failed to import snippets. Please try again." ) ;
133+ } catch ( err : any ) {
134+ const errorMessage = err . message || "Failed to import snippets" ;
135+ setError ( `${ errorMessage } . Please try again.` ) ;
75136 console . error ( "Import error:" , err ) ;
76137 } finally {
77138 setIsLoading ( false ) ;
78139 }
79140 } ;
80141
81142 // Handle Export
82- const handleExport = ( ) => {
143+ const handleExport = async ( ) => {
83144 try {
84- // Create a download link and trigger it
145+ setIsLoading ( true ) ;
146+
147+ // Ensure user is authenticated
148+ if ( ! user ) {
149+ setError ( "You must be logged in to export snippets." ) ;
150+ setIsLoading ( false ) ;
151+ return ;
152+ }
153+
154+ // Get Firebase auth instance and current user
155+ const auth = getAuth ( ) ;
156+ const firebaseUser = auth . currentUser ;
157+
158+ if ( ! firebaseUser ) {
159+ setError ( "Authentication error. Please try logging in again." ) ;
160+ setIsLoading ( false ) ;
161+ return ;
162+ }
163+
164+ // Get id token
165+ const token = await firebaseUser . getIdToken ( true ) ;
166+
167+ // Create a download with authenticated request
85168 const exportUrl = "/api/snippets/export" ;
169+
170+ // For authenticated downloads, we need to use fetch with credentials
171+ // and then create a blob URL from the response
172+ const response = await fetch ( exportUrl , {
173+ method : "GET" ,
174+ headers : {
175+ "Authorization" : `Bearer ${ token } `
176+ }
177+ } ) ;
178+
179+ if ( ! response . ok ) {
180+ throw new Error ( `Export failed: ${ response . statusText } ` ) ;
181+ }
182+
183+ // Get the response data as JSON
184+ const snippetsData = await response . json ( ) ;
185+
186+ // Create a blob URL
187+ const blob = new Blob ( [ JSON . stringify ( snippetsData , null , 2 ) ] , {
188+ type : "application/json"
189+ } ) ;
190+ const blobUrl = URL . createObjectURL ( blob ) ;
191+
192+ // Create a download link and trigger it
86193 const link = document . createElement ( "a" ) ;
87- link . href = exportUrl ;
88- link . download = " codepatchwork-snippets. json" ;
194+ link . href = blobUrl ;
195+ link . download = ` codepatchwork-snippets- ${ new Date ( ) . toISOString ( ) . slice ( 0 , 10 ) } . json` ;
89196 document . body . appendChild ( link ) ;
90197 link . click ( ) ;
198+
199+ // Clean up
91200 document . body . removeChild ( link ) ;
201+ setTimeout ( ( ) => URL . revokeObjectURL ( blobUrl ) , 100 ) ;
92202
93203 // Show success toast
94204 toast ( {
@@ -98,9 +208,12 @@ export default function ImportExportDialog({
98208
99209 // Close dialog
100210 onOpenChange ( false ) ;
101- } catch ( err ) {
102- setError ( "Failed to export snippets. Please try again." ) ;
211+ } catch ( err : any ) {
212+ const errorMessage = err . message || "Failed to export snippets" ;
213+ setError ( `${ errorMessage } . Please try again.` ) ;
103214 console . error ( "Export error:" , err ) ;
215+ } finally {
216+ setIsLoading ( false ) ;
104217 }
105218 } ;
106219
@@ -177,6 +290,13 @@ export default function ImportExportDialog({
177290 Download all your snippets as a JSON file that you can import later or share with others.
178291 </ p >
179292 </ div >
293+
294+ { error && (
295+ < div className = "bg-destructive/10 text-destructive p-3 rounded-md flex items-start" >
296+ < AlertCircle className = "h-5 w-5 mr-2 mt-0.5 flex-shrink-0" />
297+ < p className = "text-sm" > { error } </ p >
298+ </ div >
299+ ) }
180300 </ div >
181301 </ TabsContent >
182302 </ Tabs >
@@ -191,12 +311,12 @@ export default function ImportExportDialog({
191311 { isLoading ? "Importing..." : "Import Snippets" }
192312 </ Button >
193313 ) : (
194- < Button onClick = { handleExport } >
195- Export Snippets
314+ < Button onClick = { handleExport } disabled = { isLoading } >
315+ { isLoading ? "Exporting..." : " Export Snippets" }
196316 </ Button >
197317 ) }
198318 </ DialogFooter >
199319 </ DialogContent >
200320 </ Dialog >
201321 ) ;
202- }
322+ }
0 commit comments