368 lines
18 KiB
Plaintext
368 lines
18 KiB
Plaintext
@model QRRapidoApp.Models.User
|
|
@{
|
|
ViewData["Title"] = "Portal do Desenvolvedor";
|
|
Layout = "~/Views/Shared/_Layout.cshtml";
|
|
|
|
var newKey = TempData["NewKey"] as string;
|
|
var newKeyName = TempData["NewKeyName"] as string;
|
|
var errorMsg = TempData["Error"] as string;
|
|
var successMsg = TempData["Success"] as string;
|
|
|
|
var activeKeys = Model.ApiKeys.Where(k => k.IsActive).OrderByDescending(k => k.CreatedAt).ToList();
|
|
var revokedKeys = Model.ApiKeys.Where(k => !k.IsActive).OrderByDescending(k => k.CreatedAt).ToList();
|
|
|
|
var baseUrl = Context.Request.Scheme + "://" + Context.Request.Host;
|
|
var culture = ViewBag.Culture as string ?? "pt-BR";
|
|
var isEs = culture == "es-PY";
|
|
var docsBase = isEs ? "/es-PY/Developer" : "/Developer";
|
|
|
|
string T(string pt, string es) => isEs ? es : pt;
|
|
}
|
|
|
|
<div class="container mt-4 mb-5">
|
|
|
|
<!-- Cabeçalho -->
|
|
<div class="d-flex align-items-center justify-content-between mb-4 flex-wrap gap-2">
|
|
<div class="d-flex align-items-center">
|
|
<div class="me-3">
|
|
<i class="fas fa-code fa-2x text-primary"></i>
|
|
</div>
|
|
<div>
|
|
<h1 class="h3 mb-0">@T("Portal do Desenvolvedor", "Portal del Desarrollador")</h1>
|
|
<p class="text-muted mb-0 small">
|
|
@T("Gerencie suas chaves de API e integre o QR Rapido nas suas aplicações.",
|
|
"Gestioná tus claves de API ha integrá QR Rapido en tus aplicaciones.")
|
|
@{
|
|
var tier = Model.ApiSubscription?.EffectiveTier ?? QRRapidoApp.Models.ApiPlanTier.Free;
|
|
var tierLabel = tier == QRRapidoApp.Models.ApiPlanTier.Free
|
|
? "<span class='badge bg-secondary ms-2'>Free</span>"
|
|
: $"<span class='badge bg-primary ms-2'>{tier}</span>";
|
|
}
|
|
@T("Plano atual:", "Plan actual:") @Html.Raw(tierLabel)
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="d-flex gap-2">
|
|
<a href="@docsBase/docs" class="btn btn-outline-info btn-sm">
|
|
<i class="fas fa-book me-1"></i> @T("Tutoriais", "Tutoriales")
|
|
</a>
|
|
<a href="/api/docs" target="_blank" class="btn btn-outline-success btn-sm">
|
|
<i class="fas fa-code me-1"></i> Swagger
|
|
</a>
|
|
<a href="@docsBase/Pricing" class="btn btn-outline-primary btn-sm">
|
|
<i class="fas fa-arrow-up me-1"></i> @T("Ver Planos de API", "Ver Planes de API")
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Alertas -->
|
|
@if (!string.IsNullOrEmpty(errorMsg))
|
|
{
|
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
|
<i class="fas fa-exclamation-circle me-2"></i>@errorMsg
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
</div>
|
|
}
|
|
@if (!string.IsNullOrEmpty(successMsg))
|
|
{
|
|
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
|
<i class="fas fa-check-circle me-2"></i>@successMsg
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
</div>
|
|
}
|
|
|
|
<!-- Alerta de nova chave gerada (exibida UMA vez) -->
|
|
@if (!string.IsNullOrEmpty(newKey))
|
|
{
|
|
<div class="alert alert-warning border-warning shadow-sm" role="alert">
|
|
<h5 class="alert-heading">
|
|
<i class="fas fa-key me-2"></i>@T("Chave", "Clave") "<strong>@newKeyName</strong>" @T("criada com sucesso!", "¡creada porã!")
|
|
</h5>
|
|
<p class="mb-2 small">
|
|
<strong class="text-danger">@T("Copie agora.", "Copiá ko'aga.")</strong>
|
|
@T("Esta chave", "Esta clave") <strong>@T("não será exibida novamente", "no se mostrará de nuevo")</strong> @T("por segurança.", "por seguridad.")
|
|
</p>
|
|
<div class="input-group">
|
|
<input type="text" id="newApiKey" class="form-control font-monospace" value="@newKey" readonly>
|
|
<button class="btn btn-warning" type="button" onclick="copyKey('newApiKey', this)">
|
|
<i class="fas fa-copy me-1"></i> @T("Copiar", "Copiar")
|
|
</button>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<div class="row g-4">
|
|
|
|
<!-- Coluna esquerda: chaves + criar -->
|
|
<div class="col-lg-7">
|
|
|
|
<!-- Minhas Chaves Ativas -->
|
|
<div class="card border-0 shadow-sm mb-4">
|
|
<div class="card-header bg-white d-flex justify-content-between align-items-center py-3">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-key me-2 text-primary"></i>@T("Chaves Ativas", "Claves Activas")
|
|
<span class="badge bg-primary ms-1">@activeKeys.Count / 5</span>
|
|
</h5>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
@if (activeKeys.Any())
|
|
{
|
|
<div class="table-responsive">
|
|
<table class="table table-hover mb-0 align-middle">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>@T("Nome", "Nombre")</th>
|
|
<th>@T("Prefixo", "Prefijo")</th>
|
|
<th>@T("Criada", "Creada")</th>
|
|
<th>@T("Último uso", "Último uso")</th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach (var key in activeKeys)
|
|
{
|
|
<tr>
|
|
<td class="fw-semibold">@key.Name</td>
|
|
<td><code class="text-primary">@key.Prefix...</code></td>
|
|
<td class="small text-muted">@key.CreatedAt.ToString("dd/MM/yyyy")</td>
|
|
<td class="small text-muted">
|
|
@(key.LastUsedAt.HasValue
|
|
? key.LastUsedAt.Value.ToString("dd/MM/yyyy HH:mm")
|
|
: T("Nunca", "Nunca"))
|
|
</td>
|
|
<td class="text-end">
|
|
<form method="post" asp-action="RevokeKey" asp-controller="Developer"
|
|
onsubmit="return confirm('@T("Revogar", "Revocar") \'@key.Name\'?')">
|
|
<input type="hidden" name="prefix" value="@key.Prefix">
|
|
<button type="submit" class="btn btn-outline-danger btn-sm">
|
|
<i class="fas fa-ban me-1"></i>@T("Revogar", "Revocar")
|
|
</button>
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<div class="text-center py-5">
|
|
<i class="fas fa-key fa-3x text-muted opacity-25 mb-3"></i>
|
|
<p class="text-muted">@T("Nenhuma chave ativa. Crie a primeira abaixo.", "Ninguna clave activa. Creá la primera abajo.")</p>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Criar nova chave -->
|
|
@if (activeKeys.Count < 5)
|
|
{
|
|
<div class="card border-0 shadow-sm mb-4">
|
|
<div class="card-header bg-white py-3">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-plus-circle me-2 text-success"></i>@T("Criar Nova Chave", "Crear Nueva Clave")
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<form method="post" asp-action="GenerateKey" asp-controller="Developer">
|
|
<div class="mb-3">
|
|
<label for="keyName" class="form-label fw-semibold">@T("Nome da chave", "Nombre de la clave")</label>
|
|
<input type="text" id="keyName" name="keyName" class="form-control"
|
|
placeholder="@T("Ex: Meu App, Produção, Teste...", "Ej: Mi App, Producción, Prueba...")"
|
|
maxlength="50" required>
|
|
<div class="form-text">@T("Identifique para qual projeto ou ambiente esta chave será usada.", "Identificá para qué proyecto o ambiente se va a usar esta clave.")</div>
|
|
</div>
|
|
<button type="submit" class="btn btn-success">
|
|
<i class="fas fa-key me-2"></i>@T("Gerar Chave de API", "Generar Clave de API")
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<div class="alert alert-info">
|
|
<i class="fas fa-info-circle me-2"></i>
|
|
@T("Você atingiu o limite de 5 chaves ativas. Revogue uma para criar outra.",
|
|
"Llegaste al límite de 5 claves activas. Revocá una para crear otra.")
|
|
</div>
|
|
}
|
|
|
|
<!-- Chaves revogadas -->
|
|
@if (revokedKeys.Any())
|
|
{
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="card-header bg-white py-3">
|
|
<h6 class="mb-0 text-muted">
|
|
<i class="fas fa-ban me-2"></i>@T("Chaves Revogadas", "Claves Revocadas")
|
|
</h6>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<div class="table-responsive">
|
|
<table class="table table-sm mb-0 align-middle text-muted">
|
|
<tbody>
|
|
@foreach (var key in revokedKeys)
|
|
{
|
|
<tr>
|
|
<td class="ps-3"><s>@key.Name</s></td>
|
|
<td><code>@key.Prefix...</code></td>
|
|
<td class="small pe-3">@T("Revogada", "Revocada")</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
<!-- Coluna direita: documentação rápida -->
|
|
<div class="col-lg-5">
|
|
|
|
<!-- Quickstart -->
|
|
<div class="card border-0 shadow-sm mb-4">
|
|
<div class="card-header bg-primary text-white py-3">
|
|
<h5 class="mb-0"><i class="fas fa-rocket me-2"></i>Quickstart</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<p class="small text-muted mb-3">
|
|
@T("Envie suas requisições para o endpoint abaixo com o header",
|
|
"Enviá tus solicitudes al endpoint de abajo con el header") <code>X-API-Key</code>:
|
|
</p>
|
|
<div class="bg-dark rounded p-3 mb-3">
|
|
<code class="text-success small">POST @baseUrl/api/v1/QRManager/generate</code>
|
|
</div>
|
|
|
|
<h6 class="fw-bold mt-3">@T("Exemplo (cURL)", "Ejemplo (cURL)")</h6>
|
|
<div class="bg-dark rounded p-3 overflow-auto" style="max-height: 220px;">
|
|
<pre class="text-light mb-0 small"><code>curl -X POST \
|
|
@baseUrl/api/v1/QRManager/generate \
|
|
-H "X-API-Key: @T("SUA_CHAVE_AQUI", "TU_CLAVE_AQUI")" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"content": "https://seusite.com",
|
|
"type": "url",
|
|
"size": 400,
|
|
"primaryColor": "#000000",
|
|
"backgroundColor": "#FFFFFF"
|
|
}'</code></pre>
|
|
</div>
|
|
|
|
<h6 class="fw-bold mt-3">@T("Resposta de sucesso", "Respuesta exitosa")</h6>
|
|
<div class="bg-dark rounded p-3 overflow-auto" style="max-height: 160px;">
|
|
<pre class="text-light mb-0 small"><code>{
|
|
"success": true,
|
|
"qrCodeBase64": "iVBORw0KGgo...",
|
|
"qrId": "abc123",
|
|
"generationTimeMs": 280,
|
|
"remainingCredits": 42,
|
|
"fromCache": false
|
|
}</code></pre>
|
|
</div>
|
|
|
|
<a href="/api/v1/QRManager/ping" target="_blank" class="btn btn-outline-primary btn-sm mt-3">
|
|
<i class="fas fa-heartbeat me-1"></i> @T("Testar Ping", "Probar Ping")
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Limites e planos -->
|
|
<div class="card border-0 shadow-sm mb-4">
|
|
<div class="card-header bg-white py-3">
|
|
<h5 class="mb-0"><i class="fas fa-tachometer-alt me-2 text-warning"></i>@T("Limites da API", "Límites de la API")</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<table class="table table-sm mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>@T("Recurso", "Recurso")</th>
|
|
<th>@T("Gratuito", "Gratuito")</th>
|
|
<th>@T("Créditos", "Créditos")</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td>@T("QRs por chave", "QRs por clave")</td>
|
|
<td>@T("5 vitalícios", "5 de por vida")</td>
|
|
<td>1 @T("crédito/QR", "crédito/QR")</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Rate limit</td>
|
|
<td colspan="2">600 req/min @T("por IP", "por IP")</td>
|
|
</tr>
|
|
<tr>
|
|
<td>@T("Chaves ativas", "Claves activas")</td>
|
|
<td colspan="2">@T("Até 5", "Hasta 5")</td>
|
|
</tr>
|
|
<tr>
|
|
<td>@T("Formatos", "Formatos")</td>
|
|
<td colspan="2">PNG (base64)</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<div class="mt-3">
|
|
<a href="/Pagamento/SelecaoPlano" class="btn btn-warning btn-sm fw-bold">
|
|
<i class="fas fa-coins me-1"></i> @T("Comprar Créditos", "Comprar Créditos")
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tipos de QR suportados -->
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="card-header bg-white py-3">
|
|
<h5 class="mb-0"><i class="fas fa-list me-2 text-info"></i>@T("Tipos de QR (campo", "Tipos de QR (campo") <code>type</code>)</h5>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<ul class="list-group list-group-flush small">
|
|
<li class="list-group-item d-flex justify-content-between">
|
|
<code>url</code><span class="text-muted">Link / URL</span>
|
|
</li>
|
|
<li class="list-group-item d-flex justify-content-between">
|
|
<code>pix</code><span class="text-muted">@T("Pagamento Pix", "Pago Pix")</span>
|
|
</li>
|
|
<li class="list-group-item d-flex justify-content-between">
|
|
<code>wifi</code><span class="text-muted">@T("Rede Wi-Fi", "Red Wi-Fi")</span>
|
|
</li>
|
|
<li class="list-group-item d-flex justify-content-between">
|
|
<code>vcard</code><span class="text-muted">@T("Cartão de Visita", "Tarjeta de Visita")</span>
|
|
</li>
|
|
<li class="list-group-item d-flex justify-content-between">
|
|
<code>whatsapp</code><span class="text-muted">Link WhatsApp</span>
|
|
</li>
|
|
<li class="list-group-item d-flex justify-content-between">
|
|
<code>email</code><span class="text-muted">E-mail</span>
|
|
</li>
|
|
<li class="list-group-item d-flex justify-content-between">
|
|
<code>sms</code><span class="text-muted">SMS</span>
|
|
</li>
|
|
<li class="list-group-item d-flex justify-content-between">
|
|
<code>texto</code><span class="text-muted">@T("Texto livre", "Texto libre")</span>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@section Scripts {
|
|
<script>
|
|
function copyKey(inputId, btn) {
|
|
const input = document.getElementById(inputId);
|
|
navigator.clipboard.writeText(input.value).then(() => {
|
|
const original = btn.innerHTML;
|
|
btn.innerHTML = '<i class="fas fa-check me-1"></i> @T("Copiado!", "¡Copiado!")';
|
|
btn.classList.replace('btn-warning', 'btn-success');
|
|
setTimeout(() => {
|
|
btn.innerHTML = original;
|
|
btn.classList.replace('btn-success', 'btn-warning');
|
|
}, 2500);
|
|
});
|
|
}
|
|
</script>
|
|
}
|