Skip to content
Closed
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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ pnpm-debug.log*

# environment variables
.env
.env.production
.env.*
.dev.vars*
worker-configuration.d.ts

# macOS-specific files
.DS_Store
Expand Down
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ All commands are run from the root of the project, from a terminal:

## Cloudflare Workers deployment

This site remains fully static. `wrangler.jsonc` publishes Astro's `dist/` output through Cloudflare Workers Static Assets; it does not add a server entrypoint, API routes, authentication, or database bindings.
Public pages remain static. `wrangler.jsonc` publishes Astro's `dist/` output through Cloudflare Workers Static Assets. The only runtime paths are `/organizer-console/**` and `/api/auth/**`; all other public assets remain asset-first.

- `pnpm deploy:dry-run` builds the site and validates the Worker asset deployment locally.
- `pnpm deploy` builds and deploys the static site with Wrangler.
Expand All @@ -53,6 +53,27 @@ Before the first production deployment, repository administrators must create th
- `CLOUDFLARE_ACCOUNT_ID` — the Cloudflare account that owns the Worker and `devcongress.org` zone.

The first deployment should be validated at its Workers preview URL. Attach `devcongress.org` only after URL/content parity checks pass. GitHub Pages continues to deploy independently during the agreed soak window and remains the rollback path.

## Organizer console setup

The organizer console lives at `/organizer-console/`. It uses the existing Supabase project and Google OAuth flow, but creates its own app session on `devcongress.org`.

The current production Worker is static-assets-only, so Cloudflare will not show runtime variables yet. After this feature deploys the Worker script for `devcongress-website`, configure these Worker secrets in Cloudflare and rerun the deployment if the first production run needs them:

- `SUPABASE_URL` — the Supabase project URL.
- `SUPABASE_ANON_KEY` — the browser-safe Supabase anon key. The Worker deliberately returns this only from its same-origin auth-config endpoint.
- `SUPABASE_SERVICE_ROLE_KEY` — server-only Supabase key. Never expose this key in browser code, GitHub Actions variables, or the repository.

`keep_vars: true` preserves Cloudflare dashboard variables during GitHub Actions deployments. For local Worker testing, create an ignored `.dev.vars` file with the same three values.

In Supabase Auth, add both callback destinations to the allowed redirect URLs:

- `https://devcongress.org/api/auth/admin/callback`
- `https://devcongress-website.admins-a7d.workers.dev/api/auth/admin/callback`

Also add `https://devcongress.org` to the authorized JavaScript origins for the existing Google OAuth client.

Google authentication proves identity only. Access is granted only when the verified Google email has an active row in `public.admin_memberships`; the Worker then stores only a hash of an opaque, HTTP-only `devcon_admin` session token in `public.admin_sessions`.
## 👀 Want to learn more?

Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"astro": "astro"
},
"dependencies": {
"@supabase/supabase-js": "2.106.2",
"astro": "^6.4.2",
"zod": "^4.4.3"
},
Expand Down
71 changes: 69 additions & 2 deletions pnpm-lock.yaml

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

4 changes: 4 additions & 0 deletions src/components/Navbar.astro
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
---
import OrganizerActions from './OrganizerActions.astro';

interface Props { slackUrl: string; paystackUrl: string; }
const { slackUrl, paystackUrl } = Astro.props;
---
Expand All @@ -16,6 +18,7 @@ const { slackUrl, paystackUrl } = Astro.props;
</nav>

<div class="navbar-actions">
<OrganizerActions />
<a href={paystackUrl} target="_blank" rel="noopener noreferrer" class="btn btn-outline">
Support Us
</a>
Expand All @@ -36,6 +39,7 @@ const { slackUrl, paystackUrl } = Astro.props;
<a href="#meetups">Meetups</a>
</nav>
<div class="mobile-actions">
<OrganizerActions />
<a href={paystackUrl} target="_blank" rel="noopener noreferrer" class="btn btn-outline">Support Us</a>
<a href={slackUrl} target="_blank" rel="noopener noreferrer" class="btn btn-primary">Join Slack</a>
</div>
Expand Down
93 changes: 93 additions & 0 deletions src/components/OrganizerActions.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<div class="organizer-actions" data-organizer-actions>
<a href="/organizer-console/login/" class="organizer-link" data-organizer-link>Organizer</a>
<button type="button" class="organizer-signout" data-organizer-signout hidden>Sign out</button>
</div>

<style>
.organizer-actions {
display: inline-flex;
align-items: center;
gap: var(--s2);
}

.organizer-link,
.organizer-signout {
min-height: 36px;
padding: 0 var(--s3);
border: 2px solid var(--black);
border-radius: var(--r-full);
background: var(--bg-white);
color: var(--text);
font-family: var(--font-body);
font-size: var(--text-xs);
font-weight: 700;
line-height: 1;
transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1), background-color 150ms cubic-bezier(0.4, 0, 0.2, 1);
}

.organizer-link {
display: inline-flex;
align-items: center;
}

.organizer-signout {
color: var(--text-mid);
}

@media (hover: hover) and (pointer: fine) {
.organizer-link:hover,
.organizer-signout:hover {
transform: translateY(-1px);
background: var(--yellow);
}
}

.organizer-link:active,
.organizer-signout:active {
transform: scale(0.97);
}
</style>

<script>
type OrganizerSession = {
authenticated: boolean;
};

async function updateOrganizerActions(actions: HTMLElement) {
const link = actions.querySelector<HTMLAnchorElement>('[data-organizer-link]');
const signOut = actions.querySelector<HTMLButtonElement>('[data-organizer-signout]');
if (!link || !signOut) return;

try {
const response = await fetch('/api/auth/session', {
credentials: 'same-origin',
headers: { Accept: 'application/json' },
});
const session = await response.json() as OrganizerSession;

if (session.authenticated) {
link.href = '/organizer-console/';
signOut.hidden = false;
}
} catch {
// Keep the safe, signed-out link when session verification is unavailable.
}

signOut.addEventListener('click', async () => {
signOut.disabled = true;
try {
await fetch('/api/auth/logout', {
method: 'POST',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/json' },
});
} finally {
window.location.assign('/');
}
});
}

document.querySelectorAll<HTMLElement>('[data-organizer-actions]').forEach((actions) => {
void updateOrganizerActions(actions);
});
</script>
4 changes: 3 additions & 1 deletion src/layouts/Base.astro
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import FooterSection from '../components/FooterSection.astro';
interface Props {
title?: string;
description?: string;
robots?: string;
}
const {
title = "DevCongress | Africa's home for builders",
description = "DevCongress is a tech community where developers, designers, founders, and makers across Africa connect, grow, and build together.",
robots = 'index, follow',
} = Astro.props;

const siteEntries = await getCollection('site');
Expand All @@ -22,7 +24,7 @@ const site = siteEntries[0]!.data;
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{title}</title>
<meta name="description" content={description} />
<meta name="robots" content="index, follow" />
<meta name="robots" content={robots} />
<meta name="theme-color" content="#F5F2E8" />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
Expand Down
9 changes: 7 additions & 2 deletions src/pages/meetups/[slug].astro
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
import { getCollection } from 'astro:content';
import Base from '../../layouts/Base.astro';
import OrganizerActions from '../../components/OrganizerActions.astro';

export async function getStaticPaths() {
const meetups = await getCollection('meetups');
Expand Down Expand Up @@ -76,7 +77,10 @@ const folderPhotos = photos.filter((photo) => photo.type === 'folder');
<a href="/" class="mnav-logo">
<img src="/images/logo.png" alt="DevCongress" height="36" />
</a>
<a href="/#meetups" class="mnav-back">← All meetups</a>
<div class="mnav-actions">
<OrganizerActions />
<a href="/#meetups" class="mnav-back">← All meetups</a>
</div>
</div>
</header>

Expand Down Expand Up @@ -368,7 +372,8 @@ const folderPhotos = photos.filter((photo) => photo.type === 'folder');
background: var(--bg); border-bottom: 2px solid var(--black);
padding-block: var(--s4);
}
.mnav-inner { display: flex; align-items: center; justify-content: space-between; }
.mnav-inner { display: flex; align-items: center; justify-content: space-between; gap: var(--s3); }
.mnav-actions { display: inline-flex; align-items: center; gap: var(--s3); }
.mnav-back { font-size: var(--text-sm); font-weight: 500; color: var(--text-mid); transition: color 150ms ease; }
.mnav-back:hover { color: var(--pink); }

Expand Down
9 changes: 7 additions & 2 deletions src/pages/meetups/index.astro
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
import { getCollection } from 'astro:content';
import Base from '../../layouts/Base.astro';
import OrganizerActions from '../../components/OrganizerActions.astro';

const meetups = await getCollection('meetups');

Expand Down Expand Up @@ -41,7 +42,10 @@ const statusConfig = {
<a href="/" class="mnav-logo">
<img src="/images/logo.png" alt="DevCongress" height="36" />
</a>
<a href="/" class="mnav-back">← Home</a>
<div class="mnav-actions">
<OrganizerActions />
<a href="/" class="mnav-back">← Home</a>
</div>
</div>
</header>

Expand Down Expand Up @@ -98,7 +102,8 @@ const statusConfig = {
background: var(--bg); border-bottom: 2px solid var(--black);
padding-block: var(--s4);
}
.mnav-inner { display: flex; align-items: center; justify-content: space-between; }
.mnav-inner { display: flex; align-items: center; justify-content: space-between; gap: var(--s3); }
.mnav-actions { display: inline-flex; align-items: center; gap: var(--s3); }
.mnav-back { font-size: var(--text-sm); font-weight: 500; color: var(--text-mid); transition: color 150ms; }
.mnav-back:hover { color: var(--pink); }

Expand Down
Loading
Loading