From f2fe4f1546b8797dd4d0d535370e63a9ac157fde Mon Sep 17 00:00:00 2001 From: VPS 4 Builder Date: Fri, 15 May 2026 10:04:00 +0200 Subject: [PATCH] feat: persist premium ui and versioning --- frontend/src/App.tsx | 315 +++++++++++++++++++++++++++++++------------ 1 file changed, 229 insertions(+), 86 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index d1e1400..228aac6 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,16 +1,29 @@ import React, { useState, useEffect } from 'react'; +import { APP_VERSION } from './constants'; const API = '/api'; +interface Domain { + domain: string; + id?: number; + status?: string; +} + export default function App() { const [hasKey, setHasKey] = useState(null); const [apiKeyInput, setApiKeyInput] = useState(''); const [saving, setSaving] = useState(false); - const [subdomains, setSubdomains] = useState([]); + const [domains, setDomains] = useState([]); const [loadingDomains, setLoadingDomains] = useState(false); const [error, setError] = useState(''); const [success, setSuccess] = useState(''); + // Form states + const [selectedDomain, setSelectedDomain] = useState(''); + const [newSubdomain, setNewSubdomain] = useState(''); + const [newValue, setNewValue] = useState(''); + const [creating, setCreating] = useState(false); + useEffect(() => { fetch(`${API}/config`) .then(r => r.json()) @@ -22,119 +35,249 @@ export default function App() { if (!apiKeyInput.trim()) return; setSaving(true); setError(''); + setSuccess(''); try { const r = await fetch(`${API}/config/hostinger-key`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ api_key: apiKeyInput }) }); - if (r.ok) { setHasKey(true); setApiKeyInput(''); setSuccess('Chave salva com sucesso.'); } - else { const d = await r.json(); setError(d.error || 'Erro ao salvar.'); } + if (r.ok) { + setHasKey(true); + setApiKeyInput(''); + setSuccess('Chave API salva e configurada com sucesso.'); + } else { + const d = await r.json(); + setError(d.error || 'Erro ao salvar chave.'); + } } finally { setSaving(false); } } - async function fetchSubdomains() { + async function fetchDomains() { setLoadingDomains(true); setError(''); + setSuccess(''); try { const r = await fetch(`${API}/subdomains`); const d = await r.json(); - if (r.ok) setSubdomains(Array.isArray(d) ? d : d.data || []); - else setError(d.error || 'Erro ao buscar domínios.'); + if (r.ok) { + setDomains(Array.isArray(d) ? d : d.data || []); + } else { + setError(d.error || 'Erro ao buscar domínios na Hostinger.'); + } } finally { setLoadingDomains(false); } } + async function createSubdomain(e: React.FormEvent) { + e.preventDefault(); + if (!selectedDomain || !newSubdomain || !newValue) { + setError('Preencha todos os campos do subdomínio.'); + return; + } + setCreating(true); + setError(''); + setSuccess(''); + try { + const r = await fetch(`${API}/subdomains`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + domain: selectedDomain, + subdomain: newSubdomain, + value: newValue + }) + }); + const d = await r.json(); + if (r.ok) { + setSuccess(`Sucesso! ${newSubdomain}.${selectedDomain} foi criado/atualizado.`); + setNewSubdomain(''); + setNewValue(''); + } else { + setError(d.error || 'Erro ao criar subdomínio.'); + } + } catch (err: any) { + setError(err.message); + } finally { setCreating(false); } + } + return ( -
-
+
+
- {/* Header */} -
-
C
-
-

Gerenciador de Subdomínios

-

clube67.com — Hostinger DNS

-
-
- - {/* Card API Key */} -
-
-

Hostinger API Key

- {hasKey === true && ( - - ● CONFIGURADA - - )} - {hasKey === false && ( - - ● NÃO CONFIGURADA - - )} -
- -

- Insira a API Key da Hostinger para gerenciar registros DNS e criar subdomínios automaticamente. -

- -
- setApiKeyInput(e.target.value)} - onKeyDown={e => e.key === 'Enter' && saveKey()} - className="flex-1 bg-gray-800 border border-gray-700 rounded-xl px-4 py-3 text-sm focus:outline-none focus:border-orange-500 placeholder-gray-600" - /> - -
- - {success &&

{success}

} - {error &&

{error}

} -
- - {/* Card Domínios */} - {hasKey && ( -
-
-

Domínios / DNS

- + {/* HEADER */} +
+
+
+ C
+
+

Gerenciador de DNS

+

Clube67 — Automação Hostinger

+
+
+ + {hasKey && ( +
+
+ API Hostinger Ativa +
+ )} +
- {subdomains.length === 0 && !loadingDomains && ( -

Clique em "Buscar da Hostinger" para listar os domínios.

+
+ + {/* COLUNA ESQUERDA: CONFIG & LISTA */} +
+ + {/* CONFIG CARD */} + {!hasKey && ( +
+

Configuração Necessária

+
+ setApiKeyInput(e.target.value)} + className="w-full bg-gray-950 border border-gray-800 rounded-2xl px-5 py-4 text-sm focus:outline-none focus:border-orange-500/50 transition-all placeholder:text-gray-700" + /> + +
+
)} - {subdomains.length > 0 && ( -
- {subdomains.map((d: any, i: number) => ( -
- {d.domain || d.name || JSON.stringify(d)} - {d.type || ''} -
- ))} + {/* LISTA DE DOMÍNIOS */} + {hasKey && ( +
+
+

Meus Domínios

+ +
+ +
+ {domains.length === 0 ? ( +
+

Clique em sincronizar para listar seus domínios da Hostinger.

+
+ ) : ( + domains.map((d, i) => ( + + )) + )} +
)}
- )} - {/* Rodapé */} -

- subdominio.clube67.com — acesso restrito VPN -

+ {/* COLUNA DIREITA: FORMULÁRIO DE CRIAÇÃO */} +
+
+
+ DNS +
+ +

Novo Subdomínio

+ +
+
+ +
+ {selectedDomain || 'Selecione um domínio na lista ao lado'} +
+
+ +
+
+ + setNewSubdomain(e.target.value)} + className="w-full bg-gray-950 border border-gray-800 rounded-2xl px-5 py-4 text-sm focus:outline-none focus:border-orange-500/50 transition-all placeholder:text-gray-800" + /> +
+
+ + setNewValue(e.target.value)} + className="w-full bg-gray-950 border border-gray-800 rounded-2xl px-5 py-4 text-sm focus:outline-none focus:border-orange-500/50 transition-all placeholder:text-gray-800" + /> +
+
+ +
+ +
+ + {error && ( +
+ 🚨 {error} +
+ )} + + {success && ( +
+ ✅ {success} +
+ )} +
+
+
+
+ + {/* FOOTER */} +
+
+

+ {APP_VERSION} + subdominio.clube67.com — acesso restrito VPN +

+
+
+ +
); }