diff --git a/src/app/components/dashboard/dashboard.css b/src/app/components/dashboard/dashboard.css
index 722f339..57ac088 100644
--- a/src/app/components/dashboard/dashboard.css
+++ b/src/app/components/dashboard/dashboard.css
@@ -155,6 +155,26 @@
opacity: 0.92;
}
+.workspace-feedback {
+ margin: -20px 0 18px;
+ border-radius: 10px;
+ padding: 10px 12px;
+ font-size: 13px;
+ font-weight: 600;
+}
+
+.workspace-feedback.success {
+ background: #e7f7ed;
+ color: #1f6b3d;
+ border: 1px solid #b7dfc4;
+}
+
+.workspace-feedback.error {
+ background: #fdecec;
+ color: #8d1c1c;
+ border: 1px solid #f7c5c5;
+}
+
.workspace-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
@@ -237,6 +257,95 @@
background: #000;
}
+.delete-btn:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
+}
+
+.modal-backdrop {
+ position: fixed;
+ inset: 0;
+ z-index: 40;
+ background: rgba(2, 6, 23, 0.62);
+ display: grid;
+ place-items: center;
+ padding: 18px;
+}
+
+.delete-modal {
+ width: min(480px, 100%);
+ background: #fff;
+ border: 1px solid rgba(0, 0, 0, 0.16);
+ border-radius: 16px;
+ box-shadow: 0 26px 64px rgba(0, 0, 0, 0.28);
+ padding: 22px;
+}
+
+.delete-modal-eyebrow {
+ margin: 0 0 8px;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ font-size: 11px;
+ color: #6b7280;
+ font-weight: 700;
+}
+
+.delete-modal h3 {
+ margin: 0;
+ font-size: 24px;
+ line-height: 1.25;
+}
+
+.delete-modal-copy {
+ margin: 10px 0 0;
+ color: #4b5563;
+ line-height: 1.5;
+}
+
+.delete-modal-error {
+ margin: 12px 0 0;
+ background: #fdecec;
+ color: #8d1c1c;
+ border: 1px solid #f7c5c5;
+ border-radius: 10px;
+ padding: 9px 11px;
+ font-size: 13px;
+ font-weight: 600;
+}
+
+.delete-modal-actions {
+ margin-top: 18px;
+ display: flex;
+ justify-content: flex-end;
+ gap: 10px;
+}
+
+.modal-btn {
+ border-radius: 10px;
+ border: 1px solid transparent;
+ padding: 9px 14px;
+ font-size: 13px;
+ font-weight: 600;
+ cursor: pointer;
+}
+
+.modal-btn.cancel {
+ background: #fff;
+ color: #111;
+ border-color: #d1d5db;
+}
+
+.modal-btn.confirm {
+ background: #111;
+ color: #fff;
+ border-color: #111;
+}
+
+.modal-btn:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
+}
+
.empty-state {
border: 1px solid rgba(0, 0, 0, 0.08);
border-radius: 16px;
diff --git a/src/app/components/dashboard/dashboard.html b/src/app/components/dashboard/dashboard.html
index 281ace7..16775f5 100644
--- a/src/app/components/dashboard/dashboard.html
+++ b/src/app/components/dashboard/dashboard.html
@@ -20,7 +20,7 @@
Decision Workspace Dashboard
-
Track active workspaces, Slack conversations, and GitHub work in one place with clean structure and minimal noise.
+
Track active workspaces, Slack conversations, GitHub updates, and JIRA work in one place with clean structure and minimal noise.
@@ -32,6 +32,8 @@ Decision Workspace Dashboard
Your Workspaces
Create Workspace
+ {{ deleteWorkspaceSuccess }}
+ {{ deleteWorkspaceError }}
0; else empty">
@@ -40,8 +42,15 @@ {{ ws.name }}
Created: {{ ws.createdDate | date: 'mediumDate' }}
Open
-
Edit
-
+
Edit
+
@@ -54,6 +63,25 @@ No workspaces yet
+
+
+ Delete Workspace
+ Delete "{{ workspace.name }}"?
+
+ This will remove the workspace and related data for your account. This action cannot be undone.
+
+
+ {{ deleteWorkspaceError }}
+
+
+
+
+
+
+
+
- Slack messages and GitHub updates appear here as action-ready signals.
+ Slack messages, GitHub updates, and JIRA work appear here as action-ready signals.
Signals
No matching signals
-
Connect Slack or GitHub in workspace integrations to start pulling messages and assigned work into Sentinent.
+
Connect Slack, GitHub, or JIRA in workspace integrations to start pulling messages and assigned work into Sentinent.
diff --git a/src/app/components/dashboard/dashboard.spec.ts b/src/app/components/dashboard/dashboard.spec.ts
index e2b3827..e5e785f 100644
--- a/src/app/components/dashboard/dashboard.spec.ts
+++ b/src/app/components/dashboard/dashboard.spec.ts
@@ -19,7 +19,8 @@ describe('Dashboard', () => {
mockWorkspaceService = {
getWorkspaces: jasmine.createSpy('getWorkspaces').and.returnValue(of([
{ id: '1', name: 'Test Workspace', description: 'Test Desc', createdDate: new Date(), ownerId: 'u1' }
- ]))
+ ])),
+ deleteWorkspace: jasmine.createSpy('deleteWorkspace').and.returnValue(of(true))
};
mockAuthService = {
@@ -92,7 +93,7 @@ describe('Dashboard', () => {
expect(compiled.querySelector('.workspace-card h3')?.textContent).toContain('Test Workspace');
});
- it('should hide edit action for workspaces not owned by the current user', () => {
+ it('should show edit action for all workspace cards', () => {
mockWorkspaceService.getWorkspaces.and.returnValue(of([
{ id: '1', name: 'Owned Workspace', description: 'Owned Desc', createdDate: new Date(), ownerId: 'u1' },
{ id: '2', name: 'Shared Workspace', description: 'Shared Desc', createdDate: new Date(), ownerId: 'u2' }
@@ -107,7 +108,7 @@ describe('Dashboard', () => {
const sharedCard = cards.find(card => card.textContent?.includes('Shared Workspace'));
expect(ownedCard?.textContent).toContain('Edit');
- expect(sharedCard?.textContent).not.toContain('Edit');
+ expect(sharedCard?.textContent).toContain('Edit');
});
it('should call logout on button click', () => {
@@ -126,4 +127,29 @@ describe('Dashboard', () => {
expect(compiled.textContent).toContain('Slack');
expect(compiled.textContent).toContain('Test Slack Signal');
});
+
+ it('should render jira source filter', () => {
+ const compiled = fixture.nativeElement as HTMLElement;
+ expect(compiled.textContent).toContain('JIRA');
+ });
+
+ it('should open custom delete dialog from workspace card', () => {
+ const deleteButton = fixture.nativeElement.querySelector('.delete-btn') as HTMLButtonElement;
+ deleteButton.click();
+ fixture.detectChanges();
+
+ const dialog = fixture.nativeElement.querySelector('.delete-modal') as HTMLElement | null;
+ expect(dialog).not.toBeNull();
+ expect(dialog?.textContent).toContain('Delete "Test Workspace"?');
+ });
+
+ it('should delete workspace via custom dialog and remove it from list', () => {
+ component.requestDeleteWorkspace(component.workspaces[0]);
+ component.confirmDeleteWorkspace();
+ fixture.detectChanges();
+
+ expect(mockWorkspaceService.deleteWorkspace).toHaveBeenCalledWith('1');
+ expect(component.workspaces.length).toBe(0);
+ expect(component.pendingDeleteWorkspace).toBeNull();
+ });
});
diff --git a/src/app/components/dashboard/dashboard.ts b/src/app/components/dashboard/dashboard.ts
index 7909b63..3a9c10b 100644
--- a/src/app/components/dashboard/dashboard.ts
+++ b/src/app/components/dashboard/dashboard.ts
@@ -1,4 +1,4 @@
-import { ChangeDetectorRef, Component, inject, OnInit } from '@angular/core';
+import { ChangeDetectorRef, Component, OnDestroy, OnInit, inject } from '@angular/core';
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { AuthService } from '../../services/auth';
import { WorkspaceService } from '../../services/workspace';
@@ -16,7 +16,7 @@ import { SearchBarComponent } from '../search-bar/search-bar';
templateUrl: './dashboard.html',
styleUrl: './dashboard.css',
})
-export class Dashboard implements OnInit {
+export class Dashboard implements OnInit, OnDestroy {
private authService = inject(AuthService);
private workspaceService = inject(WorkspaceService);
private signalService = inject(SignalService);
@@ -29,11 +29,13 @@ export class Dashboard implements OnInit {
filters: SignalFilters = { source: 'all', status: 'all' };
githubBanner = '';
slackBanner = '';
- currentUserId: string | null = null;
+ pendingDeleteWorkspace: Workspace | null = null;
+ isDeletingWorkspace = false;
+ deleteWorkspaceError = '';
+ deleteWorkspaceSuccess = '';
+ private deleteNoticeTimeoutId?: ReturnType;
ngOnInit() {
- this.currentUserId = this.authService.getCurrentUserId();
-
this.workspaceService.getWorkspaces().subscribe({
next: ws => {
this.workspaces = ws;
@@ -60,22 +62,57 @@ export class Dashboard implements OnInit {
this.loadSignals();
}
- canEditWorkspace(workspace: Workspace): boolean {
- return this.currentUserId !== null && workspace.ownerId === this.currentUserId;
+ ngOnDestroy(): void {
+ if (this.deleteNoticeTimeoutId !== undefined) {
+ clearTimeout(this.deleteNoticeTimeoutId);
+ this.deleteNoticeTimeoutId = undefined;
+ }
+ }
+
+ requestDeleteWorkspace(workspace: Workspace): void {
+ if (this.isDeletingWorkspace) {
+ return;
+ }
+ this.pendingDeleteWorkspace = workspace;
+ this.deleteWorkspaceError = '';
}
- deleteWorkspace(workspace: Workspace) {
- const confirmed = window.confirm(`Delete workspace "${workspace.name}"?`);
- if (!confirmed) {
+ cancelDeleteWorkspace(): void {
+ if (this.isDeletingWorkspace) {
return;
}
+ this.pendingDeleteWorkspace = null;
+ this.deleteWorkspaceError = '';
+ }
+
+ confirmDeleteWorkspace(): void {
+ const workspace = this.pendingDeleteWorkspace;
+ if (!workspace || this.isDeletingWorkspace) {
+ return;
+ }
+
+ this.isDeletingWorkspace = true;
+ this.deleteWorkspaceError = '';
- this.workspaceService.deleteWorkspace(workspace.id).subscribe(deleted => {
- if (!deleted) {
- return;
+ this.workspaceService.deleteWorkspace(workspace.id).subscribe({
+ next: (deleted) => {
+ if (!deleted) {
+ this.isDeletingWorkspace = false;
+ this.deleteWorkspaceError = 'Unable to delete workspace. Please try again.';
+ return;
+ }
+
+ this.workspaces = this.workspaces.filter(ws => ws.id !== workspace.id);
+ this.pendingDeleteWorkspace = null;
+ this.isDeletingWorkspace = false;
+ this.showDeleteSuccess(`Workspace "${workspace.name}" deleted.`);
+ this.cdr.detectChanges();
+ },
+ error: () => {
+ this.isDeletingWorkspace = false;
+ this.deleteWorkspaceError = 'Unable to delete workspace. Please try again.';
+ this.cdr.detectChanges();
}
- this.workspaces = this.workspaces.filter(ws => ws.id !== workspace.id);
- this.cdr.detectChanges();
});
}
@@ -102,10 +139,24 @@ export class Dashboard implements OnInit {
this.signalService.archive(signalId).subscribe(() => this.loadSignals());
}
+ isWorkspacePendingDelete(workspace: Workspace): boolean {
+ return this.pendingDeleteWorkspace?.id === workspace.id;
+ }
+
private loadSignals(): void {
this.signalService.getSignals(this.filters).subscribe(signals => {
this.signals = signals;
this.cdr.detectChanges();
});
}
+
+ private showDeleteSuccess(message: string): void {
+ this.deleteWorkspaceSuccess = message;
+ if (this.deleteNoticeTimeoutId !== undefined) {
+ clearTimeout(this.deleteNoticeTimeoutId);
+ }
+ this.deleteNoticeTimeoutId = setTimeout(() => {
+ this.deleteWorkspaceSuccess = '';
+ }, 2600);
+ }
}