Skip to content

Commit 66cb63b

Browse files
committed
cleanup: chat scroll, tanstack query for device sessions and data export
1 parent d3338f6 commit 66cb63b

File tree

4 files changed

+230
-152
lines changed

4 files changed

+230
-152
lines changed

apps/dashboard/app/(main)/websites/[id]/_components/tabs/settings-tab.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ export function WebsiteSettingsTab({
9191
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
9292
const [showEditDialog, setShowEditDialog] = useState(false);
9393
// Data export hook
94-
const { exportData, isExporting } = useDataExport({
94+
const dataExportMutation = useDataExport({
9595
websiteId,
9696
websiteName: websiteData?.name || undefined,
9797
});

apps/dashboard/app/(main)/websites/[id]/assistant/components/chat-section.tsx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -113,17 +113,12 @@ export default function ChatSection() {
113113
}
114114
}, [isLoading]);
115115

116-
useEffect(() => {
117-
if (bottomRef.current) {
118-
bottomRef.current.scrollIntoView({ behavior: 'auto' });
119-
}
120-
}, []);
121-
116+
// Auto-scroll to bottom when messages change
122117
useEffect(() => {
123118
if (bottomRef.current) {
124119
bottomRef.current.scrollIntoView({ behavior: 'smooth' });
125120
}
126-
}, []);
121+
}, [messages.length]);
127122

128123
const hasMessages = messages.length > 1;
129124

Lines changed: 82 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useCallback, useState } from 'react';
1+
import { useMutation } from '@tanstack/react-query';
22
import { toast } from 'sonner';
33

44
export type ExportFormat = 'json' | 'csv' | 'txt' | 'proto';
@@ -16,70 +16,88 @@ interface ExportParams {
1616

1717
const 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(/filename="(.+)"/);
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 = /filename="(.+)"/;
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

Comments
 (0)