diff --git a/frontend/app/admin/dashboard/page.tsx b/frontend/app/admin/dashboard/page.tsx index 0af115857..351da9541 100644 --- a/frontend/app/admin/dashboard/page.tsx +++ b/frontend/app/admin/dashboard/page.tsx @@ -6,8 +6,10 @@ import { TVLChart } from "@/components/admin/metrics/TVLChart"; import { AssetDistributionChart } from "@/components/admin/metrics/AssetDistributionChart"; import { adminAPI, AdminMetrics } from "@/app/lib/api/admin"; import { mockAdminMetrics } from "@/lib/mockAdminUsers"; +import { useTranslations } from "next-intl"; export default function AdminDashboardPage() { + const t = useTranslations("admin"); const [metrics, setMetrics] = useState(null); const [loading, setLoading] = useState(true); const [usingMock, setUsingMock] = useState(false); @@ -31,14 +33,17 @@ export default function AdminDashboardPage() {
-

Admin Dashboard

-

Platform-wide metrics and asset overview

+

{t("dashboard")}

+

{t("dashboardDesc")}

{usingMock && (
- Backend unavailable — showing mock data derived from local user records. In production, metrics are fetched from{" "} - /api/admin/metrics. + {t("mockDataWarning")}{" "} + + In production, metrics are fetched from{" "} + /api/admin/metrics. +
)} diff --git a/frontend/app/admin/login/page.tsx b/frontend/app/admin/login/page.tsx index 450f01058..3e7898875 100644 --- a/frontend/app/admin/login/page.tsx +++ b/frontend/app/admin/login/page.tsx @@ -1,9 +1,11 @@ "use client"; import { useState } from "react"; import { useAdminAuth } from "@/context/AdminAuthContext"; +import { useTranslations } from "next-intl"; export default function AdminLoginPage() { const { login } = useAdminAuth(); + const t = useTranslations("admin"); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [error, setError] = useState(""); @@ -26,14 +28,14 @@ export default function AdminLoginPage() {

InheritX

-

Admin Portal

+

{t("adminPortal")}

- Sign in to continue + {t("signIn")}

- +
- + - {isLoading ? "Signing in..." : "Sign in"} + {isLoading ? t("signingIn") : t("signInButton")}
diff --git a/frontend/app/admin/users/page.tsx b/frontend/app/admin/users/page.tsx index 54519dd0c..88261cb56 100644 --- a/frontend/app/admin/users/page.tsx +++ b/frontend/app/admin/users/page.tsx @@ -5,10 +5,12 @@ import { AdminUser, KYCStatus } from "@/lib/adminTypes"; import { SearchFilterBar } from "@/components/admin/SearchFilterBar"; import { UserManagementTable } from "@/components/admin/UserManagementTable"; import { Pagination } from "@/components/admin/Pagination"; +import { useTranslations } from "next-intl"; const PAGE_SIZE = 10; export default function AdminUsersPage() { + const t = useTranslations("admin"); const [users, setUsers] = useState(mockUsers); const [search, setSearch] = useState(""); const [kycFilter, setKycFilter] = useState("all"); @@ -71,29 +73,20 @@ export default function AdminUsersPage() { {/* Header */}

- User Management + {t("userManagement")}

- Manage users, KYC approvals, and active plans. + {t("userManagementDesc")}

{/* Stats row */}
{[ - { label: "Total Users", value: users.length }, - { - label: "KYC Pending", - value: users.filter((u) => u.kycStatus === "pending").length, - }, - { - label: "Active Users", - value: users.filter((u) => u.status === "active").length, - }, - { - label: "Suspended", - value: users.filter((u) => u.status === "suspended").length, - }, + { label: t("totalUsers"), value: users.length }, + { label: t("kycPending"), value: users.filter((u) => u.kycStatus === "pending").length }, + { label: t("activeUsers"), value: users.filter((u) => u.status === "active").length }, + { label: t("suspended"), value: users.filter((u) => u.status === "suspended").length }, ].map((stat) => (
- Showing {paginated.length} of {filtered.length} users + {t("showing")} {paginated.length} {t("of")} {filtered.length} {t("users")}

{/* Table */} diff --git a/frontend/app/asset-owner/page.tsx b/frontend/app/asset-owner/page.tsx index 8efad2ee1..d10fecf43 100644 --- a/frontend/app/asset-owner/page.tsx +++ b/frontend/app/asset-owner/page.tsx @@ -1,24 +1,24 @@ "use client"; import { useWallet } from "@/context/WalletContext"; import { formatAddress } from "@/util/address"; +import { useTranslations } from "next-intl"; export default function AssetOwnerPage() { const { isConnected, address, openModal } = useWallet(); + const t = useTranslations("assetOwner"); return (
{/* Page heading */}
-

Overview

-

- Welcome to your InheritX dashboard. -

+

{t("overview")}

+

{t("welcomeDesc")}

{/* Wallet status card */}

- Wallet Status + {t("walletStatus")}

{isConnected && address ? ( @@ -26,11 +26,11 @@ export default function AssetOwnerPage() {
- Connected + {t("connected")}
-

Wallet Address

+

{t("walletAddress")}

{formatAddress(address)}

@@ -40,13 +40,13 @@ export default function AssetOwnerPage() {
- Not connected + {t("notConnected")}
)} diff --git a/frontend/app/components/Navbar.tsx b/frontend/app/components/Navbar.tsx index 117a3faa2..6117c91c4 100644 --- a/frontend/app/components/Navbar.tsx +++ b/frontend/app/components/Navbar.tsx @@ -1,10 +1,15 @@ +"use client"; + import React, { useState, useEffect } from "react"; import Image from "next/image"; -import { ArrowDownRight, Globe, Shield, Zap, Menu, X } from "lucide-react"; +import { Menu, X } from "lucide-react"; import Link from "next/link"; import { ConnectButton } from "@/components/ConnectButton"; +import { LanguageSwitcher } from "@/components/LanguageSwitcher"; +import { useTranslations } from "next-intl"; const Navbar = () => { + const t = useTranslations("navbar"); const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const [isScrolled, setIsScrolled] = useState(false); @@ -18,6 +23,7 @@ const Navbar = () => { }, []); const closeMenu = () => setIsMobileMenuOpen(false); + return (
{ href="/" className="hover:text-cyan-400 transition-colors focus-visible:outline-offset-2 focus-visible:outline-2 focus-visible:outline-cyan-400 rounded-sm px-1" > - Home + {t("home")} - How it Works + {t("howItWorks")} - FAQs + {t("faqs")} - Contact + {t("contact")}
@@ -92,40 +98,39 @@ const Navbar = () => { onClick={closeMenu} className="text-slate-300 hover:text-cyan-400 py-2 focus-visible:outline-offset-2 focus-visible:outline-2 focus-visible:outline-cyan-400 rounded-sm px-2 uppercase" > - Home + {t("home")} - How it Works + {t("howItWorks")} - FAQs + {t("faqs")} - Contact + {t("contact")} - - +
)} - +
+
+ +
+ +
); diff --git a/frontend/app/components/landing/AboutSection.tsx b/frontend/app/components/landing/AboutSection.tsx index 80e5c2b97..476b12754 100644 --- a/frontend/app/components/landing/AboutSection.tsx +++ b/frontend/app/components/landing/AboutSection.tsx @@ -1,6 +1,11 @@ +"use client"; + import Image from "next/image"; +import { useTranslations } from "next-intl"; export default function AboutSection() { + const t = useTranslations("landing"); + return (

- What is InheritX? + {t("aboutTitle")}

- Without roots, nothing grows. + {t("aboutTagline")}

- InheritX simplifies digital wealth transfer by introducing yield-generating - inheritance plans with multi-beneficiary allocations. Your locked assets accrue interest - continuously, maximizing the value passed to your heirs. + {t("aboutBody1")}

- Through integrated Stellar Anchors, your heirs receive payouts directly - in their local fiat bank accounts or mobile money wallets, bypassing any Web3 complexity. + {t("aboutBody2")}
{/* Decorative tree-like background glow */} diff --git a/frontend/app/components/landing/BenefitsSection.tsx b/frontend/app/components/landing/BenefitsSection.tsx index 6c52281ae..01dec09f6 100644 --- a/frontend/app/components/landing/BenefitsSection.tsx +++ b/frontend/app/components/landing/BenefitsSection.tsx @@ -1,4 +1,7 @@ +"use client"; + import { ThumbsUp, ShieldCheck, Settings, Zap, ArrowUpRight } from "lucide-react"; +import { useTranslations } from "next-intl"; const FeatureCard = ({ icon: Icon, @@ -17,9 +20,7 @@ const FeatureCard = ({ }) => (
@@ -30,6 +31,8 @@ const FeatureCard = ({ ); export default function BenefitsSection() { + const t = useTranslations("landing"); + return (

- Benefits Of InheritX + {t("benefitsTitle")}

- Here's why you should choose us + {t("benefitsSubtitle")}

- InheritX - helps you plan and allocate your digital wealth with maximum yield generation, - ensuring your capital grows until the moment of transfer. + InheritX + {t("benefitsBody1")}

- Distribute to any number of heirs via customizable percentage splits, and settle - automatically in local fiat cash straight to their local bank accounts using Stellar anchors. + {t("benefitsBody2")}

- {/* The Grid Content */}
@@ -97,7 +97,7 @@ export default function BenefitsSection() { className="flex flex-row justify-center items-center gap-4 bg-cyan-400 text-black px-8 py-2 rounded-t-[8px] rounded-b-[16px] cursor-pointer transition-all duration-300 hover:bg-cyan-300 active:scale-95 focus-visible:outline-offset-2 focus-visible:outline-2 focus-visible:outline-cyan-400 font-semibold" aria-label="Create your inheritance plan" > - CREATE YOUR PLAN + {t("createYourPlan")}
diff --git a/frontend/app/components/landing/HowItWorksSection.tsx b/frontend/app/components/landing/HowItWorksSection.tsx index d0259784f..154700a6f 100644 --- a/frontend/app/components/landing/HowItWorksSection.tsx +++ b/frontend/app/components/landing/HowItWorksSection.tsx @@ -1,7 +1,12 @@ +"use client"; + import { TreePine, Sprout, Leaf, ArrowUpRight } from "lucide-react"; import Image from "next/image"; +import { useTranslations } from "next-intl"; export default function HowItWorksSection() { + const t = useTranslations("landing"); + return (
- How It Works + {t("howItWorksTitle")} - Here's how your legacy flows + {t("howItWorksSubtitle")}
{/* Step 1 - Bottom Position */}
- {/* Connecting dot on line */}
- - {/* Content below line */}

- 1. Deposit & Grow + {t("step1Title")}

- Fund Your Plan and Opt-In to Earn Continuous Yield. + {t("step1Desc")}

{/* Step 2 - Top Position */}
- {/* Connecting dot on line */}
- - {/* Content above line */}

- Add Multiple Beneficiaries and Setup Allocation Splits. + {t("step2Desc")}

- 2. Configure Mass Payout + {t("step2Title")}

{/* Step 3 - Bottom Position */}
- {/* Connecting dot on line */}
- - {/* Content below line */}

- 3. Direct Fiat off-ramp + {t("step3Title")}

- Receive directly in local fiat cash via Stellar Anchors. + {t("step3Desc")}

@@ -143,26 +139,20 @@ export default function HowItWorksSection() {

- Why this works: + {t("whyWorksTitle")}

  • - - - Starts with purpose — “Helping your legacy reach - the people who matter” sets an emotional tone - + + {t("whyWorks1")}
  • - One short paragraph — easy to skim. + {t("whyWorks2")}
  • - - Metaphor tie-in — connects to your tree concept in a - natural way. - + {t("whyWorks3")}
@@ -171,7 +161,7 @@ export default function HowItWorksSection() { className="flex flex-row justify-center items-center gap-4 bg-cyan-400 text-black px-8 py-2 rounded-t-[8px] rounded-b-[16px] cursor-pointer transition-all duration-300 hover:bg-cyan-300 active:scale-95 focus-visible:outline-offset-2 focus-visible:outline-2 focus-visible:outline-cyan-400 font-semibold" aria-label="Get started with InheritX" > - GET STARTED + {t("getStarted")}
diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx index a4b6214f7..a7a1a71ee 100644 --- a/frontend/app/layout.tsx +++ b/frontend/app/layout.tsx @@ -5,6 +5,8 @@ import { WalletProvider } from "@/context/WalletContext"; import { AdminAuthProvider } from "@/context/AdminAuthContext"; import { KYCProvider } from "@/context/KYCContext"; import { WalletModal } from "@/components/WalletModal"; +import { NextIntlClientProvider } from "next-intl"; +import { getLocale, getMessages } from "next-intl/server"; import { OrganizationStructuredData, @@ -79,19 +81,16 @@ export const viewport: Viewport = { import { Vitals } from "@/app/components/Vitals"; -export default function RootLayout({ +export default async function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { - // We need to import these dynamically or ensure "use client" wraps them if passing context - // But Layout is a Server Component by default in Next.js App Router. - // We cannot use Context directly in a Server Component layout. - // So we need a "Providers" client component or assume WalletProvider is "use client". - // WalletProvider IS "use client". + const locale = await getLocale(); + const messages = await getMessages(); return ( - + @@ -110,14 +109,16 @@ export default function RootLayout({ suppressHydrationWarning > - - - -
{children}
- -
-
-
+ + + + +
{children}
+ +
+
+
+
); diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx index 670ac6bf5..3fe29b7df 100644 --- a/frontend/app/page.tsx +++ b/frontend/app/page.tsx @@ -5,8 +5,10 @@ import Image from "next/image"; import dynamic from "next/dynamic"; import { ArrowUpRight, Menu, X } from "lucide-react"; import { ConnectButton } from "@/components/ConnectButton"; +import { LanguageSwitcher } from "@/components/LanguageSwitcher"; import Link from "next/link"; import Footer from "./components/Footer"; +import { useTranslations } from "next-intl"; // --- Dynamic Imports for Performance --- @@ -28,6 +30,9 @@ const BenefitsSection = dynamic(() => import("./components/landing/BenefitsSecti // --- Main Page --- export default function InheritXLanding() { + const t = useTranslations("landing"); + const tNav = useTranslations("navbar"); + const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const [isScrolled, setIsScrolled] = useState(false); @@ -71,25 +76,25 @@ export default function InheritXLanding() { href="#hero" className="hover:text-cyan-400 transition-colors focus-visible:outline-offset-2 focus-visible:outline-2 focus-visible:outline-cyan-400 rounded-sm px-1" > - Home + {tNav("home")} - How it Works + {tNav("howItWorks")} - FAQs + {tNav("faqs")} - Contact + {tNav("contact")}
@@ -115,33 +120,35 @@ export default function InheritXLanding() { onClick={closeMenu} className="text-slate-300 hover:text-cyan-400 py-2 focus-visible:outline-offset-2 focus-visible:outline-2 focus-visible:outline-cyan-400 rounded-sm px-2 uppercase" > - Home + {tNav("home")} - How it Works + {tNav("howItWorks")} - FAQs + {tNav("faqs")} - Contact + {tNav("contact")} +
)} -
+
+
@@ -151,22 +158,20 @@ export default function InheritXLanding() {

- Yield-bearing, fiat-native
- digital inheritance on Stellar. + {t("heroTitle")}

- InheritX helps you secure yield-earning inheritance plans with mass payouts -
and direct local fiat off-ramping to bank accounts or mobile money via Stellar anchors. + {t("heroSubtitle")}

diff --git a/frontend/components/LanguageSwitcher.tsx b/frontend/components/LanguageSwitcher.tsx index 10da346d7..61874d52f 100644 --- a/frontend/components/LanguageSwitcher.tsx +++ b/frontend/components/LanguageSwitcher.tsx @@ -1,32 +1,42 @@ "use client"; -import { useAppStore } from "@/store/index"; -import { locales, localeNames, type Locale } from "@/i18n/config"; +import { useLocale } from "next-intl"; import { useRouter, usePathname } from "next/navigation"; +import { useTransition } from "react"; +import { locales, localeNames, type Locale } from "@/i18n/config"; export function LanguageSwitcher() { - const locale = useAppStore((s) => s.locale); - const setLocale = useAppStore((s) => s.setLocale); + const locale = useLocale(); const router = useRouter(); const pathname = usePathname(); + const [isPending, startTransition] = useTransition(); const handleChange = (newLocale: Locale) => { - setLocale(newLocale); - // next-intl locale prefix routing: replace current locale segment - const segments = pathname.split("/"); - if (locales.includes(segments[1] as Locale)) { - segments[1] = newLocale; - } else { - segments.splice(1, 0, newLocale); - } - router.push(segments.join("/") || "/"); + // next-intl middleware reads NEXT_LOCALE cookie for preference persistence. + // Setting it here ensures the selection survives hard reloads and new tabs. + document.cookie = `NEXT_LOCALE=${newLocale}; path=/; max-age=31536000; SameSite=Lax`; + + startTransition(() => { + // Strip any existing locale prefix then push the new one. + // With localePrefix "as-needed", the default locale ("en") has no prefix. + const segments = pathname.split("/").filter(Boolean); + if (locales.includes(segments[0] as Locale)) { + segments.shift(); + } + const newPath = + newLocale === "en" + ? "/" + segments.join("/") + : `/${newLocale}/${segments.join("/")}`; + router.push(newPath || "/"); + }); }; return (