diff --git a/components/form/file-input/actions.ts b/components/form/file-input/actions.ts new file mode 100644 index 000000000..cf71d22ec --- /dev/null +++ b/components/form/file-input/actions.ts @@ -0,0 +1,38 @@ +'use server'; + +import { createFile, stageUploadFile, uploadFile } from 'lib/shopify'; +import { UploadInput } from 'lib/shopify/types'; + +export const createStagedUploadFiles = async (params: UploadInput) => { + try { + const stagedTargets = await stageUploadFile(params); + if (!stagedTargets || stageUploadFile.length === 0) return null; + + return JSON.parse(JSON.stringify(stagedTargets[0])); + } catch (error) { + console.log(error); + } +}; + +export const onUploadFile = async ({ + url, + formData, + fileName, + resourceUrl +}: { + url: string; + formData: FormData; + fileName: string; + resourceUrl: string; +}) => { + try { + await uploadFile({ url, formData }); + await createFile({ + alt: fileName, + contentType: 'FILE', + originalSource: resourceUrl + }); + } catch (error) { + console.log(error); + } +}; diff --git a/components/form/file-input.tsx b/components/form/file-input/index.tsx similarity index 100% rename from components/form/file-input.tsx rename to components/form/file-input/index.tsx diff --git a/components/form/file-input/utils.ts b/components/form/file-input/utils.ts new file mode 100644 index 000000000..f359ce458 --- /dev/null +++ b/components/form/file-input/utils.ts @@ -0,0 +1,42 @@ +import { StagedUploadsCreatePayload } from 'lib/shopify/types'; +import { createStagedUploadFiles, onUploadFile } from './actions'; + +export const prepareFilePayload = ({ + stagedFileUpload, + file +}: { + stagedFileUpload: StagedUploadsCreatePayload; + file: File; +}) => { + const formData = new FormData(); + + const url = stagedFileUpload.url; + + stagedFileUpload.parameters.forEach(({ name, value }) => { + formData.append(name, value); + }); + + formData.append('file', file); + return { url, formData }; +}; + +export const handleUploadFile = async ({ file }: { file: File }) => { + if (!file) return; + const stagedTarget = await createStagedUploadFiles({ + filename: file.name, + fileSize: String(file.size), + httpMethod: 'POST', + resource: 'FILE', + mimeType: file.type + }); + + if (stagedTarget) { + const data = prepareFilePayload({ file, stagedFileUpload: stagedTarget }); + + await onUploadFile({ + ...data, + fileName: file.name, + resourceUrl: stagedTarget.resourceUrl + }); + } +}; diff --git a/lib/shopify/index.ts b/lib/shopify/index.ts index 932cc056f..6fd5c12cd 100644 --- a/lib/shopify/index.ts +++ b/lib/shopify/index.ts @@ -25,6 +25,7 @@ import { removeFromCartMutation, setCartAttributesMutation } from './mutations/cart'; +import { createFileMutation, createStageUploads } from './mutations/file'; import { getCartQuery } from './queries/cart'; import { getCollectionProductsQuery, @@ -50,6 +51,7 @@ import { Collection, Connection, Customer, + FileCreateInput, Filter, Fulfillment, Image, @@ -71,6 +73,7 @@ import { ShopifyCollectionProductsOperation, ShopifyCollectionsOperation, ShopifyCreateCartOperation, + ShopifyCreateFileOperation, ShopifyCustomer, ShopifyCustomerOperation, ShopifyCustomerOrderOperation, @@ -92,9 +95,11 @@ import { ShopifyProductsOperation, ShopifyRemoveFromCartOperation, ShopifySetCartAttributesOperation, + ShopifyStagedUploadOperation, ShopifyUpdateCartOperation, Transaction, - TransmissionType + TransmissionType, + UploadInput } from './types'; const domain = process.env.SHOPIFY_STORE_DOMAIN @@ -1044,3 +1049,28 @@ export const getImage = async (id: string): Promise => { return res.body.data.node.image; }; + +export const stageUploadFile = async (params: UploadInput) => { + const res = await adminFetch({ + query: createStageUploads, + variables: { input: [params] } + }); + + return res.body.data.stagedUploadsCreate.stagedTargets; +}; + +export const uploadFile = async ({ url, formData }: { url: string; formData: FormData }) => { + return await fetch(url, { + method: 'POST', + body: formData + }); +}; + +export const createFile = async (params: FileCreateInput) => { + const res = await adminFetch({ + query: createFileMutation, + variables: { files: [params] } + }); + + return res.body.data; +}; diff --git a/lib/shopify/mutations/file.ts b/lib/shopify/mutations/file.ts new file mode 100644 index 000000000..c9944c6ef --- /dev/null +++ b/lib/shopify/mutations/file.ts @@ -0,0 +1,31 @@ +export const createStageUploads = /* GraphQL */ ` + mutation stagedUploadsCreate($input: [StagedUploadInput!]!) { + stagedUploadsCreate(input: $input) { + stagedTargets { + url + resourceUrl + parameters { + name + value + } + } + } + } +`; + +export const createFileMutation = /* GraphQL */ ` + mutation fileCreate($files: [FileCreateInput!]!) { + fileCreate(files: $files) { + files { + fileStatus + ... on MediaImage { + id + } + } + userErrors { + field + message + } + } + } +`; diff --git a/lib/shopify/types.ts b/lib/shopify/types.ts index ce7fede25..fdded6e1c 100644 --- a/lib/shopify/types.ts +++ b/lib/shopify/types.ts @@ -783,3 +783,45 @@ export type CartAttributeInput = { key: string; value: string; }; + +export type UploadInput = { + filename: string; + mimeType: string; + httpMethod: 'POST' | 'PUT'; + fileSize: string; + resource: 'FILE' | 'IMAGE'; +}; + +export type StagedUploadsCreatePayload = { + parameters: { + name: string; + value: string; + }[]; + resourceUrl: string; + url: string; +}; + +export type ShopifyStagedUploadOperation = { + data: { + stagedUploadsCreate: { + stagedTargets: StagedUploadsCreatePayload[]; + }; + }; + variables: { input: UploadInput[] }; +}; + +export type FileCreateInput = { + alt: string; + contentType: 'FILE' | 'IMAGE'; + originalSource: string; +}; + +export type ShopifyCreateFileOperation = { + data: { + fileCreate: { + files: { fileStatus: string; id: string }[]; + userErrors: { code: string; field: string; message: string }[]; + }; + }; + variables: { files: FileCreateInput[] }; +};