Skip to content

Commit aa72b3f

Browse files
Update UI tweaks
1 parent 2684e11 commit aa72b3f

4 files changed

Lines changed: 114 additions & 59 deletions

File tree

functions/auth/oauth_callback.py

Lines changed: 78 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from dataclasses import asdict
22
import json
33
import os
4+
import time
5+
import threading
46
import requests
57
from firebase_functions import https_fn
68
from firebase_functions.params import SecretParam, StringParam
@@ -92,6 +94,63 @@
9294
)
9395

9496

97+
# ---------------------------------------------------------------------------
98+
# In-memory spreadsheet cache – avoids hitting Google Sheets on every login.
99+
# Cloud Functions reuse warm instances, so the cache survives across
100+
# invocations on the same instance. A TTL (default 5 min) keeps it fresh.
101+
# ---------------------------------------------------------------------------
102+
103+
SHEET_CACHE_TTL_SECONDS = 300 # 5 minutes
104+
105+
_sheet_cache_lock = threading.Lock()
106+
_sheet_cache: dict | None = (
107+
None # {"ts": float, "reg_headers": [...], "reg_rows": [...], "checkin_headers": [...], "checkin_rows": [...]}
108+
)
109+
110+
111+
def _fetch_sheets_data() -> dict:
112+
"""Fetch both worksheets from Google Sheets and return them as a dict."""
113+
import gspread
114+
115+
creds, _ = google.auth.default(
116+
scopes=[
117+
"https://www.googleapis.com/auth/spreadsheets.readonly",
118+
"https://www.googleapis.com/auth/drive",
119+
]
120+
)
121+
gc = gspread.authorize(creds)
122+
spreadsheet = gc.open_by_url(SPREADSHEET_URL.value)
123+
124+
reg_sheet = spreadsheet.worksheet("Registration Submissions")
125+
checkin_sheet = spreadsheet.worksheet("Check-in")
126+
127+
# Batch-fetch all values in two calls (instead of per-row later)
128+
reg_all = reg_sheet.get_all_values()
129+
checkin_all = checkin_sheet.get_all_values()
130+
131+
return {
132+
"ts": time.monotonic(),
133+
"reg_headers": reg_all[0] if reg_all else [],
134+
"reg_rows": reg_all[1:] if len(reg_all) > 1 else [],
135+
"checkin_headers": checkin_all[0] if checkin_all else [],
136+
"checkin_rows": checkin_all[1:] if len(checkin_all) > 1 else [],
137+
}
138+
139+
140+
def _get_cached_sheets() -> dict:
141+
"""Return cached sheet data, refreshing if stale or missing."""
142+
global _sheet_cache
143+
with _sheet_cache_lock:
144+
now = time.monotonic()
145+
if _sheet_cache is None or (now - _sheet_cache["ts"]) > SHEET_CACHE_TTL_SECONDS:
146+
print("[sheet-cache] cache miss – fetching from Google Sheets")
147+
_sheet_cache = _fetch_sheets_data()
148+
else:
149+
age = round(now - _sheet_cache["ts"], 1)
150+
print(f"[sheet-cache] cache hit (age {age}s)")
151+
return _sheet_cache
152+
153+
95154
def _get_col_index_from_headers(headers: list[str], name: str) -> int:
96155
"""Return 1-based column index for a given header name.
97156
@@ -116,49 +175,40 @@ def _get_registration(uid: str, username: str) -> User:
116175
Organizers will not need to be present on the spreadsheet
117176
"""
118177

119-
import gspread
120-
121178
is_organizer = uid in [
122179
o.strip() for o in ORGANIZERS_LIST.value.split(",") if o.strip()
123180
]
124181

125182
if is_organizer:
126183
return User(id=uid, role="organizer", username=username)
127184
else:
128-
creds, _ = google.auth.default(
129-
scopes=[
130-
"https://www.googleapis.com/auth/spreadsheets.readonly",
131-
"https://www.googleapis.com/auth/drive",
132-
]
133-
)
134-
gc = gspread.authorize(creds)
135-
136-
spreadsheet = gc.open_by_url(
137-
SPREADSHEET_URL.value,
138-
)
139-
140-
print(spreadsheet.worksheets())
141-
142-
reg_sheet = spreadsheet.worksheet("Registration Submissions")
143-
checkin_sheet = spreadsheet.worksheet("Check-in")
144-
145-
reg_headers = reg_sheet.row_values(1)
146-
checkin_headers = checkin_sheet.row_values(1)
185+
sheets = _get_cached_sheets()
186+
reg_headers = sheets["reg_headers"]
187+
checkin_headers = sheets["checkin_headers"]
147188

148189
username_col_idx = _get_col_index_from_headers(
149190
reg_headers, USERNAME_COL_R.value
150191
)
151192

152-
cell = reg_sheet.find(
153-
username, in_column=username_col_idx, case_sensitive=False
154-
)
193+
# Search for the user in cached registration rows
194+
username_lower = username.lower()
195+
matched_row_idx: int | None = None
196+
for i, row in enumerate(sheets["reg_rows"]):
197+
cell_val = _get_cell_from_row(row, username_col_idx)
198+
if cell_val.strip().lower() == username_lower:
199+
matched_row_idx = i
200+
break
155201

156202
# participant not registered
157-
if not cell:
203+
if matched_row_idx is None:
158204
raise ValueError("participant_not_found")
159205

160-
reg_row = reg_sheet.row_values(cell.row)
161-
checkin_row = checkin_sheet.row_values(cell.row)
206+
reg_row = sheets["reg_rows"][matched_row_idx]
207+
checkin_row = (
208+
sheets["checkin_rows"][matched_row_idx]
209+
if matched_row_idx < len(sheets["checkin_rows"])
210+
else []
211+
)
162212

163213
checked_in_idx = _get_col_index_from_headers(
164214
checkin_headers, CHECKED_IN_COL_C.value
@@ -259,7 +309,7 @@ def _generate_login_token(uid: str, username: str) -> str:
259309
user = _get_registration(uid, username)
260310

261311
# If login is disabled for participants, reject them
262-
if user.role != "organizer" and os.getenv("FUNCTIONS_EMULATOR") == "false":
312+
if user.role != "organizer" and os.getenv("FUNCTIONS_EMULATOR") != "true":
263313
db = firestore.client()
264314
event_doc = db.document("event/main").get()
265315
if event_doc.exists:

src/pages/ParticipantManager.tsx

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,19 @@ export default function ParticipantManager() {
3232

3333
return (
3434
<TableRow key={user.id}>
35-
<TableCell>{user.username}</TableCell>
36-
<TableCell>{`${user.firstName} ${user.lastName}`}</TableCell>
37-
<TableCell>{user.email}</TableCell>
38-
<TableCell>{user.university || "N/A"}</TableCell>
35+
<TableCell className="break-words">{user.username}</TableCell>
36+
<TableCell className="break-words">{`${user.firstName} ${user.lastName}`}</TableCell>
37+
<TableCell className="break-words">{user.email}</TableCell>
38+
<TableCell className="break-words">
39+
{user.university || "N/A"}
40+
</TableCell>
3941
<TableCell>{user.shirtSize}</TableCell>
40-
<TableCell>{user.dietaryRestrictions || "None"}</TableCell>
41-
<TableCell>{user.attendedEvents.join(", ") || "None"}</TableCell>
42+
<TableCell className="break-words">
43+
{user.dietaryRestrictions || "None"}
44+
</TableCell>
45+
<TableCell className="break-words">
46+
{user.attendedEvents.join(", ") || "None"}
47+
</TableCell>
4248
<TableCell className="justify-end flex">
4349
{resumeURL && (
4450
<Button
@@ -86,17 +92,17 @@ export default function ParticipantManager() {
8692
</header>
8793

8894
<main>
89-
<Table>
95+
<Table className="table-fixed w-full">
9096
<TableHeader>
9197
<TableRow>
92-
<TableHead>Username</TableHead>
93-
<TableHead>Name</TableHead>
94-
<TableHead>Email</TableHead>
95-
<TableHead>University</TableHead>
96-
<TableHead>Shirt Size</TableHead>
97-
<TableHead>Dietary Restrictions</TableHead>
98-
<TableHead>Events Attended</TableHead>
99-
<TableHead className="w-0">Actions</TableHead>
98+
<TableHead className="w-[10%]">Username</TableHead>
99+
<TableHead className="w-[12%]">Name</TableHead>
100+
<TableHead className="w-[15%]">Email</TableHead>
101+
<TableHead className="w-[12%]">University</TableHead>
102+
<TableHead className="w-[8%]">Shirt Size</TableHead>
103+
<TableHead className="w-[13%]">Dietary Restrictions</TableHead>
104+
<TableHead className="w-[18%]">Events Attended</TableHead>
105+
<TableHead className="w-[12%]">Actions</TableHead>
100106
</TableRow>
101107
</TableHeader>
102108

src/pages/RFIDReader.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { activitiesAtom } from "@/atoms/event/activities";
22
import { Button } from "@/components/ui/button";
3-
import { ButtonGroup } from "@/components/ui/button-group";
43
import { cn } from "@/lib/utils";
54
import { firestoreService } from "@/services/firestore.service";
65
import { rfidService } from "@/services/rfid.service";
@@ -174,7 +173,7 @@ export default function RFIDReader() {
174173
<br />* means this activity is not eligible for the raffle.
175174
</p>
176175

177-
<ButtonGroup orientation="horizontal" className="mt-2">
176+
<div className="mt-2 flex flex-wrap gap-2">
178177
<Button
179178
variant={!selectedActivity ? "default" : "outline"}
180179
onClick={() => setSelectedActivity(null)}
@@ -196,7 +195,7 @@ export default function RFIDReader() {
196195
{!activity.eligibleForRaffle && "*"}
197196
</Button>
198197
))}
199-
</ButtonGroup>
198+
</div>
200199

201200
{showActivityAssignedToast === "success" && (
202201
<p className="mt-2 text-green-500">

src/pages/TeamManager.tsx

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -194,15 +194,15 @@ export default function TeamManager() {
194194
key={team.id}
195195
className={isUnverified ? "bg-yellow-50 dark:bg-yellow-900/20" : ""}
196196
>
197-
<TableCell>{team.name}</TableCell>
198-
<TableCell>{team.track}</TableCell>
199-
<TableCell>
197+
<TableCell className="break-words">{team.name}</TableCell>
198+
<TableCell className="break-words">{team.track}</TableCell>
199+
<TableCell className="break-words">
200200
{team.challenges.length > 0 ? team.challenges.join(", ") : "-"}
201201
</TableCell>
202-
<TableCell>
202+
<TableCell className="break-words">
203203
<TeamMembersList team={team} />
204204
</TableCell>
205-
<TableCell className="whitespace-pre-wrap">
205+
<TableCell className="whitespace-pre-wrap break-words">
206206
{team.mentoringHelp}
207207
</TableCell>
208208
<TableCell>{team.status}</TableCell>
@@ -268,16 +268,16 @@ export default function TeamManager() {
268268
</header>
269269

270270
<main>
271-
<Table>
271+
<Table className="table-fixed w-full">
272272
<TableHeader>
273273
<TableRow>
274-
<TableHead>Name</TableHead>
275-
<TableHead>Track</TableHead>
276-
<TableHead>Challenge</TableHead>
277-
<TableHead>Members</TableHead>
278-
<TableHead>Mentoring Help</TableHead>
279-
<TableHead className="w-0">Status</TableHead>
280-
<TableHead className="w-0">Actions</TableHead>
274+
<TableHead className="w-[12%]">Name</TableHead>
275+
<TableHead className="w-[10%]">Track</TableHead>
276+
<TableHead className="w-[15%]">Challenge</TableHead>
277+
<TableHead className="w-[18%]">Members</TableHead>
278+
<TableHead className="w-[20%]">Mentoring Help</TableHead>
279+
<TableHead className="w-[8%]">Status</TableHead>
280+
<TableHead className="w-[17%]">Actions</TableHead>
281281
</TableRow>
282282
</TableHeader>
283283

0 commit comments

Comments
 (0)