Build ACM Austin community website

This commit is contained in:
akshaymittal143 committed 2026-08-21 12:51:15 -05:00
1 parent d0799b9cb0
commit b05e088b03
12 files changed
+516 -1021

No files matched your search

+37
View File
@@ -0,0 +1,37 @@
name: Deploy static site to GitHub Pages
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: true
jobs:
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Configure GitHub Pages
uses: actions/configure-pages@v5
- name: Upload site artifact
uses: actions/upload-pages-artifact@v3
with:
path: .
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
+22
View File
@@ -0,0 +1,22 @@
# Operating system files
.DS_Store
._*
# Editor and workspace metadata
.vscode/
.idea/
# Local environment and secrets
.env
.env.*
!.env.example
# Dependencies and build output
node_modules/
dist/
build/
coverage/
# Local tooling output
.cache/
*.log
+18
View File
@@ -0,0 +1,18 @@
# Code of Conduct
ACM Austin is committed to providing a welcoming, respectful, and harassment-free community for everyone who contributes to the project or attends its events.
## Expected Behavior
- Be respectful and assume good intent while remaining open to correction.
- Give constructive feedback focused on the work, not the person.
- Credit contributors, photographers, speakers, and source projects appropriately.
- Respect privacy and do not publish personal information without permission.
## Unacceptable Behavior
Harassment, discrimination, intimidation, personal attacks, sexualized attention, doxxing, and publishing private communications or media without consent are not acceptable.
## Reporting
Report a concern privately to the ACM Austin organizers through the chapter's official Meetup or LinkedIn page. Do not open a public issue for sensitive reports. Organizers will review reports fairly and may remove content, restrict participation, or contact appropriate authorities when necessary.
+44
View File
@@ -0,0 +1,44 @@
# Contributing
Thanks for helping ACM Austin make useful engineering events easier to find.
## Before You Start
1. Create a focused branch for your change.
2. Check `TODO.md` and avoid duplicating work already in progress.
3. For content or photography, confirm that ACM Austin has permission to publish it.
4. For a substantial change, open an issue first so scope and ownership are clear.
## Good First Contributions
Useful contributions include correcting event details, improving keyboard navigation, adding alt text, optimizing approved photos, improving mobile layout, and documenting integration work. Please do not add invented event data, unapproved personal information, or credentials.
## Local Review
Open the page directly with `open index.html`, or run:
```bash
python3 -m http.server 8000
```
Before opening a pull request, check:
- The page works at desktop and mobile widths.
- Navigation, external links, countdown, event cards, and the volunteer form behave correctly.
- New images have useful `alt` text and are optimized.
- No API keys, tokens, personal data, or generated build artifacts are committed.
- `get_errors` or an equivalent HTML/JavaScript check reports no new errors.
## Code Style
Keep the site dependency-light and preserve the existing Tailwind CDN approach until a build system is intentionally introduced. Keep event data normalized, use semantic HTML, and prefer accessible labels, focus states, and keyboard-friendly controls.
## Pull Requests
Describe what changed, how it was tested, and whether the change affects event data, external integrations, accessibility, or privacy. Keep unrelated formatting and content changes out of the same pull request.
Prefer small, reviewable pull requests with a clear title and screenshots for visual changes. Link the related issue, call out any follow-up work, and confirm that external links and mobile states were checked. Maintainers may request changes when content ownership, accessibility, privacy, or security is unclear.
## Review Standards
Reviewers look for correctness, accessibility, responsive behavior, clear ownership of content and images, and protection of secrets and personal data. Discussion should stay specific and respectful. See `CODE_OF_CONDUCT.md` for community expectations.
+98
View File
@@ -0,0 +1,98 @@
# ACM Austin Website
The public-facing website for the Austin ACM / IEEE engineering community. It is currently a responsive single-page site built from one static HTML file.
Everyone is welcome to help improve it. You do not need to be an ACM member or a frontend expert to contribute: report a typo, suggest a better event detail, improve accessibility, add an approved photo, or open a small pull request.
Repository: https://github.com/acmorg/acm-austin
Live site: `https://acmorg.github.io/acm-austin/` after GitHub Pages is enabled.
## Quick Start
No build step is required.
```bash
open index.html
```
For a local HTTP server, use any static server, for example:
```bash
python3 -m http.server 8000
```
Then open `http://localhost:8000`.
## Deploy With GitHub Pages
The repository includes a GitHub Actions workflow at `.github/workflows/pages.yml`. It publishes the repository root whenever `main` receives a push.
One-time repository setup:
1. Open the repository's **Settings** on GitHub.
2. Select **Pages** under **Code and automation**.
3. Set **Source** to **GitHub Actions**.
4. Push to `main` and open the URL shown in the workflow's deployment summary.
The workflow publishes the static site as-is, including `index.html`, `sessionize.html`, and `photos/`.
Typical update flow:
```bash
git status
git add .
git commit -m "Describe the change"
git push origin main
```
## Project Structure
- `index.html` - page markup, Tailwind CDN configuration, event rendering, countdown, and form behavior.
- `sessionize.html` - live Sessionize sessions and speaker directory sub-page.
- `photos/` - local speaker, volunteer, and community photography.
- `TODO.md` - implementation plan for Sessionize, Meetup, and Luma integrations.
- `CONTRIBUTING.md` - contribution and review guidelines.
## Current Integrations
The page links to the chapter's Meetup, LinkedIn, Sessionize, and venue map pages. The JavaScript includes API-ready adapters named `fetchMeetupEvents`, `fetchLumaEvents`, and `fetchSessionizeData`. They currently return local placeholder data; API credentials should be handled by a server-side proxy rather than committed to the browser.
## Volunteer Form Storage
The volunteer section embeds the Google Form connected to the chapter's response spreadsheet. The edit URL must remain private; the page uses the public `viewform` URL. Review the form's sharing settings, response access, retention, and spam protection before collecting real attendee data.
Public forms:
- [Volunteer signup form](https://docs.google.com/forms/d/1f_ucupsckbus_6K4Qd5OI2gOVTxNdLt0rQpzXUctnxI/viewform)
- [Sponsorship interest form](https://docs.google.com/forms/d/e/1FAIpQLSc8HPaboM9NUODwUT-upXe4gyXFqnrG_s37TalQi6HpJ4n2_A/viewform?usp=dialog)
## Contributing
Have an idea or found something that could be better? [Open an issue](https://github.com/acmorg/acm-austin/issues) with a clear description, screenshots when useful, and steps to reproduce a problem. For changes you can implement, fork the repository, create a focused branch, and [open a pull request](https://github.com/acmorg/acm-austin/pulls).
Good contributions include:
- Improving mobile layout, keyboard navigation, or alt text.
- Correcting event, venue, speaker, or sponsor information.
- Adding optimized, approved community photography.
- Improving documentation or the integration TODOs.
Please read [CONTRIBUTING.md](CONTRIBUTING.md) before submitting. Keep pull requests focused, explain how you tested them, and never commit API keys, private form links, personal attendee data, or unapproved media.
## Adding Photos
Place optimized images in `photos/` and reference them with relative paths such as `photos/speaker-name.webp`. Prefer WebP or compressed JPEG files, descriptive filenames, and meaningful `alt` text. Do not commit private or unapproved images.
## Content Updates
Update the `EVENTS` array in `index.html` until the API adapters are connected. Keep event dates in ISO 8601 format with the correct Austin offset. Verify event links, venue details, accessibility copy, and mobile layout before publishing.
## Credits
Starter project by Prachi Jethava (`@prachi1211`).
Contributors:
- Akshay Mittal (`@akshaymittal143`)
- Prachi Jethava (`@prachi1211`)
+18
View File
@@ -0,0 +1,18 @@
# Security Policy
## Reporting a Vulnerability
Please do not open a public GitHub issue for a security vulnerability. Report it privately to the ACM Austin maintainers through the repository's GitHub security contact or the chapter organizers' official contact channel.
Include:
- A short description of the issue and its impact.
- Reproduction steps or a minimal proof of concept.
- Affected URL, file, or integration.
- Any suggested mitigation.
Allow maintainers reasonable time to investigate before public disclosure. Never include API keys, access tokens, passwords, private attendee data, or other secrets in an issue, pull request, screenshot, or log.
## Scope
This policy covers the website, its documentation, static assets, and future server-side integrations for Meetup, Luma, Sessionize, and Certifier.
+67
View File
@@ -0,0 +1,67 @@
# Integration Plan
This checklist tracks the path from local placeholder data to reliable event and community operations.
## Foundation
- [ ] Decide which source is authoritative for event title, date, venue, description, RSVP URL, and attendance count.
- [ ] Create a small server-side API proxy or serverless function; keep all provider tokens out of `index.html`.
- [ ] Define one normalized event schema shared by Meetup and Luma adapters.
- [ ] Add environment variables for provider credentials, organizer IDs, and cache duration.
- [ ] Add loading, empty, stale-data, and provider-error states to the event grid.
## Sessionize: speaker pipeline
- [ ] Confirm the Sessionize event or organization endpoint and available public fields.
- [ ] Decide whether accepted sessions should be public before an event is published.
- [ ] Add a server-side `GET /api/sessionize` adapter with response validation.
- [ ] Render accepted speakers or open submission status from normalized data.
- [ ] Keep the direct Sessionize submission CTA available if the API is unavailable.
- [ ] Add caching and a fallback message for rate limits or downtime.
## Meetup: event and RSVP pipeline
- [ ] Create or confirm a Meetup OAuth app and organizer permissions.
- [ ] Store the Meetup client secret server-side and implement OAuth/token refresh.
- [ ] Add `GET /api/meetup-events` with pagination and a future-date filter.
- [ ] Normalize Meetup locations, images, RSVP URLs, and attendance counts.
- [ ] Replace the local `EVENTS` fallback only after the adapter passes fixture tests.
- [ ] Verify rate limits, timezone handling, cancellations, and sold-out states.
## Luma: alternative event source
- [ ] Confirm whether Luma is a source of record or a secondary calendar.
- [ ] Obtain API access and document the event collection ID and permissions.
- [ ] Add `GET /api/luma-events` with pagination and response validation.
- [ ] Normalize Luma fields to the shared event schema and deduplicate by canonical URL.
- [ ] Define conflict rules when Meetup and Luma describe the same event differently.
- [ ] Add provider attribution only where it helps attendees understand the RSVP path.
## Certifier: speaker and volunteer certificates
POC: Akshay Mittal
Dashboard: https://app.certifier.io/dashboard
- [ ] Confirm Certifier workspace access, API availability, and the certificate templates for speakers and volunteers.
- [ ] Decide the award rules: speaker session delivered, volunteer shift completed, or organizer approval.
- [ ] Define the certificate data fields, including recipient name, role, event title, event date, issuer, and verification URL.
- [ ] Store Certifier credentials server-side and add a protected certificate issuance endpoint.
- [ ] Connect approved speaker and volunteer records to Certifier issuance requests.
- [ ] Add an idempotency key so retries cannot issue duplicate certificates.
- [ ] Save certificate IDs and verification URLs against the event participation record.
- [ ] Add a private organizer view for reviewing, reissuing, or revoking certificates.
- [ ] Obtain recipient consent before collecting names and email addresses for certificates.
- [ ] Test a complete proof of concept with Akshay before enabling automatic issuance.
## Launch and operations
- [ ] Add automated tests for normalization, countdown dates, duplicate events, and malformed provider responses.
- [ ] Add an integration health check or lightweight admin status page.
- [x] Embed the Google Form public `viewform` URL for volunteer submissions.
- [ ] Confirm the Google Form response spreadsheet permissions and organizer notification settings.
- [ ] Add a custom confirmation message and response retention policy in Google Forms.
- [ ] Alternative if the organizers want spreadsheet ownership: use a Google Apps Script web app that validates the fields and appends rows to a restricted Google Sheet.
- [ ] Add consent, retention, and spam protection before collecting submissions in production.
- [ ] Add a scheduled refresh job and monitor failed syncs.
- [ ] Document the final deployment command, environment variables, and rollback process.
+127 -1021
View File
File diff suppressed because it is too large. Load diff
View File
Whitespace-only changes.
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

+85
View File
@@ -0,0 +1,85 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="ACM Austin speakers and sessions powered by Sessionize.">
<title>ACM Austin | Speakers & Sessions</title>
<link rel="icon" type="image/png" href="photos/logos/acm.png">
<link rel="apple-touch-icon" href="photos/logos/acm.png">
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = { theme: { extend: {
colors: { navy: '#071525', ink: '#10253d', electric: '#2dd4ff', lime: '#c8f169', mist: '#edf4f7' },
fontFamily: { sans: ['Plus Jakarta Sans', 'ui-sans-serif', 'sans-serif'], mono: ['IBM Plex Mono', 'monospace'] }
}}};
</script>
<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=IBM+Plex+Mono:wght@400;500;600&family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
</head>
<body class="bg-mist font-sans text-ink antialiased">
<header class="bg-navy text-white">
<nav class="mx-auto flex max-w-7xl items-center justify-between px-6 py-5 lg:px-8" aria-label="Main navigation">
<a href="index.html" class="flex items-center gap-3 font-bold tracking-tight"><img class="h-10 w-10 rounded-lg bg-white object-contain p-1" src="photos/logos/acm.png" alt="ACM Austin logo"><span>ACM <span class="text-electric">Austin</span></span></a>
<div class="flex items-center gap-5 text-sm text-slate-300"><a href="index.html#events" class="hover:text-white">Events</a><a href="index.html#community" class="hover:text-white">Community</a><a href="https://sessionize.com/austin-acm-meetup/" target="_blank" rel="noopener" class="rounded-full bg-lime px-4 py-2 font-bold text-navy hover:bg-white">Submit a talk ↗</a></div>
</nav>
</header>
<main>
<section class="bg-navy px-6 pb-16 pt-12 text-white lg:px-8 lg:pb-20 lg:pt-20"><div class="mx-auto max-w-7xl"><p class="font-mono text-xs uppercase tracking-[.2em] text-electric">Sessionize / ACM Austin</p><h1 class="mt-4 max-w-3xl text-5xl font-extrabold tracking-tight sm:text-6xl">Speakers who share the <span class="text-electric">real work.</span></h1><p class="mt-6 max-w-2xl text-lg leading-8 text-slate-300">Explore the talks and practitioners shaping our monthly engineering community.</p></div></section>
<section class="mx-auto max-w-7xl px-6 py-16 lg:px-8"><div id="status" class="rounded-xl border border-slate-200 bg-white p-5 text-sm text-slate-600" role="status">Loading the Sessionize program...</div><div class="mt-12"><div class="flex flex-wrap items-end justify-between gap-5"><div><p class="font-mono text-xs uppercase tracking-[.2em] text-cyan-600">The people</p><h2 class="mt-3 text-3xl font-extrabold tracking-tight text-navy">Speaker directory</h2></div><a href="https://sessionize.com/austin-acm-meetup/" target="_blank" rel="noopener" class="rounded-full border border-ink px-5 py-3 text-sm font-bold text-navy hover:border-cyan-600 hover:text-cyan-700">View on Sessionize ↗</a></div><div id="speakers" class="mt-8 grid gap-5 sm:grid-cols-2 lg:grid-cols-4"></div></div><div class="mt-20"><p class="font-mono text-xs uppercase tracking-[.2em] text-cyan-600">The program</p><h2 class="mt-3 text-3xl font-extrabold tracking-tight text-navy">Sessions</h2><div id="sessions" class="mt-8 grid gap-5 lg:grid-cols-2" hidden></div></div></section>
</main>
<footer class="bg-navy px-6 py-8 text-sm text-slate-400 lg:px-8"><div class="mx-auto flex max-w-7xl flex-wrap items-center justify-between gap-4"><div class="flex items-center gap-3"><img class="h-8 w-8 rounded-md bg-white object-contain p-1" src="photos/logos/acm.png" alt="ACM Austin logo"><span>© 2026 ACM Austin · A joint ACM / IEEE chapter</span></div><a href="index.html" class="font-bold text-electric hover:text-white">Back to ACM Austin home</a></div></footer>
<script>
// Keep this endpoint in one place so a regenerated Sessionize ID is easy to swap.
const SESSIONIZE_ENDPOINT = 'https://sessionize.com/api/v2/f8cooe5l/view/All';
const status = document.querySelector('#status');
const sessionsTarget = document.querySelector('#sessions');
const speakersTarget = document.querySelector('#speakers');
const escapeHtml = value => String(value || '').replace(/[&<>'"]/g, character => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', "'": '&#039;', '"': '&quot;' }[character]));
const dateLabel = value => value ? new Intl.DateTimeFormat('en-US', { weekday: 'short', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' }).format(new Date(value)) : 'Schedule coming soon';
function speakerName(speaker) {
return [speaker.firstName, speaker.lastName].filter(Boolean).join(' ') || speaker.name || 'ACM Austin speaker';
}
function sessionSpeakerNames(session, speakerLookup) {
const references = session.speakers || session.speakerIds || session.speaker || [];
const list = Array.isArray(references) ? references : [references];
return list.map(reference => {
if (typeof reference === 'object') return speakerName(reference);
return speakerLookup.get(reference) || speakerLookup.get(String(reference)) || '';
}).filter(Boolean);
}
function renderSessions(sessions, speakers) {
if (!sessions.length) return;
const speakerLookup = new Map(speakers.flatMap(speaker => [[speaker.id, speakerName(speaker)], [String(speaker.id), speakerName(speaker)]]));
sessionsTarget.innerHTML = sessions.map(session => { const names = sessionSpeakerNames(session, speakerLookup); return `<article class="rounded-2xl border border-slate-200 bg-white p-6 shadow-sm"><div class="flex flex-wrap items-center justify-between gap-3"><span class="font-mono text-xs uppercase tracking-wider text-cyan-700">${escapeHtml(session.categoryItems?.[0]?.name || 'Session')}</span><span class="font-mono text-xs text-slate-500">${dateLabel(session.startsAt || session.startTime)}</span></div><h3 class="mt-4 text-xl font-bold leading-snug text-navy">${escapeHtml(session.title || session.name || 'Untitled session')}</h3>${names.length ? `<p class="mt-2 text-sm font-semibold text-cyan-700">${escapeHtml(names.join(' · '))}</p>` : ''}<p class="mt-3 text-sm leading-6 text-slate-600">${escapeHtml(session.description || session.tagline || 'Session details coming soon.')}</p><p class="mt-5 font-mono text-xs text-slate-500">${escapeHtml(session.room?.name || session.room || '')}</p></article>`; }).join('');
sessionsTarget.hidden = false;
}
function renderSpeakers(speakers) {
speakersTarget.innerHTML = speakers.length ? speakers.map(speaker => `<article class="rounded-2xl border border-slate-200 bg-white p-5"><img class="aspect-square w-full rounded-xl bg-mist object-cover" src="${escapeHtml(speaker.profilePicture || 'photos/Akshay_Mittal_profile.png')}" alt="${escapeHtml(speakerName(speaker))}"><h3 class="mt-4 font-bold text-navy">${escapeHtml(speakerName(speaker))}</h3><p class="mt-1 text-sm leading-6 text-slate-500">${escapeHtml(speaker.tagLine || speaker.bio || 'ACM Austin community speaker')}</p></article>`).join('') : '<p class="text-slate-600">Speaker profiles will appear here as the program is published.</p>';
}
fetch(SESSIONIZE_ENDPOINT).then(response => { if (!response.ok) throw new Error(`Sessionize returned ${response.status}`); return response.json(); }).then(data => {
const sessions = data.sessions || data.sessionList || [];
const speakers = data.speakers || data.speakerList || [];
renderSessions(sessions, speakers);
renderSpeakers(speakers);
status.textContent = `Loaded ${sessions.length} sessions and ${speakers.length} speakers from Sessionize.`;
status.className = 'rounded-xl border border-emerald-200 bg-emerald-50 p-5 text-sm text-emerald-800';
}).catch(error => {
status.innerHTML = `The live Sessionize feed is temporarily unavailable. <a class="font-bold underline" href="https://sessionize.com/austin-acm-meetup/" target="_blank" rel="noopener">View the program on Sessionize ↗</a>`;
status.title = error.message;
renderSpeakers([]);
});
</script>
</body>
</html>