Skip to content

Commit 1f2d9f1

Browse files
committed
feat: migrate UI components from PicoCSS to Bootstrap 5
Replace PicoCSS colour classes with custom Bootstrap brand variants: - Alerts: alert-cb-error, alert-cb-success, alert-cb-info - Buttons: btn-cb-primary (Trust Blue), btn-outline-danger (logout) - Layout: nav bar with codebar logo + 'auth' wordmark, Open Sans font - Cards: Bootstrap card with card-header-cb (magenta accent stripe) - Tables: Bootstrap table-hover with table-light header row - Home: centered welcome page with codebar logo for signed-out state - Profile: description list (dl.row) layout inside card - Login: centered layout with full-width auth method buttons - Navigation: links styled as outline-secondary buttons
1 parent 4066a9d commit 1f2d9f1

8 files changed

Lines changed: 182 additions & 107 deletions

File tree

src/app/components/admin.js

Lines changed: 62 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,72 @@
11
import { html } from "hono/html";
22
import { formatISO } from "date-fns";
33

4-
// Admin users list component
54
export const UsersList = ({ users, total }) => html`
6-
<div class="form-section">
7-
<h3>Users (${total} total)</h3>
5+
<div class="card">
6+
<div class="card-header card-header-cb">
7+
<h3 class="h5 mb-0">Users (${total} total)</h3>
8+
</div>
89
${users && users.length > 0
910
? html`
10-
<table>
11-
<thead>
12-
<tr>
13-
<th>Name</th>
14-
<th>Email</th>
15-
<th>Verified</th>
16-
<th>Role</th>
17-
<th>Created</th>
18-
</tr>
19-
</thead>
20-
<tbody>
21-
${users.map(
22-
(user) => html`
23-
<tr>
24-
<td>${user.name}</td>
25-
<td>${user.email}</td>
26-
<td>${user.emailVerified ? "✅" : "❌"}</td>
27-
<td>
28-
<form method="post" action="/admin/user/role">
29-
<input type="hidden" name="userId" value="${user.id}" />
30-
<select name="role" onchange="this.form.submit()">
31-
<option
32-
value="user"
33-
${user.role === "user" ? "selected" : ""}
11+
<div class="table-responsive">
12+
<table class="table table-hover mb-0">
13+
<thead class="table-light">
14+
<tr>
15+
<th>Name</th>
16+
<th>Email</th>
17+
<th>Verified</th>
18+
<th>Role</th>
19+
<th>Date</th>
20+
</tr>
21+
</thead>
22+
<tbody>
23+
${users.map(
24+
(user) => html`
25+
<tr>
26+
<td>${user.name}</td>
27+
<td>${user.email}</td>
28+
<td>${user.emailVerified ? "✅" : "❌"}</td>
29+
<td>
30+
<form method="post" action="/admin/user/role">
31+
<input
32+
type="hidden"
33+
name="userId"
34+
value="${user.id}"
35+
/>
36+
<select
37+
name="role"
38+
class="form-select form-select-sm"
39+
onchange="this.form.submit()"
3440
>
35-
User
36-
</option>
37-
<option
38-
value="admin"
39-
${user.role === "admin" ? "selected" : ""}
40-
>
41-
Admin
42-
</option>
43-
</select>
44-
</form>
45-
</td>
46-
<td>
47-
${formatISO(new Date(user.createdAt), {
48-
representation: "date",
49-
})}
50-
</td>
51-
</tr>
52-
`,
53-
)}
54-
</tbody>
55-
</table>
41+
<option
42+
value="user"
43+
${user.role === "user" ? "selected" : ""}
44+
>
45+
User
46+
</option>
47+
<option
48+
value="admin"
49+
${user.role === "admin" ? "selected" : ""}
50+
>
51+
Admin
52+
</option>
53+
</select>
54+
</form>
55+
</td>
56+
<td class="text-nowrap">
57+
${formatISO(new Date(user.createdAt), {
58+
representation: "date",
59+
})}
60+
</td>
61+
</tr>
62+
`,
63+
)}
64+
</tbody>
65+
</table>
66+
</div>
5667
`
57-
: html` <p>No users found.</p> `}
68+
: html`<div class="card-body">
69+
<p class="mb-0 text-muted">No users found.</p>
70+
</div>`}
5871
</div>
5972
`;

src/app/components/common.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
import { html } from "hono/html";
22

3-
// Form section wrapper
4-
export const FormSection = ({ children }) => html` <div>${children}</div> `;
5-
63
// Message display component
7-
export const Message = ({ error, success }) => html`
4+
export const Message = ({ error, success, info } = {}) => html`
85
${error
9-
? html`<div class="pico-background-red-50">
6+
? html`<div class="alert alert-cb-error mb-3">
107
${decodeURIComponent(error)}
118
</div>`
129
: ""}
1310
${success
14-
? html`<div class="pico-background-green-50">
11+
? html`<div class="alert alert-cb-success mb-3">
1512
${decodeURIComponent(success)}
1613
</div>`
1714
: ""}
15+
${info
16+
? html`<div class="alert alert-cb-info mb-3">
17+
${decodeURIComponent(info)}
18+
</div>`
19+
: ""}
1820
`;

src/app/components/home.js

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,38 @@ import { html } from "hono/html";
22

33
// Login status display
44
export const LoginStatus = ({ user }) => html`
5-
${user
6-
? html`
7-
<div class="pico-background-green-50">
8-
✅ Logged in as: <strong>${user.name}</strong> (${user.email})
9-
</div>
10-
<a href="/profile">View Profile</a> |
11-
<form method="post" action="/logout">
12-
<button type="submit">Logout</button>
13-
</form>
14-
`
15-
: html`
16-
<div class="pico-background-red-50">❌ Not logged in</div>
17-
<a href="/login">Sign In</a>
18-
`}
5+
<div class="auth-status">
6+
${user
7+
? html`
8+
<div class="alert alert-cb-success mb-4">
9+
<strong>Logged in</strong> as ${user.name} (${user.email})
10+
</div>
11+
<div class="d-flex gap-2">
12+
<a href="/profile" class="btn btn-cb-primary">View Profile</a>
13+
<form method="post" action="/logout" class="d-inline">
14+
<button type="submit" class="btn btn-outline-danger">
15+
Logout
16+
</button>
17+
</form>
18+
</div>
19+
`
20+
: html`
21+
<div class="text-center py-5">
22+
<img
23+
src="/static/codebar-logo.svg"
24+
alt="codebar mark"
25+
height="180"
26+
class="mb-4"
27+
/>
28+
<h1 class="display-welcome display-5 mb-3">Welcome to codebar</h1>
29+
<p
30+
class="text-muted mb-4"
31+
style="max-width: 400px; margin-inline: auto;"
32+
>
33+
Sign in to manage your profile and access workshops.
34+
</p>
35+
<a href="/login" class="btn btn-cb-primary btn-lg px-5">Sign In</a>
36+
</div>
37+
`}
38+
</div>
1939
`;

src/app/components/layout.js

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,63 @@
11
import { html } from "hono/html";
22

33
// Base layout component
4-
export const Layout = ({ title, children }) => html`
4+
export const Layout = ({ title, children, hideNav }) => html`
55
<!DOCTYPE html>
6-
<html>
6+
<html data-bs-theme="auto">
77
<head>
8-
<title>${title}</title>
8+
<title>codebar Auth — ${title}</title>
99
<meta name="viewport" content="width=device-width, initial-scale=1" />
10-
<meta name="color-scheme" content="light dark" />
10+
<link rel="preconnect" href="https://fonts.googleapis.com" />
11+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
1112
<link
13+
href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;600;700&display=swap"
1214
rel="stylesheet"
13-
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"
14-
/>
15-
<link
16-
rel="stylesheet"
17-
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.colors.min.css"
1815
/>
16+
<link rel="stylesheet" href="/static/bootstrap.min.css" />
17+
<link rel="stylesheet" href="/static/codebar.css" />
1918
</head>
2019
<body>
21-
<header></header>
22-
<main class="container">${children}</main>
23-
<footer></footer>
20+
${!hideNav
21+
? html`
22+
<nav
23+
class="navbar navbar-expand-lg navbar-light bg-white fixed-top py-3"
24+
>
25+
<div class="container">
26+
<a
27+
class="navbar-brand border-0 d-flex align-items-center gap-2"
28+
href="/"
29+
>
30+
<img
31+
src="/static/codebar-logo.png"
32+
alt="codebar logo"
33+
width="200"
34+
height="54"
35+
/>
36+
<span
37+
class="fw-semibold text-muted"
38+
style="font-size: 0.85rem;"
39+
>auth</span
40+
>
41+
</a>
42+
</div>
43+
</nav>
44+
`
45+
: ""}
46+
<main class="container py-4">${children}</main>
2447
<script type="module" src="/static/auth-client.js?v=2"></script>
48+
<script src="/static/bootstrap.bundle.min.js"></script>
2549
</body>
2650
</html>
2751
`;
52+
2853
// Navigation component
2954
export const Navigation = ({ back, extra }) => html`
30-
<a href="${back.href}">${back.text}</a>
31-
${extra ? html` | <a href="${extra.href}">${extra.text}</a>` : ""}
55+
<a href="${back.href}" class="btn btn-outline-secondary btn-sm"
56+
>${back.text}</a
57+
>
58+
${extra
59+
? html`<a href="${extra.href}" class="btn btn-outline-secondary btn-sm ms-1"
60+
>${extra.text}</a
61+
>`
62+
: ""}
3263
`;

src/app/components/login.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
import { html } from "hono/html";
22

33
export const GitHubButton = ({ callbackURL } = {}) => html`
4-
<h2>Using GitHub</h2>
54
<form method="post" action="/login/github">
65
<input type="hidden" name="callbackURL" value="${callbackURL || ""}" />
7-
<button type="submit">Sign In with GitHub</button>
6+
<button type="submit" class="btn btn-cb-primary w-100">
7+
Sign in with GitHub
8+
</button>
89
</form>
910
`;
1011

1112
export const MagicLinkButton = ({ callbackURL } = {}) => html`
12-
<h2>Using magic link</h2>
1313
<form method="get" action="/login/magic-link">
1414
<input type="hidden" name="callbackURL" value="${callbackURL || ""}" />
15-
<button type="submit">Send Magic Link</button>
15+
<button type="submit" class="btn btn-cb-primary w-100">
16+
Send magic link
17+
</button>
1618
</form>
1719
`;

src/app/components/profile.js

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
11
import { html } from "hono/html";
22
import { format } from "date-fns";
33

4-
// User info display
54
export const UserInfo = ({ user }) => html`
6-
<h2>User Information</h2>
7-
<p><strong>Name:</strong> ${user.name}</p>
8-
<p><strong>Email:</strong> ${user.email}</p>
9-
<p><strong>ID:</strong> ${user.id}</p>
10-
<p><strong>Email Verified:</strong> ${user.emailVerified ? "Yes" : "No"}</p>
11-
<p>
12-
<strong>Created:</strong> ${format(
13-
new Date(user.createdAt),
14-
"yyyy-MM-dd HH:mm:ss",
15-
)}
16-
</p>
5+
<div class="card">
6+
<div class="card-header card-header-cb">
7+
<h2 class="h5 mb-0">User Information</h2>
8+
</div>
9+
<div class="card-body">
10+
<dl class="row mb-0">
11+
<dt class="col-sm-3">Name</dt>
12+
<dd class="col-sm-9">${user.name}</dd>
13+
<dt class="col-sm-3">Email</dt>
14+
<dd class="col-sm-9">${user.email}</dd>
15+
<dt class="col-sm-3">Email verified</dt>
16+
<dd class="col-sm-9">${user.emailVerified ? "Yes" : "No"}</dd>
17+
<dt class="col-sm-3">Member since</dt>
18+
<dd class="col-sm-9">
19+
${format(new Date(user.createdAt), "dd MMM yyyy")}
20+
</dd>
21+
</dl>
22+
</div>
23+
</div>
1724
`;

src/app/demo/templates/home.html

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ <h1>Demo Time</h1>
1515
</li>
1616
</ul>
1717

18-
<div class="status" id="status"></div>
18+
<div class="status alert" id="status"></div>
1919

2020
<script>
2121
try {
@@ -30,13 +30,13 @@ <h1>Demo Time</h1>
3030
const status = document.getElementById("status");
3131
status.innerHTML = `<pre>${JSON.stringify(user, null, 2)}</pre>`;
3232
if (user.role == "guest") {
33-
status.classList.remove("pico-background-green-50");
34-
status.classList.add("pico-background-red-50");
35-
status.innerHTML += `<a href="/login?redirect_url=http://localhost:3000/demo">Login here!</a>`;
33+
status.classList.remove("alert-cb-success");
34+
status.classList.add("alert-cb-error");
35+
status.innerHTML += `<a href="/login?redirect_url=http://localhost:3000/demo" class="alert-link">Login here!</a>`;
3636
} else {
37-
status.classList.remove("pico-background-red-50");
38-
status.classList.add("pico-background-green-50");
39-
status.innerHTML += `<a href="/logout?redirect_url=http://localhost:3000/demo">Logout!</a>`;
37+
status.classList.remove("alert-cb-error");
38+
status.classList.add("alert-cb-success");
39+
status.innerHTML += `<a href="/logout?redirect_url=http://localhost:3000/demo" class="alert-link">Logout!</a>`;
4040
}
4141
});
4242
});

test/features/home.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ test("home page feature tests", async (t) => {
1111

1212
t.equal(res.status, 200, "returns 200 OK");
1313
const html = await res.text();
14-
t.match(html, /codebar authentication/i, "contains app title");
14+
t.match(html, /Welcome to codebar/i, "contains app title");
1515
});
1616

1717
t.test("home page has login link", async (t) => {

0 commit comments

Comments
 (0)