Compare commits
2 Commits
master
...
dfe631edf6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dfe631edf6 | ||
|
|
ac42f8b1b9 |
@@ -6,4 +6,7 @@ export interface ShippingMethod {
|
|||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
minDeliveryDays: number;
|
minDeliveryDays: number;
|
||||||
maxDeliveryDays: number;
|
maxDeliveryDays: number;
|
||||||
|
// NEU: Gewichtsgrenzen
|
||||||
|
minWeight: number;
|
||||||
|
maxWeight: number;
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
.form-header {
|
.page-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
border-bottom: 1px solid var(--color-border);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h3[card-header] {
|
h3[card-header] {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-template #formContent>
|
<ng-template #formContent>
|
||||||
<div class="form-header">
|
<div class="page-header">
|
||||||
<h3 card-header>Neues Produkt erstellen</h3>
|
<h3 card-header>Neues Produkt erstellen</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
.form-header {
|
.page-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
border-bottom: 1px solid var(--color-border);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h3[card-header] {
|
h3[card-header] {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-template #formContent>
|
<ng-template #formContent>
|
||||||
<div class="form-header">
|
<div class="page-header">
|
||||||
<h3 card-header>Produkt bearbeiten</h3>
|
<h3 card-header>Produkt bearbeiten</h3>
|
||||||
</div>
|
</div>
|
||||||
<app-product-form
|
<app-product-form
|
||||||
|
|||||||
@@ -282,6 +282,11 @@ export class ProductEditComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (mainImagePreview && !this.newImageFiles.has(mainImagePreview.identifier)) {
|
||||||
|
console.log('Setze bestehendes Bild als Main:', mainImagePreview.identifier);
|
||||||
|
formData.append('SetMainImageId', mainImagePreview.identifier);
|
||||||
|
}
|
||||||
|
|
||||||
return formData;
|
return formData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,14 @@
|
|||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text);
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
app-search-bar {
|
app-search-bar {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
@@ -20,7 +28,9 @@ app-search-bar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.column-filter-container {
|
.column-filter-container {
|
||||||
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
gap: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-filter-dropdown {
|
.column-filter-dropdown {
|
||||||
|
|||||||
@@ -1,20 +1,34 @@
|
|||||||
<!-- /src/app/features/admin/components/products/product-list/product-list.component.html -->
|
<!-- /src/app/features/admin/components/products/product-list/product-list.component.html -->
|
||||||
|
|
||||||
<div class="page-container">
|
<div class="page-container">
|
||||||
<div class="header">
|
<div>
|
||||||
<h1 class="page-title">Produktübersicht</h1>
|
<h3 class="page-header">Produktübersicht</h3>
|
||||||
<app-button buttonType="primary" (click)="onAddNew()">
|
|
||||||
<app-icon iconName="plus"></app-icon> Neues Produkt
|
|
||||||
</app-button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="table-header">
|
<div class="table-header">
|
||||||
<app-search-bar placeholder="Produkte nach Name oder SKU suchen..." (search)="onSearch($event)"></app-search-bar>
|
<app-search-bar
|
||||||
|
placeholder="Produkte nach Name oder SKU suchen..."
|
||||||
|
(search)="onSearch($event)"
|
||||||
|
></app-search-bar>
|
||||||
|
|
||||||
<div class="column-filter-container">
|
<div class="column-filter-container">
|
||||||
<app-button buttonType="stroked" (click)="toggleColumnFilter()" iconName="filter">Spalten</app-button>
|
<app-button buttonType="primary" iconName="plus" (click)="onAddNew()">
|
||||||
|
Neues Produkt
|
||||||
|
</app-button>
|
||||||
|
|
||||||
|
<app-button
|
||||||
|
buttonType="stroked"
|
||||||
|
(click)="toggleColumnFilter()"
|
||||||
|
iconName="filter"
|
||||||
|
>Spalten</app-button
|
||||||
|
>
|
||||||
<div class="column-filter-dropdown" *ngIf="isColumnFilterVisible">
|
<div class="column-filter-dropdown" *ngIf="isColumnFilterVisible">
|
||||||
<label *ngFor="let col of allTableColumns">
|
<label *ngFor="let col of allTableColumns">
|
||||||
<input type="checkbox" [checked]="isColumnVisible(col.key)" (change)="onColumnToggle(col, $event)">
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
[checked]="isColumnVisible(col.key)"
|
||||||
|
(change)="onColumnToggle(col, $event)"
|
||||||
|
/>
|
||||||
{{ col.title }}
|
{{ col.title }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -25,6 +39,7 @@
|
|||||||
[data]="filteredProducts"
|
[data]="filteredProducts"
|
||||||
[columns]="visibleTableColumns"
|
[columns]="visibleTableColumns"
|
||||||
(edit)="onEditProduct($event.id)"
|
(edit)="onEditProduct($event.id)"
|
||||||
(delete)="onDeleteProduct($event.id)">
|
(delete)="onDeleteProduct($event.id)"
|
||||||
|
>
|
||||||
</app-generic-table>
|
</app-generic-table>
|
||||||
</div>
|
</div>
|
||||||
@@ -11,12 +11,11 @@ import { StorageService } from '../../../../core/services/storage.service';
|
|||||||
import { GenericTableComponent, ColumnConfig } from '../../../../shared/components/data-display/generic-table/generic-table.component';
|
import { GenericTableComponent, ColumnConfig } from '../../../../shared/components/data-display/generic-table/generic-table.component';
|
||||||
import { SearchBarComponent } from '../../../../shared/components/layout/search-bar/search-bar.component';
|
import { SearchBarComponent } from '../../../../shared/components/layout/search-bar/search-bar.component';
|
||||||
import { ButtonComponent } from '../../../../shared/components/ui/button/button.component';
|
import { ButtonComponent } from '../../../../shared/components/ui/button/button.component';
|
||||||
import { IconComponent } from '../../../../shared/components/ui/icon/icon.component';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-product-list',
|
selector: 'app-product-list',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule, GenericTableComponent, SearchBarComponent, ButtonComponent, IconComponent],
|
imports: [CommonModule, GenericTableComponent, SearchBarComponent, ButtonComponent],
|
||||||
providers: [DatePipe],
|
providers: [DatePipe],
|
||||||
templateUrl: './product-list.component.html',
|
templateUrl: './product-list.component.html',
|
||||||
styleUrl: './product-list.component.css'
|
styleUrl: './product-list.component.css'
|
||||||
@@ -59,6 +58,8 @@ export class ProductListComponent implements OnInit {
|
|||||||
];
|
];
|
||||||
visibleTableColumns: ColumnConfig[] = [];
|
visibleTableColumns: ColumnConfig[] = [];
|
||||||
|
|
||||||
|
public readonly fallbackImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MCIgaGVpZ2h0PSI1MCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNjY2NjY2MiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cmVjdCB3aWR0aD0iMTgiIGhlaWdodD0iMTgiIHg9IjMiIHk9IjMiIHJ4PSIyIiByeT0iMiIvPjxjaXJjbGUgY3g9IjkiIGN5PSI5IiByPSIyIi8+PHBhdGggZD0ibTIxIDE1LTUtNWwtNSA1bC0yLTJsLTUgNSIvPjwvc3ZnPg==';
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.loadTableSettings();
|
this.loadTableSettings();
|
||||||
}
|
}
|
||||||
@@ -144,8 +145,8 @@ export class ProductListComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getMainImageUrl(images?: ProductImage[]): string {
|
getMainImageUrl(images?: ProductImage[]): string {
|
||||||
if (!images || images.length === 0) return 'https://via.placeholder.com/50';
|
if (!images || images.length === 0) return this.fallbackImage;
|
||||||
const mainImage = images.find(img => img.isMainImage);
|
const mainImage = images.find(img => img.isMainImage);
|
||||||
return mainImage?.url || images[0]?.url || 'https://via.placeholder.com/50';
|
return mainImage?.url || images[0]?.url || this.fallbackImage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -22,27 +22,51 @@
|
|||||||
<input type="number" formControlName="cost" placeholder="Kosten" />
|
<input type="number" formControlName="cost" placeholder="Kosten" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- +++ NEUE FELDER HINZUGEFÜGT +++ -->
|
<div style="display: flex; gap: 10px;">
|
||||||
<div>
|
<div>
|
||||||
<label>Minimale Liefertage:</label>
|
<label>Minimale Liefertage:</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
formControlName="minDeliveryDays"
|
formControlName="minDeliveryDays"
|
||||||
placeholder="z.B. 1"
|
placeholder="z.B. 1"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label>Maximale Liefertage:</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
formControlName="maxDeliveryDays"
|
||||||
|
placeholder="z.B. 3"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<!-- +++ NEUE GEWICHTS FELDER +++ -->
|
||||||
<label>Maximale Liefertage:</label>
|
<div style="display: flex; gap: 10px; margin-top: 10px;">
|
||||||
<input
|
<div>
|
||||||
type="number"
|
<label>Gewicht von (kg):</label>
|
||||||
formControlName="maxDeliveryDays"
|
<input
|
||||||
placeholder="z.B. 3"
|
type="number"
|
||||||
/>
|
formControlName="minWeight"
|
||||||
|
placeholder="0"
|
||||||
|
step="0.01"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label>Gewicht bis (kg):</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
formControlName="maxWeight"
|
||||||
|
placeholder="10"
|
||||||
|
step="0.01"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- +++ ENDE NEU +++ -->
|
<!-- +++ ENDE NEU +++ -->
|
||||||
|
|
||||||
<div>
|
<div style="margin-top: 10px;">
|
||||||
<label><input type="checkbox" formControlName="isActive" /> Aktiv</label>
|
<label><input type="checkbox" formControlName="isActive" /> Aktiv</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -55,16 +79,26 @@
|
|||||||
Abbrechen
|
Abbrechen
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<h2>Bestehende Methoden</h2>
|
<h2>Bestehende Methoden</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li *ngFor="let method of methods$ | async">
|
<li *ngFor="let method of methods$ | async" style="margin-bottom: 10px; border-bottom: 1px solid #ccc; padding-bottom: 5px;">
|
||||||
{{ method.name }}
|
<strong>{{ method.name }}</strong> ({{ method.cost | currency : "EUR" }}) <br/>
|
||||||
({{ method.cost | currency : "EUR" }}) - Lieferzeit:
|
|
||||||
{{ method.minDeliveryDays }}-{{ method.maxDeliveryDays }} Tage - Aktiv:
|
<!-- Anzeige der Details -->
|
||||||
{{ method.isActive }}
|
<small>
|
||||||
<button (click)="selectMethod(method)">Bearbeiten</button>
|
Lieferzeit: {{ method.minDeliveryDays }}-{{ method.maxDeliveryDays }} Tage |
|
||||||
<button (click)="onDelete(method.id)">Löschen</button>
|
<!-- NEU: Gewichtsanzeige -->
|
||||||
|
Gewicht: {{ method.minWeight }}kg - {{ method.maxWeight }}kg |
|
||||||
|
Aktiv: {{ method.isActive ? 'Ja' : 'Nein' }}
|
||||||
|
</small>
|
||||||
|
|
||||||
|
<div style="margin-top: 5px;">
|
||||||
|
<button (click)="selectMethod(method)">Bearbeiten</button>
|
||||||
|
<button (click)="onDelete(method.id)">Löschen</button>
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -26,7 +26,10 @@ export class ShippingMethodListComponent implements OnInit {
|
|||||||
cost: [0, [Validators.required, Validators.min(0)]],
|
cost: [0, [Validators.required, Validators.min(0)]],
|
||||||
isActive: [true],
|
isActive: [true],
|
||||||
minDeliveryDays: [1, [Validators.required, Validators.min(0)]],
|
minDeliveryDays: [1, [Validators.required, Validators.min(0)]],
|
||||||
maxDeliveryDays: [3, [Validators.required, Validators.min(0)]]
|
maxDeliveryDays: [3, [Validators.required, Validators.min(0)]],
|
||||||
|
// NEU: Validierung für Gewicht
|
||||||
|
minWeight: [0, [Validators.required, Validators.min(0)]],
|
||||||
|
maxWeight: [10, [Validators.required, Validators.min(0)]]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,15 +49,16 @@ export class ShippingMethodListComponent implements OnInit {
|
|||||||
cost: 0,
|
cost: 0,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
minDeliveryDays: 1,
|
minDeliveryDays: 1,
|
||||||
maxDeliveryDays: 3
|
maxDeliveryDays: 3,
|
||||||
|
// NEU: Reset Werte
|
||||||
|
minWeight: 0,
|
||||||
|
maxWeight: 10
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- KORREKTUR: onSubmit sendet jetzt direkt das Formularwert-Objekt als JSON ---
|
|
||||||
onSubmit(): void {
|
onSubmit(): void {
|
||||||
if (this.methodForm.invalid) return;
|
if (this.methodForm.invalid) return;
|
||||||
|
|
||||||
// Das Formular-Objekt hat bereits die richtige Struktur, die das Backend erwartet.
|
|
||||||
const dataToSend: ShippingMethod = {
|
const dataToSend: ShippingMethod = {
|
||||||
id: this.selectedMethodId || '00000000-0000-0000-0000-000000000000',
|
id: this.selectedMethodId || '00000000-0000-0000-0000-000000000000',
|
||||||
...this.methodForm.value
|
...this.methodForm.value
|
||||||
@@ -66,7 +70,6 @@ export class ShippingMethodListComponent implements OnInit {
|
|||||||
this.shippingMethodService.create(dataToSend).subscribe(() => this.reset());
|
this.shippingMethodService.create(dataToSend).subscribe(() => this.reset());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// --- ENDE KORREKTUR ---
|
|
||||||
|
|
||||||
onDelete(id: string): void {
|
onDelete(id: string): void {
|
||||||
if (confirm('Versandmethode wirklich löschen?')) {
|
if (confirm('Versandmethode wirklich löschen?')) {
|
||||||
|
|||||||
@@ -35,8 +35,8 @@
|
|||||||
|
|
||||||
<ng-container *ngSwitchCase="'image-text'">
|
<ng-container *ngSwitchCase="'image-text'">
|
||||||
<div class="user-cell">
|
<div class="user-cell">
|
||||||
<img [src]="getProperty(item, col.imageKey!) || 'https://via.placeholder.com/40'"
|
<img [src]="getProperty(item, col.imageKey!) || fallbackImage"
|
||||||
[alt]="'Bild von ' + getProperty(item, col.key)" />
|
alt="{{ item.name }}" />
|
||||||
<div>
|
<div>
|
||||||
<div class="user-name">{{ getProperty(item, col.key) }}</div>
|
<div class="user-name">{{ getProperty(item, col.key) }}</div>
|
||||||
<div class="user-email">{{ getProperty(item, col.subKey!) }}</div>
|
<div class="user-email">{{ getProperty(item, col.subKey!) }}</div>
|
||||||
@@ -45,8 +45,8 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngSwitchCase="'image'">
|
<ng-container *ngSwitchCase="'image'">
|
||||||
<img [src]="getProperty(item, col.key) || 'https://via.placeholder.com/50'"
|
<img [src]="getProperty(item, col.key) || fallbackImage"
|
||||||
alt="Bild"
|
alt="{{ item.name }}"
|
||||||
style="width: 50px; height: 50px; object-fit: cover; border-radius: 4px;">
|
style="width: 50px; height: 50px; object-fit: cover; border-radius: 4px;">
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export class GenericTableComponent implements OnChanges, OnInit {
|
|||||||
|
|
||||||
public displayedData: any[] = [];
|
public displayedData: any[] = [];
|
||||||
public currentPage = 1;
|
public currentPage = 1;
|
||||||
|
public readonly fallbackImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MCIgaGVpZ2h0PSI1MCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNjY2NjY2MiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cmVjdCB3aWR0aD0iMTgiIGhlaWdodD0iMTgiIHg9IjMiIHk9IjMiIHJ4PSIyIiByeT0iMiIvPjxjaXJjbGUgY3g9IjkiIGN5PSI5IiByPSIyIi8+PHBhdGggZD0ibTIxIDE1LTUtNWwtNSA1bC0yLTJsLTUgNSIvPjwvc3ZnPg==';
|
||||||
|
|
||||||
ngOnInit(): void { this.updatePagination(); }
|
ngOnInit(): void { this.updatePagination(); }
|
||||||
ngOnChanges(changes: SimpleChanges): void { if (changes['data']) { this.currentPage = 1; this.updatePagination(); } }
|
ngOnChanges(changes: SimpleChanges): void { if (changes['data']) { this.currentPage = 1; this.updatePagination(); } }
|
||||||
@@ -47,6 +48,7 @@ export class GenericTableComponent implements OnChanges, OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getProperty(item: any, key: string): any {
|
getProperty(item: any, key: string): any {
|
||||||
|
|
||||||
if (!key) return '';
|
if (!key) return '';
|
||||||
return key.split('.').reduce((obj, part) => obj && obj[part], item);
|
return key.split('.').reduce((obj, part) => obj && obj[part], item);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
iconName="chevron_backward"
|
iconName="chevron_backward"
|
||||||
(click)="goToPrevious()"
|
(click)="goToPrevious()"
|
||||||
[disabled]="currentPage === 1"
|
[disabled]="currentPage === 1"
|
||||||
tooltip="Vorherige Seite">
|
>
|
||||||
|
|
||||||
</app-button>
|
</app-button>
|
||||||
<app-button
|
<app-button
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
iconName="chevron_forward"
|
iconName="chevron_forward"
|
||||||
(click)="goToNext()"
|
(click)="goToNext()"
|
||||||
[disabled]="currentPage === totalPages"
|
[disabled]="currentPage === totalPages"
|
||||||
tooltip="Nächste Seite">
|
>
|
||||||
|
|
||||||
</app-button>
|
</app-button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -135,11 +135,20 @@
|
|||||||
transform: translateX(-50%) translateY(-12px);
|
transform: translateX(-50%) translateY(-12px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.btn.is-loading {
|
.btn.is-loading {
|
||||||
cursor: wait;
|
cursor: wait;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-content {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.btn-content span {
|
||||||
|
display: flex;
|
||||||
|
height: auto;
|
||||||
|
align-content: center;
|
||||||
|
flex-wrap: wrap-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
.btn-content.is-hidden {
|
.btn-content.is-hidden {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user