first commit

This commit is contained in:
2026-10-03 13:40:26 +02:00
parent 732a5b5a16
commit 566f415fa7
555 changed files with 62704 additions and 0 deletions

View File

@@ -0,0 +1,47 @@
import { provideHttpClient, withFetch } from '@angular/common/http';
import {
ApplicationConfig,
provideBrowserGlobalErrorListeners,
} from '@angular/core';
import {
provideRouter,
withComponentInputBinding,
withRouterConfig,
} from '@angular/router';
import { provideIdentityInfrastructure } from '@task-stream/web/identity/infrastructure';
import {
apiConfigFromLocation,
provideApiConfig,
provideGraphql,
} from '@task-stream/web/shared/infrastructure';
import { providePiUi, ToastService } from '@task-stream/web/shared/ui';
import { Notifications } from '@task-stream/web/tracker/application';
import { provideTrackerInfrastructure } from '@task-stream/web/tracker/infrastructure';
import { appRoutes } from './app.routes';
/*
* Composition root of the web client:
* presenters (routes/pages) → application (NgRx signal stores, events) → domain ports ← infrastructure (GraphQL/HTTP)
*/
export const appConfig: ApplicationConfig = {
providers: [
provideBrowserGlobalErrorListeners(),
provideRouter(
appRoutes,
// Route params / query params straight into component inputs (`key`, `issueKey`, `returnUrl`…).
withComponentInputBinding(),
withRouterConfig({ paramsInheritanceStrategy: 'always' }),
),
provideHttpClient(withFetch()),
providePiUi(),
// API: same origin, proxied in development (proxy.conf.json) and by nginx in Docker.
provideApiConfig(apiConfigFromLocation()),
provideGraphql(),
// Ports → adapters
provideIdentityInfrastructure(),
provideTrackerInfrastructure(),
{ provide: Notifications, useExisting: ToastService },
],
};

View File

@@ -0,0 +1,26 @@
import { authGuard } from '@task-stream/web/identity/presenters';
import { appRoutes } from './app.routes';
describe('appRoutes', () => {
it('keeps sign-in public and everything else behind the auth guard', () => {
const paths = appRoutes.map((route) => route.path);
expect(paths).toEqual(expect.arrayContaining(['login', 'auth/callback', '', '**']));
const app = appRoutes.find((route) => route.path === '' && route.children);
expect(app?.canActivate).toContain(authGuard);
expect(app?.children?.map((route) => route.path)).toEqual(['', 'p/:key']);
});
it('exposes every project section', () => {
const project = appRoutes.find((route) => route.path === '')?.children?.find((route) => route.path === 'p/:key');
expect(project?.children?.map((route) => route.path)).toEqual([
'',
'board',
'issues/:issueKey',
'releases',
'dashboard',
'activity',
'members',
]);
});
});

View File

@@ -0,0 +1,12 @@
import { Route } from '@angular/router';
import {
authGuard,
identityRoutes,
} from '@task-stream/web/identity/presenters';
import { trackerRoutes } from '@task-stream/web/tracker/presenters';
export const appRoutes: Route[] = [
...identityRoutes,
{ path: '', canActivate: [authGuard], children: trackerRoutes },
{ path: '**', redirectTo: '' },
];

39
apps/web/src/app/app.ts Normal file
View File

@@ -0,0 +1,39 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
import { AuthFacade } from '@task-stream/web/identity/application';
import { UserMenuComponent } from '@task-stream/web/identity/presenters';
import { AppShellComponent } from '@task-stream/web/shared/ui';
@Component({
selector: 'ts-root',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
RouterOutlet,
RouterLink,
RouterLinkActive,
AppShellComponent,
UserMenuComponent,
],
template: `
<ts-app-shell>
@if (auth.isAuthenticated()) {
<a
shellNav
class="ts-nav-link"
routerLink="/"
routerLinkActive="is-active"
[routerLinkActiveOptions]="{ exact: true }"
>
Proyectos
</a>
}
@if (auth.isAuthenticated()) {
<ts-user-menu shellUser />
}
<router-outlet />
</ts-app-shell>
`,
})
export class App {
protected readonly auth = inject(AuthFacade);
}