diff --git a/src/app/pages/door-check/door-check.page.html b/src/app/pages/door-check/door-check.page.html index 1236ec64..9a451a58 100644 --- a/src/app/pages/door-check/door-check.page.html +++ b/src/app/pages/door-check/door-check.page.html @@ -7,99 +7,98 @@ - - - - - - Uh oh.. - Camera Permissions Not Granted - - - Looks like you did not grant this application permission to access your camera. - - You can resolve this by opening

- Settings > Privacy & Security > Camera > PyCon US 2026

- Settings > Apps > PyCon US 2026 > Permissions > Camera

- And enabling camera permssions for this app. -
-
-
-
+ - - - {{category.name}} - - + + + Camera permission required + + +

Looks like you didn't grant the app permission to access your camera.

+

Re-enable it under:

+

Settings › Privacy & Security › Camera › PyCon US 2026

+

Settings › Apps › PyCon US 2026 › Permissions › Camera

+
+
- - - +
+

Category

+

+ Tap a category to choose a session. +

+
+ + + {{c.name}} + +
+
- - - - ALL - - - - {{p.name}} - - - - +
+ + +
- - - - Scanning for... - - -

{{ getCategoryName(category) }}

-

{{ getProductName(product) }}

-
-
+ + + All sessions in this category + - - - Select a session to start scanning! + + {{p.name}} + - + + + + Scanning for + {{ getProductName(product) || 'All sessions' }} + {{ getCategoryName(category) }} + + + +

+ Select a session to start scanning. +

+
- - - - - - -

- - - All good attendee has access! - Entry not permitted.
- ({{ last_scan.status }})
- {{ last_scan.code }} -

-
-
-
-
-
+ + +
+ +
+ Attendee has access + Entry not permitted + {{ last_scan.code }} +
+
+
+ - - - Start Scanner! - - - Stop Scanner - - + + + {{ scanning ? 'Stop Scanner' : 'Start Scanner' }} +
diff --git a/src/app/pages/door-check/door-check.page.scss b/src/app/pages/door-check/door-check.page.scss index 3de4aff4..cc80b38b 100644 --- a/src/app/pages/door-check/door-check.page.scss +++ b/src/app/pages/door-check/door-check.page.scss @@ -1,32 +1,122 @@ -.footer-buttons { +.permission-card { + margin: 16px; +} + +.category-section { + padding: 16px 16px 0; +} + +.section-title { + font-size: 0.75rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--ion-color-medium, #666); + margin: 0 0 8px; +} + +.section-hint { + color: var(--ion-color-medium, #888); + font-size: 0.9rem; + margin: 0 0 8px; +} + +.category-chips { + display: flex; + flex-wrap: wrap; + gap: 8px; + + ion-chip { + margin: 0; + height: 40px; + font-size: 0.95rem; + } +} + +.search-row { + padding: 4px 8px 0; +} + +.scanning-for-card { + margin: 16px; +} + +.kicker-stack { display: flex; - align-items: center; - justify-content: center; flex-direction: column; - height: 100%; + gap: 4px; + + .kicker { + font-size: 0.7rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--ion-color-medium, #666); + } + + .primary { + font-size: 1.2rem; + font-weight: 700; + color: var(--ion-text-color, #111); + line-height: 1.25; + } + + .secondary { + font-size: 0.85rem; + color: var(--ion-color-medium, #888); + } } -.footer-button { - position: absolute; +.empty-hint { + color: var(--ion-color-medium, #888); + margin: 12px 16px; } + +.product-list { + // Flat list scrolls with the page — no nested overflow. + background: transparent; +} + .footer-toolbar { - padding-top: 1em; - padding-bottom: 1em; + --padding-top: 8px; + --padding-bottom: 8px; + --padding-start: 16px; + --padding-end: 16px; } -.header-info { - font-size: .6em; - padding-bottom: .5em; +.scan-toggle-button { + margin: 0; + height: 56px; + font-weight: 700; + letter-spacing: 0.02em; } -::ng-deep .mycomponent-wider-popover -{ - --width: 95%; - --max-width: 400px; +.last-scan-card { + margin: 8px 16px 0; } -.product-list { - width: 100%; - max-height: 300px; - overflow-y: auto; +.last-scan-row { + display: flex; + align-items: center; + gap: 12px; + + ion-icon { + font-size: 28px; + flex-shrink: 0; + } + + .last-scan-text { + display: flex; + flex-direction: column; + gap: 2px; + + strong { + font-size: 1rem; + } + + code { + font-size: 0.75rem; + opacity: 0.85; + } + } } diff --git a/src/app/pages/door-check/door-check.page.ts b/src/app/pages/door-check/door-check.page.ts index de476224..d894d63f 100644 --- a/src/app/pages/door-check/door-check.page.ts +++ b/src/app/pages/door-check/door-check.page.ts @@ -12,9 +12,7 @@ import { LiveUpdateService } from '../../providers/live-update.service'; styleUrls: ['./door-check.page.scss'], }) export class DoorCheckPage implements OnInit, OnDestroy { - content_visibility = 'show'; - scan_start_button_visibility = 'show'; - scan_stop_button_visibility = 'hidden'; + scanning: boolean = false; scan_presentation = []; dirty: boolean = false; @@ -71,6 +69,22 @@ export class DoorCheckPage implements OnInit, OnDestroy { this.detectorRef.detectChanges(); } + selectCategory(categoryId: number) { + // Toggle off if the same chip is tapped again so users can clear + // their selection without leaving the page. + if (this.category === categoryId) { + this.category = null; + this.display_products = null; + this.filtered_products = null; + this.product = null; + this.productSearch = ''; + this.detectorRef.detectChanges(); + return; + } + this.category = categoryId; + this.refreshProducts(); + } + filterProductList() { if (!this.productSearch || !this.productSearch.trim()) { this.filtered_products = this.display_products; @@ -290,9 +304,7 @@ export class DoorCheckPage implements OnInit, OnDestroy { return; } this.show_permissions_error = false; - this.content_visibility = 'hidden'; - this.scan_start_button_visibility = 'hidden'; - this.scan_stop_button_visibility = ''; + this.scanning = true; await this.addListeners(); BarcodeScanner.startScan({ formats: [BarcodeFormat.QrCode], @@ -307,9 +319,7 @@ export class DoorCheckPage implements OnInit, OnDestroy { clearTimeout(this.scan_timeout); await BarcodeScanner.removeAllListeners(); await BarcodeScanner.stopScan() - this.scan_stop_button_visibility = 'hidden'; - this.scan_start_button_visibility = ''; - this.content_visibility = ''; + this.scanning = false; } ionViewWillLeave() { diff --git a/src/app/pages/t-shirt-redemption/t-shirt-redemption.page.html b/src/app/pages/t-shirt-redemption/t-shirt-redemption.page.html index d6416d2e..af697b0c 100644 --- a/src/app/pages/t-shirt-redemption/t-shirt-redemption.page.html +++ b/src/app/pages/t-shirt-redemption/t-shirt-redemption.page.html @@ -3,81 +3,72 @@ - T-Shirt Redemption + Swag Pickup - - - - - - Uh oh.. - Camera Permissions Not Granted - - - Looks like you did not grant this application permission to access your camera. - - You can resolve this by opening

- Settings > Privacy & Security > Camera > PyCon US 2026

- Settings > Apps > PyCon US 2026 > Permissions > Camera

- And enabling camera permssions for this app. -
-
-
-
+ - - - {{category.name}} - - + + + Camera permission required + + +

Looks like you didn't grant the app permission to access your camera.

+

Re-enable it under:

+

Settings › Privacy & Security › Camera › PyCon US 2026

+

Settings › Apps › PyCon US 2026 › Permissions › Camera

+
+
- - - - Redeeming for... - - -

{{ getCategoryName(category_id) }}

-
-
-
+
+

Category

+

+ Tap one or more categories below. +

+
+ + + {{c.name}} + +
+
- - Select a category to start scanning! - + + + Redeeming for + {{ selectedCategoryNames() }} + + -
- - - - - - -

- -  Invalid QR Code! -

-
-
-
-
-
+ + +
+ +
+ Invalid QR code +
+
+
+ - - - Start Scanner! - - - Stop Scanner - - + + + {{ scanning ? 'Stop Scanner' : 'Start Scanner' }} +
diff --git a/src/app/pages/t-shirt-redemption/t-shirt-redemption.page.scss b/src/app/pages/t-shirt-redemption/t-shirt-redemption.page.scss index 6748f9da..75e92404 100644 --- a/src/app/pages/t-shirt-redemption/t-shirt-redemption.page.scss +++ b/src/app/pages/t-shirt-redemption/t-shirt-redemption.page.scss @@ -1,26 +1,98 @@ -.footer-buttons { +.permission-card { + margin: 16px; +} + +.category-section { + padding: 16px 16px 0; +} + +.section-title { + font-size: 0.75rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--ion-color-medium, #666); + margin: 0 0 8px; +} + +.section-hint { + color: var(--ion-color-medium, #888); + font-size: 0.9rem; + margin: 0 0 8px; +} + +.category-chips { display: flex; - align-items: center; - justify-content: center; - flex-direction: column; - height: 100%; + flex-wrap: wrap; + gap: 8px; + + ion-chip { + margin: 0; + height: 40px; + font-size: 0.95rem; + } } -.footer-button { - position: absolute; +.redeeming-card { + margin: 16px; } + +.kicker-stack { + display: flex; + flex-direction: column; + gap: 4px; + + .kicker { + font-size: 0.7rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--ion-color-medium, #666); + } + + .primary { + font-size: 1.2rem; + font-weight: 700; + color: var(--ion-text-color, #111); + line-height: 1.25; + } +} + .footer-toolbar { - padding-top: 1em; - padding-bottom: 1em; + --padding-top: 8px; + --padding-bottom: 8px; + --padding-start: 16px; + --padding-end: 16px; +} + +.scan-toggle-button { + margin: 0; + height: 56px; + font-weight: 700; + letter-spacing: 0.02em; } -.header-info { - font-size: .6em; - padding-bottom: .5em; +.last-scan-card { + margin: 8px 16px 0; } -::ng-deep .mycomponent-wider-popover -{ - --width: 95%; - --max-width: 400px; +.last-scan-row { + display: flex; + align-items: center; + gap: 12px; + + ion-icon { + font-size: 28px; + flex-shrink: 0; + } + + .last-scan-text { + display: flex; + flex-direction: column; + gap: 2px; + + strong { + font-size: 1rem; + } + } } diff --git a/src/app/pages/t-shirt-redemption/t-shirt-redemption.page.ts b/src/app/pages/t-shirt-redemption/t-shirt-redemption.page.ts index 23bf0ef2..eb57180f 100644 --- a/src/app/pages/t-shirt-redemption/t-shirt-redemption.page.ts +++ b/src/app/pages/t-shirt-redemption/t-shirt-redemption.page.ts @@ -15,9 +15,7 @@ import { RedemptionModalComponent } from '../../redemption-modal/redemption-moda styleUrls: ['./t-shirt-redemption.page.scss'], }) export class TShirtRedemptionPage implements OnInit, OnDestroy { - content_visibility = 'show'; - scan_start_button_visibility = 'show'; - scan_stop_button_visibility = 'hidden'; + scanning: boolean = false; scan_presentation = []; dirty: boolean = false; @@ -52,6 +50,35 @@ export class TShirtRedemptionPage implements OnInit, OnDestroy { return this.redeemable_categories.find(x => x.id === categoryId)?.name } + hasSelectedCategories(): boolean { + return Array.isArray(this.category) && this.category.length > 0; + } + + isCategorySelected(id: number): boolean { + return Array.isArray(this.category) && this.category.includes(id); + } + + toggleCategory(id: number) { + if (!Array.isArray(this.category)) { + this.category = []; + } + const idx = this.category.indexOf(id); + if (idx > -1) { + this.category.splice(idx, 1); + } else { + this.category.push(id); + } + if (this.category.length === 0) { + this.category = null; + } + this.detectorRef.detectChanges(); + } + + selectedCategoryNames(): string { + if (!this.hasSelectedCategories()) return ''; + return this.category!.map(id => this.getCategoryName(id)).filter(Boolean).join(', '); + } + updateLastScan = async (accessCode: string) => { this.last_scan = { "status": this.product_attendees.includes(accessCode), @@ -163,9 +190,7 @@ export class TShirtRedemptionPage implements OnInit, OnDestroy { return; } this.show_permissions_error = false; - this.content_visibility = 'hidden'; - this.scan_start_button_visibility = 'hidden'; - this.scan_stop_button_visibility = ''; + this.scanning = true; await this.addListeners(); BarcodeScanner.startScan({ formats: [BarcodeFormat.QrCode], @@ -178,9 +203,7 @@ export class TShirtRedemptionPage implements OnInit, OnDestroy { clearTimeout(this.scan_timeout); await this.removeListeners(); await BarcodeScanner.stopScan() - this.scan_stop_button_visibility = 'hidden'; - this.scan_start_button_visibility = ''; - this.content_visibility = ''; + this.scanning = false; } ionViewWillLeave() {