Skip to content

Commit

Permalink
Add conditional navigation for legal documents; open URLs directly fr…
Browse files Browse the repository at this point in the history
…om overview or display markdown content in detail view (#113)

* feat: add conditional navigation for legal documents

---------

Co-authored-by: Kunz Robert <robert_kunz@sluz.ch>
  • Loading branch information
KunzRobert and Kunz Robert authored Jun 19, 2024
1 parent d77bca4 commit 952649d
Show file tree
Hide file tree
Showing 15 changed files with 183 additions and 108 deletions.
2 changes: 2 additions & 0 deletions apps/admin-ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import { firebaseConfig, metaConfig } from './config.ts';
import { usersCollection } from './collections/users/users.tsx';
import { coursesCollection } from './collections/courses/courses.tsx';
import { generalMainCollection } from './collections/general/general-main.tsx';
import { legalCollection } from './collections/legal/legal.tsx';

const rootCollections = [
usersCollection,
coursesCollection,
generalMainCollection,
legalCollection,
];

export default function App() {
Expand Down
62 changes: 62 additions & 0 deletions apps/admin-ui/src/collections/legal/legal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { buildCollection } from 'firecms';

type Legal = {
title: string;
description: string;
markdown?: string;
url?: string;
displayType: 'markdown' | 'url';
};

export const legalCollection = buildCollection<Legal>({
name: 'Legal',
singularName: 'Legal',
path: 'legal',
icon: 'Gavel',
defaultSize: 's',
permissions: () => ({
read: true,
edit: true,
create: true,
delete: true,
}),
properties: {
title: {
name: 'Title',
validation: { required: true, min: 3, max: 100 },
dataType: 'string',
},
description: {
name: 'Description',
validation: { required: true, min: 3, max: 500 },
dataType: 'string',
},
markdown: {
name: 'Markdown',
validation: {
required: (values: Legal) => values.displayType === 'markdown',
min: 3,
max: 100000,
},
dataType: 'string',
markdown: true,
},
url: {
name: 'URL',
validation: {
required: (values: Legal) => values.displayType === 'url',
url: true,
},
dataType: 'string',
},
displayType: {
name: 'Display Type',
validation: { required: true },
dataType: 'string',
enumValues: {
markdown: 'Markdown',
url: 'URL',
},
},
},
});
1 change: 0 additions & 1 deletion apps/course/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 18 additions & 18 deletions apps/course/src/app/core/data/legal.service.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
import { inject, Injectable } from '@angular/core';
import { Legal, LegalDocument } from '../../types/legal.type';
import * as legalData from '../../../assets/legal/legal.json';
import { ToastService } from '../feedback/toast.service';
import { ToastType } from '../../types/feedback/toast.types';
import { Injectable } from '@angular/core';
import { LegalDocument } from '../../types/legal.type';
import { AngularFirestore } from '@angular/fire/compat/firestore';
import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';

@Injectable({
providedIn: 'root',
})
export class LegalService {
public legals: LegalDocument[] = (legalData as Legal).files;
constructor(private firestore: AngularFirestore) {}

getLegalDocumentByFileId(file: string): LegalDocument | undefined {
return this.legals.find((legal) => {
if (legal) {
return legal.file === file;
} else {
inject(ToastService).showToast(
'Could not find the legal document, try again.',
ToastType.Error,
);
}
return undefined;
});
getLegalDocuments(): Observable<LegalDocument[]> {
return this.firestore
.collection<LegalDocument>('legal')
.valueChanges({ idField: 'id' });
}

getLegalDocumentById(id: string): Observable<LegalDocument | undefined> {
return this.firestore
.collection<LegalDocument>('legal')
.doc<LegalDocument>(id)
.valueChanges()
.pipe(map((document) => document ?? undefined));
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
<article class="flex justify-center c-min-h-80">
<div class="mx-2 my-3 sm:mx-5 sm:my-9 w-full max-w-6xl">
<div class="mx-3 sm:mx-5 my-3 sm:my-9">
<app-legal-info [legal]="legal" />
<app-legal-info [legal]="safeLegalDocument"></app-legal-info>
<div class="divider"></div>
<app-legal-markdown-renderer [legal]="legal" />
@if (safeLegalDocument?.displayType === 'markdown') {
<app-legal-markdown-renderer
[legal]="safeLegalDocument"
></app-legal-markdown-renderer>
}
</div>
</div>
</article>
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,10 @@ import { Component, OnInit } from '@angular/core';
import { LegalInfoComponent } from './legal-info/legal-info.component';
import { LegalMarkdownRendererComponent } from './legal-markdown-renderer/legal-markdown-renderer.component';
import { LegalDocument } from '../../../types/legal.type';
import { ActivatedRoute, Router } from '@angular/router';
import { Observable, of } from 'rxjs';
import { ActivatedRoute } from '@angular/router';
import { LegalService } from '../../../core/data/legal.service';
import { Title } from '@angular/platform-browser';
import { environment } from '../../../../environments/environment';
import { AppComponent } from '../../../app.component';
import { ToastService } from '../../../core/feedback/toast.service';
import { ToastType } from '../../../types/feedback/toast.types';
import { map, switchMap } from 'rxjs/operators';

@Component({
selector: 'app-legal-detail',
Expand All @@ -18,38 +15,31 @@ import { ToastType } from '../../../types/feedback/toast.types';
styleUrl: './legal-detail.component.scss',
})
export class LegalDetailComponent implements OnInit {
public legal: LegalDocument | undefined;
legalDocument$: Observable<LegalDocument | undefined> = of(undefined);
safeLegalDocument: LegalDocument | undefined = undefined;
isLoading = true;

constructor(
private route: ActivatedRoute,
private router: Router,
private legalService: LegalService,
private title: Title,
private toastService: ToastService,
) {}

ngOnInit(): void {
const legalId = this.route.snapshot.paramMap.get('legalId');
if (legalId) {
this.legal = this.legalService.getLegalDocumentByFileId(legalId);
if (this.legal) {
{
this.title.setTitle(
this.legal.name +
' - ' +
environment.metaConfig.title +
' - ' +
AppComponent.chorizo.title,
);
this.isLoading = false;
this.legalDocument$ = this.route.paramMap.pipe(
switchMap((params) => {
const legalId = params.get('legalId'); // Use the ID from the route parameter
if (legalId) {
return this.legalService
.getLegalDocumentById(legalId)
.pipe(map((document) => document ?? undefined));
} else {
return of(undefined);
}
} else {
this.router.navigate(['/404']).then(() => {
this.toastService.showToast(
'The document could not be found.',
ToastType.Error,
);
});
}
}
}),
);
this.legalDocument$.subscribe((document) => {
this.safeLegalDocument = document;
});
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<h1 class="text-4xl font-semibold subpixel-antialiased dark:text-gray-100">
{{ legal?.name }}
{{ legal?.title }}
</h1>
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, Input } from '@angular/core';
import { Component, Input, OnInit } from '@angular/core';
import { LegalDocument } from '../../../../types/legal.type';

@Component({
Expand All @@ -8,6 +8,10 @@ import { LegalDocument } from '../../../../types/legal.type';
templateUrl: './legal-info.component.html',
styleUrl: './legal-info.component.scss',
})
export class LegalInfoComponent {
@Input() legal: LegalDocument | undefined;
export class LegalInfoComponent implements OnInit {
@Input() public legal: LegalDocument | undefined;

constructor() {}

ngOnInit(): void {}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
@if (isLoading) {
<app-loading-bars />
} @else {
<markdown
[src]="'assets/legal/content/' + legal?.file + '.md'"
class="markdown-content"
/>
@if (legal) {
<markdown [data]="legal.markdown" class="markdown-content"></markdown>
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
CLIPBOARD_OPTIONS,
ClipboardButtonComponent,
MarkdownComponent,
MarkdownService,
provideMarkdown,
} from 'ngx-markdown';
import { HttpClient } from '@angular/common/http';
Expand Down Expand Up @@ -39,14 +38,8 @@ import { LegalDocument } from '../../../../types/legal.type';
})
export class LegalMarkdownRendererComponent implements OnInit {
@Input() public legal: LegalDocument | undefined;
public isLoading: boolean = true;

constructor(private markdownService: MarkdownService) {}
constructor() {}

ngOnInit() {
const mdPath: string = `assets/legal/content/${this.legal?.file}.md`;
this.markdownService.getSource(mdPath).subscribe(() => {
this.isLoading = false;
});
}
ngOnInit(): void {}
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,45 @@
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200"
rel="stylesheet"/>

<div class="mx-1 md:mx-3 lg:mx-32 xl:mx-60 min-h-1-2">
@if (isLoading) {
<app-loading-bars />
} @else if (!isLoading) {
<app-loading-bars/>
} @else {
<div class="navbar bg-base-100 border-b-2">
<div class="items-center flex-1">
<button class="btn btn-ghost text-2xl btn-lg">Legal</button>
</div>
</div>
<div class="flex flex-col xl:flex-row flex-wrap justify-center">
@for (document of legalDocuments; track document.file) {
<div
role="link"
[routerLink]="document.file"
class="w-full xl:w-5/12 mb-2 mt-2 card lg:card-side bg-base-100 shadow-xl transition hover:opacity-75 hover:scale-95 hover:cursor-pointer"
>
<div class="card-body text-pretty">
<h2 class="card-title">{{ document.name }}</h2>
<p>{{ document.description }}</p>
@for (document of (legalDocuments$ | async); track legalDocuments$) {
@if (document.displayType === 'markdown') {
<div
role="link"
[routerLink]="['/l', document.id]"
class="w-full xl:w-5/12 mb-2 mt-2 card lg:card-side bg-base-100 shadow-xl transition hover:opacity-75 hover:scale-95 hover:cursor-pointer"
>
<div class="card-body text-pretty">
<h2 class="card-title">
{{ document.title }}
</h2>
<p>{{ document.description }}</p>
</div>
</div>
</div>
} @else {
<div
role="link"
(click)="openUrl(document.url)"
class="w-full xl:w-5/12 mb-2 mt-2 card lg:card-side bg-base-100 shadow-xl transition hover:opacity-75 hover:scale-95 hover:cursor-pointer"
>
<div class="card-body text-pretty">
<h2 class="card-title">
{{ document.title }}
<span class="material-symbols-outlined">link</span>
</h2>
<p>{{ document.description }}</p>
</div>
</div>
}
@if ($index % 2 === 0) {
<div class="my-1.5 xl:m-8"></div>
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.material-symbols-outlined {
font-variation-settings: 'FILL' 0,
'wght' 400,
'GRAD' 0,
'opsz' 24;
}
Original file line number Diff line number Diff line change
@@ -1,37 +1,33 @@
import { Component, OnInit } from '@angular/core';
import { LegalService } from '../../../core/data/legal.service';
import { LegalDocument } from '../../../types/legal.type';
import { Title } from '@angular/platform-browser';
import { RouterLink } from '@angular/router';
import { environment } from '../../../../environments/environment';
import { AppComponent } from '../../../app.component';
import { LoadingBarsComponent } from '../../../shared/feedback/loading-bars/loading-bars.component';
import { Observable } from 'rxjs';
import { AsyncPipe } from '@angular/common';

@Component({
selector: 'app-legal-overview',
standalone: true,
imports: [RouterLink, LoadingBarsComponent],
imports: [RouterLink, LoadingBarsComponent, AsyncPipe],
templateUrl: './legal-overview.component.html',
styleUrl: './legal-overview.component.scss',
})
export class LegalOverviewComponent implements OnInit {
legalDocuments: LegalDocument[] = [];
public isLoading = true;
legalDocuments$: Observable<LegalDocument[]>;
isLoading = true;

constructor(
private legalService: LegalService,
private titleService: Title,
) {
this.titleService.setTitle(
'Legal - ' +
environment.metaConfig.title +
' - ' +
AppComponent.chorizo.title,
);
constructor(private legalService: LegalService) {
this.legalDocuments$ = this.legalService.getLegalDocuments();
}

ngOnInit() {
this.legalDocuments = this.legalService.legals;
ngOnInit(): void {
this.isLoading = false;
}

openUrl(url: string | undefined): void {
if (url) {
window.open(url, '_blank');
}
}
}
Loading

0 comments on commit 952649d

Please sign in to comment.