From 7271ea8f215181937669a03f2caa4b5f06d67e19 Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Fri, 10 Jul 2026 20:08:31 +0330 Subject: [PATCH] presigned url --- src/components/PresignedImage.tsx | 18 +++++++++ src/components/UploadBoxDraggble.tsx | 18 +++++---- src/components/VoicePlayer.tsx | 8 ++-- src/pages/invoice/Create.tsx | 2 +- src/pages/invoice/Update.tsx | 2 +- src/pages/learning/Create.tsx | 4 +- src/pages/learning/List.tsx | 3 +- src/pages/order/ConvertToOrders.tsx | 2 +- src/pages/order/EditOrder.tsx | 2 +- .../components/InvoiceItemDetailSection.tsx | 3 +- src/pages/order/components/Order.tsx | 26 +++++-------- .../order/components/OrderDetailSidebar.tsx | 21 ++++++----- src/pages/order/components/OrderItem.tsx | 3 +- .../order/components/SingleOrderEdit.tsx | 26 +++++-------- src/pages/order/components/TicketSection.tsx | 30 +++++++-------- src/pages/product/Create.tsx | 2 +- src/pages/product/Update.tsx | 2 +- src/pages/product/category/Create.tsx | 2 +- src/pages/product/category/List.tsx | 3 +- src/pages/product/category/Update.tsx | 2 +- .../requests/components/RequestDetailItem.tsx | 37 +++++++++++-------- .../requests/components/TicketMessage.tsx | 4 +- .../requests/components/TicketSection.tsx | 26 +++++-------- src/pages/uploader/hooks/usePresignedUrl.ts | 33 +++++++++++++++++ src/pages/uploader/service/UploaderService.ts | 9 +++++ src/pages/uploader/types/Types.ts | 21 ++++++----- 26 files changed, 186 insertions(+), 123 deletions(-) create mode 100644 src/components/PresignedImage.tsx create mode 100644 src/pages/uploader/hooks/usePresignedUrl.ts diff --git a/src/components/PresignedImage.tsx b/src/components/PresignedImage.tsx new file mode 100644 index 0000000..9cb5871 --- /dev/null +++ b/src/components/PresignedImage.tsx @@ -0,0 +1,18 @@ +import { type FC, type ImgHTMLAttributes } from "react"; +import { usePresignedUrl } from "@/pages/uploader/hooks/usePresignedUrl"; + +type Props = ImgHTMLAttributes & { + src?: string; +}; + +const PresignedImage: FC = ({ src, alt = "", ...props }) => { + const resolvedSrc = usePresignedUrl(src); + + if (!resolvedSrc) { + return null; + } + + return {alt}; +}; + +export default PresignedImage; diff --git a/src/components/UploadBoxDraggble.tsx b/src/components/UploadBoxDraggble.tsx index e0a48aa..bd33f88 100644 --- a/src/components/UploadBoxDraggble.tsx +++ b/src/components/UploadBoxDraggble.tsx @@ -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) => { return (
- { - if (props.getCover) { - setCover(item) - props.getCover(item) - } - }} src={item} + { + 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' : '' - )} /> + )} + />
handleDeletePreview(index)} className='absolute -left-2 -top-2 shadow-md bg-white size-5 rounded-full flex justify-center items-center'>
diff --git a/src/components/VoicePlayer.tsx b/src/components/VoicePlayer.tsx index b96fab0..d9a592d 100644 --- a/src/components/VoicePlayer.tsx +++ b/src/components/VoicePlayer.tsx @@ -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 = ({ url }) => { + const resolvedUrl = usePresignedUrl(url) const audioRef = useRef(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 = ({ url }) => { return () => { audio.removeEventListener('timeupdate', update) } - }, []) + }, [resolvedUrl]) const toggle = () => { if (!audioRef.current) return @@ -66,7 +68,7 @@ const VoicePlayer: FC = ({ url }) => { })}
-
) } diff --git a/src/pages/invoice/Create.tsx b/src/pages/invoice/Create.tsx index a459b1c..6b10190 100644 --- a/src/pages/invoice/Create.tsx +++ b/src/pages/invoice/Create.tsx @@ -73,7 +73,7 @@ const CreateInvoice: FC = () => { uploadResult?.data?.forEach((item) => { attachments.push({ type: "uploads_attach", - url: item.url, + url: item.key, }); }); } diff --git a/src/pages/invoice/Update.tsx b/src/pages/invoice/Update.tsx index e3a172f..3ba0cff 100644 --- a/src/pages/invoice/Update.tsx +++ b/src/pages/invoice/Update.tsx @@ -127,7 +127,7 @@ const UpdateInvoice: FC = () => { uploadResult?.data?.forEach((item) => { attachments.push({ type: "uploads_attach", - url: item.url, + url: item.key, }); }); } diff --git a/src/pages/learning/Create.tsx b/src/pages/learning/Create.tsx index 80bae34..28c6ecb 100644 --- a/src/pages/learning/Create.tsx +++ b/src/pages/learning/Create.tsx @@ -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: () => { diff --git a/src/pages/learning/List.tsx b/src/pages/learning/List.tsx index e20cc09..8b7a3ed 100644 --- a/src/pages/learning/List.tsx +++ b/src/pages/learning/List.tsx @@ -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(1) @@ -19,7 +20,7 @@ const LearningList: FC = () => { key: 'coverUrl', width: '100px', render: (item: LearningItemType) => ( - {item.title} + ) }, { diff --git a/src/pages/order/ConvertToOrders.tsx b/src/pages/order/ConvertToOrders.tsx index 2d2ad92..0393293 100644 --- a/src/pages/order/ConvertToOrders.tsx +++ b/src/pages/order/ConvertToOrders.tsx @@ -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) }) } diff --git a/src/pages/order/EditOrder.tsx b/src/pages/order/EditOrder.tsx index b28e623..a97d1b1 100644 --- a/src/pages/order/EditOrder.tsx +++ b/src/pages/order/EditOrder.tsx @@ -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) }) } diff --git a/src/pages/order/components/InvoiceItemDetailSection.tsx b/src/pages/order/components/InvoiceItemDetailSection.tsx index 2a84767..7fa1a50 100644 --- a/src/pages/order/components/InvoiceItemDetailSection.tsx +++ b/src/pages/order/components/InvoiceItemDetailSection.tsx @@ -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 = ({ invoiceItemId }) => {
{imageUrl ? ( - {invoiceItem.product?.title} = ({ 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) { diff --git a/src/pages/order/components/OrderDetailSidebar.tsx b/src/pages/order/components/OrderDetailSidebar.tsx index d1b0f92..e11305e 100644 --- a/src/pages/order/components/OrderDetailSidebar.tsx +++ b/src/pages/order/components/OrderDetailSidebar.tsx @@ -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 = ({

پیوست‌ها

diff --git a/src/pages/order/components/OrderItem.tsx b/src/pages/order/components/OrderItem.tsx index c04593c..95687dc 100644 --- a/src/pages/order/components/OrderItem.tsx +++ b/src/pages/order/components/OrderItem.tsx @@ -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 = ({ item, estimatedDays, designerName, index }) => {
{imageUrl ? ( - {item.product?.title} = ({ 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) { diff --git a/src/pages/order/components/TicketSection.tsx b/src/pages/order/components/TicketSection.tsx index 6df98be..1528562 100644 --- a/src/pages/order/components/TicketSection.tsx +++ b/src/pages/order/components/TicketSection.tsx @@ -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' diff --git a/src/pages/product/Create.tsx b/src/pages/product/Create.tsx index 0c305f0..c45a678 100644 --- a/src/pages/product/Create.tsx +++ b/src/pages/product/Create.tsx @@ -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) diff --git a/src/pages/product/Update.tsx b/src/pages/product/Update.tsx index 60273c6..53f1d0a 100644 --- a/src/pages/product/Update.tsx +++ b/src/pages/product/Update.tsx @@ -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) diff --git a/src/pages/product/category/Create.tsx b/src/pages/product/category/Create.tsx index 9f55d34..d512405 100644 --- a/src/pages/product/category/Create.tsx +++ b/src/pages/product/category/Create.tsx @@ -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) } }) diff --git a/src/pages/product/category/List.tsx b/src/pages/product/category/List.tsx index c732a60..90a41df 100644 --- a/src/pages/product/category/List.tsx +++ b/src/pages/product/category/List.tsx @@ -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 ? {item.title} : '-' + render: (item) => item.avatarUrl ? : '-' }, { title: 'عنوان', diff --git a/src/pages/product/category/Update.tsx b/src/pages/product/category/Update.tsx index 023eed3..c1b25de 100644 --- a/src/pages/product/category/Update.tsx +++ b/src/pages/product/category/Update.tsx @@ -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) } }) diff --git a/src/pages/requests/components/RequestDetailItem.tsx b/src/pages/requests/components/RequestDetailItem.tsx index 44dd161..e6d41f2 100644 --- a/src/pages/requests/components/RequestDetailItem.tsx +++ b/src/pages/requests/components/RequestDetailItem.tsx @@ -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 = ({ 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 = ({ item, index, total }) => {
0 ? 'pt-8 mt-8 border-t border-[#EBEDF5]' : ''}>
- {item.product?.title { - e.currentTarget.src = placeholderProductImage - }} - /> + {productImage ? ( + + ) : ( + {item.product?.title + )}
@@ -115,19 +123,18 @@ const RequestDetailItem: FC = ({ item, index, total }) => {
{fileAttachments.map((att) => isImageUrl(att.url) ? ( - handleOpenLink(att.url)} className="block size-20 rounded-xl overflow-hidden border border-[#EBEDF5] hover:opacity-90 transition-opacity" > - پیوست - + ) : (