From b53902a1a7754ae81ca8a0d71a9139f0746f8164 Mon Sep 17 00:00:00 2001 From: Stivenjs Date: Thu, 5 Jun 2025 22:04:38 -0500 Subject: [PATCH] feat(store): add createStoreWithTemplate function to initialize store with default template and data --- amplify/data/resource.ts | 12 + .../config/defaultCollections.json | 30 +++ .../config/defaultSections.json | 156 ++++++++++++ .../config/templateStructure.json | 5 + .../functions/createStoreTemplate/handler.ts | 76 ++++++ .../functions/createStoreTemplate/resource.ts | 7 + .../services/collectionService.ts | 46 ++++ .../services/templateService.ts | 62 +++++ .../services/validationService.ts | 28 +++ .../createStoreTemplate/types/index.ts | 145 +++++++++++ .../utils/templateBuilder.ts | 59 +++++ .../first-steps/hooks/useFirstStepsSetup.ts | 10 +- .../first-steps/hooks/useUserStoreData.ts | 57 +++++ template/layout/theme.liquid | 139 ++++++++++ template/sections/collection-list.liquid | 237 ++++++++++++++++++ template/sections/featured-products.liquid | 202 +++++++++++++++ template/sections/footer.liquid | 216 ++++++++++++++++ template/sections/header.liquid | 131 ++++++++++ template/sections/hero-banner.liquid | 113 +++++++++ 19 files changed, 1726 insertions(+), 5 deletions(-) create mode 100644 amplify/functions/createStoreTemplate/config/defaultCollections.json create mode 100644 amplify/functions/createStoreTemplate/config/defaultSections.json create mode 100644 amplify/functions/createStoreTemplate/config/templateStructure.json create mode 100644 amplify/functions/createStoreTemplate/handler.ts create mode 100644 amplify/functions/createStoreTemplate/resource.ts create mode 100644 amplify/functions/createStoreTemplate/services/collectionService.ts create mode 100644 amplify/functions/createStoreTemplate/services/templateService.ts create mode 100644 amplify/functions/createStoreTemplate/services/validationService.ts create mode 100644 amplify/functions/createStoreTemplate/types/index.ts create mode 100644 amplify/functions/createStoreTemplate/utils/templateBuilder.ts create mode 100644 template/layout/theme.liquid create mode 100644 template/sections/collection-list.liquid create mode 100644 template/sections/featured-products.liquid create mode 100644 template/sections/footer.liquid create mode 100644 template/sections/header.liquid create mode 100644 template/sections/hero-banner.liquid diff --git a/amplify/data/resource.ts b/amplify/data/resource.ts index 6a8a38fd..995acd02 100644 --- a/amplify/data/resource.ts +++ b/amplify/data/resource.ts @@ -9,6 +9,7 @@ import { apiKeyManager } from '../functions/LambdaEncryptKeys/resource' import { getStoreProducts } from '../functions/getStoreProducts/resource' import { getStoreData } from '../functions/getStoreData/resource' import { getStoreCollections } from '../functions/getStoreCollections/resource' +import { createStoreTemplate } from '../functions/createStoreTemplate/resource' export const MODEL_ID = 'us.anthropic.claude-3-haiku-20240307-v1:0' @@ -62,6 +63,16 @@ const schema = a .authorization(allow => [allow.publicApiKey()]) .handler(a.handler.function(generatePriceSuggestionFunction)), + initializeStoreTemplate: a + .mutation() + .arguments({ + storeId: a.string().required(), + domain: a.string().required(), + }) + .returns(a.json()) + .authorization(allow => [allow.authenticated()]) + .handler(a.handler.function(createStoreTemplate)), + UserProfile: a .model({ email: a.string(), @@ -196,6 +207,7 @@ const schema = a allow.resource(getStoreProducts), allow.resource(getStoreData), allow.resource(getStoreCollections), + allow.resource(createStoreTemplate), ]) export type Schema = ClientSchema diff --git a/amplify/functions/createStoreTemplate/config/defaultCollections.json b/amplify/functions/createStoreTemplate/config/defaultCollections.json new file mode 100644 index 00000000..7281de46 --- /dev/null +++ b/amplify/functions/createStoreTemplate/config/defaultCollections.json @@ -0,0 +1,30 @@ +[ + { + "title": "Todos los productos", + "description": "Nuestra colección completa de productos disponibles", + "slug": "todos-los-productos", + "isActive": true, + "sortOrder": 1 + }, + { + "title": "Productos destacados", + "description": "Nuestros productos más populares y recomendados", + "slug": "productos-destacados", + "isActive": true, + "sortOrder": 2 + }, + { + "title": "Ofertas especiales", + "description": "Productos con descuentos y ofertas por tiempo limitado", + "slug": "ofertas-especiales", + "isActive": true, + "sortOrder": 3 + }, + { + "title": "Nuevos productos", + "description": "Los últimos productos agregados a nuestra tienda", + "slug": "nuevos-productos", + "isActive": true, + "sortOrder": 4 + } +] diff --git a/amplify/functions/createStoreTemplate/config/defaultSections.json b/amplify/functions/createStoreTemplate/config/defaultSections.json new file mode 100644 index 00000000..bb5ddf04 --- /dev/null +++ b/amplify/functions/createStoreTemplate/config/defaultSections.json @@ -0,0 +1,156 @@ +{ + "header": { + "type": "header", + "settings": { + "logo": "", + "logo_width": 120, + "logo_height": 40, + "menu_title": "Menú Principal", + "show_search": true, + "show_cart": true, + "show_wishlist": false, + "show_account": true, + "sticky_header": true, + "background_color": "#ffffff", + "text_color": "#1f2937", + "border_bottom": true, + "search_placeholder": "Buscar productos...", + "cart_type": "drawer", + "menu_items": [ + { "title": "Inicio", "url": "/", "active": true }, + { "title": "Productos", "url": "/productos", "active": true }, + { "title": "Colecciones", "url": "/colecciones", "active": true }, + { "title": "Sobre nosotros", "url": "/sobre-nosotros", "active": true }, + { "title": "Contacto", "url": "/contacto", "active": true } + ] + } + }, + + "hero": { + "type": "hero-banner", + "settings": { + "heading": "Bienvenido a tu tienda", + "heading_size": "large", + "subheading": "Descubre nuestros productos increíbles y ofertas especiales", + "subheading_size": "medium", + "button_text": "Ver productos", + "button_link": "/productos", + "button_style": "primary", + "button_size": "large", + "background_image": "", + "background_position": "center center", + "background_size": "cover", + "text_color": "#ffffff", + "text_alignment": "center", + "overlay_opacity": 50, + "overlay_color": "#000000", + "height": "large", + "content_width": "medium", + "enable_parallax": false, + "show_scroll_indicator": true + } + }, + + "featured-products": { + "type": "featured-products", + "settings": { + "heading": "Productos destacados", + "heading_alignment": "center", + "description": "Descubre nuestra selección especial de productos", + "products_to_show": 8, + "columns_desktop": 4, + "columns_tablet": 2, + "columns_mobile": 2, + "show_price": true, + "show_compare_price": true, + "show_description": true, + "show_vendor": false, + "show_rating": true, + "show_quick_view": true, + "show_add_to_cart": true, + "show_wishlist": false, + "image_ratio": "square", + "enable_slider": false, + "autoplay": false, + "autoplay_speed": 5000, + "show_navigation": true, + "show_pagination": true, + "card_style": "default", + "hover_effect": "zoom", + "spacing": "medium" + } + }, + + "collection-list": { + "type": "collection-list", + "settings": { + "heading": "Nuestras colecciones", + "heading_alignment": "center", + "description": "Explora nuestras diferentes categorías de productos", + "collections_to_show": 3, + "layout": "grid", + "columns_desktop": 3, + "columns_tablet": 2, + "columns_mobile": 1, + "show_image": true, + "show_description": true, + "show_product_count": true, + "image_ratio": "square", + "overlay_style": "gradient", + "overlay_opacity": 30, + "text_position": "center", + "button_style": "primary", + "hover_effect": "zoom", + "enable_slider": false, + "autoplay": false, + "spacing": "medium" + } + }, + + "footer": { + "type": "footer", + "settings": { + "background_color": "#1f2937", + "text_color": "#f9fafb", + "heading_color": "#ffffff", + "link_color": "#d1d5db", + "link_hover_color": "#ffffff", + "show_social_links": true, + "show_newsletter": true, + "show_payment_icons": true, + "show_store_info": true, + "newsletter_heading": "Suscríbete a nuestro newsletter", + "newsletter_description": "Recibe las últimas noticias, ofertas y actualizaciones", + "copyright_text": "© 2024 Tu tienda. Todos los derechos reservados.", + + "quick_links": [ + { "title": "Inicio", "url": "/" }, + { "title": "Productos", "url": "/productos" }, + { "title": "Colecciones", "url": "/colecciones" }, + { "title": "Sobre nosotros", "url": "/sobre-nosotros" }, + { "title": "Contacto", "url": "/contacto" } + ], + + "info_links": [ + { "title": "Política de privacidad", "url": "/politicas/privacidad" }, + { "title": "Términos y condiciones", "url": "/politicas/terminos" }, + { "title": "Política de envíos", "url": "/politicas/envios" }, + { "title": "Devoluciones", "url": "/politicas/devoluciones" }, + { "title": "Preguntas frecuentes", "url": "/faq" } + ], + + "social_links": [ + { "platform": "facebook", "url": "", "active": false }, + { "platform": "instagram", "url": "", "active": false }, + { "platform": "twitter", "url": "", "active": false }, + { "platform": "youtube", "url": "", "active": false }, + { "platform": "tiktok", "url": "", "active": false } + ], + + "payment_icons": ["visa", "mastercard", "paypal", "amex"], + + "layout": "four-columns", + "show_divider": true + } + } +} diff --git a/amplify/functions/createStoreTemplate/config/templateStructure.json b/amplify/functions/createStoreTemplate/config/templateStructure.json new file mode 100644 index 00000000..5279641d --- /dev/null +++ b/amplify/functions/createStoreTemplate/config/templateStructure.json @@ -0,0 +1,5 @@ +{ + "layout": "theme", + "sections": {}, + "order": ["header", "hero", "featured-products", "collection-list", "footer"] +} diff --git a/amplify/functions/createStoreTemplate/handler.ts b/amplify/functions/createStoreTemplate/handler.ts new file mode 100644 index 00000000..777b27f0 --- /dev/null +++ b/amplify/functions/createStoreTemplate/handler.ts @@ -0,0 +1,76 @@ +import type { Schema } from '../../data/resource' +import { Amplify } from 'aws-amplify' +import { generateClient } from 'aws-amplify/data' +import { getAmplifyDataClientConfig } from '@aws-amplify/backend/function/runtime' +import { env } from '$amplify/env/create-store-template' +import { InitializationResult } from './types/index' +import { validateInputs } from './services/validationService' +import { checkExistingTemplate, createStoreTemplate } from './services/templateService' +import { createDefaultCollections } from './services/collectionService' +import { buildTemplateData, validateTemplateData } from './utils/templateBuilder' + +const { resourceConfig, libraryOptions } = await getAmplifyDataClientConfig(env) + +Amplify.configure(resourceConfig, libraryOptions) + +const client = generateClient() + +/** + * Handler principal para inicializar templates de tienda + */ +export const handler = async (event: any): Promise => { + try { + // Validar y extraer argumentos usando el servicio de validación + const { storeId, domain, userId } = validateInputs(event) + + // Ejecutar inicialización + return await initializeStoreTemplate(storeId, domain, userId) + } catch (error) { + console.error('Error in createStoreTemplate handler:', error) + return { + success: false, + message: error instanceof Error ? error.message : 'Unknown error occurred', + } + } +} + +/** + * Inicializa el template y datos por defecto para una tienda + */ +async function initializeStoreTemplate( + storeId: string, + domain: string, + owner: string +): Promise { + try { + // Verificar si ya existe un template usando el servicio + const existingCheck = await checkExistingTemplate(client, storeId) + if (!existingCheck.canProceed) { + return existingCheck.result! + } + + // Crear template por defecto usando el builder + const templateData = buildTemplateData() + + // Validar que el template generado sea válido + if (!validateTemplateData(templateData)) { + throw new Error('Generated template data is invalid') + } + + const templateResult = await createStoreTemplate(client, storeId, domain, owner, templateData) + + // Crear colecciones por defecto usando el servicio + const collectionIds = await createDefaultCollections(client, storeId, owner) + + return { + success: true, + message: 'Store template initialized successfully', + templateId: templateResult.storeId, + templateData: templateData, + collections: collectionIds, + } + } catch (error) { + console.error('Error initializing store template:', error) + throw error + } +} diff --git a/amplify/functions/createStoreTemplate/resource.ts b/amplify/functions/createStoreTemplate/resource.ts new file mode 100644 index 00000000..21f1ed3b --- /dev/null +++ b/amplify/functions/createStoreTemplate/resource.ts @@ -0,0 +1,7 @@ +import { defineFunction } from '@aws-amplify/backend' + +export const createStoreTemplate = defineFunction({ + timeoutSeconds: 120, + name: 'create-store-template', + entry: 'handler.ts', +}) diff --git a/amplify/functions/createStoreTemplate/services/collectionService.ts b/amplify/functions/createStoreTemplate/services/collectionService.ts new file mode 100644 index 00000000..c8e5ff66 --- /dev/null +++ b/amplify/functions/createStoreTemplate/services/collectionService.ts @@ -0,0 +1,46 @@ +import type { Schema } from '../../../data/resource' +import { generateClient } from 'aws-amplify/data' +import { DefaultCollection } from '../types/index' +import defaultCollections from '../config/defaultCollections.json' + +export const createDefaultCollections = async ( + client: ReturnType>, + storeId: string, + owner: string +): Promise => { + try { + console.log(`Creating default collections for store: ${storeId}`) + + const createdCollectionIds: string[] = [] + + // Crear todas las colecciones definidas en defaultCollections + for (const collectionConfig of defaultCollections as DefaultCollection[]) { + try { + const collection = await client.models.Collection.create({ + storeId: storeId, + title: collectionConfig.title, + description: collectionConfig.description, + slug: collectionConfig.slug, + isActive: collectionConfig.isActive, + sortOrder: collectionConfig.sortOrder, + owner: owner, + }) + + if (collection.data?.id) { + createdCollectionIds.push(collection.data.id) + console.log(`Collection created: ${collectionConfig.title} (${collection.data.id})`) + } + } catch (collectionError) { + console.error(`Error creating collection ${collectionConfig.title}:`, collectionError) + // Continuamos con las demás colecciones aunque una falle + } + } + + console.log(`Default collections created successfully. Total: ${createdCollectionIds.length}`) + return createdCollectionIds + } catch (error) { + console.error('Error creating default collections:', error) + // Retornamos array vacío en caso de error general + return [] + } +} diff --git a/amplify/functions/createStoreTemplate/services/templateService.ts b/amplify/functions/createStoreTemplate/services/templateService.ts new file mode 100644 index 00000000..5bfde825 --- /dev/null +++ b/amplify/functions/createStoreTemplate/services/templateService.ts @@ -0,0 +1,62 @@ +import type { Schema } from '../../../data/resource' +import { generateClient } from 'aws-amplify/data' +import { ExistingTemplateCheck, CreateTemplateResult, TemplateData } from '../types/index' + +export const checkExistingTemplate = async ( + client: ReturnType>, + storeId: string +): Promise => { + try { + const existingTemplate = await client.models.StoreTemplate.get({ storeId }) + + if (existingTemplate.data) { + console.log('Template already exists for this store') + return { + canProceed: false, + result: { + success: false, + message: 'Template already exists for this store', + templateId: existingTemplate.data.storeId, + }, + } + } + + return { + canProceed: true, + result: { success: true, message: 'Can proceed with template creation' }, + } + } catch (error) { + console.error('Error checking existing template:', error) + throw error + } +} + +export const createStoreTemplate = async ( + client: ReturnType>, + storeId: string, + domain: string, + owner: string, + templateData: TemplateData +): Promise => { + try { + const storeTemplate = await client.models.StoreTemplate.create({ + storeId: storeId, + domain: domain, + templateKey: 'default', + templateData: JSON.stringify(templateData), + isActive: true, + lastUpdated: new Date().toISOString(), + owner: owner, + }) + + if (!storeTemplate.data?.storeId) { + throw new Error('Failed to create store template') + } + + console.log('StoreTemplate created successfully:', storeTemplate.data.storeId) + return { storeId: storeTemplate.data.storeId } + } catch (error) { + console.error('Error creating store template:', error) + throw error + } +} diff --git a/amplify/functions/createStoreTemplate/services/validationService.ts b/amplify/functions/createStoreTemplate/services/validationService.ts new file mode 100644 index 00000000..4a667823 --- /dev/null +++ b/amplify/functions/createStoreTemplate/services/validationService.ts @@ -0,0 +1,28 @@ +import { ValidationResult } from '../types/index' + +export const validateInputs = (event: any): ValidationResult => { + if (!event.arguments) { + throw new Error('Invalid event format: missing arguments') + } + + const { storeId, domain } = event.arguments + const userId = event.identity?.sub || event.identity?.claims?.sub + + if (!userId) { + throw new Error('User not authenticated') + } + + if (!storeId || typeof storeId !== 'string') { + throw new Error('Invalid or missing storeId') + } + + if (!domain || typeof domain !== 'string') { + throw new Error('Invalid or missing domain') + } + + return { + storeId, + domain, + userId, + } +} diff --git a/amplify/functions/createStoreTemplate/types/index.ts b/amplify/functions/createStoreTemplate/types/index.ts new file mode 100644 index 00000000..fa6a27ae --- /dev/null +++ b/amplify/functions/createStoreTemplate/types/index.ts @@ -0,0 +1,145 @@ +export interface SectionSettings { + [key: string]: any +} + +export interface TemplateSection { + type: string + settings: SectionSettings + blocks?: any[] +} + +export interface TemplateData { + layout: string + sections: Record + order: string[] +} + +export interface DefaultCollection { + title: string + description: string + slug: string + isActive: boolean + sortOrder: number +} + +export interface InitializationResult { + success: boolean + message: string + templateId?: string + templateData?: TemplateData + collections?: string[] +} + +export interface ValidationResult { + storeId: string + domain: string + userId: string +} + +export interface ExistingTemplateCheck { + canProceed: boolean + result: InitializationResult +} + +export interface CreateTemplateResult { + storeId: string +} + +// ==================== TIPOS DE CONFIGURACIÓN ==================== + +export type LayoutType = 'theme' | 'minimal' | 'modern' + +export type SectionType = + | 'header' + | 'hero-banner' + | 'featured-products' + | 'collection-list' + | 'footer' + | 'testimonials' + | 'newsletter' + | 'blog' + | 'video' + | 'gallery' + +export type HeaderCartType = 'drawer' | 'page' +export type ButtonStyle = 'primary' | 'secondary' | 'outline' +export type ButtonSize = 'small' | 'medium' | 'large' +export type TextAlignment = 'left' | 'center' | 'right' +export type ImageRatio = 'natural' | 'square' | 'portrait' | 'landscape' +export type HoverEffect = 'none' | 'zoom' | 'fade' | 'slide' +export type Spacing = 'small' | 'medium' | 'large' +export type OverlayStyle = 'none' | 'solid' | 'gradient' +export type FooterLayout = 'three-columns' | 'four-columns' | 'simple' + +// ==================== INTERFACES ESPECÍFICAS DE SECCIONES ==================== + +export interface HeaderSettings extends SectionSettings { + logo: string + logo_width: number + logo_height: number + menu_title: string + show_search: boolean + show_cart: boolean + show_wishlist: boolean + show_account: boolean + sticky_header: boolean + background_color: string + text_color: string + border_bottom: boolean + search_placeholder: string + cart_type: HeaderCartType + menu_items: Array<{ + title: string + url: string + active: boolean + }> +} + +export interface HeroSettings extends SectionSettings { + heading: string + heading_size: ButtonSize | 'extra-large' + subheading: string + subheading_size: ButtonSize + button_text: string + button_link: string + button_style: ButtonStyle + button_size: ButtonSize + background_image: string + background_position: string + background_size: string + text_color: string + text_alignment: TextAlignment + overlay_opacity: number + overlay_color: string + height: ButtonSize | 'full-screen' + content_width: ButtonSize | 'full' + enable_parallax: boolean + show_scroll_indicator: boolean +} + +export interface ProductsSettings extends SectionSettings { + heading: string + heading_alignment: TextAlignment + description: string + products_to_show: number + columns_desktop: number + columns_tablet: number + columns_mobile: number + show_price: boolean + show_compare_price: boolean + show_description: boolean + show_vendor: boolean + show_rating: boolean + show_quick_view: boolean + show_add_to_cart: boolean + show_wishlist: boolean + image_ratio: ImageRatio + enable_slider: boolean + autoplay: boolean + autoplay_speed: number + show_navigation: boolean + show_pagination: boolean + card_style: 'default' | 'minimal' | 'detailed' + hover_effect: HoverEffect + spacing: Spacing +} diff --git a/amplify/functions/createStoreTemplate/utils/templateBuilder.ts b/amplify/functions/createStoreTemplate/utils/templateBuilder.ts new file mode 100644 index 00000000..f81aa6e9 --- /dev/null +++ b/amplify/functions/createStoreTemplate/utils/templateBuilder.ts @@ -0,0 +1,59 @@ +import { TemplateData } from '../types/index' +import defaultSections from '../config/defaultSections.json' +import templateStructure from '../config/templateStructure.json' + +export const buildTemplateData = (): TemplateData => { + const templateData: TemplateData = { + layout: templateStructure.layout, + sections: {}, + order: templateStructure.order, + } + + // Construir cada sección usando los datos del JSON + for (const sectionKey of templateStructure.order) { + if (defaultSections[sectionKey as keyof typeof defaultSections]) { + templateData.sections[sectionKey] = + defaultSections[sectionKey as keyof typeof defaultSections] + } + } + + console.log( + 'Template data construida con', + Object.keys(templateData.sections).length, + 'secciones' + ) + return templateData +} + +export const validateTemplateData = (templateData: TemplateData): boolean => { + // Validaciones básicas + if (!templateData.layout || typeof templateData.layout !== 'string') { + return false + } + + if (!templateData.sections || typeof templateData.sections !== 'object') { + return false + } + + if (!Array.isArray(templateData.order)) { + return false + } + + // Validar que todas las secciones en order existan en sections + for (const sectionKey of templateData.order) { + if (!templateData.sections[sectionKey]) { + console.warn(`Sección '${sectionKey}' está en order pero no en sections`) + return false + } + } + + // Validar estructura de cada sección + for (const [key, section] of Object.entries(templateData.sections)) { + if (!section.type || !section.settings) { + console.warn(`Sección '${key}' no tiene la estructura correcta`) + return false + } + } + + return true +} diff --git a/app/(setup-layout)/first-steps/hooks/useFirstStepsSetup.ts b/app/(setup-layout)/first-steps/hooks/useFirstStepsSetup.ts index 26ff2345..df46b914 100644 --- a/app/(setup-layout)/first-steps/hooks/useFirstStepsSetup.ts +++ b/app/(setup-layout)/first-steps/hooks/useFirstStepsSetup.ts @@ -38,7 +38,7 @@ export const useFirstStepsSetup = () => { const [validationErrors, setValidationErrors] = useState>({}) const [saving, setSaving] = useState(false) const { userData } = useAuthUser() - const { loading, createUserStore } = useUserStoreData() + const { loading, createUserStore, createStoreWithTemplate } = useUserStoreData() const { encryptApiKey } = useApiKeyEncryption() const cognitoUsername = @@ -129,10 +129,10 @@ export const useFirstStepsSetup = () => { onboardingCompleted: true, } - const result = await createUserStore(storeInput) + const result = await createStoreWithTemplate(storeInput) if (result) { setTimeout(() => { - window.location.href = routes.store.dashboard.main(result.storeId) + window.location.href = routes.store.dashboard.main(result.store.storeId) }, 3000) } else { setSaving(false) @@ -162,11 +162,11 @@ export const useFirstStepsSetup = () => { onboardingCompleted: true, } - const result = await createUserStore(quickStoreInput) + const result = await createStoreWithTemplate(quickStoreInput) if (result) { setTimeout(() => { - window.location.href = routes.store.dashboard.main(result.storeId) + window.location.href = routes.store.dashboard.main(result.store.storeId) }, 3000) } else { setSaving(false) diff --git a/app/(setup-layout)/first-steps/hooks/useUserStoreData.ts b/app/(setup-layout)/first-steps/hooks/useUserStoreData.ts index c7585a64..43a06066 100644 --- a/app/(setup-layout)/first-steps/hooks/useUserStoreData.ts +++ b/app/(setup-layout)/first-steps/hooks/useUserStoreData.ts @@ -203,6 +203,61 @@ export const useUserStoreData = () => { return performOperation(() => client.models.UserStore.delete({ storeId })) } + /** + * Inicializa el template y datos por defecto para una nueva tienda. + * Crea las secciones por defecto, colecciones base y configuración inicial. + */ + const initializeStoreTemplate = async (storeId: string, domain: string) => { + if (!storeId || !domain) { + setError('Store ID and domain are required') + return null + } + + return performOperation(() => + client.mutations.initializeStoreTemplate({ + storeId, + domain, + }) + ) + } + + /** + * Función completa para crear una tienda con todos sus datos iniciales. + * Crea la tienda y luego inicializa su template automáticamente. + */ + const createStoreWithTemplate = async ( + storeInput: Omit + ) => { + try { + setLoading(true) + setError(null) + + // Primero crear la tienda + const storeResult = await performOperation(() => client.models.UserStore.create(storeInput)) + + if (!storeResult) { + throw new Error('Failed to create store') + } + + // Luego inicializar el template con los datos por defecto + const templateResult = await initializeStoreTemplate( + storeInput.storeId, + storeInput.customDomain || storeInput.storeName + ) + + return { + store: storeResult, + template: templateResult, + success: true, + } + } catch (err) { + setError(err) + return null + } finally { + setLoading(false) + } + } + return { loading, error, @@ -211,5 +266,7 @@ export const useUserStoreData = () => { deleteUserStore, getStorePaymentInfo, configurePaymentGateway, + initializeStoreTemplate, + createStoreWithTemplate, } } diff --git a/template/layout/theme.liquid b/template/layout/theme.liquid new file mode 100644 index 00000000..0e6be363 --- /dev/null +++ b/template/layout/theme.liquid @@ -0,0 +1,139 @@ + + + + + + + {% if page_title %}{{ page_title }} - {% endif %} + {{ store.name }} + + + + + + + + + + {{ content_for_header }} + + + +
+ {{ content_for_layout }} +
+ + + + + diff --git a/template/sections/collection-list.liquid b/template/sections/collection-list.liquid new file mode 100644 index 00000000..fe758498 --- /dev/null +++ b/template/sections/collection-list.liquid @@ -0,0 +1,237 @@ +
+
+ {% if section.settings.heading %} +
+

{{ section.settings.heading }}

+
+
+ {% endif %} + + {% if section.settings.layout == 'carousel' %} + +
+ + + + + +
+ {% elsif section.settings.layout == 'list' %} + +
+ {% for collection in collections limit: section.settings.collections_to_show %} +
+ {% if section.settings.show_image and collection.image %} +
+ {{ collection.title }} +
+ {% endif %} + +
+

{{ collection.title }}

+ + {% if section.settings.show_description and collection.description %} +

{{ collection.description }}

+ {% endif %} + +
+ {{ collection.products.size }} productos + + Ver colección + +
+
+
+ {% endfor %} +
+ {% else %} + +
+ {% for collection in collections limit: section.settings.collections_to_show %} +
+ {% if section.settings.show_image and collection.image %} +
+ {{ collection.title }} +
+ + + +
+ {% else %} +
+

+ {{ collection.title }} +

+
+ {% endif %} + +
+

{{ collection.title }}

+ + {% if section.settings.show_description and collection.description %} +

{{ collection.description | truncate: 100 }}

+ {% endif %} + +
+ {{ collection.products.size }} productos + + Ver más → + +
+
+
+ {% endfor %} +
+ {% endif %} + + + +
+
+ + + + + +{% schema %} +{ + "name": "Lista de Colecciones", + "settings": [ + { + "type": "text", + "id": "heading", + "label": "Título de la sección", + "default": "Nuestras colecciones" + }, + { + "type": "range", + "id": "collections_to_show", + "label": "Colecciones a mostrar", + "min": 2, + "max": 20, + "step": 1, + "default": 3 + }, + { + "type": "select", + "id": "layout", + "label": "Diseño", + "options": [ + { "value": "grid", "label": "Cuadrícula" }, + { "value": "list", "label": "Lista" }, + { "value": "carousel", "label": "Carrusel" } + ], + "default": "grid" + }, + { + "type": "checkbox", + "id": "show_image", + "label": "Mostrar imagen", + "default": true + }, + { + "type": "checkbox", + "id": "show_description", + "label": "Mostrar descripción", + "default": true + } + ] +} +{% endschema %} diff --git a/template/sections/featured-products.liquid b/template/sections/featured-products.liquid new file mode 100644 index 00000000..427deea8 --- /dev/null +++ b/template/sections/featured-products.liquid @@ -0,0 +1,202 @@ +
+
+ {% if section.settings.heading %} +
+

{{ section.settings.heading }}

+
+
+ {% endif %} + +
+ {% for product in products limit: section.settings.products_to_show %} +
+ +
+ {% if product.images and product.images.size > 0 %} + {{ product.name }} + {% else %} +
+ + + +
+ {% endif %} + + + {% if product.featured %} +
+ Destacado +
+ {% endif %} +
+ + +
+

+ {{ product.name }} +

+ + {% if section.settings.show_description and product.description %} +

+ {{ product.description | truncate: 100 }} +

+ {% endif %} + + {% if section.settings.show_price %} +
+
+ {% if product.compareAtPrice and product.compareAtPrice > product.price %} + ${{ product.price }} + ${{ product.compareAtPrice -}} + + {% else %} + ${{ product.price }} + {% endif %} +
+ + {% if product.quantity and product.quantity <= 5 %} + + ¡Solo {{ product.quantity }} disponibles! + + {% endif %} +
+ {% endif %} + + +
+ + + Ver detalles + +
+
+
+ {% endfor %} +
+ + + +
+
+ + + +{% schema %} +{ + "name": "Productos Destacados", + "settings": [ + { + "type": "text", + "id": "heading", + "label": "Título de la sección", + "default": "Productos destacados" + }, + { + "type": "range", + "id": "products_to_show", + "label": "Productos a mostrar", + "min": 2, + "max": 50, + "step": 1, + "default": 8 + }, + { + "type": "range", + "id": "columns_desktop", + "label": "Columnas en escritorio", + "min": 1, + "max": 6, + "step": 1, + "default": 4 + }, + { + "type": "range", + "id": "columns_mobile", + "label": "Columnas en móvil", + "min": 1, + "max": 3, + "step": 1, + "default": 2 + }, + { + "type": "checkbox", + "id": "show_price", + "label": "Mostrar precio", + "default": true + }, + { + "type": "checkbox", + "id": "show_description", + "label": "Mostrar descripción", + "default": true + } + ] +} +{% endschema %} diff --git a/template/sections/footer.liquid b/template/sections/footer.liquid new file mode 100644 index 00000000..573abbb8 --- /dev/null +++ b/template/sections/footer.liquid @@ -0,0 +1,216 @@ + + + + +{% schema %} +{ + "name": "Pie de página", + "settings": [ + { + "type": "checkbox", + "id": "show_social_links", + "label": "Mostrar enlaces sociales", + "default": true + }, + { + "type": "checkbox", + "id": "show_newsletter", + "label": "Mostrar newsletter", + "default": true + }, + { + "type": "text", + "id": "newsletter_heading", + "label": "Título del newsletter", + "default": "Suscríbete a nuestro newsletter" + }, + { + "type": "text", + "id": "copyright_text", + "label": "Texto de copyright", + "default": "© 2024 Tu tienda. Todos los derechos reservados." + }, + { + "type": "checkbox", + "id": "payment_icons", + "label": "Mostrar iconos de pago", + "default": true + } + ] +} +{% endschema %} \ No newline at end of file diff --git a/template/sections/header.liquid b/template/sections/header.liquid new file mode 100644 index 00000000..1d5f0f2c --- /dev/null +++ b/template/sections/header.liquid @@ -0,0 +1,131 @@ +
+
+
+ +
+ {% if section.settings.logo %} + {{ store.name }} + {% else %} +

{{ store.name }}

+ {% endif %} +
+ + + + + +
+ + {% if section.settings.show_search %} +
+ + +
+ {% endif %} + + + {% if section.settings.show_cart %} + + {% endif %} + + + +
+
+ + + +
+
+ + + +{% schema %} +{ + "name": "Cabecera", + "settings": [ + { + "type": "image_picker", + "id": "logo", + "label": "Logo de la tienda" + }, + { + "type": "text", + "id": "menu_title", + "label": "Título del menú", + "default": "Menú Principal" + }, + { + "type": "checkbox", + "id": "show_search", + "label": "Mostrar búsqueda", + "default": true + }, + { + "type": "checkbox", + "id": "show_cart", + "label": "Mostrar carrito", + "default": true + } + ] +} +{% endschema %} diff --git a/template/sections/hero-banner.liquid b/template/sections/hero-banner.liquid new file mode 100644 index 00000000..e2d598bf --- /dev/null +++ b/template/sections/hero-banner.liquid @@ -0,0 +1,113 @@ +
+ + {% if section.settings.background_image %} +
+ Banner principal +
+
+ {% endif %} + + +
+
+

+ {{ section.settings.heading }} +

+ + {% if section.settings.subheading %} +

+ {{ section.settings.subheading }} +

+ {% endif %} + + {% if section.settings.button_text and section.settings.button_link %} + + {{ section.settings.button_text }} + + {% endif %} +
+
+ + +
+
+ + + +
+
+
+ +{% schema %} +{ + "name": "Banner Principal", + "settings": [ + { + "type": "text", + "id": "heading", + "label": "Encabezado", + "default": "Bienvenido a tu tienda" + }, + { + "type": "textarea", + "id": "subheading", + "label": "Subtítulo", + "default": "Descubre nuestros productos increíbles" + }, + { + "type": "text", + "id": "button_text", + "label": "Texto del botón", + "default": "Ver productos" + }, + { + "type": "url", + "id": "button_link", + "label": "Enlace del botón", + "default": "/products" + }, + { + "type": "image_picker", + "id": "background_image", + "label": "Imagen de fondo" + }, + { + "type": "color", + "id": "text_color", + "label": "Color del texto", + "default": "#ffffff" + }, + { + "type": "range", + "id": "overlay_opacity", + "label": "Opacidad del overlay", + "min": 0, + "max": 100, + "step": 10, + "default": 50, + "unit": "%" + } + ] +} +{% endschema %}