Skip to content

Commit f7decc0

Browse files
Merge pull request #9 from foundersandcoders/feature/ap-17-admin-calendar-page
feat: admin events page with cohort filter
2 parents eb981d0 + cddfd5e commit f7decc0

File tree

12 files changed

+464
-11
lines changed

12 files changed

+464
-11
lines changed

.claude/agents/report_writer.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
name: report_writer
3+
description: Keeps report.md (the project's report) updated
4+
model: sonnet
5+
tools:
6+
- Read
7+
- Write
8+
- Bash
9+
---

docs/planning/sprint02_review.md

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
# Sprint 2 Review
2+
3+
**Sprint:** 2
4+
**Dates:** 19–29 December 2025
5+
**Duration:** ~6 working days (including Christmas break)
6+
7+
---
8+
9+
## Summary
10+
11+
Sprint 2 focused on authentication and event management foundations. Completed magic link auth with role-based protection, Resend email integration, and Events CRUD service. AP-17 (admin events page) was partially completed and refined after discovering additional requirements for public events.
12+
13+
---
14+
15+
## Metrics
16+
17+
| Metric | Value |
18+
|--------|-------|
19+
| Planned | 9 tickets, 18 story points |
20+
| Completed | 7 tickets, 12 story points |
21+
| Carried Over | 1 ticket (AP-17), partially done |
22+
| Velocity | 12 points |
23+
| Commits | 67 |
24+
| PRs Merged | 7 (#2#8) |
25+
26+
---
27+
28+
## Completed Tickets
29+
30+
| Key | Summary | Points | Status |
31+
|-----|---------|--------|--------|
32+
| AP-10 | Create Events table in Airtable | 1 | Done |
33+
| AP-11 | Add Event link field to Attendance table | 1 | Done |
34+
| AP-12 | Create Staff table in Airtable | 1 | Done |
35+
| AP-13 | Set up Resend email integration | 2 | Done |
36+
| AP-14 | Implement magic link authentication | 3 | Done |
37+
| AP-15 | Role-based route protection (SvelteKit hooks) | 2 | Done |
38+
| AP-16 | Event service module (Airtable CRUD) | 2 | Done |
39+
40+
---
41+
42+
## Carried Over
43+
44+
| Key | Summary | Points | Progress |
45+
|-----|---------|--------|----------|
46+
| AP-17 | Admin events page (cohort events, registered users) | 2 | ~25% - load function created |
47+
48+
**Reason:** During implementation, we discovered the need to handle public events (no cohort) and external attendees (not in Airtable). Story was split to deliver incrementally.
49+
50+
---
51+
52+
## New Ticket Created
53+
54+
| Key | Summary | Points |
55+
|-----|---------|--------|
56+
| AP-19 | Public events and external attendee check-in | 3 |
57+
58+
Split from AP-17 to handle:
59+
- Events without cohort (open to all)
60+
- External Name/Email fields on Attendance table
61+
- Public check-in flow for unregistered attendees
62+
63+
---
64+
65+
## Deliverables
66+
67+
**Authentication System:**
68+
- Magic link auth with 15-min JWT tokens
69+
- Separate staff/student login flows
70+
- 90-day session cookies
71+
- `src/lib/server/auth.ts` — token generation/verification
72+
- `src/lib/server/session.ts` — cookie helpers
73+
74+
**Route Protection:**
75+
- `src/hooks.server.ts` — middleware for protected routes
76+
- `/admin/*` → staff only
77+
- `/checkin` → any authenticated user
78+
- Login pages redirect if already authenticated
79+
80+
**Email Integration:**
81+
- Resend configured for magic link delivery
82+
- `src/lib/server/email.ts` — email service
83+
- Environment variable for sender email
84+
85+
**Events Service:**
86+
- Full CRUD operations in `src/lib/airtable/events.ts`
87+
- TypeScript types in `src/lib/types/event.ts`
88+
- Unit tests with Vitest mocks
89+
- Manual test script for verification
90+
91+
**Schema Updates:**
92+
- Events table with Name, DateTime, Cohort, EventType, Survey fields
93+
- Attendance → Event link field
94+
- Staff table with singleCollaborator field
95+
- Updated `docs/schema.md` with relationships diagram
96+
97+
---
98+
99+
## Key Decisions
100+
101+
1. **Separate login flows:** Staff and students have distinct endpoints and pages for clearer UX
102+
2. **Field IDs over names:** All Airtable queries use field IDs to prevent breakage if fields are renamed
103+
3. **Factory pattern:** Airtable clients use factory functions for testability
104+
4. **Story splitting:** AP-17 split into cohort-specific (AP-17) and public events (AP-19) for incremental delivery
105+
106+
---
107+
108+
## Design Decision: External Attendees
109+
110+
Discovered during AP-17 that we need to support:
111+
- Open events (no cohort) visible to everyone
112+
- External attendees not registered in Airtable
113+
114+
**Solution:** Add `External Name` and `External Email` fields to Attendance table.
115+
116+
| Field | Registered User | External User |
117+
|-------|-----------------|---------------|
118+
| Apprentice (link) | ✓ Populated | Empty |
119+
| External Name | Empty | ✓ Populated |
120+
| External Email | Empty | ✓ Populated |
121+
122+
Each attendance record has either an Apprentice link OR External fields (not both).
123+
124+
---
125+
126+
## Risks Identified
127+
128+
| Risk | Mitigation |
129+
|------|------------|
130+
| Public check-in abuse | Rate limiting, time-window validation |
131+
| EventType changes in Airtable | Documented in README, requires code update |
132+
| filterByFormula uses field names | Documented which queries use names vs IDs |
133+
134+
---
135+
136+
## Retrospective Notes
137+
138+
**What went well:**
139+
- Authentication system working end-to-end
140+
- Clean separation of concerns (auth, session, email, events)
141+
- Good test coverage for auth and events modules
142+
- Sprint review caught scope gap early
143+
144+
**What could improve:**
145+
- Initial story estimation missed public events requirement
146+
- Christmas break disrupted momentum
147+
148+
**Action items for Sprint 3:**
149+
- Complete AP-17 (admin events page for cohort events)
150+
- Implement AP-18 (event create/edit/delete UI)
151+
- Begin AP-19 (public events + external check-in) if time permits
152+
153+
---
154+
155+
## Next Sprint Focus
156+
157+
Sprint 3 will deliver the complete event management system and full check-in flow:
158+
159+
**Event Management:**
160+
- AP-17: Admin events page (display, filter by cohort)
161+
- AP-18: Event create/edit/delete UI
162+
163+
**Check-in Flow:**
164+
- AP-19: Public events + external attendee check-in
165+
- Attendance service module (record check-ins to Airtable)
166+
- Check-in UI for registered users (staff/students)
167+
168+
With authentication, route protection, and Events CRUD foundations in place, the project has momentum to deliver end-to-end functionality.

docs/planning/sprint03.png

102 KB
Loading

docs/scratchpad.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@ Show time counter negative and positive
22

33
Show mii plaza
44

5-
integrstion with LUMA
5+
Integration with LUMA
6+
7+
Workshops for external people (emails not on airtable)

docs/user-stories.md

Lines changed: 81 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,28 @@ Requirements for Apprentice Pulse, derived from project proposal acceptance crit
1515
## Epic 1: Attendance Management
1616

1717
### US-1: Event management
18-
**As** FAC staff
19-
**I want to** create, modify, and delete events via a calendar interface
18+
**As** FAC staff
19+
**I want to** create, modify, and delete events via an admin interface
2020
**So that** I can manage the training schedule and enable attendance tracking
2121

2222
**Acceptance Criteria:**
23-
- Calendar view displays existing events from Airtable
24-
- Create event: select date, cohort, and event type (regular class, workshop, hackathon)
25-
- Edit event: update date, cohort, or event type
26-
- Delete event: remove event from Airtable
27-
- Event syncs to Attendance (Current) table
23+
- Admin page displays existing events from Airtable (list view)
24+
- Filter events by cohort
25+
- Create event: name, date/time, cohort (optional), event type
26+
- Edit event: update any field
27+
- Delete event: remove from Airtable
28+
- 4-digit check-in code auto-generated for each event
2829
- Event type stored for filtering and reporting
2930

31+
**Event Types:**
32+
- Cohort event (has cohort) → only that cohort's apprentices can check in
33+
- Open event (no cohort) → anyone can check in (registered + external)
34+
3035
---
3136

3237
### US-2: Check-in to event
33-
**As an** apprentice
34-
**I want to** register my attendance by tapping an NFC sticker or scanning a QR code
38+
**As an** apprentice
39+
**I want to** register my attendance by tapping an NFC sticker or scanning a QR code
3540
**So that** I can check in quickly without manual roll calls
3641

3742
**Acceptance Criteria:**
@@ -41,6 +46,73 @@ Requirements for Apprentice Pulse, derived from project proposal acceptance crit
4146
- Attendance record written to Airtable
4247
- Confirmation message displayed
4348

49+
#### Check-in Workflow (Detailed)
50+
51+
**Single URL:** `/checkin` - used for all check-in methods (QR, NFC, direct link)
52+
53+
**Event Types:**
54+
| Type | Has Cohort | Who Can Attend |
55+
|------|------------|----------------|
56+
| Cohort Event | Yes | Only apprentices in that cohort |
57+
| Open Event | No | Anyone (registered + external) |
58+
59+
**Flow 1: Registered User (has session cookie)**
60+
```
61+
/checkin
62+
63+
Show available events:
64+
- User's cohort events
65+
- All open events (no cohort)
66+
67+
One-tap check-in button
68+
69+
Attendance record created (linked to Apprentice)
70+
```
71+
72+
**Flow 2: Registered User (no session)**
73+
```
74+
/checkin
75+
76+
Show login prompt
77+
78+
Enter email → Magic link sent
79+
80+
Click link → Session created (90 days)
81+
82+
Redirect to check-in → Show events → One-tap check-in
83+
```
84+
85+
**Flow 3: External User (public events only)**
86+
```
87+
/checkin
88+
89+
Select "Check in to public event"
90+
91+
Enter 4-digit event code (displayed at venue)
92+
93+
Enter name + email
94+
95+
Attendance record created (External Name/Email fields)
96+
```
97+
98+
**Security:**
99+
| User Type | Security Mechanism |
100+
|-----------|-------------------|
101+
| Registered | Magic link authentication (email verification) |
102+
| External | 4-digit event code (proves physical presence) |
103+
104+
**Edge Case:** If external user enters email that matches an Apprentice record → prompt them to log in instead (keeps attendance data consistent)
105+
106+
**Airtable Attendance Record:**
107+
| Field | Registered User | External User |
108+
|-------|-----------------|---------------|
109+
| Apprentice (link) | ✓ Populated | Empty |
110+
| External Name | Empty | ✓ Populated |
111+
| External Email | Empty | ✓ Populated |
112+
| Event (link) |||
113+
| Check-in Time |||
114+
| Status |||
115+
44116
---
45117

46118
### US-3: Attendance chase email
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Airtable Schema
2+
3+
## Learners / Events - Apprentice Pulse
4+
5+
Table ID: `tblkbskw4fuTq0E9p`
6+
7+
| Field | ID | Type |
8+
|-------|-----|------|
9+
| Name | `fldMCZijN6TJeUdFR` | singleLineText |
10+
| FAC Cohort | `fldcXDEDkeHvWTnxE` | multipleRecordLinks |
11+
| FAC Cohort (from FAC Cohort) | `fldsLUl1MrhhsVBe7` | multipleLookupValues |
12+
| Date Time | `fld8AkM3EanzZa5QX` | dateTime |
13+
| Survey | `fld9XBHnCWBtZiZah` | url |
14+
| Select | `fldo7fwAsFhkA1icC` | singleSelect |
15+
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Airtable Schema
2+
3+
## Learners / Attendace - Apprentice Pulse
4+
5+
Table ID: `tblkDbhJcuT9TTwFc`
6+
7+
| Field | ID | Type |
8+
|-------|-----|------|
9+
| Id | `fldGdpuw6SoHkQbOs` | autoNumber |
10+
| Apprentice | `fldOyo3hlj9Ht0rfZ` | multipleRecordLinks |
11+
| Cohort | `fldn53kWDE8GHg2Yy` | multipleLookupValues |
12+
| Checkin Time | `fldvXHPmoLlEA8EuN` | dateTime |
13+
| Status | `fldew45fDGpgl1aRr` | singleSelect |
14+
| Event | `fldiHd75LYtopwyN9` | multipleRecordLinks |
15+
| Date Time (from Event) | `fldokfSk68MhJGlm6` | multipleLookupValues |
16+
| FAC Cohort (from FAC Cohort) (from Event) | `fldE783vnY3SLjmh7` | multipleLookupValues |
17+
| FAC Cohort (from Event) | `fldkc9zLJe7NZVAz1` | multipleLookupValues |
18+
19+
## Learners / Events - Apprentice Pulse
20+
21+
Table ID: `tblkbskw4fuTq0E9p`
22+
23+
| Field | ID | Type |
24+
|-------|-----|------|
25+
| Name | `fldMCZijN6TJeUdFR` | singleLineText |
26+
| FAC Cohort | `fldcXDEDkeHvWTnxE` | multipleRecordLinks |
27+
| FAC Cohort (from FAC Cohort) | `fldsLUl1MrhhsVBe7` | multipleLookupValues |
28+
| Date Time | `fld8AkM3EanzZa5QX` | dateTime |
29+
| Survey | `fld9XBHnCWBtZiZah` | url |
30+
| Select | `fldo7fwAsFhkA1icC` | singleSelect |
31+
| Attendace - Apprentice Pulse | `fldcPf53fVfStFZsa` | multipleRecordLinks |
32+
| Name - Date | `fld7POykodV0LGsg1` | formula |
33+

src/lib/airtable/airtable.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ export interface Apprentice {
1010
cohortNumber: string | null;
1111
}
1212

13+
export interface Cohort {
14+
id: string;
15+
name: string;
16+
}
17+
1318
export type UserType = 'staff' | 'student';
1419

1520
export function createAirtableClient(apiKey: string, baseId: string) {
@@ -131,10 +136,29 @@ export function createAirtableClient(apiKey: string, baseId: string) {
131136
return apprenticeRecords.length > 0;
132137
}
133138

139+
/**
140+
* List all cohorts
141+
*/
142+
async function listCohorts(): Promise<Cohort[]> {
143+
const cohortsTable = base(TABLES.COHORTS);
144+
145+
const records = await cohortsTable
146+
.select({
147+
returnFieldsByFieldId: true,
148+
})
149+
.all();
150+
151+
return records.map(record => ({
152+
id: record.id,
153+
name: (record.get(COHORT_FIELDS.NUMBER) as string) || record.id,
154+
}));
155+
}
156+
134157
return {
135158
getApprenticesByFacCohort,
136159
findUserByEmail,
137160
findStaffByEmail,
138161
findApprenticeByEmail,
162+
listCohorts,
139163
};
140164
}

0 commit comments

Comments
 (0)