🪻 distributed transcription service thistle.dunkirk.sh
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: replace confirm dialogs with three-click delete confirmation

Replaced intrusive browser confirm dialogs with a progressive three-click delete system:
- First click: "Delete" → "Are you sure?"
- Second click: "Are you sure?" → "Final warning!"
- Third click: "Final warning!" → Actually deletes

Features:
- 1 second timeout between clicks before resetting
- State tracked per item (can't accidentally delete wrong item)
- Visual feedback through button text changes
- Works for both class deletion and waitlist deletion
- No blocking dialogs, cleaner UX

Implementation:
- State management with deleteState tracking id, type, clicks, and timeout
- Automatic cleanup with setTimeout
- Separate handlers for performing actual deletion
- Button text updates reactively based on state

💘 Generated with Crush

Co-Authored-By: Crush <crush@charm.land>

+73 -23
+73 -23
src/components/admin-classes.ts
··· 45 45 semester: "", 46 46 year: new Date().getFullYear(), 47 47 }; 48 + @state() deleteState: { 49 + id: string; 50 + type: "class" | "waitlist"; 51 + clicks: number; 52 + timeout: number | null; 53 + } | null = null; 48 54 49 55 static override styles = css` 50 56 :host { ··· 505 511 } 506 512 } 507 513 508 - private async handleDelete(classId: string, courseName: string) { 509 - if ( 510 - !confirm( 511 - `Are you sure you want to delete ${courseName}? This will remove all associated data and cannot be undone.`, 512 - ) 513 - ) { 514 + private handleDeleteClick(id: string, type: "class" | "waitlist") { 515 + // If this is a different item or timeout expired, reset 516 + if (!this.deleteState || this.deleteState.id !== id || this.deleteState.type !== type) { 517 + // Clear any existing timeout 518 + if (this.deleteState?.timeout) { 519 + clearTimeout(this.deleteState.timeout); 520 + } 521 + 522 + // Set first click 523 + const timeout = window.setTimeout(() => { 524 + this.deleteState = null; 525 + }, 1000); 526 + 527 + this.deleteState = { id, type, clicks: 1, timeout }; 528 + return; 529 + } 530 + 531 + // Increment clicks 532 + const newClicks = this.deleteState.clicks + 1; 533 + 534 + // Clear existing timeout 535 + if (this.deleteState.timeout) { 536 + clearTimeout(this.deleteState.timeout); 537 + } 538 + 539 + // Third click - actually delete 540 + if (newClicks === 3) { 541 + this.deleteState = null; 542 + if (type === "class") { 543 + this.performDeleteClass(id); 544 + } else { 545 + this.performDeleteWaitlist(id); 546 + } 514 547 return; 515 548 } 516 549 550 + // Second click - reset timeout 551 + const timeout = window.setTimeout(() => { 552 + this.deleteState = null; 553 + }, 1000); 554 + 555 + this.deleteState = { id, type, clicks: newClicks, timeout }; 556 + } 557 + 558 + private getDeleteButtonText(id: string, type: "class" | "waitlist"): string { 559 + if (!this.deleteState || this.deleteState.id !== id || this.deleteState.type !== type) { 560 + return "Delete"; 561 + } 562 + 563 + if (this.deleteState.clicks === 1) { 564 + return "Are you sure?"; 565 + } 566 + 567 + if (this.deleteState.clicks === 2) { 568 + return "Final warning!"; 569 + } 570 + 571 + return "Delete"; 572 + } 573 + 574 + private async performDeleteClass(classId: string) { 517 575 try { 518 576 const response = await fetch(`/api/classes/${classId}`, { 519 577 method: "DELETE", ··· 529 587 } 530 588 } 531 589 532 - private handleCreateClass() { 533 - this.showCreateModal = true; 534 - } 535 - 536 - private async handleDeleteWaitlist(id: string, courseCode: string) { 537 - if ( 538 - !confirm( 539 - `Are you sure you want to delete this waitlist request for ${courseCode}?`, 540 - ) 541 - ) { 542 - return; 543 - } 544 - 590 + private async performDeleteWaitlist(id: string) { 545 591 try { 546 592 const response = await fetch(`/api/admin/waitlist/${id}`, { 547 593 method: "DELETE", ··· 555 601 } catch { 556 602 this.error = "Failed to delete waitlist entry. Please try again."; 557 603 } 604 + } 605 + 606 + private handleCreateClass() { 607 + this.showCreateModal = true; 558 608 } 559 609 560 610 private getFilteredClasses() { ··· 652 702 </button> 653 703 <button 654 704 class="btn-delete" 655 - @click=${() => this.handleDelete(cls.id, cls.course_code)} 705 + @click=${() => this.handleDeleteClick(cls.id, "class")} 656 706 > 657 - Delete 707 + ${this.getDeleteButtonText(cls.id, "class")} 658 708 </button> 659 709 </div> 660 710 </div> ··· 706 756 </button> 707 757 <button 708 758 class="btn-delete" 709 - @click=${() => this.handleDeleteWaitlist(entry.id, entry.course_code)} 759 + @click=${() => this.handleDeleteClick(entry.id, "waitlist")} 710 760 > 711 - Delete 761 + ${this.getDeleteButtonText(entry.id, "waitlist")} 712 762 </button> 713 763 </div> 714 764 </div>