first commit
This commit is contained in:
47
apps/web/src/app/app.config.ts
Normal file
47
apps/web/src/app/app.config.ts
Normal 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 },
|
||||
],
|
||||
};
|
||||
26
apps/web/src/app/app.routes.spec.ts
Normal file
26
apps/web/src/app/app.routes.spec.ts
Normal 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',
|
||||
]);
|
||||
});
|
||||
});
|
||||
12
apps/web/src/app/app.routes.ts
Normal file
12
apps/web/src/app/app.routes.ts
Normal 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
39
apps/web/src/app/app.ts
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user