Skip to content

Commit eb539bf

Browse files
committed
Refactor React app structure: replace AppShell with TopBar for layout, enhance theme handling, and improve authentication flows. Update README for clarity on project features and setup. Add user account management capabilities including profile deletion and password change. Enhance UI components with new logo and alert designs.
1 parent 8eb0a81 commit eb539bf

27 files changed

+901
-399
lines changed

AGENTS.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,16 @@ import { Kysely } from 'kysely';
3636
```
3737
src/
3838
├── react-app/ # React frontend
39+
│ ├── assets/ # Static assets (logos, images)
40+
│ │ ├── logo-light.svg # Logo for light theme
41+
│ │ └── logo-dark.svg # Logo for dark theme
3942
│ ├── components/
4043
│ │ ├── auth/ # Auth UI (LoginForm, ProtectedRoute, AuthOverlay)
4144
│ │ ├── ui/ # shadcn/ui components
42-
│ │ ├── AppShell.tsx # Sidebar + header layout
43-
│ │ └── theme-provider.tsx
45+
│ │ ├── TopBar.tsx # Full-width header with logo and user menu
46+
│ │ ├── Logo.tsx # Theme-aware logo component
47+
│ │ ├── ModeToggle.tsx # Dark/light theme toggle
48+
│ │ └── theme-provider.tsx # Theme context with URL param support
4449
│ ├── pages/ # Route pages
4550
│ │ ├── auth/ # SignUp, VerifyEmail
4651
│ │ ├── Home.tsx # Protected home
@@ -130,8 +135,8 @@ pnpm deploy:production # Deploy to Cloudflare
130135

131136
### Add a New Page
132137
1. Create `src/react-app/pages/MyPage.tsx`
133-
2. Add route in `src/react-app/App.tsx`
134-
3. Add nav item in `src/react-app/components/AppShell.tsx`
138+
2. Add route in `src/react-app/App.tsx` wrapped with `<ProtectedRoute>` and `<TopBar>` if needed
139+
3. Add navigation as appropriate for your app (TopBar doesn't include nav by default)
135140

136141
### Add an API Endpoint
137142
1. Add route in `src/worker/index.ts`

README.md

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
[![CI](https://github.com/claudio-silva/cloudflare-fullstack-starter/actions/workflows/ci.yml/badge.svg)](https://github.com/claudio-silva/cloudflare-fullstack-starter/actions/workflows/ci.yml)
44

5-
A production-ready, agent-friendly full-stack foundation built to run on Cloudflare’s edge platform.
5+
A full-stack Cloudflare starter that gets you building real features immediately — production-ready, optimized for AI agents, and built to scale on Cloudflare’s global edge network.
66

7-
Clone it, set it up, and start building. Authentication, database, UI, CLI, and deployment are all ready to go.
7+
Install it and start building. Authentication, database, UI, CLI, and deployment are all ready to go.
88

99
A great starting point for your next MVP or SaaS app.
1010

@@ -30,9 +30,9 @@ This means you can build, launch, and validate your idea without upfront infrast
3030

3131
Cloudflare gives you remarkable power at the edge, but it isn’t a traditional hosting platform. Most mainstream web frameworks don’t run on Workers out of the box, and adapting them to Cloudflare’s request-driven runtime can feel unfamiliar if you’re used to environments that hide the wiring. That early friction slows down the part you actually care about: building something new.
3232

33-
Agentic coding helps, but unfamiliar runtimes are still a weak spot. On platforms like Cloudflare, agents rarely succeed on the first attempt. They burn cycles rebuilding boilerplate, fixing type and lint errors, debugging auth flows, smoothing out theme flicker, tweaking configs, and juggling multi-environment setups — all the fragile infrastructure work that slows real development. And every new project means repeating the same setup steps.
33+
Agentic coding helps, but unfamiliar runtimes are still a weak spot. When developing for platforms like Cloudflare, agents rarely succeed on the first attempt. They burn cycles rebuilding boilerplate, fixing type and lint errors, debugging auth flows, smoothing out theme flicker, tweaking configs, and juggling multi-environment setups — all the fragile infrastructure work that slows real development. And every new project means repeating the same setup steps.
3434

35-
This starter removes that overhead entirely. From the moment you create a project from this template — or even ask your coding agent to install it for you — you begin with a fully working, production-grade foundation:
35+
This starter removes that overhead entirely. From the moment you create a project from this template — or even ask your coding agent to install it for you — you begin with a fully working, production-ready foundation:
3636

3737
- **Complete Auth Flow** — Sign up, email verification, login, logout, profile management, powered by [Better Auth](https://www.better-auth.com/)
3838
- **Database Ready**[Cloudflare D1](https://developers.cloudflare.com/d1/) with migrations, used for auth but extensible
@@ -189,20 +189,25 @@ If wrangler is logged in, you can do it from the command line:
189189
- CLI user management (create, list, view, edit, delete, activate)
190190

191191
### UI Features
192-
- Responsive sidebar + header layout
193-
- Avatar dropdown with Profile/Logout
194-
- Dark/light theme with FOUC prevention
192+
- Clean top header bar with logo and user menu
193+
- Avatar dropdown with user info, Profile, and Sign Out
194+
- Dark/light theme toggle with FOUC prevention
195+
- Theme-aware logo component (auto-switches light/dark)
196+
- Theme can be passed via URL parameter for cross-app navigation
195197
- 50+ shadcn/ui components ready to use
196198

197199
## Project Structure
198200

199201
```
200202
├── src/
201203
│ ├── react-app/ # React frontend
204+
│ │ ├── assets/ # Static assets (logo-light.svg, logo-dark.svg)
202205
│ │ ├── components/
203-
│ │ │ ├── auth/ # Auth components (LoginForm, ProtectedRoute)
206+
│ │ │ ├── auth/ # Auth components (LoginForm, ProtectedRoute, AuthOverlay)
204207
│ │ │ ├── ui/ # shadcn/ui components
205-
│ │ │ └── AppShell.tsx # Sidebar + header layout
208+
│ │ │ ├── TopBar.tsx # Header with logo and user menu
209+
│ │ │ ├── Logo.tsx # Theme-aware logo
210+
│ │ │ └── ModeToggle.tsx # Theme toggle
206211
│ │ ├── pages/ # Route pages (Home, Profile, auth/)
207212
│ │ └── lib/auth/ # Better Auth client
208213
│ └── worker/ # Hono API backend
@@ -220,18 +225,41 @@ If wrangler is logged in, you can do it from the command line:
220225

221226
### Auth Management
222227

228+
Manage users directly from your terminal — useful for creating admin accounts, debugging, or scripting.
229+
230+
> ⚠️ When managing local users, **the dev server must be running** (`npm run dev`) for these commands to work. They communicate with the API at `http://localhost:5173`.
231+
223232
```bash
233+
# List all users (shows email, name, verification status, creation date)
224234
npm run auth list-users
235+
npm run auth list-users -- --limit 10 # limit results
236+
npm run auth list-users -- --search "john" # search by email or name
237+
238+
# Create a new user (useful for seeding admin accounts)
225239
npm run auth create-user -- -u [email protected] -p password123
240+
npm run auth create-user -- -u [email protected] -p password123 -n "Admin User"
241+
242+
# View detailed user info (includes account provider, sessions, timestamps)
226243
npm run auth show-user -- -u [email protected]
227-
npm run auth edit-user -- -u [email protected] -n "Admin User"
228-
npm run auth activate-user -- -u [email protected] -s on
244+
245+
# Edit user details
246+
npm run auth edit-user -- -u [email protected] -n "New Name"
247+
npm run auth edit-user -- -u [email protected] -e [email protected]
248+
npm run auth edit-user -- -u [email protected] -p newpassword123
249+
250+
# Activate or deactivate a user account
251+
npm run auth activate-user -- -u [email protected] -s on # activate
252+
npm run auth activate-user -- -u [email protected] -s off # deactivate
253+
254+
# Delete a user (removes user, accounts, and sessions)
229255
npm run auth delete-user -- -u [email protected]
256+
npm run auth delete-user -- --all # delete ALL users (use with caution)
230257
```
231258

232-
Or call directly:
259+
For remote environments, use the `--env` flag and set `CLI_ADMIN_EMAIL`/`CLI_ADMIN_PASSWORD` in your `.env.*` file:
233260
```bash
234-
./bin/auth list-users --env local
261+
npm run auth list-users -- --env preview
262+
npm run auth list-users -- --env production
235263
```
236264

237265
### Database
@@ -295,8 +323,8 @@ Search and replace these placeholders:
295323
### Adding Pages
296324
1. Create page in `src/react-app/pages/`
297325
2. Add route in `src/react-app/App.tsx`
298-
3. Wrap with `<ProtectedRoute>` if auth required
299-
4. Add to sidebar in `src/react-app/components/AppShell.tsx`
326+
3. Wrap with `<ProtectedRoute>` and `<TopBar>` if auth required
327+
4. Add navigation as appropriate for your app design
300328

301329
### Email Provider
302330
Replace `createResendEmailSender` in `src/worker/middleware/auth.ts` with your provider implementing the `EmailSender` interface.

bin/init.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import fs from "fs";
22
import path from "path";
33
import readline from "readline";
44
import { execSync } from "child_process";
5+
import { fileURLToPath } from "url";
6+
7+
const __filename = fileURLToPath(import.meta.url);
8+
const __dirname = path.dirname(__filename);
59

610
type Answers = {
711
projectName: string;

index.html

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,31 @@
11
<!doctype html>
2-
<html lang="en" class="dark">
2+
<html lang="en">
33
<head>
44
<meta charset="UTF-8" />
55
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7-
<title>Vite + React + TS</title>
7+
<title>My App</title>
8+
<script>
9+
// Apply theme BEFORE React renders to prevent flash of unstyled content (FOUC)
10+
(function() {
11+
const storageKey = 'app-theme';
12+
const theme = localStorage.getItem(storageKey) || 'light';
13+
14+
let actualTheme = theme;
15+
if (theme === 'system') {
16+
actualTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
17+
}
18+
19+
document.documentElement.classList.add(actualTheme);
20+
21+
// Apply background color immediately to prevent white/dark flash
22+
if (actualTheme === 'dark') {
23+
document.documentElement.style.backgroundColor = 'hsl(0 0% 3.9%)';
24+
} else {
25+
document.documentElement.style.backgroundColor = 'hsl(0 0% 100%)';
26+
}
27+
})();
28+
</script>
829
</head>
930

1031
<body>

package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@
2525
"deploy:preview": "wrangler deploy --env preview",
2626
"deploy:production": "wrangler deploy --env production",
2727
"cf-typegen": "wrangler types",
28-
"db:migrate:local": "wrangler d1 migrations apply starter --local",
29-
"db:migrate:preview": "wrangler d1 migrations apply starter --env preview",
30-
"db:migrate:production": "wrangler d1 migrations apply starter --env production",
31-
"db:seed:local": "wrangler d1 execute starter --local --file=./migrations/0002_seed_data.sql",
32-
"db:seed:preview": "wrangler d1 execute starter --env preview --file=./migrations/0002_seed_data.sql",
33-
"db:seed:production": "wrangler d1 execute starter --env production --file=./migrations/0002_seed_data.sql",
28+
"db:migrate:local": "wrangler d1 migrations apply cloudflare-fullstack-starter --local",
29+
"db:migrate:preview": "wrangler d1 migrations apply cloudflare-fullstack-starter --env preview",
30+
"db:migrate:production": "wrangler d1 migrations apply cloudflare-fullstack-starter --env production",
31+
"db:seed:local": "wrangler d1 execute cloudflare-fullstack-starter --local --file=./migrations/0002_seed_data.sql",
32+
"db:seed:preview": "wrangler d1 execute cloudflare-fullstack-starter --env preview --file=./migrations/0002_seed_data.sql",
33+
"db:seed:production": "wrangler d1 execute cloudflare-fullstack-starter --env production --file=./migrations/0002_seed_data.sql",
3434
"init": "tsx bin/init.ts",
3535
"auth": "tsx bin/auth.ts"
3636
},

src/cli/utils/output.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ import { format } from "date-fns";
22

33
type TableRow = Record<string, unknown>;
44

5+
type Timestamp = number | string | Date;
6+
57
type PrintableAccount = {
68
accountId: string;
79
providerId: string;
810
hasPassword: boolean;
9-
createdAt: number | Date;
10-
updatedAt: number | Date;
11+
createdAt: Timestamp;
12+
updatedAt: Timestamp;
1113
};
1214

1315
type PrintableUser = {
@@ -16,8 +18,8 @@ type PrintableUser = {
1618
name?: string | null;
1719
image?: string | null;
1820
emailVerified?: boolean | number;
19-
createdAt: number | Date;
20-
updatedAt: number | Date;
21+
createdAt: Timestamp;
22+
updatedAt: Timestamp;
2123
account?: PrintableAccount | null;
2224
sessions?: number;
2325
};
@@ -104,7 +106,14 @@ export function printWarning(message: string): void {
104106
console.warn(`⚠️ ${message}`);
105107
}
106108

107-
export function formatTimestamp(timestamp: number): string {
109+
export function formatTimestamp(timestamp: number | string | Date): string {
110+
// Handle various timestamp formats from API/database
111+
if (typeof timestamp === "string") {
112+
return format(new Date(timestamp), "yyyy-MM-dd HH:mm:ss");
113+
}
114+
if (timestamp instanceof Date) {
115+
return format(timestamp, "yyyy-MM-dd HH:mm:ss");
116+
}
108117
return format(new Date(timestamp), "yyyy-MM-dd HH:mm:ss");
109118
}
110119

@@ -114,6 +123,6 @@ export function formatUsersForTable(users: PrintableUser[]): UserRow[] {
114123
email: user.email,
115124
name: user.name || "N/A",
116125
verified: user.emailVerified ? "Yes" : "No",
117-
created: formatTimestamp(typeof user.createdAt === "number" ? user.createdAt : user.createdAt.getTime()),
126+
created: formatTimestamp(user.createdAt),
118127
}));
119128
}

src/react-app/App.css

Lines changed: 1 addition & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1 @@
1-
#root {
2-
max-width: 1280px;
3-
margin: 0 auto;
4-
padding: 2rem;
5-
text-align: center;
6-
}
7-
8-
.logo {
9-
height: 6em;
10-
padding: 1.5em;
11-
will-change: filter;
12-
transition: filter 300ms;
13-
}
14-
.logo:hover {
15-
filter: drop-shadow(0 0 2em #646cffaa);
16-
}
17-
.logo.react:hover {
18-
filter: drop-shadow(0 0 2em #61dafbaa);
19-
}
20-
.logo.cloudflare:hover {
21-
filter: drop-shadow(0 0 2em #f6821faa);
22-
}
23-
.logo.cloudflare {
24-
height: 9em;
25-
padding-left: 0;
26-
position: relative;
27-
left: -1em;
28-
}
29-
30-
@keyframes logo-spin {
31-
from {
32-
transform: rotate(0deg);
33-
}
34-
to {
35-
transform: rotate(360deg);
36-
}
37-
}
38-
39-
@media (prefers-reduced-motion: no-preference) {
40-
a:nth-of-type(2) .logo {
41-
animation: logo-spin infinite 20s linear;
42-
}
43-
}
44-
45-
.card {
46-
padding: 2em;
47-
}
48-
49-
.read-the-docs {
50-
color: #888;
51-
}
1+
/* App-specific styles */

src/react-app/App.tsx

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,79 @@
1-
import { Routes, Route } from "react-router-dom";
2-
import { AppShell } from "@/components/AppShell";
1+
import { Routes, Route, useLocation } from "react-router-dom";
2+
import { TopBar } from "@/components/TopBar";
33
import { ProtectedRoute } from "@/components/auth/ProtectedRoute";
44
import { Home } from "@/pages/Home";
55
import { Profile } from "@/pages/Profile";
66
import { SignUp } from "@/pages/auth/SignUp";
77
import { VerifyEmail } from "@/pages/auth/VerifyEmail";
8+
import { authClient } from "@/lib/auth/client";
89
import "./App.css";
910

10-
function App() {
11+
function AppContent() {
12+
const location = useLocation();
13+
const { data: session } = authClient.useSession();
14+
15+
// Auth pages don't use the TopBar layout
16+
const isAuthPage = location.pathname === "/signup" || location.pathname === "/verify-email";
17+
18+
// Only show TopBar if not on an auth page AND user is authenticated
19+
// This prevents flash of TopBar while checking auth
20+
const shouldShowTopBar = !isAuthPage && !!session;
21+
1122
return (
1223
<Routes>
24+
{/* Public auth routes - no TopBar */}
1325
<Route path="/signup" element={<SignUp />} />
1426
<Route path="/verify-email" element={<VerifyEmail />} />
1527

28+
{/* Protected routes with TopBar */}
1629
<Route
1730
path="/"
1831
element={
1932
<ProtectedRoute>
20-
<AppShell>
33+
{shouldShowTopBar ? (
34+
<TopBar>
35+
<Home />
36+
</TopBar>
37+
) : (
2138
<Home />
22-
</AppShell>
39+
)}
2340
</ProtectedRoute>
2441
}
2542
/>
2643
<Route
2744
path="/profile"
2845
element={
2946
<ProtectedRoute>
30-
<AppShell>
47+
{shouldShowTopBar ? (
48+
<TopBar>
49+
<Profile />
50+
</TopBar>
51+
) : (
3152
<Profile />
32-
</AppShell>
53+
)}
3354
</ProtectedRoute>
3455
}
3556
/>
3657
<Route
3758
path="*"
3859
element={
3960
<ProtectedRoute>
40-
<AppShell>
61+
{shouldShowTopBar ? (
62+
<TopBar>
63+
<Home />
64+
</TopBar>
65+
) : (
4166
<Home />
42-
</AppShell>
67+
)}
4368
</ProtectedRoute>
4469
}
4570
/>
4671
</Routes>
4772
);
4873
}
4974

75+
function App() {
76+
return <AppContent />;
77+
}
78+
5079
export default App;

0 commit comments

Comments
 (0)