From 80487aea653ed4ebf1e3121618f64881c5b0c6b5 Mon Sep 17 00:00:00 2001 From: Stivenjs Date: Sat, 17 May 2025 14:34:30 -0500 Subject: [PATCH 1/3] refactor(account-settings): extract Lottie animation logic into reusable component Moved Lottie animation logic from CancellationDialog to a new LottieWrapper component to improve code reusability and maintainability. Removed unused Loader component from AccountSettingsPage and updated Suspense fallback to be more concise. --- .../components/CancellationDialog.tsx | 11 ++++++++--- .../account-settings/components/LottieWrapper.tsx | 12 ++++++++++++ app/(main-layout)/account-settings/page.tsx | 13 +------------ 3 files changed, 21 insertions(+), 15 deletions(-) create mode 100644 app/(main-layout)/account-settings/components/LottieWrapper.tsx diff --git a/app/(main-layout)/account-settings/components/CancellationDialog.tsx b/app/(main-layout)/account-settings/components/CancellationDialog.tsx index 47409ad5..30addca1 100644 --- a/app/(main-layout)/account-settings/components/CancellationDialog.tsx +++ b/app/(main-layout)/account-settings/components/CancellationDialog.tsx @@ -1,6 +1,5 @@ import { useState } from 'react' import { motion, AnimatePresence } from 'framer-motion' -import Lottie from 'lottie-react' import { format } from 'date-fns' import { es } from 'date-fns/locale' import { useRouter } from 'next/navigation' @@ -14,6 +13,7 @@ import { AlertDialogFooter, } from '@/components/ui/alert-dialog' import { Button } from '@/components/ui/button' +import dynamic from 'next/dynamic' import cancelAnimation from '@/app/(main-layout)/account-settings/anim/cancel-animation.json' import successAnimation from '@/app/(main-layout)/account-settings/anim/success-animation.json' @@ -32,6 +32,11 @@ export function CancellationDialog({ isPendingFree, expirationDate, }: CancellationDialogProps) { + const LottieWrapper = dynamic( + () => import('@/app/(main-layout)/account-settings/components/LottieWrapper'), + { ssr: false } + ) + const [isOpen, setIsOpen] = useState(false) const [isCancelled, setIsCancelled] = useState(false) const router = useRouter() @@ -80,7 +85,7 @@ export function CancellationDialog({ >
- +
¿Estás seguro de cancelar tu suscripción? @@ -118,7 +123,7 @@ export function CancellationDialog({ >
- +
Tu suscripción ha sido cancelada diff --git a/app/(main-layout)/account-settings/components/LottieWrapper.tsx b/app/(main-layout)/account-settings/components/LottieWrapper.tsx new file mode 100644 index 00000000..7ef71530 --- /dev/null +++ b/app/(main-layout)/account-settings/components/LottieWrapper.tsx @@ -0,0 +1,12 @@ +'use client' + +import Lottie from 'lottie-react' + +interface LottieWrapperProps { + animationData: object + loop?: boolean +} + +export default function LottieWrapper({ animationData, loop = true }: LottieWrapperProps) { + return +} diff --git a/app/(main-layout)/account-settings/page.tsx b/app/(main-layout)/account-settings/page.tsx index caf226d7..c7015672 100644 --- a/app/(main-layout)/account-settings/page.tsx +++ b/app/(main-layout)/account-settings/page.tsx @@ -5,7 +5,6 @@ import { AccountSettings } from '@/app/(main-layout)/account-settings/components import { PaymentSettings } from '@/app/(main-layout)/account-settings/components/PaymentSettings' import { ActiveSessions } from '@/app/(main-layout)/account-settings/components/ActiveSessions' import { useState, useEffect, Suspense } from 'react' -import { Loader } from '@/components/ui/loader' import { Amplify } from 'aws-amplify' import { useSearchParams } from 'next/navigation' import useUserStore from '@/context/core/userStore' @@ -67,17 +66,7 @@ function AccountSettingsContent() { export default function AccountSettingsPage() { return (
- - } - > +
From ef616a862c41b11ce34a678de8c76a8a1f844d30 Mon Sep 17 00:00:00 2001 From: Stivenjs Date: Sat, 17 May 2025 15:25:08 -0500 Subject: [PATCH 2/3] feat: add AWS region to S3 URLs and new CDN hostname Update S3 public URLs to include the AWS region for proper bucket access. Also, add 'cdn.fasttify.com' as a new image hostname in the Next.js config to support additional CDN functionality. --- .../account-settings/hooks/useUpdateProfilePicture.ts | 3 ++- app/store/hooks/useLogoUpload.ts | 3 ++- app/store/hooks/useProductImageUpload.ts | 4 +++- next.config.ts | 5 +++++ 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/app/(main-layout)/account-settings/hooks/useUpdateProfilePicture.ts b/app/(main-layout)/account-settings/hooks/useUpdateProfilePicture.ts index 9f749e0c..7ee3c142 100644 --- a/app/(main-layout)/account-settings/hooks/useUpdateProfilePicture.ts +++ b/app/(main-layout)/account-settings/hooks/useUpdateProfilePicture.ts @@ -32,12 +32,13 @@ export function useUpdateProfilePicture() { // 2. Construir la URL pública manualmente. const bucketName = process.env.NEXT_PUBLIC_S3_URL + const awsRegion = process.env.NEXT_PUBLIC_AWS_REGION if (!bucketName) { throw new Error('There is no bucket for profile pictures') } - const publicUrl = `${bucketName}/${result.path}` + const publicUrl = `https://${bucketName}.s3.${awsRegion}.amazonaws.com/${result.path}` // 3. Actualizar el atributo 'picture' del usuario con la URL pública. await updateUserAttributes({ diff --git a/app/store/hooks/useLogoUpload.ts b/app/store/hooks/useLogoUpload.ts index 29aa8e54..9fb3fc36 100644 --- a/app/store/hooks/useLogoUpload.ts +++ b/app/store/hooks/useLogoUpload.ts @@ -28,6 +28,7 @@ export function useLogoUpload(): UseLogoUploadReturn { // Obtener el bucket correcto para logos de tienda const bucketName = process.env.NEXT_PUBLIC_S3_URL + const awsRegion = process.env.NEXT_PUBLIC_AWS_REGION if (!bucketName) { throw new Error('There is no bucket for store logos') @@ -62,7 +63,7 @@ export function useLogoUpload(): UseLogoUploadReturn { }).result // Construir la URL pública correcta usando el nombre del bucket - const publicUrl = `${bucketName}/${key}` + const publicUrl = `https://${bucketName}.s3.${awsRegion}.amazonaws.com/${key}` setStatus('success') diff --git a/app/store/hooks/useProductImageUpload.ts b/app/store/hooks/useProductImageUpload.ts index cf4ef2ff..521cf014 100644 --- a/app/store/hooks/useProductImageUpload.ts +++ b/app/store/hooks/useProductImageUpload.ts @@ -18,6 +18,7 @@ export function useProductImageUpload() { // Obtener el bucket correcto para imágenes de productos const bucketName = process.env.NEXT_PUBLIC_S3_URL + const awsRegion = process.env.NEXT_PUBLIC_AWS_REGION if (!bucketName) { throw new Error('There is no bucket for product images') @@ -45,7 +46,8 @@ export function useProductImageUpload() { }).result // Construir la URL pública correcta usando el nombre del bucket - const publicUrl = `${bucketName}/${result.path}` + const publicUrl = `https://${bucketName}.s3.${awsRegion}.amazonaws.com/${result.path}` + return { url: publicUrl, alt: '', diff --git a/next.config.ts b/next.config.ts index ff72d7f8..8fadab22 100644 --- a/next.config.ts +++ b/next.config.ts @@ -29,6 +29,11 @@ const nextConfig = { hostname: 'd1etr7t5j9fzio.cloudfront.net', port: '', }, + { + protocol: 'https', + hostname: 'cdn.fasttify.com', + port: '', + }, ], }, } From 88f6be5e5ce847af43ead025046fe522fb07a8a1 Mon Sep 17 00:00:00 2001 From: Stivenjs Date: Sat, 17 May 2025 15:40:52 -0500 Subject: [PATCH 3/3] feat: add CloudFront domain support for asset URLs Introduce a new environment variable `CLOUDFRONT_DOMAIN` to allow using CloudFront for asset URLs. This provides a fallback to S3 URLs when CloudFront is not configured. Updated hooks for profile picture, logo, and product image uploads to conditionally construct URLs based on the presence of CloudFront domain. This improves flexibility and performance for asset delivery. --- .env.example | 1 + .../hooks/useUpdateProfilePicture.ts | 17 ++++++++++--- app/store/hooks/useLogoUpload.ts | 24 +++++++++++++++--- app/store/hooks/useProductImageUpload.ts | 25 ++++++++++++++++--- 4 files changed, 56 insertions(+), 11 deletions(-) diff --git a/.env.example b/.env.example index 737109d0..7e41b107 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,3 @@ BUCKET_NAME="YOUR_BUCKET_NAME" AWS_REGION="YOUR_AWS_REGION" +CLOUDFRONT_DOMAIN="YOUR_CLOUDFRONT_DOMAIN" diff --git a/app/(main-layout)/account-settings/hooks/useUpdateProfilePicture.ts b/app/(main-layout)/account-settings/hooks/useUpdateProfilePicture.ts index 7ee3c142..a7224dc8 100644 --- a/app/(main-layout)/account-settings/hooks/useUpdateProfilePicture.ts +++ b/app/(main-layout)/account-settings/hooks/useUpdateProfilePicture.ts @@ -30,15 +30,26 @@ export function useUpdateProfilePicture() { }, }).result - // 2. Construir la URL pública manualmente. + // 2. Construir la URL pública condicionalmente. const bucketName = process.env.NEXT_PUBLIC_S3_URL const awsRegion = process.env.NEXT_PUBLIC_AWS_REGION + const cloudFrontDomain = process.env.NEXT_PUBLIC_CLOUDFRONT_DOMAIN if (!bucketName) { - throw new Error('There is no bucket for profile pictures') + throw new Error('NEXT_PUBLIC_S3_URL is not defined in your environment variables') } - const publicUrl = `https://${bucketName}.s3.${awsRegion}.amazonaws.com/${result.path}` + let publicUrl: string + const s3Key = result.path + + if (cloudFrontDomain && cloudFrontDomain.trim() !== '') { + // Usar CloudFront para producción (o cuando esté configurado) + publicUrl = `https://${cloudFrontDomain}/${s3Key}` + } else { + // Fallback a la URL de S3 para otros entornos + const regionForS3Url = awsRegion + publicUrl = `https://${bucketName}.s3.${regionForS3Url}.amazonaws.com/${s3Key}` + } // 3. Actualizar el atributo 'picture' del usuario con la URL pública. await updateUserAttributes({ diff --git a/app/store/hooks/useLogoUpload.ts b/app/store/hooks/useLogoUpload.ts index 9fb3fc36..61c67411 100644 --- a/app/store/hooks/useLogoUpload.ts +++ b/app/store/hooks/useLogoUpload.ts @@ -26,12 +26,19 @@ export function useLogoUpload(): UseLogoUploadReturn { const [status, setStatus] = useState('idle') const [error, setError] = useState(null) - // Obtener el bucket correcto para logos de tienda + // Obtener el bucket y la región desde las variables de entorno const bucketName = process.env.NEXT_PUBLIC_S3_URL const awsRegion = process.env.NEXT_PUBLIC_AWS_REGION + const cloudFrontDomain = process.env.NEXT_PUBLIC_CLOUDFRONT_DOMAIN if (!bucketName) { - throw new Error('There is no bucket for store logos') + throw new Error('environment variable NEXT_PUBLIC_S3_URL is not defined') + } + + if (!awsRegion && (!cloudFrontDomain || cloudFrontDomain.trim() === '')) { + throw new Error( + 'environment variable NEXT_PUBLIC_AWS_REGION is not defined or NEXT_PUBLIC_CLOUDFRONT_DOMAIN is not defined or empty' + ) } const reset = () => { @@ -62,8 +69,17 @@ export function useLogoUpload(): UseLogoUploadReturn { }, }).result - // Construir la URL pública correcta usando el nombre del bucket - const publicUrl = `https://${bucketName}.s3.${awsRegion}.amazonaws.com/${key}` + // Construir la URL pública condicionalmente + let publicUrl: string + const s3Key = result.path + + if (cloudFrontDomain && cloudFrontDomain.trim() !== '') { + publicUrl = `https://${cloudFrontDomain}/${s3Key}` + } else { + // Fallback a la URL de S3 + const regionForS3Url = awsRegion + publicUrl = `https://${bucketName}.s3.${regionForS3Url}.amazonaws.com/${s3Key}` + } setStatus('success') diff --git a/app/store/hooks/useProductImageUpload.ts b/app/store/hooks/useProductImageUpload.ts index 521cf014..1803223a 100644 --- a/app/store/hooks/useProductImageUpload.ts +++ b/app/store/hooks/useProductImageUpload.ts @@ -16,12 +16,19 @@ export function useProductImageUpload() { const [isLoading, setIsLoading] = useState(false) const [error, setError] = useState(null) - // Obtener el bucket correcto para imágenes de productos + // Obtener el bucket, la región y el dominio del CDN desde las variables de entorno const bucketName = process.env.NEXT_PUBLIC_S3_URL const awsRegion = process.env.NEXT_PUBLIC_AWS_REGION + const cloudFrontDomain = process.env.NEXT_PUBLIC_CLOUDFRONT_DOMAIN if (!bucketName) { - throw new Error('There is no bucket for product images') + throw new Error('environment variable NEXT_PUBLIC_S3_URL is not defined') + } + // Es bueno tener awsRegion si se usa el fallback a S3 + if (!awsRegion && (!cloudFrontDomain || cloudFrontDomain.trim() === '')) { + throw new Error( + 'environment variable NEXT_PUBLIC_AWS_REGION is not defined or NEXT_PUBLIC_CLOUDFRONT_DOMAIN is not defined or empty' + ) } const uploadProductImage = async (file: File, storeId: string): Promise => { @@ -45,8 +52,18 @@ export function useProductImageUpload() { data: file, }).result - // Construir la URL pública correcta usando el nombre del bucket - const publicUrl = `https://${bucketName}.s3.${awsRegion}.amazonaws.com/${result.path}` + // Construir la URL pública condicionalmente + let publicUrl: string + const s3Key = result.path + + if (cloudFrontDomain && cloudFrontDomain.trim() !== '') { + // Usar CloudFront (generalmente para producción o cuando esté configurado) + publicUrl = `https://${cloudFrontDomain}/${s3Key}` + } else { + // Fallback a la URL de S3 + const regionForS3Url = awsRegion + publicUrl = `https://${bucketName}.s3.${regionForS3Url}.amazonaws.com/${s3Key}` + } return { url: publicUrl,