Skip to content

Instantly share code, notes, and snippets.

@zbeyens
Last active May 7, 2024 10:51
Show Gist options
  • Save zbeyens/4815f8dcffa5f92f1df57b0c34843ca3 to your computer and use it in GitHub Desktop.
Save zbeyens/4815f8dcffa5f92f1df57b0c34843ca3 to your computer and use it in GitHub Desktop.
import type { ZodSchema } from 'zod';
import { parseSchema } from '@/lib/schemas/parseSchema';
/** Get a response with a file to download */
export const getFileResponse = <T>({
file,
filename,
schema,
}: {
file: T;
filename: string;
schema: ZodSchema<T>;
}) => {
parseSchema(schema, file);
const headers = new Headers();
const encodedFilename = encodeURIComponent(filename);
headers.append(
'Content-Disposition',
`attachment; filename="${encodedFilename}"`
);
headers.append('Content-Type', 'application/text');
const buffer = Buffer.from(JSON.stringify(file), 'utf8');
// return a new response but use 'content-disposition' to suggest saving the file to the user's computer
return new Response(buffer, {
headers,
});
};
import type { ZodSchema } from 'zod';
import { boomZod } from '@/lib/api/utils/boomZod';
export const parseSchema = <T>(schema: ZodSchema<T>, data: any) => {
const parsed = schema.safeParse(data);
if (!parsed.success) throw boomZod(parsed);
return parsed.data;
};
import { useRef } from 'react';
import { toast } from 'sonner';
export type UseNetworkToastOptions = {
error?: string;
loading?: string;
success?: string;
};
export const useAsyncToasts = ({
error = 'Error',
loading = 'Loading...',
success = 'Success!',
}: UseNetworkToastOptions = {}) => {
const toastIdRef = useRef<null | number | string>(null);
return {
onError: ({ message }: { message?: string } = {}) => {
const messageStr = message ? ` ${message}` : '';
toast.error((error + '.' + messageStr).trim(), {
id: toastIdRef.current!,
});
},
onMutate: () => {
toastIdRef.current = toast.loading(loading, {
duration: 10_000,
id: toastIdRef.current!,
});
},
onSuccess: () => {
toast.success(success, {
duration: 2000,
id: toastIdRef.current!,
});
},
};
};
'use client';
import {
type UseNetworkToastOptions,
useAsyncToasts,
} from '@/hooks/useAsyncToasts';
export const useFetchExport = (
{
api,
onError,
onMutate,
onSuccess,
toasts,
}: {
api: string;
onError?: (message: string) => void;
onMutate?: () => void;
onSuccess?: () => void;
toasts?: UseNetworkToastOptions;
},
fetchOptions?: Parameters<typeof fetch>[1]
) => {
const toast = useAsyncToasts(toasts);
return async () => {
onMutate?.();
toast.onMutate();
const response = await fetch(api, { cache: 'no-store', ...fetchOptions });
if (!response.ok) {
const error = await response.json();
onError?.(error);
toast.onError(error);
return;
}
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
const filename = response.headers.get('content-disposition')?.split('"')[1];
if (filename) {
link.download = filename;
}
link.click();
window.URL.revokeObjectURL(url);
onSuccess?.();
toast.onSuccess();
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment