From a9fb52fd4dec2781a7c1d14ec3e0baa9b8b64b6c Mon Sep 17 00:00:00 2001 From: tw-blue Date: Sat, 29 Nov 2025 17:47:36 +0100 Subject: [PATCH] frontend skeleton --- packages/frontend/.gitignore | 2 +- packages/frontend/angular.json | 17 +- packages/frontend/public/favicon.ico | Bin 15086 -> 766 bytes .../frontend/public/silent-check-sso.html | 7 + packages/frontend/src/app/app.config.ts | 48 ++- packages/frontend/src/app/app.html | 349 +----------------- packages/frontend/src/app/app.routes.ts | 51 ++- packages/frontend/src/app/app.scss | 10 + packages/frontend/src/app/app.ts | 12 +- .../src/app/components/home/home.html | 1 + .../src/app/components/home/home.scss | 0 .../src/app/components/home/home.spec.ts | 23 ++ .../frontend/src/app/components/home/home.ts | 11 + .../src/app/components/login/login.html | 1 + .../src/app/components/login/login.scss | 0 .../src/app/components/login/login.spec.ts | 23 ++ .../src/app/components/login/login.ts | 15 + .../components/missing-page/missing-page.html | 1 + .../components/missing-page/missing-page.scss | 0 .../missing-page/missing-page.spec.ts | 23 ++ .../components/missing-page/missing-page.ts | 11 + .../src/app/components/test/test.html | 1 + .../src/app/components/test/test.scss | 0 .../src/app/components/test/test.spec.ts | 23 ++ .../frontend/src/app/components/test/test.ts | 11 + .../src/app/services/authentication.spec.ts | 16 + .../src/app/services/authentication.ts | 84 +++++ packages/frontend/src/index.html | 30 +- packages/frontend/src/robots.txt | 2 + packages/frontend/src/styles.scss | 38 ++ 30 files changed, 433 insertions(+), 377 deletions(-) create mode 100644 packages/frontend/public/silent-check-sso.html create mode 100644 packages/frontend/src/app/components/home/home.html create mode 100644 packages/frontend/src/app/components/home/home.scss create mode 100644 packages/frontend/src/app/components/home/home.spec.ts create mode 100644 packages/frontend/src/app/components/home/home.ts create mode 100644 packages/frontend/src/app/components/login/login.html create mode 100644 packages/frontend/src/app/components/login/login.scss create mode 100644 packages/frontend/src/app/components/login/login.spec.ts create mode 100644 packages/frontend/src/app/components/login/login.ts create mode 100644 packages/frontend/src/app/components/missing-page/missing-page.html create mode 100644 packages/frontend/src/app/components/missing-page/missing-page.scss create mode 100644 packages/frontend/src/app/components/missing-page/missing-page.spec.ts create mode 100644 packages/frontend/src/app/components/missing-page/missing-page.ts create mode 100644 packages/frontend/src/app/components/test/test.html create mode 100644 packages/frontend/src/app/components/test/test.scss create mode 100644 packages/frontend/src/app/components/test/test.spec.ts create mode 100644 packages/frontend/src/app/components/test/test.ts create mode 100644 packages/frontend/src/app/services/authentication.spec.ts create mode 100644 packages/frontend/src/app/services/authentication.ts create mode 100644 packages/frontend/src/robots.txt diff --git a/packages/frontend/.gitignore b/packages/frontend/.gitignore index df7fb2a..0be734a 100644 --- a/packages/frontend/.gitignore +++ b/packages/frontend/.gitignore @@ -43,4 +43,4 @@ __screenshots__/ Thumbs.db ### -src/generated-api/ +src/app/generated-api/ diff --git a/packages/frontend/angular.json b/packages/frontend/angular.json index 92bcada..227f981 100644 --- a/packages/frontend/angular.json +++ b/packages/frontend/angular.json @@ -21,9 +21,7 @@ "builder": "@angular/build:application", "options": { "browser": "src/main.ts", - "polyfills": [ - "zone.js" - ], + "polyfills": ["zone.js"], "tsConfig": "tsconfig.app.json", "inlineStyleLanguage": "scss", "assets": [ @@ -32,9 +30,7 @@ "input": "public" } ], - "styles": [ - "src/styles.scss" - ] + "styles": ["src/styles.scss"] }, "configurations": { "production": { @@ -78,10 +74,7 @@ "test": { "builder": "@angular/build:karma", "options": { - "polyfills": [ - "zone.js", - "zone.js/testing" - ], + "polyfills": ["zone.js", "zone.js/testing"], "tsConfig": "tsconfig.spec.json", "inlineStyleLanguage": "scss", "assets": [ @@ -90,9 +83,7 @@ "input": "public" } ], - "styles": [ - "src/styles.scss" - ] + "styles": ["src/styles.scss"] } } } diff --git a/packages/frontend/public/favicon.ico b/packages/frontend/public/favicon.ico index 57614f9c967596fad0a3989bec2b1deff33034f6..22b8e32c02d1c66a5a2be27e2a2a2bb6de52fb6f 100644 GIT binary patch literal 766 zcmaJYx_}(#5xm|;RzZegL4slt z0wHk9O<9-hA)pzQ+*l;A^$+;Ji&gZxP)kqWeG# zcnC!M|0M4cDWKc!QVuL($n%^Ou#8$MT1G6F6e2?StDJZzZ*FXf7t!LsYz>Js&y`lC z-$qzhdGEzK*N+hmBQpQ|a_lSpJ#u&KM|`ixsTkiUN{qcY;V7@2o`S1yd^TWC3%1H3 zyqs}o#(dD@tg?MF!aM{&D|lb@v|;DW6YxpFJT|L_;69EV$c#zQjK}aro>utr0rJ0c z5pC^CDvi!ZKhna&&(3-W)iI|}*Bo<1o_YIT-r?eXWuYD&yUaYp#g*P4GUrreQ)1rt zu7Q5-e0SEF(hH2}5pIBp8k&~^oIB_Os zuf)EvP`im95AgazSkyweZiYYOv;xfl literal 15086 zcmd^G33O9Omi+`8$@{|M-I6TH3wzF-p5CV8o}7f~KxR60LK+ApEFB<$bcciv%@SmA zV{n>g85YMFFeU*Uvl=i4v)C*qgnb;$GQ=3XTe9{Y%c`mO%su)noNCCQ*@t1WXn|B(hQ7i~ zrUK8|pUkD6#lNo!bt$6)jR!&C?`P5G(`e((P($RaLeq+o0Vd~f11;qB05kdbAOm?r zXv~GYr_sibQO9NGTCdT;+G(!{4Xs@4fPak8#L8PjgJwcs-Mm#nR_Z0s&u?nDX5^~@ z+A6?}g0|=4e_LoE69pPFO`yCD@BCjgKpzMH0O4Xs{Ahc?K3HC5;l=f zg>}alhBXX&);z$E-wai+9TTRtBX-bWYY@cl$@YN#gMd~tM_5lj6W%8ah4;uZ;jP@Q zVbuel1rPA?2@x9Y+u?e`l{Z4ngfG5q5BLH5QsEu4GVpt{KIp1?U)=3+KQ;%7ec8l* zdV=zZgN5>O3G(3L2fqj3;oBbZZw$Ij@`Juz@?+yy#OPw)>#wsTewVgTK9BGt5AbZ&?K&B3GVF&yu?@(Xj3fR3n+ZP0%+wo)D9_xp>Z$`A4 zfV>}NWjO#3lqumR0`gvnffd9Ka}JJMuHS&|55-*mCD#8e^anA<+sFZVaJe7{=p*oX zE_Uv?1>e~ga=seYzh{9P+n5<+7&9}&(kwqSaz;1aD|YM3HBiy<))4~QJSIryyqp| z8nGc(8>3(_nEI4n)n7j(&d4idW1tVLjZ7QbNLXg;LB ziHsS5pXHEjGJZb59KcvS~wv;uZR-+4qEqow`;JCfB*+b^UL^3!?;-^F%yt=VjU|v z39SSqKcRu_NVvz!zJzL0CceJaS6%!(eMshPv_0U5G`~!a#I$qI5Ic(>IONej@aH=f z)($TAT#1I{iCS4f{D2+ApS=$3E7}5=+y(rA9mM#;Cky%b*Gi0KfFA`ofKTzu`AV-9 znW|y@19rrZ*!N2AvDi<_ZeR3O2R{#dh1#3-d%$k${Rx42h+i&GZo5!C^dSL34*AKp z27mTd>k>?V&X;Nl%GZ(>0s`1UN~Hfyj>KPjtnc|)xM@{H_B9rNr~LuH`Gr5_am&Ep zTjZA8hljNj5H1Ipm-uD9rC}U{-vR!eay5&6x6FkfupdpT*84MVwGpdd(}ib)zZ3Ky z7C$pnjc82(W_y_F{PhYj?o!@3__UUvpX)v69aBSzYj3 zdi}YQkKs^SyXyFG2LTRz9{(w}y~!`{EuAaUr6G1M{*%c+kP1olW9z23dSH!G4_HSK zzae-DF$OGR{ofP*!$a(r^5Go>I3SObVI6FLY)N@o<*gl0&kLo-OT{Tl*7nCz>Iq=? zcigIDHtj|H;6sR?or8Wd_a4996GI*CXGU}o;D9`^FM!AT1pBY~?|4h^61BY#_yIfO zKO?E0 zJ{Pc`9rVEI&$xxXu`<5E)&+m(7zX^v0rqofLs&bnQT(1baQkAr^kEsk)15vlzAZ-l z@OO9RF<+IiJ*O@HE256gCt!bF=NM*vh|WVWmjVawcNoksRTMvR03H{p@cjwKh(CL4 z7_PB(dM=kO)!s4fW!1p0f93YN@?ZSG` z$B!JaAJCtW$B97}HNO9(x-t30&E}Mo1UPi@Av%uHj~?T|!4JLwV;KCx8xO#b9IlUW zI6+{a@Wj|<2Y=U;a@vXbxqZNngH8^}LleE_4*0&O7#3iGxfJ%Id>+sb;7{L=aIic8 z|EW|{{S)J-wr@;3PmlxRXU8!e2gm_%s|ReH!reFcY8%$Hl4M5>;6^UDUUae?kOy#h zk~6Ee_@ZAn48Bab__^bNmQ~+k=02jz)e0d9Z3>G?RGG!65?d1>9}7iG17?P*=GUV-#SbLRw)Hu{zx*azHxWkGNTWl@HeWjA?39Ia|sCi{e;!^`1Oec zb>Z|b65OM*;eC=ZLSy?_fg$&^2xI>qSLA2G*$nA3GEnp3$N-)46`|36m*sc#4%C|h zBN<2U;7k>&G_wL4=Ve5z`ubVD&*Hxi)r@{4RCDw7U_D`lbC(9&pG5C*z#W>8>HU)h z!h3g?2UL&sS!oY5$3?VlA0Me9W5e~V;2jds*fz^updz#AJ%G8w2V}AEE?E^=MK%Xt z__Bx1cr7+DQmuHmzn*|hh%~eEc9@m05@clWfpEFcr+06%0&dZJH&@8^&@*$qR@}o3 z@Tuuh2FsLz^zH+dN&T&?0G3I?MpmYJ;GP$J!EzjeM#YLJ!W$}MVNb0^HfOA>5Fe~UNn%Zk(PT@~9}1dt)1UQ zU*B5K?Dl#G74qmg|2>^>0WtLX#Jz{lO4NT`NYB*(L#D|5IpXr9v&7a@YsGp3vLR7L zHYGHZg7{ie6n~2p$6Yz>=^cEg7tEgk-1YRl%-s7^cbqFb(U7&Dp78+&ut5!Tn(hER z|Gp4Ed@CnOPeAe|N>U(dB;SZ?NU^AzoD^UAH_vamp6Ws}{|mSq`^+VP1g~2B{%N-!mWz<`)G)>V-<`9`L4?3dM%Qh6<@kba+m`JS{Ya@9Fq*m6$$ zA1%Ogc~VRH33|S9l%CNb4zM%k^EIpqY}@h{w(aBcJ9c05oiZx#SK9t->5lSI`=&l~ z+-Ic)a{FbBhXV$Xt!WRd`R#Jk-$+_Z52rS>?Vpt2IK<84|E-SBEoIw>cs=a{BlQ7O z-?{Fy_M&84&9|KM5wt~)*!~i~E=(6m8(uCO)I=)M?)&sRbzH$9Rovzd?ZEY}GqX+~ zFbEbLz`BZ49=2Yh-|<`waK-_4!7`ro@zlC|r&I4fc4oyb+m=|c8)8%tZ-z5FwhzDt zL5kB@u53`d@%nHl0Sp)Dw`(QU&>vujEn?GPEXUW!Wi<+4e%BORl&BIH+SwRcbS}X@ z01Pk|vA%OdJKAs17zSXtO55k!;%m9>1eW9LnyAX4uj7@${O6cfii`49qTNItzny5J zH&Gj`e}o}?xjQ}r?LrI%FjUd@xflT3|7LA|ka%Q3i}a8gVm<`HIWoJGH=$EGClX^C0lysQJ>UO(q&;`T#8txuoQ_{l^kEV9CAdXuU1Ghg8 zN_6hHFuy&1x24q5-(Z7;!poYdt*`UTdrQOIQ!2O7_+AHV2hgXaEz7)>$LEdG z<8vE^Tw$|YwZHZDPM!SNOAWG$?J)MdmEk{U!!$M#fp7*Wo}jJ$Q(=8>R`Ats?e|VU?Zt7Cdh%AdnfyN3MBWw{ z$OnREvPf7%z6`#2##_7id|H%Y{vV^vWXb?5d5?a_y&t3@p9t$ncHj-NBdo&X{wrfJ zamN)VMYROYh_SvjJ=Xd!Ga?PY_$;*L=SxFte!4O6%0HEh%iZ4=gvns7IWIyJHa|hT z2;1+e)`TvbNb3-0z&DD_)Jomsg-7p_Uh`wjGnU1urmv1_oVqRg#=C?e?!7DgtqojU zWoAB($&53;TsXu^@2;8M`#z{=rPy?JqgYM0CDf4v@z=ZD|ItJ&8%_7A#K?S{wjxgd z?xA6JdJojrWpB7fr2p_MSsU4(R7=XGS0+Eg#xR=j>`H@R9{XjwBmqAiOxOL` zt?XK-iTEOWV}f>Pz3H-s*>W z4~8C&Xq25UQ^xH6H9kY_RM1$ch+%YLF72AA7^b{~VNTG}Tj#qZltz5Q=qxR`&oIlW Nr__JTFzvMr^FKp4S3v*( diff --git a/packages/frontend/public/silent-check-sso.html b/packages/frontend/public/silent-check-sso.html new file mode 100644 index 0000000..b3bd540 --- /dev/null +++ b/packages/frontend/public/silent-check-sso.html @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/frontend/src/app/app.config.ts b/packages/frontend/src/app/app.config.ts index d953f4c..2cd232f 100644 --- a/packages/frontend/src/app/app.config.ts +++ b/packages/frontend/src/app/app.config.ts @@ -1,12 +1,52 @@ -import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core'; -import { provideRouter } from '@angular/router'; +import { + ApplicationConfig, + provideBrowserGlobalErrorListeners, + provideZoneChangeDetection, +} from '@angular/core'; +import { + PreloadAllModules, + provideRouter, + withComponentInputBinding, + withPreloading, +} from '@angular/router'; + +import { + AutoRefreshTokenService, + createInterceptorCondition, + INCLUDE_BEARER_TOKEN_INTERCEPTOR_CONFIG, + IncludeBearerTokenCondition, + provideKeycloak, + UserActivityService, + withAutoRefreshToken, +} from 'keycloak-angular'; import { routes } from './app.routes'; export const appConfig: ApplicationConfig = { providers: [ + provideRouter(routes, withPreloading(PreloadAllModules), withComponentInputBinding()), provideBrowserGlobalErrorListeners(), provideZoneChangeDetection({ eventCoalescing: true }), - provideRouter(routes) - ] + provideKeycloak({ + config: { + url: 'https://keycloak.toni714.de:8443/', + realm: 'taekwondo', + clientId: 'angular-frontend', + }, + initOptions: { + onLoad: 'check-sso', + silentCheckSsoRedirectUri: window.location.origin + '/silent-check-sso.html', + }, + features: [withAutoRefreshToken({ sessionTimeout: 600000 })], + providers: [AutoRefreshTokenService, UserActivityService], + }), + { + provide: INCLUDE_BEARER_TOKEN_INTERCEPTOR_CONFIG, + useValue: [ + createInterceptorCondition({ + urlPattern: /^https:\/\/tkd-api\.toni714\.de.*$/, + }), + ], + }, + ], }; diff --git a/packages/frontend/src/app/app.html b/packages/frontend/src/app/app.html index 7528372..942524b 100644 --- a/packages/frontend/src/app/app.html +++ b/packages/frontend/src/app/app.html @@ -1,342 +1,7 @@ - - - - - - - - - - - -
-
-
- -

Hello, {{ title() }}

-

Congratulations! Your app is running. 🎉

-
- -
-
- @for (item of [ - { title: 'Explore the Docs', link: 'https://angular.dev' }, - { title: 'Learn with Tutorials', link: 'https://angular.dev/tutorials' }, - { title: 'Prompt and best practices for AI', link: 'https://angular.dev/ai/develop-with-ai'}, - { title: 'CLI Docs', link: 'https://angular.dev/tools/cli' }, - { title: 'Angular Language Service', link: 'https://angular.dev/tools/language-service' }, - { title: 'Angular DevTools', link: 'https://angular.dev/tools/devtools' }, - ]; track item.title) { - - {{ item.title }} - - - - - } -
- -
-
-
- - - - - - - - - - - +@if(this.loggedIn()){ +
+ + Home +
+} + diff --git a/packages/frontend/src/app/app.routes.ts b/packages/frontend/src/app/app.routes.ts index dc39edb..2b8765d 100644 --- a/packages/frontend/src/app/app.routes.ts +++ b/packages/frontend/src/app/app.routes.ts @@ -1,3 +1,50 @@ -import { Routes } from '@angular/router'; +import { inject } from '@angular/core'; +import { + ActivatedRouteSnapshot, + CanActivateFn, + Router, + RouterStateSnapshot, + Routes, + UrlTree, +} from '@angular/router'; +import { AuthGuardData, createAuthGuard } from 'keycloak-angular'; +import { Login } from './components/login/login'; +import { MissingPage } from './components/missing-page/missing-page'; -export const routes: Routes = []; +export const roles = { + UserAdmin: 'useradmin', + MemberAdmin: 'memberadmin', +}; + +function requireRoles(roles: string[], any: boolean = false): CanActivateFn { + const isAllowed = async ( + _: ActivatedRouteSnapshot, + __: RouterStateSnapshot, + authData: AuthGuardData + ): Promise => { + const { authenticated, grantedRoles } = authData; + const allRoles = Object.values(grantedRoles).flat(); + + const hasRequiredRoles = any + ? roles.some((role) => allRoles.includes(role)) + : roles.every((role) => allRoles.includes(role)); + + if (authenticated && hasRequiredRoles) { + return true; + } + const router = inject(Router); + return router.parseUrl('/login'); + }; + + return createAuthGuard(isAllowed); +} + +export const routes: Routes = [ + { path: 'login', component: Login }, + { + path: '', + loadComponent: () => import('./components/home/home').then((mod) => mod.Home), + canActivate: [requireRoles([roles.MemberAdmin, roles.UserAdmin], true)], + }, + { path: '**', component: MissingPage }, +]; diff --git a/packages/frontend/src/app/app.scss b/packages/frontend/src/app/app.scss index e69de29..2e49617 100644 --- a/packages/frontend/src/app/app.scss +++ b/packages/frontend/src/app/app.scss @@ -0,0 +1,10 @@ +@media print { + .no-print { + display: none; + visibility: hidden; + } + + :not(.no-print) { + visibility: visible; + } +} diff --git a/packages/frontend/src/app/app.ts b/packages/frontend/src/app/app.ts index d560a9f..4640aab 100644 --- a/packages/frontend/src/app/app.ts +++ b/packages/frontend/src/app/app.ts @@ -1,12 +1,18 @@ import { Component, signal } from '@angular/core'; -import { RouterOutlet } from '@angular/router'; +import { MatButtonModule } from '@angular/material/button'; +import { RouterModule, RouterOutlet } from '@angular/router'; @Component({ selector: 'app-root', - imports: [RouterOutlet], + imports: [RouterOutlet, MatButtonModule, RouterModule], templateUrl: './app.html', - styleUrl: './app.scss' + styleUrl: './app.scss', }) export class App { + protected loggedIn = signal(false); protected readonly title = signal('frontend'); + + protected onLogOut() { + this.loggedIn.set(false); + } } diff --git a/packages/frontend/src/app/components/home/home.html b/packages/frontend/src/app/components/home/home.html new file mode 100644 index 0000000..5f2c53f --- /dev/null +++ b/packages/frontend/src/app/components/home/home.html @@ -0,0 +1 @@ +

home works!

diff --git a/packages/frontend/src/app/components/home/home.scss b/packages/frontend/src/app/components/home/home.scss new file mode 100644 index 0000000..e69de29 diff --git a/packages/frontend/src/app/components/home/home.spec.ts b/packages/frontend/src/app/components/home/home.spec.ts new file mode 100644 index 0000000..e2d9468 --- /dev/null +++ b/packages/frontend/src/app/components/home/home.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { Home } from './home'; + +describe('Home', () => { + let component: Home; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [Home] + }) + .compileComponents(); + + fixture = TestBed.createComponent(Home); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/packages/frontend/src/app/components/home/home.ts b/packages/frontend/src/app/components/home/home.ts new file mode 100644 index 0000000..fe83818 --- /dev/null +++ b/packages/frontend/src/app/components/home/home.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-home', + imports: [], + templateUrl: './home.html', + styleUrl: './home.scss', +}) +export class Home { + +} diff --git a/packages/frontend/src/app/components/login/login.html b/packages/frontend/src/app/components/login/login.html new file mode 100644 index 0000000..06d2bbd --- /dev/null +++ b/packages/frontend/src/app/components/login/login.html @@ -0,0 +1 @@ +

Weiterleitung zur Login-Seite erfolgt in Kürze...

diff --git a/packages/frontend/src/app/components/login/login.scss b/packages/frontend/src/app/components/login/login.scss new file mode 100644 index 0000000..e69de29 diff --git a/packages/frontend/src/app/components/login/login.spec.ts b/packages/frontend/src/app/components/login/login.spec.ts new file mode 100644 index 0000000..dd8bbb3 --- /dev/null +++ b/packages/frontend/src/app/components/login/login.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { Login } from './login'; + +describe('Login', () => { + let component: Login; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [Login] + }) + .compileComponents(); + + fixture = TestBed.createComponent(Login); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/packages/frontend/src/app/components/login/login.ts b/packages/frontend/src/app/components/login/login.ts new file mode 100644 index 0000000..7d1526b --- /dev/null +++ b/packages/frontend/src/app/components/login/login.ts @@ -0,0 +1,15 @@ +import { Component, inject } from '@angular/core'; +import { Authentication } from '../../services/authentication'; + +@Component({ + selector: 'app-login', + imports: [], + templateUrl: './login.html', + styleUrl: './login.scss', +}) +export class Login { + private readonly authentication = inject(Authentication); + public constructor() { + this.authentication.login(); + } +} diff --git a/packages/frontend/src/app/components/missing-page/missing-page.html b/packages/frontend/src/app/components/missing-page/missing-page.html new file mode 100644 index 0000000..5f1f15c --- /dev/null +++ b/packages/frontend/src/app/components/missing-page/missing-page.html @@ -0,0 +1 @@ +

missing-page works!

diff --git a/packages/frontend/src/app/components/missing-page/missing-page.scss b/packages/frontend/src/app/components/missing-page/missing-page.scss new file mode 100644 index 0000000..e69de29 diff --git a/packages/frontend/src/app/components/missing-page/missing-page.spec.ts b/packages/frontend/src/app/components/missing-page/missing-page.spec.ts new file mode 100644 index 0000000..34f9c4f --- /dev/null +++ b/packages/frontend/src/app/components/missing-page/missing-page.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MissingPage } from './missing-page'; + +describe('MissingPage', () => { + let component: MissingPage; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [MissingPage] + }) + .compileComponents(); + + fixture = TestBed.createComponent(MissingPage); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/packages/frontend/src/app/components/missing-page/missing-page.ts b/packages/frontend/src/app/components/missing-page/missing-page.ts new file mode 100644 index 0000000..945845f --- /dev/null +++ b/packages/frontend/src/app/components/missing-page/missing-page.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-missing-page', + imports: [], + templateUrl: './missing-page.html', + styleUrl: './missing-page.scss', +}) +export class MissingPage { + +} diff --git a/packages/frontend/src/app/components/test/test.html b/packages/frontend/src/app/components/test/test.html new file mode 100644 index 0000000..941f267 --- /dev/null +++ b/packages/frontend/src/app/components/test/test.html @@ -0,0 +1 @@ +

test works!

diff --git a/packages/frontend/src/app/components/test/test.scss b/packages/frontend/src/app/components/test/test.scss new file mode 100644 index 0000000..e69de29 diff --git a/packages/frontend/src/app/components/test/test.spec.ts b/packages/frontend/src/app/components/test/test.spec.ts new file mode 100644 index 0000000..a469f82 --- /dev/null +++ b/packages/frontend/src/app/components/test/test.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { Test } from './test'; + +describe('Test', () => { + let component: Test; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [Test] + }) + .compileComponents(); + + fixture = TestBed.createComponent(Test); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/packages/frontend/src/app/components/test/test.ts b/packages/frontend/src/app/components/test/test.ts new file mode 100644 index 0000000..2dd40d0 --- /dev/null +++ b/packages/frontend/src/app/components/test/test.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-test', + imports: [], + templateUrl: './test.html', + styleUrl: './test.scss', +}) +export class Test { + +} diff --git a/packages/frontend/src/app/services/authentication.spec.ts b/packages/frontend/src/app/services/authentication.spec.ts new file mode 100644 index 0000000..d3617bd --- /dev/null +++ b/packages/frontend/src/app/services/authentication.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { Authentication } from './authentication'; + +describe('Authentication', () => { + let service: Authentication; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(Authentication); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/packages/frontend/src/app/services/authentication.ts b/packages/frontend/src/app/services/authentication.ts new file mode 100644 index 0000000..eba2cca --- /dev/null +++ b/packages/frontend/src/app/services/authentication.ts @@ -0,0 +1,84 @@ +import { effect, inject, Injectable, signal } from '@angular/core'; +import { + KEYCLOAK_EVENT_SIGNAL, + KeycloakEventType, + ReadyArgs, + typeEventArgs, +} from 'keycloak-angular'; +import Keycloak, { KeycloakProfile } from 'keycloak-js'; + +export enum AuthenticationState { + Authenticated, + Unauthenticated, + Unknown, +} + +@Injectable({ + providedIn: 'root', +}) +export class Authentication { + private readonly keycloak = inject(Keycloak); + private readonly keycloakSignal = inject(KEYCLOAK_EVENT_SIGNAL); + private readonly _authenticationState = signal(AuthenticationState.Unknown); + private readonly _userInfo = signal(null); + public readonly authenticationState = this._authenticationState.asReadonly(); + public readonly userInfo = this._userInfo.asReadonly(); + + public constructor() { + effect(() => { + const event = this.keycloakSignal(); + switch (event?.type) { + case KeycloakEventType.Ready: + this._authenticationState.set( + typeEventArgs(event.args) + ? AuthenticationState.Authenticated + : AuthenticationState.Unauthenticated + ); + break; + case KeycloakEventType.AuthSuccess: + this._authenticationState.set(AuthenticationState.Authenticated); + break; + case KeycloakEventType.AuthLogout: + this._authenticationState.set(AuthenticationState.Unauthenticated); + break; + case KeycloakEventType.AuthError: + this._authenticationState.set(AuthenticationState.Unauthenticated); + break; + case KeycloakEventType.AuthRefreshError: + this._authenticationState.set(AuthenticationState.Unauthenticated); + break; + case KeycloakEventType.AuthRefreshSuccess: + this._authenticationState.set(AuthenticationState.Authenticated); + break; + case KeycloakEventType.TokenExpired: + this._authenticationState.set(AuthenticationState.Unauthenticated); + break; + default: + break; + } + }); + + effect(async () => { + const authenticationState = this.authenticationState(); + if (authenticationState !== AuthenticationState.Authenticated) { + this._userInfo.set(null); + } + const profile = await this.keycloak.loadUserProfile(); + this._userInfo.set(profile); + }); + } + + public async login(location?: string): Promise { + var redirectUri = location + ? `${window.location.origin}/${location}` + : `${window.location.origin}/login`; + return await this.keycloak.login({ + redirectUri: redirectUri, + locale: 'de-DE', + }); + } + + public async logout(): Promise { + return await this.keycloak.logout({ redirectUri: window.location.origin + '/login' }); + } +} diff --git a/packages/frontend/src/index.html b/packages/frontend/src/index.html index 3af61ec..8492554 100644 --- a/packages/frontend/src/index.html +++ b/packages/frontend/src/index.html @@ -1,13 +1,19 @@ - - - - - Frontend - - - - - - - + + + + + + 1.TC Chemnitz Mitgliederdatenbank + + + + + + + + + diff --git a/packages/frontend/src/robots.txt b/packages/frontend/src/robots.txt new file mode 100644 index 0000000..1f53798 --- /dev/null +++ b/packages/frontend/src/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: / diff --git a/packages/frontend/src/styles.scss b/packages/frontend/src/styles.scss index 90d4ee0..c3f88db 100644 --- a/packages/frontend/src/styles.scss +++ b/packages/frontend/src/styles.scss @@ -1 +1,39 @@ + +// Include theming for Angular Material with `mat.theme()`. +// This Sass mixin will define CSS variables that are used for styling Angular Material +// components according to the Material 3 design spec. +// Learn more about theming and how to use it for your application's +// custom components at https://material.angular.dev/guide/theming +@use '@angular/material' as mat; + +html { + @include mat.theme(( + color: ( + primary: mat.$cyan-palette, + tertiary: mat.$orange-palette, + ), + typography: Roboto, + density: 0, + )); +} + +body { + // Default the application to a light color theme. This can be changed to + // `dark` to enable the dark color theme, or to `light dark` to defer to the + // user's system settings. + color-scheme: light; + + // Set a default background, font and text colors for the application using + // Angular Material's system-level CSS variables. Learn more about these + // variables at https://material.angular.dev/guide/system-variables + background-color: var(--mat-sys-surface); + color: var(--mat-sys-on-surface); + font: var(--mat-sys-body-medium); + + // Reset the user agent margin. + margin: 0; +} /* You can add global styles to this file, and also import other style files */ + +html, body { height: 100%; } +body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }