feat(i18n): migrate providers/new + AuditLogTab + remaining keys
Phase 4: providers/new/page.tsx — 15 strings (form labels, errors, buttons) Phase 5: logs/AuditLogTab.tsx — 15 strings (headers, filters, pagination) Phase 6: Added ~50 new keys to en.json + pt-BR.json Providers namespace: selectProvider, apiKeyRequired, authMethod, etc. Logs namespace: auditLogDesc, filterByAction, filterByActor, etc.
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
interface AuditEntry {
|
||||
id: number;
|
||||
@@ -27,6 +28,7 @@ export default function AuditLogTab() {
|
||||
const [actorFilter, setActorFilter] = useState("");
|
||||
const [offset, setOffset] = useState(0);
|
||||
const [hasMore, setHasMore] = useState(false);
|
||||
const t = useTranslations("logs");
|
||||
|
||||
const fetchEntries = useCallback(async () => {
|
||||
setLoading(true);
|
||||
@@ -85,10 +87,8 @@ export default function AuditLogTab() {
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-[var(--color-text-main)]">Audit Log</h2>
|
||||
<p className="text-sm text-[var(--color-text-muted)] mt-1">
|
||||
Administrative actions and security events
|
||||
</p>
|
||||
<h2 className="text-xl font-bold text-[var(--color-text-main)]">{t("auditLog")}</h2>
|
||||
<p className="text-sm text-[var(--color-text-muted)] mt-1">{t("auditLogDesc")}</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={fetchEntries}
|
||||
@@ -96,7 +96,7 @@ export default function AuditLogTab() {
|
||||
aria-label="Refresh audit log"
|
||||
className="px-4 py-2 rounded-lg text-sm font-medium bg-[var(--color-surface)] border border-[var(--color-border)] text-[var(--color-text-main)] hover:bg-[var(--color-bg-alt)] transition-colors disabled:opacity-50"
|
||||
>
|
||||
{loading ? "Loading..." : "Refresh"}
|
||||
{loading ? t("loading") : t("refresh")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -108,7 +108,7 @@ export default function AuditLogTab() {
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Filter by action..."
|
||||
placeholder={t("filterByAction")}
|
||||
value={actionFilter}
|
||||
onChange={(e) => setActionFilter(e.target.value)}
|
||||
onKeyDown={(e) => e.key === "Enter" && handleSearch()}
|
||||
@@ -117,7 +117,7 @@ export default function AuditLogTab() {
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Filter by actor..."
|
||||
placeholder={t("filterByActor")}
|
||||
value={actorFilter}
|
||||
onChange={(e) => setActorFilter(e.target.value)}
|
||||
onKeyDown={(e) => e.key === "Enter" && handleSearch()}
|
||||
@@ -128,7 +128,7 @@ export default function AuditLogTab() {
|
||||
onClick={handleSearch}
|
||||
className="px-4 py-2 rounded-lg text-sm font-medium bg-[var(--color-accent)] text-white hover:bg-[var(--color-accent-hover)] transition-colors focus:outline-2 focus:outline-offset-2 focus:outline-[var(--color-accent)]"
|
||||
>
|
||||
Search
|
||||
{t("search")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -148,19 +148,19 @@ export default function AuditLogTab() {
|
||||
<thead>
|
||||
<tr className="bg-[var(--color-bg-alt)] border-b border-[var(--color-border)]">
|
||||
<th className="text-left px-4 py-3 font-medium text-[var(--color-text-muted)]">
|
||||
Timestamp
|
||||
{t("timestamp")}
|
||||
</th>
|
||||
<th className="text-left px-4 py-3 font-medium text-[var(--color-text-muted)]">
|
||||
Action
|
||||
{t("action")}
|
||||
</th>
|
||||
<th className="text-left px-4 py-3 font-medium text-[var(--color-text-muted)]">
|
||||
Actor
|
||||
{t("actor")}
|
||||
</th>
|
||||
<th className="text-left px-4 py-3 font-medium text-[var(--color-text-muted)]">
|
||||
Target
|
||||
{t("target")}
|
||||
</th>
|
||||
<th className="text-left px-4 py-3 font-medium text-[var(--color-text-muted)]">
|
||||
Details
|
||||
{t("details")}
|
||||
</th>
|
||||
<th className="text-left px-4 py-3 font-medium text-[var(--color-text-muted)]">IP</th>
|
||||
</tr>
|
||||
@@ -169,7 +169,7 @@ export default function AuditLogTab() {
|
||||
{entries.length === 0 && !loading ? (
|
||||
<tr>
|
||||
<td colSpan={6} className="px-4 py-8 text-center text-[var(--color-text-muted)]">
|
||||
No audit log entries found
|
||||
{t("noEntries")}
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
@@ -216,14 +216,14 @@ export default function AuditLogTab() {
|
||||
disabled={offset === 0}
|
||||
className="px-3 py-1.5 rounded-lg text-xs font-medium bg-[var(--color-surface)] border border-[var(--color-border)] text-[var(--color-text-main)] hover:bg-[var(--color-bg-alt)] disabled:opacity-30 transition-colors"
|
||||
>
|
||||
← Previous
|
||||
← {t("previous")}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setOffset(offset + PAGE_SIZE)}
|
||||
disabled={!hasMore}
|
||||
className="px-3 py-1.5 rounded-lg text-xs font-medium bg-[var(--color-surface)] border border-[var(--color-border)] text-[var(--color-text-main)] hover:bg-[var(--color-bg-alt)] disabled:opacity-30 transition-colors"
|
||||
>
|
||||
Next →
|
||||
{t("next")} →
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useRouter } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import { Card, Button, Input, Select, Toggle } from "@/shared/components";
|
||||
import { AI_PROVIDERS, AUTH_METHODS } from "@/shared/constants/config";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
const providerOptions = Object.values(AI_PROVIDERS).map((p) => ({
|
||||
value: p.id,
|
||||
@@ -19,6 +20,7 @@ const authMethodOptions = Object.values(AUTH_METHODS).map((m) => ({
|
||||
export default function NewProviderPage() {
|
||||
const router = useRouter();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const t = useTranslations("providers");
|
||||
const [formData, setFormData] = useState({
|
||||
provider: "",
|
||||
authMethod: "api_key",
|
||||
@@ -37,9 +39,9 @@ export default function NewProviderPage() {
|
||||
|
||||
const validate = () => {
|
||||
const newErrors: any = {};
|
||||
if (!formData.provider) newErrors.provider = "Please select a provider";
|
||||
if (!formData.provider) newErrors.provider = t("selectProvider");
|
||||
if (formData.authMethod === "api_key" && !formData.apiKey) {
|
||||
newErrors.apiKey = "API Key is required";
|
||||
newErrors.apiKey = t("apiKeyRequired");
|
||||
}
|
||||
setErrors(newErrors);
|
||||
return Object.keys(newErrors).length === 0;
|
||||
@@ -61,10 +63,10 @@ export default function NewProviderPage() {
|
||||
router.push("/dashboard/providers");
|
||||
} else {
|
||||
const data = await response.json();
|
||||
setErrors({ submit: data.error || "Failed to create provider" });
|
||||
setErrors({ submit: data.error || t("failedCreateProvider") });
|
||||
}
|
||||
} catch (error) {
|
||||
setErrors({ submit: "An error occurred. Please try again." });
|
||||
setErrors({ submit: t("errorOccurredRetry") });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -81,12 +83,10 @@ export default function NewProviderPage() {
|
||||
className="inline-flex items-center gap-1 text-sm text-text-muted hover:text-primary transition-colors mb-4"
|
||||
>
|
||||
<span className="material-symbols-outlined text-lg">arrow_back</span>
|
||||
Back to Providers
|
||||
{t("backToProviders")}
|
||||
</Link>
|
||||
<h1 className="text-3xl font-semibold tracking-tight">Add New Provider</h1>
|
||||
<p className="text-text-muted mt-2">
|
||||
Configure a new AI provider to use with your applications.
|
||||
</p>
|
||||
<h1 className="text-3xl font-semibold tracking-tight">{t("addNewProvider")}</h1>
|
||||
<p className="text-text-muted mt-2">{t("configureNewProvider")}</p>
|
||||
</div>
|
||||
|
||||
{/* Form */}
|
||||
@@ -98,7 +98,7 @@ export default function NewProviderPage() {
|
||||
options={providerOptions}
|
||||
value={formData.provider}
|
||||
onChange={(e) => handleChange("provider", e.target.value)}
|
||||
placeholder="Select a provider"
|
||||
placeholder={t("selectProviderPlaceholder")}
|
||||
error={errors.provider as string}
|
||||
required
|
||||
/>
|
||||
@@ -116,7 +116,7 @@ export default function NewProviderPage() {
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium">{selectedProvider.name}</p>
|
||||
<p className="text-sm text-text-muted">Selected provider</p>
|
||||
<p className="text-sm text-text-muted">{t("selectedProvider")}</p>
|
||||
</div>
|
||||
</Card.Section>
|
||||
)}
|
||||
@@ -124,7 +124,7 @@ export default function NewProviderPage() {
|
||||
{/* Auth Method */}
|
||||
<div className="flex flex-col gap-3">
|
||||
<label className="text-sm font-medium">
|
||||
Authentication Method <span className="text-red-500">*</span>
|
||||
{t("authMethod")} <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<div className="flex gap-3">
|
||||
{authMethodOptions.map((method) => (
|
||||
@@ -152,11 +152,11 @@ export default function NewProviderPage() {
|
||||
<Input
|
||||
label="API Key"
|
||||
type="password"
|
||||
placeholder="Enter your API key"
|
||||
placeholder={t("enterApiKey")}
|
||||
value={formData.apiKey}
|
||||
onChange={(e) => handleChange("apiKey", e.target.value)}
|
||||
error={errors.apiKey as string}
|
||||
hint="Your API key will be encrypted and stored securely."
|
||||
hint={t("apiKeyEncrypted")}
|
||||
required
|
||||
/>
|
||||
)}
|
||||
@@ -164,11 +164,9 @@ export default function NewProviderPage() {
|
||||
{/* OAuth2 Button */}
|
||||
{formData.authMethod === "oauth2" && (
|
||||
<Card.Section className="">
|
||||
<p className="text-sm text-text-muted mb-4">
|
||||
Connect your account using OAuth2 authentication.
|
||||
</p>
|
||||
<p className="text-sm text-text-muted mb-4">{t("connectOAuth2")}</p>
|
||||
<Button type="button" variant="secondary" icon="link">
|
||||
Connect with OAuth2
|
||||
{t("connectWithOAuth2")}
|
||||
</Button>
|
||||
</Card.Section>
|
||||
)}
|
||||
@@ -186,8 +184,8 @@ export default function NewProviderPage() {
|
||||
<Toggle
|
||||
checked={formData.isActive}
|
||||
onChange={(checked) => handleChange("isActive", checked)}
|
||||
label="Active"
|
||||
description="Enable this provider for use in your applications"
|
||||
label={t("activeLabel")}
|
||||
description={t("activeDescription")}
|
||||
/>
|
||||
|
||||
{/* Error Message */}
|
||||
@@ -201,11 +199,11 @@ export default function NewProviderPage() {
|
||||
<div className="flex gap-3 pt-4 border-t border-border">
|
||||
<Link href="/dashboard/providers" className="flex-1">
|
||||
<Button type="button" variant="ghost" fullWidth>
|
||||
Cancel
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
</Link>
|
||||
<Button type="submit" loading={loading} fullWidth className="flex-1">
|
||||
Create Provider
|
||||
{t("createProvider")}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -439,7 +439,21 @@
|
||||
"requestLogs": "Request Logs",
|
||||
"proxyLogs": "Proxy Logs",
|
||||
"auditLog": "Audit Log",
|
||||
"console": "Console"
|
||||
"console": "Console",
|
||||
"auditLogDesc": "Administrative actions and security events",
|
||||
"loading": "Loading...",
|
||||
"refresh": "Refresh",
|
||||
"filterByAction": "Filter by action...",
|
||||
"filterByActor": "Filter by actor...",
|
||||
"search": "Search",
|
||||
"timestamp": "Timestamp",
|
||||
"action": "Action",
|
||||
"actor": "Actor",
|
||||
"target": "Target",
|
||||
"details": "Details",
|
||||
"noEntries": "No audit log entries found",
|
||||
"previous": "Previous",
|
||||
"next": "Next"
|
||||
},
|
||||
"onboarding": {
|
||||
"welcome": "Welcome",
|
||||
@@ -598,7 +612,24 @@
|
||||
"disableRateLimitProtection": "Click to disable rate limit protection",
|
||||
"productionKey": "Production Key",
|
||||
"enterNewApiKey": "Enter new API key",
|
||||
"optional": "Optional"
|
||||
"optional": "Optional",
|
||||
"selectProvider": "Please select a provider",
|
||||
"apiKeyRequired": "API Key is required",
|
||||
"failedCreateProvider": "Failed to create provider",
|
||||
"errorOccurredRetry": "An error occurred. Please try again.",
|
||||
"addNewProvider": "Add New Provider",
|
||||
"configureNewProvider": "Configure a new AI provider to use with your applications.",
|
||||
"selectProviderPlaceholder": "Select a provider",
|
||||
"selectedProvider": "Selected provider",
|
||||
"authMethod": "Authentication Method",
|
||||
"enterApiKey": "Enter your API key",
|
||||
"apiKeyEncrypted": "Your API key will be encrypted and stored securely.",
|
||||
"connectOAuth2": "Connect your account using OAuth2 authentication.",
|
||||
"connectWithOAuth2": "Connect with OAuth2",
|
||||
"activeLabel": "Active",
|
||||
"activeDescription": "Enable this provider for use in your applications",
|
||||
"cancel": "Cancel",
|
||||
"createProvider": "Create Provider"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Settings",
|
||||
|
||||
@@ -439,7 +439,21 @@
|
||||
"requestLogs": "Logs de Requisições",
|
||||
"proxyLogs": "Logs do Proxy",
|
||||
"auditLog": "Log de Auditoria",
|
||||
"console": "Console"
|
||||
"console": "Console",
|
||||
"auditLogDesc": "Ações administrativas e eventos de segurança",
|
||||
"loading": "Carregando...",
|
||||
"refresh": "Atualizar",
|
||||
"filterByAction": "Filtrar por ação...",
|
||||
"filterByActor": "Filtrar por ator...",
|
||||
"search": "Buscar",
|
||||
"timestamp": "Data/Hora",
|
||||
"action": "Ação",
|
||||
"actor": "Ator",
|
||||
"target": "Alvo",
|
||||
"details": "Detalhes",
|
||||
"noEntries": "Nenhuma entrada de log de auditoria encontrada",
|
||||
"previous": "Anterior",
|
||||
"next": "Próximo"
|
||||
},
|
||||
"onboarding": {
|
||||
"welcome": "Bem-vindo",
|
||||
@@ -598,7 +612,24 @@
|
||||
"disableRateLimitProtection": "Clique para desabilitar proteção de limite de taxa",
|
||||
"productionKey": "Chave de Produção",
|
||||
"enterNewApiKey": "Insira a nova chave API",
|
||||
"optional": "Opcional"
|
||||
"optional": "Opcional",
|
||||
"selectProvider": "Por favor, selecione um provedor",
|
||||
"apiKeyRequired": "Chave API é obrigatória",
|
||||
"failedCreateProvider": "Falha ao criar provedor",
|
||||
"errorOccurredRetry": "Ocorreu um erro. Tente novamente.",
|
||||
"addNewProvider": "Adicionar Novo Provedor",
|
||||
"configureNewProvider": "Configure um novo provedor de IA para usar com suas aplicações.",
|
||||
"selectProviderPlaceholder": "Selecione um provedor",
|
||||
"selectedProvider": "Provedor selecionado",
|
||||
"authMethod": "Método de Autenticação",
|
||||
"enterApiKey": "Insira sua chave API",
|
||||
"apiKeyEncrypted": "Sua chave API será criptografada e armazenada com segurança.",
|
||||
"connectOAuth2": "Conecte sua conta usando autenticação OAuth2.",
|
||||
"connectWithOAuth2": "Conectar com OAuth2",
|
||||
"activeLabel": "Ativo",
|
||||
"activeDescription": "Habilitar este provedor para uso em suas aplicações",
|
||||
"cancel": "Cancelar",
|
||||
"createProvider": "Criar Provedor"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Configurações",
|
||||
|
||||
Reference in New Issue
Block a user