Skip to content

Commit 78f84fe

Browse files
authored
Merge pull request #264 from aabxtract/main
fix: resolve mobile responsiveness issues and typescript type errors
2 parents f757bcc + 0a269a9 commit 78f84fe

22 files changed

Lines changed: 869 additions & 1263 deletions

frontend/package-lock.json

Lines changed: 499 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,12 @@
1717
"@stellar/freighter-api": "^6.0.1",
1818
"@stellar/stellar-sdk": "^14.5.0",
1919
"@tailwindcss/vite": "^4.1.18",
20+
"@types/qrcode": "^1.5.6",
21+
"html2pdf.js": "^0.14.0",
2022
"i18next": "^25.10.9",
2123
"i18next-browser-languagedetector": "^8.2.1",
24+
"lucide-react": "^1.11.0",
25+
"qrcode": "^1.5.4",
2226
"react": "^19.2.0",
2327
"react-dom": "^19.2.0",
2428
"react-i18next": "^16.6.6",
@@ -30,6 +34,7 @@
3034
"@testing-library/jest-dom": "^6.4.2",
3135
"@testing-library/react": "^14.2.1",
3236
"@testing-library/user-event": "^14.5.2",
37+
"@types/html2pdf.js": "^0.10.0",
3338
"@types/node": "^24.12.0",
3439
"@types/react": "^19.2.7",
3540
"@types/react-dom": "^19.2.3",

frontend/src/App.tsx

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ const Home = memo(() => {
293293
<h2 id="payment-form-title" className="sr-only">Payment Options</h2>
294294

295295
<div className="border-b border-slate-200 dark:border-slate-800">
296-
<nav className="-mb-px flex space-x-8" aria-label="Payment type">
296+
<nav className="-mb-px flex gap-4 sm:gap-8 overflow-x-auto whitespace-nowrap pb-1" aria-label="Payment type">
297297
<button
298298
onClick={() => setPaymentType('manual')}
299299
className={`py-2 px-1 border-b-2 font-medium text-sm transition-colors ${paymentType === 'manual'
@@ -460,34 +460,36 @@ export default function App() {
460460
}, []);
461461

462462
return (
463-
<ThemeProvider>
464-
<Router>
465-
<ErrorBoundary
466-
FallbackComponent={GlobalErrorFallback}
467-
onError={(error, errorInfo) => logClientError(error, errorInfo?.componentStack || '', { module: 'App' })}
468-
>
469-
<div className="app-container min-h-screen bg-slate-50 dark:bg-slate-950 text-slate-900 dark:text-slate-100 transition-colors duration-200">
470-
<SkipLinks />
471-
<OfflineBanner />
463+
<ErrorBoundary fallback={<GlobalErrorFallback />}>
464+
<ThemeProvider>
465+
<Router>
466+
<SkipLinks />
467+
<div className="min-h-screen bg-slate-50 dark:bg-slate-950 text-slate-900 dark:text-slate-100 transition-colors duration-200">
472468
<ResponsiveNavigation />
473-
474-
<Routes>
475-
<Route path="/" element={<Home />} />
476-
<Route path="/about" element={<Suspense fallback={<div>{t('app.loading')}</div>}><About /></Suspense>} />
477-
<Route path="/contact" element={<Suspense fallback={<div>{t('app.loading')}</div>}><Contact /></Suspense>} />
478-
<Route path="/rate" element={<Suspense fallback={<div>{t('app.loading')}</div>}><Rate /></Suspense>} />
479-
<Route path="/schedules" element={<Suspense fallback={<div>{t('app.loading')}</div>}><ScheduledPayments /></Suspense>} />
480-
<Route path="/analytics" element={<Suspense fallback={<div>{t('app.loading')}</div>}><AnalyticsDashboard /></Suspense>} />
481-
<Route path="/monitoring" element={<Suspense fallback={<div>{t('app.loading')}</div>}><RealTimeMonitoringDashboard /></Suspense>} />
482-
<Route path="/privacy-policy" element={<Suspense fallback={<div>{t('app.loading')}</div>}><PrivacyPolicy /></Suspense>} />
483-
<Route path="/retention-policy" element={<Suspense fallback={<div>{t('app.loading')}</div>}><DataRetentionPolicy /></Suspense>} />
484-
<Route path="/payment" element={<Suspense fallback={<div>{t('app.loading')}</div>}><QRPaymentHandler /></Suspense>} />
485-
</Routes>
469+
<OfflineBanner />
470+
<Suspense fallback={
471+
<div className="flex h-[50vh] items-center justify-center">
472+
<div className="h-8 w-8 animate-spin rounded-full border-4 border-sky-500 border-t-transparent" />
473+
</div>
474+
}>
475+
<Routes>
476+
<Route path="/" element={<Home />} />
477+
<Route path="/about" element={<About />} />
478+
<Route path="/contact" element={<Contact />} />
479+
<Route path="/rate" element={<Rate />} />
480+
<Route path="/schedules" element={<ScheduledPayments />} />
481+
<Route path="/pay/qr" element={<QRPaymentHandler />} />
482+
<Route path="/privacy-policy" element={<PrivacyPolicy />} />
483+
<Route path="/retention-policy" element={<DataRetentionPolicy />} />
484+
<Route path="/analytics" element={<AnalyticsDashboard />} />
485+
<Route path="/monitoring" element={<RealTimeMonitoringDashboard />} />
486+
</Routes>
487+
</Suspense>
486488
<GDPRConsent />
487489
</div>
488-
</ErrorBoundary>
489-
</Router>
490-
</ThemeProvider>
490+
</Router>
491+
</ThemeProvider>
492+
</ErrorBoundary>
491493
);
492494
}
493495
// After a successful pay_bill transaction:

frontend/src/components/OfflineBanner.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ export function OfflineBanner({ className = '', showDetails = false }: OfflineBa
7777
role="alert"
7878
aria-live="polite"
7979
>
80-
<div className="max-w-7xl mx-auto flex items-center justify-between">
81-
<div className="flex items-center space-x-3">
80+
<div className="max-w-7xl mx-auto flex flex-col sm:flex-row sm:items-center justify-between gap-3">
81+
<div className="flex items-start sm:items-center space-x-3">
8282
<span className="text-lg" aria-hidden="true">
8383
{getConnectionIcon()}
8484
</span>

frontend/src/components/ResponsiveNavigation.tsx

Lines changed: 14 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -30,29 +30,10 @@ export const ResponsiveNavigation: React.FC = memo(() => {
3030

3131
const isActive = useCallback((path: string) => {
3232
return location.pathname === path
33-
? 'text-sky-500 dark:text-sky-400'
34-
: 'text-slate-600 dark:text-slate-300 hover:text-slate-900 dark:hover:text-slate-100';
33+
? 'text-brand-primary font-semibold'
34+
: 'text-brand-text-secondary hover:text-brand-text-primary';
3535
}, [location.pathname]);
3636

37-
const toggleMobileMenu = useCallback(() => {
38-
const newOpenState = !isMobileMenuOpen;
39-
setIsMobileMenuOpen(newOpenState);
40-
41-
if (newOpenState) {
42-
announceToScreenReader(t('accessibility.navigation.menuOpened'));
43-
if (mobileMenuRef.current) {
44-
cleanupRef.current = trapFocus(mobileMenuRef.current);
45-
}
46-
} else {
47-
announceToScreenReader(t('accessibility.navigation.menuClosed'));
48-
if (cleanupRef.current) {
49-
cleanupRef.current();
50-
cleanupRef.current = null;
51-
}
52-
menuButtonRef.current?.focus();
53-
}
54-
}, [isMobileMenuOpen, t]);
55-
5637
const closeMobileMenu = useCallback(() => {
5738
if (isMobileMenuOpen) {
5839
setIsMobileMenuOpen(false);
@@ -68,8 +49,7 @@ export const ResponsiveNavigation: React.FC = memo(() => {
6849
// Close on route change
6950
useEffect(() => {
7051
closeMobileMenu();
71-
// eslint-disable-next-line react-hooks/exhaustive-deps
72-
}, [location.pathname]);
52+
}, [location.pathname, closeMobileMenu]);
7353

7454
useEffect(() => {
7555
const handleEscape = (e: KeyboardEvent) => {
@@ -95,10 +75,6 @@ export const ResponsiveNavigation: React.FC = memo(() => {
9575
<>
9676
<nav
9777
className="border-b border-brand-surface-high bg-brand-surface-low/80 backdrop-blur-md sticky top-0 z-40"
98-
return (
99-
<>
100-
<nav
101-
className="border-b border-slate-200 dark:border-slate-800 bg-white/80 dark:bg-slate-900/60 backdrop-blur-sm sticky top-0 z-40 transition-colors duration-200"
10278
role="navigation"
10379
aria-label="Main navigation"
10480
id={navigationId.current}
@@ -128,15 +104,21 @@ export const ResponsiveNavigation: React.FC = memo(() => {
128104
{label}
129105
</Link>
130106
))}
107+
</div>
108+
<div className="flex items-center gap-3 ml-4 pl-4 border-l border-brand-surface-high">
109+
<ThemeSwitcher variant="icon" />
110+
<NetworkSwitcher showLabel={false} />
111+
</div>
112+
</div>
131113

132-
{/* Mobile/Tablet controls */}
114+
{/* Mobile/Tablet menu button */}
133115
<div className="lg:hidden flex items-center gap-3">
134116
<ThemeSwitcher variant="icon" />
135117
<NetworkSwitcher showLabel={false} />
136118
<button
137119
ref={menuButtonRef}
138120
onClick={toggleMobileMenu}
139-
className="p-2 rounded-lg text-slate-500 dark:text-slate-400 hover:text-slate-800 dark:hover:text-slate-200 hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors focus:outline-none focus:ring-2 focus:ring-sky-500 focus:ring-offset-2 focus:ring-offset-white dark:focus:ring-offset-slate-900"
121+
className="p-2 rounded-lg text-brand-text-secondary hover:text-brand-text-primary hover:bg-brand-surface-high transition-colors focus:outline-none focus:ring-2 focus:ring-brand-primary focus:ring-offset-2 focus:ring-offset-brand-bg"
140122
aria-label={getAriaLabel('menu-button')}
141123
aria-expanded={isMobileMenuOpen}
142124
aria-controls={navigationId.current}
@@ -153,9 +135,9 @@ export const ResponsiveNavigation: React.FC = memo(() => {
153135
</div>
154136
</div>
155137

156-
{/* Tablet Navigation row */}
157-
<div className="hidden md:flex lg:hidden py-3 border-t border-slate-200 dark:border-slate-800" role="menubar">
158-
<div className="flex items-center gap-4 text-sm w-full justify-center flex-1">
138+
{/* Tablet Navigation row (shown on md/lg, hidden on lg+) */}
139+
<div className="hidden md:flex lg:hidden py-3 border-t border-brand-surface-high" role="menubar">
140+
<div className="flex items-center gap-2 text-sm w-full justify-center flex-1 flex-wrap">
159141
{NAV_ITEMS.map(({ path, label }) => (
160142
<Link
161143
key={path}
@@ -168,10 +150,6 @@ export const ResponsiveNavigation: React.FC = memo(() => {
168150
</Link>
169151
))}
170152
</div>
171-
<div className="flex items-center gap-3 ml-auto">
172-
<ThemeSwitcher variant="icon" />
173-
<NetworkSwitcher showLabel={false} />
174-
</div>
175153
</div>
176154
</nav>
177155

frontend/src/components/WalletBalance.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ export const WalletBalance: React.FC<WalletBalanceProps> = (props) => {
102102
return (
103103
<div className={`rounded-xl border border-brand-surface-high bg-brand-surface-low/40 p-4 ${className}`} aria-busy={isLoading}>
104104
{/* Main Balance Display */}
105-
<div className="flex items-center justify-between mb-3">
105+
<div className="flex flex-col sm:flex-row sm:items-center justify-between mb-3 gap-3">
106106
<div>
107107
<div className="text-xs font-semibold uppercase tracking-wide text-brand-text-secondary mb-1">
108108
Wallet Balance

frontend/src/components/WalletSelector.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,8 @@ export const WalletSelector: React.FC<WalletSelectorProps> = ({
122122
))}
123123
</div>
124124

125-
<div className="flex items-center justify-between">
126-
<div className="text-xs text-slate-400">
125+
<div className="flex flex-col sm:flex-row items-center justify-between gap-3 mt-4">
126+
<div className="text-xs text-slate-400 text-center sm:text-left">
127127
{connectedAddress ? (
128128
<span className="text-green-400">{t('wallet.selector.connected', { address: `${connectedAddress.slice(0, 8)}...${connectedAddress.slice(-4)}` })}</span>
129129
) : (

frontend/src/main.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
import { StrictMode } from 'react'
22
import { createRoot } from 'react-dom/client'
3+
4+
// Add global error listener for early debugging
5+
window.onerror = function(message, source, lineno, colno, error) {
6+
console.error('[Global Error]', { message, source, lineno, colno, error });
7+
};
8+
9+
window.onunhandledrejection = function(event) {
10+
console.error('[Unhandled Rejection]', event.reason);
11+
};
312
import './index.css'
413
import App from './App.tsx'
514
import { registerServiceWorker, listenToServiceWorkerMessages } from './utils/serviceWorkerRegistration'
615
import './i18n'
716
import { initFrontendConfigTracking } from './utils/configVersion'
817

18+
/*
919
// Track and version the active frontend configuration at startup
1020
initFrontendConfigTracking();
1121
@@ -31,6 +41,7 @@ registerServiceWorker().then((result) => {
3141
console.warn('[Main] Service worker registration failed:', result.error);
3242
}
3343
});
44+
*/
3445

3546
createRoot(document.getElementById('root')!).render(
3647
<StrictMode>

frontend/src/services/notificationService.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ export class NotificationService {
167167
// Send browser notification
168168
private async sendBrowserNotification(notification: PaymentNotification): Promise<void> {
169169
const title = this.getNotificationTitle(notification.type);
170-
const options: NotificationOptions = {
170+
const options: any = {
171171
body: notification.message,
172172
icon: '/icon-192x192.png',
173173
badge: '/icon-72x72.png',
@@ -319,7 +319,7 @@ export class NotificationService {
319319
}
320320

321321
// Get days until date
322-
private getDaysUntil(date: Date): number {
322+
private getDaysUntil(date: Date | string): number {
323323
const now = new Date();
324324
const targetDate = new Date(date);
325325
const diffTime = targetDate.getTime() - now.getTime();

frontend/src/services/pushNotificationService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ class PushNotificationService {
190190
return;
191191
}
192192

193-
const options: NotificationOptions = {
193+
const options: any = {
194194
body: data.body,
195195
icon: data.icon || '/icon-192x192.png',
196196
badge: data.badge || '/notification-badge.png',

0 commit comments

Comments
 (0)