+28
-28
package.json
+28
-28
package.json
···
+19
-1
public/favicon/site.webmanifest
+19
-1
public/favicon/site.webmanifest
···-{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
+3
-1
scripts/clear-rate-limits.ts
+3
-1
scripts/clear-rate-limits.ts
···-console.log(`✅ Successfully cleared ${deletedCount} rate limit attempt${deletedCount === 1 ? '' : 's'}`);
+1
-1
scripts/send-test-emails.ts
+1
-1
scripts/send-test-emails.ts
+5
-5
src/components/admin-classes.ts
+5
-5
src/components/admin-classes.ts
············
+19
-10
src/components/admin-pending-recordings.ts
+19
-10
src/components/admin-pending-recordings.ts
······-this.error = err instanceof Error ? err.message : "Failed to load pending recordings. Please try again.";···-this.error = err instanceof Error ? err.message : "Failed to approve recording. Please try again.";···-this.error = err instanceof Error ? err.message : "Failed to delete recording. Please try again.";
+8
-2
src/components/admin-transcriptions.ts
+8
-2
src/components/admin-transcriptions.ts
···-this.error = err instanceof Error ? err.message : "Failed to load transcriptions. Please try again.";···-this.error = err instanceof Error ? err.message : "Failed to delete transcription. Please try again.";
+64
-30
src/components/admin-users.ts
+64
-30
src/components/admin-users.ts
·········-private handleRevokeClick(userId: number, email: string, subscriptionId: string, event: Event) {···-private async performRevokeSubscription(userId: number, _email: string, subscriptionId: string) {······// Don't open modal if clicking on delete button, revoke button, sync button, or role select······-<div class="user-card ${u.id === 0 ? 'system' : ''}" @click=${(e: Event) => this.handleCardClick(u.id, e)}>+<div class="user-card ${u.id === 0 ? "system" : ""}" @click=${(e: Event) => this.handleCardClick(u.id, e)}>······-? html`<span class="subscription-badge ${u.subscription_status.toLowerCase()}">${u.subscription_status}</span>`+? html`<span class="subscription-badge ${u.subscription_status.toLowerCase()}">${u.subscription_status}</span>`···-? html`<div style="color: var(--paynes-gray); font-size: 0.875rem; padding: 0.5rem;">System account cannot be modified</div>`+? html`<div style="color: var(--paynes-gray); font-size: 0.875rem; padding: 0.5rem;">System account cannot be modified</div>`···?disabled=${!u.subscription_status || !u.subscription_id || this.revokingSubscriptions.has(u.id)}···
+10
-10
src/components/auth.ts
+10
-10
src/components/auth.ts
···············
+11
-7
src/components/class-view.ts
+11
-7
src/components/class-view.ts
···<div style="background: color-mix(in srgb, var(--accent) 10%, transparent); border: 1px solid var(--accent); border-radius: 8px; padding: 1.5rem; margin: 2rem 0; text-align: center;"><p style="margin: 0 0 1rem 0; color: var(--text); opacity: 0.8;">You need an active subscription to upload and view transcriptions.</p><a href="/settings?tab=billing" style="display: inline-block; padding: 0.75rem 1.5rem; background: var(--accent); color: white; text-decoration: none; border-radius: 8px; font-weight: 600; transition: opacity 0.2s;">Subscribe Now</a>···<p>${this.searchQuery ? "Try a different search term" : "Upload a recording to get started!"}</p>···
+18
-7
src/components/reset-password-form.ts
+18
-7
src/components/reset-password-form.ts
············
+8
-3
src/components/transcription.ts
+8
-3
src/components/transcription.ts
···<div style="background: color-mix(in srgb, var(--accent) 10%, transparent); border: 1px solid var(--accent); border-radius: 8px; padding: 1.5rem; margin-bottom: 2rem; text-align: center;"><h3 style="margin: 0 0 0.5rem 0; color: var(--text);">Subscribe to Upload Transcriptions</h3><p style="margin: 0 0 1rem 0; color: var(--text); opacity: 0.8;">You need an active subscription to upload and transcribe audio files.</p><a href="/settings?tab=billing" style="display: inline-block; padding: 0.75rem 1.5rem; background: var(--accent); color: white; text-decoration: none; border-radius: 8px; font-weight: 600; transition: opacity 0.2s;">Subscribe Now</a><div class="upload-area ${this.dragOver ? "drag-over" : ""} ${!canUpload ? "disabled" : ""}"
+14
-6
src/components/user-modal.ts
+14
-6
src/components/user-modal.ts
·········"Password reset email sent successfully. The user will receive a link to set a new password.",
+106
-44
src/components/user-settings.ts
+106
-44
src/components/user-settings.ts
···-type SettingsPage = "account" | "sessions" | "passkeys" | "billing" | "notifications" | "danger";······-return ["account", "sessions", "passkeys", "billing", "notifications", "danger"].includes(tab);···············-${this.pendingEmailChange ? html`<br><strong>New email:</strong> ${this.pendingEmailChange}` : ''}+${this.pendingEmailChange ? html`<br><strong>New email:</strong> ${this.pendingEmailChange}` : ""}····································
+153
-80
src/index.ts
+153
-80
src/index.ts
··········································"Set-Cookie": `session=${sessionId}; HttpOnly; Secure; Path=/; Max-Age=${7 * 24 * 60 * 60}; SameSite=Lax`,············-return Response.json({ message: "If an account exists with that email, a verification code has been sent" });·····················-return Response.json({ error: "email_notifications_enabled must be a boolean" }, { status: 400 });-db.run("UPDATE users SET email_notifications_enabled = ? WHERE id = ?", [email_notifications_enabled ? 1 : 0, user.id]);·································
+26
-24
src/lib/auth.ts
+26
-24
src/lib/auth.ts
···"SELECT id, status, cancel_at_period_end FROM subscriptions WHERE user_id = ? ORDER BY created_at DESC LIMIT 1",······-export function createEmailVerificationToken(userId: number): { code: string; token: string; sentAt: number } {···"INSERT INTO email_verification_tokens (id, user_id, token, expires_at) VALUES (?, ?, ?, ?)","INSERT INTO email_verification_tokens (id, user_id, token, expires_at) VALUES (?, ?, ?, ?)",············-export function verifyEmailChangeToken(token: string): { userId: number; newEmail: string } | null {
-1
src/lib/client-auth.ts
-1
src/lib/client-auth.ts
+1
-2
src/lib/crypto-fallback.ts
+1
-2
src/lib/crypto-fallback.ts
···
+31
-11
src/lib/email-change.test.ts
+31
-11
src/lib/email-change.test.ts
···-const user = await createUser(`test-email-change-${timestamp}@example.com`, "password123", "Test User");······-const user = await createUser(`test-expire-${timestamp}@example.com`, "password123", "Test User");···-const user = await createUser(`test-single-token-${timestamp}@example.com`, "password123", "Test User");
+6
-3
src/lib/email-templates.ts
+6
-3
src/lib/email-templates.ts
······
+10
-12
src/lib/email-verification.test.ts
+10
-12
src/lib/email-verification.test.ts
·········
+12
-10
src/lib/rate-limit.ts
+12
-10
src/lib/rate-limit.ts
···
+3
-6
src/lib/transcription.ts
+3
-6
src/lib/transcription.ts
···