diff --git a/admin/src/app/api/tenants/route.ts b/admin/src/app/api/tenants/route.ts index 4b5099c..897cf96 100644 --- a/admin/src/app/api/tenants/route.ts +++ b/admin/src/app/api/tenants/route.ts @@ -63,18 +63,20 @@ export async function POST(req: NextRequest) { export async function PUT(req: NextRequest) { try { - const { id, name, slug, email, phone, plan, status, password } = await req.json(); + const { id, name, slug, email, phone, plan, status, password, grantAccessDays } = await req.json(); if (!id) return NextResponse.json({ error: 'Missing id' }, { status: 400 }); const planRes = await query('SELECT id FROM plans WHERE name = $1 LIMIT 1', [plan || 'Starter']); const planId = planRes.rows.length > 0 ? planRes.rows[0].id : null; - const res = await query( - `UPDATE tenants - SET name = $1, slug = $2, email = $3, phone = $4, plan_id = $5, subscription_status = $6, updated_at = NOW() - WHERE id = $7 RETURNING *`, - [name, slug, email, phone, planId, status, id] - ); + let updateQuery = `UPDATE tenants SET name = $1, slug = $2, email = $3, phone = $4, plan_id = $5, subscription_status = $6, updated_at = NOW() WHERE id = $7 RETURNING *`; + const params: any[] = [name, slug, email, phone, planId, status, id]; + + if (grantAccessDays) { + updateQuery = `UPDATE tenants SET name = $1, slug = $2, email = $3, phone = $4, plan_id = $5, subscription_status = 'active', updated_at = NOW(), subscription_ends_at = NOW() + INTERVAL '${Number(grantAccessDays)} days' WHERE id = $7 RETURNING *`; + } + + const res = await query(updateQuery, params); if (password) { const salt = await bcrypt.genSalt(10); diff --git a/admin/src/app/globals.css b/admin/src/app/globals.css index 5d497d4..76addc3 100644 --- a/admin/src/app/globals.css +++ b/admin/src/app/globals.css @@ -104,18 +104,30 @@ body { .stat-value { font-size: 28px; font-weight: 800; letter-spacing: -1px; line-height: 1.2; } .stat-label { font-size: 13px; color: var(--text-secondary); margin-top: 2px; } -.btn { - display: inline-flex; align-items: center; gap: 8px; padding: 10px 20px; border-radius: var(--radius-sm); - font-size: 14px; font-weight: 600; cursor: pointer; border: none; transition: all 0.2s; font-family: inherit; +.btn, button:not(.tab):not(.toggle) { + display: inline-flex; align-items: center; justify-content: center; gap: 8px; padding: 8px 16px; border-radius: 6px; + font-size: 13.5px; font-weight: 500; cursor: pointer; border: 1px solid transparent; background: transparent; + box-shadow: none !important; outline: none; transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease, transform 0.08s ease; font-family: inherit; user-select: none; } -.btn-primary { background: var(--gradient-primary); color: white; box-shadow: var(--shadow-glow); } -.btn-primary:hover { transform: translateY(-1px); } -.btn-secondary { background: var(--bg-elevated); color: var(--text-primary); border: 1px solid var(--border-color); } -.btn-danger { background: var(--danger-bg); color: var(--danger); border: 1px solid rgba(239,68,68,0.2); } -.btn-ghost { background: transparent; color: var(--text-secondary); padding: 8px 12px; border: none; cursor: pointer; font-family: inherit; } -.btn-ghost:hover { color: var(--text-primary); background: var(--bg-card); } -.btn-sm { padding: 6px 14px; font-size: 13px; } -.btn-icon { padding: 8px; border-radius: var(--radius-sm); } +.btn:active, button:not(.tab):not(.toggle):active { + transform: scale(0.95) !important; +} +.btn-primary { background: var(--accent); color: white; border: 1px solid transparent; } +.btn-primary:hover { background: var(--accent-hover); color: white; filter: brightness(1.15); } +.btn-secondary { background: rgba(255, 255, 255, 0.04); color: var(--text-primary); border: 1px solid var(--border-color); } +.btn-secondary:hover { background: rgba(255, 255, 255, 0.1); border-color: rgba(255, 255, 255, 0.2); color: white; } +.btn-danger { background: rgba(239, 68, 68, 0.1); color: #ef4444; border: 1px solid rgba(239, 68, 68, 0.25); } +.btn-danger:hover { background: rgba(239, 68, 68, 0.25); border-color: rgba(239, 68, 68, 0.5); color: #f87171; } +.btn-ghost { background: rgba(255, 255, 255, 0.03); color: var(--text-secondary); border: 1px solid rgba(255, 255, 255, 0.08); padding: 6px 10px; } +.btn-ghost:hover { color: var(--text-primary); background: rgba(255, 255, 255, 0.12); border-color: rgba(255, 255, 255, 0.25); } +.btn-ghost[style*="danger"], .btn-ghost[style*="239, 68, 68"], .btn-ghost.danger { + background: rgba(239, 68, 68, 0.1) !important; border: 1px solid rgba(239, 68, 68, 0.2) !important; color: #ef4444 !important; +} +.btn-ghost[style*="danger"]:hover, .btn-ghost[style*="239, 68, 68"]:hover, .btn-ghost.danger:hover { + background: rgba(239, 68, 68, 0.25) !important; border-color: rgba(239, 68, 68, 0.5) !important; color: #f87171 !important; +} +.btn-sm { padding: 5px 12px; font-size: 12.5px; } +.btn-icon { padding: 6px; border-radius: 6px; display: inline-flex; align-items: center; justify-content: center; } .form-group { margin-bottom: 20px; } .form-label { display: block; font-size: 13px; font-weight: 600; color: var(--text-secondary); margin-bottom: 6px; } diff --git a/admin/src/components/Plans.tsx b/admin/src/components/Plans.tsx index 169b92c..2b80d2d 100644 --- a/admin/src/components/Plans.tsx +++ b/admin/src/components/Plans.tsx @@ -198,24 +198,24 @@ export default function Plans() {
Define a validade da assinatura sem exigir pagamento do cliente. O status mudará automaticamente para Ativo.
+