add file download for tracking page

This commit is contained in:
smanylov
2026-05-08 13:39:37 +07:00
parent 07e1237f05
commit b8a76bd6a9
8 changed files with 109 additions and 41 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "no-copy-frontend",
"version": "0.109.0",
"version": "0.110.0",
"private": true,
"scripts": {
"dev": "next dev -p 2999",
+10 -9
View File
@@ -1,9 +1,9 @@
import Providers from '@/app/providers/getQueryServer'
import type { Metadata } from "next";
import { NextIntlClientProvider, hasLocale } from 'next-intl';
import { notFound } from 'next/navigation';
import { routing } from '@/i18n/routing';
import { ToastProvider } from '@/app/providers/ToastProvider';
import ClientProviders from '@/app/providers/ClientProviders';
import { Suspense } from 'react';
import "../styles/globals.css";
import "../styles/global-styles.scss";
@@ -30,14 +30,15 @@ export default async function RootLayout({
}
return (
<html lang={locale}>
<html>
<body>
<NextIntlClientProvider>
<Providers>
{children}
<ToastProvider />
</Providers>
</NextIntlClientProvider>
<Suspense fallback={<div>Loading...</div>}>
<NextIntlClientProvider locale={locale}>
<ClientProviders>
{children}
</ClientProviders>
</NextIntlClientProvider>
</Suspense>
</body>
</html>
);
+8 -3
View File
@@ -1,9 +1,14 @@
'use client'
import { PdfProvider } from '@/app/contexts/PdfContext';
import { WatchDocProviders } from '@/app/providers/ClientProviders';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<PdfProvider>
{children}
</PdfProvider>
<WatchDocProviders>
<PdfProvider>
{children}
</PdfProvider>
</WatchDocProviders>
);
}
+1 -1
View File
@@ -119,7 +119,7 @@ export default async function Page({ searchParams }: PageProps) {
<div className="watch-doc-actions">
{response?.permissions?.DOWNLOAD && (
<DownloadButton />
<DownloadButton fileId={docid} fileName={response?.fileName}/>
)}
<CopyLinkButton />
</div>
+23
View File
@@ -239,3 +239,26 @@ export async function fetchTrackingStats() {
return null;
}
}
export async function downloadTrackingFile(fileId: string, fileName?: string) {
try {
const response = await fetch(`${API_BASE_URL}/api/v1/files/public-download/${fileId}`, {
method: 'GET'
});
if (!response.ok) {
throw 'failed-to-download-file';
}
const arrayBuffer = await response.arrayBuffer()
const base64 = Buffer.from(arrayBuffer).toString('base64')
return {
data: base64,
contentType: response.headers.get('content-type') || 'application/octet-stream',
fileName: fileName ? fileName : 'fileName'
}
} catch (error) {
return null;
}
}
@@ -1,12 +1,49 @@
'use client';
import { usePdf } from '@/app/contexts/PdfContext';
import { downloadTrackingFile } from '@/app/actions/trackingActions';
import { useTranslations } from 'next-intl';
import { toast } from 'sonner';
interface DownloadButtonProps {
fileId: string;
fileName?: string;
}
export function DownloadButton() {
const { downloadPdf } = usePdf();
export function DownloadButton({ fileId, fileName }: DownloadButtonProps) {
const t = useTranslations('Global');
const handleDownload = async () => {
try {
const result = await downloadTrackingFile(fileId, fileName);
if (!result) {
throw 'failed-to-download-file'
}
const byteCharacters = atob(result.data);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i)
}
const byteArray = new Uint8Array(byteNumbers);
const blob = new Blob([byteArray], { type: result.contentType });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = result.fileName;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
toast.success(`${t('file-is-downloading')} - `);
} catch (error) {
toast.error(t('failed-to-download-file'));
}
};
return (
<button className="btn btn-out" onClick={downloadPdf}>
<button className="btn btn-out" onClick={handleDownload}>
Скачать документ
</button>
);
+25
View File
@@ -0,0 +1,25 @@
'use client';
import Providers from './getQueryServer';
import { ToastProvider } from './ToastProvider';
export default function ClientProviders({
children
}: {
children: React.ReactNode;
}) {
return (
<Providers>
{children}
<ToastProvider />
</Providers>
);
}
export function WatchDocProviders({ children }: { children: React.ReactNode }) {
return (
<Providers>
{children}
</Providers>
);
}
-23
View File
@@ -1,34 +1,11 @@
'use client'
import {
isServer,
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query'
import { getQueryClient } from '@/app/providers/getQueryClient';
/* export function makeQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000,
},
},
})
}
let browserQueryClient: QueryClient | undefined = undefined
function getQueryClient() {
if (isServer) {
return makeQueryClient()
} else {
if (!browserQueryClient) browserQueryClient = makeQueryClient()
return browserQueryClient
}
} */
export default function Providers({ children }: { children: React.ReactNode }) {
const queryClient = getQueryClient();