···
import { css, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators.js";
4
+
interface ClassResult {
9
+
section: string | null;
@customElement("class-registration-modal")
export class ClassRegistrationModal extends LitElement {
@property({ type: Boolean }) open = false;
7
-
@state() classCode = "";
8
-
@state() isSubmitting = false;
17
+
@state() searchQuery = "";
18
+
@state() results: ClassResult[] = [];
19
+
@state() isSearching = false;
20
+
@state() isJoining = false;
22
+
@state() hasSearched = false;
static override styles = css`
···
border: 2px solid var(--secondary);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
···
background: var(--secondary);
92
+
.search-section > label {
93
+
margin-bottom: 0.5rem;
99
+
align-items: center;
100
+
margin-bottom: 0.5rem;
103
+
.search-input-wrapper {
···
96
-
text-transform: uppercase;
···
border-color: var(--primary);
134
+
padding: 0.75rem 1.5rem;
135
+
background: var(--primary);
137
+
border: 2px solid var(--primary);
138
+
border-radius: 6px;
142
+
transition: all 0.2s;
143
+
font-family: inherit;
146
+
.search-btn:hover:not(:disabled) {
147
+
background: var(--gunmetal);
148
+
border-color: var(--gunmetal);
151
+
.search-btn:disabled {
153
+
cursor: not-allowed;
···
123
-
padding: 0.75rem 1.5rem;
124
-
border: 2px solid var(--primary);
125
-
border-radius: 6px;
178
+
background: var(--background);
179
+
border: 2px solid var(--secondary);
180
+
border-radius: 8px;
130
-
font-family: inherit;
186
+
.class-card:hover:not(:disabled) {
187
+
border-color: var(--accent);
188
+
transform: translateX(4px);
191
+
.class-card:disabled {
198
+
justify-content: space-between;
199
+
align-items: flex-start;
201
+
margin-bottom: 0.5rem;
209
+
font-size: 0.875rem;
211
+
color: var(--accent);
212
+
text-transform: uppercase;
216
+
font-size: 1.125rem;
219
+
color: var(--text);
225
+
font-size: 0.875rem;
226
+
color: var(--paynes-gray);
227
+
margin-top: 0.5rem;
231
+
padding: 0.5rem 1rem;
background: var(--primary);
234
+
border: 2px solid var(--primary);
235
+
border-radius: 6px;
236
+
font-size: 0.875rem;
239
+
transition: all 0.2s;
240
+
font-family: inherit;
241
+
white-space: nowrap;
144
-
.btn-primary:hover:not(:disabled) {
244
+
.join-btn:hover:not(:disabled) {
background: var(--gunmetal);
border-color: var(--gunmetal);
150
-
background: transparent;
151
-
color: var(--text);
152
-
border-color: var(--secondary);
249
+
.join-btn:disabled {
251
+
cursor: not-allowed;
155
-
.btn-secondary:hover:not(:disabled) {
156
-
border-color: var(--primary);
157
-
color: var(--primary);
255
+
text-align: center;
256
+
padding: 3rem 2rem;
257
+
color: var(--paynes-gray);
261
+
text-align: center;
263
+
color: var(--paynes-gray);
162
-
this.classCode = "";
268
+
this.searchQuery = "";
271
+
this.hasSearched = false;
this.dispatchEvent(new CustomEvent("close"));
private handleInput(e: Event) {
168
-
this.classCode = (e.target as HTMLInputElement).value.toUpperCase();
276
+
this.searchQuery = (e.target as HTMLInputElement).value;
172
-
private async handleSubmit(e: Event) {
280
+
private async handleSearch(e: Event) {
282
+
if (!this.searchQuery.trim()) return;
284
+
this.isSearching = true;
175
-
this.isSubmitting = true;
286
+
this.hasSearched = true;
289
+
const response = await fetch(
290
+
`/api/classes/search?q=${encodeURIComponent(this.searchQuery.trim())}`,
293
+
if (!response.ok) {
294
+
throw new Error("Search failed");
297
+
const data = await response.json();
298
+
this.results = data.classes || [];
300
+
this.error = "Failed to search classes. Please try again.";
302
+
this.isSearching = false;
306
+
private async handleJoin(classId: string) {
307
+
this.isJoining = true;
const response = await fetch("/api/classes/join", {
headers: { "Content-Type": "application/json" },
181
-
body: JSON.stringify({ class_code: this.classCode.trim() }),
314
+
body: JSON.stringify({ class_id: classId }),
···
this.error = "Failed to join class. Please try again.";
196
-
this.isSubmitting = false;
329
+
this.isJoining = false;
···
<div class="modal-overlay" @click=${this.handleClose}>
<div class="modal" @click=${(e: Event) => e.stopPropagation()}>
<div class="modal-header">
207
-
<h2 class="modal-title">Register for Class</h2>
340
+
<h2 class="modal-title">Find a Class</h2>
<button class="close-btn" @click=${this.handleClose} type="button">×</button>
211
-
<form @submit=${this.handleSubmit}>
212
-
<div class="form-group">
213
-
<label for="class-code">Class Code</label>
217
-
placeholder="ABC123"
218
-
.value=${this.classCode}
219
-
@input=${this.handleInput}
221
-
?disabled=${this.isSubmitting}
224
-
<div class="helper-text">
225
-
Enter the class code provided by your instructor
344
+
<div class="search-section">
345
+
<label for="search">Course Code</label>
346
+
<form class="search-form" @submit=${this.handleSearch}>
347
+
<div class="search-input-wrapper">
351
+
placeholder="CS 101, MATH 220, etc."
352
+
.value=${this.searchQuery}
353
+
@input=${this.handleInput}
354
+
?disabled=${this.isSearching}
227
-
${this.error ? html`<div class="error-message">${this.error}</div>` : ""}
230
-
<div class="modal-actions">
233
-
class="btn-primary"
234
-
?disabled=${this.isSubmitting || !this.classCode.trim()}
360
+
?disabled=${this.isSearching || !this.searchQuery.trim()}
236
-
${this.isSubmitting ? "Joining..." : "Join Class"}
362
+
${this.isSearching ? "Searching..." : "Search"}
240
-
class="btn-secondary"
241
-
@click=${this.handleClose}
242
-
?disabled=${this.isSubmitting}
365
+
<div class="helper-text">
366
+
Search by course code to find available classes
368
+
${this.error ? html`<div class="error-message">${this.error}</div>` : ""}
374
+
<div class="results-section">
377
+
? html`<div class="loading">Searching...</div>`
378
+
: this.results.length === 0
380
+
<div class="empty-state">
381
+
No classes found matching "${this.searchQuery}"
385
+
<div class="results-grid">
386
+
${this.results.map(
390
+
@click=${() => this.handleJoin(cls.id)}
391
+
?disabled=${this.isJoining}
393
+
<div class="class-header">
394
+
<div class="class-info">
395
+
<div class="course-code">${cls.course_code}</div>
396
+
<div class="class-name">${cls.name}</div>
397
+
<div class="class-meta">
398
+
<span>👤 ${cls.professor}</span>
399
+
${cls.section ? html`<span>📍 Section ${cls.section}</span>` : ""}
400
+
<span>📅 ${cls.semester} ${cls.year}</span>
405
+
?disabled=${this.isJoining}
406
+
@click=${(e: Event) => {
407
+
e.stopPropagation();
408
+
this.handleJoin(cls.id);
411
+
${this.isJoining ? "Joining..." : "Join"}