feat(back-end and front-end): back-office
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
import { ApplicationConfig, provideZoneChangeDetection, importProvidersFrom } from '@angular/core';
|
||||
import { provideRouter, withComponentInputBinding, withInMemoryScrolling, withViewTransitions } from '@angular/router';
|
||||
import { routes } from './app.routes';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideHttpClient, withInterceptors } from '@angular/common/http';
|
||||
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
|
||||
import { provideTranslateHttpLoader, TranslateHttpLoader } from '@ngx-translate/http-loader';
|
||||
import { adminAuthInterceptor } from './core/interceptors/admin-auth.interceptor';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
@@ -16,7 +17,9 @@ export const appConfig: ApplicationConfig = {
|
||||
scrollPositionRestoration: 'top'
|
||||
})
|
||||
),
|
||||
provideHttpClient(),
|
||||
provideHttpClient(
|
||||
withInterceptors([adminAuthInterceptor])
|
||||
),
|
||||
provideTranslateHttpLoader({
|
||||
prefix: './assets/i18n/',
|
||||
suffix: '.json'
|
||||
|
||||
@@ -37,6 +37,10 @@ const appChildRoutes: Routes = [
|
||||
path: '',
|
||||
loadChildren: () => import('./features/legal/legal.routes').then(m => m.LEGAL_ROUTES)
|
||||
},
|
||||
{
|
||||
path: 'admin',
|
||||
loadChildren: () => import('./features/admin/admin.routes').then(m => m.ADMIN_ROUTES)
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
redirectTo: ''
|
||||
|
||||
37
frontend/src/app/core/interceptors/admin-auth.interceptor.ts
Normal file
37
frontend/src/app/core/interceptors/admin-auth.interceptor.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { HttpErrorResponse, HttpInterceptorFn } from '@angular/common/http';
|
||||
import { inject } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { catchError, throwError } from 'rxjs';
|
||||
|
||||
const SUPPORTED_LANGS = new Set(['it', 'en', 'de', 'fr']);
|
||||
|
||||
function resolveLangFromUrl(url: string): string {
|
||||
const cleanUrl = (url || '').split('?')[0].split('#')[0];
|
||||
const segments = cleanUrl.split('/').filter(Boolean);
|
||||
if (segments.length > 0 && SUPPORTED_LANGS.has(segments[0])) {
|
||||
return segments[0];
|
||||
}
|
||||
return 'it';
|
||||
}
|
||||
|
||||
export const adminAuthInterceptor: HttpInterceptorFn = (req, next) => {
|
||||
if (!req.url.includes('/api/admin/')) {
|
||||
return next(req);
|
||||
}
|
||||
|
||||
const router = inject(Router);
|
||||
const request = req.clone({ withCredentials: true });
|
||||
const isLoginRequest = request.url.includes('/api/admin/auth/login');
|
||||
|
||||
return next(request).pipe(
|
||||
catchError((error: unknown) => {
|
||||
if (!isLoginRequest && error instanceof HttpErrorResponse && error.status === 401) {
|
||||
const lang = resolveLangFromUrl(router.url);
|
||||
if (!router.url.includes('/admin/login')) {
|
||||
void router.navigate(['/', lang, 'admin', 'login']);
|
||||
}
|
||||
}
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
};
|
||||
14
frontend/src/app/features/admin/admin.routes.ts
Normal file
14
frontend/src/app/features/admin/admin.routes.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Routes } from '@angular/router';
|
||||
import { adminAuthGuard } from './guards/admin-auth.guard';
|
||||
|
||||
export const ADMIN_ROUTES: Routes = [
|
||||
{
|
||||
path: 'login',
|
||||
loadComponent: () => import('./pages/admin-login.component').then(m => m.AdminLoginComponent)
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
canActivate: [adminAuthGuard],
|
||||
loadComponent: () => import('./pages/admin-dashboard.component').then(m => m.AdminDashboardComponent)
|
||||
}
|
||||
];
|
||||
41
frontend/src/app/features/admin/guards/admin-auth.guard.ts
Normal file
41
frontend/src/app/features/admin/guards/admin-auth.guard.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { inject } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivateFn, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
|
||||
import { catchError, map, Observable, of } from 'rxjs';
|
||||
import { AdminAuthService } from '../services/admin-auth.service';
|
||||
|
||||
const SUPPORTED_LANGS = new Set(['it', 'en', 'de', 'fr']);
|
||||
|
||||
function resolveLang(route: ActivatedRouteSnapshot): string {
|
||||
for (const level of route.pathFromRoot) {
|
||||
const candidate = level.paramMap.get('lang');
|
||||
if (candidate && SUPPORTED_LANGS.has(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return 'it';
|
||||
}
|
||||
|
||||
export const adminAuthGuard: CanActivateFn = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot
|
||||
): Observable<boolean | UrlTree> => {
|
||||
const authService = inject(AdminAuthService);
|
||||
const router = inject(Router);
|
||||
const lang = resolveLang(route);
|
||||
|
||||
return authService.me().pipe(
|
||||
map((isAuthenticated) => {
|
||||
if (isAuthenticated) {
|
||||
return true;
|
||||
}
|
||||
return router.createUrlTree(['/', lang, 'admin', 'login'], {
|
||||
queryParams: { redirect: state.url }
|
||||
});
|
||||
}),
|
||||
catchError(() => of(
|
||||
router.createUrlTree(['/', lang, 'admin', 'login'], {
|
||||
queryParams: { redirect: state.url }
|
||||
})
|
||||
))
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
<section class="admin-dashboard">
|
||||
<header class="dashboard-header">
|
||||
<div>
|
||||
<h1>Back-office ordini</h1>
|
||||
<p>Gestione pagamenti e dettaglio ordini</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<button type="button" (click)="loadOrders()" [disabled]="loading">Aggiorna</button>
|
||||
<button type="button" class="ghost" (click)="logout()">Logout</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<p class="error" *ngIf="errorMessage">{{ errorMessage }}</p>
|
||||
|
||||
<div class="table-wrap" *ngIf="!loading; else loadingTpl">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Ordine</th>
|
||||
<th>Email</th>
|
||||
<th>Stato</th>
|
||||
<th>Pagamento</th>
|
||||
<th>Totale</th>
|
||||
<th>Azioni</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let order of orders" [class.selected]="selectedOrder?.id === order.id">
|
||||
<td>{{ order.orderNumber }}</td>
|
||||
<td>{{ order.customerEmail }}</td>
|
||||
<td>{{ order.status }}</td>
|
||||
<td>{{ order.paymentStatus || 'PENDING' }}</td>
|
||||
<td>{{ order.totalChf | currency:'CHF':'symbol':'1.2-2' }}</td>
|
||||
<td class="actions">
|
||||
<button type="button" class="ghost" (click)="openDetails(order.id)">Dettaglio</button>
|
||||
<button
|
||||
type="button"
|
||||
(click)="confirmPayment(order.id)"
|
||||
[disabled]="confirmingOrderId === order.id || order.paymentStatus === 'COMPLETED'"
|
||||
>
|
||||
{{ confirmingOrderId === order.id ? 'Invio...' : 'Conferma pagamento' }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<section class="details" *ngIf="selectedOrder">
|
||||
<h2>Dettaglio ordine {{ selectedOrder.orderNumber }}</h2>
|
||||
<p *ngIf="detailLoading">Caricamento dettaglio...</p>
|
||||
<p><strong>Cliente:</strong> {{ selectedOrder.customerEmail }}</p>
|
||||
<p><strong>Pagamento:</strong> {{ selectedOrder.paymentStatus || 'PENDING' }}</p>
|
||||
|
||||
<div class="items">
|
||||
<div class="item" *ngFor="let item of selectedOrder.items">
|
||||
<p><strong>File:</strong> {{ item.originalFilename }}</p>
|
||||
<p><strong>Qta:</strong> {{ item.quantity }}</p>
|
||||
<p><strong>Prezzo riga:</strong> {{ item.lineTotalChf | currency:'CHF':'symbol':'1.2-2' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<ng-template #loadingTpl>
|
||||
<p>Caricamento ordini...</p>
|
||||
</ng-template>
|
||||
@@ -0,0 +1,119 @@
|
||||
.admin-dashboard {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.dashboard-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.dashboard-header h1 {
|
||||
margin: 0;
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
.dashboard-header p {
|
||||
margin: 0.35rem 0 0;
|
||||
color: #4b5a70;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
button {
|
||||
border: 0;
|
||||
border-radius: 10px;
|
||||
background: #0f3f6f;
|
||||
color: #fff;
|
||||
padding: 0.55rem 0.8rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button.ghost {
|
||||
background: #eef2f8;
|
||||
color: #163a5f;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.65;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.table-wrap {
|
||||
overflow: auto;
|
||||
border: 1px solid #d8e0ec;
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
thead {
|
||||
background: #f3f6fa;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
text-align: left;
|
||||
padding: 0.75rem;
|
||||
border-bottom: 1px solid #e5ebf4;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
td.actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
min-width: 210px;
|
||||
}
|
||||
|
||||
tr.selected {
|
||||
background: #f4f9ff;
|
||||
}
|
||||
|
||||
.details {
|
||||
margin-top: 1rem;
|
||||
background: #fff;
|
||||
border: 1px solid #d8e0ec;
|
||||
border-radius: 12px;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.details h2 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.items {
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
|
||||
}
|
||||
|
||||
.item {
|
||||
border: 1px solid #e5ebf4;
|
||||
border-radius: 10px;
|
||||
padding: 0.65rem;
|
||||
}
|
||||
|
||||
.item p {
|
||||
margin: 0.2rem 0;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #b4232c;
|
||||
margin-bottom: 0.9rem;
|
||||
}
|
||||
|
||||
@media (max-width: 820px) {
|
||||
.dashboard-header {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, inject, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { AdminAuthService } from '../services/admin-auth.service';
|
||||
import { AdminOrder, AdminOrdersService } from '../services/admin-orders.service';
|
||||
|
||||
const SUPPORTED_LANGS = new Set(['it', 'en', 'de', 'fr']);
|
||||
|
||||
@Component({
|
||||
selector: 'app-admin-dashboard',
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
templateUrl: './admin-dashboard.component.html',
|
||||
styleUrl: './admin-dashboard.component.scss'
|
||||
})
|
||||
export class AdminDashboardComponent implements OnInit {
|
||||
private readonly adminOrdersService = inject(AdminOrdersService);
|
||||
private readonly adminAuthService = inject(AdminAuthService);
|
||||
private readonly route = inject(ActivatedRoute);
|
||||
private readonly router = inject(Router);
|
||||
|
||||
orders: AdminOrder[] = [];
|
||||
selectedOrder: AdminOrder | null = null;
|
||||
loading = false;
|
||||
detailLoading = false;
|
||||
confirmingOrderId: string | null = null;
|
||||
errorMessage: string | null = null;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadOrders();
|
||||
}
|
||||
|
||||
loadOrders(): void {
|
||||
this.loading = true;
|
||||
this.errorMessage = null;
|
||||
this.adminOrdersService.listOrders().subscribe({
|
||||
next: (orders) => {
|
||||
this.orders = orders;
|
||||
this.loading = false;
|
||||
},
|
||||
error: () => {
|
||||
this.loading = false;
|
||||
this.errorMessage = 'Impossibile caricare gli ordini.';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
openDetails(orderId: string): void {
|
||||
this.detailLoading = true;
|
||||
this.adminOrdersService.getOrder(orderId).subscribe({
|
||||
next: (order) => {
|
||||
this.selectedOrder = order;
|
||||
this.detailLoading = false;
|
||||
},
|
||||
error: () => {
|
||||
this.detailLoading = false;
|
||||
this.errorMessage = 'Impossibile caricare il dettaglio ordine.';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
confirmPayment(orderId: string): void {
|
||||
this.confirmingOrderId = orderId;
|
||||
this.adminOrdersService.confirmPayment(orderId).subscribe({
|
||||
next: (updatedOrder) => {
|
||||
this.confirmingOrderId = null;
|
||||
this.orders = this.orders.map((order) => order.id === updatedOrder.id ? updatedOrder : order);
|
||||
if (this.selectedOrder?.id === updatedOrder.id) {
|
||||
this.selectedOrder = updatedOrder;
|
||||
}
|
||||
},
|
||||
error: () => {
|
||||
this.confirmingOrderId = null;
|
||||
this.errorMessage = 'Conferma pagamento non riuscita.';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
logout(): void {
|
||||
this.adminAuthService.logout().subscribe({
|
||||
next: () => {
|
||||
void this.router.navigate(['/', this.resolveLang(), 'admin', 'login']);
|
||||
},
|
||||
error: () => {
|
||||
void this.router.navigate(['/', this.resolveLang(), 'admin', 'login']);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private resolveLang(): string {
|
||||
for (const level of this.route.pathFromRoot) {
|
||||
const lang = level.snapshot.paramMap.get('lang');
|
||||
if (lang && SUPPORTED_LANGS.has(lang)) {
|
||||
return lang;
|
||||
}
|
||||
}
|
||||
return 'it';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<section class="admin-login-page">
|
||||
<div class="admin-login-card">
|
||||
<h1>Back-office</h1>
|
||||
<p>Inserisci la password condivisa.</p>
|
||||
|
||||
<form (ngSubmit)="submit()">
|
||||
<label for="admin-password">Password</label>
|
||||
<input
|
||||
id="admin-password"
|
||||
name="password"
|
||||
type="password"
|
||||
[(ngModel)]="password"
|
||||
[disabled]="loading"
|
||||
autocomplete="current-password"
|
||||
required
|
||||
/>
|
||||
|
||||
<button type="submit" [disabled]="loading || !password.trim()">
|
||||
{{ loading ? 'Accesso...' : 'Accedi' }}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@if (errorMessage) {
|
||||
<p class="error">{{ errorMessage }}</p>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,64 @@
|
||||
.admin-login-page {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 70vh;
|
||||
padding: 2rem 1rem;
|
||||
}
|
||||
|
||||
.admin-login-card {
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
background: #fff;
|
||||
border: 1px solid #d6dde8;
|
||||
border-radius: 14px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 20px 45px rgba(15, 23, 42, 0.08);
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0.5rem 0 1.25rem;
|
||||
color: #46546a;
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.65rem;
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
input {
|
||||
border: 1px solid #c3cedd;
|
||||
border-radius: 10px;
|
||||
padding: 0.75rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
button {
|
||||
border: 0;
|
||||
border-radius: 10px;
|
||||
background: #0f3f6f;
|
||||
color: #fff;
|
||||
padding: 0.75rem 0.9rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.65;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.error {
|
||||
margin-top: 1rem;
|
||||
color: #b0182a;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { AdminAuthService } from '../services/admin-auth.service';
|
||||
|
||||
const SUPPORTED_LANGS = new Set(['it', 'en', 'de', 'fr']);
|
||||
|
||||
@Component({
|
||||
selector: 'app-admin-login',
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule],
|
||||
templateUrl: './admin-login.component.html',
|
||||
styleUrl: './admin-login.component.scss'
|
||||
})
|
||||
export class AdminLoginComponent {
|
||||
private readonly authService = inject(AdminAuthService);
|
||||
private readonly router = inject(Router);
|
||||
private readonly route = inject(ActivatedRoute);
|
||||
|
||||
password = '';
|
||||
loading = false;
|
||||
errorMessage: string | null = null;
|
||||
|
||||
submit(): void {
|
||||
if (!this.password.trim() || this.loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
this.errorMessage = null;
|
||||
|
||||
this.authService.login(this.password).subscribe({
|
||||
next: (isAuthenticated) => {
|
||||
this.loading = false;
|
||||
if (!isAuthenticated) {
|
||||
this.errorMessage = 'Password non valida.';
|
||||
return;
|
||||
}
|
||||
|
||||
const redirect = this.route.snapshot.queryParamMap.get('redirect');
|
||||
if (redirect && redirect.startsWith('/')) {
|
||||
void this.router.navigateByUrl(redirect);
|
||||
return;
|
||||
}
|
||||
|
||||
void this.router.navigate(['/', this.resolveLang(), 'admin']);
|
||||
},
|
||||
error: () => {
|
||||
this.loading = false;
|
||||
this.errorMessage = 'Password non valida.';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private resolveLang(): string {
|
||||
for (const level of this.route.pathFromRoot) {
|
||||
const lang = level.snapshot.paramMap.get('lang');
|
||||
if (lang && SUPPORTED_LANGS.has(lang)) {
|
||||
return lang;
|
||||
}
|
||||
}
|
||||
return 'it';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { map, Observable } from 'rxjs';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
|
||||
interface AdminAuthResponse {
|
||||
authenticated: boolean;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AdminAuthService {
|
||||
private readonly http = inject(HttpClient);
|
||||
private readonly baseUrl = `${environment.apiUrl}/api/admin/auth`;
|
||||
|
||||
login(password: string): Observable<boolean> {
|
||||
return this.http.post<AdminAuthResponse>(`${this.baseUrl}/login`, { password }, { withCredentials: true }).pipe(
|
||||
map((response) => Boolean(response?.authenticated))
|
||||
);
|
||||
}
|
||||
|
||||
logout(): Observable<void> {
|
||||
return this.http.post<void>(`${this.baseUrl}/logout`, {}, { withCredentials: true });
|
||||
}
|
||||
|
||||
me(): Observable<boolean> {
|
||||
return this.http.get<AdminAuthResponse>(`${this.baseUrl}/me`, { withCredentials: true }).pipe(
|
||||
map((response) => Boolean(response?.authenticated))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
|
||||
export interface AdminOrderItem {
|
||||
id: string;
|
||||
originalFilename: string;
|
||||
materialCode: string;
|
||||
colorCode: string;
|
||||
quantity: number;
|
||||
printTimeSeconds: number;
|
||||
materialGrams: number;
|
||||
unitPriceChf: number;
|
||||
lineTotalChf: number;
|
||||
}
|
||||
|
||||
export interface AdminOrder {
|
||||
id: string;
|
||||
orderNumber: string;
|
||||
status: string;
|
||||
paymentStatus?: string;
|
||||
paymentMethod?: string;
|
||||
customerEmail: string;
|
||||
totalChf: number;
|
||||
createdAt: string;
|
||||
items: AdminOrderItem[];
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AdminOrdersService {
|
||||
private readonly http = inject(HttpClient);
|
||||
private readonly baseUrl = `${environment.apiUrl}/api/admin/orders`;
|
||||
|
||||
listOrders(): Observable<AdminOrder[]> {
|
||||
return this.http.get<AdminOrder[]>(this.baseUrl, { withCredentials: true });
|
||||
}
|
||||
|
||||
getOrder(orderId: string): Observable<AdminOrder> {
|
||||
return this.http.get<AdminOrder>(`${this.baseUrl}/${orderId}`, { withCredentials: true });
|
||||
}
|
||||
|
||||
confirmPayment(orderId: string): Observable<AdminOrder> {
|
||||
return this.http.post<AdminOrder>(`${this.baseUrl}/${orderId}/payments/confirm`, {}, { withCredentials: true });
|
||||
}
|
||||
}
|
||||
@@ -152,8 +152,6 @@ export class QuoteEstimatorService {
|
||||
getOptions(): Observable<OptionsResponse> {
|
||||
console.log('QuoteEstimatorService: Requesting options...');
|
||||
const headers: any = {};
|
||||
// @ts-ignore
|
||||
if (environment.basicAuth) headers['Authorization'] = 'Basic ' + btoa(environment.basicAuth);
|
||||
return this.http.get<OptionsResponse>(`${environment.apiUrl}/api/calculator/options`, { headers }).pipe(
|
||||
tap({
|
||||
next: (res) => console.log('QuoteEstimatorService: Options loaded', res),
|
||||
@@ -166,43 +164,31 @@ export class QuoteEstimatorService {
|
||||
|
||||
getQuoteSession(sessionId: string): Observable<any> {
|
||||
const headers: any = {};
|
||||
// @ts-ignore
|
||||
if (environment.basicAuth) headers['Authorization'] = 'Basic ' + btoa(environment.basicAuth);
|
||||
return this.http.get(`${environment.apiUrl}/api/quote-sessions/${sessionId}`, { headers });
|
||||
}
|
||||
|
||||
updateLineItem(lineItemId: string, changes: any): Observable<any> {
|
||||
const headers: any = {};
|
||||
// @ts-ignore
|
||||
if (environment.basicAuth) headers['Authorization'] = 'Basic ' + btoa(environment.basicAuth);
|
||||
return this.http.patch(`${environment.apiUrl}/api/quote-sessions/line-items/${lineItemId}`, changes, { headers });
|
||||
}
|
||||
|
||||
createOrder(sessionId: string, orderDetails: any): Observable<any> {
|
||||
const headers: any = {};
|
||||
// @ts-ignore
|
||||
if (environment.basicAuth) headers['Authorization'] = 'Basic ' + btoa(environment.basicAuth);
|
||||
return this.http.post(`${environment.apiUrl}/api/orders/from-quote/${sessionId}`, orderDetails, { headers });
|
||||
}
|
||||
|
||||
getOrder(orderId: string): Observable<any> {
|
||||
const headers: any = {};
|
||||
// @ts-ignore
|
||||
if (environment.basicAuth) headers['Authorization'] = 'Basic ' + btoa(environment.basicAuth);
|
||||
return this.http.get(`${environment.apiUrl}/api/orders/${orderId}`, { headers });
|
||||
}
|
||||
|
||||
reportPayment(orderId: string, method: string): Observable<any> {
|
||||
const headers: any = {};
|
||||
// @ts-ignore
|
||||
if (environment.basicAuth) headers['Authorization'] = 'Basic ' + btoa(environment.basicAuth);
|
||||
return this.http.post(`${environment.apiUrl}/api/orders/${orderId}/payments/report`, { method }, { headers });
|
||||
}
|
||||
|
||||
getOrderInvoice(orderId: string): Observable<Blob> {
|
||||
const headers: any = {};
|
||||
// @ts-ignore
|
||||
if (environment.basicAuth) headers['Authorization'] = 'Basic ' + btoa(environment.basicAuth);
|
||||
return this.http.get(`${environment.apiUrl}/api/orders/${orderId}/invoice`, {
|
||||
headers,
|
||||
responseType: 'blob'
|
||||
@@ -211,8 +197,6 @@ export class QuoteEstimatorService {
|
||||
|
||||
getOrderConfirmation(orderId: string): Observable<Blob> {
|
||||
const headers: any = {};
|
||||
// @ts-ignore
|
||||
if (environment.basicAuth) headers['Authorization'] = 'Basic ' + btoa(environment.basicAuth);
|
||||
return this.http.get(`${environment.apiUrl}/api/orders/${orderId}/confirmation`, {
|
||||
headers,
|
||||
responseType: 'blob'
|
||||
@@ -221,8 +205,6 @@ export class QuoteEstimatorService {
|
||||
|
||||
getTwintPayment(orderId: string): Observable<any> {
|
||||
const headers: any = {};
|
||||
// @ts-ignore
|
||||
if (environment.basicAuth) headers['Authorization'] = 'Basic ' + btoa(environment.basicAuth);
|
||||
return this.http.get(`${environment.apiUrl}/api/orders/${orderId}/twint`, { headers });
|
||||
}
|
||||
|
||||
@@ -236,8 +218,6 @@ export class QuoteEstimatorService {
|
||||
return new Observable(observer => {
|
||||
// 1. Create Session first
|
||||
const headers: any = {};
|
||||
// @ts-ignore
|
||||
if (environment.basicAuth) headers['Authorization'] = 'Basic ' + btoa(environment.basicAuth);
|
||||
|
||||
this.http.post<any>(`${environment.apiUrl}/api/quote-sessions`, {}, { headers }).subscribe({
|
||||
next: (sessionRes) => {
|
||||
@@ -347,8 +327,6 @@ export class QuoteEstimatorService {
|
||||
// Session File Retrieval
|
||||
getLineItemContent(sessionId: string, lineItemId: string): Observable<Blob> {
|
||||
const headers: any = {};
|
||||
// @ts-ignore
|
||||
if (environment.basicAuth) headers['Authorization'] = 'Basic ' + btoa(environment.basicAuth);
|
||||
return this.http.get(`${environment.apiUrl}/api/quote-sessions/${sessionId}/line-items/${lineItemId}/content`, {
|
||||
headers,
|
||||
responseType: 'blob'
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export const environment = {
|
||||
production: true,
|
||||
apiUrl: '',
|
||||
basicAuth: ''
|
||||
apiUrl: ''
|
||||
};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export const environment = {
|
||||
production: false,
|
||||
apiUrl: 'http://localhost:8000',
|
||||
basicAuth: 'fab:0presura' // Format: 'username:password'
|
||||
apiUrl: 'http://localhost:8000'
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user