An open API service indexing awesome lists of open source software.

https://github.com/noahpro99/gnwedding2026


https://github.com/noahpro99/gnwedding2026

Last synced: 25 days ago
JSON representation

Awesome Lists containing this project

README

          

# Noah & G — Wedding 2026

A wedding website. Server-rendered React via **TanStack Start**, **Bun**
runtime, **Tailwind CSS v4**, and a tiny **SQLite** database for RSVPs.

---

## Tech stack

| Concern | Choice |
| -------------- | --------------------------------------- |
| Runtime | Bun (`bun` v1.x) |
| SSR framework | TanStack Start (React 19) |
| Routing | TanStack Router (file-based) |
| Styling | Tailwind CSS v4 (`@tailwindcss/vite`) |
| Database | SQLite via `bun:sqlite` (no native deps)|
| Build | Vite 6 |
| Deploy target | Docker container on `noahpi` (Linux/ARM)|

Server functions (`createServerFn`) handle form submissions, so there's no
separate REST/API layer to maintain. Everything that touches the DB lives in
`src/server/`.

---

## Project layout

```
src/
├── routes/ file-based routes (TanStack Router)
│ ├── __root.tsx shared shell: , nav, footer
│ ├── index.tsx homepage (hero + quick links + album CTA)
│ ├── save-the-date.tsx card with PNG frame + RSVP CTA
│ ├── invite.$id.tsx invite page; "Accept/Decline" reveals RSVP form
│ ├── rsvp.tsx standalone RSVP page
│ ├── our-story.tsx timeline
│ ├── travel.tsx hotels + shuttle request form
│ ├── itinerary.tsx Google Calendar embed
│ ├── wedding-party.tsx bridal party grid
│ ├── registry.tsx outbound registry links
│ └── faq.tsx accordion
├── components/
│ ├── SiteNav.tsx sticky top nav (responsive)
│ ├── SiteFooter.tsx
│ ├── SectionHeader.tsx
│ ├── CardFrame.tsx PNG-decoration card wrapper (transparent center)
│ └── RsvpForm.tsx shared RSVP form (yes/no + party + dietary…)
├── server/
│ ├── db.ts bun:sqlite connection + schema bootstrap
│ ├── db-init.ts seed script: inserts demo invite
│ └── rsvp.ts createServerFn endpoints (RSVP, transport, invite lookup)
├── router.tsx router instance
├── routeTree.gen.ts AUTO-GENERATED by TanStack plugin
└── styles.css Tailwind import + theme tokens
public/
├── hero.jpg homepage hero (REPLACE)
└── card-frame.png invite card decoration with transparent middle (REPLACE)
data/ SQLite file lives here (gitignored, Docker volume)
```

---

## Local development

```bash
# one-time
bun install

# run dev server (SSR + HMR)
bun run dev
# → http://localhost:3000

# optional: seed a demo invite at /invite/demo
bun run db:init
```

### Things to fill in before you ship

Search the codebase for `REPLACE` to find every placeholder. Highlights:

- `src/routes/index.tsx` — wedding date, location, photo album URL
- `src/routes/itinerary.tsx` — Google Calendar embed `src`
- `src/routes/travel.tsx` — hotel names, addresses, block codes, booking URLs
- `src/routes/wedding-party.tsx` — names, roles, photos
- `src/routes/registry.tsx` — registry URLs
- `src/routes/our-story.tsx` — story milestones
- `public/hero.jpg` — main homepage photo
- `public/card-frame.png` — the PNG card decoration (transparent center)

---

## How the invite-only flow works

There are **two** invite-style pages:

1. `/save-the-date` — generic, share-with-anyone. Card art, date, RSVP/Travel links.
2. `/invite/$id` — formal invitation **personalized** to a guest. The id maps
to a row in the `invites` table (guest names, max party size). Clicking
"Accept / Decline" reveals the RSVP form pre-filled with their name and
party-size cap.

You generate one invite row per household. The link you send to that household
is `https://yoursite/invite/`. RSVPs from that page are linked back to
the invite via `rsvps.invite_id`.

### Creating invites

For now, do it from a Bun shell:

```bash
bun --print "
const { db } = await import('./src/server/db.ts');
db.run('INSERT INTO invites (id, guest_names, party_size_max) VALUES (?, ?, ?)',
['smith-family', 'The Smith Family', 4]);
console.log('done');
"
```

(A small admin page can be added later if needed.)

---

## Database

SQLite file path is controlled by `DB_PATH` (defaults to `./data/wedding.sqlite`).
Schema is created on first connection (`src/server/db.ts`). Tables:

- **invites** — `id`, `guest_names`, `party_size_max`
- **rsvps** — `invite_id?`, `primary_name`, `email`, `attending`, `party_size`,
`dietary`, `notes`, `needs_transport`, `created_at`
- **transport_requests** — separate shuttle requests from `/travel`

To inspect:

```bash
sqlite3 data/wedding.sqlite '.dump'
sqlite3 data/wedding.sqlite 'SELECT * FROM rsvps;'
```

---

## Deployment — `noahpi`

The repo is wired for CI deploy via `.github/workflows/deploy.yml`. On every
push to `main`, the workflow SSHes into `noahpi`, pulls the repo, rebuilds
the Docker image, and tells the central `~/docker-compose.yml` to come up.

### Required GitHub secrets

- `SSH_HOST` — Pi hostname or IP
- `SSH_USERNAME` — login user
- `SSH_PRIVATE_KEY` — private key authorized on the Pi

### Image

`noahpro/gnwedding2026:latest`. Built from the `Dockerfile` in this repo —
multi-stage with `oven/bun:1` for both build and runtime. The runtime stage
serves both SSR (TanStack Start) and the static `dist/client/` via a thin
`serve.ts` Bun wrapper. Container listens on `:3000`.

### docker-compose entry on the Pi

Lives in `~/docker-compose.yml` alongside the other services. Traefik picks
up routing via labels — no `ports:` mapping needed.

```yaml
gnwedding2026:
image: noahpro/gnwedding2026:latest
restart: unless-stopped
volumes:
- ./gnwedding-data:/app/data # SQLite persists across rebuilds
labels:
- traefik.enable=true
- traefik.http.routers.gnwedding2026.rule=Host(`gnwedding2026.com`)
- traefik.http.routers.gnwedding2026.entrypoints=https
- traefik.http.routers.gnwedding2026.tls=true
- traefik.http.routers.gnwedding2026.tls.certresolver=${CERT_RESOLVER}
- traefik.http.services.gnwedding2026.loadbalancer.server.port=3000
```

Public host: **gnwedding2026.com** (apex only — there's no `www.` DNS
record, and adding `www.` to the Host rule made Let's Encrypt 429 us out
for an hour. Add a `www.` A record then extend the rule with
`|| Host(\`www.gnwedding2026.com\`)` if you want both.)

SQLite file lives at `~/gnwedding-data/wedding.sqlite` and survives
image rebuilds.

### Backups

Add a host-side cron on `noahpi` to snapshot the DB:

```cron
0 3 * * * sqlite3 /home//gnwedding-data/wedding.sqlite ".backup '/home//backups/wedding-$(date +\%F).sqlite'"
```

---

## Scripts reference

| Command | What it does |
| ------------------ | ------------------------------------------------ |
| `bun run dev` | Vite dev server with SSR + HMR on `:3000` |
| `bun run build` | Production build into `dist/` |
| `bun run start` | Run the built server (`bun serve.ts`) on `:3000` |
| `bun run typecheck`| `tsc --noEmit` |
| `bun run db:init` | Create tables and insert a demo invite |