Skip to content

Commit b56e75f

Browse files
committed
unit tests
1 parent 6dfcb7f commit b56e75f

8 files changed

Lines changed: 622 additions & 141 deletions

File tree

docs/adding-page-routes.md

Lines changed: 100 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,21 @@ Every route in the client is declared in a **single source of truth** at the top
1313
import { createBrowserRouter, RouterProvider } from 'react-router';
1414
import HomePage from './pages/HomePage';
1515
import NotFoundPage from './pages/NotFoundPage';
16-
import AboutPage from './pages/AboutPage'; // ← new import
16+
import AboutPage from './pages/AboutPage'; // ← new import
1717

1818
const router = createBrowserRouter([
19-
{
20-
path: '/',
21-
element: <HomePage />,
22-
},
23-
{
24-
path: '/about', // ← new public route
25-
element: <AboutPage />,
26-
},
27-
{
28-
path: '*', // catch-all stays last
29-
element: <NotFoundPage />,
30-
},
19+
{
20+
path: '/',
21+
element: <HomePage />,
22+
},
23+
{
24+
path: '/about', // ← new public route
25+
element: <AboutPage />,
26+
},
27+
{
28+
path: '*', // catch-all stays last
29+
element: <NotFoundPage />,
30+
},
3131
]);
3232
```
3333

@@ -42,10 +42,10 @@ Key things to know:
4242

4343
## File naming convention for page components
4444

45-
| What | Convention | Example |
46-
|---|---|---|
47-
| File location | `src/pages/` | `src/pages/HomePage.tsx` |
48-
| File name | PascalCase + `Page` suffix | `AboutPage.tsx` |
45+
| What | Convention | Example |
46+
| ------------------ | ----------------------------------------------- | ------------------------------------- |
47+
| File location | `src/pages/` | `src/pages/HomePage.tsx` |
48+
| File name | PascalCase + `Page` suffix | `AboutPage.tsx` |
4949
| Exported component | Default export of a function named `<Name>Page` | `export default function AboutPage()` |
5050

5151
The component itself uses `export default function <Name>Page()` — not a named export, and not an arrow const. This keeps imports straightforward and matches every existing page in `src/pages/`:
@@ -67,16 +67,16 @@ Top-level page components take **no props**. They own their own layout, fetching
6767
```tsx
6868
// src/pages/AboutPage.tsx
6969
export default function AboutPage() {
70-
return (
71-
<main className="min-h-screen px-6 py-16 text-white">
72-
<h1 className="font-grotesque text-4xl font-black">
73-
About Access Layer
74-
</h1>
75-
<p className="mt-4 font-jakarta text-white/70">
76-
Access Layer is a Stellar-native creator keys marketplace.
77-
</p>
78-
</main>
79-
);
70+
return (
71+
<main className="min-h-screen px-6 py-16 text-white">
72+
<h1 className="font-grotesque text-4xl font-black">
73+
About Access Layer
74+
</h1>
75+
<p className="mt-4 font-jakarta text-white/70">
76+
Access Layer is a Stellar-native creator keys marketplace.
77+
</p>
78+
</main>
79+
);
8080
}
8181
```
8282

@@ -111,31 +111,31 @@ import { Navigate, useLocation } from 'react-router';
111111
import PendingOnboardingPlaceholder from '@/components/common/PendingOnboardingPlaceholder';
112112

113113
interface RequireAuthProps {
114-
children: ReactNode;
114+
children: ReactNode;
115115
}
116116

117117
export default function RequireAuth({ children }: RequireAuthProps) {
118-
const { isConnected, isConnecting } = useAccount();
119-
const location = useLocation();
120-
121-
// Show the placeholder while a fresh wallet connection is in flight
122-
// so the user isn't redirected to "/" mid-connect. Once it resolves,
123-
// isConnected flips to true and we render the protected page below.
124-
// Note: isReconnecting is intentionally NOT in this branch — a
125-
// reconnect of a prior session keeps the user authenticated, so
126-
// rendering the protected page during a reconnect is fine.
127-
if (isConnecting) {
128-
return <PendingOnboardingPlaceholder />;
129-
}
130-
131-
if (!isConnected) {
132-
// Send unauthenticated users back to the homepage while preserving
133-
// the path they tried to reach so a future flow can deep-link them
134-
// back here after they connect.
135-
return <Navigate to="/" state={{ from: location.pathname }} replace />;
136-
}
137-
138-
return <>{children}</>;
118+
const { isConnected, isConnecting } = useAccount();
119+
const location = useLocation();
120+
121+
// Show the placeholder while a fresh wallet connection is in flight
122+
// so the user isn't redirected to "/" mid-connect. Once it resolves,
123+
// isConnected flips to true and we render the protected page below.
124+
// Note: isReconnecting is intentionally NOT in this branch — a
125+
// reconnect of a prior session keeps the user authenticated, so
126+
// rendering the protected page during a reconnect is fine.
127+
if (isConnecting) {
128+
return <PendingOnboardingPlaceholder />;
129+
}
130+
131+
if (!isConnected) {
132+
// Send unauthenticated users back to the homepage while preserving
133+
// the path they tried to reach so a future flow can deep-link them
134+
// back here after they connect.
135+
return <Navigate to="/" state={{ from: location.pathname }} replace />;
136+
}
137+
138+
return <>{children}</>;
139139
}
140140
```
141141

@@ -147,28 +147,28 @@ import RequireAuth from './components/auth/RequireAuth';
147147
import DashboardPage from './pages/DashboardPage';
148148

149149
const router = createBrowserRouter([
150-
{ path: '/', element: <HomePage /> },
151-
{
152-
path: '/dashboard',
153-
// Public route → element: <DashboardPage />
154-
// Protected route → wrap in <RequireAuth>:
155-
element: (
156-
<RequireAuth>
157-
<DashboardPage />
158-
</RequireAuth>
159-
),
160-
},
161-
{ path: '*', element: <NotFoundPage /> },
150+
{ path: '/', element: <HomePage /> },
151+
{
152+
path: '/dashboard',
153+
// Public route → element: <DashboardPage />
154+
// Protected route → wrap in <RequireAuth>:
155+
element: (
156+
<RequireAuth>
157+
<DashboardPage />
158+
</RequireAuth>
159+
),
160+
},
161+
{ path: '*', element: <NotFoundPage /> },
162162
]);
163163
```
164164

165165
### Choosing which auth check to use
166166

167-
| Use case | Check |
168-
|---|---|
169-
| The page reads or writes Stellar assets (keys, trades, portfolio) | `useAccount().isConnected` from wagmi |
170-
| The page reads or writes user-profile data via the backend REST API | `authService.isAuthenticated()` |
171-
| Both | Call both. Render the placeholder until both resolve; redirect if either is false. |
167+
| Use case | Check |
168+
| ------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
169+
| The page reads or writes Stellar assets (keys, trades, portfolio) | `useAccount().isConnected` from wagmi |
170+
| The page reads or writes user-profile data via the backend REST API | `authService.isAuthenticated()` |
171+
| Both | Call both. Render the placeholder until both resolve; redirect if either is false. |
172172

173173
Don't mix the two states in a single component without documenting which is the source of truth for that page — that's the kind of bug that's hard to spot in review.
174174

@@ -177,7 +177,7 @@ Don't mix the two states in a single component without documenting which is the
177177
Two pre-existing repo facts you should know before shipping a wagmi-based guard:
178178

179179
1. **`<Web3Provider>` is not currently mounted above `<App />` in `src/main.tsx`.** As of writing this guide, `main.tsx` renders `<App />` directly inside `<StrictMode>`. Any wagmi hook — including `useAccount` inside `RequireAuth` — will throw at runtime because the `WagmiProvider` context is missing. Wiring `<Web3Provider>` here is an app-level change and should be tracked separately; reference the tracking issue in your route PR description rather than embedding the wiring fix in your route PR.
180-
2. **Wagmi has a transient `isConnecting` state** while a fresh wallet handshake is in flight. During this window `isConnected` is `false`, but redirecting the user mid-connect would bounce them away. The example above handles this by rendering `PendingOnboardingPlaceholder` for the `isConnecting` branch — copy that pattern verbatim. (Note: `isReconnecting` is *not* included in that branch — a reconnect of a prior, already-authenticated session keeps the user authenticated, so rendering the protected page during a reconnect is fine and avoids a UX flash.)
180+
2. **Wagmi has a transient `isConnecting` state** while a fresh wallet handshake is in flight. During this window `isConnected` is `false`, but redirecting the user mid-connect would bounce them away. The example above handles this by rendering `PendingOnboardingPlaceholder` for the `isConnecting` branch — copy that pattern verbatim. (Note: `isReconnecting` is _not_ included in that branch — a reconnect of a prior, already-authenticated session keeps the user authenticated, so rendering the protected page during a reconnect is fine and avoids a UX flash.)
181181

182182
If your guard only uses `authService.isAuthenticated()` (no wagmi hooks), neither caveat applies — the helper reads `localStorage` directly.
183183

@@ -197,23 +197,23 @@ import { Link } from 'react-router';
197197
import { Button } from '@/components/ui/button';
198198

199199
export default function AboutPage() {
200-
return (
201-
<main className="min-h-screen bg-[#06111f] px-6 py-16 text-white">
202-
<h1 className="font-grotesque text-5xl font-black tracking-tight">
203-
About Access Layer
204-
</h1>
205-
<p className="mt-6 max-w-2xl font-jakarta text-lg text-white/70">
206-
Access Layer is a Stellar-native creator keys marketplace built on
207-
the open AccessLayer protocol.
208-
</p>
209-
210-
<div className="mt-8">
211-
<Button asChild>
212-
<Link to="/">Back to marketplace</Link>
213-
</Button>
214-
</div>
215-
</main>
216-
);
200+
return (
201+
<main className="min-h-screen bg-[#06111f] px-6 py-16 text-white">
202+
<h1 className="font-grotesque text-5xl font-black tracking-tight">
203+
About Access Layer
204+
</h1>
205+
<p className="mt-6 max-w-2xl font-jakarta text-lg text-white/70">
206+
Access Layer is a Stellar-native creator keys marketplace built on
207+
the open AccessLayer protocol.
208+
</p>
209+
210+
<div className="mt-8">
211+
<Button asChild>
212+
<Link to="/">Back to marketplace</Link>
213+
</Button>
214+
</div>
215+
</main>
216+
);
217217
}
218218
```
219219

@@ -225,12 +225,12 @@ Add the import and a new entry to the router array in `src/App.tsx`:
225225
// src/App.tsx
226226
import HomePage from './pages/HomePage';
227227
import NotFoundPage from './pages/NotFoundPage';
228-
import AboutPage from './pages/AboutPage'; // ← added
228+
import AboutPage from './pages/AboutPage'; // ← added
229229

230230
const router = createBrowserRouter([
231-
{ path: '/', element: <HomePage /> },
232-
{ path: '/about', element: <AboutPage /> }, // ← added
233-
{ path: '*', element: <NotFoundPage /> },
231+
{ path: '/', element: <HomePage /> },
232+
{ path: '/about', element: <AboutPage /> }, // ← added
233+
{ path: '*', element: <NotFoundPage /> },
234234
]);
235235
```
236236

@@ -243,8 +243,8 @@ import { Link } from 'react-router';
243243

244244
// inside the JSX you return
245245
<Link to="/about" className="font-jakarta text-amber-300 hover:underline">
246-
About this project
247-
</Link>
246+
About this project
247+
</Link>;
248248
```
249249

250250
### 4. Verify locally
@@ -261,12 +261,13 @@ If `pnpm build` succeeds and `/about` renders the page with a working "Back to m
261261

262262
## Key files at a glance
263263

264-
| File | Purpose |
265-
|---|---|
266-
| `src/App.tsx` | The single source of truth for routing — the only place routes are registered. |
267-
| `src/pages/` | Folder where every page component lives. One file per page, PascalCase + `Page` suffix, default export. |
268-
| `src/components/auth/RequireAuth.tsx` | The recommended wrapper for auth-protected pages. Create it the first time a protected route is added. |
269-
| `src/main.tsx` | Mounts `<App />` inside React's `createRoot`. **Currently does not wrap `<App />` in `Web3Provider`** — must be updated the first time a wagmi-based route guard lands. |
270-
| `src/providers/Web3Provider.tsx` | Provides `WagmiProvider` + `QueryClientProvider`. Any auth wrapper that uses `useAccount` only works after this provider is mounted above the router in `main.tsx`. |
271-
| `CONTRIBUTING.md` | High-level project conventions — read this alongside this guide. |
272-
| `docs/api-layer.md` | Sibling guide covering how to add backend/API endpoints. |
264+
| File | Purpose |
265+
| ---------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
266+
| `src/App.tsx` | The single source of truth for routing — the only place routes are registered. |
267+
| `src/pages/` | Folder where every page component lives. One file per page, PascalCase + `Page` suffix, default export. |
268+
| `src/components/auth/RequireAuth.tsx` | The recommended wrapper for auth-protected pages. Create it the first time a protected route is added. |
269+
| `src/main.tsx` | Mounts `<App />` inside React's `createRoot`. **Currently does not wrap `<App />` in `Web3Provider`** — must be updated the first time a wagmi-based route guard lands. |
270+
| `src/providers/Web3Provider.tsx` | Provides `WagmiProvider` + `QueryClientProvider`. Any auth wrapper that uses `useAccount` only works after this provider is mounted above the router in `main.tsx`. |
271+
| `CONTRIBUTING.md` | High-level project conventions — read this alongside this guide. |
272+
| `docs/api-layer.md` | Sibling guide covering how to add backend/API endpoints. |
273+
| [docs/shared-components.md](file:///Users/marvellous/Desktop/accesslayer-client/docs/shared-components.md) | Guide to the project's shared UI component library, key props, and styling. |

0 commit comments

Comments
 (0)