@shopify/shop-minis-react
Version:
React component library for Shopify Shop Minis with Tailwind CSS v4 support (source-only, requires TypeScript)
136 lines (115 loc) • 3.19 kB
text/typescript
import {useCallback} from 'react'
import {useShopActions} from '../../internal/useShopActions'
import {UploadTarget} from '../../types'
export interface UploadImageParams {
/**
* The MIME type of the image.
*/
mimeType: string
/**
* The size of the image in bytes.
*/
fileSize: number
/**
* The URI of the image to upload.
*/
uri: string
}
export interface UploadedImage {
/**
* The ID of the uploaded image.
*/
id: string
/**
* The URL of the uploaded image.
*/
imageUrl?: string
/**
* The resource URL of the uploaded image.
*/
resourceUrl?: string
}
interface UseImageUploadReturns {
/**
* Upload an image attached to the current user.
*/
uploadImage: (params: UploadImageParams[]) => Promise<UploadedImage[]>
}
const uploadFileToGCS = async (
image: UploadImageParams,
target: UploadTarget
) => {
const formData = new FormData()
target.parameters.forEach(({name, value}) => {
formData.append(name, value)
})
// Append the actual file data last
formData.append('file', new Blob([image.uri], {type: image.mimeType}))
const uploadResponse = await fetch(target.url, {
method: 'POST',
body: formData,
})
if (!uploadResponse.ok) {
console.error('Failed to upload image', {
response: await uploadResponse.text(),
})
return {error: 'Failed to upload image'}
}
return {}
}
export const useImageUpload = (): UseImageUploadReturns => {
const {createImageUploadLink, completeImageUpload} = useShopActions()
const uploadImage = useCallback(
async (params: UploadImageParams[]) => {
if (params.length > 1) {
throw new Error('Multiple image upload is not supported yet')
}
const links = await createImageUploadLink({
input: params.map(image => ({
mimeType: image.mimeType,
fileSize: image.fileSize,
})),
})
if (!links.ok) {
throw new Error(links.error.message)
}
// Upload single file to GCS
// TODO: Upload multiple files to GCS
const {error: uploadError} = await uploadFileToGCS(
params[0],
links?.data?.targets?.[0]!
)
if (uploadError) {
throw new Error(uploadError)
}
// 10 second polling for image upload
let count = 0
while (count < 30) {
const result = await completeImageUpload({
resourceUrls:
links?.data?.targets?.map(target => target.resourceUrl) || [],
})
if (!result.ok) {
throw new Error(result.error.message)
}
// TODO: Add support for multiple files
if (result.data?.files?.[0]?.fileStatus === 'READY') {
return [
{
id: result.data.files[0].id,
imageUrl: result.data.files[0].image?.url,
resourceUrl: links?.data?.targets?.[0]?.resourceUrl,
},
]
}
await new Promise(resolve => setTimeout(resolve, 1000))
count++
}
throw new Error('Image upload completion timed out')
},
[createImageUploadLink, completeImageUpload]
)
return {
uploadImage,
}
}