🪻 distributed transcription service thistle.dunkirk.sh

feat: grey out registered classes and remove section code

dunkirk.sh ae943ba8 e4c3b131

verified
Changed files
+68 -18
src
+51 -16
src/components/class-registration-modal.ts
···
professor: string;
semester: string;
year: number;
+
is_enrolled?: boolean;
}
@customElement("class-registration-modal")
···
transition: all 0.2s;
}
-
.class-card:hover:not(:disabled) {
+
.class-card.enrolled {
+
opacity: 0.6;
+
background: var(--background);
+
cursor: default;
+
}
+
+
.class-card:hover:not(:disabled):not(.enrolled) {
border-color: var(--accent);
transform: translateX(4px);
}
···
cursor: not-allowed;
}
+
.enrolled-badge {
+
display: inline-block;
+
padding: 0.25rem 0.5rem;
+
background: var(--secondary);
+
color: var(--text);
+
border-radius: 4px;
+
font-size: 0.75rem;
+
font-weight: 600;
+
text-transform: uppercase;
+
}
+
.class-header {
display: flex;
justify-content: space-between;
···
this.isSearching = true;
this.error = "";
+
this.suggestedQuery = "";
this.hasSearched = true;
+
// Auto-remove section numbers (e.g., MATH-1720-01 -> MATH-1720)
+
let queryToSearch = this.searchQuery.trim();
+
if (queryToSearch.match(/.*-\d{2,}$/)) {
+
queryToSearch = queryToSearch.replace(/-\d{2,}$/, "");
+
this.searchQuery = queryToSearch;
+
}
+
try {
const response = await fetch(
-
`/api/classes/search?q=${encodeURIComponent(this.searchQuery.trim())}`,
+
`/api/classes/search?q=${encodeURIComponent(queryToSearch)}`,
);
if (!response.ok) {
···
${this.results.map(
(cls) => html`
<button
-
class="class-card"
-
@click=${() => this.handleJoin(cls.id)}
-
?disabled=${this.isJoining}
+
class="class-card ${cls.is_enrolled ? "enrolled" : ""}"
+
@click=${() => !cls.is_enrolled && this.handleJoin(cls.id)}
+
?disabled=${this.isJoining || cls.is_enrolled}
>
<div class="class-header">
<div class="class-info">
-
<div class="course-code">${cls.course_code}</div>
+
<div class="course-code">
+
${cls.course_code}
+
${cls.is_enrolled ? html`<span class="enrolled-badge">Registered</span>` : ""}
+
</div>
<div class="class-name">${cls.name}</div>
<div class="class-meta">
<span>👤 ${cls.professor}</span>
<span>📅 ${cls.semester} ${cls.year}</span>
</div>
</div>
-
<button
-
class="join-btn"
-
?disabled=${this.isJoining}
-
@click=${(e: Event) => {
-
e.stopPropagation();
-
this.handleJoin(cls.id);
-
}}
-
>
-
${this.isJoining ? "Joining..." : "Join"}
-
</button>
+
${
+
!cls.is_enrolled
+
? html`
+
<button
+
class="join-btn"
+
?disabled=${this.isJoining}
+
@click=${(e: Event) => {
+
e.stopPropagation();
+
this.handleJoin(cls.id);
+
}}
+
>
+
${this.isJoining ? "Joining..." : "Join"}
+
</button>
+
`
+
: ""
+
}
</div>
</button>
`,
+17 -2
src/index.ts
···
"/api/classes/search": {
GET: async (req) => {
try {
-
requireAuth(req);
+
const user = requireAuth(req);
const url = new URL(req.url);
const query = url.searchParams.get("q");
···
const classes = searchClassesByCourseCode(query);
-
return Response.json({ classes });
+
+
// Get user's enrolled classes to mark them
+
const enrolledClassIds = db
+
.query<{ class_id: string }, [number]>(
+
"SELECT class_id FROM class_members WHERE user_id = ?",
+
)
+
.all(user.id)
+
.map((row) => row.class_id);
+
+
// Add is_enrolled flag to each class
+
const classesWithEnrollment = classes.map((cls) => ({
+
...cls,
+
is_enrolled: enrolledClassIds.includes(cls.id),
+
}));
+
+
return Response.json({ classes: classesWithEnrollment });
} catch (error) {
return handleError(error);