This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
import { type FC, type ImgHTMLAttributes } from "react";
|
||||
import { usePresignedUrl } from "@/pages/uploader/hooks/usePresignedUrl";
|
||||
|
||||
type Props = ImgHTMLAttributes<HTMLImageElement> & {
|
||||
src?: string;
|
||||
};
|
||||
|
||||
const PresignedImage: FC<Props> = ({ src, alt = "", ...props }) => {
|
||||
const resolvedSrc = usePresignedUrl(src);
|
||||
|
||||
if (!resolvedSrc) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <img src={resolvedSrc} alt={alt} {...props} />;
|
||||
};
|
||||
|
||||
export default PresignedImage;
|
||||
@@ -2,6 +2,7 @@ import { CloseCircle, DocumentUpload, Gallery } from 'iconsax-react'
|
||||
import { type FC, useCallback, useEffect, useState } from 'react'
|
||||
import { useDropzone } from 'react-dropzone';
|
||||
import { clx } from '../helpers/utils';
|
||||
import PresignedImage from './PresignedImage';
|
||||
|
||||
type Props = {
|
||||
label: string;
|
||||
@@ -100,16 +101,19 @@ const UploadBoxDraggble: FC<Props> = (props: Props) => {
|
||||
return (
|
||||
<div key={index} className='flex items-center gap-2'>
|
||||
<div key={index} className='flex relative items-center gap-2'>
|
||||
<img onClick={() => {
|
||||
if (props.getCover) {
|
||||
setCover(item)
|
||||
props.getCover(item)
|
||||
}
|
||||
}} src={item}
|
||||
<PresignedImage
|
||||
onClick={() => {
|
||||
if (props.getCover) {
|
||||
setCover(item)
|
||||
props.getCover(item)
|
||||
}
|
||||
}}
|
||||
src={item}
|
||||
className={clx(
|
||||
'size-10 rounded-full object-cover',
|
||||
cover === item ? 'border-2 border-red-400' : ''
|
||||
)} />
|
||||
)}
|
||||
/>
|
||||
<div onClick={() => handleDeletePreview(index)} className='absolute -left-2 -top-2 shadow-md bg-white size-5 rounded-full flex justify-center items-center'>
|
||||
<CloseCircle className='size-4 ' color='red' />
|
||||
</div>
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
import { Pause, Play } from "iconsax-react"
|
||||
import { useEffect, useRef, useState, type FC } from "react"
|
||||
import { usePresignedUrl } from "@/pages/uploader/hooks/usePresignedUrl"
|
||||
|
||||
type VoicePlayerProps = {
|
||||
url: string
|
||||
}
|
||||
|
||||
const VoicePlayer: FC<VoicePlayerProps> = ({ url }) => {
|
||||
const resolvedUrl = usePresignedUrl(url)
|
||||
const audioRef = useRef<HTMLAudioElement | null>(null)
|
||||
const [isPlaying, setIsPlaying] = useState(false)
|
||||
const [progress, setProgress] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
const audio = audioRef.current
|
||||
if (!audio) return
|
||||
if (!audio || !resolvedUrl) return
|
||||
|
||||
const update = () => {
|
||||
if (!audio.duration) return
|
||||
@@ -28,7 +30,7 @@ const VoicePlayer: FC<VoicePlayerProps> = ({ url }) => {
|
||||
return () => {
|
||||
audio.removeEventListener('timeupdate', update)
|
||||
}
|
||||
}, [])
|
||||
}, [resolvedUrl])
|
||||
|
||||
const toggle = () => {
|
||||
if (!audioRef.current) return
|
||||
@@ -66,7 +68,7 @@ const VoicePlayer: FC<VoicePlayerProps> = ({ url }) => {
|
||||
})}
|
||||
</div>
|
||||
|
||||
<audio ref={audioRef} src={url} className="hidden" />
|
||||
<audio ref={audioRef} src={resolvedUrl} className="hidden" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ const CreateInvoice: FC = () => {
|
||||
uploadResult?.data?.forEach((item) => {
|
||||
attachments.push({
|
||||
type: "uploads_attach",
|
||||
url: item.url,
|
||||
url: item.key,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ const UpdateInvoice: FC = () => {
|
||||
uploadResult?.data?.forEach((item) => {
|
||||
attachments.push({
|
||||
type: "uploads_attach",
|
||||
url: item.url,
|
||||
url: item.key,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -49,10 +49,10 @@ const CreateLearning: FC = () => {
|
||||
if (videoFile && coverFile) {
|
||||
|
||||
const videoResult = await singleUpload.mutateAsync(videoFile)
|
||||
values.videoUrl = videoResult?.data?.url ?? ''
|
||||
values.videoUrl = videoResult?.data?.key ?? ''
|
||||
|
||||
const coverResult = await singleUpload.mutateAsync(coverFile)
|
||||
values.coverUrl = coverResult?.data?.url ?? ''
|
||||
values.coverUrl = coverResult?.data?.key ?? ''
|
||||
|
||||
createLearning.mutate(values, {
|
||||
onSuccess: () => {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Add } from 'iconsax-react'
|
||||
import { useGetLearning } from './hooks/useLearningData'
|
||||
import { type LearningItemType } from './hooks/useLearningData'
|
||||
import Table from '../../components/Table'
|
||||
import PresignedImage from '@/components/PresignedImage'
|
||||
import { type ColumnType } from '../../components/types/TableTypes'
|
||||
const LearningList: FC = () => {
|
||||
const [page, setPage] = useState<number>(1)
|
||||
@@ -19,7 +20,7 @@ const LearningList: FC = () => {
|
||||
key: 'coverUrl',
|
||||
width: '100px',
|
||||
render: (item: LearningItemType) => (
|
||||
<img src={item.coverUrl} className='w-20 h-12 object-cover rounded' alt={item.title} />
|
||||
<PresignedImage src={item.coverUrl} className='w-20 h-12 object-cover rounded' alt={item.title} />
|
||||
)
|
||||
},
|
||||
{
|
||||
|
||||
@@ -75,7 +75,7 @@ const ConvertToOrders: FC = () => {
|
||||
if (attachmentFiles.length > 0) {
|
||||
const result = await multiUpload.mutateAsync(attachmentFiles)
|
||||
result?.data?.forEach((item) => {
|
||||
attachments.push(item.url)
|
||||
attachments.push(item.key)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ const EditOrder: FC = () => {
|
||||
if (attachmentFiles.length > 0) {
|
||||
const result = await multiUpload.mutateAsync(attachmentFiles)
|
||||
result?.data?.forEach((item) => {
|
||||
attachments.push(item.url)
|
||||
attachments.push(item.key)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { type FC, type ReactNode } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import moment from 'moment-jalaali'
|
||||
import { DocumentText } from 'iconsax-react'
|
||||
import PresignedImage from '@/components/PresignedImage'
|
||||
import { Paths } from '@/config/Paths'
|
||||
import { useGetInvoiceItem } from '@/pages/invoice/hooks/useInvoiceData'
|
||||
import { formatItemDiscountDisplay } from '@/pages/invoice/utils/invoiceItem'
|
||||
@@ -80,7 +81,7 @@ const InvoiceItemDetailSection: FC<Props> = ({ invoiceItemId }) => {
|
||||
<div className="flex flex-col gap-5 md:flex-row">
|
||||
<div className="size-24 shrink-0 overflow-hidden rounded-xl border border-gray-200 bg-gray-50">
|
||||
{imageUrl ? (
|
||||
<img
|
||||
<PresignedImage
|
||||
src={imageUrl}
|
||||
alt={invoiceItem.product?.title}
|
||||
className="size-full object-cover"
|
||||
|
||||
@@ -51,25 +51,19 @@ const Order: FC<Props> = ({ addNewItem }) => {
|
||||
onSubmit: async (values) => {
|
||||
const attachments: AttachmentsType[] = []
|
||||
if (files.length) {
|
||||
await multiUpload.mutateAsync(files, {
|
||||
onSuccess: (data) => {
|
||||
data?.data?.map((item) => {
|
||||
attachments.push({
|
||||
type: 'uploads_attach',
|
||||
url: item.url
|
||||
})
|
||||
})
|
||||
}
|
||||
const filesResult = await multiUpload.mutateAsync(files)
|
||||
filesResult.data?.forEach((item) => {
|
||||
attachments.push({
|
||||
type: 'uploads_attach',
|
||||
url: item.key,
|
||||
})
|
||||
})
|
||||
}
|
||||
if (voiceFile) {
|
||||
await singleUpload.mutateAsync(voiceFile, {
|
||||
onSuccess: (data) => {
|
||||
attachments.push({
|
||||
type: 'voice',
|
||||
url: data?.data?.url
|
||||
})
|
||||
}
|
||||
const voiceResult = await singleUpload.mutateAsync(voiceFile)
|
||||
attachments.push({
|
||||
type: 'voice',
|
||||
url: voiceResult.data.key,
|
||||
})
|
||||
}
|
||||
if (indexOrder === -1) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Link } from 'react-router-dom'
|
||||
import { Call, Edit2, Location, Paperclip2, Profile2User, Receipt21, User } from 'iconsax-react'
|
||||
import { clx } from '@/helpers/utils'
|
||||
import { getFileNameAndExtensionFromUrl } from '@/config/func'
|
||||
import { getPresignedUrl } from '@/pages/uploader/service/UploaderService'
|
||||
import { Paths } from '@/config/Paths'
|
||||
import {
|
||||
getAttachmentUrl,
|
||||
@@ -201,19 +202,21 @@ const OrderDetailSidebar: FC<Props> = ({
|
||||
<section className="bg-white rounded-3xl p-5 shadow-sm">
|
||||
<h3 className="text-sm font-medium text-gray-900 mb-4">پیوستها</h3>
|
||||
<div className="flex flex-col gap-2">
|
||||
{attachments.map((url, idx) => {
|
||||
const { fileName } = getFileNameAndExtensionFromUrl(url)
|
||||
{attachments.map((key, idx) => {
|
||||
const { fileName } = getFileNameAndExtensionFromUrl(key)
|
||||
return (
|
||||
<a
|
||||
key={url}
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-2 rounded-xl border border-gray-100 bg-gray-50/80 px-3 py-2.5 text-sm text-[#0037FF] hover:bg-blue-50 transition-colors"
|
||||
<button
|
||||
key={key}
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
const resolvedUrl = await getPresignedUrl(key)
|
||||
window.open(resolvedUrl, '_blank', 'noopener,noreferrer')
|
||||
}}
|
||||
className="flex items-center gap-2 rounded-xl border border-gray-100 bg-gray-50/80 px-3 py-2.5 text-sm text-[#0037FF] hover:bg-blue-50 transition-colors text-right w-full"
|
||||
>
|
||||
<Paperclip2 size={18} className="shrink-0" />
|
||||
<span className="truncate">{fileName || `پیوست ${idx + 1}`}</span>
|
||||
</a>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Link, useParams } from 'react-router-dom'
|
||||
import { Paths } from '@/config/Paths'
|
||||
import { clx } from '@/helpers/utils'
|
||||
import { DocumentText, Printer } from 'iconsax-react'
|
||||
import PresignedImage from '@/components/PresignedImage'
|
||||
|
||||
type Props = {
|
||||
item: OrderItemType
|
||||
@@ -66,7 +67,7 @@ const OrderItem: FC<Props> = ({ item, estimatedDays, designerName, index }) => {
|
||||
<div className="flex flex-col gap-5 md:flex-row">
|
||||
<div className="size-24 shrink-0 overflow-hidden rounded-xl border border-gray-200 bg-white">
|
||||
{imageUrl ? (
|
||||
<img
|
||||
<PresignedImage
|
||||
src={imageUrl}
|
||||
alt={item.product?.title}
|
||||
className="size-full object-cover"
|
||||
|
||||
@@ -55,25 +55,19 @@ const EditOrder: FC<Props> = ({ addNewItem, initialValues }) => {
|
||||
onSubmit: async (values) => {
|
||||
const attachments: AttachmentsType[] = []
|
||||
if (files.length) {
|
||||
await multiUpload.mutateAsync(files, {
|
||||
onSuccess: (data) => {
|
||||
data?.data?.map((item) => {
|
||||
attachments.push({
|
||||
type: 'uploads_attach',
|
||||
url: item.url
|
||||
})
|
||||
})
|
||||
}
|
||||
const filesResult = await multiUpload.mutateAsync(files)
|
||||
filesResult.data?.forEach((item) => {
|
||||
attachments.push({
|
||||
type: 'uploads_attach',
|
||||
url: item.key,
|
||||
})
|
||||
})
|
||||
}
|
||||
if (voiceFile) {
|
||||
await singleUpload.mutateAsync(voiceFile, {
|
||||
onSuccess: (data) => {
|
||||
attachments.push({
|
||||
type: 'voice',
|
||||
url: data?.data?.url
|
||||
})
|
||||
}
|
||||
const voiceResult = await singleUpload.mutateAsync(voiceFile)
|
||||
attachments.push({
|
||||
type: 'voice',
|
||||
url: voiceResult.data.key,
|
||||
})
|
||||
}
|
||||
if (indexOrder === -1) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import { useAddTicket, useGetTickets } from '../hooks/useOrderData'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { toast } from '@/shared/toast'
|
||||
import { extractErrorMessage, getFileNameAndExtensionFromUrl } from '@/config/func'
|
||||
import { getPresignedUrl } from '@/pages/uploader/service/UploaderService'
|
||||
import VoicePlayer from '@/components/VoicePlayer'
|
||||
|
||||
|
||||
@@ -44,25 +45,19 @@ const TicketSection: FC = () => {
|
||||
content: message
|
||||
}
|
||||
if (audioFile) {
|
||||
await singleUpload.mutateAsync(audioFile, {
|
||||
onSuccess: (data) => {
|
||||
params.attachments.push({
|
||||
type: 'voice',
|
||||
url: data?.data?.url
|
||||
})
|
||||
}
|
||||
const voiceResult = await singleUpload.mutateAsync(audioFile)
|
||||
params.attachments.push({
|
||||
type: 'voice',
|
||||
url: voiceResult.data.key,
|
||||
})
|
||||
}
|
||||
if (files.length) {
|
||||
await multiUpload.mutateAsync(files, {
|
||||
onSuccess: (data) => {
|
||||
data?.data?.map((item) => {
|
||||
params.attachments.push({
|
||||
type: 'uploads_attach',
|
||||
url: item.url
|
||||
})
|
||||
})
|
||||
}
|
||||
const filesResult = await multiUpload.mutateAsync(files)
|
||||
filesResult.data?.forEach((item) => {
|
||||
params.attachments.push({
|
||||
type: 'uploads_attach',
|
||||
url: item.key,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -80,7 +75,8 @@ const TicketSection: FC = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const handleOpenLink = (url: string) => {
|
||||
const handleOpenLink = async (key: string) => {
|
||||
const url = await getPresignedUrl(key)
|
||||
window.open(
|
||||
url,
|
||||
'_blank'
|
||||
|
||||
@@ -56,7 +56,7 @@ const CreateProduct: FC = () => {
|
||||
onSuccess: (data) => {
|
||||
const imagesUrl: string[] = []
|
||||
data.data?.map((item) => {
|
||||
imagesUrl.push(item.url)
|
||||
imagesUrl.push(item.key)
|
||||
})
|
||||
values.images = imagesUrl
|
||||
handleSave(values)
|
||||
|
||||
@@ -59,7 +59,7 @@ const UpdateProduct: FC = () => {
|
||||
onSuccess: (data) => {
|
||||
const imagesUrl: string[] = [...values.images]
|
||||
data.data?.map((item) => {
|
||||
imagesUrl.push(item.url)
|
||||
imagesUrl.push(item.key)
|
||||
})
|
||||
values.images = imagesUrl
|
||||
handleSave(values)
|
||||
|
||||
@@ -58,7 +58,7 @@ const ProductCategory: FC = () => {
|
||||
if (file) {
|
||||
upload(file, {
|
||||
onSuccess: (data) => {
|
||||
values.avatarUrl = data?.data?.url
|
||||
values.avatarUrl = data?.data?.key
|
||||
handleSave(values)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { type FC, useMemo } from 'react'
|
||||
import PresignedImage from '@/components/PresignedImage'
|
||||
import { useDeleteCategory, useGetCategory } from '../hooks/useProductData'
|
||||
import Table from '@/components/Table'
|
||||
import type { ColumnType, RowDataType } from '@/components/types/TableTypes'
|
||||
@@ -33,7 +34,7 @@ const CategoryList: FC = () => {
|
||||
{
|
||||
title: 'تصویر',
|
||||
key: 'avatarUrl',
|
||||
render: (item) => item.avatarUrl ? <img src={item.avatarUrl} className='w-14' alt={item.title} /> : '-'
|
||||
render: (item) => item.avatarUrl ? <PresignedImage src={item.avatarUrl} className='w-14' alt={item.title} /> : '-'
|
||||
},
|
||||
{
|
||||
title: 'عنوان',
|
||||
|
||||
@@ -60,7 +60,7 @@ const UpdateCategory: FC = () => {
|
||||
if (file) {
|
||||
upload(file, {
|
||||
onSuccess: (data) => {
|
||||
values.avatarUrl = data?.data?.url
|
||||
values.avatarUrl = data?.data?.key
|
||||
handleSave(values)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -3,6 +3,8 @@ import { Paperclip2 } from 'iconsax-react'
|
||||
import placeholderProductImage from '@/assets/images/placeholder-product.svg'
|
||||
import { getFileNameAndExtensionFromUrl } from '@/config/func'
|
||||
import VoicePlayer from '@/components/VoicePlayer'
|
||||
import PresignedImage from '@/components/PresignedImage'
|
||||
import { getPresignedUrl } from '@/pages/uploader/service/UploaderService'
|
||||
import { useGetEntityField } from '@/pages/formBuilder/hooks/useFormBuilderData'
|
||||
import type { RequestDetailItemType } from '../types/Types'
|
||||
|
||||
@@ -47,7 +49,8 @@ const RequestDetailItem: FC<Props> = ({ item, index, total }) => {
|
||||
const voiceAttachments = item.attachments?.filter((a) => a.type === 'voice') ?? []
|
||||
const fileAttachments = item.attachments?.filter((a) => a.type !== 'voice') ?? []
|
||||
|
||||
const handleOpenLink = (url: string) => {
|
||||
const handleOpenLink = async (key: string) => {
|
||||
const url = await getPresignedUrl(key)
|
||||
window.open(url, '_blank', 'noopener,noreferrer')
|
||||
}
|
||||
|
||||
@@ -55,14 +58,19 @@ const RequestDetailItem: FC<Props> = ({ item, index, total }) => {
|
||||
<article className={index > 0 ? 'pt-8 mt-8 border-t border-[#EBEDF5]' : ''}>
|
||||
<div className="flex items-start gap-5">
|
||||
<div className="size-20 rounded-xl overflow-hidden bg-[#F5F7FC] shrink-0">
|
||||
<img
|
||||
src={productImage || placeholderProductImage}
|
||||
alt={item.product?.title ?? 'محصول'}
|
||||
className="size-full object-cover"
|
||||
onError={(e) => {
|
||||
e.currentTarget.src = placeholderProductImage
|
||||
}}
|
||||
/>
|
||||
{productImage ? (
|
||||
<PresignedImage
|
||||
src={productImage}
|
||||
alt={item.product?.title ?? 'محصول'}
|
||||
className="size-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
src={placeholderProductImage}
|
||||
alt={item.product?.title ?? 'محصول'}
|
||||
className="size-full object-cover"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex-1 min-w-0">
|
||||
@@ -115,19 +123,18 @@ const RequestDetailItem: FC<Props> = ({ item, index, total }) => {
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{fileAttachments.map((att) =>
|
||||
isImageUrl(att.url) ? (
|
||||
<a
|
||||
<button
|
||||
key={att.url}
|
||||
href={att.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
type="button"
|
||||
onClick={() => handleOpenLink(att.url)}
|
||||
className="block size-20 rounded-xl overflow-hidden border border-[#EBEDF5] hover:opacity-90 transition-opacity"
|
||||
>
|
||||
<img
|
||||
<PresignedImage
|
||||
src={att.url}
|
||||
alt="پیوست"
|
||||
className="size-full object-cover"
|
||||
/>
|
||||
</a>
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
key={att.url}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { type FC } from 'react'
|
||||
import moment from 'moment-jalaali'
|
||||
import { Paperclip2 } from 'iconsax-react'
|
||||
import { getFileNameAndExtensionFromUrl } from '@/config/func'
|
||||
import { getPresignedUrl } from '@/pages/uploader/service/UploaderService'
|
||||
import VoicePlayer from '@/components/VoicePlayer'
|
||||
import type { AttachmentsType } from '@/pages/order/types/Types'
|
||||
|
||||
@@ -25,7 +26,8 @@ const TicketMessage: FC<Props> = ({
|
||||
const fileAttachments = attachments.filter((a) => a.type !== 'voice')
|
||||
const voiceAttachments = attachments.filter((a) => a.type === 'voice')
|
||||
|
||||
const handleOpenLink = (url: string) => {
|
||||
const handleOpenLink = async (key: string) => {
|
||||
const url = await getPresignedUrl(key)
|
||||
window.open(url, '_blank', 'noopener,noreferrer')
|
||||
}
|
||||
|
||||
|
||||
@@ -54,26 +54,20 @@ const TicketSection: FC<Props> = ({ customerName }) => {
|
||||
}
|
||||
|
||||
if (audioFile) {
|
||||
await singleUpload.mutateAsync(audioFile, {
|
||||
onSuccess: (data) => {
|
||||
params.attachments.push({
|
||||
type: 'voice',
|
||||
url: data?.data?.url,
|
||||
})
|
||||
},
|
||||
const voiceResult = await singleUpload.mutateAsync(audioFile)
|
||||
params.attachments.push({
|
||||
type: 'voice',
|
||||
url: voiceResult.data.key,
|
||||
})
|
||||
}
|
||||
|
||||
if (files.length) {
|
||||
await multiUpload.mutateAsync(files, {
|
||||
onSuccess: (data) => {
|
||||
data?.data?.forEach((item) => {
|
||||
params.attachments.push({
|
||||
type: 'uploads_attach',
|
||||
url: item.url,
|
||||
})
|
||||
})
|
||||
},
|
||||
const filesResult = await multiUpload.mutateAsync(files)
|
||||
filesResult.data?.forEach((item) => {
|
||||
params.attachments.push({
|
||||
type: 'uploads_attach',
|
||||
url: item.key,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { getPresignedUrl } from "../service/UploaderService";
|
||||
|
||||
export const usePresignedUrl = (key?: string) => {
|
||||
const [url, setUrl] = useState<string>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!key) {
|
||||
setUrl(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
|
||||
getPresignedUrl(key)
|
||||
.then((resolvedUrl) => {
|
||||
if (!cancelled) {
|
||||
setUrl(resolvedUrl);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) {
|
||||
setUrl(undefined);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [key]);
|
||||
|
||||
return url;
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
import axios from "@/config/axios";
|
||||
import type {
|
||||
MultiUploadResponseType,
|
||||
PresignedUrlResponseType,
|
||||
SingleUploadResponseType,
|
||||
} from "../types/Types";
|
||||
|
||||
@@ -25,3 +26,11 @@ export const multiUpload = async (files: File[]) => {
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getPresignedUrl = async (key: string) => {
|
||||
const { data } = await axios.get<PresignedUrlResponseType>(
|
||||
"/admin/presigned-url",
|
||||
{ params: { key } },
|
||||
);
|
||||
return data.data.url;
|
||||
};
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import type { BaseResponse } from "@/shared/types/Types";
|
||||
|
||||
export type SingleUploadType = {
|
||||
url: string;
|
||||
file: {
|
||||
url: string;
|
||||
key: string;
|
||||
bucket: string;
|
||||
};
|
||||
export type UploadResultType = {
|
||||
key: string;
|
||||
bucket: string;
|
||||
};
|
||||
|
||||
export type SingleUploadResponseType = BaseResponse<SingleUploadType>;
|
||||
export type MultiUploadResponseType = BaseResponse<SingleUploadType[]>;
|
||||
export type PresignedUrlResultType = {
|
||||
key: string;
|
||||
url: string;
|
||||
expiresIn: number;
|
||||
};
|
||||
|
||||
export type SingleUploadResponseType = BaseResponse<UploadResultType>;
|
||||
export type MultiUploadResponseType = BaseResponse<UploadResultType[]>;
|
||||
export type PresignedUrlResponseType = BaseResponse<PresignedUrlResultType>;
|
||||
|
||||
Reference in New Issue
Block a user