mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-05 23:02:10 +03:00
feat(i18n): migrate onboarding wizard
- Onboarding: 37 strings (all wizard steps, labels, messages, buttons) - Expanded en.json and pt-BR.json onboarding namespace with 30+ keys - Dynamic STEPS array from translations for wizard step titles
This commit is contained in:
@@ -2,14 +2,10 @@
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
const STEPS = [
|
||||
{ id: "welcome", title: "Welcome", icon: "waving_hand" },
|
||||
{ id: "security", title: "Security", icon: "lock" },
|
||||
{ id: "provider", title: "Provider", icon: "dns" },
|
||||
{ id: "test", title: "Test", icon: "play_circle" },
|
||||
{ id: "done", title: "Ready!", icon: "check_circle" },
|
||||
];
|
||||
const STEP_IDS = ["welcome", "security", "provider", "test", "done"];
|
||||
const STEP_ICONS = ["waving_hand", "lock", "dns", "play_circle", "check_circle"];
|
||||
|
||||
const COMMON_PROVIDERS = [
|
||||
{ id: "openai", name: "OpenAI", color: "#10A37F" },
|
||||
@@ -22,6 +18,8 @@ const COMMON_PROVIDERS = [
|
||||
|
||||
export default function OnboardingWizard() {
|
||||
const router = useRouter();
|
||||
const t = useTranslations("onboarding");
|
||||
const tc = useTranslations("common");
|
||||
const [step, setStep] = useState(0);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
@@ -60,6 +58,12 @@ export default function OnboardingWizard() {
|
||||
checkSetup();
|
||||
}, [router]);
|
||||
|
||||
const STEPS = STEP_IDS.map((id, i) => ({
|
||||
id,
|
||||
title: t(id === "done" ? "ready" : id),
|
||||
icon: STEP_ICONS[i],
|
||||
}));
|
||||
|
||||
const currentStep = STEPS[step];
|
||||
const isLastStep = step === STEPS.length - 1;
|
||||
|
||||
@@ -88,12 +92,12 @@ export default function OnboardingWizard() {
|
||||
});
|
||||
if (!res.ok) {
|
||||
const data = await res.json().catch(() => ({}));
|
||||
setErrorMessage(data.error || "Failed to set password. Try again.");
|
||||
setErrorMessage(data.error || t("failedSetPassword"));
|
||||
return;
|
||||
}
|
||||
handleNext();
|
||||
} catch {
|
||||
setErrorMessage("Connection error. Please try again.");
|
||||
setErrorMessage(t("connectionError"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -123,18 +127,18 @@ export default function OnboardingWizard() {
|
||||
});
|
||||
if (!res.ok) {
|
||||
const data = await res.json().catch(() => ({}));
|
||||
setErrorMessage(data.error || "Failed to add provider. Try again.");
|
||||
setErrorMessage(data.error || t("failedAddProvider"));
|
||||
return;
|
||||
}
|
||||
handleNext();
|
||||
} catch {
|
||||
setErrorMessage("Connection error. Please try again.");
|
||||
setErrorMessage(t("connectionError"));
|
||||
}
|
||||
};
|
||||
|
||||
const handleTestProvider = async () => {
|
||||
setTestStatus("testing");
|
||||
setTestMessage("Testing connection...");
|
||||
setTestMessage(t("testingConnection"));
|
||||
try {
|
||||
const res = await fetch("/api/providers");
|
||||
if (!res.ok) throw new Error("Failed to fetch");
|
||||
@@ -142,21 +146,21 @@ export default function OnboardingWizard() {
|
||||
const conn = data.connections?.[0];
|
||||
if (!conn) {
|
||||
setTestStatus("error");
|
||||
setTestMessage("No provider found. You can add one from the dashboard later.");
|
||||
setTestMessage(t("noProviderFound"));
|
||||
return;
|
||||
}
|
||||
const testRes = await fetch(`/api/providers/${conn.id}/test`, { method: "POST" });
|
||||
if (testRes.ok) {
|
||||
setTestStatus("success");
|
||||
setTestMessage("Connection successful! Your provider is ready.");
|
||||
setTestMessage(t("connectionSuccessful"));
|
||||
} else {
|
||||
const err = await testRes.json().catch(() => ({}));
|
||||
setTestStatus("error");
|
||||
setTestMessage(err.error || "Test failed, but you can configure this later.");
|
||||
setTestMessage(err.error || t("testFailed"));
|
||||
}
|
||||
} catch {
|
||||
setTestStatus("error");
|
||||
setTestMessage("Could not test right now. You can test from the dashboard.");
|
||||
setTestMessage(t("couldNotTest"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -176,7 +180,7 @@ export default function OnboardingWizard() {
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-bg">
|
||||
<div className="animate-pulse text-text-muted">Loading...</div>
|
||||
<div className="animate-pulse text-text-muted">{tc("loading")}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -234,15 +238,14 @@ export default function OnboardingWizard() {
|
||||
{currentStep.id === "welcome" && (
|
||||
<div className="text-center space-y-4">
|
||||
<p className="text-text-muted">
|
||||
<strong className="text-text-main">OmniRoute</strong> is your local AI API proxy.
|
||||
It routes requests to multiple AI providers with load balancing, failover, and
|
||||
usage tracking.
|
||||
<strong className="text-text-main">OmniRoute</strong>{" "}
|
||||
{t("welcomeDesc").replace("OmniRoute is your local AI API proxy. ", "")}
|
||||
</p>
|
||||
<div className="grid grid-cols-3 gap-3 mt-6">
|
||||
{[
|
||||
{ icon: "swap_horiz", label: "Multi-Provider" },
|
||||
{ icon: "monitoring", label: "Usage Tracking" },
|
||||
{ icon: "shield", label: "API Key Mgmt" },
|
||||
{ icon: "swap_horiz", label: t("multiProvider") },
|
||||
{ icon: "monitoring", label: t("usageTracking") },
|
||||
{ icon: "shield", label: t("apiKeyMgmt") },
|
||||
].map((f) => (
|
||||
<div
|
||||
key={f.icon}
|
||||
@@ -261,9 +264,7 @@ export default function OnboardingWizard() {
|
||||
{/* Security */}
|
||||
{currentStep.id === "security" && (
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-text-muted text-center">
|
||||
Set a password to protect your dashboard, or skip for now.
|
||||
</p>
|
||||
<p className="text-sm text-text-muted text-center">{t("securityDesc")}</p>
|
||||
<label className="flex items-center gap-2 cursor-pointer text-sm text-text-muted">
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -271,26 +272,26 @@ export default function OnboardingWizard() {
|
||||
onChange={(e) => setSkipSecurity(e.target.checked)}
|
||||
className="accent-primary"
|
||||
/>
|
||||
Skip password setup
|
||||
{t("skipPassword")}
|
||||
</label>
|
||||
{!skipSecurity && (
|
||||
<div className="space-y-3">
|
||||
<input
|
||||
type="password"
|
||||
placeholder="Enter password"
|
||||
placeholder={t("enterPassword")}
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
className="w-full px-4 py-2.5 bg-white/[0.04] border border-white/10 rounded-lg text-text-main text-sm placeholder:text-text-muted/50 focus:outline-none focus:ring-2 focus:ring-primary/40"
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
placeholder="Confirm password"
|
||||
placeholder={t("confirmPasswordPlaceholder")}
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
className="w-full px-4 py-2.5 bg-white/[0.04] border border-white/10 rounded-lg text-text-main text-sm placeholder:text-text-muted/50 focus:outline-none focus:ring-2 focus:ring-primary/40"
|
||||
/>
|
||||
{password && confirmPassword && password !== confirmPassword && (
|
||||
<p className="text-xs text-red-400">Passwords do not match</p>
|
||||
<p className="text-xs text-red-400">{t("passwordsMismatch")}</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@@ -300,9 +301,7 @@ export default function OnboardingWizard() {
|
||||
{/* Provider */}
|
||||
{currentStep.id === "provider" && (
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-text-muted text-center">
|
||||
Connect your first AI provider. You can add more later.
|
||||
</p>
|
||||
<p className="text-sm text-text-muted text-center">{t("providerDesc")}</p>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{COMMON_PROVIDERS.map((p) => (
|
||||
<button
|
||||
@@ -325,14 +324,14 @@ export default function OnboardingWizard() {
|
||||
<div className="space-y-3 mt-4">
|
||||
<input
|
||||
type="password"
|
||||
placeholder="API Key (required)"
|
||||
placeholder={t("apiKeyRequired")}
|
||||
value={providerKey}
|
||||
onChange={(e) => setProviderKey(e.target.value)}
|
||||
className="w-full px-4 py-2.5 bg-white/[0.04] border border-white/10 rounded-lg text-text-main text-sm placeholder:text-text-muted/50 focus:outline-none focus:ring-2 focus:ring-primary/40"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Custom URL (optional)"
|
||||
placeholder={t("customUrlOptional")}
|
||||
value={providerUrl}
|
||||
onChange={(e) => setProviderUrl(e.target.value)}
|
||||
className="w-full px-4 py-2.5 bg-white/[0.04] border border-white/10 rounded-lg text-text-main text-sm placeholder:text-text-muted/50 focus:outline-none focus:ring-2 focus:ring-primary/40"
|
||||
@@ -345,15 +344,13 @@ export default function OnboardingWizard() {
|
||||
{/* Test */}
|
||||
{currentStep.id === "test" && (
|
||||
<div className="text-center space-y-4">
|
||||
<p className="text-sm text-text-muted">
|
||||
Let's verify your provider connection works.
|
||||
</p>
|
||||
<p className="text-sm text-text-muted">{t("testDesc")}</p>
|
||||
{testStatus === "idle" && (
|
||||
<button
|
||||
onClick={handleTestProvider}
|
||||
className="px-6 py-2.5 bg-primary rounded-lg text-white font-medium text-sm hover:bg-primary/90 transition-colors cursor-pointer"
|
||||
>
|
||||
Run Connection Test
|
||||
{t("runTest")}
|
||||
</button>
|
||||
)}
|
||||
{testStatus === "testing" && (
|
||||
@@ -380,7 +377,7 @@ export default function OnboardingWizard() {
|
||||
onClick={handleTestProvider}
|
||||
className="text-xs text-text-muted underline cursor-pointer"
|
||||
>
|
||||
Retry
|
||||
{t("retry")}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@@ -390,12 +387,9 @@ export default function OnboardingWizard() {
|
||||
{/* Done */}
|
||||
{currentStep.id === "done" && (
|
||||
<div className="text-center space-y-4">
|
||||
<p className="text-text-muted">
|
||||
You're all set! Your OmniRoute instance is configured and ready to proxy AI
|
||||
requests.
|
||||
</p>
|
||||
<p className="text-text-muted">{t("doneDesc")}</p>
|
||||
<div className="bg-white/[0.03] rounded-xl p-4 border border-white/[0.06] text-left">
|
||||
<p className="text-xs text-text-muted mb-2 font-medium">Your endpoint:</p>
|
||||
<p className="text-xs text-text-muted mb-2 font-medium">{t("yourEndpoint")}</p>
|
||||
<code className="text-sm text-primary">http://localhost:20128/api/v1</code>
|
||||
</div>
|
||||
</div>
|
||||
@@ -410,7 +404,7 @@ export default function OnboardingWizard() {
|
||||
onClick={handleBack}
|
||||
className="px-4 py-2 text-sm text-text-muted hover:text-text-main transition-colors cursor-pointer"
|
||||
>
|
||||
Back
|
||||
{tc("back")}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -420,7 +414,7 @@ export default function OnboardingWizard() {
|
||||
onClick={handleNext}
|
||||
className="px-4 py-2 text-sm text-text-muted hover:text-text-main transition-colors cursor-pointer"
|
||||
>
|
||||
Skip
|
||||
{t("skip")}
|
||||
</button>
|
||||
)}
|
||||
{currentStep.id === "welcome" && (
|
||||
@@ -428,7 +422,7 @@ export default function OnboardingWizard() {
|
||||
onClick={handleNext}
|
||||
className="px-6 py-2.5 bg-primary rounded-lg text-white font-medium text-sm hover:bg-primary/90 transition-colors cursor-pointer"
|
||||
>
|
||||
Get Started
|
||||
{t("getStarted")}
|
||||
</button>
|
||||
)}
|
||||
{currentStep.id === "security" && (
|
||||
@@ -437,7 +431,7 @@ export default function OnboardingWizard() {
|
||||
disabled={!skipSecurity && (!password || password !== confirmPassword)}
|
||||
className="px-6 py-2.5 bg-primary rounded-lg text-white font-medium text-sm hover:bg-primary/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer"
|
||||
>
|
||||
{skipSecurity ? "Skip & Continue" : "Set Password"}
|
||||
{skipSecurity ? t("skipAndContinue") : t("setPassword")}
|
||||
</button>
|
||||
)}
|
||||
{currentStep.id === "provider" && (
|
||||
@@ -446,7 +440,7 @@ export default function OnboardingWizard() {
|
||||
disabled={!selectedProvider || !providerKey}
|
||||
className="px-6 py-2.5 bg-primary rounded-lg text-white font-medium text-sm hover:bg-primary/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer"
|
||||
>
|
||||
Add Provider
|
||||
{t("addProvider")}
|
||||
</button>
|
||||
)}
|
||||
{currentStep.id === "test" && (
|
||||
@@ -454,7 +448,7 @@ export default function OnboardingWizard() {
|
||||
onClick={handleNext}
|
||||
className="px-6 py-2.5 bg-primary rounded-lg text-white font-medium text-sm hover:bg-primary/90 transition-colors cursor-pointer"
|
||||
>
|
||||
{testStatus === "success" ? "Continue" : "Skip"}
|
||||
{testStatus === "success" ? t("continue") : t("skip")}
|
||||
</button>
|
||||
)}
|
||||
{isLastStep && (
|
||||
@@ -462,7 +456,7 @@ export default function OnboardingWizard() {
|
||||
onClick={handleFinish}
|
||||
className="px-6 py-2.5 bg-green-500 rounded-lg text-white font-medium text-sm hover:bg-green-500/90 transition-colors cursor-pointer"
|
||||
>
|
||||
Go to Dashboard →
|
||||
{t("goToDashboard")}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -476,7 +470,7 @@ export default function OnboardingWizard() {
|
||||
onClick={handleFinish}
|
||||
className="text-xs text-text-muted/60 hover:text-text-muted transition-colors cursor-pointer"
|
||||
>
|
||||
Skip wizard entirely
|
||||
{t("skipWizard")}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -301,15 +301,46 @@
|
||||
"console": "Console"
|
||||
},
|
||||
"onboarding": {
|
||||
"welcome": "Welcome to OmniRoute",
|
||||
"welcome": "Welcome",
|
||||
"security": "Security",
|
||||
"test": "Test",
|
||||
"ready": "Ready!",
|
||||
"setPassword": "Set Password",
|
||||
"addProvider": "Add your first provider",
|
||||
"getStarted": "Get Started",
|
||||
"skip": "Skip",
|
||||
"skipWizard": "Skip wizard entirely",
|
||||
"skipPassword": "Skip password setup",
|
||||
"skipAndContinue": "Skip & Continue",
|
||||
"passwordLabel": "Password",
|
||||
"confirmPassword": "Confirm Password",
|
||||
"enterPassword": "Enter password",
|
||||
"confirmPasswordPlaceholder": "Confirm password",
|
||||
"passwordsMismatch": "Passwords do not match",
|
||||
"setupComplete": "Setup Complete!",
|
||||
"goToDashboard": "Go to Dashboard"
|
||||
"goToDashboard": "Go to Dashboard \u2192",
|
||||
"welcomeDesc": "OmniRoute is your local AI API proxy. It routes requests to multiple AI providers with load balancing, failover, and usage tracking.",
|
||||
"multiProvider": "Multi-Provider",
|
||||
"usageTracking": "Usage Tracking",
|
||||
"apiKeyMgmt": "API Key Mgmt",
|
||||
"securityDesc": "Set a password to protect your dashboard, or skip for now.",
|
||||
"providerDesc": "Connect your first AI provider. You can add more later.",
|
||||
"apiKeyRequired": "API Key (required)",
|
||||
"customUrlOptional": "Custom URL (optional)",
|
||||
"testDesc": "Let's verify your provider connection works.",
|
||||
"runTest": "Run Connection Test",
|
||||
"testingConnection": "Testing connection...",
|
||||
"connectionSuccessful": "Connection successful! Your provider is ready.",
|
||||
"noProviderFound": "No provider found. You can add one from the dashboard later.",
|
||||
"testFailed": "Test failed, but you can configure this later.",
|
||||
"couldNotTest": "Could not test right now. You can test from the dashboard.",
|
||||
"doneDesc": "You're all set! Your OmniRoute instance is configured and ready to proxy AI requests.",
|
||||
"yourEndpoint": "Your endpoint:",
|
||||
"continue": "Continue",
|
||||
"retry": "Retry",
|
||||
"failedSetPassword": "Failed to set password. Try again.",
|
||||
"failedAddProvider": "Failed to add provider. Try again.",
|
||||
"connectionError": "Connection error. Please try again."
|
||||
},
|
||||
"providers": {
|
||||
"title": "Providers",
|
||||
|
||||
@@ -301,15 +301,46 @@
|
||||
"console": "Console"
|
||||
},
|
||||
"onboarding": {
|
||||
"welcome": "Bem-vindo ao OmniRoute",
|
||||
"welcome": "Bem-vindo",
|
||||
"security": "Segurança",
|
||||
"test": "Teste",
|
||||
"ready": "Pronto!",
|
||||
"setPassword": "Definir Senha",
|
||||
"addProvider": "Adicione seu primeiro provedor",
|
||||
"getStarted": "Começar",
|
||||
"skip": "Pular",
|
||||
"skipWizard": "Pular assistente completamente",
|
||||
"skipPassword": "Pular configuração de senha",
|
||||
"skipAndContinue": "Pular e Continuar",
|
||||
"passwordLabel": "Senha",
|
||||
"confirmPassword": "Confirmar Senha",
|
||||
"enterPassword": "Digite a senha",
|
||||
"confirmPasswordPlaceholder": "Confirme a senha",
|
||||
"passwordsMismatch": "As senhas não coincidem",
|
||||
"setupComplete": "Configuração Concluída!",
|
||||
"goToDashboard": "Ir para o Painel"
|
||||
"goToDashboard": "Ir para o Painel \u2192",
|
||||
"welcomeDesc": "OmniRoute é seu proxy local de API de IA. Ele roteia requisições para múltiplos provedores de IA com balanceamento de carga, failover e rastreamento de uso.",
|
||||
"multiProvider": "Multi-Provedor",
|
||||
"usageTracking": "Rastreamento de Uso",
|
||||
"apiKeyMgmt": "Ger. de Chaves API",
|
||||
"securityDesc": "Defina uma senha para proteger seu painel, ou pule por enquanto.",
|
||||
"providerDesc": "Conecte seu primeiro provedor de IA. Você pode adicionar mais depois.",
|
||||
"apiKeyRequired": "Chave de API (obrigatório)",
|
||||
"customUrlOptional": "URL personalizada (opcional)",
|
||||
"testDesc": "Vamos verificar se a conexão com seu provedor funciona.",
|
||||
"runTest": "Executar Teste de Conexão",
|
||||
"testingConnection": "Testando conexão...",
|
||||
"connectionSuccessful": "Conexão bem-sucedida! Seu provedor está pronto.",
|
||||
"noProviderFound": "Nenhum provedor encontrado. Você pode adicionar um pelo painel depois.",
|
||||
"testFailed": "Teste falhou, mas você pode configurar isso depois.",
|
||||
"couldNotTest": "Não foi possível testar agora. Você pode testar pelo painel.",
|
||||
"doneDesc": "Tudo pronto! Sua instância OmniRoute está configurada e pronta para rotear requisições de IA.",
|
||||
"yourEndpoint": "Seu endpoint:",
|
||||
"continue": "Continuar",
|
||||
"retry": "Tentar Novamente",
|
||||
"failedSetPassword": "Falha ao definir senha. Tente novamente.",
|
||||
"failedAddProvider": "Falha ao adicionar provedor. Tente novamente.",
|
||||
"connectionError": "Erro de conexão. Tente novamente."
|
||||
},
|
||||
"providers": {
|
||||
"title": "Provedores",
|
||||
|
||||
Reference in New Issue
Block a user