Merge pull request #22 in TEA/dafa-web-monorepo from feature/TV-252-and-TV-255-login-sidor to develop

Squashed commit of the following:

commit 39d7130c131ac7a7dda050e5894f4762e841627f
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Mon Jun 14 08:40:44 2021 +0200

    Update mock-login.component.html

commit 6012a7f526e3b9410a364c39a74e6c9a9ae2426a
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Wed Jun 9 10:59:35 2021 +0200

    Fixat enligt kommentarer

commit 3c76368b772f37498e419299554bcb4b5f519164
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Wed Jun 9 10:03:49 2021 +0200

    Bytt ut jwt-referenser

commit 853387f8cc18e38387bed50ed2d65342ec68dcc2
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Wed Jun 9 09:50:16 2021 +0200

    merge with develop

commit 553431a880f97eda948b13a2b967c8dee4cf9dd3
Merge: 8b1cca3 48801a9
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Wed Jun 9 09:46:17 2021 +0200

    Merge branch 'develop' into feature/TV-252-and-TV-255-login-sidor

    # Conflicts:
    #	apps/dafa-web/src/app/data/models/employee.model.ts
    #	apps/dafa-web/src/app/data/models/participant.model.ts
    #	apps/dafa-web/src/app/data/models/sort.model.ts
    #	apps/dafa-web/src/app/pages/administration/pages/employee-card/employee-card.component.ts
    #	apps/dafa-web/src/app/pages/administration/pages/employees/components/employees-list/employees-list.component.ts
    #	apps/dafa-web/src/app/pages/administration/pages/employees/employees.component.html
    #	apps/dafa-web/src/app/services/api/employee.service.ts
    #	apps/dafa-web/src/app/services/api/participants.service.ts
    #	apps/dafa-web/src/app/services/api/user.service.ts
    #	apps/dafa-web/src/app/utils/sort.util.ts
    #	mock-api/dafa-web/server.js

commit 8b1cca3553be37cb9bf68f67ae4612095e2b67d8
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Tue Jun 8 16:44:07 2021 +0200

    fix basic styling at login and logout pages

commit cdad17716bac1510d34c9d4bc8653455b6c16d24
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Tue Jun 8 16:34:12 2021 +0200

    Bryter ut logged-in-shell

commit bf294eefe448d3c708c6299154f376e264b53c81
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Tue Jun 8 16:14:18 2021 +0200

    extract logged in shell to own component

commit b44e6f0dc6e45f4f27fb4375ecfc59bc35ba745c
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Tue Jun 8 16:14:03 2021 +0200

    auth interceptor

commit 1540759b97afe18b47d86ea5f33d5cc29663ba3a
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Tue Jun 8 15:05:41 2021 +0200

    lägg till skelett för ciam landing sida och en mock-login-sida

commit d211f7cdb46bea0d9ac1d6400c7171b924ddff77
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Tue Jun 8 15:03:59 2021 +0200

    return singular token

commit e0e725cee778d488fd3ec467d28592c1c8e9e3eb
Author: Daniel Appelgren <daniel.appelgren@arbetsformedlingen.se>
Date:   Tue Jun 8 14:20:36 2021 +0200

    add auth check and an API to get the token http://localhost:4200/api/get-token?code=auth_code_from_CIAM_with_all_permissions

commit 4055d3a14eda9737ef76ed5e85ea35480e19e71c
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Mon Jun 7 15:20:26 2021 +0200

    updates after PR comments

commit f515ed6d06087f62de7522745691429d7ca91153
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Mon Jun 7 12:07:50 2021 +0200

    Now using Sort interface again

commit 5c793a5a7579a520c3792bb3d13c00bb68dbfcd4
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Mon Jun 7 11:54:27 2021 +0200

    Fixed bug showing wrong amount in pagination component

commit 7c55751147e05c1279b75356dc143b069e63e6b2
Merge: 11eab63 a701888
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Mon Jun 7 11:42:59 2021 +0200

    Updated after merge with develop

commit 11eab6330191a140c2cfd7094838495793e02719
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Mon Jun 7 11:23:52 2021 +0200

    Added functionality to sort on different columns

commit f13422a2aa53a69a243f32f9cd0b7ed6bd3441fc
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Fri Jun 4 11:40:22 2021 +0200

    Fixed other mappings after changes in the mock-api

commit ba2d3200167281422354f5e3cfdf7720444a9c4c
Merge: 6232b32 d91b3e6
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Fri Jun 4 10:00:00 2021 +0200

    Added paging functionality

commit 6232b3274ff73f2da929342a244fbc87430b796f
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date:   Fri Jun 4 09:25:01 2021 +0200

    Added meta model and changed services to adapt new API data structure

... and 1 more commit
This commit is contained in:
Daniel Appelgren
2021-06-14 08:41:01 +02:00
parent 48801a93a0
commit c0202ccd78
58 changed files with 693 additions and 164 deletions

View File

@@ -1,59 +1,81 @@
import { NgModule } from '@angular/core';
import { ExtraOptions, RouterModule, Routes } from '@angular/router';
import { environment } from '@dafa-environment';
const routes: Routes = [
{
path: '',
data: { title: '' },
loadChildren: () => import('./pages/start/start.module').then(m => m.StartModule),
loadChildren: () => import('./pages/start/start.module').then(m => m.StartModule)
},
{
path: 'administration',
data: { title: 'Administration' },
loadChildren: () => import('./pages/administration/administration.module').then(m => m.AdministrationModule),
loadChildren: () => import('./pages/administration/administration.module').then(m => m.AdministrationModule)
},
{
path: 'mina-deltagare',
data: { title: 'Mina deltagare' },
loadChildren: () => import('./pages/participants/participants.module').then(m => m.ParticipantsModule),
loadChildren: () => import('./pages/participants/participants.module').then(m => m.ParticipantsModule)
},
{
path: 'avrop',
data: { title: 'Avrop' },
loadChildren: () => import('./pages/call-off/call-off.module').then(m => m.CallOffModule),
loadChildren: () => import('./pages/call-off/call-off.module').then(m => m.CallOffModule)
},
{
path: 'meddelanden',
data: { title: 'Meddelanden' },
loadChildren: () => import('./pages/messages/messages.module').then(m => m.MessagesModule),
loadChildren: () => import('./pages/messages/messages.module').then(m => m.MessagesModule)
},
{
path: 'statistik',
data: { title: 'Statistik' },
loadChildren: () => import('./pages/statistics/statistics.module').then(m => m.StatisticsModule),
loadChildren: () => import('./pages/statistics/statistics.module').then(m => m.StatisticsModule)
},
{
path: 'installningar',
data: { title: 'Inställningar' },
loadChildren: () => import('./pages/settings/settings.module').then(m => m.SettingsModule),
loadChildren: () => import('./pages/settings/settings.module').then(m => m.SettingsModule)
},
{
path: 'releases',
data: { title: 'Releases' },
loadChildren: () => import('./pages/releases/releases.module').then(m => m.ReleasesModule),
loadChildren: () => import('./pages/releases/releases.module').then(m => m.ReleasesModule)
},
{
path: 'ciam-landing',
data: { title: 'Ciam landing page' },
loadChildren: () => import('./pages/ciam-landing/ciam-landing.module').then(m => m.CiamLandingModule)
},
{
path: 'logout',
data: { title: 'Ciam landing page' },
loadChildren: () => import('./pages/logout/logout.module').then(m => m.LogoutModule)
}
];
if (!environment.production) {
routes.push({
path: 'mock-login',
data: { title: 'Mock login' },
loadChildren: () => import('./pages/mock-login/mock-login.module').then(m => m.MockLoginModule)
});
}
routes.push({
path: '**',
data: { title: 'Sidan hittas inte' },
loadChildren: () => import('./pages/page-not-found/page-not-found.module').then(m => m.PageNotFoundModule),
},
];
loadChildren: () => import('./pages/page-not-found/page-not-found.module').then(m => m.PageNotFoundModule)
});
const options: ExtraOptions = {
useHash: false,
useHash: false
};
@NgModule({
imports: [RouterModule.forRoot(routes, options)],
exports: [RouterModule],
exports: [RouterModule]
})
export class AppRoutingModule {}
export class AppRoutingModule {
}

View File

@@ -1,19 +1,2 @@
<div class="dafa">
<dafa-skip-to-content mainContentId="dafa-main-content"></dafa-skip-to-content>
<header class="dafa__header">
<dafa-navigation [currentUser]="currentUser$ | async"></dafa-navigation>
</header>
<dafa-sidebar class="dafa__sidebar"></dafa-sidebar>
<main id="dafa-main-content" class="dafa__content">
<digi-ng-navigation-breadcrumbs
class="dafa__breadcrumbs"
[afItems]="breadcrumbsItems"
></digi-ng-navigation-breadcrumbs>
<router-outlet></router-outlet>
</main>
<dafa-footer class="dafa__footer"></dafa-footer>
</div>
<dafa-toast-list></dafa-toast-list>

View File

@@ -1,47 +0,0 @@
@import 'variables/navigation';
@import 'variables/breakpoints';
@import 'variables/gutters';
.dafa {
display: grid;
height: 100vh;
grid-template-columns: 15rem 1fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
'header header'
'sidebar content'
'footer footer';
// @media (min-width: $digi--layout--breakpoint--m) {
// grid-template-rows: $dafa__navigation-height-large 1fr auto;
// }
&__header {
grid-area: header;
position: sticky;
top: 0;
z-index: 1;
}
&__sidebar {
grid-area: sidebar;
background-color: var(--digi--ui--color--background--secondary);
border-right: 1px solid var(--digi--ui--color--background--off);
}
&__content {
grid-area: content;
padding: var(--digi--layout--gutter) $digi--layout--gutter--l $digi--layout--gutter--xxl;
}
&__breadcrumbs {
display: block;
margin-bottom: var(--digi--layout--gutter);
}
&__footer {
grid-area: footer;
background-color: var(--digi--ui--color--primary);
min-height: 10rem;
}
}

View File

@@ -1,12 +1,4 @@
import { NavigationBreadcrumbsItem } from '@af/digi-ng/_navigation/navigation-breadcrumbs';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { NavigationEnd, Router } from '@angular/router';
import { User } from '@dafa-models/user.model';
import { UserService } from '@dafa-services/api/user.service';
import { mapPathsToBreadcrumbs } from '@dafa-utils/map-paths-to-breadcrumbs.util';
import { BehaviorSubject, Observable } from 'rxjs';
import { filter } from 'rxjs/operators';
import { UnsubscribeDirective } from './directives/unsubscribe.directive';
@Component({
selector: 'dafa-root',
@@ -14,30 +6,6 @@ import { UnsubscribeDirective } from './directives/unsubscribe.directive';
styleUrls: ['./app.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppComponent extends UnsubscribeDirective {
private startBreadcrumb: NavigationBreadcrumbsItem = {
text: 'Start',
routerLink: '/',
};
private _breadcrumbsItems$ = new BehaviorSubject<NavigationBreadcrumbsItem[]>([this.startBreadcrumb]);
public currentUser$: Observable<User> = this.userService.currentUser$;
export class AppComponent {
get breadcrumbsItems(): NavigationBreadcrumbsItem[] {
return this._breadcrumbsItems$.getValue();
}
constructor(private router: Router, private userService: UserService) {
super();
super.unsubscribeOnDestroy(
this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(() => {
const urlTree = this.router.parseUrl(this.router.url);
urlTree.queryParams = {};
const paths = urlTree
.toString()
.split('/')
.filter(path => !!path);
this._breadcrumbsItems$.next(mapPathsToBreadcrumbs(paths, this.startBreadcrumb));
})
);
}
}

View File

@@ -1,5 +1,5 @@
import { DigiNgNavigationBreadcrumbsModule } from '@af/digi-ng/_navigation/navigation-breadcrumbs';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { HTTP_INTERCEPTORS, HttpClient, HttpClientModule } from '@angular/common/http';
import { ErrorHandler, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
@@ -12,6 +12,7 @@ import { NavigationModule } from './components/navigation/navigation.module';
import { SidebarModule } from './components/sidebar/sidebar.module';
import { SkipToContentModule } from './components/skip-to-content/skip-to-content.module';
import { ToastListModule } from './components/toast-list/toast-list.module';
import { AuthInterceptor } from '@dafa-services/api/auth.interceptor';
@NgModule({
declarations: [AppComponent],
@@ -33,6 +34,7 @@ import { ToastListModule } from './components/toast-list/toast-list.module';
provide: ErrorHandler,
useClass: CustomErrorHandler,
},
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
],
bootstrap: [AppComponent],
})

View File

@@ -0,0 +1,21 @@
<div class="dafa">
<dafa-skip-to-content mainContentId="dafa-main-content"></dafa-skip-to-content>
<header class="dafa__header">
<dafa-navigation *ngIf="isLoggedIn" [currentUser]="currentUser$ | async"></dafa-navigation>
</header>
<dafa-sidebar class="dafa__sidebar"></dafa-sidebar>
<main id="dafa-main-content" class="dafa__content">
<digi-ng-navigation-breadcrumbs
class="dafa__breadcrumbs"
[afItems]="breadcrumbsItems"
></digi-ng-navigation-breadcrumbs>
<ng-content></ng-content>
</main>
<dafa-footer class="dafa__footer"></dafa-footer>
</div>
<dafa-toast-list></dafa-toast-list>

View File

@@ -0,0 +1,47 @@
@import 'variables/navigation';
@import 'variables/breakpoints';
@import 'variables/gutters';
.dafa {
display: grid;
height: 100vh;
grid-template-columns: 15rem 1fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
'header header'
'sidebar content'
'footer footer';
// @media (min-width: $digi--layout--breakpoint--m) {
// grid-template-rows: $dafa__navigation-height-large 1fr auto;
// }
&__header {
grid-area: header;
position: sticky;
top: 0;
z-index: 1;
}
&__sidebar {
grid-area: sidebar;
background-color: var(--digi--ui--color--background--secondary);
border-right: 1px solid var(--digi--ui--color--background--off);
}
&__content {
grid-area: content;
padding: var(--digi--layout--gutter) $digi--layout--gutter--l $digi--layout--gutter--xxl;
}
&__breadcrumbs {
display: block;
margin-bottom: var(--digi--layout--gutter);
}
&__footer {
grid-area: footer;
background-color: var(--digi--ui--color--primary);
min-height: 10rem;
}
}

View File

@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { LoggedInShellComponent } from './logged-in-shell.component';
describe('LoggedInShellComponent', () => {
let component: LoggedInShellComponent;
let fixture: ComponentFixture<LoggedInShellComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ LoggedInShellComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(LoggedInShellComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,56 @@
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
import { NavigationBreadcrumbsItem } from '@af/digi-ng/_navigation/navigation-breadcrumbs';
import { BehaviorSubject, Observable } from 'rxjs';
import { User } from '@dafa-models/user.model';
import { NavigationEnd, Router } from '@angular/router';
import { UserService } from '@dafa-services/api/user.service';
import { filter } from 'rxjs/operators';
import { mapPathsToBreadcrumbs } from '@dafa-utils/map-paths-to-breadcrumbs.util';
import { UnsubscribeDirective } from '@dafa-directives/unsubscribe.directive';
import { AuthenticationService } from '@dafa-services/api/authentication.service';
import { environment } from '@dafa-environment';
@Component({
selector: 'dafa-logged-in-shell',
templateUrl: './logged-in-shell.component.html',
styleUrls: ['./logged-in-shell.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class LoggedInShellComponent extends UnsubscribeDirective {
private startBreadcrumb: NavigationBreadcrumbsItem = {
text: 'Start',
routerLink: '/',
};
private _breadcrumbsItems$ = new BehaviorSubject<NavigationBreadcrumbsItem[]>([this.startBreadcrumb]);
public currentUser$: Observable<User> = this.userService.currentUser$;
get breadcrumbsItems(): NavigationBreadcrumbsItem[] {
return this._breadcrumbsItems$.getValue();
}
get isLoggedIn() {
return this.authService.isLoggedIn();
}
constructor(private router: Router, private authService: AuthenticationService, private userService: UserService) {
super();
if(this.authService.isLoggedOut()) {
this.router.navigateByUrl(environment.loginUrl);
}
super.unsubscribeOnDestroy(
this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(() => {
const urlTree = this.router.parseUrl(this.router.url);
urlTree.queryParams = {};
const paths = urlTree
.toString()
.split('/')
.filter(path => !!path);
this._breadcrumbsItems$.next(mapPathsToBreadcrumbs(paths, this.startBreadcrumb));
})
);
}
}

View File

@@ -0,0 +1,30 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { LoggedInShellComponent } from './logged-in-shell.component';
import { SkipToContentModule } from '../skip-to-content/skip-to-content.module';
import { NavigationModule } from '../navigation/navigation.module';
import { SidebarModule } from '../sidebar/sidebar.module';
import { DigiNgNavigationBreadcrumbsModule } from '@af/digi-ng/_navigation/navigation-breadcrumbs';
import { FooterModule } from '../footer/footer.module';
import { ToastListModule } from '../toast-list/toast-list.module';
@NgModule({
imports: [
CommonModule,
SkipToContentModule,
NavigationModule,
SidebarModule,
DigiNgNavigationBreadcrumbsModule,
FooterModule,
ToastListModule
],
declarations: [
LoggedInShellComponent
],
exports: [
LoggedInShellComponent
],
})
export class LoggedInShellModule { }

View File

@@ -0,0 +1,29 @@
export interface AuthenticationResult {
idToken: string;
expiresIn: number;
}
export interface AuthenticationApiResponse {
data: AuthenticationApiResponseData;
}
export interface AuthenticationApiResponseData {
id: string;
access_token: string;
scope: string;
id_token: string;
token_type: string;
expires_in: number;
}
export function mapAuthApiResponseToAuthenticationResult(data: AuthenticationApiResponseData): AuthenticationResult {
const {
id_token,
expires_in } = data;
return {
idToken: id_token,
expiresIn: expires_in
};
}

View File

@@ -1,3 +1,5 @@
<dafa-logged-in-shell>
<section class="employee-card">
<digi-typography *ngIf="detailedEmployeeData$ | async as detailedEmployeeData; else loadingRef">
<h1>{{ detailedEmployeeData.fullName }}</h1>
@@ -102,3 +104,4 @@
<span class="dafa__a11y-sr-only">Info saknas</span>
</dd>
</ng-template>
</dafa-logged-in-shell>

View File

@@ -5,6 +5,7 @@ import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { LocalDatePipeModule } from '@dafa-shared/pipes/local-date/local-date.module';
import { EmployeeCardComponent } from './employee-card.component';
import { LoggedInShellModule } from '../../../../components/logged-in-shell/logged-in-shell.module';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
@@ -15,6 +16,7 @@ import { EmployeeCardComponent } from './employee-card.component';
DigiNgSkeletonBaseModule,
DigiNgLayoutExpansionPanelModule,
LocalDatePipeModule,
LoggedInShellModule
],
})
export class EmployeeCardModule {}

View File

@@ -1,4 +1,5 @@
<section class="employee-form">
<dafa-logged-in-shell>
<section class="employee-form">
<digi-typography>
<h1>Skapa nytt konto</h1>
<p>
@@ -117,3 +118,4 @@
</div>
</form>
</section>
</dafa-logged-in-shell>

View File

@@ -10,6 +10,7 @@ import { ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { LocalDatePipeModule } from '@dafa-shared/pipes/local-date/local-date.module';
import { EmployeeFormComponent } from './employee-form.component';
import { LoggedInShellModule } from '../../../../components/logged-in-shell/logged-in-shell.module';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
@@ -25,6 +26,7 @@ import { EmployeeFormComponent } from './employee-form.component';
DigiNgFormSelectModule,
DigiNgPopoverModule,
DigiNgFormCheckboxModule,
],
LoggedInShellModule
]
})
export class EmployeeFormModule {}

View File

@@ -1,4 +1,5 @@
<section class="employees">
<dafa-logged-in-shell>
<section class="employees">
<digi-typography>
<h1>Personal</h1>
<p>
@@ -35,4 +36,5 @@
<ng-template #loadingRef>
<digi-ng-skeleton-base [afCount]="3" afText="Laddar personal"></digi-ng-skeleton-base>
</ng-template>
</section>
</section>
</dafa-logged-in-shell>

View File

@@ -7,6 +7,7 @@ import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { EmployeesListModule } from './components/employees-list/employees-list.module';
import { EmployeesComponent } from './employees.component';
import { LoggedInShellModule } from '../../../../components/logged-in-shell/logged-in-shell.module';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
@@ -19,6 +20,7 @@ import { EmployeesComponent } from './employees.component';
EmployeesListModule,
DigiNgLinkButtonModule,
FormsModule,
],
LoggedInShellModule
]
})
export class EmployeesModule {}

View File

@@ -1 +1 @@
<section class="call-off">Avrop funkar!</section>
<dafa-logged-in-shell><section class="call-off">Avrop funkar!</section></dafa-logged-in-shell>

View File

@@ -2,9 +2,10 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { CallOffComponent } from './call-off.component';
import { LoggedInShellModule } from '../../components/logged-in-shell/logged-in-shell.module';
@NgModule({
declarations: [CallOffComponent],
imports: [CommonModule, RouterModule.forChild([{ path: '', component: CallOffComponent }])],
imports: [CommonModule, RouterModule.forChild([{ path: '', component: CallOffComponent }]), LoggedInShellModule]
})
export class CallOffModule {}

View File

@@ -0,0 +1,7 @@
<digi-typography>
<section class="releases">
<h1>Ciam landing</h1>
<p>Redirecting to /......</p>
</section>
</digi-typography>

View File

@@ -0,0 +1,27 @@
import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { CiamLandingComponent } from './ciam-landing.component';
describe('ReleasesComponent', () => {
let component: CiamLandingComponent;
let fixture: ComponentFixture<CiamLandingComponent>;
beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA],
declarations: [CiamLandingComponent],
}).compileComponents();
})
);
beforeEach(() => {
fixture = TestBed.createComponent(CiamLandingComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,38 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { first, map, switchMap } from 'rxjs/operators';
import { AuthenticationService } from '@dafa-services/api/authentication.service';
@Component({
selector: 'dafa-ciam-landing',
templateUrl: './ciam-landing.component.html',
styleUrls: ['./ciam-landing.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CiamLandingComponent implements OnInit {
constructor(private route: ActivatedRoute,
private router: Router,
private authenticationService: AuthenticationService
) {
}
ngOnInit() {
this.route.queryParams.pipe(
first(),
map(({code}) => {
if (!code) {
throw new Error('Expected CIAM to return \'code\' in queryparams.');
}
return code as string;
}),
switchMap(code => {
return this.authenticationService.login$(code)
}))
.subscribe(() => {
this.router.navigateByUrl('/')
})
;
}
}

View File

@@ -0,0 +1,16 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { CiamLandingComponent } from './ciam-landing.component';
import { DigiNgButtonModule } from '@af/digi-ng/_button/button';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [CiamLandingComponent],
imports: [
CommonModule,
RouterModule.forChild([{ path: '', component: CiamLandingComponent }]),
DigiNgButtonModule
]
})
export class CiamLandingModule {}

View File

@@ -0,0 +1,8 @@
<digi-typography>
<section class="logout">
<h1>Du har nu loggats ut</h1>
<p>
<a [routerLink]="loginUrl">Logga in</a>
</p>
</section>
</digi-typography>

View File

@@ -0,0 +1,3 @@
.logout {
margin: 3rem;
}

View File

@@ -0,0 +1,27 @@
import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { LogoutComponent } from './logout.component';
describe('ReleasesComponent', () => {
let component: LogoutComponent;
let fixture: ComponentFixture<LogoutComponent>;
beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA],
declarations: [LogoutComponent],
}).compileComponents();
})
);
beforeEach(() => {
fixture = TestBed.createComponent(LogoutComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,24 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { environment } from '@dafa-environment';
import { AuthenticationService } from '@dafa-services/api/authentication.service';
@Component({
selector: 'dafa-logout',
templateUrl: './logout.component.html',
styleUrls: ['./logout.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class LogoutComponent implements OnInit {
loginUrl = environment.loginUrl;
constructor(
private authenticationService: AuthenticationService
) {
}
ngOnInit(): void {
this.authenticationService.logout();
}
}

View File

@@ -0,0 +1,17 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { LogoutComponent } from './logout.component';
import { DigiNgButtonModule } from '@af/digi-ng/_button/button';
import { LoggedInShellModule } from '../../components/logged-in-shell/logged-in-shell.module';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [LogoutComponent],
imports: [
CommonModule,
RouterModule.forChild([{ path: '', component: LogoutComponent }]),
DigiNgButtonModule
]
})
export class LogoutModule {}

View File

@@ -1 +1,4 @@
<dafa-logged-in-shell>
<section class="messages">Meddelanden funkar!</section>
</dafa-logged-in-shell>

View File

@@ -2,9 +2,10 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { MessagesComponent } from './messages.component';
import { LoggedInShellModule } from '../../components/logged-in-shell/logged-in-shell.module';
@NgModule({
declarations: [MessagesComponent],
imports: [CommonModule, RouterModule.forChild([{ path: '', component: MessagesComponent }])],
imports: [CommonModule, RouterModule.forChild([{ path: '', component: MessagesComponent }]), LoggedInShellModule]
})
export class MessagesModule {}

View File

@@ -0,0 +1,9 @@
<digi-typography>
<section class="login__wrapper">
<h1>Mock login</h1>
<p>Simulera att man loggar in och blir redirectad till /ciam-landing med en Authorization-code som sedan används för att hämta authentication-token:</p>
<digi-ng-button [routerLink]="'/ciam-landing'" [queryParams]="{code: 'auth_code_from_CIAM_with_all_permissions'}">
Logga in med fullständiga rättigheter
</digi-ng-button>
</section>
</digi-typography>

View File

@@ -0,0 +1,3 @@
.login__wrapper {
margin: 5rem;
}

View File

@@ -0,0 +1,27 @@
import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { MockLoginComponent } from './mock-login.component';
describe('ReleasesComponent', () => {
let component: MockLoginComponent;
let fixture: ComponentFixture<MockLoginComponent>;
beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA],
declarations: [MockLoginComponent],
}).compileComponents();
})
);
beforeEach(() => {
fixture = TestBed.createComponent(MockLoginComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,9 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
@Component({
selector: 'dafa-mock-login',
templateUrl: './mock-login.component.html',
styleUrls: ['./mock-login.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MockLoginComponent {}

View File

@@ -0,0 +1,17 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { MockLoginComponent } from './mock-login.component';
import { DigiNgButtonModule } from '@af/digi-ng/_button/button';
import { LoggedInShellModule } from '../../components/logged-in-shell/logged-in-shell.module';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [MockLoginComponent],
imports: [
CommonModule,
RouterModule.forChild([{ path: '', component: MockLoginComponent }]),
DigiNgButtonModule
]
})
export class MockLoginModule {}

View File

@@ -1,3 +1,4 @@
<dafa-logged-in-shell>
<digi-typography>
<section class="page-not-found">
<h1>Oj då! Vi kan inte hitta sidan.</h1>
@@ -8,3 +9,4 @@
</a>
</section>
</digi-typography>
</dafa-logged-in-shell>

View File

@@ -2,10 +2,11 @@ import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { PageNotFoundComponent } from './page-not-found.component';
import { LoggedInShellModule } from '../../components/logged-in-shell/logged-in-shell.module';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [PageNotFoundComponent],
imports: [CommonModule, RouterModule.forChild([{ path: '', component: PageNotFoundComponent }])],
imports: [CommonModule, RouterModule.forChild([{ path: '', component: PageNotFoundComponent }]), LoggedInShellModule]
})
export class PageNotFoundModule {}

View File

@@ -1,3 +1,4 @@
<dafa-logged-in-shell>
<section class="participants">
<digi-typography>
<h1>Mina deltagare</h1>
@@ -40,3 +41,4 @@
<digi-ng-skeleton-base [afCount]="3" afText="Laddar deltagare"></digi-ng-skeleton-base>
</ng-template>
</section>
</dafa-logged-in-shell>

View File

@@ -5,6 +5,7 @@ import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { ParticipantsListModule } from './components/participants-list/participants-list.module';
import { ParticipantsComponent } from './participants.component';
import { LoggedInShellModule } from '../../components/logged-in-shell/logged-in-shell.module';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
@@ -15,6 +16,7 @@ import { ParticipantsComponent } from './participants.component';
FormsModule,
DigiNgSkeletonBaseModule,
ParticipantsListModule,
],
LoggedInShellModule
]
})
export class ParticipantsModule {}

View File

@@ -1,3 +1,4 @@
<dafa-logged-in-shell>
<digi-typography>
<section class="releases">
<h1>Releaser</h1>
@@ -8,3 +9,4 @@
<markdown src="assets/CHANGELOG.md"></markdown>
</section>
</digi-typography>
</dafa-logged-in-shell>

View File

@@ -3,6 +3,7 @@ import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { MarkdownModule } from 'ngx-markdown';
import { ReleasesComponent } from './releases.component';
import { LoggedInShellModule } from '../../components/logged-in-shell/logged-in-shell.module';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
@@ -11,6 +12,7 @@ import { ReleasesComponent } from './releases.component';
CommonModule,
MarkdownModule.forChild(),
RouterModule.forChild([{ path: '', component: ReleasesComponent }]),
],
LoggedInShellModule
]
})
export class ReleasesModule {}

View File

@@ -1 +1,3 @@
<dafa-logged-in-shell>
<section class="settings">Inställningar funkar!</section>
</dafa-logged-in-shell>

View File

@@ -2,9 +2,10 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { SettingsComponent } from './settings.component';
import { LoggedInShellModule } from '../../components/logged-in-shell/logged-in-shell.module';
@NgModule({
declarations: [SettingsComponent],
imports: [CommonModule, RouterModule.forChild([{ path: '', component: SettingsComponent }])],
imports: [CommonModule, RouterModule.forChild([{ path: '', component: SettingsComponent }]), LoggedInShellModule]
})
export class SettingsModule {}

View File

@@ -1,3 +1,4 @@
<dafa-logged-in-shell>
<section class="start">
<digi-typography>
<h1>Välkommen till Mina Sidor FA</h1>
@@ -32,3 +33,4 @@
</div>
</div>
</section>
</dafa-logged-in-shell>

View File

@@ -5,6 +5,7 @@ import { StartComponent } from './start.component';
import { DigiNgCardModule } from '@af/digi-ng/_card/card';
import { DigiNgNotificationAlertModule } from '@af/digi-ng/_notification/notification-alert';
import { DigiNgLinkInternalModule } from '@af/digi-ng/_link/link-internal';
import { LoggedInShellModule } from '../../components/logged-in-shell/logged-in-shell.module';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
@@ -15,6 +16,7 @@ import { DigiNgLinkInternalModule } from '@af/digi-ng/_link/link-internal';
DigiNgCardModule,
DigiNgNotificationAlertModule,
DigiNgLinkInternalModule,
],
LoggedInShellModule
]
})
export class StartModule {}

View File

@@ -1 +1,3 @@
<section class="statistics">Statistik funkar!</section>
<dafa-logged-in-shell>
<section class="statistics">Statistik funkar!</section>
</dafa-logged-in-shell>

View File

@@ -2,9 +2,10 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { StatisticsComponent } from './statistics.component';
import { LoggedInShellModule } from '../../components/logged-in-shell/logged-in-shell.module';
@NgModule({
declarations: [StatisticsComponent],
imports: [CommonModule, RouterModule.forChild([{ path: '', component: StatisticsComponent }])],
imports: [CommonModule, RouterModule.forChild([{ path: '', component: StatisticsComponent }]), LoggedInShellModule]
})
export class StatisticsModule {}

View File

@@ -0,0 +1,25 @@
import { HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { AuthenticationService } from './authentication.service';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private auth: AuthenticationService) {
}
intercept(req: HttpRequest<any>, next: HttpHandler) {
const idToken = this.auth.getAuthorizationToken();
if (idToken) {
const cloned = req.clone({
headers: req.headers.set('Authorization', 'Bearer ' + idToken)
});
return next.handle(cloned);
}
else {
return next.handle(req);
}
}
}

View File

@@ -0,0 +1,67 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { environment } from '@dafa-environment';
import { Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import {
AuthenticationApiResponse,
AuthenticationResult,
mapAuthApiResponseToAuthenticationResult
} from '@dafa-models/authentication.model';
import { add, isBefore } from 'date-fns';
const API_HEADERS = { headers: environment.api.headers };
@Injectable({
providedIn: 'root'
})
export class AuthenticationService {
private static _authTokenApiUrl(code: string): string {
return `${environment.api.url}/get-token?code=${code}`;
};
private static _setSession(authenticationResult: AuthenticationResult): void {
const expiresAt = add(new Date(), { seconds: authenticationResult.expiresIn });
localStorage.setItem('id_token', authenticationResult.idToken);
localStorage.setItem('expires_at', JSON.stringify(expiresAt.valueOf()));
}
login$(authorizationCodeFromCiam: string): Observable<AuthenticationResult> {
return this.httpClient
.get<AuthenticationApiResponse>(AuthenticationService._authTokenApiUrl(authorizationCodeFromCiam), API_HEADERS)
.pipe(
map(response => mapAuthApiResponseToAuthenticationResult(response.data)),
tap(authenticationResult => {
AuthenticationService._setSession(authenticationResult);
})
);
}
isLoggedIn(): boolean {
return isBefore(new Date(), this.getExpiration());
}
isLoggedOut(): boolean {
return !this.isLoggedIn();
}
getAuthorizationToken(): string {
return localStorage.getItem('id_token');
}
getExpiration(): number {
const expiration = localStorage.getItem('expires_at');
const expiresAt = JSON.parse(expiration);
return expiresAt;
}
constructor(private httpClient: HttpClient) {
}
logout(): void {
localStorage.removeItem('id_token');
localStorage.removeItem('expires_at');
}
}

View File

@@ -1,4 +1,5 @@
export const environment = {
loginUrl: 'https://ciam-test.arbetsformedlingen.se:8443/uas/oauth2/authorization?response_type=code&scope=openid&redirect_uri=https://localhost:4200/ciam-landing&client_id=5d08c2e4-763e-42f6-b858-24e4773bb83d',
production: false,
api: {
url: '/api',

View File

@@ -1,4 +1,5 @@
export const environment = {
loginUrl: 'https://ciam.arbetsformedlingen.se/',
production: true,
api: {
url: '/api',

View File

@@ -1,4 +1,5 @@
export const environment = {
loginUrl: 'mock-login',
production: false,
api: {
url: '/api',

View File

@@ -0,0 +1,11 @@
export const authTokens =
{
'auth_code_from_CIAM_with_all_permissions': {
'access_token': 'eyJjdHkiOiJKV1QiLCJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiemlwIjoiREVGIiwiaXNzIjoiNWQwOGMyZTQtNzYzZS00MmY2LWI4NTgtMjRlNDc3M2JiODNkIn0..nRvvEEjT1ZID5k0x-hf5oA.ZG0e16C7qIAgxKCemu6OppwyKSkCfJ9pnb-9kcTYa0ikntd5IYiF7hLmG5sUK9gmE-BBTeYd2CJ-YgG3HBfqvAmEnlHu1Nr-KjupR6z4fxvxkhrwuDCEPFMYElBsnwiMHyO3jdKQ4E9ET5eIDWwq3cVGIOj3dvrn-T87KS4_sLomOsY1NQvYgQrmjhmSqQXyRFKf53_nwjm6GcMPuz3z5JMv0i7Uy0qGC-OozmpG5O_CQjLRIP26J7N-yYtltWGkzgEbF7sCnTP4Xw6YOUXMGGqEbTYCsZxTfgkDjXUzfwq4M8TyjJ4fAAIJp_BU6tZRXuMdq4jlVFMDL0aubC_lqg.3XiXLQ5TqUryZ0R-562K7g',
'scope': 'openid',
'id_token': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjhJQWUyXzRjRE5JOEZHdjZSYVlOY2hYdDRJUSJ9.eyJzdWIiOiJjbj0wYzE1YmQxNy05NjM0LTQxZjAtYmJhYy0zNjJmY2NiNmRjMzQsb3U9VXNlcnMsb3U9ZUlETSBVc2Vycyxjbj1VYmlsb2dpbixkYz1jaWFtLXRlc3QsZGM9YXJiZXRzZm9ybWVkbGluZ2VuLGRjPXNlIiwiaXNzIjoiaHR0cHM6Ly9jaWFtLXRlc3QuYXJiZXRzZm9ybWVkbGluZ2VuLnNlOjg0NDMvdWFzIiwiYXVkIjpbIjVkMDhjMmU0LTc2M2UtNDJmNi1iODU4LTI0ZTQ3NzNiYjgzZCJdLCJleHAiOjE2MjMxNDkwMTYsImlhdCI6MTYyMzE0NTQyOSwiYXV0aF90aW1lIjoxNjIzMTQ1NDE2LCJhY3IiOiIyIiwiYW1yIjpbImh0dHBzOi8vY2lhbS10ZXN0LmFyYmV0c2Zvcm1lZGxpbmdlbi5zZTo4NDQzL3Vhcy9zYW1sMi9uYW1lcy9hYy9wYXNzd29yZC4yIl0sImF6cCI6IjVkMDhjMmU0LTc2M2UtNDJmNi1iODU4LTI0ZTQ3NzNiYjgzZCIsInNlc3Npb25faW5kZXgiOiJfYTM5NTM1MjlmYjk1N2Q0MTU4MzM4ZDI3MGJlMDQxNDVkNTNhYjkxOCIsIm1pbm9yaW5mb2F0dHJpYnV0ZSI6IlRob21hcyJ9.M9Y74kj4h1M7ONsfyOAn0cAe5uGwO5JYzmqTMsykmxJeSeWYZDP8u4KA9F6mO9rdpnjH9AAU0oEBtkxe14xMJIBVbtaljQuwuZSQ4mFYaJDiEG-NfEsmt6-WXK6hoNrH32qAWZ_fFNRTljwvqD0wUOH5jxBsRJlhcv9JKt6FQElChjMhuNHAX9M2f7DK17bx-VFhMkbwwmpc12dwEYIE1ejfrzBabhDAqysRCGV_TiWEZmKzPAPO-xYHOFQHeEwQZhsMPF8g96blqHIPDpgDlH20KwvysYqunJmlj8HkjzFXKbMHN-zkb-yJa22ioluhgcfCGIvroFZgo3o1zd0DPA',
'token_type': 'Bearer',
'expires_in': 3600
}
};

View File

@@ -7,6 +7,7 @@ import languages from './languages.js';
import organizations from './organizations.js';
import participants from './participants.js';
import services from './services.js';
import { authTokens } from './auth-tokens.js';
const generatedEmployees = employees.generate(50);
@@ -22,6 +23,7 @@ const apiData = {
})),
currentUser: currentUser.generate(),
authorizations: authorizations.generate(),
getTokenFullAccess: authTokens.auth_code_from_CIAM_with_all_permissions
};
fs.writeFileSync('api.json', JSON.stringify(apiData, null, '\t'));

View File

@@ -19,10 +19,17 @@ server.use(
'*limit=*': '$1_limit=$2',
'*sort=*': '$1_sort=$2',
'*order=*': '$1_order=$2',
'/get-token?code=auth_code_from_CIAM_with_all_permissions': '/getTokenFullAccess',
})
);
router.render = (req, res) => {
// all paths except getToken requires Authorization header.
if (!req._parsedUrl.pathname.includes('getToken') && !req.headers.authorization) {
return res.status(401).jsonp({ error: "No valid access-token" });
}
const params = new URLSearchParams(req._parsedUrl.query);
// Add createdAt to the body

18
package-lock.json generated
View File

@@ -23,6 +23,7 @@
"@digi/core": "^9.1.0",
"@digi/styles": "^6.0.2",
"@nrwl/angular": "11.5.1",
"date-fns": "^2.22.1",
"ngx-markdown": "^11.1.3",
"rxjs": "~6.6.3",
"tslib": "^2.0.0",
@@ -8479,11 +8480,16 @@
}
},
"node_modules/date-fns": {
"version": "2.21.1",
"resolved": "http://nexus.arbetsformedlingen.se/repository/npm/date-fns/-/date-fns-2.21.1.tgz",
"integrity": "sha512-m1WR0xGiC6j6jNFAyW4Nvh4WxAi4JF4w9jRJwSI8nBmNcyZXPcP9VUQG+6gHQXAmqaGEKDKhOqAtENDC941UkA==",
"version": "2.22.1",
"resolved": "http://nexus.arbetsformedlingen.se/repository/npm/date-fns/-/date-fns-2.22.1.tgz",
"integrity": "sha512-yUFPQjrxEmIsMqlHhAhmxkuH769baF21Kk+nZwZGyrMoyLA+LugaQtC0+Tqf9CBUUULWwUJt6Q5ySI3LJDDCGg==",
"license": "MIT",
"engines": {
"node": ">=0.11"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/date-fns"
}
},
"node_modules/dateformat": {
@@ -39666,9 +39672,9 @@
}
},
"date-fns": {
"version": "2.21.1",
"resolved": "http://nexus.arbetsformedlingen.se/repository/npm/date-fns/-/date-fns-2.21.1.tgz",
"integrity": "sha512-m1WR0xGiC6j6jNFAyW4Nvh4WxAi4JF4w9jRJwSI8nBmNcyZXPcP9VUQG+6gHQXAmqaGEKDKhOqAtENDC941UkA=="
"version": "2.22.1",
"resolved": "http://nexus.arbetsformedlingen.se/repository/npm/date-fns/-/date-fns-2.22.1.tgz",
"integrity": "sha512-yUFPQjrxEmIsMqlHhAhmxkuH769baF21Kk+nZwZGyrMoyLA+LugaQtC0+Tqf9CBUUULWwUJt6Q5ySI3LJDDCGg=="
},
"dateformat": {
"version": "3.0.3",

View File

@@ -54,6 +54,7 @@
"@digi/core": "^9.1.0",
"@digi/styles": "^6.0.2",
"@nrwl/angular": "11.5.1",
"date-fns": "^2.22.1",
"ngx-markdown": "^11.1.3",
"rxjs": "~6.6.3",
"tslib": "^2.0.0",