diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index a2a087712d..a5b86beebf 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -3253,5 +3253,6 @@ "booker_limit_exceeded_error_reschedule": "You already have a booking for this event on {{date}}. Would you like to reschedule to the new selected time?", "duration_min_error": "Duration must be at least {{min}} minute(s)", "duration_max_error": "Duration cannot exceed {{max}} minutes (24 hours)", + "converted_image_size_limit_exceed": "Image size limit exceeded, please use a smaller image preferably in JPEG format", "ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑" } diff --git a/packages/features/ee/organizations/pages/settings/profile.tsx b/packages/features/ee/organizations/pages/settings/profile.tsx index acf241e301..a9ba9880c2 100644 --- a/packages/features/ee/organizations/pages/settings/profile.tsx +++ b/packages/features/ee/organizations/pages/settings/profile.tsx @@ -183,6 +183,11 @@ const OrgProfileForm = ({ defaultValues }: { defaultValues: FormValues }) => { const mutation = trpc.viewer.organizations.update.useMutation({ onError: (err) => { + // Handle JSON parsing errors from body size limit exceeded + if (err.message.includes("Unexpected token") && err.message.includes("Body excee")) { + showToast(t("converted_image_size_limit_exceed"), "error"); + return; + } showToast(err.message, "error"); }, onSuccess: async (res) => { diff --git a/packages/trpc/server/routers/viewer/organizations/update.handler.ts b/packages/trpc/server/routers/viewer/organizations/update.handler.ts index 4d36c08175..5f1beff0e0 100644 --- a/packages/trpc/server/routers/viewer/organizations/update.handler.ts +++ b/packages/trpc/server/routers/viewer/organizations/update.handler.ts @@ -161,7 +161,12 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => { metadata: mergeMetadata({ ...input.metadata }), }; - if (input.banner && input.banner.startsWith("data:image/png;base64,")) { + if ( + input.banner && + (input.banner.startsWith("data:image/png;base64,") || + input.banner.startsWith("data:image/jpeg;base64,") || + input.banner.startsWith("data:image/jpg;base64,")) + ) { const banner = await resizeBase64Image(input.banner, { maxSize: 1500 }); data.bannerUrl = await uploadLogo({ logo: banner, @@ -172,7 +177,12 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => { data.bannerUrl = null; } - if (input.logoUrl && input.logoUrl.startsWith("data:image/png;base64,")) { + if ( + input.logoUrl && + (input.logoUrl.startsWith("data:image/png;base64,") || + input.logoUrl.startsWith("data:image/jpeg;base64,") || + input.logoUrl.startsWith("data:image/jpg;base64,")) + ) { data.logoUrl = await uploadLogo({ logo: await resizeBase64Image(input.logoUrl), teamId: currentOrgId, diff --git a/packages/ui/components/image-uploader/BannerUploader.tsx b/packages/ui/components/image-uploader/BannerUploader.tsx index a1ec88df4e..458ae8e94d 100644 --- a/packages/ui/components/image-uploader/BannerUploader.tsx +++ b/packages/ui/components/image-uploader/BannerUploader.tsx @@ -209,6 +209,12 @@ async function getCroppedImg( const ctx = canvas.getContext("2d"); if (!ctx) throw new Error("Context is null, this should never happen."); + // Detect original image format from data URL + const originalFormat = + imageSrc.startsWith("data:image/jpeg") || imageSrc.startsWith("data:image/jpg") + ? "image/jpeg" + : "image/png"; + canvas.width = width; canvas.height = height; @@ -224,5 +230,6 @@ async function getCroppedImg( canvas.height ); - return canvas.toDataURL("image/png"); + // Use original format with quality setting for JPEG + return canvas.toDataURL(originalFormat, originalFormat === "image/jpeg" ? 0.6 : undefined); } diff --git a/packages/ui/components/image-uploader/ImageUploader.tsx b/packages/ui/components/image-uploader/ImageUploader.tsx index ba6a637110..12ab7e0c67 100644 --- a/packages/ui/components/image-uploader/ImageUploader.tsx +++ b/packages/ui/components/image-uploader/ImageUploader.tsx @@ -192,6 +192,12 @@ async function getCroppedImg(imageSrc: string, pixelCrop: Area): Promise const ctx = canvas.getContext("2d"); if (!ctx) throw new Error("Context is null, this should never happen."); + // Detect original image format from data URL + const originalFormat = + imageSrc.startsWith("data:image/jpeg") || imageSrc.startsWith("data:image/jpg") + ? "image/jpeg" + : "image/png"; + const maxSize = Math.max(image.naturalWidth, image.naturalHeight); const resizeRatio = MAX_IMAGE_SIZE / maxSize < 1 ? Math.max(MAX_IMAGE_SIZE / maxSize, 0.75) : 1; @@ -216,7 +222,7 @@ async function getCroppedImg(imageSrc: string, pixelCrop: Area): Promise // on very low ratios, the quality of the resize becomes awful. For this reason the resizeRatio is limited to 0.75 if (resizeRatio <= 0.75) { // With a smaller image, thus improved ratio. Keep doing this until the resizeRatio > 0.75. - return getCroppedImg(canvas.toDataURL("image/png"), { + return getCroppedImg(canvas.toDataURL(originalFormat), { width: canvas.width, height: canvas.height, x: 0, @@ -224,5 +230,6 @@ async function getCroppedImg(imageSrc: string, pixelCrop: Area): Promise }); } - return canvas.toDataURL("image/png"); + // Use original format with quality setting for JPEG + return canvas.toDataURL(originalFormat, originalFormat === "image/jpeg" ? 0.9 : undefined); }