Add loading indicators to product CRUD and dialog components
This commit is contained in:
@@ -47,6 +47,21 @@ th, td {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.product-list-root {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-list-loading-overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(255, 255, 255, 0.6);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 10;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
|
||||||
mat-paginator {
|
mat-paginator {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@@ -1,16 +1,27 @@
|
|||||||
<section class="crud">
|
<section class="crud">
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<button mat-raised-button color="primary" (click)="create()">
|
<button mat-raised-button
|
||||||
|
color="primary"
|
||||||
|
(click)="create()"
|
||||||
|
[disabled]="isLoading">
|
||||||
<mat-icon>add</mat-icon> Nouveau produit
|
<mat-icon>add</mat-icon> Nouveau produit
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="filter">
|
<mat-form-field appearance="outline" class="filter">
|
||||||
<mat-label>Filtrer</mat-label>
|
<mat-label>Filtrer</mat-label>
|
||||||
<input matInput [formControl]="filterCtrl" placeholder="Nom, ID, catégorie, marque, fournisseur…">
|
<input matInput
|
||||||
|
[formControl]="filterCtrl"
|
||||||
|
placeholder="Nom, ID, catégorie, marque, fournisseur…"
|
||||||
|
[disabled]="isLoading">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mat-elevation-z2">
|
<div class="mat-elevation-z2 product-list-root">
|
||||||
|
<!-- Overlay de chargement -->
|
||||||
|
<div class="product-list-loading-overlay" *ngIf="isLoading">
|
||||||
|
<mat-spinner diameter="48"></mat-spinner>
|
||||||
|
</div>
|
||||||
|
|
||||||
<table mat-table [dataSource]="dataSource" matSort>
|
<table mat-table [dataSource]="dataSource" matSort>
|
||||||
|
|
||||||
<ng-container matColumnDef="id">
|
<ng-container matColumnDef="id">
|
||||||
@@ -51,8 +62,19 @@
|
|||||||
<ng-container matColumnDef="actions">
|
<ng-container matColumnDef="actions">
|
||||||
<th mat-header-cell *matHeaderCellDef>Actions</th>
|
<th mat-header-cell *matHeaderCellDef>Actions</th>
|
||||||
<td mat-cell *matCellDef="let el">
|
<td mat-cell *matCellDef="let el">
|
||||||
<button mat-icon-button (click)="edit(el)" aria-label="edit"><mat-icon>edit</mat-icon></button>
|
<button mat-icon-button
|
||||||
<button mat-icon-button color="warn" (click)="remove(el)" aria-label="delete"><mat-icon>delete</mat-icon></button>
|
aria-label="edit"
|
||||||
|
(click)="edit(el)"
|
||||||
|
[disabled]="isLoading">
|
||||||
|
<mat-icon>edit</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button mat-icon-button
|
||||||
|
color="warn"
|
||||||
|
aria-label="delete"
|
||||||
|
(click)="remove(el)"
|
||||||
|
[disabled]="isLoading">
|
||||||
|
<mat-icon>delete</mat-icon>
|
||||||
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
@@ -60,10 +82,17 @@
|
|||||||
<tr mat-row *matRowDef="let row; columns: displayed;"></tr>
|
<tr mat-row *matRowDef="let row; columns: displayed;"></tr>
|
||||||
|
|
||||||
<tr class="mat-row" *matNoDataRow>
|
<tr class="mat-row" *matNoDataRow>
|
||||||
<td class="mat-cell" [attr.colspan]="displayed.length">Aucune donnée.</td>
|
<td class="mat-cell" [attr.colspan]="displayed.length">
|
||||||
|
Aucune donnée.
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<mat-paginator [pageSizeOptions]="[5,10,25,100]" [pageSize]="10" aria-label="Pagination"></mat-paginator>
|
<mat-paginator
|
||||||
|
[pageSizeOptions]="[5,10,25,100]"
|
||||||
|
[pageSize]="10"
|
||||||
|
aria-label="Pagination"
|
||||||
|
[disabled]="isLoading">
|
||||||
|
</mat-paginator>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ import {MatButton, MatIconButton} from '@angular/material/button';
|
|||||||
import {MatIcon} from '@angular/material/icon';
|
import {MatIcon} from '@angular/material/icon';
|
||||||
import {FormBuilder, ReactiveFormsModule} from '@angular/forms';
|
import {FormBuilder, ReactiveFormsModule} from '@angular/forms';
|
||||||
import {MatDialog, MatDialogModule} from '@angular/material/dialog';
|
import {MatDialog, MatDialogModule} from '@angular/material/dialog';
|
||||||
import {forkJoin} from 'rxjs';
|
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
|
||||||
|
import {forkJoin, finalize} from 'rxjs';
|
||||||
|
|
||||||
import {PsItem} from '../../interfaces/ps-item';
|
import {PsItem} from '../../interfaces/ps-item';
|
||||||
import {ProductListItem} from '../../interfaces/product-list-item';
|
import {ProductListItem} from '../../interfaces/product-list-item';
|
||||||
@@ -32,7 +33,8 @@ import {ProductDialogData, PsProductDialogComponent} from '../ps-product-dialog/
|
|||||||
MatSortModule, MatPaginatorModule,
|
MatSortModule, MatPaginatorModule,
|
||||||
MatFormField, MatLabel, MatInput,
|
MatFormField, MatLabel, MatInput,
|
||||||
MatButton, MatIconButton, MatIcon,
|
MatButton, MatIconButton, MatIcon,
|
||||||
MatDialogModule
|
MatDialogModule,
|
||||||
|
MatProgressSpinnerModule
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class PsProductCrudComponent implements OnInit {
|
export class PsProductCrudComponent implements OnInit {
|
||||||
@@ -40,26 +42,24 @@ export class PsProductCrudComponent implements OnInit {
|
|||||||
private readonly ps = inject(PrestashopService);
|
private readonly ps = inject(PrestashopService);
|
||||||
private readonly dialog = inject(MatDialog);
|
private readonly dialog = inject(MatDialog);
|
||||||
|
|
||||||
// référentiels
|
|
||||||
categories: PsItem[] = [];
|
categories: PsItem[] = [];
|
||||||
manufacturers: PsItem[] = [];
|
manufacturers: PsItem[] = [];
|
||||||
suppliers: PsItem[] = [];
|
suppliers: PsItem[] = [];
|
||||||
|
|
||||||
// maps d’affichage
|
|
||||||
private catMap = new Map<number, string>();
|
private catMap = new Map<number, string>();
|
||||||
private manMap = new Map<number, string>();
|
private manMap = new Map<number, string>();
|
||||||
private supMap = new Map<number, string>();
|
private supMap = new Map<number, string>();
|
||||||
|
|
||||||
// table
|
|
||||||
displayed: string[] = ['id', 'name', 'category', 'manufacturer', 'supplier', 'priceTtc', 'quantity', 'actions'];
|
displayed: string[] = ['id', 'name', 'category', 'manufacturer', 'supplier', 'priceTtc', 'quantity', 'actions'];
|
||||||
dataSource = new MatTableDataSource<any>([]);
|
dataSource = new MatTableDataSource<any>([]);
|
||||||
@ViewChild(MatPaginator) paginator!: MatPaginator;
|
@ViewChild(MatPaginator) paginator!: MatPaginator;
|
||||||
@ViewChild(MatSort) sort!: MatSort;
|
@ViewChild(MatSort) sort!: MatSort;
|
||||||
@ViewChild(MatTable) table!: MatTable<any>;
|
@ViewChild(MatTable) table!: MatTable<any>;
|
||||||
|
|
||||||
// filtre
|
|
||||||
filterCtrl = this.fb.control<string>('');
|
filterCtrl = this.fb.control<string>('');
|
||||||
|
|
||||||
|
isLoading = false;
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
forkJoin({
|
forkJoin({
|
||||||
cats: this.ps.list('categories'),
|
cats: this.ps.list('categories'),
|
||||||
@@ -73,6 +73,7 @@ export class PsProductCrudComponent implements OnInit {
|
|||||||
this.manMap = new Map(this.manufacturers.map(x => [x.id, x.name]));
|
this.manMap = new Map(this.manufacturers.map(x => [x.id, x.name]));
|
||||||
this.suppliers = sups ?? [];
|
this.suppliers = sups ?? [];
|
||||||
this.supMap = new Map(this.suppliers.map(x => [x.id, x.name]));
|
this.supMap = new Map(this.suppliers.map(x => [x.id, x.name]));
|
||||||
|
|
||||||
this.reload();
|
this.reload();
|
||||||
},
|
},
|
||||||
error: err => {
|
error: err => {
|
||||||
@@ -80,7 +81,6 @@ export class PsProductCrudComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// filtre client
|
|
||||||
this.filterCtrl.valueChanges.subscribe(v => {
|
this.filterCtrl.valueChanges.subscribe(v => {
|
||||||
this.dataSource.filter = (v ?? '').toString().trim().toLowerCase();
|
this.dataSource.filter = (v ?? '').toString().trim().toLowerCase();
|
||||||
if (this.paginator) this.paginator.firstPage();
|
if (this.paginator) this.paginator.firstPage();
|
||||||
@@ -133,10 +133,24 @@ export class PsProductCrudComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reload() {
|
reload() {
|
||||||
this.ps.listProducts().subscribe(p => this.bindProducts(p));
|
this.isLoading = true;
|
||||||
|
this.ps.listProducts()
|
||||||
|
.pipe(
|
||||||
|
finalize(() => {
|
||||||
|
this.isLoading = false;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.subscribe({
|
||||||
|
next: p => this.bindProducts(p),
|
||||||
|
error: err => {
|
||||||
|
console.error('Erreur lors du chargement des produits', err);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
create() {
|
create() {
|
||||||
|
if (this.isLoading) return;
|
||||||
|
|
||||||
const data: ProductDialogData = {
|
const data: ProductDialogData = {
|
||||||
mode: 'create',
|
mode: 'create',
|
||||||
refs: {
|
refs: {
|
||||||
@@ -145,12 +159,16 @@ export class PsProductCrudComponent implements OnInit {
|
|||||||
suppliers: this.suppliers
|
suppliers: this.suppliers
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.dialog.open(PsProductDialogComponent, {width: '900px', data}).afterClosed().subscribe(ok => {
|
this.dialog.open(PsProductDialogComponent, {width: '900px', data})
|
||||||
|
.afterClosed()
|
||||||
|
.subscribe(ok => {
|
||||||
if (ok) this.reload();
|
if (ok) this.reload();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
edit(row: ProductListItem & { priceHt?: number }) {
|
edit(row: ProductListItem & { priceHt?: number }) {
|
||||||
|
if (this.isLoading) return;
|
||||||
|
|
||||||
const data: ProductDialogData = {
|
const data: ProductDialogData = {
|
||||||
mode: 'edit',
|
mode: 'edit',
|
||||||
productRow: row,
|
productRow: row,
|
||||||
@@ -160,16 +178,29 @@ export class PsProductCrudComponent implements OnInit {
|
|||||||
suppliers: this.suppliers
|
suppliers: this.suppliers
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.dialog.open(PsProductDialogComponent, {width: '900px', data}).afterClosed().subscribe(ok => {
|
this.dialog.open(PsProductDialogComponent, {width: '900px', data})
|
||||||
|
.afterClosed()
|
||||||
|
.subscribe(ok => {
|
||||||
if (ok) this.reload();
|
if (ok) this.reload();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(row: ProductListItem) {
|
remove(row: ProductListItem) {
|
||||||
|
if (this.isLoading) return;
|
||||||
if (!confirm(`Supprimer le produit "${row.name}" (#${row.id}) ?`)) return;
|
if (!confirm(`Supprimer le produit "${row.name}" (#${row.id}) ?`)) return;
|
||||||
this.ps.deleteProduct(row.id).subscribe({
|
|
||||||
|
this.isLoading = true;
|
||||||
|
this.ps.deleteProduct(row.id)
|
||||||
|
.pipe(
|
||||||
|
finalize(() => {
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.subscribe({
|
||||||
next: () => this.reload(),
|
next: () => this.reload(),
|
||||||
error: (e: unknown) => alert('Erreur: ' + (e instanceof Error ? e.message : String(e)))
|
error: (e: unknown) => {
|
||||||
|
this.isLoading = false;
|
||||||
|
alert('Erreur: ' + (e instanceof Error ? e.message : String(e)));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -163,3 +163,19 @@
|
|||||||
.thumb-placeholder mat-icon {
|
.thumb-placeholder mat-icon {
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dialog-root {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Overlay plein écran dans le dialog pendant la sauvegarde */
|
||||||
|
.dialog-loading-overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(255, 255, 255, 0.6);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 50;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
<h2 mat-dialog-title>{{ mode === 'create' ? 'Nouveau produit' : 'Modifier le produit' }}</h2>
|
<h2 mat-dialog-title>{{ mode === 'create' ? 'Nouveau produit' : 'Modifier le produit' }}</h2>
|
||||||
|
|
||||||
|
<div class="dialog-root">
|
||||||
|
<!-- Overlay de chargement -->
|
||||||
|
@if (isSaving) {
|
||||||
|
<div class="dialog-loading-overlay">
|
||||||
|
<mat-spinner diameter="48"></mat-spinner>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<div mat-dialog-content class="grid" [formGroup]="form">
|
<div mat-dialog-content class="grid" [formGroup]="form">
|
||||||
|
|
||||||
<!-- CARROUSEL IMAGES -->
|
<!-- CARROUSEL IMAGES -->
|
||||||
@@ -152,11 +160,24 @@
|
|||||||
<input matInput type="number" step="1" min="0" formControlName="quantity">
|
<input matInput type="number" step="1" min="0" formControlName="quantity">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Actions -->
|
<!-- Actions -->
|
||||||
<div mat-dialog-actions>
|
<mat-dialog-actions align="end">
|
||||||
<button mat-button (click)="close()">Annuler</button>
|
<button mat-button
|
||||||
<button mat-raised-button color="primary" (click)="save()" [disabled]="form.invalid">
|
(click)="close()"
|
||||||
{{ mode === 'create' ? 'Créer' : 'Enregistrer' }}
|
[disabled]="isSaving">
|
||||||
|
Annuler
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
|
<button mat-raised-button
|
||||||
|
color="primary"
|
||||||
|
(click)="save()"
|
||||||
|
[disabled]="form.invalid || isSaving">
|
||||||
|
@if (!isSaving) {
|
||||||
|
Enregistrer
|
||||||
|
} @else {
|
||||||
|
Enregistrement...
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</mat-dialog-actions>
|
||||||
|
|||||||
@@ -15,11 +15,12 @@ import {
|
|||||||
} from '@angular/material/dialog';
|
} from '@angular/material/dialog';
|
||||||
import {MatIcon} from '@angular/material/icon';
|
import {MatIcon} from '@angular/material/icon';
|
||||||
|
|
||||||
import {catchError, forkJoin, of, Observable} from 'rxjs';
|
import {catchError, forkJoin, of, Observable, finalize} from 'rxjs';
|
||||||
|
|
||||||
import {PsItem} from '../../interfaces/ps-item';
|
import {PsItem} from '../../interfaces/ps-item';
|
||||||
import {ProductListItem} from '../../interfaces/product-list-item';
|
import {ProductListItem} from '../../interfaces/product-list-item';
|
||||||
import {PrestashopService} from '../../services/prestashop.serivce';
|
import {PrestashopService} from '../../services/prestashop.serivce';
|
||||||
|
import {MatProgressSpinner} from '@angular/material/progress-spinner';
|
||||||
|
|
||||||
export type ProductDialogData = {
|
export type ProductDialogData = {
|
||||||
mode: 'create' | 'edit';
|
mode: 'create' | 'edit';
|
||||||
@@ -38,7 +39,7 @@ type CarouselItem = { src: string; isPlaceholder: boolean };
|
|||||||
CommonModule, ReactiveFormsModule,
|
CommonModule, ReactiveFormsModule,
|
||||||
MatFormField, MatLabel, MatInput, MatSelectModule, MatCheckbox,
|
MatFormField, MatLabel, MatInput, MatSelectModule, MatCheckbox,
|
||||||
MatButton, MatDialogActions, MatDialogContent, MatDialogTitle,
|
MatButton, MatDialogActions, MatDialogContent, MatDialogTitle,
|
||||||
MatIcon, MatIconButton
|
MatIcon, MatIconButton, MatProgressSpinner
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class PsProductDialogComponent implements OnInit, OnDestroy {
|
export class PsProductDialogComponent implements OnInit, OnDestroy {
|
||||||
@@ -51,6 +52,8 @@ export class PsProductDialogComponent implements OnInit, OnDestroy {
|
|||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isSaving = false;
|
||||||
|
|
||||||
mode!: 'create' | 'edit';
|
mode!: 'create' | 'edit';
|
||||||
categories: PsItem[] = [];
|
categories: PsItem[] = [];
|
||||||
manufacturers: PsItem[] = [];
|
manufacturers: PsItem[] = [];
|
||||||
@@ -265,7 +268,10 @@ export class PsProductDialogComponent implements OnInit, OnDestroy {
|
|||||||
// -------- Save / close --------
|
// -------- Save / close --------
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
if (this.form.invalid) return;
|
if (this.form.invalid || this.isSaving) return;
|
||||||
|
|
||||||
|
this.isSaving = true;
|
||||||
|
this.dialogRef.disableClose = true;
|
||||||
|
|
||||||
const v = this.form.getRawValue();
|
const v = this.form.getRawValue();
|
||||||
const effectiveDescription = (v.description ?? '').trim() || this.lastLoadedDescription;
|
const effectiveDescription = (v.description ?? '').trim() || this.lastLoadedDescription;
|
||||||
@@ -276,7 +282,7 @@ export class PsProductDialogComponent implements OnInit, OnDestroy {
|
|||||||
categoryId: +v.categoryId!,
|
categoryId: +v.categoryId!,
|
||||||
manufacturerId: +v.manufacturerId!,
|
manufacturerId: +v.manufacturerId!,
|
||||||
supplierId: +v.supplierId!,
|
supplierId: +v.supplierId!,
|
||||||
images: this.images, // toujours les fichiers sélectionnés
|
images: this.images,
|
||||||
complete: !!v.complete,
|
complete: !!v.complete,
|
||||||
hasManual: !!v.hasManual,
|
hasManual: !!v.hasManual,
|
||||||
conditionLabel: v.conditionLabel || undefined,
|
conditionLabel: v.conditionLabel || undefined,
|
||||||
@@ -292,9 +298,18 @@ export class PsProductDialogComponent implements OnInit, OnDestroy {
|
|||||||
op$ = this.ps.updateProduct(this.productRow.id, dto) as Observable<unknown>;
|
op$ = this.ps.updateProduct(this.productRow.id, dto) as Observable<unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
op$.subscribe({
|
op$
|
||||||
|
.pipe(
|
||||||
|
finalize(() => {
|
||||||
|
// si la boîte de dialogue est encore ouverte, on réactive tout
|
||||||
|
this.isSaving = false;
|
||||||
|
this.dialogRef.disableClose = false;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.subscribe({
|
||||||
next: () => this.dialogRef.close(true),
|
next: () => this.dialogRef.close(true),
|
||||||
error: (e: unknown) => alert('Erreur: ' + (e instanceof Error ? e.message : String(e)))
|
error: (e: unknown) =>
|
||||||
|
alert('Erreur: ' + (e instanceof Error ? e.message : String(e)))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,6 +390,7 @@ export class PsProductDialogComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
|
if (this.isSaving) return;
|
||||||
this.dialogRef.close(false);
|
this.dialogRef.close(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -516,6 +516,16 @@ export class PrestashopService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteProductImage(productId: number, imageId: number) {
|
||||||
|
// Presta : DELETE /images/products/{id_product}/{id_image}
|
||||||
|
return this.http.delete(
|
||||||
|
`${this.base}/images/products/${productId}/${imageId}`,
|
||||||
|
{ responseType: 'text' }
|
||||||
|
).pipe(
|
||||||
|
map(() => true)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// -------- Stock (quantité) — gestion fine via stock_availables
|
// -------- Stock (quantité) — gestion fine via stock_availables
|
||||||
|
|
||||||
getProductQuantity(productId: number) {
|
getProductQuantity(productId: number) {
|
||||||
|
|||||||
@@ -2,4 +2,5 @@ export const environment = {
|
|||||||
production: true,
|
production: true,
|
||||||
apiUrl: '/gameovergne-api/api',
|
apiUrl: '/gameovergne-api/api',
|
||||||
psUrl: '/gameovergne-api/api/ps',
|
psUrl: '/gameovergne-api/api/ps',
|
||||||
|
indexBase: '/gameovergne/',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,4 +2,5 @@ export const environment = {
|
|||||||
production: false,
|
production: false,
|
||||||
apiUrl: 'http://localhost:3000/api',
|
apiUrl: 'http://localhost:3000/api',
|
||||||
psUrl: '/ps',
|
psUrl: '/ps',
|
||||||
|
indexBase: '/',
|
||||||
};
|
};
|
||||||
@@ -3,7 +3,11 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Game Over'gne App</title>
|
<title>Game Over'gne App</title>
|
||||||
<base href="/gameovergne/">
|
<script>
|
||||||
|
import {environment} from "./environments/environment.prod";
|
||||||
|
document.write('<base href="' + environment.indexBase + '">');
|
||||||
|
</script>
|
||||||
|
<base href="/">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
|
||||||
|
|||||||
Reference in New Issue
Block a user