add file download for tracking page
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "no-copy-frontend",
|
"name": "no-copy-frontend",
|
||||||
"version": "0.109.0",
|
"version": "0.110.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev -p 2999",
|
"dev": "next dev -p 2999",
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import Providers from '@/app/providers/getQueryServer'
|
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { NextIntlClientProvider, hasLocale } from 'next-intl';
|
import { NextIntlClientProvider, hasLocale } from 'next-intl';
|
||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
import { routing } from '@/i18n/routing';
|
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/globals.css";
|
||||||
import "../styles/global-styles.scss";
|
import "../styles/global-styles.scss";
|
||||||
@@ -30,14 +30,15 @@ export default async function RootLayout({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang={locale}>
|
<html>
|
||||||
<body>
|
<body>
|
||||||
<NextIntlClientProvider>
|
<Suspense fallback={<div>Loading...</div>}>
|
||||||
<Providers>
|
<NextIntlClientProvider locale={locale}>
|
||||||
{children}
|
<ClientProviders>
|
||||||
<ToastProvider />
|
{children}
|
||||||
</Providers>
|
</ClientProviders>
|
||||||
</NextIntlClientProvider>
|
</NextIntlClientProvider>
|
||||||
|
</Suspense>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
import { PdfProvider } from '@/app/contexts/PdfContext';
|
import { PdfProvider } from '@/app/contexts/PdfContext';
|
||||||
|
import { WatchDocProviders } from '@/app/providers/ClientProviders';
|
||||||
|
|
||||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<PdfProvider>
|
<WatchDocProviders>
|
||||||
{children}
|
<PdfProvider>
|
||||||
</PdfProvider>
|
{children}
|
||||||
|
</PdfProvider>
|
||||||
|
</WatchDocProviders>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -119,7 +119,7 @@ export default async function Page({ searchParams }: PageProps) {
|
|||||||
|
|
||||||
<div className="watch-doc-actions">
|
<div className="watch-doc-actions">
|
||||||
{response?.permissions?.DOWNLOAD && (
|
{response?.permissions?.DOWNLOAD && (
|
||||||
<DownloadButton />
|
<DownloadButton fileId={docid} fileName={response?.fileName}/>
|
||||||
)}
|
)}
|
||||||
<CopyLinkButton />
|
<CopyLinkButton />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -239,3 +239,26 @@ export async function fetchTrackingStats() {
|
|||||||
return null;
|
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';
|
'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() {
|
export function DownloadButton({ fileId, fileName }: DownloadButtonProps) {
|
||||||
const { downloadPdf } = usePdf();
|
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 (
|
return (
|
||||||
<button className="btn btn-out" onClick={downloadPdf}>
|
<button className="btn btn-out" onClick={handleDownload}>
|
||||||
Скачать документ
|
Скачать документ
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,34 +1,11 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
isServer,
|
|
||||||
QueryClient,
|
|
||||||
QueryClientProvider,
|
QueryClientProvider,
|
||||||
} from '@tanstack/react-query'
|
} from '@tanstack/react-query'
|
||||||
|
|
||||||
import { getQueryClient } from '@/app/providers/getQueryClient';
|
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 }) {
|
export default function Providers({ children }: { children: React.ReactNode }) {
|
||||||
const queryClient = getQueryClient();
|
const queryClient = getQueryClient();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user