fix: login e termos de uso
All checks were successful
Deploy QR Rapido / test (push) Successful in 1m20s
Deploy QR Rapido / build-and-push (push) Successful in 9m56s
Deploy QR Rapido / deploy-staging (push) Has been skipped
Deploy QR Rapido / deploy-production (push) Successful in 1m58s

This commit is contained in:
Ricardo Carneiro 2026-03-30 10:47:03 -03:00
parent c10caf9463
commit 5bcccce8a4
3 changed files with 282 additions and 102 deletions

21
.claude/napkin.md Normal file
View File

@ -0,0 +1,21 @@
# Napkin Runbook
## Curation Rules
- Re-prioritize on every read.
- Keep recurring, high-value notes only.
- Max 10 items per category.
- Each item includes date + "Do instead".
## Execution & Validation (Highest Priority)
_(empty — add entries as they emerge)_
## Shell & Command Reliability
1. **[2026-03-30] Windows paths in bash tools need forward slash style**
Do instead: use `C:/vscode/qrrapido` style, not `C:\...` or `/mnt/c/...` (WSL mount not available in this environment).
## Domain Behavior Guardrails
_(empty — add entries as they emerge)_
## User Directives
1. **[2026-03-30] Diagnose before fixing**
Do instead: always present diagnosis and reasoning first; wait for user approval before touching code.

View File

@ -284,111 +284,30 @@ with open('qrcode.svg', 'wb') as f:
</div>
</section>
<!-- ======================== PLANOS (teaser) ======================== -->
<!-- ======================== PLANOS ======================== -->
<section class="py-5 bg-light">
<div class="container">
<div class="text-center mb-5">
<h2 class="fw-bold">@T("Planos para cada escala", "Planes para cada escala", "Plans for every scale")</h2>
<p class="text-muted">@T("De projetos pessoais a sistemas de alto volume.", "De proyectos personales a sistemas de alto volumen.", "From personal projects to high-volume systems.")</p>
</div>
<div class="row g-4 justify-content-center">
<!-- Free -->
<div class="col-md-6 col-lg-3">
<div class="card h-100 border-0 shadow-sm text-center">
<div class="card-header bg-secondary text-white py-3">
<h5 class="mb-0">Free</h5>
</div>
<div class="card-body">
<div class="display-6 fw-bold my-3">R$0</div>
<ul class="list-unstyled text-start small">
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>10 req/min</li>
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>500 req/@T("mês","mes","month")</li>
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>PNG + WebP</li>
<li class="mb-2 text-muted"><i class="fas fa-times me-2"></i>SVG</li>
</ul>
</div>
</div>
</div>
<!-- Starter -->
<div class="col-md-6 col-lg-3">
<div class="card h-100 border-0 shadow-sm text-center">
<div class="card-header bg-primary text-white py-3">
<h5 class="mb-0">Starter</h5>
</div>
<div class="card-body">
<div class="display-6 fw-bold my-3 text-muted">
<i class="fas fa-lock fs-4 mb-1 d-block"></i>
<span class="fs-6 fw-normal">@T("Faça login para ver o preço", "Iniciá sesión para ver el precio", "Log in to see pricing")</span>
</div>
<ul class="list-unstyled text-start small">
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>50 req/min</li>
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>10.000 req/@T("mês","mes","month")</li>
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>PNG + WebP + SVG</li>
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>@T("Suporte e-mail","Soporte e-mail","Email support")</li>
</ul>
</div>
</div>
</div>
<!-- Pro -->
<div class="col-md-6 col-lg-3">
<div class="card h-100 border-0 shadow border-warning text-center">
<div class="card-header bg-warning text-dark py-3 position-relative">
<h5 class="mb-0">Pro</h5>
<span class="position-absolute top-0 end-0 translate-middle badge bg-danger" style="font-size:.6rem;">@T("Popular","Popular","Popular")</span>
</div>
<div class="card-body">
<div class="display-6 fw-bold my-3 text-muted">
<i class="fas fa-lock fs-4 mb-1 d-block"></i>
<span class="fs-6 fw-normal">@T("Faça login para ver o preço", "Iniciá sesión para ver el precio", "Log in to see pricing")</span>
</div>
<ul class="list-unstyled text-start small">
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>200 req/min</li>
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>100.000 req/@T("mês","mes","month")</li>
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>PNG + WebP + SVG</li>
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>@T("Suporte prioritário","Soporte prioritario","Priority support")</li>
</ul>
</div>
</div>
</div>
<!-- Business -->
<div class="col-md-6 col-lg-3">
<div class="card h-100 border-0 shadow-sm text-center">
<div class="card-header bg-success text-white py-3">
<h5 class="mb-0">Business</h5>
</div>
<div class="card-body">
<div class="display-6 fw-bold my-3 text-muted">
<i class="fas fa-lock fs-4 mb-1 d-block"></i>
<span class="fs-6 fw-normal">@T("Faça login para ver o preço", "Iniciá sesión para ver el precio", "Log in to see pricing")</span>
</div>
<ul class="list-unstyled text-start small">
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>500 req/min</li>
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>500.000 req/@T("mês","mes","month")</li>
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>PNG + WebP + SVG</li>
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>@T("Suporte dedicado + SLA","Soporte dedicado + SLA","Dedicated support + SLA")</li>
</ul>
</div>
</div>
</div>
</div>
<!-- Nota de login para preços -->
<div class="alert alert-info text-center mt-4 mx-auto" style="max-width:560px;">
<i class="fas fa-info-circle me-2"></i>
@T(
"Os preços dos planos pagos estão disponíveis após o login. O cadastro é gratuito e leva menos de 30 segundos.",
"Los precios de los planes pagos están disponibles después del inicio de sesión. El registro es gratuito y lleva menos de 30 segundos.",
"Paid plan pricing is available after logging in. Sign-up is free and takes under 30 seconds."
)
<div class="mt-2">
<a href="/Account/Login" class="btn btn-primary btn-sm">
<i class="fas fa-sign-in-alt me-1"></i>@T("Entrar / Criar conta", "Iniciar sesión / Crear cuenta", "Login / Create account")
<div class="text-center" style="max-width:640px; margin:0 auto;">
<h2 class="fw-bold mb-3">@T("Planos para cada escala", "Planes para cada escala", "Plans for every scale")</h2>
<p class="text-muted mb-4">
@T(
"Temos planos para diferentes volumes de requisições, do uso pessoal ao alto volume empresarial. Todos os planos suportam PNG e WebP; os planos pagos liberam também o formato SVG.",
"Tenemos planes para distintos volúmenes de solicitudes, desde uso personal hasta alto volumen empresarial. Todos los planes soportan PNG y WebP; los planes pagos habilitan también el formato SVG.",
"We have plans for different request volumes, from personal use to high-volume enterprise. All plans support PNG and WebP; paid plans also unlock SVG output."
)
</p>
@if (ViewBag.IsAuthenticated == true)
{
<a href="/Developer/Pricing" class="btn btn-primary">
<i class="fas fa-tags me-2"></i>@T("Ver planos e preços", "Ver planes y precios", "View plans and pricing")
</a>
</div>
}
else
{
<a href="/Account/Login" class="btn btn-primary">
<i class="fas fa-sign-in-alt me-2"></i>@T("Entrar para ver os planos", "Iniciá sesión para ver los planes", "Log in to view plans")
</a>
}
</div>
</div>
</section>

240
openapi.json Normal file
View File

@ -0,0 +1,240 @@
{
"openapi": "3.0.1",
"info": {
"title": "QRRapido API",
"description": "**QRRapido** — Ultra-fast QR Code generation API.\n\nGenerate QR codes for URLs, Pix payments, Wi-Fi, vCards, WhatsApp, SMS and more.\n\n**PT-BR:** Gere QR codes para URLs, Pix, Wi-Fi, vCard, WhatsApp, SMS e muito mais.\n\n**Authentication:** All endpoints (except `/ping`) require an API key sent in the `X-API-Key` header.\nGet your key at [qrrapido.site/Developer](https://qrrapido.site/Developer).",
"contact": {
"name": "QRRapido",
"url": "https://qrrapido.site",
"email": "contato@qrrapido.site"
},
"license": {
"name": "Commercial — see qrrapido.site/terms"
},
"version": "v1"
},
"paths": {
"/api/v1/QRManager/ping": {
"get": {
"tags": [
"QRManager"
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/api/v1/QRManager/generate": {
"post": {
"tags": [
"QRManager"
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/QRGenerationRequest"
}
},
"text/json": {
"schema": {
"$ref": "#/components/schemas/QRGenerationRequest"
}
},
"application/*+json": {
"schema": {
"$ref": "#/components/schemas/QRGenerationRequest"
}
}
}
},
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/QRResponseDto"
}
}
}
},
"400": {
"description": "Bad Request",
"content": {
"application/json": {
"schema": { }
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": { }
}
}
},
"402": {
"description": "Payment Required",
"content": {
"application/json": {
"schema": { }
}
}
},
"429": {
"description": "Too Many Requests",
"content": {
"application/json": {
"schema": { }
}
}
}
}
}
}
},
"components": {
"schemas": {
"QRGenerationRequest": {
"type": "object",
"properties": {
"type": {
"type": "string",
"nullable": true
},
"content": {
"type": "string",
"nullable": true
},
"quickStyle": {
"type": "string",
"nullable": true
},
"primaryColor": {
"type": "string",
"nullable": true
},
"backgroundColor": {
"type": "string",
"nullable": true
},
"size": {
"type": "integer",
"format": "int32"
},
"margin": {
"type": "integer",
"format": "int32"
},
"cornerStyle": {
"type": "string",
"nullable": true
},
"optimizeForSpeed": {
"type": "boolean"
},
"language": {
"type": "string",
"nullable": true
},
"isPremium": {
"type": "boolean"
},
"hasLogo": {
"type": "boolean"
},
"logo": {
"type": "string",
"format": "byte",
"nullable": true
},
"logoSizePercent": {
"type": "integer",
"format": "int32",
"nullable": true
},
"applyLogoColorization": {
"type": "boolean"
},
"enableTracking": {
"type": "boolean"
},
"outputFormat": {
"type": "string",
"nullable": true
}
},
"additionalProperties": false
},
"QRResponseDto": {
"type": "object",
"properties": {
"success": {
"type": "boolean"
},
"qrCodeBase64": {
"type": "string",
"nullable": true
},
"qrId": {
"type": "string",
"nullable": true
},
"message": {
"type": "string",
"nullable": true
},
"errorCode": {
"type": "string",
"nullable": true
},
"remainingCredits": {
"type": "integer",
"format": "int32"
},
"remainingFreeQRs": {
"type": "integer",
"format": "int32"
},
"fromCache": {
"type": "boolean"
},
"newDeviceId": {
"type": "string",
"nullable": true
},
"generationTimeMs": {
"type": "integer",
"format": "int64"
},
"format": {
"type": "string",
"nullable": true
},
"mimeType": {
"type": "string",
"nullable": true
}
},
"additionalProperties": false
}
},
"securitySchemes": {
"ApiKey": {
"type": "apiKey",
"description": "Your QRRapido API key. Obtain one at https://qrrapido.site/Developer",
"name": "X-API-Key",
"in": "header"
}
}
},
"security": [
{
"ApiKey": [ ]
}
]
}