diff --git a/src/app/components/app-nav/app-nav.css b/src/app/components/app-nav/app-nav.css new file mode 100644 index 0000000..515e5b3 --- /dev/null +++ b/src/app/components/app-nav/app-nav.css @@ -0,0 +1,114 @@ +:host { + display: block; +} + +.app-nav { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 100; + height: 56px; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 28px; + background: rgba(5, 5, 5, 0.88); + backdrop-filter: blur(14px); + -webkit-backdrop-filter: blur(14px); + border-bottom: 1px solid rgba(255, 255, 255, 0.07); +} + +/* ── Brand ───────────────────────────────────────────────── */ + +.nav-brand { + display: inline-flex; + align-items: center; + gap: 10px; + text-decoration: none; + color: #f7f7f7; + font-size: 17px; + font-weight: 800; + letter-spacing: -0.02em; +} + +.nav-brand-icon { + width: 32px; + height: 32px; + border-radius: 8px; + background: #f7f7f7; + color: #050505; + display: grid; + place-items: center; + flex-shrink: 0; +} + +/* ── Links ───────────────────────────────────────────────── */ + +.nav-links { + display: flex; + align-items: center; + gap: 6px; +} + +.nav-link { + position: relative; + display: inline-flex; + align-items: center; + gap: 7px; + text-decoration: none; + color: rgba(247, 247, 247, 0.65); + font-size: 14px; + font-weight: 600; + padding: 6px 12px; + border-radius: 8px; + transition: color 0.15s ease, background 0.15s ease; +} + +.nav-link:hover { + color: #f7f7f7; + background: rgba(255, 255, 255, 0.07); +} + +.nav-link.active { + color: #f7f7f7; + background: rgba(255, 255, 255, 0.1); +} + +/* ── Unread badge ────────────────────────────────────────── */ + +.nav-badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 18px; + height: 18px; + padding: 0 5px; + border-radius: 999px; + background: #f7f7f7; + color: #050505; + font-size: 11px; + font-weight: 800; + line-height: 1; +} + +/* ── Logout ──────────────────────────────────────────────── */ + +.nav-logout { + border: 1px solid rgba(255, 255, 255, 0.18); + background: transparent; + color: rgba(247, 247, 247, 0.65); + border-radius: 8px; + padding: 6px 12px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease; + margin-left: 6px; +} + +.nav-logout:hover { + color: #f7f7f7; + background: rgba(255, 255, 255, 0.07); + border-color: rgba(255, 255, 255, 0.3); +} diff --git a/src/app/components/app-nav/app-nav.html b/src/app/components/app-nav/app-nav.html new file mode 100644 index 0000000..a4d6fee --- /dev/null +++ b/src/app/components/app-nav/app-nav.html @@ -0,0 +1,24 @@ + diff --git a/src/app/components/app-nav/app-nav.ts b/src/app/components/app-nav/app-nav.ts new file mode 100644 index 0000000..28cc63d --- /dev/null +++ b/src/app/components/app-nav/app-nav.ts @@ -0,0 +1,49 @@ +import { CommonModule } from '@angular/common'; +import { Component, OnInit, OnDestroy, inject } from '@angular/core'; +import { Router, RouterLink, RouterLinkActive } from '@angular/router'; +import { AuthService } from '../../services/auth'; +import { SignalService } from '../../services/signal.service'; + +@Component({ + selector: 'app-nav', + standalone: true, + imports: [CommonModule, RouterLink, RouterLinkActive], + templateUrl: './app-nav.html', + styleUrl: './app-nav.css', +}) +export class AppNavComponent implements OnInit, OnDestroy { + private readonly authService = inject(AuthService); + private readonly signalService = inject(SignalService); + private readonly router = inject(Router); + + unreadCount = 0; + private pollInterval?: ReturnType; + + ngOnInit(): void { + this.refreshUnread(); + // Poll every 60s so the badge stays fresh without hammering the API + this.pollInterval = setInterval(() => this.refreshUnread(), 60_000); + } + + ngOnDestroy(): void { + if (this.pollInterval !== undefined) { + clearInterval(this.pollInterval); + } + } + + logout(): void { + this.authService.logout(); + this.router.navigate(['/login']); + } + + get badgeLabel(): string { + return this.unreadCount > 99 ? '99+' : String(this.unreadCount); + } + + private refreshUnread(): void { + this.signalService.getSignals({ source: 'all', status: 'unread' }).subscribe({ + next: (signals) => { this.unreadCount = signals.length; }, + error: () => {} + }); + } +} diff --git a/src/app/components/dashboard/dashboard.css b/src/app/components/dashboard/dashboard.css index 57ac088..d89cd23 100644 --- a/src/app/components/dashboard/dashboard.css +++ b/src/app/components/dashboard/dashboard.css @@ -21,6 +21,7 @@ position: relative; z-index: 1; min-height: 100vh; + padding-top: 56px; } .hero { @@ -96,6 +97,17 @@ color: rgba(247, 247, 247, 0.75); } +.hero-link { + color: #ffffff; + font-weight: 700; + text-decoration: underline; + text-underline-offset: 3px; +} + +.hero-link:hover { + opacity: 0.82; +} + .content { max-width: 1280px; margin: 0 auto; @@ -120,26 +132,111 @@ } .signals-header { + display: flex; flex-direction: column; - align-items: flex-start; - gap: 20px; + gap: 0; + margin-bottom: 0; } -.title-group { +.signals-title-row { display: flex; align-items: center; - gap: 24px; - width: 100%; + justify-content: space-between; + gap: 16px; + margin-bottom: 20px; +} + +.signals-title-row h2 { + margin: 0; + font-size: 28px; +} + +.signals-header-right { + display: flex; + align-items: center; + gap: 12px; } .signals-search { flex: 1; - max-width: 400px; + max-width: 380px; } -.section-copy { - margin: -10px 0 18px; - color: #4a5568; +.unread-toggle { + border: 1px solid rgba(0, 0, 0, 0.18); + background: transparent; + color: #555; + border-radius: 8px; + padding: 7px 13px; + font-size: 13px; + font-weight: 600; + cursor: pointer; + white-space: nowrap; + transition: all 0.15s ease; +} + +.unread-toggle:hover { + background: #f0f0f0; + color: #111; +} + +.unread-toggle.active { + background: #111; + color: #fff; + border-color: #111; +} + +/* ── Source tabs ─────────────────────────────────────────── */ + +.signal-tabs { + display: flex; + gap: 2px; + border-bottom: 2px solid rgba(0, 0, 0, 0.08); + margin-bottom: 28px; +} + +.signal-tab { + display: inline-flex; + align-items: center; + gap: 7px; + background: transparent; + border: none; + border-bottom: 2px solid transparent; + margin-bottom: -2px; + padding: 10px 16px; + font-size: 14px; + font-weight: 600; + color: #666; + cursor: pointer; + transition: color 0.15s ease, border-color 0.15s ease; +} + +.signal-tab:hover { + color: #111; +} + +.signal-tab.active { + color: #111; + border-bottom-color: #111; +} + +.tab-count { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 20px; + height: 20px; + padding: 0 6px; + border-radius: 999px; + background: rgba(0, 0, 0, 0.07); + color: #444; + font-size: 11px; + font-weight: 700; +} + +.signal-tab.active .tab-count { + background: #111; + color: #fff; } .create-btn { @@ -196,6 +293,31 @@ border-color: rgba(0, 0, 0, 0.15); } +.card-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + margin-bottom: 8px; +} + +.card-header h3 { + margin: 0; + font-size: 20px; + font-weight: 700; + letter-spacing: -0.01em; +} + +.unread-badge { + flex-shrink: 0; + background: #050505; + color: #f7f7f7; + border-radius: 999px; + padding: 3px 10px; + font-size: 12px; + font-weight: 700; +} + .workspace-card h3 { margin: 0 0 8px; font-size: 20px; @@ -350,7 +472,7 @@ border: 1px solid rgba(0, 0, 0, 0.08); border-radius: 16px; background: #fff; - padding: 48px 32px; + padding: 56px 32px; text-align: center; } @@ -364,6 +486,45 @@ color: #555; } +.empty-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 56px; + height: 56px; + border-radius: 16px; + margin: 0 auto 20px; + color: #888; +} + +.empty-no-integrations .empty-icon { + background: #f3f4f6; + border: 1px solid rgba(0, 0, 0, 0.07); +} + +.empty-caught-up .empty-icon { + background: #edfaf3; + border: 1px solid rgba(34, 197, 94, 0.18); + color: #22863a; +} + +.empty-cta { + display: inline-block; + margin-top: 18px; + padding: 10px 18px; + background: #111; + color: #fff; + border-radius: 10px; + text-decoration: none; + font-size: 13px; + font-weight: 600; + transition: opacity 0.15s ease; +} + +.empty-cta:hover { + opacity: 0.85; +} + .integration-banner { max-width: 1200px; margin: 22px auto 0; diff --git a/src/app/components/dashboard/dashboard.html b/src/app/components/dashboard/dashboard.html index 16775f5..457571e 100644 --- a/src/app/components/dashboard/dashboard.html +++ b/src/app/components/dashboard/dashboard.html @@ -1,23 +1,9 @@ + +
-
-
-
- -
- Sentinent -
-
- Profile - -
-
-

Decision Workspace Dashboard

Track active workspaces, Slack conversations, GitHub updates, and JIRA work in one place with clean structure and minimal noise.

@@ -37,7 +23,12 @@

Your Workspaces

-

{{ ws.name }}

+
+

{{ ws.name }}

+ + {{ unreadCountFor(ws.id) }} unread + +

{{ ws.description || 'No description yet.' }}

Created: {{ ws.createdDate | date: 'mediumDate' }}
@@ -83,21 +74,39 @@

Delete "{{ workspace.name }}"?

-
-
+
+

Signals

- +
+ + +
-
- - - - - - + +
+ + + +
-

Slack messages, GitHub updates, and JIRA work appear here as action-ready signals.

Signals /> -
-

No matching signals

-

Connect Slack, GitHub, or JIRA in workspace integrations to start pulling messages and assigned work into Sentinent.

+ +
+ +

No signals yet

+

Connect Slack, GitHub, or JIRA to start pulling messages and assigned work into Sentinent.

+ Connect integrations → +
+ + +
+ +

All caught up{{ sourceTab !== 'all' ? ' on ' + sourceTab : '' }}

+

No unread {{ sourceTab === 'all' ? '' : sourceTab + ' ' }}signals — you're up to date.

+

No {{ sourceTab === 'all' ? '' : sourceTab + ' ' }}signals match the current filter.

diff --git a/src/app/components/dashboard/dashboard.ts b/src/app/components/dashboard/dashboard.ts index 3a9c10b..a92ab11 100644 --- a/src/app/components/dashboard/dashboard.ts +++ b/src/app/components/dashboard/dashboard.ts @@ -1,6 +1,5 @@ import { ChangeDetectorRef, Component, OnDestroy, OnInit, inject } from '@angular/core'; -import { ActivatedRoute, Router, RouterLink } from '@angular/router'; -import { AuthService } from '../../services/auth'; +import { ActivatedRoute, RouterLink } from '@angular/router'; import { WorkspaceService } from '../../services/workspace'; import { Workspace } from '../../models/workspace'; import { CommonModule } from '@angular/common'; @@ -8,24 +7,27 @@ import { Signal, SignalFilters } from '../../models/signal.model'; import { SignalService } from '../../services/signal.service'; import { SignalBoardComponent } from '../signal-board/signal-board'; import { SearchBarComponent } from '../search-bar/search-bar'; +import { AppNavComponent } from '../app-nav/app-nav'; @Component({ selector: 'app-dashboard', standalone: true, - imports: [CommonModule, RouterLink, SignalBoardComponent, SearchBarComponent], + imports: [CommonModule, RouterLink, SignalBoardComponent, SearchBarComponent, AppNavComponent], templateUrl: './dashboard.html', styleUrl: './dashboard.css', }) export class Dashboard implements OnInit, OnDestroy { - private authService = inject(AuthService); private workspaceService = inject(WorkspaceService); private signalService = inject(SignalService); - private router = inject(Router); private route = inject(ActivatedRoute); private cdr = inject(ChangeDetectorRef); workspaces: Workspace[] = []; - signals: Signal[] = []; + allSignals: Signal[] = []; // full list, only filtered by status + signals: Signal[] = []; // allSignals filtered by active source tab + unreadByWorkspace: Record = {}; + sourceTab: 'all' | 'slack' | 'github' | 'jira' = 'all'; + showUnreadOnly = false; filters: SignalFilters = { source: 'all', status: 'all' }; githubBanner = ''; slackBanner = ''; @@ -70,26 +72,20 @@ export class Dashboard implements OnInit, OnDestroy { } requestDeleteWorkspace(workspace: Workspace): void { - if (this.isDeletingWorkspace) { - return; - } + if (this.isDeletingWorkspace) return; this.pendingDeleteWorkspace = workspace; this.deleteWorkspaceError = ''; } cancelDeleteWorkspace(): void { - if (this.isDeletingWorkspace) { - return; - } + if (this.isDeletingWorkspace) return; this.pendingDeleteWorkspace = null; this.deleteWorkspaceError = ''; } confirmDeleteWorkspace(): void { const workspace = this.pendingDeleteWorkspace; - if (!workspace || this.isDeletingWorkspace) { - return; - } + if (!workspace || this.isDeletingWorkspace) return; this.isDeletingWorkspace = true; this.deleteWorkspaceError = ''; @@ -101,7 +97,6 @@ export class Dashboard implements OnInit, OnDestroy { 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; @@ -116,19 +111,19 @@ export class Dashboard implements OnInit, OnDestroy { }); } - logout() { - this.authService.logout(); - this.router.navigate(['/login']); + setSourceTab(tab: typeof this.sourceTab): void { + this.sourceTab = tab; + this.applyFilters(); } - setSourceFilter(source: SignalFilters['source']): void { - this.filters = { ...this.filters, source }; + toggleUnreadOnly(): void { + this.showUnreadOnly = !this.showUnreadOnly; + this.filters = { ...this.filters, status: this.showUnreadOnly ? 'unread' : 'all' }; this.loadSignals(); } - setStatusFilter(status: SignalFilters['status']): void { - this.filters = { ...this.filters, status }; - this.loadSignals(); + countFor(source: 'slack' | 'github' | 'jira'): number { + return this.allSignals.filter(s => s.sourceType === source).length; } markSignalAsRead(signalId: string): void { @@ -143,18 +138,36 @@ export class Dashboard implements OnInit, OnDestroy { return this.pendingDeleteWorkspace?.id === workspace.id; } + unreadCountFor(workspaceId: number): number { + return this.unreadByWorkspace[workspaceId] ?? 0; + } + private loadSignals(): void { - this.signalService.getSignals(this.filters).subscribe(signals => { - this.signals = signals; + // Always fetch with status filter only; source filtering is done client-side + const fetchFilter: SignalFilters = { source: 'all', status: this.filters.status }; + this.signalService.getSignals(fetchFilter).subscribe(signals => { + this.allSignals = signals; + this.unreadByWorkspace = signals.reduce>((acc, s) => { + if (s.status === 'unread' && s.workspaceId != null) { + acc[s.workspaceId] = (acc[s.workspaceId] ?? 0) + 1; + } + return acc; + }, {}); + this.applyFilters(); this.cdr.detectChanges(); }); } + private applyFilters(): void { + this.signals = this.sourceTab === 'all' + ? this.allSignals + : this.allSignals.filter(s => s.sourceType === this.sourceTab); + this.cdr.detectChanges(); + } + private showDeleteSuccess(message: string): void { this.deleteWorkspaceSuccess = message; - if (this.deleteNoticeTimeoutId !== undefined) { - clearTimeout(this.deleteNoticeTimeoutId); - } + if (this.deleteNoticeTimeoutId !== undefined) clearTimeout(this.deleteNoticeTimeoutId); this.deleteNoticeTimeoutId = setTimeout(() => { this.deleteWorkspaceSuccess = ''; }, 2600); diff --git a/src/app/components/decision-list/decision-list.component.css b/src/app/components/decision-list/decision-list.component.css index b0fd5e5..a0ad961 100644 --- a/src/app/components/decision-list/decision-list.component.css +++ b/src/app/components/decision-list/decision-list.component.css @@ -151,12 +151,55 @@ } .empty-state { - padding: 48px; + padding: 56px 32px; text-align: center; - background: #f9f9f9; - border-radius: 12px; - border: 2px dashed #e5e5e5; + background: #fff; + border-radius: 14px; + border: 1px solid rgba(0, 0, 0, 0.08); + color: #444; +} + +.empty-state h3 { + margin: 0 0 8px; + font-size: 20px; + color: #111; +} + +.empty-state p { + margin: 0; + font-size: 14px; color: #666; + line-height: 1.6; +} + +.empty-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 52px; + height: 52px; + border-radius: 14px; + background: #f3f4f6; + border: 1px solid rgba(0, 0, 0, 0.07); + color: #555; + margin: 0 auto 18px; +} + +.empty-cta { + display: inline-block; + margin-top: 20px; + padding: 10px 18px; + background: #0a0a0a; + color: #fff; + border-radius: 10px; + text-decoration: none; + font-size: 13px; + font-weight: 600; + transition: opacity 0.15s ease; +} + +.empty-cta:hover { + opacity: 0.82; } .loading-state { diff --git a/src/app/components/decision-list/decision-list.component.html b/src/app/components/decision-list/decision-list.component.html index 628d28e..7cc72b9 100644 --- a/src/app/components/decision-list/decision-list.component.html +++ b/src/app/components/decision-list/decision-list.component.html @@ -17,7 +17,17 @@

Decisions

- No decisions found. Create one to get started! + +

No decisions yet

+

Capture key decisions here — track context, options, and outcomes in one place.

+ Create your first decision
diff --git a/src/app/components/profile/profile.css b/src/app/components/profile/profile.css index e794a6c..2770ecf 100644 --- a/src/app/components/profile/profile.css +++ b/src/app/components/profile/profile.css @@ -17,6 +17,10 @@ padding: 42px 24px 64px; } +:host > .profile-shell:first-of-type { + padding-top: calc(56px + 42px); +} + .profile-hero { display: flex; justify-content: space-between; @@ -308,6 +312,75 @@ margin-top: 2px; } +/* ── Integrations section ─────────────────────────────────────── */ + +.integrations-section, +.integrations-empty { + margin-top: 48px; + padding-top: 40px; + border-top: 1px solid rgba(255, 255, 255, 0.08); +} + +.integrations-section-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 24px; + margin-bottom: 32px; +} + +.integrations-heading { + margin: 4px 0 8px; + font-size: 32px; + color: #f5f5f5; + letter-spacing: -0.02em; +} + +.integrations-intro { + margin: 0; + color: rgba(255, 255, 255, 0.55); + max-width: 540px; + line-height: 1.6; +} + +.workspace-selector { + display: flex; + flex-direction: column; + gap: 6px; + flex-shrink: 0; +} + +.workspace-selector-label { + font-size: 11px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.1em; + color: rgba(255, 255, 255, 0.45); +} + +.workspace-select { + appearance: none; + border: 1px solid rgba(255, 255, 255, 0.16); + border-radius: 12px; + background: rgba(255, 255, 255, 0.06); + color: #f5f5f5; + padding: 10px 36px 10px 14px; + font: inherit; + font-size: 14px; + font-weight: 600; + cursor: pointer; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%23ffffff' stroke-width='1.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 12px center; +} + +.workspace-select:focus { + outline: none; + border-color: rgba(255, 255, 255, 0.4); +} + +/* ── Responsive ───────────────────────────────────────────────── */ + @media (max-width: 860px) { .profile-hero, .profile-layout, @@ -320,4 +393,12 @@ .hero-actions { justify-content: flex-start; } + + .integrations-section-header { + flex-direction: column; + } + + .workspace-select { + width: 100%; + } } diff --git a/src/app/components/profile/profile.html b/src/app/components/profile/profile.html index 88e9e21..1eadcd9 100644 --- a/src/app/components/profile/profile.html +++ b/src/app/components/profile/profile.html @@ -1,15 +1,17 @@ + +

Loading

Loading your profile...

-

We’re pulling your account details from Sentinent now.

+

We're pulling your account details from Sentinent now.

Profile unavailable

-

We couldn’t load this profile yet.

+

We couldn't load this profile yet.

{{ errorMessage || 'Try refreshing after the backend is running with the new profile API.' }}

@@ -32,7 +34,6 @@

{{ displayName() }}

- Back to dashboard
@@ -166,4 +167,36 @@

Personal summary

+ + +
+
+
+

Connected accounts

+

Integrations

+

Connect Slack, GitHub, and Jira to pull your assigned work and team messages into the signals feed.

+
+ +
+ + +
+
+ + +
+ +
+

Connected accounts

+

Integrations

+

Create a workspace first to set up your Slack, GitHub, and Jira connections.

+ Create a workspace +
diff --git a/src/app/components/profile/profile.ts b/src/app/components/profile/profile.ts index 8fd5049..1683cf1 100644 --- a/src/app/components/profile/profile.ts +++ b/src/app/components/profile/profile.ts @@ -5,11 +5,15 @@ import { RouterLink } from '@angular/router'; import { Subscription } from 'rxjs'; import { UserProfile, UserProfileUpdate } from '../../models/user-profile.model'; import { UserProfileService } from '../../services/user-profile.service'; +import { WorkspaceService } from '../../services/workspace'; +import { Workspace } from '../../models/workspace'; +import { WorkspaceIntegrationsComponent } from '../workspace-integrations/workspace-integrations'; +import { AppNavComponent } from '../app-nav/app-nav'; @Component({ selector: 'app-profile', standalone: true, - imports: [CommonModule, FormsModule, RouterLink], + imports: [CommonModule, FormsModule, RouterLink, WorkspaceIntegrationsComponent, AppNavComponent], templateUrl: './profile.html', styleUrl: './profile.css', }) @@ -17,6 +21,7 @@ export class ProfileComponent implements OnInit, OnDestroy { private static readonly PROFILE_LOAD_TIMEOUT_MS = 8000; private readonly userProfileService = inject(UserProfileService); + private readonly workspaceService = inject(WorkspaceService); private readonly cdr = inject(ChangeDetectorRef); private loadSubscription?: Subscription; private loadTimeoutId?: ReturnType; @@ -36,18 +41,33 @@ export class ProfileComponent implements OnInit, OnDestroy { errorMessage = ''; successMessage = ''; + workspaces: Workspace[] = []; + selectedWorkspaceId = ''; + ngOnInit(): void { this.loadProfile(); + this.workspaceService.getWorkspaces().subscribe({ + next: (ws) => { + this.workspaces = ws; + if (ws.length > 0) { + this.selectedWorkspaceId = String(ws[0].id); + } + this.cdr.detectChanges(); + }, + error: () => {} + }); } ngOnDestroy(): void { this.clearActiveLoad(); } + selectWorkspace(id: string): void { + this.selectedWorkspaceId = id; + } + startEditing(): void { - if (!this.profile) { - return; - } + if (!this.profile) return; this.isEditing = true; this.errorMessage = ''; this.successMessage = ''; @@ -58,9 +78,7 @@ export class ProfileComponent implements OnInit, OnDestroy { this.isEditing = false; this.errorMessage = ''; this.successMessage = ''; - if (this.profile) { - this.draft = this.toDraft(this.profile); - } + if (this.profile) this.draft = this.toDraft(this.profile); } saveProfile(): void { @@ -102,14 +120,10 @@ export class ProfileComponent implements OnInit, OnDestroy { displayName(): string { const resolvedName = this.displayValue(this.profile?.fullName); - if (resolvedName) { - return resolvedName; - } + if (resolvedName) return resolvedName; const emailName = this.displayValue(this.profile?.email)?.split('@')[0] ?? ''; - if (!emailName) { - return 'Sentinent User'; - } + if (!emailName) return 'Sentinent User'; return emailName .split(/[^a-zA-Z0-9]+/) @@ -165,9 +179,7 @@ export class ProfileComponent implements OnInit, OnDestroy { } private clearLoadTimeout(): void { - if (this.loadTimeoutId === undefined) { - return; - } + if (this.loadTimeoutId === undefined) return; clearTimeout(this.loadTimeoutId); this.loadTimeoutId = undefined; } diff --git a/src/app/components/workspace-integrations/workspace-integrations.ts b/src/app/components/workspace-integrations/workspace-integrations.ts index 37ffdad..92fcff4 100644 --- a/src/app/components/workspace-integrations/workspace-integrations.ts +++ b/src/app/components/workspace-integrations/workspace-integrations.ts @@ -1,5 +1,5 @@ import { CommonModule } from '@angular/common'; -import { Component, OnInit, OnDestroy, inject } from '@angular/core'; +import { Component, Input, OnInit, OnDestroy, OnChanges, SimpleChanges, inject } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { IntegrationService } from '../../services/integration.service'; import { GitHubRepo, SyncStatus } from '../../models/github-integration.model'; @@ -13,7 +13,10 @@ import { AtlassianResource, JiraSyncStatus } from '../../models/jira-integration templateUrl: './workspace-integrations.html', styleUrl: './workspace-integrations.css' }) -export class WorkspaceIntegrationsComponent implements OnInit, OnDestroy { +export class WorkspaceIntegrationsComponent implements OnInit, OnChanges, OnDestroy { + /** When provided (e.g. embedded in profile), this takes precedence over the route param. */ + @Input() inputWorkspaceId?: string; + private readonly route = inject(ActivatedRoute); private readonly integrationService = inject(IntegrationService); @@ -51,11 +54,18 @@ export class WorkspaceIntegrationsComponent implements OnInit, OnDestroy { isJiraSyncing = false; ngOnInit(): void { - this.workspaceId = this.getWorkspaceIdFromRoute() ?? ''; + this.workspaceId = this.inputWorkspaceId ?? this.getWorkspaceIdFromRoute() ?? ''; this.loadAllIntegrations(); window.addEventListener('focus', this.onWindowFocus); } + ngOnChanges(changes: SimpleChanges): void { + if (changes['inputWorkspaceId'] && !changes['inputWorkspaceId'].firstChange) { + this.workspaceId = this.inputWorkspaceId ?? ''; + this.loadAllIntegrations(); + } + } + private getWorkspaceIdFromRoute(): string | null { const path = this.route.pathFromRoot || []; for (const route of path) { diff --git a/src/app/components/workspace/workspace-details/workspace-details.css b/src/app/components/workspace/workspace-details/workspace-details.css index d103101..4121517 100644 --- a/src/app/components/workspace/workspace-details/workspace-details.css +++ b/src/app/components/workspace/workspace-details/workspace-details.css @@ -1,6 +1,6 @@ .workspace-page { min-height: 100vh; - padding: 28px 22px 36px; + padding: 84px 22px 36px; background: radial-gradient(circle at 12% 8%, rgba(0, 0, 0, 0.05), transparent 34%), linear-gradient(160deg, #f7f7f7 0%, #ffffff 52%, #f2f2f2 100%); diff --git a/src/app/components/workspace/workspace-details/workspace-details.html b/src/app/components/workspace/workspace-details/workspace-details.html index 1c7336d..5a8f329 100644 --- a/src/app/components/workspace/workspace-details/workspace-details.html +++ b/src/app/components/workspace/workspace-details/workspace-details.html @@ -1,7 +1,9 @@ + +
- Back to Dashboard + ← Dashboard
Edit Workspace New Decision @@ -38,7 +40,7 @@

{{ workspace.name }}

diff --git a/src/app/components/workspace/workspace-details/workspace-details.ts b/src/app/components/workspace/workspace-details/workspace-details.ts index 0160bc5..c9c5a18 100644 --- a/src/app/components/workspace/workspace-details/workspace-details.ts +++ b/src/app/components/workspace/workspace-details/workspace-details.ts @@ -4,11 +4,12 @@ import { ActivatedRoute, RouterModule, RouterLink, RouterOutlet } from '@angular import { WorkspaceService } from '../../../services/workspace'; import { Workspace } from '../../../models/workspace'; import { Observable } from 'rxjs'; +import { AppNavComponent } from '../../app-nav/app-nav'; @Component({ selector: 'app-workspace-details', standalone: true, - imports: [CommonModule, RouterModule, RouterLink, RouterOutlet], + imports: [CommonModule, RouterModule, RouterLink, RouterOutlet, AppNavComponent], templateUrl: './workspace-details.html', styleUrls: ['./workspace-details.css'] })