Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .fallowrc.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{
"$schema": "https://raw.githubusercontent.com/fallow-rs/fallow/main/schema.json",
"entry": ["test/features/*.test.js", "test/unit/*.test.js"],
"ignorePatterns": ["static/**"],
"ignoreUnresolvedImports": ["/**/static/**"],
"ignoreDependencies": ["pino"],
"ignoreExports": [{ "file": "static/auth-client.js", "exports": ["*"] }]
}
3 changes: 3 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import globals from "globals";
import { defineConfig } from "eslint/config";

export default defineConfig([
{
ignores: ["static/**"],
},
{
files: ["**/*.{js,mjs,cjs}"],
plugins: { js },
Expand Down
111 changes: 62 additions & 49 deletions src/app/components/admin.js
Original file line number Diff line number Diff line change
@@ -1,59 +1,72 @@
import { html } from "hono/html";
import { formatISO } from "date-fns";

// Admin users list component
export const UsersList = ({ users, total }) => html`
<div class="form-section">
<h3>Users (${total} total)</h3>
<div class="card">
<div class="card-header card-header-cb">
<h3 class="h5 mb-0">Users (${total} total)</h3>
</div>
${users && users.length > 0
? html`
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Verified</th>
<th>Role</th>
<th>Created</th>
</tr>
</thead>
<tbody>
${users.map(
(user) => html`
<tr>
<td>${user.name}</td>
<td>${user.email}</td>
<td>${user.emailVerified ? "✅" : "❌"}</td>
<td>
<form method="post" action="/admin/user/role">
<input type="hidden" name="userId" value="${user.id}" />
<select name="role" onchange="this.form.submit()">
<option
value="user"
${user.role === "user" ? "selected" : ""}
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th>Name</th>
<th>Email</th>
<th>Verified</th>
<th>Role</th>
<th>Date</th>
</tr>
</thead>
<tbody>
${users.map(
(user) => html`
<tr>
<td>${user.name}</td>
<td>${user.email}</td>
<td>${user.emailVerified ? "✅" : "❌"}</td>
<td>
<form method="post" action="/admin/user/role">
<input
type="hidden"
name="userId"
value="${user.id}"
/>
<select
name="role"
class="form-select form-select-sm"
onchange="this.form.submit()"
>
User
</option>
<option
value="admin"
${user.role === "admin" ? "selected" : ""}
>
Admin
</option>
</select>
</form>
</td>
<td>
${formatISO(new Date(user.createdAt), {
representation: "date",
})}
</td>
</tr>
`,
)}
</tbody>
</table>
<option
value="user"
${user.role === "user" ? "selected" : ""}
>
User
</option>
<option
value="admin"
${user.role === "admin" ? "selected" : ""}
>
Admin
</option>
</select>
</form>
</td>
<td class="text-nowrap">
${formatISO(new Date(user.createdAt), {
representation: "date",
})}
</td>
</tr>
`,
)}
</tbody>
</table>
</div>
`
: html` <p>No users found.</p> `}
: html`<div class="card-body">
<p class="mb-0 text-muted">No users found.</p>
</div>`}
</div>
`;
14 changes: 8 additions & 6 deletions src/app/components/common.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import { html } from "hono/html";

// Form section wrapper
export const FormSection = ({ children }) => html` <div>${children}</div> `;

// Message display component
export const Message = ({ error, success }) => html`
export const Message = ({ error, success, info } = {}) => html`
${error
? html`<div class="pico-background-red-50">
? html`<div class="alert alert-cb-error mb-3">
${decodeURIComponent(error)}
</div>`
: ""}
${success
? html`<div class="pico-background-green-50">
? html`<div class="alert alert-cb-success mb-3">
${decodeURIComponent(success)}
</div>`
: ""}
${info
? html`<div class="alert alert-cb-info mb-3">
${decodeURIComponent(info)}
</div>`
: ""}
`;
48 changes: 34 additions & 14 deletions src/app/components/home.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,38 @@ import { html } from "hono/html";

// Login status display
export const LoginStatus = ({ user }) => html`
${user
? html`
<div class="pico-background-green-50">
✅ Logged in as: <strong>${user.name}</strong> (${user.email})
</div>
<a href="/profile">View Profile</a> |
<form method="post" action="/logout">
<button type="submit">Logout</button>
</form>
`
: html`
<div class="pico-background-red-50">❌ Not logged in</div>
<a href="/login">Sign In</a>
`}
<div class="auth-status">
${user
? html`
<div class="alert alert-cb-success mb-4">
<strong>Logged in</strong> as ${user.name} (${user.email})
</div>
<div class="d-flex gap-2">
<a href="/profile" class="btn btn-cb-primary">View Profile</a>
<form method="post" action="/logout" class="d-inline">
<button type="submit" class="btn btn-outline-danger">
Logout
</button>
</form>
</div>
`
: html`
<div class="text-center py-5">
<img
src="/static/codebar-logo.svg"
alt="codebar mark"
height="180"
class="mb-4"
/>
<h1 class="display-welcome display-5 mb-3">Welcome to codebar</h1>
<p
class="text-muted mb-4"
style="max-width: 400px; margin-inline: auto;"
>
Sign in to manage your profile and access workshops.
</p>
<a href="/login" class="btn btn-cb-primary btn-lg px-5">Sign In</a>
</div>
`}
</div>
`;
59 changes: 45 additions & 14 deletions src/app/components/layout.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,63 @@
import { html } from "hono/html";

// Base layout component
export const Layout = ({ title, children }) => html`
export const Layout = ({ title, children, hideNav }) => html`
<!DOCTYPE html>
<html>
<html data-bs-theme="auto">
<head>
<title>${title}</title>
<title>codebar Auth — ${title}</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="color-scheme" content="light dark" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;600;700&display=swap"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not gdpr compatible

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! I will fix that!

rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"
/>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.colors.min.css"
/>
<link rel="stylesheet" href="/static/bootstrap.min.css" />
<link rel="stylesheet" href="/static/codebar.css" />
</head>
<body>
<header></header>
<main class="container">${children}</main>
<footer></footer>
${!hideNav
? html`
<nav
class="navbar navbar-expand-lg navbar-light bg-white fixed-top py-3"
>
<div class="container">
<a
class="navbar-brand border-0 d-flex align-items-center gap-2"
href="/"
>
<img
src="/static/codebar-logo.png"
alt="codebar logo"
width="200"
height="54"
/>
<span
class="fw-semibold text-muted"
style="font-size: 0.85rem;"
>auth</span
>
</a>
</div>
</nav>
`
: ""}
<main class="container py-4">${children}</main>
<script type="module" src="/static/auth-client.js?v=2"></script>
<script src="/static/bootstrap.bundle.min.js"></script>
</body>
</html>
`;

// Navigation component
export const Navigation = ({ back, extra }) => html`
<a href="${back.href}">← ${back.text}</a>
${extra ? html` | <a href="${extra.href}">${extra.text}</a>` : ""}
<a href="${back.href}" class="btn btn-outline-secondary btn-sm"
>← ${back.text}</a
>
${extra
? html`<a href="${extra.href}" class="btn btn-outline-secondary btn-sm ms-1"
>${extra.text}</a
>`
: ""}
`;
10 changes: 6 additions & 4 deletions src/app/components/login.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { html } from "hono/html";

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

export const MagicLinkButton = ({ callbackURL } = {}) => html`
<h2>Using magic link</h2>
<form method="get" action="/login/magic-link">
<input type="hidden" name="callbackURL" value="${callbackURL || ""}" />
<button type="submit">Send Magic Link</button>
<button type="submit" class="btn btn-cb-primary w-100">
Send magic link
</button>
</form>
`;
31 changes: 19 additions & 12 deletions src/app/components/profile.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
import { html } from "hono/html";
import { format } from "date-fns";

// User info display
export const UserInfo = ({ user }) => html`
<h2>User Information</h2>
<p><strong>Name:</strong> ${user.name}</p>
<p><strong>Email:</strong> ${user.email}</p>
<p><strong>ID:</strong> ${user.id}</p>
<p><strong>Email Verified:</strong> ${user.emailVerified ? "Yes" : "No"}</p>
<p>
<strong>Created:</strong> ${format(
new Date(user.createdAt),
"yyyy-MM-dd HH:mm:ss",
)}
</p>
<div class="card">
<div class="card-header card-header-cb">
<h2 class="h5 mb-0">User Information</h2>
</div>
<div class="card-body">
<dl class="row mb-0">
<dt class="col-sm-3">Name</dt>
<dd class="col-sm-9">${user.name}</dd>
<dt class="col-sm-3">Email</dt>
<dd class="col-sm-9">${user.email}</dd>
<dt class="col-sm-3">Email verified</dt>
<dd class="col-sm-9">${user.emailVerified ? "Yes" : "No"}</dd>
<dt class="col-sm-3">Member since</dt>
<dd class="col-sm-9">
${format(new Date(user.createdAt), "dd MMM yyyy")}
</dd>
</dl>
</div>
</div>
`;
14 changes: 7 additions & 7 deletions src/app/demo/templates/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ <h1>Demo Time</h1>
</li>
</ul>

<div class="status" id="status"></div>
<div class="status alert" id="status"></div>

<script>
try {
Expand All @@ -30,13 +30,13 @@ <h1>Demo Time</h1>
const status = document.getElementById("status");
status.innerHTML = `<pre>${JSON.stringify(user, null, 2)}</pre>`;
if (user.role == "guest") {
status.classList.remove("pico-background-green-50");
status.classList.add("pico-background-red-50");
status.innerHTML += `<a href="/login?redirect_url=http://localhost:3000/demo">Login here!</a>`;
status.classList.remove("alert-cb-success");
status.classList.add("alert-cb-error");
status.innerHTML += `<a href="/login?redirect_url=http://localhost:3000/demo" class="alert-link">Login here!</a>`;
} else {
status.classList.remove("pico-background-red-50");
status.classList.add("pico-background-green-50");
status.innerHTML += `<a href="/logout?redirect_url=http://localhost:3000/demo">Logout!</a>`;
status.classList.remove("alert-cb-error");
status.classList.add("alert-cb-success");
status.innerHTML += `<a href="/logout?redirect_url=http://localhost:3000/demo" class="alert-link">Logout!</a>`;
}
});
});
Expand Down
Loading