This commit is contained in:
Flo 2024-09-01 20:13:57 +00:00
parent d8d09d7bac
commit 1f99cfb53c
34 changed files with 551 additions and 345 deletions

View File

@ -15,8 +15,12 @@ export class AppComponent implements OnInit {
) {
var language = navigator.language;
translate.setDefaultLang(language);
translate.use(language);
this.translate.setDefaultLang(language);
this.translate.use(language);
this.translate.onLangChange.subscribe(() => {
document.title = this.translate.instant("title");
});
}
ngOnInit(): void {

View File

@ -8,6 +8,7 @@ import { ReactiveFormsModule } from "@angular/forms";
import { ForgotPasswordComponent } from "./components/forgot-password/forgot-password.component";
import { ResetPasswordComponent } from "./components/reset-password/reset-password.component";
import { TranslateModule } from "@ngx-translate/core";
import { SharedModule } from "src/app/shared/shared.module";
const routes: Routes = [
{ path: "login", component: LoginComponent },
@ -44,6 +45,7 @@ const routes: Routes = [
CommonModule,
ReactiveFormsModule,
TranslateModule,
SharedModule,
],
})
export class AuthModule {}

View File

@ -11,39 +11,20 @@
<form class="max-w-sm mx-auto" [formGroup]="confirmRegistrationForm">
<div class="mb-5">
<label
for="password"
class="block mb-2 text-sm font-medium text-skin-primary-muted"
>{{ "auth.password" | translate }}</label
>
<input
formControlName="password"
type="password"
id="password"
class="bg-skin-primary border border-gray-300 text-skin-primary text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
required
<shared-input
key="password"
inputType="password"
label="auth.password"
required="true"
/>
</div>
<div class="mb-5">
<label
for="passwordConfirmation"
class="block mb-2 text-sm font-medium text-skin-primary-muted"
>{{ "auth.password-confirmation" | translate }}</label
>
<input
formControlName="passwordConfirmation"
type="password"
id="passwordConfirmation"
class="bg-skin-primary border border-gray-300 text-skin-primary text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
required
<shared-input
key="passwordConfirmation"
inputType="password"
label="auth.password-confirmation"
required="true"
/>
</div>
<button
(click)="confirm()"
[disabled]="!confirmRegistrationForm.valid"
type="submit"
class="w-full 9xl:w-auto font-bold text-skin-secondary bg-skin-accent hover:text-skin-primary rounded-lg text-sm px-5 py-2.5 text-center"
>
{{ "auth.confirm-registration" | translate }}
</button>
<shared-submit label="auth.confirm-registration" (onSubmit)="confirm()" />
</form>

View File

@ -40,5 +40,7 @@ export class ConfirmRegistrationComponent {
passwordConfirmation:
this.confirmRegistrationForm.value.passwordConfirmation!,
});
this.router.navigateByUrl("/auth/login");
}
}

View File

@ -11,26 +11,12 @@
<form class="max-w-sm mx-auto" [formGroup]="forgotPasswordForm">
<div class="mb-5">
<label
for="mail"
class="block mb-2 text-sm font-medium text-skin-primary-muted"
>
{{ "auth.email" | translate }}</label
>
<input
formControlName="mail"
type="email"
id="mail"
class="bg-skin-primary border border-gray-300 text-skin-primary text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
required
<shared-input
key="mail"
inputType="email"
label="auth.email"
required="true"
/>
</div>
<button
(click)="confirm()"
[disabled]="!forgotPasswordForm.valid"
type="submit"
class="w-full 9xl:w-auto font-bold text-skin-secondary bg-skin-accent hover:text-skin-primary rounded-lg text-sm px-5 py-2.5 text-center"
>
{{ "auth.reset-password" | translate }}
</button>
<shared-submit label="auth.reset-password" (onSubmit)="confirm()" />
</form>

View File

@ -11,23 +11,13 @@
<form class="max-w-sm mx-auto" [formGroup]="loginForm">
<div class="mb-5">
<label
for="identifier"
class="block mb-2 text-sm font-medium text-skin-primary-muted"
>{{ "auth.username-or-email" | translate }}</label
>
<input
formControlName="identifier"
type="text"
id="identifier"
class="bg-skin-primary border border-gray-300 text-skin-primary text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
placeholder="username72 / your@email.com"
required
<shared-input
key="identifier"
inputType="text"
label="auth.username-or-email"
required="true"
/>
<p
id="helper-text-explanation"
class="mt-2 text-sm text-skin-primary-muted"
>
<p class="mt-2 text-sm text-skin-primary-muted">
{{ "auth.not-yet-registered" | translate }}
<a
routerLink="/auth/registration"
@ -37,22 +27,13 @@
</p>
</div>
<div class="mb-5">
<label
for="password"
class="block mb-2 text-sm font-medium text-skin-primary-muted"
>{{ "auth.password" | translate }}</label
>
<input
formControlName="password"
type="password"
id="password"
class="bg-skin-primary border border-gray-300 text-skin-primary text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
required
<shared-input
key="password"
inputType="password"
label="auth.password"
required="true"
/>
<p
id="helper-text-explanation"
class="mt-2 text-sm text-skin-primary-muted"
>
<p class="mt-2 text-sm text-skin-primary-muted">
<a
routerLink="/auth/forgot-password"
class="font-medium text-skin-accent hover:underline hover:font-bold"
@ -60,12 +41,6 @@
>
</p>
</div>
<button
(click)="login()"
[disabled]="!loginForm.valid"
type="submit"
class="w-full 9xl:w-auto font-bold text-skin-secondary bg-skin-accent hover:text-skin-primary rounded-lg text-sm px-5 py-2.5 text-center"
>
{{ "auth.login" | translate }}
</button>
<shared-submit label="auth.login" (onSubmit)="login()" />
</form>

View File

@ -11,34 +11,12 @@
<form class="max-w-sm mx-auto" [formGroup]="registrationForm">
<div class="mb-5">
<label
for="mail"
class="block mb-2 text-sm font-medium text-skin-primary-muted"
>
{{ "auth.email" | translate }}</label
>
<input
formControlName="mail"
type="mail"
id="mail"
class="bg-skin-primary border border-gray-300 text-skin-primary text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
<shared-input
key="mail"
inputType="email"
label="auth.email"
required="true"
placeholder="your@email.com"
required
/>
</div>
<div class="mb-5">
<label
for="username"
class="block mb-2 text-sm font-medium text-skin-primary-muted"
>
{{ "auth.reset-password" | translate }}</label
>
<input
formControlName="username"
type="string"
id="username"
class="bg-skin-primary border border-gray-300 text-skin-primary text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
required
/>
<p
id="helper-text-explanation"
@ -52,12 +30,13 @@
>
</p>
</div>
<button
(click)="register()"
[disabled]="!registrationForm.valid"
type="submit"
class="w-full 9xl:w-auto font-bold text-skin-secondary bg-skin-accent hover:text-skin-primary rounded-lg text-sm px-5 py-2.5 text-center"
>
{{ "auth.register" | translate }}
</button>
<div class="mb-5">
<shared-input
key="username"
inputType="string"
label="auth.username"
required="true"
/>
</div>
<shared-submit label="auth.register" (onSubmit)="register()" />
</form>

View File

@ -11,39 +11,20 @@
<form class="max-w-sm mx-auto" [formGroup]="resetPasswordForm">
<div class="mb-5">
<label
for="newPassword"
class="block mb-2 text-sm font-medium text-skin-primary-muted"
>{{ "auth.new-password" | translate }}</label
>
<input
formControlName="newPassword"
type="password"
id="newPassword"
class="bg-skin-primary border border-gray-300 text-skin-primary text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
required
<shared-input
key="newPassword"
inputType="password"
label="auth.new-password"
required="true"
/>
</div>
<div class="mb-5">
<label
for="passwordConfirmation"
class="block mb-2 text-sm font-medium text-skin-primary-muted"
>{{ "auth.password-confirmation" | translate }}</label
>
<input
formControlName="passwordConfirmation"
type="password"
id="passwordConfirmation"
class="bg-skin-primary border border-gray-300 text-skin-primary text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
required
<shared-input
key="passwordConfirmation"
inputType="password"
label="auth.password-confirmation"
required="true"
/>
</div>
<button
(click)="confirm()"
[disabled]="!resetPasswordForm.valid"
type="submit"
class="w-full 9xl:w-auto font-bold text-skin-secondary bg-skin-accent hover:text-skin-primary rounded-lg text-sm px-5 py-2.5 text-center"
>
{{ "auth.new-password" | translate }}
</button>
<shared-submit label="auth.reset-password" (onSubmit)="confirm()" />
</form>

View File

@ -21,18 +21,27 @@
</button>
<!-- Title -->
<a routerLink="/" class="flex items-center space-x-3">
<img src="assets/icon.png" class="h-10 w-10" alt="Beekeeper Logo" />
<span
class="text-skin-primary self-center text-4xl font-semibold whitespace-nowrap"
>{{ "title" | translate }}</span
<a routerLink="/" class="text-skin-primary flex items-center space-x-3"
><svg
xmlns="http://www.w3.org/2000/svg"
viewBox="4 4 16 16"
class="w-12 h-12"
>
<title>{{ "title" | translate }}</title>
<path
fill="currentColor"
d="M17.4 9C17 7.8 16.2 7 15 6.5V5H14V6.4H13.6C12.5 6.4 11.6 6.8 10.8 7.6L10.4 8L9 7.5C8.7 7.4 8.4 7.3 8 7.3C7.4 7.3 6.8 7.5 6.3 7.9C5.7 8.3 5.4 8.8 5.2 9.3C5 10 5 10.6 5.2 11.3C5.5 12 5.8 12.5 6.3 12.8C5.9 14.3 6.2 15.6 7.3 16.7C8.1 17.5 9 17.9 10.1 17.9C10.6 17.9 10.9 17.9 11.2 17.8C11.8 18.6 12.6 19.1 13.6 19.1C13.9 19.1 14.3 19.1 14.6 19C15.2 18.8 15.6 18.4 16 17.9C16.4 17.3 16.6 16.8 16.6 16.2C16.6 15.8 16.6 15.5 16.5 15.2L16 13.6L16.6 13.2C17.4 12.4 17.8 11.3 17.7 10.1H19V9H17.4M7.7 11.3C7.1 11 6.9 10.6 7.1 10C7.3 9.4 7.7 9.2 8.3 9.4L11.5 10.6C9.9 11.4 8.7 11.6 7.7 11.3M14 16.9C13.4 17.1 13 16.9 12.7 16.3C12.4 15.3 12.6 14.1 13.4 12.5L14.6 15.6C14.8 16.3 14.6 16.7 14 16.9M15.2 11.6L14.6 10V9.9L14.3 9.6H14.2L12.6 9C13 8.7 13.4 8.5 13.9 8.5C14.4 8.5 14.9 8.7 15.3 9.1C15.7 9.5 15.9 9.9 15.9 10.4C15.7 10.7 15.5 11.2 15.2 11.6Z"
/>
</svg>
<span class="self-center text-4xl font-semibold whitespace-nowrap">{{
"title" | translate
}}</span>
</a>
<!--User Bubble-->
<button
type="button"
class="w-12 h-12 text-sm bg-skin-primary rounded-full focus:ring-4 focus:ring-skin-primary"
class="w-12 h-12 text-sm bg-skin-primary rounded-full focus:ring-4 focus:ring-skin-accent"
id="user-menu-button"
aria-expanded="false"
data-dropdown-toggle="user-dropdown"
@ -57,13 +66,26 @@
class="block py-2 px-3 rounded text-skin-primary hover:text-skin-secondary hover:bg-skin-accent"
[routerLink]="navigationLink.routerLink"
[routerLinkActiveOptions]="{ exact: true }"
routerLinkActive="font-bold"
>{{ getNavigationLinkLabel(navigationLink) | translate }}</a
routerLinkActive="underline"
>
<div class="flex flex-row gap-4">
<svg
*ngIf="navigationLink.path !== undefined"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
class="w-8 h-8 basis-auto"
>
<title>{{ navigationLink.label | translate }}</title>
<path fill="currentColor" [attr.d]="navigationLink.path" />
</svg>
<div class="basis-full">
{{ navigationLink.label | translate }}
</div>
</div>
</a>
</li>
</ul>
</div>
<!-- User dropdown -->
<div id="user-dropdown" class="hidden min-w-full md:min-w-64 px-4 z-50">
<div
@ -81,7 +103,7 @@
<ul class="p-4" aria-labelledby="user-menu-button">
<li *ngFor="let usermenuButton of usermenuButtons">
<button
class="w-full text-center block py-2 px-3 rounded text-skin-primary hover:bg-skin-accent"
class="block py-2 px-3 rounded text-skin-primary hover:text-skin-secondary hover:bg-skin-accent w-full text-right"
[routerLink]="
usermenuButton.routerLink === undefined
? null

View File

@ -9,7 +9,8 @@ import { NotificationService } from "../../services/notification.service";
import { TranslateService } from "@ngx-translate/core";
interface NavigationLink {
label: Function | string;
path: string | undefined;
label: string;
routerLink: string;
}
@ -26,10 +27,20 @@ interface UserMenuButton {
})
export class NavigationComponent implements OnInit {
navigationLinks: NavigationLink[] = [
{ routerLink: "/", label: () => "navigation.home" },
{
routerLink: "/",
path: "M10,20V14H14V20H19V12H22L12,3L2,12H5V20H10Z",
label: "navigation.home",
},
{
routerLink: "/settings",
path: "M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z",
label: "menu.settings",
},
{
routerLink: "/colonies",
label: () => "navigation.colonies",
path: "M23 14.5C23 12.94 21.97 11.63 20.55 11.18C20.83 10.68 21 10.11 21 9.5C21 7.94 19.97 6.63 18.55 6.18C18.83 5.68 19 5.11 19 4.5C19 2.57 17.43 1 15.5 1H8.5C6.57 1 5 2.57 5 4.5C5 5.11 5.17 5.68 5.45 6.18C4.04 6.63 3 7.94 3 9.5C3 10.11 3.17 10.68 3.45 11.18C2.04 11.63 1 12.94 1 14.5C1 15.76 1.67 16.84 2.67 17.46C2.25 18.03 2 18.74 2 19.5C2 21.43 3.57 23 5.5 23H18.5C20.43 23 22 21.43 22 19.5C22 18.74 21.75 18.03 21.33 17.46C22.33 16.84 23 15.76 23 14.5M8.5 3H15.5C16.33 3 17 3.67 17 4.5S16.33 6 15.5 6H8.5C7.67 6 7 5.33 7 4.5S7.67 3 8.5 3M6.5 8H17.5C18.33 8 19 8.67 19 9.5S18.33 11 17.5 11H6.5C5.67 11 5 10.33 5 9.5S5.67 8 6.5 8M4 19.5C4 18.67 4.67 18 5.5 18H9V21H5.5C4.67 21 4 20.33 4 19.5M18.5 21H15V18H18.5C19.33 18 20 18.67 20 19.5S19.33 21 18.5 21M19.5 16H14.82C14.4 14.84 13.3 14 12 14S9.6 14.84 9.18 16H4.5C3.67 16 3 15.33 3 14.5S3.67 13 4.5 13H19.5C20.33 13 21 13.67 21 14.5S20.33 16 19.5 16Z",
label: "navigation.colonies",
},
];
@ -143,11 +154,4 @@ export class NavigationComponent implements OnInit {
return button.label;
}
}
getNavigationLinkLabel(link: NavigationLink) {
if (typeof link.label === "function") {
return link.label();
} else {
return link.label;
}
}
}

View File

@ -19,7 +19,7 @@
</div>
<button
type="button"
class="ms-auto -mx-1.5 -my-1.5 bg-skin-secondary text-skin-accent rounded-lg focus:ring-2 focus:ring-blue-400 p-1.5 hover:bg-skin-primary inline-flex items-center justify-center h-8 w-8"
class="ms-auto -mx-1.5 -my-1.5 bg-skin-secondary text-skin-accent rounded-lg focus:ring-2 focus:ring-skin-accent p-1.5 hover:bg-skin-primary inline-flex items-center justify-center h-8 w-8"
(click)="dismiss(item)"
>
<span class="sr-only">Close</span>

View File

@ -1,5 +1,5 @@
<div class="rounded-lg shadow-sm shadow-skin-primary bg-skin-secondary p-5">
<div class="inline-block text-skin-secondary bg-skin-accent rounded-lg p-2">
<div class="inline-block text-skin-primary bg-skin-accent rounded-lg p-2">
<div class="font-bold">
{{ state.username }}
</div>
@ -10,57 +10,49 @@
<div class="mb-4 border-b border-skin-primary">
<ul
class="flex flex-wrap -mb-px text-sm font-medium text-center"
id="default-styled-tab"
data-tabs-toggle="#default-styled-tab-content"
role="tablist"
class="flex flex-nowrap -mb-px text-sm font-medium text-center"
data-tabs-toggle="#tab-outlet"
data-tabs-active-classes="text-skin-accent border-skin-accent"
data-tabs-inactive-classes="text-skin-primary-muted hover:text-skin-primary border-skin-primary hover:border-skin-accent"
role="tablist"
>
<li class="me-2" role="presentation">
<li class="me-2 w-full xl:w-auto" role="presentation">
<button
class="inline-block p-4 border-b-2 rounded-t-lg"
id="profile-styled-tab"
data-tabs-target="#styled-profile"
type="button"
role="tab"
aria-controls="profile"
aria-selected="false"
class="block p-4 w-full xl:w-auto border-b-2 rounded-t-lg"
data-tabs-target="#profile"
>
{{ "settings.tab-profile" | translate }}
</button>
</li>
<li class="me-2" role="presentation">
<li class="me-2 last:me-0 w-full xl:w-auto" role="presentation">
<button
class="inline-block p-4 border-b-2 rounded-t-lg"
id="security-styled-tab"
data-tabs-target="#styled-security"
type="button"
class="block p-4 w-full xl:w-auto border-b-2 rounded-t-lg"
data-tabs-target="#security"
role="tab"
aria-controls="security"
aria-selected="false"
>
{{ "settings.tab-security" | translate }}
</button>
</li>
</ul>
</div>
<div id="default-styled-tab-content" class="text-skin-primary">
<div
class="hidden p-4 rounded-lg"
id="styled-profile"
role="tabpanel"
aria-labelledby="profile-tab"
>
<div id="tab-outlet" class="text-skin-primary">
<div role="tabpanel" class="hidden py-4 xl:p-4 rounded-lg" id="profile">
<app-tab-profile />
</div>
<div
class="hidden p-4 rounded-lg"
id="styled-security"
role="tabpanel"
aria-labelledby="security-tab"
>
<div role="tabpanel" class="hidden py-4 xl:p-4 rounded-lg" id="security">
<app-tab-security />
</div>
</div>
<!--
<shared-tab-control [tabs]="tabs">
<div role="tabpanel" class="hidden py-4 xl:p-4 rounded-lg" id="profile">
<app-tab-profile />
</div>
<div role="tabpanel" class="hidden py-4 xl:p-4 rounded-lg" id="security">
<app-tab-security />
</div>
</shared-tab-control>
-->
</div>

View File

@ -1,8 +1,8 @@
import { Component, OnInit } from "@angular/core";
import { initFlowbite } from "flowbite";
import { AuthService } from "../../services/auth.service";
import { filter } from "rxjs";
import { UserStateResponse } from "../../models/user-state-request.model";
import { TabItem } from "src/app/shared/models/tab-item.model";
@Component({
selector: "app-settings",
@ -11,6 +11,16 @@ import { UserStateResponse } from "../../models/user-state-request.model";
})
export class SettingsComponent implements OnInit {
state: UserStateResponse;
tabs: TabItem[] = [
{
label: "settings.tab-profile",
target: "profile",
},
{
label: "settings.tab-security",
target: "security",
},
];
constructor(private authService: AuthService) {
this.state = this.authService.currentState$.value!;

View File

@ -0,0 +1,2 @@
<shared-table [items]="items" [columns]="columns" [rowActions]="rowActions">
</shared-table>

View File

@ -1,9 +1,15 @@
import { Component } from "@angular/core";
import { NotificationService } from "src/app/core/services/notification.service";
import {
ColumnDefinition,
RowAction,
} from "src/app/shared/components/table/table.component";
interface User {
username: string;
mail: string;
age: number;
interface Product {
description: string;
id: string;
price: number;
stock: number;
}
@Component({
@ -11,4 +17,75 @@ interface User {
templateUrl: "./tab-profile.component.html",
styleUrls: ["./tab-profile.component.scss"],
})
export class TabProfileComponent {}
export class TabProfileComponent {
constructor(private notificationService: NotificationService) {}
columns: ColumnDefinition[] = [
{
label: "test.product",
content: (item: Product) => item.description,
routerLink: (item: Product) => ["/", "product", item.id],
},
{
label: "test.price",
content: (item: Product) => item.price,
routerLink: undefined,
},
{
label: "test.delete",
content: "Löschen",
routerLink: undefined,
},
{
label: "test.stock",
content: (item: Product) => item.stock,
routerLink: undefined,
},
];
rowActions: RowAction[] = [
{
label: "test.edit",
path: "M20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18,2.9 17.35,2.9 16.96,3.29L15.12,5.12L18.87,8.87M3,17.25V21H6.75L17.81,9.93L14.06,6.18L3,17.25Z",
action: (item: Product) => {
this.edit(item);
},
},
{
label: "test.delete",
path: "M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z",
action: (item: Product) => {
this.delete(item);
},
},
];
items: Product[] = [
{
id: "1",
description: "Flüssiger Honig",
price: 6.5,
stock: 40,
},
{
id: "2",
description: "Cremiger Honig",
price: 5,
stock: 32,
},
{
id: "3",
description: "Labello",
price: 2.3,
stock: 7,
},
];
delete(item: Product) {
this.notificationService.push(item.description + " gelöscht", "info");
}
edit(item: Product) {
this.notificationService.push(item.description + " bearbeitet", "info");
}
}

View File

@ -1,104 +1,66 @@
<div class="grid grid-cols-1 md:grid-cols-2">
<div class="grid grid-cols-1 md:grid-cols-2 gap-10">
<div>
<h2>{{ "settings.security.change-password" | translate }}</h2>
<form class="mx-auto p-5" [formGroup]="changePasswordForm">
<form class="mx-auto py-5" [formGroup]="changePasswordForm">
<div class="mb-5">
<label
for="password"
class="block mb-2 text-sm font-medium text-skin-primary-muted"
>{{ "settings.security.current-password" | translate }}</label
>
<input
formControlName="password"
type="password"
id="password"
class="bg-skin-primary border border-gray-300 text-skin-primary text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
required
<shared-input
key="password"
inputType="password"
label="settings.security.current-password"
required="true"
/>
</div>
<div class="mb-5">
<label
for="newPassword"
class="block mb-2 text-sm font-medium text-skin-primary-muted"
>{{ "settings.security.new-password" | translate }}</label
>
<input
formControlName="newPassword"
type="password"
id="newPassword"
class="bg-skin-primary border border-gray-300 text-skin-primary text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
required
<shared-input
key="newPassword"
inputType="password"
label="settings.security.new-password"
required="true"
/>
</div>
<div class="mb-5">
<label
for="newPasswordConfirmation"
class="block mb-2 text-sm font-medium text-skin-primary-muted"
>{{ "settings.security.password-confirmation" | translate }}</label
>
<input
formControlName="newPasswordConfirmation"
type="password"
id="newPasswordConfirmation"
class="bg-skin-primary border border-gray-300 text-skin-primary text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
required
<shared-input
key="newPasswordConfirmation"
inputType="password"
label="settings.security.password-confirmation"
required="true"
/>
</div>
<button
(click)="changePassword()"
[disabled]="!changePasswordForm.valid"
type="submit"
class="w-full 9xl:w-auto font-bold text-skin-secondary bg-skin-accent hover:text-skin-primary rounded-lg text-sm px-5 py-2.5 text-center"
>
{{ "settings.security.change-password" | translate }}
</button>
<shared-submit
label="settings.security.change-password"
(onSubmit)="changePassword()"
/>
</form>
</div>
<div>
<h2>{{ "settings.security.change-username" | translate }}</h2>
<form class="mx-auto p-5" [formGroup]="changeUsernameForm">
<form class="mx-auto py-5" [formGroup]="changeUsernameForm">
<div class="mb-5">
<label
for="password"
class="block mb-2 text-sm font-medium text-skin-primary-muted"
>{{ "settings.security.current-password" | translate }}</label
>
<input
formControlName="password"
type="password"
id="password"
class="bg-skin-primary border border-gray-300 text-skin-primary text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
required
<shared-input
key="password"
inputType="password"
label="settings.security.current-password"
required="true"
/>
</div>
<div class="mb-5">
<label
for="newUsername"
class="block mb-2 text-sm font-medium text-skin-primary-muted"
>{{ "settings.security.new-username" | translate }}</label
>
<input
formControlName="newUsername"
type="string"
id="newUsername"
class="bg-skin-primary border border-gray-300 text-skin-primary text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
required
<shared-input
key="newUsername"
inputType="string"
label="settings.security.new-username"
required="true"
/>
</div>
<button
(click)="changeUsername()"
[disabled]="!changeUsernameForm.valid"
type="submit"
class="w-full 9xl:w-auto font-bold text-skin-secondary bg-skin-accent hover:text-skin-primary rounded-lg text-sm px-5 py-2.5 text-center"
>
{{ "settings.security.change-username" | translate }}
</button>
<shared-submit
label="settings.security.change-username"
(onSubmit)="changeUsername()"
/>
</form>
</div>
</div>

View File

@ -22,6 +22,14 @@ export class TabSecurityComponent {
constructor(private authService: AuthService) {}
changePassword(): void {
console.log({
password: this.changePasswordForm.value.password!,
newPassword: this.changePasswordForm.value.newPassword!,
newPasswordConfirmation:
this.changePasswordForm.value.newPasswordConfirmation!,
});
return;
this.authService.changePassword({
password: this.changePasswordForm.value.password!,
newPassword: this.changePasswordForm.value.newPassword!,

View File

@ -0,0 +1,27 @@
<div>
<label
*ngIf="label !== undefined"
[for]="key"
class="block mb-2 text-sm font-medium text-skin-primary-muted"
>{{ label | translate }}</label
>
<input
*ngIf="required"
[id]="key"
[type]="inputType"
[formControlName]="key"
[placeholder]="placeholder"
class="bg-skin-primary border border-skin-primary text-skin-primary text-sm rounded-lg focus:ring-skin-accent focus:border-skin-accent block w-full p-2.5"
required
/>
<input
*ngIf="!required"
[id]="key"
[type]="inputType"
[formControlName]="key"
[placeholder]="placeholder"
class="bg-skin-primary border border-skin-primary text-skin-primary text-sm rounded-lg focus:ring-skin-accent focus:border-skin-accent block w-full p-2.5"
/>
</div>

View File

@ -0,0 +1,21 @@
import { Component, inject, Input } from "@angular/core";
import { ControlContainer, FormGroup } from "@angular/forms";
@Component({
selector: "shared-input",
templateUrl: "./input.component.html",
styleUrl: "./input.component.scss",
viewProviders: [
{
provide: ControlContainer,
useFactory: () => inject(ControlContainer, { skipSelf: true }),
},
],
})
export class InputComponent {
@Input() label: string | undefined = undefined;
@Input() placeholder: string = "";
@Input() required: boolean = false;
@Input({ required: true }) inputType!: string;
@Input({ required: true }) key!: string;
}

View File

@ -0,0 +1,8 @@
<button
(click)="submit()"
[disabled]="!getParentFormGroup().valid"
type="submit"
class="w-full cursor-pointer 9xl:w-auto font-bold text-skin-primary bg-skin-accent hover:bg-skin-accent-muted focus:ring-2 focus:ring-skin-accent rounded-lg text-sm px-5 py-2.5 text-center"
>
{{ label | translate }}
</button>

View File

@ -0,0 +1,34 @@
import { Component, EventEmitter, inject, Input, Output } from "@angular/core";
import { ControlContainer, FormGroup } from "@angular/forms";
@Component({
selector: "shared-submit",
templateUrl: "./submit.component.html",
styleUrl: "./submit.component.scss",
viewProviders: [
{
provide: ControlContainer,
useFactory: () => inject(ControlContainer, { skipSelf: true }),
},
],
})
export class SubmitComponent {
@Output() onSubmit = new EventEmitter();
@Input({ required: true }) label!: string;
constructor(private controlContainer: ControlContainer) {}
getParentFormGroup(): FormGroup {
return this.controlContainer.control as FormGroup;
}
submit(): void {
var formGroup = this.getParentFormGroup();
if (!formGroup.valid) {
console.log("formGroup not Valid");
return;
}
this.onSubmit.emit();
}
}

View File

@ -0,0 +1,28 @@
<div class="">
<div class="mb-4 border-b border-skin-primary">
<ul
role="tablist"
class="flex flex-nowrap -mb-px text-sm font-medium text-center"
data-tabs-toggle="#tab-outlet"
data-tabs-active-classes="text-skin-accent border-skin-accent"
data-tabs-inactive-classes="text-skin-primary-muted hover:text-skin-primary border-skin-primary hover:border-skin-accent"
>
<li
*ngFor="let tab of tabs"
class="me-2 w-full xl:w-auto"
role="presentation"
>
<button
role="tab"
class="block p-4 w-full xl:w-auto border-b-2 rounded-t-lg"
[data-tabs-target]="tab.target"
>
{{ tab.label | translate }}
</button>
</li>
</ul>
</div>
<div id="tab-outlet" class="text-skin-primary">
<ng-content />
</div>
</div>

View File

@ -0,0 +1,16 @@
import { Component, Input, OnInit } from "@angular/core";
import { initFlowbite } from "flowbite";
import { TabItem } from "../../models/tab-item.model";
@Component({
selector: "shared-tab-control",
templateUrl: "./tab-control.component.html",
styleUrl: "./tab-control.component.scss",
})
export class TabControlComponent implements OnInit {
@Input({ required: true }) tabs!: TabItem[];
ngOnInit(): void {
initFlowbite();
}
}

View File

@ -1,55 +1,57 @@
<table class="w-full text-skin-primary">
<thead class="uppercase bg-skin-accent border-b border-skin-primary text-left">
<table class="w-full table-fixed text-skin-primary">
<thead
class="uppercase bg-skin-accent border-b border-skin-primary text-left"
>
<tr>
<th
*ngFor="let column of columns"
class="px-5 py-2"
>
{{column.header}}
<th *ngFor="let column of columns" class="px-5 py-2">
{{ column.label | translate }}
</th>
<th *ngIf="rowActions.length > 0" class="px-5 py-2"></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of items" class="border-b border-skin-primary">
<td
*ngFor="let column of columns; index as currentIndex"
class="px-5 py-2"
class="px-5 py-2 text-skin-primary"
[ngClass]="{
'bg-skin-primary': currentIndex % 2 === 0,
'bg-skin-secondary': currentIndex % 2 === 1,
}"
>
<div *ngIf="column.routerLink !== undefined">
<a [routerLink]="column.routerLink(item)" class="font-bold hover:text-skin-accent hover:underline">
<div *ngIf="column.columnContent !== undefined">
{{ column.columnContent }}
<a [routerLink]="getRouterLink(column, item)">
<div class="text-skin-accent hover:underline">
{{ getContent(column, item) }}
</div>
<div
*ngIf="column.columnFunction !== undefined"
[innerHtml]="column.columnFunction(item)"
></div>
</a>
</div>
<div *ngIf="column.routerLink === undefined">
<div *ngIf="column.columnContent !== undefined">
<div *ngIf="isBoolean(column.columnContent)">
<input type="checkbox" [(ngModel)]="column.columnContent" />
</div>
<div *ngIf="!isBoolean(column.columnContent)">
{{ column.columnContent }}
</div>
</div>
<div
*ngIf="column.columnFunction !== undefined"
[innerHtml]="column.columnFunction(item)"
></div>
{{ getContent(column, item) }}
</div>
</td>
<td
*ngIf="rowActions.length > 0"
class="text-right w-auto h-full"
[ngClass]="{
'bg-skin-primary': columns.length % 2 === 0,
'bg-skin-secondary': columns.length % 2 === 1,
}"
>
<button
class="p-2 font-bold text-skin-primary-muted hover:text-skin-primary"
*ngFor="let rowAction of rowActions"
(click)="rowAction.action(item)"
>
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6">
<title>{{ rowAction.label | translate }}</title>
<path fill="currentColor" [attr.d]="rowAction.path" />
</svg>
</button>
</td>
</tr>
</tbody>
</table>

View File

@ -1,10 +1,15 @@
import { Component, Input } from "@angular/core";
export interface ColumnDefinition {
header: string;
columnContent?: string | undefined;
columnFunction?: Function | undefined;
routerLink?: Function | undefined;
label: string;
content: Function | string | boolean | number;
routerLink: Function | string | undefined;
}
export interface RowAction {
label: string;
action: Function;
path: string;
}
@Component({
@ -15,8 +20,29 @@ export interface ColumnDefinition {
export class TableComponent {
@Input() items: any[] = [];
@Input() columns: ColumnDefinition[] = [];
@Input() rowActions: RowAction[] = [];
isBoolean(obje: any): boolean {
return typeof obje === "boolean";
}
getRouterLink(column: ColumnDefinition, item: any) {
if (column.routerLink === undefined) {
return undefined;
}
if (typeof column.routerLink === "string") {
return column.routerLink;
}
return column.routerLink(item);
}
getContent(column: ColumnDefinition, item: any) {
if (typeof column.content === "function") {
return column.content(item);
}
return column.content;
}
}

View File

@ -1,4 +1,4 @@
export interface TabItem {
id: string;
title: string;
label: string;
target: string;
}

View File

@ -4,11 +4,35 @@ import { CommonModule } from "@angular/common";
import { CardComponent } from "./components/card/card.component";
import { PaginatorComponent } from "./components/paginator/paginator.component";
import { TableComponent } from "./components/table/table.component";
import { FormsModule } from "@angular/forms";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { InputComponent } from "./components/input/input.component";
import { TranslateModule } from "@ngx-translate/core";
import { SubmitComponent } from "./components/submit/submit.component";
import { TabControlComponent } from "./components/tab-control/tab-control.component";
@NgModule({
declarations: [CardComponent, PaginatorComponent, TableComponent],
exports: [CardComponent, PaginatorComponent, TableComponent, FormsModule],
imports: [RouterModule, CommonModule, FormsModule],
declarations: [
CardComponent,
PaginatorComponent,
TableComponent,
TabControlComponent,
InputComponent,
SubmitComponent,
],
exports: [
CardComponent,
PaginatorComponent,
TableComponent,
TabControlComponent,
InputComponent,
SubmitComponent,
],
imports: [
RouterModule,
CommonModule,
FormsModule,
ReactiveFormsModule,
TranslateModule,
],
})
export class SharedModule {}

View File

@ -36,7 +36,7 @@
"login-now": "Jetzt anmelden!",
"register-now": "Jetzt registrieren!",
"forgot-password": "Passwort vergessen",
"confirm-registration": "Registrierung bestätigen"
"confirm-registration": "Registrierung abschließen"
},
"settings": {
"tab-security": "Sicherheit",
@ -50,5 +50,13 @@
"new-username": "Neuer Benutzername",
"password-confirmation": "Passwort wiederholen"
}
},
"test": {
"product": "Produkt",
"price": "Preis",
"stock": "Lagerbestand",
"edit": "Bearbeiten",
"delete": "Löschen"
}
}

View File

@ -50,5 +50,13 @@
"new-username": "New Username",
"password-confirmation": "Confirm Password"
}
},
"test": {
"product": "Product",
"price": "Price",
"stock": "Stock",
"edit": "Edit",
"delete": "Delete"
}
}

View File

@ -3,28 +3,41 @@
@tailwind utilities;
@layer base {
/* Light Theme */
:root {
--color-text-primary: 0, 0, 0;
--color-text-primary-muted: 100, 100, 100;
--color-text-primary-muted: 50, 50, 50;
--color-text-secondary: 255, 255, 255;
--color-text-secondary-muted: 170, 170, 170;
--color-text-accent: 250, 183, 0;
--color-text-accent-muted: 250, 183, 0;
--color-text-secondary-muted: 200, 200, 200;
--color-primary: 244, 244, 245;
--color-secondary: 238, 238, 240;
--color-accent: 255, 199, 44;
--color-text-accent: 255, 180, 0;
--color-text-accent-muted: 240, 170, 0;
--color-background-primary: 255, 255, 255;
--color-background-primary-muted: 240, 240, 240;
--color-background-secondary: 245, 245, 245;
--color-background-secondary-muted: 230, 230, 230;
--color-background-accent: 255, 199, 44;
--color-background-accent-muted: 240, 184, 34;
}
/* Dark Theme */
.theme-dark {
--color-text-primary: 255, 255, 255;
--color-text-primary-muted: 170, 170, 170;
--color-text-secondary: 0, 0, 0;
--color-text-secondary-muted: 100, 100, 100;
--color-text-accent: 250, 183, 0;
--color-text-accent-muted: 250, 183, 0;
--color-primary: 24, 24, 27;
--color-secondary: 39, 39, 42;
--color-accent: 250, 183, 0;
--color-text-accent: 255, 180, 50;
--color-text-accent-muted: 230, 160, 40;
--color-background-primary: 24, 24, 27;
--color-background-primary-muted: 34, 34, 37;
--color-background-secondary: 39, 39, 42;
--color-background-secondary-muted: 49, 49, 52;
--color-background-accent: 250, 183, 0;
--color-background-accent-muted: 225, 158, 0;
}
}

View File

@ -37,9 +37,12 @@ module.exports = {
// "backgrounds"
backgroundColor: {
skin: {
primary: withOpacity("--color-primary"),
secondary: withOpacity("--color-secondary"),
accent: withOpacity("--color-accent"),
primary: withOpacity("--color-background-primary"),
"primary-muted": withOpacity("--color-background-primary-muted"),
secondary: withOpacity("--color-background-secondary"),
"secondary-muted": withOpacity("--color-background-secondary-muted"),
accent: withOpacity("--color-background-accent"),
"accent-muted": withOpacity("--color-background-accent-muted"),
},
},
@ -52,6 +55,7 @@ module.exports = {
ringColor: {
skin: {
primary: withOpacity("--color-text-secondary-muted"),
accent: withOpacity("--color-text-accent-muted"),
},
},
divideColor: {