Skip to content
Merged
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
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Auth0 SPA configuration.
# These are PUBLIC, non-secret identifiers (they ship to the browser in the
# Authorization Code + PKCE flow). Copy this file to `.env` for local dev, and
# set the same variables in your Cloudflare build environment for deploys.
PUBLIC_AUTH0_DOMAIN=id.letsbuilda.dev
PUBLIC_AUTH0_CLIENT_ID=THoRnVJxMOfLxMAW0kaNW8nuP1q8q3qQ
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# build output
dist/
.wrangler/
# generated types
.astro/
worker-configuration.d.ts

# dependencies
node_modules/
Expand Down
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,26 @@ All commands are run from the root of the project, from a terminal:
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
| `npm run astro -- --help` | Get help using the Astro CLI |

## 🔐 Auth0 configuration

Authentication uses the [`@auth0/auth0-spa-js`](https://github.com/auth0/auth0-spa-js)
SDK (Authorization Code + PKCE). Configuration comes from two **public,
non-secret** environment variables:

| Variable | Description |
| :----------------------- | :--------------------------- |
| `PUBLIC_AUTH0_DOMAIN` | Auth0 tenant domain |
| `PUBLIC_AUTH0_CLIENT_ID` | Auth0 SPA application client ID |

Copy `.env.example` to `.env` for local development, and set the same variables
in the Cloudflare build environment for deploys (Astro inlines `PUBLIC_*` values
at build time).

In the Auth0 application (type: **Single Page Application**), add both your local
(`http://localhost:4321`) and production (`https://letsbuilda.dev`) origins to
**Allowed Callback URLs**, **Allowed Logout URLs**, and **Allowed Web Origins**,
and enable **Refresh Token Rotation**.

## 👀 Want to learn more?

Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
18 changes: 18 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@
"react-dom": "^19.2.6"
},
"devDependencies": {
"wrangler": "^4.95.0",
"@biomejs/biome": "2.4.15"
"@biomejs/biome": "2.4.15",
"@types/node": "^24.13.2",
"wrangler": "^4.95.0"
},
"engines": {
"node": ">=24.12.0"
Expand Down
4 changes: 0 additions & 4 deletions public/auth_config.json

This file was deleted.

89 changes: 84 additions & 5 deletions public/css/main.css
Original file line number Diff line number Diff line change
@@ -1,8 +1,87 @@
.hidden {
display: none;
:root {
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
line-height: 1.5;
color: #1a1a1a;
}

label {
margin-bottom: 10px;
display: block;
body {
margin: 0;
}

nav {
display: flex;
align-items: center;
gap: 1rem;
padding: 1rem 1.5rem;
border-bottom: 1px solid #e2e2e2;
}

nav a {
color: #2563eb;
text-decoration: none;
}

nav a:hover {
text-decoration: underline;
}

.nav-spacer {
flex: 1 1 auto;
}

nav button {
cursor: pointer;
padding: 0.4rem 0.9rem;
border: 1px solid #d1d5db;
border-radius: 6px;
background: #f9fafb;
font: inherit;
}

nav button:hover {
background: #f3f4f6;
}

main {
max-width: 40rem;
margin: 2rem auto;
padding: 0 1.5rem;
}

.profile-card {
display: grid;
gap: 1rem;
padding: 1.5rem;
border: 1px solid #e2e2e2;
border-radius: 12px;
box-shadow: 0 1px 3px rgb(0 0 0 / 8%);
}

.profile-card__avatar {
width: 96px;
height: 96px;
border-radius: 50%;
object-fit: cover;
background: #f3f4f6;
}

.profile-card__name {
margin: 0;
}

.profile-card__fields {
display: grid;
grid-template-columns: max-content 1fr;
gap: 0.5rem 1.5rem;
margin: 0;
}

.profile-card__fields dt {
font-weight: 600;
color: #4b5563;
}

.profile-card__fields dd {
margin: 0;
word-break: break-word;
}
68 changes: 0 additions & 68 deletions public/js/app.js

This file was deleted.

12 changes: 12 additions & 0 deletions src/env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/// <reference types="astro/client" />

interface ImportMetaEnv {
/** Auth0 tenant domain, e.g. `id.letsbuilda.dev`. Public, non-secret. */
readonly PUBLIC_AUTH0_DOMAIN: string;
/** Auth0 SPA application client ID. Public, non-secret. */
readonly PUBLIC_AUTH0_CLIENT_ID: string;
}

interface ImportMeta {
readonly env: ImportMetaEnv;
}
48 changes: 48 additions & 0 deletions src/layouts/BaseLayout.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
interface Props {
title: string;
}

const { title } = Astro.props;
---

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>{title}</title>
<link rel="stylesheet" type="text/css" href="/css/main.css" />
</head>

<body>
<nav>
<a href="/">Home</a>
<a href="/account">Account</a>
<span class="nav-spacer"></span>
<button id="btn-login" type="button" hidden>Log in</button>
<button id="btn-logout" type="button" hidden>Log out</button>
</nav>

<main>
<slot />
</main>

<script>
import { initPage, login, logout } from "../lib/auth";

const loginBtn = document.getElementById("btn-login");
const logoutBtn = document.getElementById("btn-logout");

loginBtn?.addEventListener("click", () => login());
logoutBtn?.addEventListener("click", () => logout());

// Bootstrap auth (handles any login redirect) and reflect the result in
// the nav. Buttons start hidden to avoid a flash of the wrong control.
const authed = await initPage();
if (loginBtn) loginBtn.hidden = authed;
if (logoutBtn) logoutBtn.hidden = !authed;
</script>
</body>
</html>
Loading