From eb7c3ec148e7fc6fd051732a130f5cc1f7e87d12 Mon Sep 17 00:00:00 2001 From: feispro <262678496+feiscs@users.noreply.github.com> Date: Fri, 15 May 2026 01:47:13 -0400 Subject: [PATCH 1/2] Point hero CTA to live Shopify store --- assets/store.js | 14 ++++++++++++-- index.html | 41 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/assets/store.js b/assets/store.js index bba98de..84dcd5e 100644 --- a/assets/store.js +++ b/assets/store.js @@ -467,10 +467,20 @@ document.querySelector('[data-view-toggle]').addEventListener('click', (event) = document.querySelector('[data-newsletter-form]').addEventListener('submit', async (event) => { event.preventDefault(); - const email = event.currentTarget.querySelector('input[type="email"]').value; + const input = event.currentTarget.querySelector('input[type="email"]'); + const email = input.value.trim(); + if (!email) return; + await window.FormaIntegrations?.saveNewsletter?.(email); window.FormaIntegrations?.trackEvent('newsletter_signup', { email }); - document.querySelector('[data-newsletter-message]').textContent = 'Listo. Te avisaremos del próximo drop privado.'; + + const target = 'feispla@hotmail.com'; + const subject = encodeURIComponent('Nuevo registro de newsletter FORMA'); + const body = encodeURIComponent(`Correo del cliente: ${email}`); + window.location.href = `mailto:${target}?subject=${subject}&body=${body}`; + + document.querySelector('[data-newsletter-message]').textContent = 'Listo. Abrimos tu app de correo para enviar el registro.'; + event.currentTarget.reset(); }); modal.addEventListener('click', (event) => { diff --git a/index.html b/index.html index 5543082..e9c8736 100644 --- a/index.html +++ b/index.html @@ -52,13 +52,14 @@

Nueva campaña · Primavera urbana

-

Esenciales con forma, intención y carácter.

+

Esenciales premium para comprar hoy. Soluciones e-commerce para crecer mañana.

- Compra una selección curada de moda, accesorios y objetos lifestyle con estética editorial, materiales premium y experiencia de compra sin fricción. + Compra una selección curada de moda, accesorios y lifestyle con experiencia premium. + Además, si tienes una marca, en FORMA diseñamos y desarrollamos tu tienda Shopify con enfoque en conversión.

24h
despacho
@@ -75,6 +76,39 @@

Esenciales con forma, intención y carácter.

+
+
+

Servicios FORMA

+

También construimos tiendas que venden.

+

+ Diseño + desarrollo + integración Shopify + automatizaciones en un flujo profesional, + manteniendo estética premium y foco total en conversión. +

+
+ +
+
+ Setup Shopify + Listo para operar +

Catálogo optimizado, navegación clara y estructura comercial sólida.

+
+
+ UX & Performance + Mobile-first +

Checkout más fluido, mejor experiencia de compra y carga rápida.

+
+
+ Integración & Escala + Operación conectada +

Eventos, CRM, automatizaciones y despliegues continuos.

+
+
+ +

+ Stack validado para operación real: Shopify · Supabase · Vercel · GitHub AI +

+
+
🚚Envío gratis

En pedidos desde $100.

Devoluciones fáciles

30 días para cambios.

@@ -189,7 +223,6 @@

Lista para Shopify, Supabase, Chatbase, GitHub AI y Vercel.

FFORMA

Un e-commerce editorial construido para vender productos premium con claridad, confianza y estilo.

-

Contacto comercial: FORMA Ventas · feispla@hotmail.com

From 5024513f33589e739591d99d318b70fe90af89d0 Mon Sep 17 00:00:00 2001 From: feispro <262678496+feiscs@users.noreply.github.com> Date: Fri, 15 May 2026 02:24:50 -0400 Subject: [PATCH 2/2] Add Stripe Checkout API endpoints and cart checkout redirect --- api/checkout.js | 43 +++++++++++++++++++++++++++++++++++++++++++ api/stripe-webhook.js | 43 +++++++++++++++++++++++++++++++++++++++++++ assets/store.js | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+) create mode 100644 api/checkout.js create mode 100644 api/stripe-webhook.js diff --git a/api/checkout.js b/api/checkout.js new file mode 100644 index 0000000..1721e1f --- /dev/null +++ b/api/checkout.js @@ -0,0 +1,43 @@ +const Stripe = require('stripe'); + +module.exports = async function handler(req, res) { + if (req.method !== 'POST') { + res.setHeader('Allow', 'POST'); + return res.status(405).json({ error: 'Method not allowed' }); + } + + try { + const secretKey = process.env.STRIPE_SECRET_KEY; + const appUrl = process.env.APP_URL; + if (!secretKey || !appUrl) { + return res.status(500).json({ error: 'Missing Stripe configuration on server.' }); + } + + const stripe = new Stripe(secretKey); + const items = Array.isArray(req.body?.items) ? req.body.items : []; + + if (!items.length) { + return res.status(400).json({ error: 'Cart is empty.' }); + } + + const line_items = items.map((item) => ({ + price_data: { + currency: 'usd', + product_data: { name: String(item.name || 'Producto FORMA') }, + unit_amount: Math.max(1, Math.round(Number(item.price || 0) * 100)), + }, + quantity: Math.max(1, Number(item.quantity || 1)), + })); + + const session = await stripe.checkout.sessions.create({ + mode: 'payment', + line_items, + success_url: `${appUrl}/?paid=1`, + cancel_url: `${appUrl}/?paid=0`, + }); + + return res.status(200).json({ url: session.url }); + } catch (error) { + return res.status(500).json({ error: error.message || 'Checkout failed.' }); + } +}; diff --git a/api/stripe-webhook.js b/api/stripe-webhook.js new file mode 100644 index 0000000..1ce273e --- /dev/null +++ b/api/stripe-webhook.js @@ -0,0 +1,43 @@ +const Stripe = require('stripe'); + +module.exports.config = { + api: { + bodyParser: false, + }, +}; + +async function readRawBody(req) { + const chunks = []; + for await (const chunk of req) chunks.push(chunk); + return Buffer.concat(chunks); +} + +module.exports = async function handler(req, res) { + if (req.method !== 'POST') { + res.setHeader('Allow', 'POST'); + return res.status(405).end(); + } + + try { + const secretKey = process.env.STRIPE_SECRET_KEY; + const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET; + if (!secretKey || !webhookSecret) { + return res.status(500).send('Missing Stripe webhook configuration.'); + } + + const stripe = new Stripe(secretKey); + const signature = req.headers['stripe-signature']; + const rawBody = await readRawBody(req); + + const event = stripe.webhooks.constructEvent(rawBody, signature, webhookSecret); + + if (event.type === 'checkout.session.completed') { + const session = event.data.object; + console.log('[FORMA] checkout.session.completed', session.id); + } + + return res.status(200).json({ received: true }); + } catch (error) { + return res.status(400).send(`Webhook Error: ${error.message}`); + } +}; diff --git a/assets/store.js b/assets/store.js index 84dcd5e..4170987 100644 --- a/assets/store.js +++ b/assets/store.js @@ -449,6 +449,14 @@ document.addEventListener('click', (event) => { if (event.target.closest('[data-open-cart]')) openDrawer(cartDrawer); if (event.target.closest('[data-close-cart]')) closeDrawer(cartDrawer); + + if (event.target.closest('.cart-footer .btn.btn-dark')) { + event.preventDefault(); + goToStripeCheckout().catch((error) => { + console.error('[FORMA] Stripe checkout error:', error); + document.querySelector('[data-newsletter-message]').textContent = 'No pudimos iniciar el pago. Intenta de nuevo.'; + }); + } if (event.target.closest('[data-open-wishlist]')) openDrawer(wishlistDrawer); if (event.target.closest('[data-close-wishlist]')) closeDrawer(wishlistDrawer); if (event.target.closest('[data-close-modal]')) closeModal(); @@ -465,6 +473,30 @@ document.querySelector('[data-view-toggle]').addEventListener('click', (event) = renderProducts(); }); + +async function goToStripeCheckout() { + const items = store.state.cart.map((item) => ({ + name: item.name, + price: item.price, + quantity: item.quantity, + })); + + if (!items.length) { + document.querySelector('[data-newsletter-message]').textContent = 'Tu carrito está vacío. Agrega productos antes de pagar.'; + return; + } + + const response = await fetch('/api/checkout', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ items }), + }); + + const data = await response.json(); + if (!response.ok || !data.url) throw new Error(data.error || 'No se pudo iniciar el checkout.'); + window.location.href = data.url; +} + document.querySelector('[data-newsletter-form]').addEventListener('submit', async (event) => { event.preventDefault(); const input = event.currentTarget.querySelector('input[type="email"]');