feat: migrate to single PRO plan and update Stripe Production Checkout
This commit is contained in:
parent
2e28f9d27c
commit
cce4743b71
5 changed files with 96 additions and 108 deletions
35
image-renderer/Dockerfile
Normal file
35
image-renderer/Dockerfile
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# Usa a imagem oficial do Node.js baseada em Alpine (Leve mas precisa de libs do browser)
|
||||
# Trocando pra Debian/Ubuntu leve por causa das dependencias complexas do Chrome
|
||||
FROM node:18-slim
|
||||
|
||||
# Instalar dependências necessárias para rodar o Puppeteer (Chromium) no Linux
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y wget gnupg \
|
||||
&& wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
|
||||
&& sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1 \
|
||||
--no-install-recommends \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Definir a variável de ambiente para forçar o Puppeteer a usar o Chrome instalado ao invés de baixar um próprio
|
||||
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
|
||||
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/google-chrome-stable
|
||||
|
||||
# Criar o diretório de trabalho da aplicação
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Copiar os arquivos de dependência
|
||||
COPY package*.json ./
|
||||
|
||||
# Instalar as dependências do Node
|
||||
RUN npm install
|
||||
|
||||
# Copiar o resto do código do servidor
|
||||
COPY . .
|
||||
|
||||
# Expor a porta que a aplicação Express escuta
|
||||
EXPOSE 3001
|
||||
|
||||
# Comando para iniciar o servidor
|
||||
CMD [ "npm", "start" ]
|
||||
14
image-renderer/coolify.yaml
Normal file
14
image-renderer/coolify.yaml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Configuração do Coolify Nixpacks
|
||||
# O Coolify vai ler isso na raiz para o serviço específico "image-renderer"
|
||||
|
||||
preBuild:
|
||||
- npm ci
|
||||
|
||||
start:
|
||||
- node index.js
|
||||
|
||||
# Variáveis do Node / Chrome
|
||||
envs:
|
||||
- PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
|
||||
- PUPPETEER_EXECUTABLE_PATH=/usr/bin/google-chrome-stable
|
||||
- NODE_ENV=production
|
||||
|
|
@ -41,64 +41,34 @@ const Pricing: React.FC<PricingProps> = ({ onRegister }) => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid lg:grid-cols-3 gap-8 lg:gap-14 max-w-7xl mx-auto items-center relative z-10">
|
||||
|
||||
{/* Plan: Monthly (Was Starter in structure, now Monthly) */}
|
||||
<div className="bg-white rounded-3xl border border-gray-200 p-8 flex flex-col hover:border-gray-300 transition-colors lg:mt-12 hover:shadow-lg">
|
||||
<h3 className="text-lg font-bold text-gray-900">{t.pricing.plans.monthly.title}</h3>
|
||||
<div className="mt-4 mb-2 flex items-baseline gap-1">
|
||||
<span className="text-4xl font-extrabold text-gray-900">{t.pricing.plans.monthly.price}</span>
|
||||
<span className="text-gray-500 text-sm">{t.pricing.plans.monthly.period}</span>
|
||||
</div>
|
||||
<p className="text-gray-500 mb-6 text-sm">{t.pricing.plans.monthly.description}</p>
|
||||
<p className="text-xs font-semibold text-gray-400 mb-6 uppercase tracking-wide">{t.pricing.plans.monthly.billingInfo}</p>
|
||||
|
||||
<ul className="space-y-4 mb-8 flex-grow">
|
||||
{t.pricing.plans.monthly.features.map((item, i) => (
|
||||
<li key={i} className="flex items-start gap-3 text-gray-600 text-sm">
|
||||
<Check className="w-5 h-5 text-gray-400 shrink-0" strokeWidth={2} />
|
||||
<span>{item}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<button
|
||||
onClick={() => onRegister('monthly')}
|
||||
className="block w-full text-center bg-white border border-gray-200 hover:bg-gray-50 hover:border-gray-300 text-gray-900 font-semibold py-3.5 rounded-xl transition-all text-sm shadow-sm"
|
||||
>
|
||||
{t.pricing.plans.monthly.btnText}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Plan: Annual (Highlighted) */}
|
||||
<div className="relative z-20 transform lg:scale-105">
|
||||
<div className="bg-brand-950 rounded-3xl border border-brand-800 p-8 flex flex-col relative shadow-2xl shadow-brand-900/20 ring-1 ring-brand-700/50 h-full">
|
||||
<div className="max-w-md mx-auto relative z-10">
|
||||
<div className="bg-brand-950 rounded-3xl border border-brand-800 p-8 flex flex-col relative shadow-2xl shadow-brand-900/20 ring-1 ring-brand-700/50">
|
||||
<div className="flex justify-between items-start mt-2">
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-white flex items-center gap-2">
|
||||
{t.pricing.plans.annual.title}
|
||||
<span className="bg-white/10 text-white text-[10px] px-2 py-0.5 rounded-full border border-white/10">{t.pricing.plans.annual.savings}</span>
|
||||
<h3 className="flex items-center gap-2 text-xl font-bold text-white">
|
||||
{t.pricing.plans.monthly.title}
|
||||
<span className="bg-brand-500 rounded-full px-2 py-0.5 pb-[3px] text-[10px] font-bold uppercase tracking-wider text-white">PRO</span>
|
||||
</h3>
|
||||
<p className="text-brand-200 text-sm mt-1 opacity-90">{t.pricing.plans.annual.description}</p>
|
||||
<p className="text-brand-200 mt-1 text-sm opacity-90">{t.pricing.plans.monthly.description}</p>
|
||||
</div>
|
||||
<div className="p-2 bg-white/5 rounded-lg border border-white/10">
|
||||
<Sparkles className="text-accent-400" size={20} />
|
||||
<div className="bg-white/5 border-white/10 rounded-lg border p-2">
|
||||
<Sparkles className="text-accent-400" size={24} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 mb-2 flex items-baseline gap-1">
|
||||
<span className="text-5xl font-extrabold text-white tracking-tight">{t.pricing.plans.annual.price}</span>
|
||||
<span className="text-brand-200 text-sm font-medium">{t.pricing.plans.annual.period}</span>
|
||||
<div className="mb-2 mt-6 flex items-baseline gap-1">
|
||||
<span className="text-5xl font-extrabold tracking-tight text-white">{t.pricing.plans.monthly.price}</span>
|
||||
<span className="text-brand-200 text-sm font-medium">{t.pricing.plans.monthly.period}</span>
|
||||
</div>
|
||||
<p className="text-xs font-medium text-brand-300 mb-8 uppercase tracking-wide">{t.pricing.plans.annual.billingInfo}</p>
|
||||
<p className="text-brand-300 mb-8 text-xs font-medium uppercase tracking-wide">{t.pricing.plans.monthly.billingInfo}</p>
|
||||
|
||||
<div className="h-px bg-gradient-to-r from-transparent via-brand-800 to-transparent mb-8"></div>
|
||||
<div className="from-transparent via-brand-800 to-transparent mb-8 h-px bg-gradient-to-r"></div>
|
||||
|
||||
<ul className="space-y-4 mb-8 flex-grow">
|
||||
{t.pricing.plans.annual.features.map((item, i) => (
|
||||
<li key={i} className="flex items-start gap-3 text-gray-100 text-sm">
|
||||
<div className="bg-brand-600 rounded-full p-0.5 text-white shadow-sm mt-0.5">
|
||||
<Check className="w-3 h-3" strokeWidth={3} />
|
||||
<ul className="mb-8 flex-grow space-y-4">
|
||||
{t.pricing.plans.monthly.features.map((item, i) => (
|
||||
<li key={i} className="flex items-start gap-3 text-sm text-gray-100">
|
||||
<div className="bg-brand-600 mt-0.5 rounded-full p-0.5 text-white shadow-sm">
|
||||
<Check className="h-3 w-3" strokeWidth={3} />
|
||||
</div>
|
||||
<span className="leading-snug">{item}</span>
|
||||
</li>
|
||||
|
|
@ -106,45 +76,14 @@ const Pricing: React.FC<PricingProps> = ({ onRegister }) => {
|
|||
</ul>
|
||||
|
||||
<button
|
||||
onClick={() => onRegister('annual')}
|
||||
className="block w-full text-center bg-gradient-to-r from-brand-500 to-brand-600 hover:from-brand-400 hover:to-brand-500 text-white font-bold py-4 rounded-xl transition-all shadow-lg shadow-brand-500/20 text-sm hover:-translate-y-0.5 relative overflow-hidden group"
|
||||
onClick={() => onRegister('monthly')}
|
||||
className="group from-brand-500 to-brand-600 hover:from-brand-400 hover:to-brand-500 shadow-brand-500/20 hover:-translate-y-0.5 relative block w-full overflow-hidden rounded-xl bg-gradient-to-r py-4 text-center text-sm font-bold text-white shadow-lg transition-all"
|
||||
>
|
||||
<div className="absolute inset-0 bg-white/20 translate-y-full group-hover:translate-y-0 transition-transform duration-300"></div>
|
||||
<span className="relative">{t.pricing.plans.annual.btnText}</span>
|
||||
<div className="absolute inset-0 translate-y-full bg-white/20 transition-transform duration-300 group-hover:translate-y-0"></div>
|
||||
<span className="relative">{t.pricing.plans.monthly.btnText}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Plan: Quarterly */}
|
||||
<div className="bg-white rounded-3xl border border-gray-200 p-8 flex flex-col hover:border-brand-300 hover:shadow-lg transition-all lg:mt-12 group">
|
||||
<h3 className="text-lg font-bold text-gray-900 group-hover:text-brand-600 transition-colors">{t.pricing.plans.quarterly.title}</h3>
|
||||
<div className="mt-4 mb-2 flex items-baseline gap-1">
|
||||
<span className="text-4xl font-extrabold text-gray-900">{t.pricing.plans.quarterly.price}</span>
|
||||
<span className="text-gray-500 text-sm">{t.pricing.plans.quarterly.period}</span>
|
||||
</div>
|
||||
<p className="text-gray-500 mb-6 text-sm">{t.pricing.plans.quarterly.description}</p>
|
||||
<p className="text-xs font-semibold text-gray-400 mb-6 uppercase tracking-wide">{t.pricing.plans.quarterly.billingInfo}</p>
|
||||
|
||||
<ul className="space-y-4 mb-8 flex-grow">
|
||||
{t.pricing.plans.quarterly.features.map((item, i) => (
|
||||
<li key={i} className="flex items-start gap-3 text-gray-600 text-sm">
|
||||
<Check className="w-5 h-5 text-brand-500 shrink-0" strokeWidth={2} />
|
||||
<span>{item}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<button
|
||||
onClick={() => onRegister('quarterly')}
|
||||
className="block w-full text-center bg-white border-2 border-gray-100 hover:border-brand-500 hover:text-brand-600 text-gray-700 font-bold py-3.5 rounded-xl transition-all text-sm"
|
||||
>
|
||||
{t.pricing.plans.quarterly.btnText}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* --- PROFESSIONAL PLAN SECTION --- */}
|
||||
</div>\n {/* --- PROFESSIONAL PLAN SECTION --- */}
|
||||
<div className="max-w-2xl mx-auto mt-16 animate-in fade-in slide-in-from-bottom-6 duration-700">
|
||||
<div className="relative bg-gray-900 rounded-2xl p-6 md:p-8 overflow-hidden shadow-xl border border-gray-800">
|
||||
{/* Abstract Shapes */}
|
||||
|
|
|
|||
|
|
@ -371,12 +371,12 @@ const dictionary: Record<Language, Translations> = {
|
|||
secure: 'Compra segura. Satisfação garantida ou seu dinheiro de volta em 7 dias.',
|
||||
plans: {
|
||||
monthly: {
|
||||
title: 'Mensal',
|
||||
price: 'R$ 49,90',
|
||||
title: 'Plano Único PRO',
|
||||
price: 'R$ 14,99',
|
||||
period: '/mês',
|
||||
billingInfo: 'Sem fidelidade',
|
||||
description: 'Para quem quer flexibilidade.',
|
||||
btnText: 'Começar Mensal',
|
||||
billingInfo: 'Cancele quando quiser',
|
||||
description: 'Acesso completo e ilimitado ao FoodSnap.',
|
||||
btnText: 'Assinar Agora',
|
||||
features: [
|
||||
'Fotos Ilimitadas',
|
||||
'Feedback Imediato',
|
||||
|
|
@ -721,12 +721,12 @@ const dictionary: Record<Language, Translations> = {
|
|||
secure: 'Secure payment via Stripe. Cancel anytime.',
|
||||
plans: {
|
||||
monthly: {
|
||||
title: 'Monthly',
|
||||
price: '$9.99',
|
||||
title: 'PRO Subscription',
|
||||
price: '$14.99',
|
||||
period: '/mo',
|
||||
billingInfo: 'Billed monthly',
|
||||
description: 'Total flexibility.',
|
||||
btnText: 'Subscribe Monthly',
|
||||
billingInfo: 'Cancel anytime',
|
||||
description: 'Full unlimited access to FoodSnap.',
|
||||
btnText: 'Subscribe Now',
|
||||
features: [
|
||||
'Unlimited Queries',
|
||||
'AI Nutritionist Chat',
|
||||
|
|
@ -1071,12 +1071,12 @@ const dictionary: Record<Language, Translations> = {
|
|||
secure: 'Pago seguro vía Stripe. Cancela cuando quieras.',
|
||||
plans: {
|
||||
monthly: {
|
||||
title: 'Mensual',
|
||||
price: '€ 14,90',
|
||||
title: 'Plan Único PRO',
|
||||
price: '€ 14,99',
|
||||
period: '/mes',
|
||||
billingInfo: 'Cobrado mensualmente',
|
||||
description: 'Flexibilidad total.',
|
||||
btnText: 'Suscribir Mensual',
|
||||
billingInfo: 'Cancela cuando quieras',
|
||||
description: 'Acceso completo e ilimitado.',
|
||||
btnText: 'Suscribir Ahora',
|
||||
features: [
|
||||
'Consultas Ilimitadas',
|
||||
'Chat con Nutricionista IA',
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ const SUPABASE_ANON_KEY = Deno.env.get("SUPABASE_ANON_KEY")!;
|
|||
const SITE_URL = Deno.env.get("SITE_URL")!;
|
||||
|
||||
// ✅ seus PRICE IDs (recorrentes)
|
||||
const PRICE_MENSAL = "price_1SeOVpPHwVDouhbBWZj9beS3";
|
||||
const PRICE_MENSAL = "price_1TLsAFA5oAF7o14GoHRM3LZ8";
|
||||
const PRICE_TRIMESTRAL = "price_1SeOeXPHwVDouhbBcaiUy3vu";
|
||||
const PRICE_ANUAL = "price_1SeOg4PHwVDouhbBTEiUPhMl";
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue