From 0adf0a9bfcb96e0d6d93c09777243d441032d26f Mon Sep 17 00:00:00 2001 From: Flo Date: Sun, 25 Aug 2024 20:42:39 +0000 Subject: [PATCH] intermediate commit --- angular.json | 6 +- package.json | 4 +- src/app/app.component.ts | 11 ++- src/app/app.module.ts | 4 +- src/app/core/auth/auth.module.ts | 33 +++++++++ .../confirm-registration.component.html | 1 + .../confirm-registration.component.scss | 0 .../confirm-registration.component.ts | 10 +++ .../components/login/login.component.html | 24 +++++++ .../components/login/login.component.scss | 0 .../auth/components/login/login.component.ts | 35 ++++++++++ .../registration/registration.component.html | 1 + .../registration/registration.component.scss | 0 .../registration/registration.component.ts | 10 +++ .../core/components/home/home.component.html | 2 +- .../navigation/navigation.component.html | 52 +++++++++++++-- .../navigation/navigation.component.ts | 31 ++++++++- src/app/core/core.module.ts | 6 +- src/app/core/guards/auth.guard.ts | 26 ++++++++ .../core/models/login-request.model copy.ts | 8 +++ .../core/models/user-state-request.model.ts | 12 ++++ src/app/core/services/auth.service.ts | 56 ++++++++++++++++ src/app/core/services/request.service.ts | 56 +++++++++------- src/app/shared/shared.module.ts | 63 ------------------ src/assets/icon.ico | Bin 2296 -> 67646 bytes src/assets/icon.png | Bin 0 -> 4986 bytes src/favicon.ico | Bin 4286 -> 67646 bytes src/index.html | 5 +- src/styles.scss | 19 +++++- tailwind.config.js | 46 +++++++++++-- 30 files changed, 407 insertions(+), 114 deletions(-) create mode 100644 src/app/core/auth/auth.module.ts create mode 100644 src/app/core/auth/components/confirm-registration/confirm-registration.component.html create mode 100644 src/app/core/auth/components/confirm-registration/confirm-registration.component.scss create mode 100644 src/app/core/auth/components/confirm-registration/confirm-registration.component.ts create mode 100644 src/app/core/auth/components/login/login.component.html create mode 100644 src/app/core/auth/components/login/login.component.scss create mode 100644 src/app/core/auth/components/login/login.component.ts create mode 100644 src/app/core/auth/components/registration/registration.component.html create mode 100644 src/app/core/auth/components/registration/registration.component.scss create mode 100644 src/app/core/auth/components/registration/registration.component.ts create mode 100644 src/app/core/guards/auth.guard.ts create mode 100644 src/app/core/models/login-request.model copy.ts create mode 100644 src/app/core/models/user-state-request.model.ts create mode 100644 src/app/core/services/auth.service.ts create mode 100644 src/assets/icon.png diff --git a/angular.json b/angular.json index 4bd67bb..50745c6 100644 --- a/angular.json +++ b/angular.json @@ -47,10 +47,11 @@ "src/favicon.ico" ], "styles": [ - "@angular/material/prebuilt-themes/deeppurple-amber.css", "src/styles.scss" ], - "scripts": [] + "scripts": [ + "node_modules/flowbite/dist/flowbite.min.js" + ] }, "configurations": { "production": { @@ -110,7 +111,6 @@ "src/favicon.ico" ], "styles": [ - "@angular/material/prebuilt-themes/deeppurple-amber.scss", "src/styles.scss" ], "scripts": [] diff --git a/package.json b/package.json index 442d257..98dc225 100644 --- a/package.json +++ b/package.json @@ -15,10 +15,10 @@ "@angular/compiler": "^15.2.0", "@angular/core": "^15.2.0", "@angular/forms": "^15.2.0", - "@angular/material": "^15.2.9", "@angular/platform-browser": "^15.2.0", "@angular/platform-browser-dynamic": "^15.2.0", "@angular/router": "^15.2.0", + "flowbite": "^2.5.1", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.12.0" @@ -34,7 +34,7 @@ "karma-coverage": "~2.2.0", "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.0.0", - "tailwindcss": "^3.4.3", + "tailwindcss": "^3.4.10", "typescript": "~4.9.4" } } diff --git a/src/app/app.component.ts b/src/app/app.component.ts index fe8da85..eef36c1 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,9 +1,18 @@ import { Component, OnInit } from '@angular/core'; +import { AuthService } from './core/services/auth.service'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) -export class AppComponent { +export class AppComponent implements OnInit { + constructor( + private authService: AuthService + ) { + } + + ngOnInit(): void { + this.authService.readUserState(); + } } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index ad51b68..5c578f8 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -8,9 +8,11 @@ import { RouterModule, Routes } from '@angular/router'; import { SharedModule } from './shared/shared.module'; import { HomeComponent } from './core/components/home/home.component'; import { CoreModule } from './core/core.module'; +import { AuthGuard } from './core/guards/auth.guard'; const routes: Routes = [ - { path: 'home', component: HomeComponent }, + { path: 'auth', loadChildren: () => import('./core/auth/auth.module').then(m => m.AuthModule) }, + { path: 'home', component: HomeComponent, canActivate: [AuthGuard] }, { path: '', redirectTo: 'home', pathMatch: 'full' }, ]; diff --git a/src/app/core/auth/auth.module.ts b/src/app/core/auth/auth.module.ts new file mode 100644 index 0000000..82680d8 --- /dev/null +++ b/src/app/core/auth/auth.module.ts @@ -0,0 +1,33 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { LoginComponent } from './components/login/login.component'; +import { RegistrationComponent } from './components/registration/registration.component'; +import { ConfirmRegistrationComponent } from './components/confirm-registration/confirm-registration.component'; +import { RouterModule, Routes } from '@angular/router'; +import { ReactiveFormsModule } from '@angular/forms'; + +const routes: Routes = [ + { path: 'login', component: LoginComponent }, + { path: 'registration', component: RegistrationComponent }, + { path: 'registration/:id', component: ConfirmRegistrationComponent }, + { path: '', redirectTo: 'login', pathMatch: 'full' }, +]; + +@NgModule({ + declarations: [ + LoginComponent, + RegistrationComponent, + ConfirmRegistrationComponent + ], + exports: [ + LoginComponent, + RegistrationComponent, + ConfirmRegistrationComponent + ], + imports: [ + RouterModule.forChild(routes), + CommonModule, + ReactiveFormsModule + ] +}) +export class AuthModule { } diff --git a/src/app/core/auth/components/confirm-registration/confirm-registration.component.html b/src/app/core/auth/components/confirm-registration/confirm-registration.component.html new file mode 100644 index 0000000..6465355 --- /dev/null +++ b/src/app/core/auth/components/confirm-registration/confirm-registration.component.html @@ -0,0 +1 @@ +

confirm-registration works!

diff --git a/src/app/core/auth/components/confirm-registration/confirm-registration.component.scss b/src/app/core/auth/components/confirm-registration/confirm-registration.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/core/auth/components/confirm-registration/confirm-registration.component.ts b/src/app/core/auth/components/confirm-registration/confirm-registration.component.ts new file mode 100644 index 0000000..977a152 --- /dev/null +++ b/src/app/core/auth/components/confirm-registration/confirm-registration.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-confirm-registration', + templateUrl: './confirm-registration.component.html', + styleUrls: ['./confirm-registration.component.scss'] +}) +export class ConfirmRegistrationComponent { + +} diff --git a/src/app/core/auth/components/login/login.component.html b/src/app/core/auth/components/login/login.component.html new file mode 100644 index 0000000..a8bcc95 --- /dev/null +++ b/src/app/core/auth/components/login/login.component.html @@ -0,0 +1,24 @@ +
+
+ +
+

+ Beekeeper +

+

+ Anmeldung +

+
+ +
+
+ + +
+
+ + +

Neu hier? Jetzt registrieren!

+
+ +
diff --git a/src/app/core/auth/components/login/login.component.scss b/src/app/core/auth/components/login/login.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/core/auth/components/login/login.component.ts b/src/app/core/auth/components/login/login.component.ts new file mode 100644 index 0000000..d750d33 --- /dev/null +++ b/src/app/core/auth/components/login/login.component.ts @@ -0,0 +1,35 @@ +import { Component } from '@angular/core'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { Router } from '@angular/router'; +import { filter } from 'rxjs'; +import { AuthService } from 'src/app/core/services/auth.service'; + +@Component({ + selector: 'app-login', + templateUrl: './login.component.html', + styleUrls: ['./login.component.scss'] +}) +export class LoginComponent { + loginForm = new FormGroup({ + identifier: new FormControl('', [Validators.required]), + password: new FormControl('', [Validators.required]), + }); + + constructor( + private authService: AuthService, + private router: Router + ) { + this.authService.currentState$.pipe( + filter(state => state !== undefined) + ).subscribe(state => { + this.router.navigateByUrl('/home'); + }); + } + + login(): void { + this.authService.login({ + identifier: this.loginForm.value.identifier!, + password: this.loginForm.value.password! + }); + } +} diff --git a/src/app/core/auth/components/registration/registration.component.html b/src/app/core/auth/components/registration/registration.component.html new file mode 100644 index 0000000..d2f7abe --- /dev/null +++ b/src/app/core/auth/components/registration/registration.component.html @@ -0,0 +1 @@ +

registration works!

diff --git a/src/app/core/auth/components/registration/registration.component.scss b/src/app/core/auth/components/registration/registration.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/core/auth/components/registration/registration.component.ts b/src/app/core/auth/components/registration/registration.component.ts new file mode 100644 index 0000000..2330af4 --- /dev/null +++ b/src/app/core/auth/components/registration/registration.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-registration', + templateUrl: './registration.component.html', + styleUrls: ['./registration.component.scss'] +}) +export class RegistrationComponent { + +} diff --git a/src/app/core/components/home/home.component.html b/src/app/core/components/home/home.component.html index bd16462..d696ec9 100644 --- a/src/app/core/components/home/home.component.html +++ b/src/app/core/components/home/home.component.html @@ -1,5 +1,5 @@
- Home +
\ No newline at end of file diff --git a/src/app/core/components/navigation/navigation.component.html b/src/app/core/components/navigation/navigation.component.html index a658057..83bac92 100644 --- a/src/app/core/components/navigation/navigation.component.html +++ b/src/app/core/components/navigation/navigation.component.html @@ -1,5 +1,47 @@ - -

- Navigation -

-
\ No newline at end of file + + + diff --git a/src/app/core/components/navigation/navigation.component.ts b/src/app/core/components/navigation/navigation.component.ts index 6c2fd97..481ce3a 100644 --- a/src/app/core/components/navigation/navigation.component.ts +++ b/src/app/core/components/navigation/navigation.component.ts @@ -1,9 +1,36 @@ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; +import { initFlowbite } from 'flowbite'; +import { AuthService } from '../../services/auth.service'; +import { UserStateResponse } from '../../models/user-state-request.model'; +import { Router } from '@angular/router'; +import { filter, map } from 'rxjs'; @Component({ selector: 'app-navigation', templateUrl: './navigation.component.html', styleUrls: ['./navigation.component.scss'], }) -export class NavigationComponent { +export class NavigationComponent implements OnInit { + + state: UserStateResponse | undefined | null; + + constructor( + private authService: AuthService, + private router: Router, + ) { + this.state = this.authService.currentState$.value; + this.authService.currentState$.pipe( + filter(state => state === undefined) + ).subscribe( state => + this.router.navigateByUrl('/auth/login') + ); + } + + ngOnInit(): void { + initFlowbite(); + } + + logout(): void { + this.authService.logout(); + } } diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 2d58380..7b4ea58 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -2,6 +2,9 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { HomeComponent } from './components/home/home.component'; import { NavigationComponent } from './components/navigation/navigation.component'; +import { AuthGuard } from './guards/auth.guard'; +import { AuthService } from './services/auth.service'; +import { RequestService } from './services/request.service'; @@ -10,6 +13,7 @@ import { NavigationComponent } from './components/navigation/navigation.componen exports: [HomeComponent, NavigationComponent], imports: [ CommonModule - ] + ], + providers: [AuthGuard, AuthService, RequestService] }) export class CoreModule { } diff --git a/src/app/core/guards/auth.guard.ts b/src/app/core/guards/auth.guard.ts new file mode 100644 index 0000000..22b69c6 --- /dev/null +++ b/src/app/core/guards/auth.guard.ts @@ -0,0 +1,26 @@ +import { Injectable } from "@angular/core"; +import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree } from "@angular/router"; +import { filter, map, Observable } from "rxjs"; +import { AuthService } from "../services/auth.service"; + +@Injectable() +export class AuthGuard implements CanActivate { + + constructor( + private authService: AuthService, + private router: Router + ) { + } + + canActivate(): Observable | boolean { + return this.authService.currentState$.pipe( + map((currentState) => { + if (!currentState) { + this.router.navigateByUrl('/auth'); + return false; + } + return true; + }) + ); + } +} \ No newline at end of file diff --git a/src/app/core/models/login-request.model copy.ts b/src/app/core/models/login-request.model copy.ts new file mode 100644 index 0000000..679a754 --- /dev/null +++ b/src/app/core/models/login-request.model copy.ts @@ -0,0 +1,8 @@ +export interface LoginRequest { + identifier: string, + password: string, +} + +export interface LoginResponse { + sessionId: string +} \ No newline at end of file diff --git a/src/app/core/models/user-state-request.model.ts b/src/app/core/models/user-state-request.model.ts new file mode 100644 index 0000000..64d1f79 --- /dev/null +++ b/src/app/core/models/user-state-request.model.ts @@ -0,0 +1,12 @@ +export interface UserStateRequest { +} + +export interface UserStateResponse { + id: string, + sessionId: string, + username: string, + roleIdentifier: string, + createdAt: string, + updatedAt: string, + permissions: string[], +} \ No newline at end of file diff --git a/src/app/core/services/auth.service.ts b/src/app/core/services/auth.service.ts new file mode 100644 index 0000000..c7c7e7a --- /dev/null +++ b/src/app/core/services/auth.service.ts @@ -0,0 +1,56 @@ +import { Injectable } from "@angular/core"; +import { RequestService } from "./request.service"; +import { UserStateResponse } from "../models/user-state-request.model"; +import { BehaviorSubject, Observable } from "rxjs"; +import { LoginRequest, LoginResponse } from "../models/login-request.model copy"; +import { Router } from "@angular/router"; + +@Injectable() +export class AuthService { + + currentState$ = new BehaviorSubject(undefined); + + constructor( + private requestService: RequestService, + private router: Router + ) { + } + + readUserState(): void { + this.requestService.get( + 'user/state', + {}, + (response: UserStateResponse) => { + this.currentState$.next(response); + }, + () => { + this.currentState$.next(undefined); + } + ) + } + + login(body: LoginRequest): LoginResponse|null { + let result = null; + + this.requestService.post( + 'auth/login-user', + body, + (response: LoginResponse) => { + result = response; + this.readUserState(); + } + ); + + return result; + } + + logout(): void { + this.requestService.post( + 'auth/logout-user', + {}, + (response: any) => { + this.readUserState(); + } + ); + } +} \ No newline at end of file diff --git a/src/app/core/services/request.service.ts b/src/app/core/services/request.service.ts index 38ec3fa..c390d63 100644 --- a/src/app/core/services/request.service.ts +++ b/src/app/core/services/request.service.ts @@ -1,7 +1,6 @@ import { HttpClient } from '@angular/common/http'; import { Router } from '@angular/router'; import { Injectable } from '@angular/core'; -import { MatSnackBar } from '@angular/material/snack-bar'; @Injectable({ providedIn: 'root' @@ -11,28 +10,46 @@ export class RequestService { constructor( private http: HttpClient, private router: Router, - private snackBar: MatSnackBar ) { } - post(apiPath: string, body: any, fct: Function) + public post(apiPath: string, body: any, successFunction: Function, errorFunction: Function|null = null) { let url = this.obtainUrl(apiPath); let observable = this.http.post(url, body).subscribe( (answer:any) => { - fct(answer); + successFunction(answer); }, (error:any) => { - this.handleError(error); + if (errorFunction === null) + this.handleError(error); + else + errorFunction(error); + } + ); + } + + public get(apiPath: string, body: any, successFunction: Function, errorFunction: Function|null = null) + { + let url = this.obtainUrl(apiPath); + let observable = this.http.get(url, body).subscribe( + (answer:any) => { + successFunction(answer); + }, + (error:any) => { + if (errorFunction === null) + this.handleError(error); + else + errorFunction(error); } ); } - postFiles(apiPath: string, files: File[], fct: Function) { + public postFiles(apiPath: string, files: File[], successFunction: Function, errorFunction: Function|null = null) { if (!files || files.length === 0) { throw 'Need to select at least one file'; } - + const formData = new FormData(); for (let fileIndex = 0; fileIndex < files.length; fileIndex++) { @@ -42,23 +59,13 @@ export class RequestService { let url = this.obtainUrl(apiPath); let observable = this.http.post(url, formData).subscribe( (answer: any) => { - fct(answer); + successFunction(answer); }, (error: any) => { - this.handleError(error); - } - ); - } - - get(apiPath: string, body: any, fct: Function) - { - let url = this.obtainUrl(apiPath); - let observable = this.http.get(url, body).subscribe( - (answer:any) => { - fct(answer); - }, - (error:any) => { - this.handleError(error); + if (errorFunction === null) + this.handleError(error); + else + errorFunction(error); } ); } @@ -98,8 +105,9 @@ export class RequestService { } private showSnackBar(message: string, action?: string) { - this.snackBar.open(message.toString(), action, { + /*this.snackBar.open(message.toString(), action, { duration: 3000, - }); + });*/ + console.log(message); } } diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index b068e2c..2be9984 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -7,27 +7,6 @@ import { PaginatorComponent } from './components/paginator/paginator.component'; import { TabControlComponent } from './components/tab-control/tab-control.component'; import { TableComponent } from './components/table/table.component'; import { FormsModule } from '@angular/forms'; -import { MatInputModule } from '@angular/material/input'; -import { MatSelectModule } from '@angular/material/select'; -import { MatSidenavModule } from '@angular/material/sidenav'; -import { MatToolbarModule } from '@angular/material/toolbar'; -import { MatMenuModule } from '@angular/material/menu'; -import { MatListModule } from '@angular/material/list'; -import { MatIconModule } from '@angular/material/icon'; -import { MatGridListModule } from '@angular/material/grid-list'; -import { MatButtonModule } from '@angular/material/button'; -import { MatCardModule } from '@angular/material/card'; -import { MatExpansionModule } from '@angular/material/expansion'; -import { MatSlideToggleModule } from '@angular/material/slide-toggle'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatDividerModule } from '@angular/material/divider'; -import { MatDialogModule } from '@angular/material/dialog'; -import { MatSnackBarModule } from '@angular/material/snack-bar'; -import { MatCheckboxModule } from '@angular/material/checkbox'; -import { MatPaginatorModule } from '@angular/material/paginator'; -import { MatTabsModule } from '@angular/material/tabs'; -import { MatTableModule } from '@angular/material/table'; -import { MatChipsModule } from '@angular/material/chips'; @NgModule({ declarations: [ @@ -44,54 +23,12 @@ import { MatChipsModule } from '@angular/material/chips'; FormComponent, TableComponent, - MatSlideToggleModule, - MatCardModule, - MatButtonModule, - MatIconModule, - MatGridListModule, - MatFormFieldModule, FormsModule, - MatExpansionModule, - MatMenuModule, - MatListModule, - MatToolbarModule, - MatSidenavModule, - MatInputModule, - MatSelectModule, - MatDividerModule, - MatDialogModule, - MatSnackBarModule, - MatPaginatorModule, - MatCheckboxModule, - MatTabsModule, - MatTableModule, - MatChipsModule, ], imports: [ RouterModule, CommonModule, - MatSlideToggleModule, - MatCardModule, - MatButtonModule, - MatIconModule, - MatGridListModule, - MatFormFieldModule, FormsModule, - MatExpansionModule, - MatMenuModule, - MatListModule, - MatToolbarModule, - MatSidenavModule, - MatInputModule, - MatSelectModule, - MatDividerModule, - MatDialogModule, - MatSnackBarModule, - MatPaginatorModule, - MatCheckboxModule, - MatTabsModule, - MatTableModule, - MatChipsModule, ], }) export class SharedModule {} diff --git a/src/assets/icon.ico b/src/assets/icon.ico index c9092904f1d9c4e009cf97ad23d1cb473d3e45f2..dfc2768c4d52629df0375fb2d8f8d8d16dba4a2f 100644 GIT binary patch literal 67646 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 literal 2296 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 literal 4286 zcmeH~OD|ikS}|9ISnv%9TM~p= zcx@!?5YL+PE7K=OJNKM3awn5arlj3Ub=AMRtGcWDI8MU-D=TyO@BH}UI7!EGzPiH| zIp5uZT*;kFeR~^k0>_d66gvX1udi}*b0d3ud$O^yA&ZNPI^dBphfcQX@$pf1c6MZV zcvu=68>OVAL<$QFB`+^e2RyQ-rY6^8xjE;`V^z^h2cx23>W7dN$7-0Pl zy}Z20+}xbEXZBcES0^(wGje`@E>BNSAK;KNhYq?XF63o?eqO+gODiiY8bkI$ZU+Vi zG8Ufj4p!YizJBC@4@YFu@KZ z&UtTduj&)O!NEbfzrR z=N~?ke=xuT6CG+u5K+6RALww8&d$!>soDP7Gc|@0wtqmt0uyX};LE1n-CZyC-QC?E z?yT*qo&CT_4&%HW5J(ZJ_6PcKpc(=ze|L9_at+}VvO0e;PFPnn=)61wuwY9ZUR8$n( zKe{zFHL|?C?DfMa|Lg1Pf$=AIOG`_VN~L1^uc)YybUOX6PDjN*>ikht8X6iR*MQFE z=BBrA;_N?v)Bt?h6m|d5KR!N|mX;RHqn&?p+uYnNM@L6CbgcV_QCR&kpjTH{T9-{- zXC3hU{r$pR5XGrK*xB_rQ_rKWPfkwindGdEkB`gk?QM{2?S*mcZ&?4a!O7{`+L{jd zAfk8C!^~TN-WJw>z}Vm4_uf2q{|)kyNoM-5scCWgufek4AMD}s^3qI4{-TSG@y&ZM z?)w99YL@-}B8RiHv!8l@fo1Ao;36p<7eLA z#l^+a)6*0A-tzki<2kc@e^4L7zCV~lC!60N{&mrhuCK4Pe%jw()YL!q{pDZ7zs9@g H`mfSYN!>=` diff --git a/src/index.html b/src/index.html index 9bb891a..4123f0d 100644 --- a/src/index.html +++ b/src/index.html @@ -5,11 +5,10 @@ Bienen - + - - + diff --git a/src/styles.scss b/src/styles.scss index bd6213e..cfa7bc1 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -1,3 +1,20 @@ @tailwind base; @tailwind components; -@tailwind utilities; \ No newline at end of file +@tailwind utilities; + +@layer base { + :root { + --color-text-primary: 0,0,0; + --color-text-primary-muted: 100,100,100; + --color-text-secondary: 255,255,255; + --color-text-secondary-muted: 170,170,170; + --color-text-accent: 255, 199, 44; + --color-text-accent-muted: 255, 199, 44; + + + --color-primary: 255, 255, 255; + --color-secondary: 255, 199, 44; + --color-accent: 255, 199, 44; + } + } + \ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js index 5397866..95fbc3f 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,9 +1,41 @@ -/** @type {import('tailwindcss').Config} */ -module.exports = { - content: ["./src/**/*.{html,ts}"], - theme: { - extend: {}, - }, - plugins: [], +function withOpacity(variableName) { + return ({ opacityValue }) => { + if (opacityValue !== undefined) { + return `rgba(var(${variableName}), ${opacityValue})` + } + return `rgb(var(${variableName}))` + } +} + +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + "./src/**/*.{html,ts}", + "./node_modules/flowbite/**/*.js" + ], + theme: { + extend: { + textColor: { + skin: { + primary: withOpacity('--color-text-primary'), + 'primary-muted': withOpacity('--color-text-primary-muted'), + secondary: withOpacity('--color-text-secondary'), + 'secondary-muted': withOpacity('--color-text-secondary-muted'), + accent: withOpacity('--color-text-accent'), + 'accent-muted': withOpacity('--color-text-accent-muted'), + }, + }, + backgroundColor: { + skin: { + primary: withOpacity('--color-primary'), + secondary: withOpacity('--color-secondary'), + accent: withOpacity('--color-accent'), + }, + }, + }, + }, + plugins: [ + require('flowbite/plugin') + ], }