From 3ae0bdf2a23bdb1d0aa34ccf681978c195434dff Mon Sep 17 00:00:00 2001 From: juancarmore Date: Tue, 2 Sep 2025 11:47:28 +0200 Subject: [PATCH] frontend: implement delete room dialog with meeting and recordings policy options --- .../delete-room-dialog.component.html | 51 +++ .../delete-room-dialog.component.scss | 321 ++++++++++++++++++ .../delete-room-dialog.component.ts | 73 ++++ .../src/lib/models/notification.model.ts | 14 + 4 files changed, 459 insertions(+) create mode 100644 frontend/projects/shared-meet-components/src/lib/components/dialogs/delete-room-dialog/delete-room-dialog.component.html create mode 100644 frontend/projects/shared-meet-components/src/lib/components/dialogs/delete-room-dialog/delete-room-dialog.component.scss create mode 100644 frontend/projects/shared-meet-components/src/lib/components/dialogs/delete-room-dialog/delete-room-dialog.component.ts diff --git a/frontend/projects/shared-meet-components/src/lib/components/dialogs/delete-room-dialog/delete-room-dialog.component.html b/frontend/projects/shared-meet-components/src/lib/components/dialogs/delete-room-dialog/delete-room-dialog.component.html new file mode 100644 index 0000000..03f6873 --- /dev/null +++ b/frontend/projects/shared-meet-components/src/lib/components/dialogs/delete-room-dialog/delete-room-dialog.component.html @@ -0,0 +1,51 @@ +
+

+ delete_outlined + {{ data.title }} +

+ + +
+

Please choose how to handle this situation:

+ +
+ @if (data.showWithMeetingPolicy) { +
+

With Active Meeting Policy:

+ + @for (option of meetingPolicyOptions; track option.value) { + +
+ {{ option.label }} + {{ option.description }} +
+
+ } +
+
+ } + + @if (data.showWithRecordingsPolicy) { +
+

With Recordings Policy:

+ + @for (option of recordingPolicyOptions; track option.value) { + +
+ {{ option.label }} + {{ option.description }} +
+
+ } +
+
+ } +
+
+ + + + +
diff --git a/frontend/projects/shared-meet-components/src/lib/components/dialogs/delete-room-dialog/delete-room-dialog.component.scss b/frontend/projects/shared-meet-components/src/lib/components/dialogs/delete-room-dialog/delete-room-dialog.component.scss new file mode 100644 index 0000000..477d7d8 --- /dev/null +++ b/frontend/projects/shared-meet-components/src/lib/components/dialogs/delete-room-dialog/delete-room-dialog.component.scss @@ -0,0 +1,321 @@ +// Import design system utilities +@import '../../../../../../../src/assets/styles/design-tokens'; + +// === DIALOG CONTAINER === +::ng-deep { + .mat-mdc-dialog-container { + border-radius: var(--ov-meet-card-border-radius) !important; + overflow: hidden; + } + + .mat-mdc-dialog-surface { + border-radius: var(--ov-meet-card-border-radius) !important; + } +} + +:host { + .dialog-container { + @include ov-theme-transition; + display: block; + animation: dialogFadeIn 0.2s ease-out; + } +} + +// === DIALOG ANIMATION === +@keyframes dialogFadeIn { + from { + opacity: 0; + transform: scale(0.95) translateY(-10px); + } + to { + opacity: 1; + transform: scale(1) translateY(0); + } +} + +// === DIALOG TITLE === +.dialog-title { + @extend .ov-text-center; + @include ov-theme-transition; + @include ov-flex-center; + gap: var(--ov-meet-spacing-sm); + color: var(--ov-meet-text-primary); + font-size: var(--ov-meet-font-size-xl) !important; + font-weight: var(--ov-meet-font-weight-medium); + margin-bottom: var(--ov-meet-spacing-md); + padding: var(--ov-meet-spacing-lg) var(--ov-meet-spacing-lg) 0; + line-height: var(--ov-meet-line-height-tight); + + .dialog-icon { + @include ov-icon(md); + color: var(--ov-meet-color-error); + } +} + +// === DIALOG CONTENT === +::ng-deep mat-dialog-content { + @include ov-theme-transition; + color: var(--ov-meet-text-secondary); + font-size: var(--ov-meet-font-size-md) !important; + line-height: var(--ov-meet-line-height-relaxed); + padding: var(--ov-meet-spacing-md) var(--ov-meet-spacing-lg); + text-align: center; + min-height: auto; + + .dialog-message { + margin-bottom: var(--ov-meet-spacing-md); + text-align: left; + + strong { + color: var(--ov-meet-color-error); + font-weight: var(--ov-meet-font-weight-medium); + } + } + + p { + margin: var(--ov-meet-spacing-sm) 0; + text-align: left; + } +} + +// === POLICY OPTIONS CONTAINER === +.policy-options-container { + @include ov-theme-transition; + margin-top: var(--ov-meet-spacing-md); + padding: var(--ov-meet-spacing-md); + background: var(--ov-meet-surface-variant); + border: 1px solid var(--ov-meet-border-color); + border-radius: var(--ov-meet-radius-md); + text-align: left; +} + +.policy-section { + margin-bottom: var(--ov-meet-spacing-lg); + + &:last-child { + margin-bottom: 0; + } + + h4 { + margin: 0 0 var(--ov-meet-spacing-sm) 0; + font-size: var(--ov-meet-font-size-md); + font-weight: var(--ov-meet-font-weight-medium); + color: var(--ov-meet-text-primary); + } +} + +.policy-group { + display: flex; + flex-direction: column; + gap: var(--ov-meet-spacing-sm); +} + +.policy-option { + @include ov-theme-transition; + display: flex !important; + align-items: flex-start; + width: 100%; + margin: 0; + padding: var(--ov-meet-spacing-sm); + border-radius: var(--ov-meet-radius-sm); + + &:hover { + background: var(--ov-meet-surface-hover); + } + + // Override Material radio button styles + ::ng-deep { + .mdc-radio { + margin-top: 2px; // Align with text baseline + } + + .mdc-form-field > label { + width: 100%; + } + } + + .policy-option-content { + display: flex; + flex-direction: column; + margin-left: var(--ov-meet-spacing-sm); + flex: 1; + + .policy-label { + font-weight: var(--ov-meet-font-weight-medium); + font-size: var(--ov-meet-font-size-md); + color: var(--ov-meet-text-primary); + line-height: var(--ov-meet-line-height-tight); + } + + .policy-description { + font-size: var(--ov-meet-font-size-sm); + color: var(--ov-meet-text-secondary); + margin-top: var(--ov-meet-spacing-xs); + line-height: var(--ov-meet-line-height-relaxed); + } + } + + // Selected state styling + &.mat-mdc-radio-checked { + background: var(--ov-meet-primary-container); + + .policy-label { + color: var(--ov-meet-primary); + } + } +} + +// === DIALOG ACTIONS === +.dialog-action { + @include ov-flex-center; + gap: var(--ov-meet-spacing-sm); + padding: var(--ov-meet-spacing-lg); + margin: 0; + justify-content: center; + + // Button styling + button { + @include ov-button-base; + @include ov-theme-transition; + @include ov-flex-center; + gap: var(--ov-meet-spacing-xs); + min-width: 100px; + margin: 0; + + mat-icon { + @include ov-icon(sm); + } + + // Cancel/Secondary button + &.cancel-button { + color: var(--ov-meet-text-secondary); + border: 1px solid var(--ov-meet-border-color); + background: transparent; + + &:hover { + @include ov-hover-lift(-1px); + background: var(--ov-meet-surface-hover); + color: var(--ov-meet-text-primary); + } + } + + // Confirm/Primary button (Delete action) + &.confirm-button { + background: var(--ov-meet-color-error); + border-color: var(--ov-meet-color-error); + color: var(--ov-meet-text-on-error); + + &:hover { + @include ov-hover-lift(-1px); + background: var(--ov-meet-color-error); + filter: brightness(0.9); + } + + &:focus { + outline: 2px solid var(--ov-meet-color-error); + outline-offset: 2px; + } + + &:active { + transform: translateY(0); + } + } + } +} + +// === RESPONSIVE DESIGN === +@include ov-mobile-down { + .dialog-title { + font-size: var(--ov-meet-font-size-lg); + padding: var(--ov-meet-spacing-md) var(--ov-meet-spacing-md) 0; + flex-direction: column; + gap: var(--ov-meet-spacing-xs); + + .dialog-icon { + @include ov-icon(lg); + } + } + + ::ng-deep mat-dialog-content { + padding: var(--ov-meet-spacing-sm) var(--ov-meet-spacing-md); + font-size: var(--ov-meet-font-size-sm); + } + + .policy-options-container { + margin-top: var(--ov-meet-spacing-sm); + padding: var(--ov-meet-spacing-sm); + } + + .policy-section { + margin-bottom: var(--ov-meet-spacing-md); + + h4 { + font-size: var(--ov-meet-font-size-sm); + } + } + + .policy-option { + padding: var(--ov-meet-spacing-xs); + + .policy-option-content { + .policy-label { + font-size: var(--ov-meet-font-size-sm); + } + + .policy-description { + font-size: var(--ov-meet-font-size-xs); + } + } + } + + .dialog-action { + padding: var(--ov-meet-spacing-md); + flex-direction: column; + gap: var(--ov-meet-spacing-xs); + + button { + width: 100%; + min-width: auto; + justify-content: center; + } + } +} + +// === ACCESSIBILITY ENHANCEMENTS === +@media (prefers-reduced-motion: reduce) { + :host { + animation: none; + } + + @keyframes dialogFadeIn { + from, + to { + opacity: 1; + transform: none; + } + } +} + +// === HIGH CONTRAST MODE === +@media (prefers-contrast: high) { + .dialog-title { + font-weight: var(--ov-meet-font-weight-bold); + } + + .policy-option { + border: 1px solid var(--ov-meet-border-color); + + &.mat-mdc-radio-checked { + border-color: var(--ov-meet-primary); + border-width: 2px; + } + } + + .dialog-action button { + border-width: 2px; + + &[mat-button] { + border-color: currentColor; + } + } +} diff --git a/frontend/projects/shared-meet-components/src/lib/components/dialogs/delete-room-dialog/delete-room-dialog.component.ts b/frontend/projects/shared-meet-components/src/lib/components/dialogs/delete-room-dialog/delete-room-dialog.component.ts new file mode 100644 index 0000000..d90206b --- /dev/null +++ b/frontend/projects/shared-meet-components/src/lib/components/dialogs/delete-room-dialog/delete-room-dialog.component.ts @@ -0,0 +1,73 @@ +import { ChangeDetectionStrategy, Component, Inject, inject } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { + MAT_DIALOG_DATA, + MatDialogActions, + MatDialogContent, + MatDialogRef, + MatDialogTitle +} from '@angular/material/dialog'; +import { MatIconModule } from '@angular/material/icon'; +import { MatRadioModule } from '@angular/material/radio'; +import type { DeleteRoomDialogOptions } from '@lib/models'; +import { MeetRoomDeletionPolicyWithMeeting, MeetRoomDeletionPolicyWithRecordings } from '@lib/typings/ce'; + +@Component({ + selector: 'ov-dialog', + standalone: true, + imports: [ + FormsModule, + MatButtonModule, + MatIconModule, + MatRadioModule, + MatDialogActions, + MatDialogContent, + MatDialogTitle + ], + templateUrl: './delete-room-dialog.component.html', + styleUrl: './delete-room-dialog.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class DeleteRoomDialogComponent { + readonly dialogRef = inject(MatDialogRef); + + meetingPolicyOptions = [ + { + value: MeetRoomDeletionPolicyWithMeeting.WHEN_MEETING_ENDS, + label: 'When meeting ends', + description: 'The room will be deleted when the meeting ends.' + }, + { + value: MeetRoomDeletionPolicyWithMeeting.FORCE, + label: 'Force', + description: + 'The meeting will be ended immediately, and the room will be deleted without waiting for participants to leave.' + } + ]; + recordingPolicyOptions = [ + { + value: MeetRoomDeletionPolicyWithRecordings.CLOSE, + label: 'Close', + description: 'The room will be closed instead of deleted, maintaining its recordings.' + }, + { + value: MeetRoomDeletionPolicyWithRecordings.FORCE, + label: 'Force', + description: 'The room and its recordings will be deleted immediately.' + } + ]; + + selectedMeetingPolicy = MeetRoomDeletionPolicyWithMeeting.WHEN_MEETING_ENDS; + selectedRecordingPolicy = MeetRoomDeletionPolicyWithRecordings.CLOSE; + + constructor(@Inject(MAT_DIALOG_DATA) public data: DeleteRoomDialogOptions) {} + + close(type: 'confirm' | 'cancel'): void { + this.dialogRef.close(); + + if (type === 'confirm') { + this.data.confirmCallback(this.selectedMeetingPolicy, this.selectedRecordingPolicy); + } + } +} diff --git a/frontend/projects/shared-meet-components/src/lib/models/notification.model.ts b/frontend/projects/shared-meet-components/src/lib/models/notification.model.ts index a2da2bf..6097358 100644 --- a/frontend/projects/shared-meet-components/src/lib/models/notification.model.ts +++ b/frontend/projects/shared-meet-components/src/lib/models/notification.model.ts @@ -1,3 +1,5 @@ +import { MeetRoomDeletionPolicyWithMeeting, MeetRoomDeletionPolicyWithRecordings } from '@lib/typings/ce'; + export interface DialogOptions { title?: string; icon?: string; @@ -12,3 +14,15 @@ export interface DialogOptions { forceCheckboxDescription?: string; forceConfirmCallback?: () => void; } + +export interface DeleteRoomDialogOptions { + title: string; + message: string; + showWithMeetingPolicy: boolean; + showWithRecordingsPolicy: boolean; + confirmText?: string; + confirmCallback: ( + meetingPolicy: MeetRoomDeletionPolicyWithMeeting, + recordingPolicy: MeetRoomDeletionPolicyWithRecordings + ) => void; +}