Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
a3f06bd
Add Contributors section to README
Ebby46 Jun 30, 2026
f45830e
Update .env.example
Ebby46 Jun 30, 2026
3a261db
test(e2e): add wallet connect flow E2E tests with mocked Freighter an…
Ebby46 Jul 2, 2026
cfaa6c6
fix: pre-existing CI failures in Go indexer test and frontend tests
Ebby46 Jul 2, 2026
2ca946b
style: fix prettier and gofmt formatting issues across 13 files
Ebby46 Jul 2, 2026
a21165a
fix(e2e): fix wallet connect tests - mock requestAccess returns addre…
Ebby46 Jul 2, 2026
5062437
style: fix prettier formatting in invoice-lifecycle.spec.ts
Ebby46 Jul 2, 2026
2f08392
fix(e2e): rewrite freighter fixture to use postMessage mocking instea…
Ebby46 Jul 2, 2026
dbaa3a3
fix(e2e): monkey-patch window.postMessage to intercept freighter exte…
Ebby46 Jul 2, 2026
9ee983d
fix(e2e): use plain JS for addInitScript to avoid TS issues in browse…
Ebby46 Jul 2, 2026
22a0d42
fix(e2e): use window property mocks in lib/freighter.ts instead of po…
Ebby46 Jul 2, 2026
45e2ed8
fix(e2e): fix testnet badge exact match and increase reload wait timeout
Ebby46 Jul 2, 2026
0b97422
fix(e2e): fix invoice-lifecycle test routes and button texts to match…
Ebby46 Jul 2, 2026
8cc9297
fix(e2e): mock profile verification and fix reload test
Ebby46 Jul 2, 2026
d947fb6
fix(e2e): persist wallet connected state, fix form accessibility, and…
Ebby46 Jul 6, 2026
78cb6a2
Merge branch 'upstream/main' into test/e2e-wallet-connect-flow
Ebby46 Jul 6, 2026
86f2422
fix: resolve build errors from upstream merge
Ebby46 Jul 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# TrusTrove Environment Variables

#

# This root file is the single source of truth for local, Testnet, and Mainnet
# configuration across the frontend app, Go indexer/API, and TypeScript SDK.
# Copy it before local development:
Expand Down
40 changes: 38 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,44 @@ jobs:
- name: Check Formatting
run: npx prettier --check .

- name: Check Formatting
run: npx prettier --check .
e2e-tests:
name: E2E Tests (Playwright)
runs-on: ubuntu-latest
needs: verify-frontend-and-sdk

steps:
- name: Checkout Code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20

- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10

- name: Install Dependencies
run: pnpm install --frozen-lockfile

- name: Build Packages (SDK & Web App)
run: pnpm build

- name: Install Playwright Browsers
run: pnpm --filter web exec playwright install chromium

- name: Run E2E Tests
run: pnpm test:e2e

- name: Upload Playwright Report
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: apps/web/playwright-report/
retention-days: 7

verify-go-indexer:
name: Verify Go Indexer & API
Expand Down
30 changes: 7 additions & 23 deletions apps/web/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@

import React, { useState } from "react";
import Link from "next/link";
import dynamic from "next/dynamic";
import { PageLayout } from "@/components/shared/PageLayout";
import { InvoiceForm } from "@/components/invoice/InvoiceForm";
import { InvoiceTable } from "@/components/invoice/InvoiceTable";
import { InvoiceCard } from "@/components/invoice/InvoiceCard";
import { ErrorBoundary } from "@/components/shared/ErrorBoundary";
import { useInvoices } from "@/hooks/useInvoices";
import { useRecentEvents } from "@/hooks/useEvents";
import { useWalletStore } from "@/store/wallet";
Expand All @@ -29,15 +28,6 @@ import { motion, AnimatePresence } from "framer-motion";
import { formatAmount } from "@/lib/assets";
import { useFocusTrap } from "@/hooks/useFocusTrap";

const InvoiceForm = dynamic(() => import("@/components/invoice/InvoiceForm"), {
ssr: false,
loading: () => (
<div className="h-64 w-full flex items-center justify-center">
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-primary" />
</div>
),
});

export default function SMEDashboard() {
const { address, connected, role } = useWalletStore();
const { invoices, isLoading } = useInvoices({ issuer: address || undefined });
Expand Down Expand Up @@ -331,12 +321,11 @@ export default function SMEDashboard() {
{isLoading ? (
<InvoiceTableSkeleton />
) : (
<ErrorBoundary context="InvoiceTable">
<InvoiceTable
invoices={invoices}
onSelectInvoice={(invoice) => setSelectedInvoice(invoice)}
activeId={selectedInvoice?.id}
emptyState={
<InvoiceTable
invoices={invoices}
onSelectInvoice={(invoice) => setSelectedInvoice(invoice)}
activeId={selectedInvoice?.id}
emptyState={
<div className="flex flex-col items-center justify-center py-16 px-6 text-center">
<p className="text-slate-500 text-xs font-mono mb-6 leading-relaxed max-w-xs">
Create your first invoice to get started
Expand All @@ -351,14 +340,12 @@ export default function SMEDashboard() {
</div>
}
/>
</ErrorBoundary>
)}

{/* Recent activity timeline */}
{eventsLoading ? (
<ActivityTimelineSkeleton />
) : (
<ErrorBoundary context="ActivityLog">
<div className="bg-card border border-border rounded-lg p-5 space-y-4">
<h3 className="text-xs font-bold font-mono text-white uppercase tracking-wider border-b border-border/40 pb-2">
On-Chain Activity Logs
Expand Down Expand Up @@ -392,7 +379,6 @@ export default function SMEDashboard() {
})}
</div>
</div>
</ErrorBoundary>
)}
</div>

Expand All @@ -402,7 +388,6 @@ export default function SMEDashboard() {
Management console
</h2>

<ErrorBoundary context="ManagementConsole">
<AnimatePresence mode="wait">
{selectedInvoice ? (
<motion.div
Expand Down Expand Up @@ -444,7 +429,6 @@ export default function SMEDashboard() {
</div>
)}
</AnimatePresence>
</ErrorBoundary>
</div>
</div>
</div>
Expand Down Expand Up @@ -484,7 +468,7 @@ export default function SMEDashboard() {
</div>
</div>
</div>
</Modal>
)}
</PageLayout>
);
}
5 changes: 3 additions & 2 deletions apps/web/app/lp/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ import { PoolClient } from "@trusttrove/sdk";
import { Address, nativeToScVal } from "@stellar/stellar-sdk";
import { SimulationPreview } from "@/components/shared/SimulationPreview";
import { useQuery } from "@tanstack/react-query";
import { useRecentEvents } from "@/hooks/useEvents";
import { getPoolSnapshots } from "@/lib/api";

const TransactionPending = dynamic(() => import("@/components/shared/TransactionPending"), {
const TransactionPending = dynamic(() => import("@/components/shared/TransactionPending").then(mod => mod.TransactionPending), {
ssr: false,
loading: () => (
<div className="fixed inset-0 flex items-center justify-center z-50 bg-black/50">
Expand Down Expand Up @@ -478,7 +479,7 @@ export default function LPDashboard() {
<path d={chartLines.areaD} fill="url(#chartGlow)" />

{/* Line path */}
<path
<motion.path
d={chartLines.lineD}
fill="transparent"
stroke="#00d4aa"
Expand Down
2 changes: 1 addition & 1 deletion apps/web/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export default function Home() {
</div>

<div className="text-xs font-mono text-slate-400 border-l-2 border-primary pl-3">
"From invoice to USDC in minutes. Not weeks."
&ldquo;From invoice to USDC in minutes. Not weeks.&rdquo;
</div>
</div>

Expand Down
134 changes: 66 additions & 68 deletions apps/web/app/profile/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ import React, { useState } from "react";
import { PageLayout } from "@/components/shared/PageLayout";
import { useWalletStore } from "@/store/wallet";
import { useProfile } from "@/hooks/useProfile";
import { useFocusTrap } from "@/hooks/useFocusTrap";
import { WalletConnect } from "@/components/shared/WalletConnect";
import { ErrorBoundary } from "@/components/shared/ErrorBoundary";
import { TransactionPending } from "@/components/shared/TransactionPending";
import { Button } from "@/components/ui/button";
import {
Expand Down Expand Up @@ -38,9 +36,6 @@ export default function ProfilePage() {
registerError,
} = useProfile();

// Modal Refs
const modalRef = useFocusTrap<HTMLDivElement>(showRegModal, () => setShowRegModal(false));

// Registration Form States
const [showRegModal, setShowRegModal] = useState(false);
const [regRole, setRegRole] = useState<"issuer" | "buyer">("issuer");
Expand Down Expand Up @@ -144,7 +139,6 @@ export default function ProfilePage() {
</p>
</div>

<ErrorBoundary context="ProfileContent">
{isLoading ? (
<div className="bg-[#0d131a] border border-border rounded-lg p-12 flex flex-col items-center justify-center space-y-4 font-mono text-xs">
<span className="w-1.5 h-1.5 rounded-full bg-primary animate-ping" />
Expand Down Expand Up @@ -329,12 +323,10 @@ export default function ProfilePage() {
</div>
</div>
)}
</ErrorBoundary>
</div>

{/* Registration Modal Dialog */}
{showRegModal && (
<ErrorBoundary context="RegistrationModal">
<div className="fixed inset-0 z-50 flex items-center justify-center bg-[#080c10]/95 backdrop-blur-sm p-4">
<div className="w-full max-w-lg bg-card border border-border rounded-lg p-6 relative shadow-[0_0_50px_rgba(0,212,170,0.05)]">
<button
Expand Down Expand Up @@ -389,61 +381,95 @@ export default function ProfilePage() {
</button>
</div>
</div>
</div>

{/* Tax ID & Country */}
<div className="grid grid-cols-2 gap-4">
{/* Company Name */}
<div className="space-y-1">
<label className="block text-[10px] font-bold text-slate-500 uppercase tracking-wider">
Tax ID / Registration No.
Company Name
</label>
<div className="relative">
<FileText className="absolute left-3 top-2.5 w-4 h-4 text-slate-500" />
<Building className="absolute left-3 top-2.5 w-4 h-4 text-slate-500" />
<input
type="text"
required
placeholder="e.g. EU123456789"
placeholder="e.g. Acme Corp"
className="w-full bg-[#080c10] border border-border rounded pl-10 pr-3 py-2 text-white placeholder-slate-600 focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary"
value={taxId}
onChange={(e) => setTaxId(e.target.value)}
value={companyName}
onChange={(e) => setCompanyName(e.target.value)}
/>
</div>
</div>

{/* Tax ID & Country */}
<div className="grid grid-cols-2 gap-4">
<div className="space-y-1">
<label className="block text-[10px] font-bold text-slate-500 uppercase tracking-wider">
Tax ID / Registration No.
</label>
<div className="relative">
<FileText className="absolute left-3 top-2.5 w-4 h-4 text-slate-500" />
<input
type="text"
required
placeholder="e.g. EU123456789"
className="w-full bg-[#080c10] border border-border rounded pl-10 pr-3 py-2 text-white placeholder-slate-600 focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary"
value={taxId}
onChange={(e) => setTaxId(e.target.value)}
/>
</div>
</div>

<div className="space-y-1">
<label className="block text-[10px] font-bold text-slate-500 uppercase tracking-wider">
Country of Incorporation
</label>
<div className="relative">
<Globe className="absolute left-3 top-2.5 w-4 h-4 text-slate-500" />
<input
type="text"
required
placeholder="e.g. Germany"
className="w-full bg-[#080c10] border border-border rounded pl-10 pr-3 py-2 text-white placeholder-slate-600 focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary"
value={country}
onChange={(e) => setCountry(e.target.value)}
/>
</div>
</div>
</div>

{/* Website URL */}
<div className="space-y-1">
<label className="block text-[10px] font-bold text-slate-500 uppercase tracking-wider">
Country of Incorporation
Corporate Website URL (Optional)
</label>
<div className="relative">
<Globe className="absolute left-3 top-2.5 w-4 h-4 text-slate-500" />
<input
type="text"
required
placeholder="e.g. Germany"
type="url"
placeholder="e.g. https://acme.corp"
className="w-full bg-[#080c10] border border-border rounded pl-10 pr-3 py-2 text-white placeholder-slate-600 focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary"
value={country}
onChange={(e) => setCountry(e.target.value)}
value={website}
onChange={(e) => setWebsite(e.target.value)}
/>
</div>
</div>
</div>

{/* Website URL */}
<div className="space-y-1">
<label className="block text-[10px] font-bold text-slate-500 uppercase tracking-wider">
Corporate Website URL (Optional)
</label>
<div className="relative">
<Globe className="absolute left-3 top-2.5 w-4 h-4 text-slate-500" />
<input
type="url"
placeholder="e.g. https://acme.corp"
className="w-full bg-[#080c10] border border-border rounded pl-10 pr-3 py-2 text-white placeholder-slate-600 focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary"
value={website}
onChange={(e) => setWebsite(e.target.value)}
/>
{/* Email */}
<div className="space-y-1">
<label className="block text-[10px] font-bold text-slate-500 uppercase tracking-wider">
Contact Email (Optional)
</label>
<div className="relative">
<Mail className="absolute left-3 top-2.5 w-4 h-4 text-slate-500" />
<input
type="email"
placeholder="e.g. admin@acme.corp"
className="w-full bg-[#080c10] border border-border rounded pl-10 pr-3 py-2 text-white placeholder-slate-600 focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
</div>
</div>

{/* Warnings and errors */}
{(localError || registerError) && (
Expand All @@ -465,7 +491,6 @@ export default function ProfilePage() {
your wallet address and locks your business credentials.
</span>
</div>
</div>

{/* Buttons */}
<div className="flex gap-2 pt-2">
Expand All @@ -485,36 +510,9 @@ export default function ProfilePage() {
{isRegistering ? "Signing..." : "Register Profile"}
</button>
</div>
)}

<div className="bg-[#080c10] border border-amber-500/20 p-3 rounded text-[10px] text-amber-500 leading-normal flex items-start gap-1.5">
<Lock className="w-3.5 h-3.5 shrink-0 mt-0.5" />
<span>
Signing this registration requires Freighter authorization. You will submit a Soroban write transaction, which whitelists your wallet address and locks your business credentials.
</span>
</div>

{/* Buttons */}
<div className="flex gap-2 pt-2">
<button
type="button"
onClick={() => setShowRegModal(false)}
className="flex-1 border border-border bg-transparent hover:bg-slate-900 text-slate-400 font-bold uppercase py-2.5 rounded"
disabled={isRegistering}
>
Cancel
</button>
<button
type="submit"
className="flex-1 bg-primary hover:bg-primary-hover text-black font-bold uppercase py-2.5 rounded shadow-[0_0_15px_rgba(0,212,170,0.15)] flex items-center justify-center gap-1.5"
disabled={isRegistering}
>
{isRegistering ? 'Signing...' : 'Register Profile'}
</button>
</div>
</form>
</form>
</div>
</div>
</ErrorBoundary>
)}

{/* Transaction Pending Dialog Modal */}
Expand Down
Loading
Loading