Skip to content

Commit c594fec

Browse files
Copilot0xrinegade
andcommitted
Implement comprehensive routing system - unique URLs for all tabs and sub-tabs
Co-authored-by: 0xrinegade <[email protected]>
1 parent 1748f5e commit c594fec

File tree

16 files changed

+4519
-1725
lines changed

16 files changed

+4519
-1725
lines changed

package-lock.json

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

src/components/Layout.js

Lines changed: 31 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import React, { useContext, useState, useEffect } from 'react';
22
import Head from 'next/head';
33
import Image from 'next/image';
4+
import Link from 'next/link';
5+
import { useRouter } from 'next/router';
46
import { PhantomWalletButton } from './PhantomWalletButton';
57
import { usePhantomWallet } from '@/contexts/PhantomWalletProvider';
68
import { createLogger } from '@/utils/logger';
@@ -23,11 +25,10 @@ export default function Layout({ children, title = 'OpenSVM P2P Exchange' }) {
2325
network,
2426
selectedNetwork,
2527
setSelectedNetwork,
26-
activeTab,
27-
setActiveTab,
2828
networks
2929
} = useContext(AppContext);
3030

31+
const router = useRouter();
3132
const { connected, publicKey } = usePhantomWallet();
3233
const [showOnboarding, setShowOnboarding] = useState(false);
3334
const [currentLocale, setCurrentLocale] = useState('en');
@@ -76,18 +77,18 @@ export default function Layout({ children, title = 'OpenSVM P2P Exchange' }) {
7677

7778
// Top navbar items (most important sections)
7879
const topNavItems = [
79-
{ key: 'buy', label: 'BUY', icon: 'B' },
80-
{ key: 'sell', label: 'SELL', icon: 'S' },
81-
{ key: 'analytics', label: 'ANALYTICS', icon: '📊' },
82-
{ key: 'help', label: 'HELP', icon: '?' },
80+
{ key: 'buy', label: 'BUY', icon: 'B', href: '/buy' },
81+
{ key: 'sell', label: 'SELL', icon: 'S', href: '/sell' },
82+
{ key: 'analytics', label: 'ANALYTICS', icon: '📊', href: '/analytics' },
83+
{ key: 'help', label: 'HELP', icon: '?', href: '/help' },
8384
];
8485

8586
// Sidebar navigation items (secondary sections)
8687
const sidebarNavItems = [
87-
{ key: 'myoffers', label: 'MY OFFERS', icon: 'M' },
88-
{ key: 'disputes', label: 'DISPUTES', icon: 'D' },
89-
{ key: 'rewards', label: 'REWARDS', icon: '🎁' },
90-
{ key: 'profile', label: 'PROFILE', icon: 'P' },
88+
{ key: 'myoffers', label: 'MY OFFERS', icon: 'M', href: '/myoffers' },
89+
{ key: 'disputes', label: 'DISPUTES', icon: 'D', href: '/disputes' },
90+
{ key: 'rewards', label: 'REWARDS', icon: '🎁', href: '/rewards' },
91+
{ key: 'profile', label: 'PROFILE', icon: 'P', href: '/profile' },
9192
];
9293

9394
return (
@@ -124,44 +125,37 @@ export default function Layout({ children, title = 'OpenSVM P2P Exchange' }) {
124125
<nav className="desktop-nav">
125126
{/* Primary navigation items */}
126127
{topNavItems.map((item) => (
127-
<button
128+
<Link
128129
key={item.key}
129-
className={`nav-tab ${
130-
activeTab === item.key ? 'active' : ''
131-
}`}
132-
onClick={() => setActiveTab(item.key)}
130+
href={item.href}
131+
className={`nav-tab ${router.pathname === item.href ? 'active' : ''}`}
133132
>
134133
<span className="nav-label">{item.label}</span>
135-
</button>
134+
</Link>
136135
))}
137136

138137
{/* Secondary navigation items (previously in sidebar) */}
139138
{sidebarNavItems.map((item) => (
140-
<button
139+
<Link
141140
key={item.key}
142-
className={`nav-tab ${
143-
activeTab === item.key ? 'active' : ''
144-
}`}
145-
onClick={() => setActiveTab(item.key)}
141+
href={item.href}
142+
className={`nav-tab ${router.pathname === item.href ? 'active' : ''}`}
146143
>
147144
<span className="nav-label">{item.label}</span>
148-
</button>
145+
</Link>
149146
))}
150147
</nav>
151148

152149
{/* RIGHT SIDE: ALL HEADER CONTROLS */}
153150
<div className="header-controls">
154151
{/* PROFILE element - now properly in the flex container */}
155152
<div className="profile-nav">
156-
<a
157-
href="#"
158-
onClick={(e) => {
159-
e.preventDefault();
160-
setActiveTab('profile');
161-
}}
153+
<Link
154+
href="/profile"
155+
className={router.pathname === '/profile' ? 'active' : ''}
162156
>
163157
PROFILE
164-
</a>
158+
</Link>
165159
</div>
166160

167161
{/* Network selector */}
@@ -213,28 +207,24 @@ export default function Layout({ children, title = 'OpenSVM P2P Exchange' }) {
213207
<div className="mobile-nav-buttons">
214208
{/* Primary navigation items */}
215209
{topNavItems.map((item) => (
216-
<button
210+
<Link
217211
key={item.key}
218-
className={`mobile-nav-btn ${
219-
activeTab === item.key ? 'active' : ''
220-
}`}
221-
onClick={() => setActiveTab(item.key)}
212+
href={item.href}
213+
className={`mobile-nav-btn ${router.pathname === item.href ? 'active' : ''}`}
222214
>
223215
<span className="nav-label">{item.label}</span>
224-
</button>
216+
</Link>
225217
))}
226218

227219
{/* Secondary navigation items */}
228220
{sidebarNavItems.map((item) => (
229-
<button
221+
<Link
230222
key={item.key}
231-
className={`mobile-nav-btn ${
232-
activeTab === item.key ? 'active' : ''
233-
}`}
234-
onClick={() => setActiveTab(item.key)}
223+
href={item.href}
224+
className={`mobile-nav-btn ${router.pathname === item.href ? 'active' : ''}`}
235225
>
236226
<span className="nav-label">{item.label}</span>
237-
</button>
227+
</Link>
238228
))}
239229
</div>
240230
</nav>

src/components/UserProfile.js

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ const LoadingFallback = () => (
2424
* Optimized for performance with React.memo, lazy loading, and hooks
2525
* Now uses SafeWallet context to prevent null reference errors
2626
*/
27-
const UserProfile = ({ wallet: walletProp, network }) => {
27+
const UserProfile = ({ wallet: walletProp, network, initialTab = 'overview', onTabChange }) => {
2828
// Use safe wallet context if no wallet prop provided
2929
const contextWallet = useSafeWallet();
3030
const wallet = walletProp || contextWallet;
3131

3232
// Initialize all hooks first (Rules of Hooks)
33-
const [activeTab, setActiveTab] = useState('overview');
33+
const [activeTab, setActiveTab] = useState(initialTab);
3434
const [loading, setLoading] = useState(true);
3535
const [error, setError] = useState(null);
3636
const [profileData, setProfileData] = useState({
@@ -174,30 +174,43 @@ const UserProfile = ({ wallet: walletProp, network }) => {
174174
console.log('Saving settings:', newSettings);
175175
}, []);
176176

177+
// Handle tab change with URL update
178+
const handleTabChange = (newTab) => {
179+
setActiveTab(newTab);
180+
if (onTabChange) {
181+
onTabChange(newTab);
182+
}
183+
};
184+
185+
// Update tab when initialTab changes (for URL navigation)
186+
useEffect(() => {
187+
setActiveTab(initialTab);
188+
}, [initialTab]);
189+
177190
// Render tabs navigation - memoized to prevent recreation on each render
178191
const renderTabs = useMemo(() => (
179192
<div className="profile-tabs">
180193
<button
181194
className={`tab-button ${activeTab === 'overview' ? 'active' : ''}`}
182-
onClick={() => setActiveTab('overview')}
195+
onClick={() => handleTabChange('overview')}
183196
>
184197
Overview
185198
</button>
186199
<button
187200
className={`tab-button ${activeTab === 'transactions' ? 'active' : ''}`}
188-
onClick={() => setActiveTab('transactions')}
201+
onClick={() => handleTabChange('transactions')}
189202
>
190203
Transactions
191204
</button>
192205
<button
193206
className={`tab-button ${activeTab === 'stats' ? 'active' : ''}`}
194-
onClick={() => setActiveTab('stats')}
207+
onClick={() => handleTabChange('stats')}
195208
>
196209
Statistics
197210
</button>
198211
<button
199212
className={`tab-button ${activeTab === 'settings' ? 'active' : ''}`}
200-
onClick={() => setActiveTab('settings')}
213+
onClick={() => handleTabChange('settings')}
201214
>
202215
Settings
203216
</button>
@@ -323,7 +336,9 @@ UserProfile.propTypes = {
323336
}),
324337
network: PropTypes.shape({
325338
name: PropTypes.string
326-
})
339+
}),
340+
initialTab: PropTypes.string,
341+
onTabChange: PropTypes.func
327342
};
328343

329344
// Export memoized component to prevent unnecessary re-renders

src/contexts/AppContext.js

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ export const AppContext = createContext({
77
networks: {},
88
selectedNetwork: 'solana',
99
setSelectedNetwork: () => {},
10-
activeTab: 'buy',
11-
setActiveTab: () => {},
1210
});
1311

1412
// SVM Networks configuration
@@ -58,7 +56,6 @@ const SVM_NETWORKS = {
5856
export const AppContextProvider = ({ children }) => {
5957
// State for selected network
6058
const [selectedNetwork, setSelectedNetwork] = useState('solana');
61-
const [activeTab, setActiveTab] = useState('buy'); // 'buy', 'sell', 'myoffers', 'disputes', 'profile'
6259

6360
// Get network configuration
6461
const network = SVM_NETWORKS[selectedNetwork];
@@ -69,9 +66,7 @@ export const AppContextProvider = ({ children }) => {
6966
networks: SVM_NETWORKS,
7067
selectedNetwork,
7168
setSelectedNetwork,
72-
activeTab,
73-
setActiveTab,
74-
}), [network, selectedNetwork, activeTab]);
69+
}), [network, selectedNetwork]);
7570

7671
return (
7772
<AppContext.Provider value={contextValue}>

src/pages/404.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import React, { useEffect } from 'react';
2+
import { useRouter } from 'next/router';
3+
import Link from 'next/link';
4+
5+
export default function Custom404() {
6+
const router = useRouter();
7+
8+
return (
9+
<div className="error-page">
10+
<div className="error-content">
11+
<h1>404 - Page Not Found</h1>
12+
<p>The page you're looking for doesn't exist.</p>
13+
<div className="error-actions">
14+
<Link href="/buy" className="button button-primary">
15+
Go to Home
16+
</Link>
17+
<button
18+
className="button button-secondary"
19+
onClick={() => router.back()}
20+
>
21+
Go Back
22+
</button>
23+
</div>
24+
</div>
25+
</div>
26+
);
27+
}

src/pages/_app.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import '@/styles/TransactionAnalytics.css'; // Transaction Analytics component s
2424
import '@/styles/TransactionProgressIndicator.css'; // Transaction Progress Indicator component styles
2525
import '@/styles/AnalyticsDashboard.css'; // Analytics Dashboard component styles
2626
import '@/styles/pwa.css'; // PWA-specific styles
27+
import '@/styles/routing.css'; // Routing and navigation styles
2728

2829
// Import context
2930
import { AppContextProvider } from '@/contexts/AppContext';

src/pages/analytics.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import React from 'react';
2+
import dynamic from 'next/dynamic';
3+
import { createLazyComponent } from '@/utils/lazyLoading';
4+
5+
// Lazy load components
6+
const AnalyticsDashboard = createLazyComponent(
7+
() => import('@/components/AnalyticsDashboard'),
8+
{
9+
fallback: <div className="loading-analytics">Loading analytics...</div>
10+
}
11+
);
12+
13+
export default function AnalyticsPage() {
14+
return <AnalyticsDashboard />;
15+
}

src/pages/buy.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import React from 'react';
2+
import dynamic from 'next/dynamic';
3+
import { createLazyComponent } from '@/utils/lazyLoading';
4+
5+
// Lazy load components
6+
const OfferList = createLazyComponent(
7+
() => import('@/components/OfferList'),
8+
{
9+
fallback: <div className="loading-offer-list">Loading offers...</div>,
10+
retryDelay: 1000,
11+
maxRetries: 3
12+
}
13+
);
14+
15+
export default function BuyPage() {
16+
return <OfferList type="buy" />;
17+
}

src/pages/disputes.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import React from 'react';
2+
import dynamic from 'next/dynamic';
3+
import { createLazyComponent } from '@/utils/lazyLoading';
4+
5+
// Lazy load components
6+
const DisputeResolution = createLazyComponent(
7+
() => import('@/components/DisputeResolution'),
8+
{
9+
fallback: <div className="loading-disputes">Loading disputes...</div>
10+
}
11+
);
12+
13+
export default function DisputesPage() {
14+
return <DisputeResolution />;
15+
}

src/pages/help.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import React from 'react';
2+
3+
export default function HelpPage() {
4+
return (
5+
<div className="help-page">
6+
<h1>Help & Support</h1>
7+
<div className="help-content">
8+
<section>
9+
<h2>Getting Started</h2>
10+
<p>Learn how to use the OpenSVM P2P Exchange platform.</p>
11+
</section>
12+
13+
<section>
14+
<h2>Trading Guide</h2>
15+
<p>Step-by-step instructions for buying and selling SOL.</p>
16+
</section>
17+
18+
<section>
19+
<h2>FAQ</h2>
20+
<p>Frequently asked questions and answers.</p>
21+
</section>
22+
23+
<section>
24+
<h2>Contact Support</h2>
25+
<p>Get in touch with our support team for assistance.</p>
26+
</section>
27+
</div>
28+
</div>
29+
);
30+
}

0 commit comments

Comments
 (0)