1- import { useCallback , useState } from 'react' ;
1+ import { useMutation } from '@tanstack/ react-query ' ;
22import { toast } from 'sonner' ;
33
44export type ExportFormat = 'json' | 'csv' | 'txt' | 'proto' ;
@@ -16,70 +16,88 @@ interface ExportParams {
1616
1717const API_BASE_URL = process . env . NEXT_PUBLIC_API_URL ;
1818
19- export function useDataExport ( { websiteId, websiteName } : UseDataExportOptions ) {
20- const [ isExporting , setIsExporting ] = useState ( false ) ;
21-
22- const exportData = useCallback ( async ( { format = 'csv' , startDate, endDate } : ExportParams ) => {
23- setIsExporting ( true ) ;
24-
25- try {
26- const response = await fetch ( `${ API_BASE_URL } /v1/export/data` , {
27- method : 'POST' ,
28- credentials : 'include' ,
29- headers : {
30- 'Content-Type' : 'application/json' ,
31- } ,
32- body : JSON . stringify ( {
33- website_id : websiteId ,
34- format,
35- start_date : startDate ,
36- end_date : endDate ,
37- } ) ,
38- } ) ;
39-
40- if ( ! response . ok ) {
41- const error = await response . json ( ) ;
42- throw new Error ( error . error || 'Export failed' ) ;
43- }
44-
45- // Get filename from response headers if available
46- const contentDisposition = response . headers . get ( 'Content-Disposition' ) ;
47- let filename = `${ websiteName || 'website' } -export-${ new Date ( ) . toISOString ( ) . split ( 'T' ) [ 0 ] } .zip` ;
48-
49- if ( contentDisposition ) {
50- const filenameMatch = contentDisposition . match ( / f i l e n a m e = " ( .+ ) " / ) ;
51- if ( filenameMatch ) {
52- filename = filenameMatch [ 1 ] ;
53- }
54- }
55-
56- // Create blob and trigger download
57- const blob = await response . blob ( ) ;
58- const url = window . URL . createObjectURL ( blob ) ;
59- const a = document . createElement ( 'a' ) ;
60- a . href = url ;
61- a . download = filename ;
62- a . style . display = 'none' ;
63- document . body . appendChild ( a ) ;
64- a . click ( ) ;
65-
66- // Cleanup
67- window . URL . revokeObjectURL ( url ) ;
68- document . body . removeChild ( a ) ;
19+ // Regex for extracting filename from Content-Disposition header
20+ const FILENAME_REGEX = / f i l e n a m e = " ( .+ ) " / ;
6921
70- toast . success ( 'Data exported successfully!' ) ;
71- return { success : true , filename } ;
72- } catch ( error ) {
73- const errorMessage = error instanceof Error ? error . message : 'Export failed' ;
74- toast . error ( errorMessage ) ;
75- return { success : false , error : errorMessage } ;
76- } finally {
77- setIsExporting ( false ) ;
22+ // Helper function to handle file download
23+ function downloadFile ( blob : Blob , filename : string ) {
24+ const url = window . URL . createObjectURL ( blob ) ;
25+ const a = document . createElement ( 'a' ) ;
26+ a . href = url ;
27+ a . download = filename ;
28+ a . style . display = 'none' ;
29+ document . body . appendChild ( a ) ;
30+ a . click ( ) ;
31+
32+ // Cleanup
33+ window . URL . revokeObjectURL ( url ) ;
34+ document . body . removeChild ( a ) ;
35+ }
36+
37+ // Helper function to extract filename from response
38+ function getFilenameFromResponse (
39+ response : Response ,
40+ websiteName ?: string
41+ ) : string {
42+ const contentDisposition = response . headers . get ( 'Content-Disposition' ) ;
43+ const defaultFilename = `${ websiteName || 'website' } -export-${ new Date ( ) . toISOString ( ) . split ( 'T' ) [ 0 ] } .zip` ;
44+
45+ if ( contentDisposition ) {
46+ const filenameMatch = contentDisposition . match ( FILENAME_REGEX ) ;
47+ if ( filenameMatch ) {
48+ return filenameMatch [ 1 ] ;
7849 }
79- } , [ websiteId , websiteName ] ) ;
50+ }
51+
52+ return defaultFilename ;
53+ }
8054
81- return {
82- exportData,
83- isExporting,
84- } ;
55+ // Main export function
56+ async function exportDataFromAPI (
57+ websiteId : string ,
58+ websiteName : string | undefined ,
59+ { format = 'csv' , startDate, endDate } : ExportParams
60+ ) : Promise < { filename : string } > {
61+ const response = await fetch ( `${ API_BASE_URL } /v1/export/data` , {
62+ method : 'POST' ,
63+ credentials : 'include' ,
64+ headers : {
65+ 'Content-Type' : 'application/json' ,
66+ } ,
67+ body : JSON . stringify ( {
68+ website_id : websiteId ,
69+ format,
70+ start_date : startDate ,
71+ end_date : endDate ,
72+ } ) ,
73+ } ) ;
74+
75+ if ( ! response . ok ) {
76+ const error = await response . json ( ) ;
77+ throw new Error ( error . error || 'Export failed' ) ;
78+ }
79+
80+ const filename = getFilenameFromResponse ( response , websiteName ) ;
81+ const blob = await response . blob ( ) ;
82+
83+ downloadFile ( blob , filename ) ;
84+
85+ return { filename } ;
86+ }
87+
88+ export function useDataExport ( {
89+ websiteId,
90+ websiteName,
91+ } : UseDataExportOptions ) {
92+ return useMutation ( {
93+ mutationFn : ( params : ExportParams ) =>
94+ exportDataFromAPI ( websiteId , websiteName , params ) ,
95+ onSuccess : ( ) => {
96+ toast . success ( 'Data exported successfully!' ) ;
97+ } ,
98+ onError : ( error : Error ) => {
99+ const errorMessage = error . message || 'Export failed' ;
100+ toast . error ( errorMessage ) ;
101+ } ,
102+ } ) ;
85103}
0 commit comments