Sign in through the admin app instead of this one #20

Merged
Paddy merged 3 commits from feature/admin-auth-cutover into master 2026-09-06 21:13:12 +00:00
2 changed files with 84 additions and 33 deletions
Showing only changes of commit e8d2dc2901 - Show all commits
+59 -21
View File
@@ -3,7 +3,9 @@ import {Subject} from 'rxjs';
import {takeUntil} from 'rxjs/operators'; import {takeUntil} from 'rxjs/operators';
import {MatDialog} from '@angular/material/dialog'; import {MatDialog} from '@angular/material/dialog';
import {Event} from '../../models/event'; import {Event} from '../../models/event';
import {HttpErrorResponse} from '@angular/common/http';
import {ApiService} from '../../services/api.service'; import {ApiService} from '../../services/api.service';
import {AdminAuthService} from '../../services/admin-auth.service';
import {EventMovePopupComponent} from "../event-move-popup/event-move-popup.component"; import {EventMovePopupComponent} from "../event-move-popup/event-move-popup.component";
@Component({ @Component({
@@ -71,20 +73,21 @@ export class EventComponent implements OnInit, OnDestroy {
} }
if(this.event.eventId === undefined) { if(this.event.eventId === undefined) {
this.api.createEvent(this.event).pipe(takeUntil(this.destroy$)).subscribe((res: any) => { this.api.createEvent(this.event).pipe(takeUntil(this.destroy$)).subscribe({
console.log(res); next: (res: any) => {
if(res.eventId) {
if(res.eventId) { this.event!.eventId = res.eventId;
this.event!.eventId = res.eventId; } else {
} else { this.showCreateError = true;
this.showCreateError = true; }
return; },
} error: this.handleWriteError('The new event')
}); });
} else { } else {
// Update existing event // Update existing event
this.api.updateEvent(this.event).pipe(takeUntil(this.destroy$)).subscribe((res: any) => { this.api.updateEvent(this.event).pipe(takeUntil(this.destroy$)).subscribe({
console.log(res); next: () => {},
error: this.handleWriteError('Your change')
}); });
} }
} }
@@ -180,15 +183,48 @@ export class EventComponent implements OnInit, OnDestroy {
let deleteConfirmed = window.confirm(`Are you sure you want to delete "${this.event!.name}"? This action cannot be undone.`); let deleteConfirmed = window.confirm(`Are you sure you want to delete "${this.event!.name}"? This action cannot be undone.`);
if(deleteConfirmed && this.event) { if(deleteConfirmed && this.event) {
this.api.deleteEvent(this.event).pipe(takeUntil(this.destroy$)).subscribe((res: any) => { this.api.deleteEvent(this.event).pipe(takeUntil(this.destroy$)).subscribe({
console.log(res); next: (res: any) => {
if(res.message) { if(res.message) {
this.deleteEvent.next(this.event!.eventId); this.deleteEvent.next(this.event!.eventId);
} }
},
error: this.handleWriteError('The deletion')
}); });
} }
} }
/**
* What to do when a write fails.
*
* Before the auth cutover none of the writes here had an error callback, so
* a failure was invisible: the row closed, nothing was saved, and the user
* had every reason to think it had been. A 401 is now a routine event - the
* session expires, or is ended from another app or another tab - so silence
* is no longer survivable.
*
* A 401 means the session is gone, and nothing on this page can be saved
* until it comes back, so it goes straight to the login carrying this page
* as the return target. Everything else says what happened and leaves the
* user where they are, with their edits still on screen.
*/
private handleWriteError(action: string): (error: HttpErrorResponse) => void {
return (error: HttpErrorResponse) => {
if (error.status === 401) {
window.alert(`Your session has expired, so ${action} was not saved. Signing you in again.`);
AdminAuthService.goToLogin();
return;
}
if (error.status === 403) {
window.alert(`${action} failed: this account no longer has access to the calendar.`);
return;
}
window.alert(`${action} failed. Please try again. (${error.status || 'no response from the server'})`);
};
}
triggerMove() { triggerMove() {
if(this.editActive) { if(this.editActive) {
window.alert('Please save your changes before moving the event to a different calendar.'); window.alert('Please save your changes before moving the event to a different calendar.');
@@ -204,11 +240,13 @@ export class EventComponent implements OnInit, OnDestroy {
movePopup.afterClosed().pipe(takeUntil(this.destroy$)).subscribe(result => { movePopup.afterClosed().pipe(takeUntil(this.destroy$)).subscribe(result => {
// If popup is dismissed, undefined will be returned // If popup is dismissed, undefined will be returned
if(result) { if(result) {
this.api.moveEvent(result).pipe(takeUntil(this.destroy$)).subscribe((res: any) => { this.api.moveEvent(result).pipe(takeUntil(this.destroy$)).subscribe({
console.log(res); next: () => {
// Uses the same interface as delete as from the calendar table perspective it is the same action // Uses the same interface as delete as from the calendar table perspective it is the same action
// as a delete // as a delete
this.deleteEvent.next(result.eventId); this.deleteEvent.next(result.eventId);
},
error: this.handleWriteError('The move')
}); });
} }
}); });
+25 -12
View File
@@ -78,20 +78,33 @@ export class AdminComponent implements OnInit, OnDestroy {
return; return;
} }
this.api.getEvents(this.selectedCalendar).pipe(takeUntil(this.destroy$)).subscribe((events: Event[]): void => { this.api.getEvents(this.selectedCalendar).pipe(takeUntil(this.destroy$)).subscribe({
for (let event of events) { next: (events: Event[]): void => {
if(event.status !== 'DELETED') { for (let event of events) {
this.events.push({ if (event.status !== 'DELETED') {
...event, this.events.push({
startDateTime: new Date(event.startDateTime), ...event,
endDateTime: new Date(event.endDateTime), startDateTime: new Date(event.startDateTime),
createdDate: new Date(event.createdDate), endDateTime: new Date(event.endDateTime),
lastModifiedDate: new Date(event.lastModifiedDate) createdDate: new Date(event.createdDate),
}); lastModifiedDate: new Date(event.lastModifiedDate)
});
}
} }
this.filterEvents();
this.sortEvents();
},
// Without this a failed load is indistinguishable from an empty
// calendar - which is exactly what the old bundle looks like against
// the post-cutover API, and how a deploy in progress gets mistaken for
// lost data.
error: (error: HttpErrorResponse): void => {
if (error.status === 401) {
AdminAuthService.goToLogin();
return;
}
this.failure = error.status === 403 ? 'denied' : 'unavailable';
} }
this.filterEvents();
this.sortEvents();
}); });
} }