Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Routes } from '@angular/router';
import { Login } from './components/login/login';
import { authGuard } from './guards/auth-guard';
import { CreateWorkspace } from './components/workspace/create-workspace';
import { WorkspaceIntegrationsComponent } from './components/workspace-integrations/workspace-integrations';

export const routes: Routes = [
{ path: 'login', component: Login },
Expand All @@ -16,6 +17,11 @@ export const routes: Routes = [
component: CreateWorkspace,
canActivate: [authGuard]
},
{
path: 'workspaces/:id/settings/integrations',
component: WorkspaceIntegrationsComponent,
canActivate: [authGuard]
},
{
path: 'workspaces/:id',
loadComponent: () => import('./components/workspace/workspace-details/workspace-details').then(m => m.WorkspaceDetailsComponent),
Expand Down
47 changes: 47 additions & 0 deletions src/app/components/dashboard/dashboard.css
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@
padding: 30px 32px 40px;
}

.signals-section {
padding-top: 0;
}

.section-header {
display: flex;
justify-content: space-between;
Expand All @@ -105,6 +109,11 @@
font-size: 28px;
}

.section-copy {
margin: 6px 0 0;
color: #4a5568;
}

.create-btn {
text-decoration: none;
background: #050505;
Expand Down Expand Up @@ -206,6 +215,39 @@
color: #555;
}

.integration-banner {
max-width: 1200px;
margin: 22px auto 0;
padding: 14px 18px;
border-radius: 16px;
background: rgba(229, 255, 245, 0.9);
color: #166534;
border: 1px solid rgba(34, 197, 94, 0.2);
font-weight: 600;
}

.filter-row {
display: flex;
flex-wrap: wrap;
gap: 10px;
}

.filter-pill {
border: 1px solid rgba(15, 23, 42, 0.1);
background: #fff;
color: #1f2937;
border-radius: 999px;
padding: 9px 14px;
font-weight: 600;
cursor: pointer;
}

.filter-pill.active {
background: #111827;
color: #fff;
border-color: #111827;
}

@media (max-width: 768px) {
.hero,
.content {
Expand All @@ -216,4 +258,9 @@
.hero-copy h1 {
font-size: 32px;
}

.integration-banner {
margin-left: 18px;
margin-right: 18px;
}
}
33 changes: 32 additions & 1 deletion src/app/components/dashboard/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@

<div class="hero-copy">
<h1>Decision Workspace Dashboard</h1>
<p>Track all active workspaces in one place with clean structure and minimal noise.</p>
<p>Track active workspaces and GitHub work in one place with clean structure and minimal noise.</p>
</div>
</section>

<p class="integration-banner" *ngIf="githubBanner">{{ githubBanner }}</p>

<section class="content">
<div class="section-header">
<h2>Your Workspaces</h2>
Expand All @@ -47,4 +49,33 @@ <h3>No workspaces yet</h3>
</div>
</ng-template>
</section>

<section class="content signals-section">
<div class="section-header">
<div>
<h2>Signals</h2>
<p class="section-copy">Assigned GitHub issues and pull requests appear here as action-ready signals.</p>
</div>
<div class="filter-row">
<button type="button" class="filter-pill" [class.active]="filters.source === 'all'" (click)="setSourceFilter('all')">All</button>
<button type="button" class="filter-pill" [class.active]="filters.source === 'github'" (click)="setSourceFilter('github')">GitHub</button>
<button type="button" class="filter-pill" [class.active]="filters.status === 'all'" (click)="setStatusFilter('all')">All statuses</button>
<button type="button" class="filter-pill" [class.active]="filters.status === 'unread'" (click)="setStatusFilter('unread')">Unread</button>
</div>
</div>

<app-signal-board
*ngIf="signals.length; else noSignals"
[signals]="signals"
(markAsRead)="markSignalAsRead($event)"
(archive)="archiveSignal($event)"
/>

<ng-template #noSignals>
<div class="empty-state">
<h3>No matching signals</h3>
<p>Connect GitHub and run a sync to start pulling assigned issues and pull requests into Sentinent.</p>
</div>
</ng-template>
</section>
</div>
36 changes: 35 additions & 1 deletion src/app/components/dashboard/dashboard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import { AuthService } from '../../services/auth';
import { of } from 'rxjs';
import { RouterTestingModule } from '@angular/router/testing';
import { Router } from '@angular/router';
import { SignalService } from '../../services/signal.service';

describe('Dashboard', () => {
let component: Dashboard;
let fixture: ComponentFixture<Dashboard>;
let mockWorkspaceService: any;
let mockAuthService: any;
let mockSignalService: any;
let router: Router;

beforeEach(async () => {
Expand All @@ -24,11 +26,38 @@ describe('Dashboard', () => {
logout: jasmine.createSpy('logout')
};

mockSignalService = {
getSignals: jasmine.createSpy('getSignals').and.returnValue(of([
{
id: 'github-1',
sourceType: 'github',
sourceId: 'Sentinent-AI/frontend-angular',
externalId: '10',
title: 'Test GitHub Signal',
content: 'Signal content',
author: '@tester',
status: 'unread',
receivedAt: new Date(),
metadata: {
type: 'issue',
number: 10,
repository: 'Sentinent-AI/frontend-angular',
state: 'open',
labels: ['frontend'],
assignees: ['@tester']
}
}
])),
markAsRead: jasmine.createSpy('markAsRead').and.returnValue(of(void 0)),
archive: jasmine.createSpy('archive').and.returnValue(of(void 0))
};

await TestBed.configureTestingModule({
imports: [Dashboard, RouterTestingModule],
providers: [
{ provide: WorkspaceService, useValue: mockWorkspaceService },
{ provide: AuthService, useValue: mockAuthService }
{ provide: AuthService, useValue: mockAuthService },
{ provide: SignalService, useValue: mockSignalService }
]
}).compileComponents();

Expand All @@ -52,4 +81,9 @@ describe('Dashboard', () => {
button.click();
expect(mockAuthService.logout).toHaveBeenCalled();
});

it('should render github signals', () => {
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.textContent).toContain('Test GitHub Signal');
});
});
47 changes: 45 additions & 2 deletions src/app/components/dashboard/dashboard.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,47 @@
import { Component, inject, OnInit } from '@angular/core';
import { Router, RouterLink } from '@angular/router';
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { AuthService } from '../../services/auth';
import { WorkspaceService } from '../../services/workspace';
import { Workspace } from '../../models/workspace';
import { CommonModule } from '@angular/common';
import { Signal, SignalFilters } from '../../models/signal.model';
import { SignalService } from '../../services/signal.service';
import { SignalBoardComponent } from '../signal-board/signal-board';

@Component({
selector: 'app-dashboard',
standalone: true,
imports: [CommonModule, RouterLink],
imports: [CommonModule, RouterLink, SignalBoardComponent],
templateUrl: './dashboard.html',
styleUrl: './dashboard.css',
})
export class Dashboard implements OnInit {
private authService = inject(AuthService);
private workspaceService = inject(WorkspaceService);
private signalService = inject(SignalService);
private router = inject(Router);
private route = inject(ActivatedRoute);

workspaces: Workspace[] = [];
signals: Signal[] = [];
filters: SignalFilters = { source: 'all', status: 'all' };
githubBanner = '';

ngOnInit() {
this.workspaceService.getWorkspaces().subscribe(ws => {
this.workspaces = ws;
});

this.route.queryParamMap.subscribe(params => {
const githubStatus = params.get('github');
this.githubBanner = githubStatus === 'connected'
? 'GitHub connected successfully. Review repository access in your workspace integrations.'
: githubStatus === 'error'
? 'GitHub connection failed. Please try the OAuth flow again.'
: '';
});

this.loadSignals();
}

editWorkspace(workspace: Workspace) {
Expand Down Expand Up @@ -64,4 +83,28 @@ export class Dashboard implements OnInit {
this.authService.logout();
this.router.navigate(['/login']);
}

setSourceFilter(source: SignalFilters['source']): void {
this.filters = { ...this.filters, source };
this.loadSignals();
}

setStatusFilter(status: SignalFilters['status']): void {
this.filters = { ...this.filters, status };
this.loadSignals();
}

markSignalAsRead(signalId: string): void {
this.signalService.markAsRead(signalId).subscribe(() => this.loadSignals());
}

archiveSignal(signalId: string): void {
this.signalService.archive(signalId).subscribe(() => this.loadSignals());
}

private loadSignals(): void {
this.signalService.getSignals(this.filters).subscribe(signals => {
this.signals = signals;
});
}
}
105 changes: 105 additions & 0 deletions src/app/components/signal-board/signal-board.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
.signals-board {
display: grid;
gap: 1rem;
}

.signal-card {
background: #ffffff;
border: 1px solid #dbe4f0;
border-radius: 20px;
padding: 1.25rem;
box-shadow: 0 18px 35px rgba(15, 23, 42, 0.06);
}

.signal-top,
.signal-footer,
.signal-actions {
display: flex;
justify-content: space-between;
gap: 0.75rem;
align-items: center;
}

.signal-source {
display: inline-flex;
margin-bottom: 0.45rem;
padding: 0.2rem 0.55rem;
border-radius: 999px;
background: #111827;
color: #ffffff;
font-size: 0.76rem;
font-weight: 700;
}

.signal-card h3 {
margin: 0;
font-size: 1.05rem;
}

.signal-status,
.badge {
display: inline-flex;
align-items: center;
border-radius: 999px;
padding: 0.28rem 0.6rem;
font-size: 0.78rem;
font-weight: 700;
background: #e2e8f0;
color: #0f172a;
}

.signal-status.unread {
background: #dbeafe;
color: #1d4ed8;
}

.signal-meta,
.signal-body {
color: #475569;
}

.signal-badges,
.assignees {
display: flex;
flex-wrap: wrap;
gap: 0.45rem;
margin: 0.9rem 0;
}

.badge.neutral {
background: #f1f5f9;
}

.signal-actions {
margin-top: 1rem;
justify-content: flex-start;
flex-wrap: wrap;
}

.link-btn,
.ghost-btn {
border-radius: 999px;
padding: 0.65rem 0.9rem;
font-weight: 600;
text-decoration: none;
}

.link-btn {
background: #111827;
color: #ffffff;
}

.ghost-btn {
border: 1px solid #cbd5e1;
background: #ffffff;
color: #0f172a;
cursor: pointer;
}

@media (max-width: 720px) {
.signal-top,
.signal-footer {
flex-direction: column;
align-items: flex-start;
}
}
Loading
Loading