Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@
PyCon US 2026
</ion-list-header>

<ion-menu-toggle *ngIf="isDev" autoHide="false">
<ion-item routerLink="/app/tabs/dev-tools" routerLinkActive="active" routerDirection="root" detail="false" color="warning">
<ion-icon slot="start" name="construct-outline"></ion-icon>
<ion-label>Dev Tools</ion-label>
</ion-item>
</ion-menu-toggle>

<ion-item *ngIf="loggedIn">
<ion-label>Hello, {{nickname}}</ion-label>
</ion-item>
Expand Down Expand Up @@ -43,7 +50,7 @@
</ion-item>
</ion-menu-toggle>

<ion-menu-toggle *ngIf="hasLeadRetrieval" autoHide="false">
<ion-menu-toggle *ngIf="hasLeadRetrieval || hasDoorCheck" autoHide="false">
<ion-item routerLink="/app/tabs/lead-retrieval" routerLinkActive="active" routerDirection="root" detail="false">
<ion-icon slot="start" name="qr-code"></ion-icon>
<ion-label>
Expand Down
4 changes: 4 additions & 0 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Storage } from '@ionic/storage-angular';
import { UserData } from './providers/user-data';
import { ConferenceData } from './providers/conference-data';
import { LiveUpdateService } from './providers/live-update.service';
import { environment } from '../environments/environment';

@Component({
selector: 'app-root',
Expand Down Expand Up @@ -67,6 +68,8 @@ export class AppComponent implements OnInit {
hasLeadRetrieval = false;
hasDoorCheck= false;
hasMaskViolation = false;
isDev = !environment.production;
environmentUrl = environment.baseUrl;

constructor(
private menu: MenuController,
Expand Down Expand Up @@ -215,4 +218,5 @@ export class AppComponent implements OnInit {
openUrl(url: string) {
window.open(url, '_system', 'location=yes');
}

}
11 changes: 11 additions & 0 deletions src/app/pages/dev-tools/dev-tools-routing.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { DevToolsPage } from './dev-tools.page';

const routes: Routes = [{ path: '', component: DevToolsPage }];

@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class DevToolsPageRoutingModule {}
11 changes: 11 additions & 0 deletions src/app/pages/dev-tools/dev-tools.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IonicModule } from '@ionic/angular';
import { DevToolsPageRoutingModule } from './dev-tools-routing.module';
import { DevToolsPage } from './dev-tools.page';

@NgModule({
imports: [CommonModule, IonicModule, DevToolsPageRoutingModule],
declarations: [DevToolsPage]
})
export class DevToolsPageModule {}
100 changes: 100 additions & 0 deletions src/app/pages/dev-tools/dev-tools.page.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<ion-header class="ion-no-border">
<ion-toolbar color="warning">
<ion-buttons slot="start">
<ion-menu-button></ion-menu-button>
</ion-buttons>
<ion-title>Dev Tools</ion-title>
</ion-toolbar>
</ion-header>

<ion-content>
<ion-card>
<ion-card-header>
<ion-card-title>Environment</ion-card-title>
</ion-card-header>
<ion-card-content>
<ion-list lines="none">
<ion-item>
<ion-label>
<p>Name</p>
<h3>{{env.name}}</h3>
</ion-label>
</ion-item>
<ion-item>
<ion-label>
<p>API Base URL</p>
<h3 style="font-family: monospace; font-size: 0.85rem;">{{env.baseUrl}}</h3>
</ion-label>
</ion-item>
<ion-item>
<ion-label>
<p>Timezone</p>
<h3>{{env.timezone}}</h3>
</ion-label>
</ion-item>
<ion-item>
<ion-label>
<p>Production</p>
<h3>{{env.production}}</h3>
</ion-label>
</ion-item>
</ion-list>
</ion-card-content>
</ion-card>

<ion-card>
<ion-card-header>
<ion-card-title>Storage</ion-card-title>
<ion-card-subtitle>{{storageCount}} keys | {{scanCount}} scans</ion-card-subtitle>
</ion-card-header>
<ion-card-content>
<ion-button expand="block" fill="outline" color="medium" (click)="dumpStorage()">
<ion-icon slot="start" name="code-outline"></ion-icon>
Dump Storage to Console
</ion-button>
</ion-card-content>
</ion-card>

<ion-card>
<ion-card-header>
<ion-card-title>Actions</ion-card-title>
</ion-card-header>
<ion-card-content>
<ion-list lines="none">
<ion-item button="true" (click)="clearScanData()" detail="false">
<ion-icon slot="start" name="trash-outline" color="warning"></ion-icon>
<ion-label>
<h2>Clear Scan Data</h2>
<p>Remove all scans, notes, consent, sponsor selection</p>
</ion-label>
</ion-item>

<ion-item button="true" (click)="invalidateCache()" detail="false">
<ion-icon slot="start" name="refresh-outline" color="warning"></ion-icon>
<ion-label>
<h2>Invalidate Schedule Cache</h2>
<p>Force re-fetch of conference.json on next load</p>
</ion-label>
</ion-item>

<ion-item button="true" (click)="nukeStorage()" detail="false">
<ion-icon slot="start" name="nuclear-outline" color="danger"></ion-icon>
<ion-label>
<h2>Nuke All Storage</h2>
<p>Wipe everything and reload — you will be logged out</p>
</ion-label>
</ion-item>

<ion-item button="true" (click)="refreshStats()" detail="false">
<ion-icon slot="start" name="analytics-outline" color="primary"></ion-icon>
<ion-label>
<h2>Refresh Stats</h2>
<p>Update storage key counts</p>
</ion-label>
</ion-item>
</ion-list>
</ion-card-content>
</ion-card>

<div style="height: 80px"></div>
</ion-content>
1 change: 1 addition & 0 deletions src/app/pages/dev-tools/dev-tools.page.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/* Minimal — uses Ionic defaults */
82 changes: 82 additions & 0 deletions src/app/pages/dev-tools/dev-tools.page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { Component } from '@angular/core';
import { Storage } from '@ionic/storage-angular';
import { ToastController } from '@ionic/angular';
import { ConferenceData } from '../../providers/conference-data';
import { environment } from '../../../environments/environment';

@Component({
selector: 'app-dev-tools',
templateUrl: './dev-tools.page.html',
styleUrls: ['./dev-tools.page.scss'],
})
export class DevToolsPage {
env = environment;
storageKeys: string[] = [];
storageCount = 0;
scanCount = 0;

constructor(
private storage: Storage,
private toastCtrl: ToastController,
private confData: ConferenceData,
) {}

async ionViewWillEnter() {
await this.refreshStats();
}

async refreshStats() {
const keys = await this.storage.keys();
this.storageKeys = keys;
this.storageCount = keys.length;
this.scanCount = keys.filter(k =>
k.startsWith('pending-scan-') || k.startsWith('synced-scan-') || k.startsWith('failed-scan-')
).length;
}

async clearScanData() {
const keys = await this.storage.keys();
let cleared = 0;
for (const key of keys) {
if (key.startsWith('pending-scan-') || key.startsWith('synced-scan-') || key.startsWith('failed-scan-') ||
key.startsWith('pending-note-') || key.startsWith('note-') ||
key === 'staff-sponsor-id' || key === 'staff-sponsor-name' || key === 'hasScannerConsent') {
await this.storage.remove(key);
cleared++;
}
}
await this.showToast(`Cleared ${cleared} scan entries`);
await this.refreshStats();
}

async invalidateCache() {
this.confData.invalidateCache();
await this.showToast('Schedule cache invalidated — pull to refresh');
await this.refreshStats();
}

async nukeStorage() {
await this.storage.clear();
await this.showToast('All storage wiped — reloading...');
setTimeout(() => window.location.reload(), 500);
}

async dumpStorage() {
const dump: any = {};
await this.storage.forEach((value, key) => {
dump[key] = typeof value === 'object' ? JSON.stringify(value).substring(0, 100) : String(value);
});
console.table(dump);
await this.showToast('Storage dumped to console (open DevTools)');
}

private async showToast(message: string) {
const toast = await this.toastCtrl.create({
message,
duration: 2000,
position: 'bottom',
color: 'dark',
});
toast.present();
}
}
Loading
Loading