From 6738f38f2366b440a905e53a010ac0d666fb3ad0 Mon Sep 17 00:00:00 2001 From: Nwokedi Chinelo Date: Sat, 27 Jun 2026 21:53:15 +0100 Subject: [PATCH] fix: i18n disconnected state, tl.json translations, e2e CI job, dedup criticalFlows closes #1241 closes #1242 closes #1250 closes #1251 --- .github/workflows/ci.yml | 32 ++++++++ frontend/e2e/criticalFlows.spec.ts | 122 +---------------------------- frontend/messages/en.json | 17 ++++ frontend/messages/es.json | 17 ++++ frontend/messages/tl.json | 75 +++++++++++------- frontend/src/app/[locale]/page.tsx | 33 ++++---- 6 files changed, 133 insertions(+), 163 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 364d6181..fc2a2847 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -162,6 +162,38 @@ jobs: run: npm run build working-directory: frontend + e2e: + needs: [supply-chain-audit, frontend] + runs-on: ubuntu-latest + if: github.event_name == 'push' || contains(github.event.pull_request.changed_files, 'frontend/') + steps: + - uses: actions/checkout@v4 + - name: Use Node.js 20 + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: "npm" + cache-dependency-path: frontend/package-lock.json + - name: Install dependencies + run: npm ci + working-directory: frontend + - name: Install Playwright browsers + run: npx playwright install --with-deps chromium + working-directory: frontend + - name: Run e2e tests + run: npm run test:e2e + working-directory: frontend + env: + NODE_ENV: test + CI: true + - name: Upload Playwright report + if: failure() + uses: actions/upload-artifact@v4 + with: + name: playwright-report + path: frontend/playwright-report/ + retention-days: 7 + env-docs-check: runs-on: ubuntu-latest steps: diff --git a/frontend/e2e/criticalFlows.spec.ts b/frontend/e2e/criticalFlows.spec.ts index b2624f42..bc055555 100644 --- a/frontend/e2e/criticalFlows.spec.ts +++ b/frontend/e2e/criticalFlows.spec.ts @@ -59,69 +59,8 @@ test.beforeEach(async ({ page }: { page: Page }) => { }); }); -// ─── Flow 1: Loan Wizard ─────────────────────────────────────────────────────── - -test("Borrow: Connect wallet → Request Loan → Wizard steps", async ({ page }: { page: Page }) => { - // Mock Loan Config (min score, etc) - await page.route("**/api/loans/config", async (route: Route) => { - await route.fulfill({ - status: 200, - contentType: "application/json", - body: JSON.stringify({ - success: true, - data: { minScore: 500, maxAmount: 10000, interestRatePercent: 8 }, - }), - }); - }); - - // Mock User Credit Score - await page.route("**/api/score/*", async (route: Route) => { - await route.fulfill({ - status: 200, - contentType: "application/json", - body: JSON.stringify({ success: true, score: 715 }), - }); - }); - - await page.goto("/en"); // Explicitly go to English locale - - // Navigate to Loan Wizard (via Apply button in quick actions) - // Text matches HomePage.quickActions.applyLoan in en.json - const applyBtn = page.getByRole("button", { name: /Apply for Loan/i }); - await applyBtn.waitFor(); - await applyBtn.click(); - - // Step 1: Amount & Asset - await expect(page.locator("text=Loan Amount")).toBeVisible(); - await page.selectOption('select[name="asset"]', "USDC"); - await page.fill('input[placeholder="0.00"]', "1000"); - const continueToCollateral = page.getByRole("button", { name: /Continue to Collateral/i }); - await continueToCollateral.click(); - - // Step 2: Collateral & NFT Link - await expect(page.locator("text=Collateral & NFT Link")).toBeVisible(); - await page.click('input[type="checkbox"]'); // Accept terms - const continueToSignature = page.getByRole("button", { name: /Continue to Signature/i }); - await continueToSignature.click(); - - // Step 3: Transaction Signature - await expect(page.locator("text=Ready to Sign")).toBeVisible(); - - // Mock creation request - await page.route("**/api/loans", async (route: Route) => { - await route.fulfill({ - status: 200, - contentType: "application/json", - body: JSON.stringify({ id: "loan_123", status: "pending", txHash: "tx_abc" }), - }); - }); - - await page.click('button:has-text("Sign & Submit Application")'); - - // Success view - await expect(page.locator("text=Application Submitted")).toBeVisible(); - await expect(page.locator("text=Reviewing your application")).toBeVisible(); -}); +// Loan wizard and repay flows removed: covered by borrower-loan-flow.spec.ts +// and borrower-repay-flow.spec.ts with a single consistent set of route mocks. // ─── Flow 2: Lending Pool ────────────────────────────────────────────────────── @@ -168,63 +107,6 @@ test("Lend: Deposit funds → View updated pool stats", async ({ page }: { page: await expect(page.locator("text=1,002,500")).toBeVisible(); }); -// ─── Flow 3: Repayment ───────────────────────────────────────────────────────── - -test("Borrower: Repay loan → Confirm transaction → Check status update", async ({ - page, -}: { - page: Page; -}) => { - // Mock existing loans for borrower - await page.route("**/api/loans/borrower/**", async (route: Route) => { - await route.fulfill({ - status: 200, - contentType: "application/json", - body: JSON.stringify({ - success: true, - data: { - borrower: MOCK_ADDRESS, - loans: [ - { - id: 123, - principal: 1000, - totalOwed: 500, - status: "active", - nextPaymentDeadline: "2026-12-31T00:00:00Z", - }, - ], - }, - }), - }); - }); - - await page.goto("/en"); - - // Click repay on the specific loan (assuming dashboard has a 'Repay' button in the loans list or card) - const repayBtn = page.getByRole("button", { name: "Repay" }).first(); - await repayBtn.click(); - - // Perform repayment - await expect(page.locator("text=Repayment Amount")).toBeVisible(); - await page.fill('input[type="number"]', "500"); - - // Mock repayment finish - await page.route("**/api/loans/123/repay", async (route: Route) => { - await route.fulfill({ - status: 200, - contentType: "application/json", - body: JSON.stringify({ success: true, txHash: "tx_repay" }), - }); - }); - - await page.click('button:has-text("Review Repayment")'); - await page.click('button:has-text("Confirm Payment")'); // assuming it's in the preview modal - - // Success message - await expect(page.locator("text=Progress")).toBeVisible(); // transaction progress - await expect(page.locator("text=Repayment Successful")).toBeVisible(); -}); - // ─── Flow 4: Remittance History ──────────────────────────────────────────────── test("Remittance: View history", async ({ page }: { page: Page }) => { diff --git a/frontend/messages/en.json b/frontend/messages/en.json index 86d92f43..a53ba317 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -26,6 +26,23 @@ "title": "Community Outreach", "description": "New borrowers in Ghana are looking for micro-loans for agricultural tools. Help grow the ecosystem!", "explore": "Explore Opportunities" + }, + "disconnected": { + "heading": "Dashboard", + "subheading": "Welcome to RemitLend. Please connect your wallet to view your portfolio.", + "walletNotConnected": "Wallet Not Connected", + "connectPrompt": "Connect your wallet to analyze your on-chain credit score, manage active positions, and track cross-border remittances.", + "connectButton": "Connect Wallet" + }, + "reminder": { + "due": "Repayment due in {hours}h — Loan #{loanId}", + "repayNow": "Repay now", + "dismiss": "Dismiss repayment reminder" + }, + "creditScore": { + "label": "Credit Score", + "tooltip": "Credit Score: An on-chain signal of repayment reliability. Higher scores can unlock better terms over time.", + "tooltipLabel": "Credit score info" } }, "ActivityPage": { diff --git a/frontend/messages/es.json b/frontend/messages/es.json index f8cf9aea..3dedac68 100644 --- a/frontend/messages/es.json +++ b/frontend/messages/es.json @@ -26,6 +26,23 @@ "title": "Alcance comunitario", "description": "Nuevos prestatarios en Ghana buscan micropréstamos para herramientas agrícolas. ¡Ayuda a hacer crecer el ecosistema!", "explore": "Explorar oportunidades" + }, + "disconnected": { + "heading": "Tablero", + "subheading": "Bienvenido a RemitLend. Por favor conecta tu billetera para ver tu portafolio.", + "walletNotConnected": "Billetera no conectada", + "connectPrompt": "Conecta tu billetera para analizar tu puntuación crediticia on-chain, gestionar posiciones activas y rastrear remesas internacionales.", + "connectButton": "Conectar billetera" + }, + "reminder": { + "due": "Pago pendiente en {hours}h — Préstamo #{loanId}", + "repayNow": "Pagar ahora", + "dismiss": "Descartar recordatorio de pago" + }, + "creditScore": { + "label": "Puntuación crediticia", + "tooltip": "Puntuación crediticia: una señal on-chain de confiabilidad de pago. Las puntuaciones más altas pueden desbloquear mejores condiciones con el tiempo.", + "tooltipLabel": "Información sobre puntuación crediticia" } }, "ActivityPage": { diff --git a/frontend/messages/tl.json b/frontend/messages/tl.json index 9f13d911..8e964d43 100644 --- a/frontend/messages/tl.json +++ b/frontend/messages/tl.json @@ -5,10 +5,10 @@ "getStarted": "Magsimula Na", "learnMore": "Matuto Nang Higit Pa", "stats": { - "netWorth": "Net Worth", + "netWorth": "Netong Halaga", "activeLoans": "Mga aktibong loan", "totalRemitted": "Kabuuang naipadala", - "yieldApy": "Yield (APY)" + "yieldApy": "Kita (APY)" }, "activity": { "title": "Kamakailang aktibidad", @@ -23,9 +23,26 @@ "sendRemittanceDesc": "Maglipat ng pondo sa buong mundo" }, "outreach": { - "title": "Community Outreach", + "title": "Pakikilahok sa Komunidad", "description": "Ang mga bagong borrower sa Ghana ay naghahanap ng mga micro-loan para sa mga kagamitang pang-agrikultura. Tulungang palaguin ang ecosystem!", "explore": "Galugarin ang mga pagkakataon" + }, + "disconnected": { + "heading": "Dashboard", + "subheading": "Maligayang pagdating sa RemitLend. Ikonekta ang iyong wallet upang makita ang iyong portfolio.", + "walletNotConnected": "Hindi Nakakonekta ang Wallet", + "connectPrompt": "Ikonekta ang iyong wallet upang suriin ang iyong on-chain credit score, pamahalaan ang mga aktibong posisyon, at subaybayan ang mga cross-border na remittance.", + "connectButton": "Ikonekta ang Wallet" + }, + "reminder": { + "due": "Dapat bayaran sa loob ng {hours}h — Loan #{loanId}", + "repayNow": "Bayaran ngayon", + "dismiss": "Itago ang paalala ng pagbabayad" + }, + "creditScore": { + "label": "Credit Score", + "tooltip": "Credit Score: Isang on-chain na senyales ng pagiging mapagkakatiwalaan sa pagbabayad. Ang mas mataas na score ay maaaring mag-unlock ng mas magagandang kondisyon sa paglipas ng panahon.", + "tooltipLabel": "Impormasyon tungkol sa credit score" } }, "ActivityPage": { @@ -46,7 +63,7 @@ "completed": "Nakumpleto", "repaid": "Binayaran", "failed": "Nabigo", - "liquidated": "Liquidated", + "liquidated": "Na-liquidate", "defaulted": "Di-nakamit" }, "pagination": { @@ -56,13 +73,13 @@ } }, "Navigation": { - "home": "Home", + "home": "Tahanan", "loans": "Mga Loan", "repay": "Bayaran", "dashboard": "Dashboard", "activity": "Aktibidad", "notifications": "Mga Notification", - "liquidations": "Liquidations", + "liquidations": "Mga Liquidasyon", "adminDisputes": "Mga Dispute" }, "Loans": { @@ -82,7 +99,7 @@ "all": "Lahat", "active": "Aktibo", "repaid": "Bayad Na", - "defaulted": "Defaulted" + "defaulted": "Hindi Nabayaran" }, "empty": { "title": "Walang nahanap na loan", @@ -102,7 +119,7 @@ } }, "Kingdom": { - "title": "Kingdom Dashboard", + "title": "Dashboard ng Kaharian", "description": "Subaybayan ang iyong pag-unlad, i-unlock ang mga tagumpay, at umangat sa mga ranggo", "welcome": "Maligayang pagdating, {kingdomTitle}!", "level": "Ngayon ay nasa Level {level} ka", @@ -120,14 +137,14 @@ "error": "Hindi ma-load ang iyong Remittance NFT ngayon.", "emptyTitle": "Wala pang Remittance NFT", "emptyDescription": "Ipadala ang unang remittance para simulan ang paggawa ng on-chain credit asset.", - "emptyAction": "Send first remittance", + "emptyAction": "Magpadala ng unang remittance", "score": "Score", "historyHash": "History hash", - "defaultCount": "Default count", + "defaultCount": "Bilang ng default", "cooldownRemaining": "Natitirang transfer cooldown", "lastUpdateLedger": "Huling update ledger", "metadataUri": "Metadata URI", - "ledgers": "{count} ledgers", + "ledgers": "{count} na ledger", "notAvailable": "Hindi available" } }, @@ -136,7 +153,7 @@ "title": "Mga Notification", "description": "Suriin ang repayment reminders, score changes, loan updates, at alerts sa iisang lugar.", "viewAll": "Tingnan lahat ng notifications", - "unread": "Unread", + "unread": "Hindi pa Nababasa", "markRead": "Markahan read", "markAllVisibleRead": "Markahan ang unread bilang read", "filters": { @@ -163,7 +180,7 @@ }, "LoanCard": { "totalOwed": "Kabuuang Utang", - "principal": "Principal", + "principal": "Pangunahing Halaga", "accruedInterest": "Naipon na Interes", "interest": "Interes", "nextPayment": "Susunod na Pagbabayad", @@ -173,7 +190,7 @@ "onTrack": "Sa Track", "dueSoon": "Malapit nang Dapat Bayaran", "overdue": "Lintasan", - "defaulted": "Defaulted", + "defaulted": "Hindi Nabayaran", "repayNow": "Bayaran Ngayon", "payNowOverdue": "Bayaran Ngayon (Lintasan)", "contactSupport": "Kontakin ang Support", @@ -181,7 +198,7 @@ "details": "Mga Detalye" }, "AdminDisputes": { - "eyebrow": "Admin Review", + "eyebrow": "Pagsusuri ng Admin", "title": "Mga Dispute", "description": "Suriin ang bukas na default disputes at itala ang pinal na resolusyon.", "checkingAccess": "Sinusuri ang admin access...", @@ -197,8 +214,8 @@ "description": "Suriin ang iyong admin session at subukan muli." }, "table": { - "borrower": "Borrower", - "loanId": "Loan ID", + "borrower": "Nangungutang", + "loanId": "ID ng Loan", "reason": "Buod ng dahilan", "submitted": "Isinumite", "action": "Aksyon" @@ -210,7 +227,7 @@ "back": "Bumalik sa disputes", "error": "Hindi ma-load ang dispute na ito.", "reason": "Dahilan ng borrower", - "noteLabel": "Resolution note", + "noteLabel": "Tala ng resolusyon", "notePlaceholder": "Ipaliwanag kung bakit kinukumpirma o binabaligtad ang default.", "noteHelp": "Maglagay ng hindi bababa sa 5 character.", "confirmDefault": "Kumpirmahin ang default", @@ -218,10 +235,10 @@ "loan": { "title": "Buod ng kaugnay na loan", "openLoan": "Buksan ang loan", - "principal": "Principal", + "principal": "Pangunahing Halaga", "totalOwed": "Kabuuang utang", "totalRepaid": "Kabuuang nabayaran", - "status": "Status", + "status": "Katayuan", "nextPayment": "Susunod na bayad" }, "modal": { @@ -242,7 +259,7 @@ }, "LoanDetails": { "actions": { - "refinance": "Refinance", + "refinance": "I-refinance", "requestExtension": "Humiling ng extension" }, "common": { @@ -250,19 +267,19 @@ "confirming": "Kinukumpirma..." }, "health": { - "title": "Loan health", + "title": "Kalusugan ng Loan", "loading": "Naglo-load ng health factor...", "unavailableTitle": "Hindi available ang health data", "unavailableDescription": "Hindi pa available ang collateral at debt data para sa loan na ito.", "collateral": "Collateral", "totalDebt": "Kabuuang utang", - "threshold": "Liquidation threshold", + "threshold": "Limitasyon ng liquidasyon", "sourceContract": "Gamit ang contract health view", "sourceBackend": "Gamit ang backend-computed health data", "sourceDerived": "Derived mula sa collateral at kabuuang utang", "cta": "Magdagdag ng collateral", "states": { - "healthy": "Healthy", + "healthy": "Malusog", "watch": "Bantayan", "atRisk": "Nasa panganib" }, @@ -275,9 +292,9 @@ "refinance": { "title": "I-refinance ang loan", "principal": "Bagong principal na halaga", - "interestRate": "Interest rate (%)", + "interestRate": "Rate ng interes (%)", "term": "Termino", - "previewTitle": "Projected amortization", + "previewTitle": "Inaasahang amortisasyon", "previewDescription": "Preview ng inaasahang repayment schedule bago pumirma.", "submit": "I-build, Pirmahan at Isumite" }, @@ -292,7 +309,7 @@ "cooldownActive": "Available ang withdraw sa humigit-kumulang {minutes} minuto habang aktibo ang cooldown." }, "Liquidations": { - "eyebrow": "Liquidator Console", + "eyebrow": "Console ng Liquidator", "title": "Mga Liquidatable Loan", "description": "Suriin ang active positions na mababa sa liquidation threshold at magsumite ng liquidation transactions.", "refresh": "I-refresh", @@ -315,10 +332,10 @@ }, "table": { "loan": "Loan", - "borrower": "Borrower", + "borrower": "Nangungutang", "collateral": "Collateral", "debt": "Kabuuang utang", - "health": "Health", + "health": "Kalusugan", "action": "Aksyon" } } diff --git a/frontend/src/app/[locale]/page.tsx b/frontend/src/app/[locale]/page.tsx index f23eb3ab..4994b0bb 100644 --- a/frontend/src/app/[locale]/page.tsx +++ b/frontend/src/app/[locale]/page.tsx @@ -78,6 +78,7 @@ function RepaymentReminderBanner({ onDismiss: () => void; }) { const router = useRouter(); + const t = useTranslations("HomePage"); const mostUrgent = urgentLoans[0]; if (!mostUrgent) return null; @@ -96,7 +97,7 @@ function RepaymentReminderBanner({ />

- Repayment due in {hoursLeft}h — Loan #{mostUrgent.id} + {t("reminder.due", { hours: hoursLeft, loanId: mostUrgent.id })} {urgentLoans.length > 1 && ( +{urgentLoans.length - 1} more @@ -123,11 +124,11 @@ function RepaymentReminderBanner({ onClick={() => router.push(`/repay/${mostUrgent.id}`)} className="rounded-lg bg-amber-600 px-3 py-1.5 text-xs font-semibold text-white hover:bg-amber-700 transition-colors" > - Repay now + {t("reminder.repayNow")}

@@ -496,15 +501,15 @@ export default function Home() { {/* Credit Score Gauge */}

- Credit Score + {t("creditScore.label")}