Skip to content
Merged
607 changes: 607 additions & 0 deletions backend/src/__tests__/contractIntegration.test.ts

Large diffs are not rendered by default.

26 changes: 4 additions & 22 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,15 @@ import TwoFactorSettings from './pages/TwoFactorSettings';
import CustomReportBuilder from './pages/CustomReportBuilder';
import CrossAssetPayment from './pages/CrossAssetPayment';
import TransactionHistory from './pages/TransactionHistory';
import BulkPaymentTracker from './pages/BulkPaymentTracker';
import AdminPanel from './pages/AdminPanel';
import VestingEscrow from './pages/VestingEscrow';
import RevenueSplitDashboard from './pages/RevenueSplitDashboard';
import Forecasting from './pages/Forecasting';
import TaxComplianceWizard from './pages/TaxComplianceWizard';

import EmployeePortal from './pages/EmployeePortal';
import Login from './pages/Login';
import AuthCallback from './pages/AuthCallback';
import { useTranslation } from 'react-i18next';
import { contractService } from './services/contracts';
import TaxComplianceWizard from './pages/TaxComplianceWizard';

function App() {
const { t } = useTranslation();
Expand Down Expand Up @@ -194,26 +192,10 @@ function App() {
}
/>
<Route
path="/forecast"
element={
<ErrorBoundary fallback={<ErrorFallback onReset={() => {}} />}>
<Forecasting />
</ErrorBoundary>
}
/>
<Route
path="/vesting"
element={
<ErrorBoundary fallback={<ErrorFallback onReset={() => {}} />}>
<VestingEscrow />
</ErrorBoundary>
}
/>
<Route
path="/revenue-split"
path="/bulk-payments"
element={
<ErrorBoundary fallback={<ErrorFallback onReset={() => {}} />}>
<RevenueSplitDashboard />
<BulkPaymentTracker />
</ErrorBoundary>
}
/>
Expand Down
26 changes: 4 additions & 22 deletions frontend/src/components/AppNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ import {
ShieldAlert,
Menu,
X,
Lock,
PieChart,
BarChart2,
TrendingUp,
} from 'lucide-react';
import { Avatar } from './Avatar';
Expand Down Expand Up @@ -147,7 +146,7 @@ const AppNav: React.FC = () => {
</NavLink>

<NavLink
to="/vesting"
to="/bulk-payments"
className={({ isActive }) =>
`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-[13px] font-semibold transition ${
isActive
Expand All @@ -158,26 +157,9 @@ const AppNav: React.FC = () => {
onClick={() => setMobileOpen(false)}
>
<span className="opacity-70">
<Lock className="w-4 h-4" />
<BarChart2 className="w-4 h-4" />
</span>
<span className="hidden sm:inline">Vesting</span>
</NavLink>

<NavLink
to="/revenue-split"
className={({ isActive }) =>
`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-[13px] font-semibold transition ${
isActive
? 'text-(--accent) bg-white/5'
: 'text-(--muted) hover:bg-white/10 hover:text-white'
}`
}
onClick={() => setMobileOpen(false)}
>
<span className="opacity-70">
<PieChart className="w-4 h-4" />
</span>
<span className="hidden sm:inline">Revenue Split</span>
<span className="hidden sm:inline">Bulk Payments</span>
</NavLink>

<div className="w-px h-5 bg-(--border-hi) mx-2" />
Expand Down
157 changes: 157 additions & 0 deletions frontend/src/components/ContractErrorPanel.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
.panel {
width: 100%;
background: rgba(220, 38, 38, 0.05);
border: 1px solid rgba(220, 38, 38, 0.2);
border-radius: 12px;
overflow: hidden;
transition: all 0.2s ease;
margin-bottom: 1.5rem;
}

.panel.expanded {
background: rgba(220, 38, 38, 0.08);
border-color: rgba(220, 38, 38, 0.3);
}

.header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 1.25rem;
cursor: pointer;
user-select: none;
}

.header:hover {
background: rgba(220, 38, 38, 0.05);
}

.headerLeft {
display: flex;
align-items: center;
gap: 0.75rem;
}

.errorIcon {
color: #ef4444;
}

.title {
font-weight: 700;
font-size: 0.9rem;
color: #fca5a5;
text-transform: uppercase;
letter-spacing: 0.025em;
}

.headerRight {
display: flex;
align-items: center;
gap: 1rem;
color: rgba(255, 255, 255, 0.5);
}

.errorCode {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-size: 0.75rem;
background: rgba(0, 0, 0, 0.3);
padding: 0.25rem 0.5rem;
border-radius: 4px;
color: #f87171;
}

.content {
padding: 0 1.25rem 1.25rem;
border-top: 1px solid rgba(220, 38, 38, 0.1);
}

.messageSection {
padding: 1rem 0;
}

.message {
font-size: 1rem;
line-height: 1.5;
color: rgba(255, 255, 255, 0.9);
font-weight: 500;
}

.actionSection {
background: rgba(0, 0, 0, 0.2);
border-radius: 8px;
padding: 0.875rem;
margin-bottom: 1rem;
}

.actionHeader {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.5rem;
color: #3b82f6;
}

.actionLabel {
font-size: 0.7rem;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.05em;
}

.actionText {
font-size: 0.85rem;
color: rgba(255, 255, 255, 0.7);
line-height: 1.4;
}

.rawSection {
margin-top: 1rem;
}

.rawHeader {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.5rem;
}

.rawLabel {
font-size: 0.7rem;
font-weight: 700;
color: rgba(255, 255, 255, 0.4);
text-transform: uppercase;
letter-spacing: 0.05em;
}

.copyButton {
display: flex;
align-items: center;
gap: 0.375rem;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
color: rgba(255, 255, 255, 0.6);
padding: 0.25rem 0.625rem;
border-radius: 4px;
font-size: 0.7rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}

.copyButton:hover {
background: rgba(255, 255, 255, 0.1);
color: white;
}

.rawContent {
background: #000;
padding: 0.75rem;
border-radius: 6px;
font-family: ui-monospace, Consolas, monospace;
font-size: 0.7rem;
color: rgba(255, 255, 255, 0.5);
word-break: break-all;
max-height: 100px;
overflow-y: auto;
border: 1px solid rgba(255, 255, 255, 0.1);
}
76 changes: 76 additions & 0 deletions frontend/src/components/ContractErrorPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React, { useState } from 'react';
import { ChevronDown, ChevronUp, Copy, AlertTriangle, Info } from 'lucide-react';
import { ContractErrorDetails } from '../utils/contractErrorParser';
import styles from './ContractErrorPanel.module.css';

interface Props {
error: ContractErrorDetails | null;
className?: string;
}

export const ContractErrorPanel: React.FC<Props> = ({ error, className = '' }) => {
const [isExpanded, setIsExpanded] = useState(true);

if (!error) return null;

const handleCopyRaw = () => {
if (error.rawXdr) {
void navigator.clipboard.writeText(error.rawXdr);
}
};

const isUnknown = error.code === 'UNKNOWN_FORMAT' || error.code === 'UNPARSEABLE_XDR';

return (
<div className={`${styles.panel} ${className} ${isExpanded ? styles.expanded : ''}`}>
<div className={styles.header} onClick={() => setIsExpanded(!isExpanded)}>
<div className={styles.headerLeft}>
<AlertTriangle className={styles.errorIcon} size={18} />
<span className={styles.title}>Contract Invocation Failed</span>
</div>
<div className={styles.headerRight}>
<span className={styles.errorCode}>{error.code}</span>
{isExpanded ? <ChevronUp size={18} /> : <ChevronDown size={18} />}
</div>
</div>

{isExpanded && (
<div className={styles.content}>
<div className={styles.messageSection}>
<p className={styles.message}>{error.message}</p>
</div>

<div className={styles.actionSection}>
<div className={styles.actionHeader}>
<Info size={14} className={styles.infoIcon} />
<span className={styles.actionLabel}>Suggested Action</span>
</div>
<p className={styles.actionText}>{error.action}</p>
</div>

{(isUnknown || error.rawXdr) && (
<div className={styles.rawSection}>
<div className={styles.rawHeader}>
<span className={styles.rawLabel}>Raw Transaction Result (XDR)</span>
<button
type="button"
onClick={handleCopyRaw}
className={styles.copyButton}
title="Copy XDR"
>
<Copy size={14} />
<span>Copy</span>
</button>
</div>
<div className={styles.rawContent}>
<code>{error.rawXdr || 'N/A'}</code>
</div>
</div>
)}
</div>
)}
</div>
);
};

export default ContractErrorPanel;
Loading
Loading