From 126e874888107dd27b338ab85f5ea3308e686964 Mon Sep 17 00:00:00 2001 From: Stivenjs Date: Mon, 19 May 2025 22:17:14 -0500 Subject: [PATCH 1/2] refactor(inventory-page): enhance routing and store ID handling Updated the InventoryPage component to retrieve the store ID using `useParams` and `usePathname`, improving dynamic routing. Modified the product link to utilize the new routing structure for better navigation. Adjusted the collection form redirection to point to the correct products collection route, ensuring consistency in navigation across the application. --- app/store/components/product-management/InventoryPage.tsx | 8 +++++++- .../product-management/utils/collection-form-utils.ts | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/store/components/product-management/InventoryPage.tsx b/app/store/components/product-management/InventoryPage.tsx index 60c05fbc..bd260772 100644 --- a/app/store/components/product-management/InventoryPage.tsx +++ b/app/store/components/product-management/InventoryPage.tsx @@ -2,8 +2,14 @@ import Link from 'next/link' import { Button } from '@/components/ui/button' import { Card } from '@/components/ui/card' import { Icons } from '@/app/store/icons/index' +import { getStoreId } from '@/utils/store-utils' +import { useParams, usePathname } from 'next/navigation' +import { routes } from '@/utils/routes' export function InventoryPage() { + const pathname = usePathname() + const params = useParams() + const storeId = getStoreId(params, pathname) return (

Inventario

@@ -24,7 +30,7 @@ export function InventoryPage() {

{/* Botón */} - + diff --git a/app/store/components/product-management/utils/collection-form-utils.ts b/app/store/components/product-management/utils/collection-form-utils.ts index 9585dfb5..1d3154da 100644 --- a/app/store/components/product-management/utils/collection-form-utils.ts +++ b/app/store/components/product-management/utils/collection-form-utils.ts @@ -264,7 +264,7 @@ export const useCollectionForm = ({ ) // Redirigir a la lista de colecciones - router.push(routes.store.collections(storeId)) + router.push(routes.store.products.collections(storeId)) // No desactivamos isSubmitting para mantener el botón deshabilitado hasta la redirección } catch (error) { console.error('Error al guardar la colección:', error) From e910ddd6036c1b69143ac35210417bbdadfc3f96 Mon Sep 17 00:00:00 2001 From: Stivenjs Date: Tue, 20 May 2025 23:20:11 -0500 Subject: [PATCH 2/2] refactor(data-models): update secondary indexes and improve data fetching logic Modified the resource schema to change the secondary index from 'storeId' to 'userId' for better user-specific data retrieval. Enhanced data fetching methods in various hooks to utilize new model methods for listing user stores and collections, improving clarity and efficiency. Updated product management components to streamline loading states and pagination options, ensuring a more user-friendly experience. --- amplify/data/resource.ts | 3 +- .../my-store/hooks/useUserStores.ts | 17 +++-- .../product-management/ProductList.tsx | 3 +- .../product-table/product-pagination.tsx | 2 + app/store/hooks/useCollections.ts | 75 ++++++++----------- app/store/hooks/useProducts.ts | 47 ++++++------ middlewares/ownership/collectionOwnership.ts | 41 ++++------ middlewares/ownership/productOwnership.ts | 41 ++++------ middlewares/store-access/store.ts | 5 +- middlewares/store-access/storeAccess.ts | 17 +++-- utils/AmplifyUtils.ts | 1 + 11 files changed, 111 insertions(+), 141 deletions(-) diff --git a/amplify/data/resource.ts b/amplify/data/resource.ts index 1ce7cdfd..693f2ed4 100644 --- a/amplify/data/resource.ts +++ b/amplify/data/resource.ts @@ -114,7 +114,7 @@ const schema = a onboardingCompleted: a.boolean().required(), onboardingData: a.json(), }) - .secondaryIndexes(index => [index('storeId')]) + .secondaryIndexes(index => [index('userId')]) .authorization(allow => [allow.authenticated().to(['read', 'update', 'delete', 'create'])]), Product: a @@ -142,6 +142,7 @@ const schema = a collection: a.belongsTo('Collection', 'collectionId'), owner: a.string().required(), }) + .secondaryIndexes(index => [index('storeId'), index('collectionId')]) .authorization(allow => [ allow.ownerDefinedIn('owner').to(['update', 'delete', 'read', 'create']), allow.guest().to(['read']), diff --git a/app/(setup-layout)/my-store/hooks/useUserStores.ts b/app/(setup-layout)/my-store/hooks/useUserStores.ts index 267ac900..27ad8ae2 100644 --- a/app/(setup-layout)/my-store/hooks/useUserStores.ts +++ b/app/(setup-layout)/my-store/hooks/useUserStores.ts @@ -2,7 +2,9 @@ import { use } from 'react' import { generateClient } from 'aws-amplify/data' import type { Schema } from '@/amplify/data/resource' -const client = generateClient() +const client = generateClient({ + authMode: 'userPool', +}) const STORE_LIMITS = { Imperial: 5, @@ -47,13 +49,14 @@ export function getUserStores(userId: string | null, userPlan?: string) { async function fetchUserStores(userId: string, userPlan?: string) { try { // Obtener todas las tiendas del usuario (para verificar límites) - const { data: allUserStores } = await client.models.UserStore.list({ - authMode: 'userPool', - filter: { - userId: { eq: userId }, + const { data: allUserStores } = await client.models.UserStore.listUserStoreByUserId( + { + userId: userId, }, - selectionSet: ['storeId', 'storeName', 'storeType', 'onboardingCompleted'], - }) + { + selectionSet: ['storeId', 'storeName', 'storeType', 'onboardingCompleted'], + } + ) const completedStores = allUserStores || [] diff --git a/app/store/components/product-management/ProductList.tsx b/app/store/components/product-management/ProductList.tsx index cd5359c9..6489fa79 100644 --- a/app/store/components/product-management/ProductList.tsx +++ b/app/store/components/product-management/ProductList.tsx @@ -125,8 +125,7 @@ export function ProductList({
{loading && (
- -

Cargando productos...

+
)} diff --git a/app/store/components/product-management/product-table/product-pagination.tsx b/app/store/components/product-management/product-table/product-pagination.tsx index 752b5124..1c6919d6 100644 --- a/app/store/components/product-management/product-table/product-pagination.tsx +++ b/app/store/components/product-management/product-table/product-pagination.tsx @@ -47,6 +47,8 @@ export function ProductPagination({ 5 10 20 + 50 + 100
diff --git a/app/store/hooks/useCollections.ts b/app/store/hooks/useCollections.ts index 09534269..520ba163 100644 --- a/app/store/hooks/useCollections.ts +++ b/app/store/hooks/useCollections.ts @@ -3,7 +3,9 @@ import { generateClient } from 'aws-amplify/data' import type { Schema } from '@/amplify/data/resource' import { useQuery, useMutation, useQueryClient, UseQueryResult } from '@tanstack/react-query' -const client = generateClient() +const client = generateClient({ + authMode: 'userPool', +}) // Clave base para las consultas de colecciones const COLLECTIONS_KEY = 'collections' @@ -65,17 +67,14 @@ export const useCollections = () => { queryKey: [COLLECTIONS_KEY, id], queryFn: async () => { // Obtener la colección - const collection = await performOperation(() => - client.models.Collection.get({ id }, { authMode: 'userPool' }) - ) + const collection = await performOperation(() => client.models.Collection.get({ id })) // Si la colección existe, obtener sus productos if (collection) { // Obtener productos de la colección const productsData = await performOperation(() => - client.models.Product.list({ - filter: { collectionId: { eq: id } }, - authMode: 'userPool', + client.models.Product.listProductByCollectionId({ + collectionId: id, }) ) @@ -102,20 +101,23 @@ export const useCollections = () => { return useQuery({ queryKey: [COLLECTIONS_KEY, 'list', storeId], queryFn: () => { - const filter = storeId ? { storeId: { eq: storeId } } : undefined + if (!storeId) { + throw new Error('Store ID is required to list collections by store.') + } + return performOperation(() => - client.models.Collection.list({ - filter, - authMode: 'userPool', + client.models.Collection.listCollectionByStoreId({ + storeId: storeId, }) ) }, staleTime: 5 * 60 * 1000, // 5 minutos en caché + enabled: !!storeId, }) } /** - * Obtiene los productos de una colección específica + * Obtiene los productos de una colección específica usando el GSI en collectionId * @param collectionId - ID de la colección * @returns Resultado de la consulta con los productos de la colección */ @@ -124,9 +126,8 @@ export const useCollections = () => { queryKey: [COLLECTIONS_KEY, collectionId, 'products'], queryFn: () => { return performOperation(() => - client.models.Product.list({ - filter: { collectionId: { eq: collectionId } }, - authMode: 'userPool', + client.models.Product.listProductByCollectionId({ + collectionId: collectionId, }) ) }, @@ -141,11 +142,7 @@ export const useCollections = () => { const useCreateCollection = () => { return useMutation({ mutationFn: (collectionInput: CollectionInput) => - performOperation(() => - client.models.Collection.create(collectionInput, { - authMode: 'userPool', - }) - ), + performOperation(() => client.models.Collection.create(collectionInput)), onSuccess: () => { // Invalidar consultas para actualizar la lista queryClient.invalidateQueries({ queryKey: [COLLECTIONS_KEY, 'list'] }) @@ -160,15 +157,10 @@ export const useCollections = () => { return useMutation({ mutationFn: ({ id, data }: { id: string; data: Partial }) => performOperation(() => - client.models.Collection.update( - { - id, - ...data, - }, - { - authMode: 'userPool', - } - ) + client.models.Collection.update({ + id, + ...data, + }) ), onSuccess: data => { // Actualizar la colección en caché @@ -183,8 +175,7 @@ export const useCollections = () => { */ const useDeleteCollection = () => { return useMutation({ - mutationFn: (id: string) => - performOperation(() => client.models.Collection.delete({ id }, { authMode: 'userPool' })), + mutationFn: (id: string) => performOperation(() => client.models.Collection.delete({ id })), onSuccess: (_, id) => { // Eliminar la colección de la caché queryClient.removeQueries({ queryKey: [COLLECTIONS_KEY, id] }) @@ -202,13 +193,10 @@ export const useCollections = () => { const addProductToCollection = async (collectionId: string, productId: string) => { // Actualizar el producto para asignarle la colección return performOperation(() => - client.models.Product.update( - { - id: productId, - collectionId: collectionId, - }, - { authMode: 'userPool' } - ) + client.models.Product.update({ + id: productId, + collectionId: collectionId, + }) ) } @@ -220,13 +208,10 @@ export const useCollections = () => { const removeProductFromCollection = async (productId: string) => { // Actualizar el producto para eliminar la referencia a la colección return performOperation(() => - client.models.Product.update( - { - id: productId, - collectionId: null, // Eliminar la referencia a la colección - }, - { authMode: 'userPool' } - ) + client.models.Product.update({ + id: productId, + collectionId: null, // Eliminar la referencia a la colección + }) ) } diff --git a/app/store/hooks/useProducts.ts b/app/store/hooks/useProducts.ts index f4d992d6..257691c1 100644 --- a/app/store/hooks/useProducts.ts +++ b/app/store/hooks/useProducts.ts @@ -3,7 +3,9 @@ import { generateClient } from 'aws-amplify/api' import { getCurrentUser } from 'aws-amplify/auth' import type { Schema } from '@/amplify/data/resource' -const client = generateClient() +const client = generateClient({ + authMode: 'userPool', +}) /** * Interfaz para representar un producto @@ -126,7 +128,7 @@ export function useProducts( const queryClient = useQueryClient() // Valores por defecto para paginación - const limit = options?.limit || 60 + const limit = options?.limit || 10 const sortDirection = options?.sortDirection || 'DESC' const sortField = options?.sortField || 'creationDate' const enabled = options?.enabled !== false && !!storeId @@ -135,12 +137,15 @@ export function useProducts( const fetchProductsPage = async ({ pageParam = null }: { pageParam: string | null }) => { if (!storeId) throw new Error('Store ID is required') - const { data, nextToken } = await client.models.Product.list({ - filter: { storeId: { eq: storeId } }, - authMode: 'userPool', - limit, - nextToken: pageParam, - }) + const { data, nextToken } = await client.models.Product.listProductByStoreId( + { + storeId: storeId, + }, + { + limit, + nextToken: pageParam, + } + ) // Ordenamos manualmente los resultados const sortedData = [...(data || [])].sort((a, b) => { @@ -187,16 +192,13 @@ export function useProducts( mutationFn: async (productData: ProductCreateInput) => { const { username } = await getCurrentUser() - const { data } = await client.models.Product.create( - { - ...productData, - storeId: storeId || '', - owner: username, - status: productData.status || 'DRAFT', - quantity: productData.quantity || 0, - }, - { authMode: 'userPool' } - ) + const { data } = await client.models.Product.create({ + ...productData, + storeId: storeId || '', + owner: username, + status: productData.status || 'DRAFT', + quantity: productData.quantity || 0, + }) return data as IProduct }, @@ -229,7 +231,7 @@ export function useProducts( // Mutación para actualizar un producto const updateProductMutation = useMutation({ mutationFn: async (productData: ProductUpdateInput) => { - const { data } = await client.models.Product.update(productData, { authMode: 'userPool' }) + const { data } = await client.models.Product.update(productData) return data as IProduct }, onSuccess: updatedProduct => { @@ -259,7 +261,7 @@ export function useProducts( // Mutación para eliminar un producto const deleteProductMutation = useMutation({ mutationFn: async (id: string) => { - await client.models.Product.delete({ id }, { authMode: 'userPool' }) + await client.models.Product.delete({ id }) return id }, onSuccess: deletedId => { @@ -287,9 +289,7 @@ export function useProducts( // Mutación para eliminar múltiples productos const deleteMultipleProductsMutation = useMutation({ mutationFn: async (ids: string[]) => { - await Promise.all( - ids.map(id => client.models.Product.delete({ id }, { authMode: 'userPool' })) - ) + await Promise.all(ids.map(id => client.models.Product.delete({ id }))) return ids }, onSuccess: deletedIds => { @@ -352,7 +352,6 @@ export function useProducts( // Primero obtenemos todos los productos de la tienda actual const { data: storeProducts } = await client.models.Product.list({ filter: { storeId: { eq: storeId } }, - authMode: 'userPool', }) // Buscamos el producto en los productos de la tienda diff --git a/middlewares/ownership/collectionOwnership.ts b/middlewares/ownership/collectionOwnership.ts index 62c92720..16914575 100644 --- a/middlewares/ownership/collectionOwnership.ts +++ b/middlewares/ownership/collectionOwnership.ts @@ -50,28 +50,22 @@ export async function handleCollectionOwnershipMiddleware(request: NextRequest) try { // Verificar que el usuario tenga acceso a la tienda - const storeResult = await cookiesClient.models.UserStore.get( - { - id: currentStoreId, - }, - { - authMode: 'userPool', - } - ) + const storeResult = await cookiesClient.models.UserStore.get({ + id: currentStoreId, + }) // Si la tienda no existe o no pertenece al usuario, verificar si es colaborador if (!storeResult.data || storeResult.data.userId !== userId) { - const userStoreResult = await cookiesClient.models.UserStore.list({ - filter: { - storeId: { - eq: currentStoreId, - }, - userId: { - eq: userId, - }, + const userStoreResult = await cookiesClient.models.UserStore.listUserStoreByUserId( + { + userId: userId, }, - authMode: 'userPool', - }) + { + filter: { + storeId: { eq: currentStoreId }, + }, + } + ) if (!userStoreResult.data || userStoreResult.data.length === 0) { const redirectUrl = new URL('/my-store', request.url) @@ -96,14 +90,9 @@ export async function handleCollectionOwnershipMiddleware(request: NextRequest) } // Para colecciones existentes, verificar que pertenezcan a la tienda actual - const { data: collection } = await cookiesClient.models.Collection.get( - { - id: collectionId, - }, - { - authMode: 'userPool', - } - ) + const { data: collection } = await cookiesClient.models.Collection.get({ + id: collectionId, + }) if (!collection) { const redirectUrl = new URL(`/store/${currentStoreId}/products/collections`, request.url) diff --git a/middlewares/ownership/productOwnership.ts b/middlewares/ownership/productOwnership.ts index afb23385..7e050a0b 100644 --- a/middlewares/ownership/productOwnership.ts +++ b/middlewares/ownership/productOwnership.ts @@ -50,28 +50,22 @@ export async function handleProductOwnershipMiddleware(request: NextRequest) { try { // Verificar que el usuario tenga acceso a la tienda - const storeResult = await cookiesClient.models.UserStore.get( - { - id: currentStoreId, - }, - { - authMode: 'userPool', - } - ) + const storeResult = await cookiesClient.models.UserStore.get({ + id: currentStoreId, + }) // Si la tienda no existe o no pertenece al usuario, verificar si es colaborador if (!storeResult.data || storeResult.data.userId !== userId) { - const userStoreResult = await cookiesClient.models.UserStore.list({ - filter: { - storeId: { - eq: currentStoreId, - }, - userId: { - eq: userId, - }, + const userStoreResult = await cookiesClient.models.UserStore.listUserStoreByUserId( + { + userId: userId, }, - authMode: 'userPool', - }) + { + filter: { + storeId: { eq: currentStoreId }, + }, + } + ) if (!userStoreResult.data || userStoreResult.data.length === 0) { const redirectUrl = new URL('/my-store', request.url) @@ -99,14 +93,9 @@ export async function handleProductOwnershipMiddleware(request: NextRequest) { } // Para productos existentes, verificar que pertenezcan a la tienda actual - const { data: product } = await cookiesClient.models.Product.get( - { - id: productId, - }, - { - authMode: 'userPool', - } - ) + const { data: product } = await cookiesClient.models.Product.get({ + id: productId, + }) if (!product) { const redirectUrl = new URL(`/store/${currentStoreId}/products`, request.url) diff --git a/middlewares/store-access/store.ts b/middlewares/store-access/store.ts index db31524e..eaa21570 100644 --- a/middlewares/store-access/store.ts +++ b/middlewares/store-access/store.ts @@ -16,9 +16,8 @@ async function hasValidPlan(session: any) { async function checkStoreLimit(userId: string, plan: string) { try { - const { data: stores } = await cookiesClient.models.UserStore.list({ - authMode: 'userPool', - filter: { userId: { eq: userId } }, + const { data: stores } = await cookiesClient.models.UserStore.listUserStoreByUserId({ + userId: userId, }) const storeCount = stores?.length || 0 diff --git a/middlewares/store-access/storeAccess.ts b/middlewares/store-access/storeAccess.ts index 9b3c7f53..0663f348 100644 --- a/middlewares/store-access/storeAccess.ts +++ b/middlewares/store-access/storeAccess.ts @@ -33,14 +33,17 @@ export async function handleStoreAccessMiddleware(request: NextRequest) { try { // Verificar si la tienda pertenece al usuario - const { data: stores } = await cookiesClient.models.UserStore.list({ - filter: { - userId: { eq: userId as string }, - storeId: { eq: requestedStoreId }, + const { data: stores } = await cookiesClient.models.UserStore.listUserStoreByUserId( + { + userId: userId as string, }, - selectionSet: ['storeId'], - authMode: 'userPool', - }) + { + filter: { + storeId: { eq: requestedStoreId }, + }, + selectionSet: ['storeId'], + } + ) // Si la tienda no pertenece al usuario, redirigir a my-store if (!stores || stores.length === 0) { diff --git a/utils/AmplifyUtils.ts b/utils/AmplifyUtils.ts index 8c196b94..4a393a7d 100644 --- a/utils/AmplifyUtils.ts +++ b/utils/AmplifyUtils.ts @@ -12,6 +12,7 @@ export const { runWithAmplifyServerContext } = createServerRunner({ export const cookiesClient = generateServerClientUsingCookies({ config: outputs, cookies, + authMode: 'userPool', }) export async function AuthGetCurrentUserServer() {