feat(back-end front-end): new UX for
This commit is contained in:
@@ -152,6 +152,13 @@ export class QuoteEstimatorService {
|
||||
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
|
||||
|
||||
@@ -4,14 +4,41 @@
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="confirmation-layout">
|
||||
<div class="confirmation-layout" *ngIf="order() as o">
|
||||
<app-card class="status-card">
|
||||
<div class="status-badge">{{ 'ORDER_CONFIRMED.STATUS' | translate }}</div>
|
||||
<div class="status-badge">{{ o.status === 'SHIPPED' ? ('TRACKING.STEP_SHIPPED' | translate) : ('ORDER_CONFIRMED.STATUS' | translate) }}</div>
|
||||
<h2>{{ 'ORDER_CONFIRMED.HEADING' | translate }}</h2>
|
||||
<p class="order-ref" *ngIf="orderNumber">
|
||||
{{ 'ORDER_CONFIRMED.ORDER_REF' | translate }}: <strong>#{{ orderNumber }}</strong>
|
||||
</p>
|
||||
|
||||
<div class="status-timeline">
|
||||
<div class="timeline-step"
|
||||
[class.active]="o.status === 'PENDING_PAYMENT' && o.paymentStatus !== 'REPORTED'"
|
||||
[class.completed]="o.paymentStatus === 'REPORTED' || o.status !== 'PENDING_PAYMENT'">
|
||||
<div class="circle">1</div>
|
||||
<div class="label">{{ 'TRACKING.STEP_PENDING' | translate }}</div>
|
||||
</div>
|
||||
<div class="timeline-step"
|
||||
[class.active]="o.paymentStatus === 'REPORTED' && o.status === 'PENDING_PAYMENT'"
|
||||
[class.completed]="o.status === 'PAID' || o.status === 'IN_PRODUCTION' || o.status === 'SHIPPED' || o.status === 'COMPLETED'">
|
||||
<div class="circle">2</div>
|
||||
<div class="label">{{ 'TRACKING.STEP_REPORTED' | translate }}</div>
|
||||
</div>
|
||||
<div class="timeline-step"
|
||||
[class.active]="o.status === 'PAID' || o.status === 'IN_PRODUCTION'"
|
||||
[class.completed]="o.status === 'SHIPPED' || o.status === 'COMPLETED'">
|
||||
<div class="circle">3</div>
|
||||
<div class="label">{{ 'TRACKING.STEP_PRODUCTION' | translate }}</div>
|
||||
</div>
|
||||
<div class="timeline-step"
|
||||
[class.active]="o.status === 'SHIPPED'"
|
||||
[class.completed]="o.status === 'COMPLETED'">
|
||||
<div class="circle">4</div>
|
||||
<div class="label">{{ 'TRACKING.STEP_SHIPPED' | translate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="message-block">
|
||||
<p>{{ 'ORDER_CONFIRMED.PROCESSING_TEXT' | translate }}</p>
|
||||
<p>{{ 'ORDER_CONFIRMED.EMAIL_TEXT' | translate }}</p>
|
||||
|
||||
@@ -60,3 +60,100 @@ h2 {
|
||||
.actions {
|
||||
max-width: 320px;
|
||||
}
|
||||
|
||||
.status-timeline {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: var(--space-6);
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
left: 20px;
|
||||
right: 20px;
|
||||
height: 2px;
|
||||
background: var(--color-border);
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.timeline-step {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
|
||||
.circle {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-neutral-100);
|
||||
border: 2px solid var(--color-border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 600;
|
||||
margin-bottom: var(--space-2);
|
||||
color: var(--color-text-muted);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 0.85rem;
|
||||
color: var(--color-text-muted);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&.active {
|
||||
.circle {
|
||||
border-color: var(--color-primary);
|
||||
background: var(--color-primary-light);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
.label {
|
||||
color: var(--color-text);
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
&.completed {
|
||||
.circle {
|
||||
background: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
color: white;
|
||||
}
|
||||
.label {
|
||||
color: var(--color-text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.status-timeline {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: var(--space-4);
|
||||
|
||||
&::before {
|
||||
top: 10px;
|
||||
bottom: 10px;
|
||||
left: 15px;
|
||||
width: 2px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.timeline-step {
|
||||
flex-direction: row;
|
||||
gap: var(--space-3);
|
||||
|
||||
.circle {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, OnInit, inject } from '@angular/core';
|
||||
import { Component, OnInit, inject, signal } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
@@ -20,6 +20,7 @@ export class OrderConfirmedComponent implements OnInit {
|
||||
|
||||
orderId: string | null = null;
|
||||
orderNumber: string | null = null;
|
||||
order = signal<any>(null);
|
||||
|
||||
ngOnInit(): void {
|
||||
this.orderId = this.route.snapshot.paramMap.get('orderId');
|
||||
@@ -30,6 +31,7 @@ export class OrderConfirmedComponent implements OnInit {
|
||||
this.orderNumber = this.extractOrderNumber(this.orderId);
|
||||
this.quoteService.getOrder(this.orderId).subscribe({
|
||||
next: (order) => {
|
||||
this.order.set(order);
|
||||
this.orderNumber = order?.orderNumber ?? this.orderNumber;
|
||||
},
|
||||
error: () => {
|
||||
|
||||
@@ -6,6 +6,13 @@
|
||||
<div class="container">
|
||||
<div class="payment-layout" *ngIf="order() as o">
|
||||
<div class="payment-main">
|
||||
<app-card class="mb-6 status-reported-card" *ngIf="o.paymentStatus === 'REPORTED'">
|
||||
<div class="status-content text-center">
|
||||
<h3>{{ 'PAYMENT.STATUS_REPORTED_TITLE' | translate }}</h3>
|
||||
<p>{{ 'PAYMENT.STATUS_REPORTED_DESC' | translate }}</p>
|
||||
</div>
|
||||
</app-card>
|
||||
|
||||
<app-card class="mb-6">
|
||||
<div class="card-header-simple">
|
||||
<h3>{{ 'PAYMENT.METHOD' | translate }}</h3>
|
||||
@@ -69,8 +76,11 @@
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<app-button (click)="completeOrder()" [disabled]="!selectedPaymentMethod" [fullWidth]="true">
|
||||
{{ 'PAYMENT.CONFIRM' | translate }}
|
||||
<app-button
|
||||
(click)="completeOrder()"
|
||||
[disabled]="!selectedPaymentMethod || o.paymentStatus === 'REPORTED'"
|
||||
[fullWidth]="true">
|
||||
{{ o.paymentStatus === 'REPORTED' ? ('PAYMENT.IN_VERIFICATION' | translate) : ('PAYMENT.CONFIRM' | translate) }}
|
||||
</app-button>
|
||||
</div>
|
||||
</app-card>
|
||||
|
||||
@@ -116,11 +116,22 @@ export class PaymentComponent implements OnInit {
|
||||
}
|
||||
|
||||
completeOrder(): void {
|
||||
if (!this.orderId) {
|
||||
this.router.navigate(['/']);
|
||||
if (!this.orderId || !this.selectedPaymentMethod) {
|
||||
return;
|
||||
}
|
||||
this.router.navigate(['/order-confirmed', this.orderId]);
|
||||
|
||||
this.quoteService.reportPayment(this.orderId, this.selectedPaymentMethod).subscribe({
|
||||
next: (order) => {
|
||||
this.order.set(order);
|
||||
// The UI will re-render and show the 'REPORTED' state.
|
||||
// We stay on this page to let the user see the "In verifica"
|
||||
// status along with payment instructions.
|
||||
},
|
||||
error: (err) => {
|
||||
console.error('Failed to report payment', err);
|
||||
this.error.set('Failed to report payment. Please try again.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getDisplayOrderNumber(order: any): string {
|
||||
|
||||
@@ -194,7 +194,18 @@
|
||||
"SHIPPING": "Shipping",
|
||||
"SETUP_FEE": "Setup Fee",
|
||||
"TOTAL": "Total",
|
||||
"LOADING": "Loading order details..."
|
||||
"LOADING": "Loading order details...",
|
||||
"METHOD_TWINT": "TWINT",
|
||||
"METHOD_BANK": "Bank Transfer / QR",
|
||||
"STATUS_REPORTED_TITLE": "Payment Reported",
|
||||
"STATUS_REPORTED_DESC": "We are verifying your transaction. Your order will move to production as soon as the payment is confirmed.",
|
||||
"IN_VERIFICATION": "Verifying Payment"
|
||||
},
|
||||
"TRACKING": {
|
||||
"STEP_PENDING": "Pending",
|
||||
"STEP_REPORTED": "Verifying",
|
||||
"STEP_PRODUCTION": "Production",
|
||||
"STEP_SHIPPED": "Shipped"
|
||||
},
|
||||
"ORDER_CONFIRMED": {
|
||||
"TITLE": "Order Confirmed",
|
||||
|
||||
@@ -267,7 +267,16 @@
|
||||
"TOTAL": "Totale",
|
||||
"LOADING": "Caricamento dettagli ordine...",
|
||||
"METHOD_TWINT": "TWINT",
|
||||
"METHOD_BANK": "Fattura QR / Bonifico"
|
||||
"METHOD_BANK": "Fattura QR / Bonifico",
|
||||
"STATUS_REPORTED_TITLE": "Abbiamo ricevuto la tua segnalazione",
|
||||
"STATUS_REPORTED_DESC": "Stiamo verificando la transazione. Il tuo ordine passerà in produzione non appena l'accredito sarà confermato.",
|
||||
"IN_VERIFICATION": "Pagamento in verifica"
|
||||
},
|
||||
"TRACKING": {
|
||||
"STEP_PENDING": "In attesa",
|
||||
"STEP_REPORTED": "In verifica",
|
||||
"STEP_PRODUCTION": "In Produzione",
|
||||
"STEP_SHIPPED": "Spedito"
|
||||
},
|
||||
"ORDER_CONFIRMED": {
|
||||
"TITLE": "Ordine Confermato",
|
||||
|
||||
Reference in New Issue
Block a user