Skip to content

Commit d54c03e

Browse files
Add way too much
Resource reordering, better MD rendering, restricting to universities
1 parent 51c2f13 commit d54c03e

14 files changed

Lines changed: 225 additions & 39 deletions

File tree

functions/auth/models.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
from dataclasses import dataclass, field
22
from typing import Literal, Optional
33

4+
45
@dataclass
56
class User:
67
id: str
78
"""Unique identifier for the user. Probably their Discord ID"""
89

9-
role: Literal['participant', 'organizer'] # 'participant' | 'organizer'
10+
role: Literal["participant", "organizer"] # 'participant' | 'organizer'
1011
username: str
1112

1213
# registration info (required for participant data structure)
@@ -17,6 +18,7 @@ class User:
1718
shirtSize: Optional[str] = None
1819
dietaryRestrictions: Optional[str] = None
1920
rfidUUID: Optional[str] = None
21+
university: Optional[str] = None
2022

2123
# init event info (there may be more defined in the frontend...just because these are the ones used in auth)
2224
teamId: Optional[str] = None # this will be unset (undefined) until team assignment

functions/auth/oauth_callback.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@
8585
default="RFID UUID",
8686
description="The header name of the column containing participants' RFID UUIDs.",
8787
)
88+
UNIVERSITY_COL_R = StringParam(
89+
"UNIVERSITY_COL_R",
90+
default="University",
91+
description="The header name of the column containing participants' university in the Registrations sheet.",
92+
)
8893

8994

9095
def _get_col_index_from_headers(headers: list[str], name: str) -> int:
@@ -113,7 +118,9 @@ def _get_registration(uid: str, username: str) -> User:
113118

114119
import gspread
115120

116-
is_organizer = uid in [o.strip() for o in ORGANIZERS_LIST.value.split(",") if o.strip()]
121+
is_organizer = uid in [
122+
o.strip() for o in ORGANIZERS_LIST.value.split(",") if o.strip()
123+
]
117124

118125
if is_organizer:
119126
return User(id=uid, role="organizer", username=username)
@@ -142,7 +149,9 @@ def _get_registration(uid: str, username: str) -> User:
142149
reg_headers, USERNAME_COL_R.value
143150
)
144151

145-
cell = reg_sheet.find(username, in_column=username_col_idx, case_sensitive=False)
152+
cell = reg_sheet.find(
153+
username, in_column=username_col_idx, case_sensitive=False
154+
)
146155

147156
# participant not registered
148157
if not cell:
@@ -196,6 +205,11 @@ def _get_registration(uid: str, username: str) -> User:
196205
rfid_idx = _get_col_index_from_headers(checkin_headers, RFID_UUID_COL_C.value)
197206
rfid_uuid = _get_cell_from_row(checkin_row, rfid_idx)
198207

208+
university_idx = _get_col_index_from_headers(
209+
reg_headers, UNIVERSITY_COL_R.value
210+
)
211+
university = _get_cell_from_row(reg_row, university_idx)
212+
199213
print(
200214
email,
201215
first_name,
@@ -204,6 +218,7 @@ def _get_registration(uid: str, username: str) -> User:
204218
dietary_restrictions,
205219
shirt_size,
206220
rfid_uuid,
221+
university,
207222
)
208223

209224
return User(
@@ -217,6 +232,7 @@ def _get_registration(uid: str, username: str) -> User:
217232
dietaryRestrictions=dietary_restrictions,
218233
shirtSize=shirt_size,
219234
rfidUUID=rfid_uuid,
235+
university=university,
220236
# mark if they checked in on friday as having attended the career fair event
221237
attendedEvents=["career_fair_friday"] if friday_checked_in else [],
222238
)

functions/firestore/event.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,24 +30,42 @@ def initialize_event(request: https_fn.CallableRequest) -> None:
3030

3131
default_config = {
3232
"tracks": [
33-
{"name": "Track 1", "description": "Sample elite ball track"},
34-
{"name": "Track 2", "description": "Sample great ball track"},
35-
{"name": "Track 3", "description": "Sample poke ball track"},
33+
{
34+
"name": "Track 1",
35+
"description": "Sample elite ball track",
36+
"fullDescription": "",
37+
"allowedUniversities": [],
38+
},
39+
{
40+
"name": "Track 2",
41+
"description": "Sample great ball track",
42+
"fullDescription": "",
43+
"allowedUniversities": [],
44+
},
45+
{
46+
"name": "Track 3",
47+
"description": "Sample poke ball track",
48+
"fullDescription": "",
49+
"allowedUniversities": [],
50+
},
3651
],
3752
"challenges": [
3853
{
3954
"name": "Sample Challenge 1",
4055
"description": "This is a sample challenge description.",
56+
"fullDescription": "",
4157
"category": "default",
4258
},
4359
{
4460
"name": "MLH Best Use of AI",
4561
"description": "Build a project that uses AI in a creative way.",
62+
"fullDescription": "",
4663
"category": "mlh",
4764
},
4865
{
4966
"name": "MLH Best Domain Name",
5067
"description": "Register a domain name for your project.",
68+
"fullDescription": "",
5169
"category": "mlh",
5270
},
5371
],

functions/firestore/teams.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ def submit_team_registration(request: https_fn.CallableRequest) -> None:
5555
event_config_doc = db.collection("event").document("main").get()
5656
event_config = event_config_doc.to_dict() if event_config_doc.exists else {}
5757
configured_challenges = event_config.get("challenges", [])
58+
configured_tracks = event_config.get("tracks", [])
5859

5960
# Build a lookup of challenge name -> category
6061
challenge_category_map = {}
@@ -82,12 +83,14 @@ def submit_team_registration(request: https_fn.CallableRequest) -> None:
8283
# check if members are not already in a team (check their teamId field)
8384

8485
users_ref = db.collection("users")
86+
member_docs = {}
8587

8688
for member_id in member_ids:
8789
user_doc = users_ref.document(member_id).get()
8890

8991
if user_doc.exists:
9092
user_data = user_doc.to_dict()
93+
member_docs[member_id] = user_data
9194

9295
if user_data and user_data.get("teamId"):
9396
raise https_fn.HttpsError(
@@ -100,6 +103,26 @@ def submit_team_registration(request: https_fn.CallableRequest) -> None:
100103
message=f"User {member_id} not found.",
101104
)
102105

106+
# Validate track university restrictions
107+
selected_track_config = next(
108+
(t for t in configured_tracks if t.get("name") == track), None
109+
)
110+
if selected_track_config:
111+
allowed_universities = selected_track_config.get("allowedUniversities", [])
112+
if allowed_universities:
113+
for member_id in member_ids:
114+
member_data = member_docs.get(member_id, {})
115+
member_university = (member_data.get("university") or "").strip()
116+
if member_university not in allowed_universities:
117+
raise https_fn.HttpsError(
118+
code=https_fn.FunctionsErrorCode.INVALID_ARGUMENT,
119+
message=(
120+
f"Your selected track is restricted to certain universities, "
121+
f"and one or more of your team members are not from an eligible university. "
122+
f"If you believe this is a mistake, please open a support ticket on the Discord."
123+
),
124+
)
125+
103126
teams_ref = db.collection("teams")
104127
team_doc_ref = teams_ref.document()
105128

src/atoms/event/resources.ts

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,28 @@ export const resourcesAtom = atom((get) => {
1111

1212
// straight up hardcoding the tracks and challenges resource here to always be in sync 😭😭
1313
let tracksContent = tracks
14-
.map((t) => `**${t.name}**\\\n${t.description ?? ""}`)
14+
.map((t) => {
15+
const isRestricted = t.allowedUniversities && t.allowedUniversities.length > 0;
16+
const desc = t.fullDescription || t.description || "";
17+
let entry = `**${t.name}**${isRestricted ? "*" : ""}`;
18+
if (desc) entry += ` \n${desc}`;
19+
if (isRestricted) {
20+
entry += ` \n*\\* this track is restricted to students of: ${t.allowedUniversities.join(", ")}*`;
21+
}
22+
return entry;
23+
})
1524
.join("\n\n");
16-
tracksContent = `Select the track that best fits your project. You can only submit to one track.\\\n\\\n${tracksContent}`;
25+
tracksContent = `Select the track that best fits your project. You can only submit to one track.\n\n${tracksContent}`;
1726

1827
let challengesContent = challenges
19-
.map((c) => `**${c.name}**\\\n${c.description ?? ""}`)
28+
.map((c) => {
29+
const desc = c.fullDescription || c.description || "";
30+
let entry = `**${c.name}**`;
31+
if (desc) entry += ` \n${desc}`;
32+
return entry;
33+
})
2034
.join("\n\n");
21-
challengesContent = `Complete these additional challenges for extra prizes. You can only submit to one challenge.\\\n\\\n${challengesContent}`;
35+
challengesContent = `Participate in these additional challenges for extra prizes.\n\n*You can only choose one challenge. However, you can choose to participate in any additional MLH challenges as well.*\n\n${challengesContent}`;
2236

2337
const newResources = [...resources];
2438

@@ -101,6 +115,33 @@ export const deleteResourceAtom = atom(null, async (get, _, index: number) => {
101115
});
102116

103117
// TODO: add reorderResourceAtom?
118+
export const reorderResourceAtom = atom(
119+
null,
120+
async (
121+
get,
122+
_,
123+
payload: { fromIndex: number; toIndex: number },
124+
) => {
125+
const resources = get(resourcesAtom);
126+
if (!resources) return;
127+
128+
const { fromIndex, toIndex } = payload;
129+
if (
130+
fromIndex < 0 ||
131+
fromIndex >= resources.length ||
132+
toIndex < 0 ||
133+
toIndex >= resources.length
134+
)
135+
return;
136+
137+
const updated = [...resources];
138+
const [moved] = updated.splice(fromIndex, 1);
139+
updated.splice(toIndex, 0, moved);
140+
141+
await firestoreService.updateEventConfig({ resources: updated });
142+
},
143+
);
144+
104145
export const setResourceAtom = atom(
105146
null,
106147
async (get, _, payload: { index: number; resource: Resource }) => {

src/components/TeamForm.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -404,10 +404,14 @@ export default function TeamForm({
404404
<FieldLabel>
405405
Members ({members.length}/{maxMembers})
406406
</FieldLabel>
407+
407408
<span className="text-xs text-muted-foreground">
408409
Min {minMembers} members required
409410
</span>
410411
</div>
412+
<FieldDescription>
413+
Ensure that all your team members have logged into Hack_NCState Today at least once in order to appear in the search results.
414+
</FieldDescription>
411415

412416
<div className="flex flex-col gap-2">
413417
<ItemGroup>
@@ -486,8 +490,7 @@ export default function TeamForm({
486490
query.length >= 2 &&
487491
filteredSearchResults.length === 0 && (
488492
<CommandEmpty>
489-
No users found. Make sure everyone from your team
490-
has logged in for them to appear here.
493+
No users found
491494
</CommandEmpty>
492495
)}
493496
{!loading &&

src/pages/Home/Main/OrganizerView/ChallengeEditor.tsx

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,16 @@ function ChallengeForm({
4040
const [description, setDescription] = useState(
4141
initialChallenge?.description ?? "",
4242
);
43+
const [fullDescription, setFullDescription] = useState(
44+
initialChallenge?.fullDescription ?? "",
45+
);
4346
const [category, setCategory] = useState<ChallengeCategory>(
4447
initialChallenge?.category ?? "default",
4548
);
4649

4750
const handleSubmit = (e: React.FormEvent) => {
4851
e.preventDefault();
49-
onSave({ name, description, category });
52+
onSave({ name, description, fullDescription, category });
5053
};
5154

5255
return (
@@ -62,11 +65,21 @@ function ChallengeForm({
6265
</div>
6366

6467
<div className="flex flex-col gap-2">
65-
<Label>Description</Label>
66-
<Textarea
68+
<Label>Brief Description</Label>
69+
<Input
6770
value={description}
6871
onChange={(e) => setDescription(e.target.value)}
69-
placeholder="Challenge Description"
72+
placeholder="Short description shown during team registration"
73+
/>
74+
</div>
75+
76+
<div className="flex flex-col gap-2">
77+
<Label>Full Description (Markdown)</Label>
78+
<Textarea
79+
value={fullDescription}
80+
onChange={(e) => setFullDescription(e.target.value)}
81+
placeholder="Detailed description shown in the Resources panel. Supports markdown."
82+
rows={4}
7083
/>
7184
</div>
7285

0 commit comments

Comments
 (0)