From 699a968875ec79ceb97461822de4056cd5a98d4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joe=20K=C3=BCng?= Date: Tue, 24 Feb 2026 08:44:42 +0100 Subject: [PATCH] feat(back-end front-end): upgrade to the order componen instead of payment and order-confirmed --- backend/build.gradle | 3 +- .../event/listener/OrderEmailListener.java | 2 +- .../service/PaymentService.java | 12 +- .../src/main/resources/templates/invoice.html | 625 +++++++++--------- .../listener/OrderEmailListenerTest.java | 4 +- db.sql | 2 +- frontend/src/app/app.routes.ts | 12 +- .../features/checkout/checkout.component.ts | 3 +- .../src/app/features/home/home.component.html | 1 - .../order-confirmed.component.html | 52 -- .../order-confirmed.component.scss | 159 ----- .../order-confirmed.component.ts | 50 -- .../app/features/order/order.component.html | 151 +++++ .../order.component.scss} | 98 +++ .../order.component.ts} | 10 +- .../features/payment/payment.component.html | 129 ---- .../features/shop/shop-page.component.html | 26 +- .../features/shop/shop-page.component.scss | 77 ++- .../app/features/shop/shop-page.component.ts | 18 +- frontend/src/assets/i18n/en.json | 21 +- frontend/src/assets/i18n/it.json | 23 +- 21 files changed, 717 insertions(+), 761 deletions(-) delete mode 100644 frontend/src/app/features/order-confirmed/order-confirmed.component.html delete mode 100644 frontend/src/app/features/order-confirmed/order-confirmed.component.scss delete mode 100644 frontend/src/app/features/order-confirmed/order-confirmed.component.ts create mode 100644 frontend/src/app/features/order/order.component.html rename frontend/src/app/features/{payment/payment.component.scss => order/order.component.scss} (70%) rename frontend/src/app/features/{payment/payment.component.ts => order/order.component.ts} (95%) delete mode 100644 frontend/src/app/features/payment/payment.component.html diff --git a/backend/build.gradle b/backend/build.gradle index b465785..6a74ea1 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -26,7 +26,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'xyz.capybara:clamav-client:2.1.2' - runtimeOnly 'org.postgresql:postgresql' + implementation 'org.springframework.boot:spring-boot-starter-actuator' + runtimeOnly 'org.postgresql:postgresql' developmentOnly 'org.springframework.boot:spring-boot-devtools' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'com.h2database:h2' diff --git a/backend/src/main/java/com/printcalculator/event/listener/OrderEmailListener.java b/backend/src/main/java/com/printcalculator/event/listener/OrderEmailListener.java index bd7761d..df361eb 100644 --- a/backend/src/main/java/com/printcalculator/event/listener/OrderEmailListener.java +++ b/backend/src/main/java/com/printcalculator/event/listener/OrderEmailListener.java @@ -122,6 +122,6 @@ public class OrderEmailListener { private String buildOrderDetailsUrl(Order order) { String baseUrl = frontendBaseUrl == null ? "" : frontendBaseUrl.replaceAll("/+$", ""); - return baseUrl + "/ordine/" + order.getId(); + return baseUrl + "/co/" + order.getId(); } } diff --git a/backend/src/main/java/com/printcalculator/service/PaymentService.java b/backend/src/main/java/com/printcalculator/service/PaymentService.java index 2588523..3948d22 100644 --- a/backend/src/main/java/com/printcalculator/service/PaymentService.java +++ b/backend/src/main/java/com/printcalculator/service/PaymentService.java @@ -38,7 +38,8 @@ public class PaymentService { Payment payment = new Payment(); payment.setOrder(order); - payment.setMethod(defaultMethod != null ? defaultMethod : "OTHER"); + // Default to "OTHER" always, as payment method should only be set by the admin explicitly + payment.setMethod("OTHER"); payment.setStatus("PENDING"); payment.setCurrency(order.getCurrency() != null ? order.getCurrency() : "CHF"); payment.setAmountChf(order.getTotalChf() != null ? order.getTotalChf() : BigDecimal.ZERO); @@ -53,7 +54,7 @@ public class PaymentService { .orElseThrow(() -> new RuntimeException("Order not found with id " + orderId)); Payment payment = paymentRepo.findByOrder_Id(orderId) - .orElseThrow(() -> new RuntimeException("No active payment found for order " + orderId)); + .orElseGet(() -> getOrCreatePaymentForOrder(order, "OTHER")); if (!"PENDING".equals(payment.getStatus())) { throw new IllegalStateException("Payment is not in PENDING state. Current state: " + payment.getStatus()); @@ -61,9 +62,10 @@ public class PaymentService { payment.setStatus("REPORTED"); payment.setReportedAt(OffsetDateTime.now()); - if (method != null && !method.isBlank()) { - payment.setMethod(method); - } + + // We intentionally do not update the payment method here based on user input, + // because the user cannot reliably determine the actual method without an integration. + // It will be updated by the backoffice admin manually. payment = paymentRepo.save(payment); diff --git a/backend/src/main/resources/templates/invoice.html b/backend/src/main/resources/templates/invoice.html index 87677d3..3f6cb55 100644 --- a/backend/src/main/resources/templates/invoice.html +++ b/backend/src/main/resources/templates/invoice.html @@ -1,358 +1,379 @@ - - + .qr-bill-bottom { + position: absolute; + left: 0; + bottom: 0; + width: 100%; + height: 105mm; + overflow: hidden; + background: #fff; + } + + .qr-bill-bottom svg { + width: 100% !important; + height: 105mm !important; + display: block; + } +
- - - - - -
-
Conferma ordine
-
Ricevuta semplificata
-
-
3D Fab Switzerland
-
Sede Ticino, Svizzera
-
Sede Bienne, Svizzera
-
info@3dfab.ch
-
+ + + + + + + +
+ 3D-fab.ch + +
3D Fab Switzerland
+
Sede Ticino, Svizzera
+
Sede Bienne, Svizzera
+
+ www.3d-fab.ch +
- - - - - -
- - - - - - - - - - - - - - - - - -
Data ordine / fattura2026-02-13
Numero documentoINV-2026-000123
Data di scadenza2026-02-20
ValutaCHF
-
-
Cliente
-
Cliente SA
-
Via Cliente 7
-
8000 Zürich, CH
-
+ +
+ Conferma dell'ordine 141052743 +
- - - - - - - - - - - - - - - - - -
DescrizioneQtàPrezzo unit.Totale
Stampa 3D pezzo X1CHF 10.00CHF 10.00
- - - - - - -
-
Informazioni
-
- Appena riceviamo il pagamento l'ordine entra nella coda di stampa. Grazie per la fiducia. -
-
- Verifica i dettagli dell'ordine al ricevimento. Per assistenza, rispondi alla nostra email di conferma. -
-
- + +
- - + + - - - +
SubtotaleCHF 10.00 + + + + + + + + + + + + + + + + + + + + + +
Data dell'ordine / fattura07.03.2025
Numero documentoINV-2026-000123
Data di scadenza07.03.2025
Metodo di pagamentoQR / Bonifico oppure TWINT
ValutaCHF
+
+
Joe Küng
+
Via G.Pioda, 29a
+
6710 biasca
+
Svizzera
+
Totale ordineCHF 10.00
+ + + + + + + + + - - - + + + + + + + -
DescrizioneQuantitàPrezzo unitarioPrezzo incl.
Importo dovutoCHF 10.00
Apple iPhone 16 Pro1968.551'047.00
-
+ + + + + + + + + + + + + + + + + +
Importo totale1'012.86
Totale di tutte le consegne e di tutti i servizi CHF1'094.90
Importo dovuto1'094.90
+ + + + + + + + + + + +
-
-
-
+ +
+
+
diff --git a/backend/src/test/java/com/printcalculator/event/listener/OrderEmailListenerTest.java b/backend/src/test/java/com/printcalculator/event/listener/OrderEmailListenerTest.java index 688bcf5..291beec 100644 --- a/backend/src/test/java/com/printcalculator/event/listener/OrderEmailListenerTest.java +++ b/backend/src/test/java/com/printcalculator/event/listener/OrderEmailListenerTest.java @@ -56,7 +56,7 @@ class OrderEmailListenerTest { ReflectionTestUtils.setField(orderEmailListener, "adminMailEnabled", true); ReflectionTestUtils.setField(orderEmailListener, "adminMailAddress", "admin@printcalculator.local"); - ReflectionTestUtils.setField(orderEmailListener, "frontendBaseUrl", "https://tuosito.it"); + ReflectionTestUtils.setField(orderEmailListener, "frontendBaseUrl", "https://3d-fab.ch"); } @Test @@ -76,7 +76,7 @@ class OrderEmailListenerTest { assertEquals("John", customerData.get("customerName")); assertEquals(order.getId(), customerData.get("orderId")); assertEquals(order.getOrderNumber(), customerData.get("orderNumber")); - assertEquals("https://tuosito.it/ordine/" + order.getId(), customerData.get("orderDetailsUrl")); + assertEquals("https://3d-fab.ch/co/" + order.getId(), customerData.get("orderDetailsUrl")); assertEquals(order.getCreatedAt().format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm")), customerData.get("orderDate")); assertEquals("150.50", customerData.get("totalCost")); diff --git a/db.sql b/db.sql index e5a061e..8c4bf80 100644 --- a/db.sql +++ b/db.sql @@ -552,7 +552,7 @@ CREATE TABLE IF NOT EXISTS payments payment_id uuid PRIMARY KEY DEFAULT gen_random_uuid(), order_id uuid NOT NULL REFERENCES orders (order_id) ON DELETE CASCADE, - method text NOT NULL CHECK (method IN ('QR_BILL', 'BANK_TRANSFER', 'TWINT', 'CARD', 'OTHER')), + method text NOT NULL CHECK (method IN ('QR_BILL', 'TWINT', 'OTHER')), status text NOT NULL CHECK (status IN ('PENDING', 'REPORTED', 'RECEIVED', 'FAILED', 'CANCELLED', 'REFUNDED')), currency char(3) NOT NULL DEFAULT 'CHF', diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts index 2e6cc2c..4e80bcd 100644 --- a/frontend/src/app/app.routes.ts +++ b/frontend/src/app/app.routes.ts @@ -30,16 +30,12 @@ export const routes: Routes = [ loadComponent: () => import('./features/checkout/checkout.component').then(m => m.CheckoutComponent) }, { - path: 'payment/:orderId', - loadComponent: () => import('./features/payment/payment.component').then(m => m.PaymentComponent) + path: 'order/:orderId', + loadComponent: () => import('./features/order/order.component').then(m => m.OrderComponent) }, { - path: 'ordine/:orderId', - loadComponent: () => import('./features/payment/payment.component').then(m => m.PaymentComponent) - }, - { - path: 'order-confirmed/:orderId', - loadComponent: () => import('./features/order-confirmed/order-confirmed.component').then(m => m.OrderConfirmedComponent) + path: 'co/:orderId', + loadComponent: () => import('./features/order/order.component').then(m => m.OrderComponent) }, { path: '', diff --git a/frontend/src/app/features/checkout/checkout.component.ts b/frontend/src/app/features/checkout/checkout.component.ts index 78a2aa2..f9632ed 100644 --- a/frontend/src/app/features/checkout/checkout.component.ts +++ b/frontend/src/app/features/checkout/checkout.component.ts @@ -193,8 +193,7 @@ export class CheckoutComponent implements OnInit { this.quoteService.createOrder(this.sessionId, orderRequest).subscribe({ next: (order) => { - console.log('Order created', order); - this.router.navigate(['/payment', order.id]); + this.router.navigate(['/order', order.id]); }, error: (err) => { console.error('Order creation failed', err); diff --git a/frontend/src/app/features/home/home.component.html b/frontend/src/app/features/home/home.component.html index 5aca4a3..ba263cf 100644 --- a/frontend/src/app/features/home/home.component.html +++ b/frontend/src/app/features/home/home.component.html @@ -28,7 +28,6 @@

diff --git a/frontend/src/app/features/order-confirmed/order-confirmed.component.html b/frontend/src/app/features/order-confirmed/order-confirmed.component.html deleted file mode 100644 index 07014f3..0000000 --- a/frontend/src/app/features/order-confirmed/order-confirmed.component.html +++ /dev/null @@ -1,52 +0,0 @@ -
-

{{ 'ORDER_CONFIRMED.TITLE' | translate }}

-

{{ 'ORDER_CONFIRMED.SUBTITLE' | translate }}

-
- -
-
- -
{{ o.status === 'SHIPPED' ? ('TRACKING.STEP_SHIPPED' | translate) : ('ORDER_CONFIRMED.STATUS' | translate) }}
-

{{ 'ORDER_CONFIRMED.HEADING' | translate }}

-

- {{ 'ORDER_CONFIRMED.ORDER_REF' | translate }}: #{{ orderNumber }} -

- -
-
-
1
-
{{ 'TRACKING.STEP_PENDING' | translate }}
-
-
-
2
-
{{ 'TRACKING.STEP_REPORTED' | translate }}
-
-
-
3
-
{{ 'TRACKING.STEP_PRODUCTION' | translate }}
-
-
-
4
-
{{ 'TRACKING.STEP_SHIPPED' | translate }}
-
-
- -
-

{{ 'ORDER_CONFIRMED.PROCESSING_TEXT' | translate }}

-

{{ 'ORDER_CONFIRMED.EMAIL_TEXT' | translate }}

-
- -
- {{ 'ORDER_CONFIRMED.BACK_HOME' | translate }} -
-
-
-
diff --git a/frontend/src/app/features/order-confirmed/order-confirmed.component.scss b/frontend/src/app/features/order-confirmed/order-confirmed.component.scss deleted file mode 100644 index bf42e17..0000000 --- a/frontend/src/app/features/order-confirmed/order-confirmed.component.scss +++ /dev/null @@ -1,159 +0,0 @@ -.hero { - padding: var(--space-12) 0 var(--space-8); - text-align: center; - - h1 { - font-size: 2.4rem; - margin-bottom: var(--space-2); - } -} - -.subtitle { - font-size: 1.1rem; - color: var(--color-text-muted); - max-width: 720px; - margin: 0 auto; -} - -.confirmation-layout { - max-width: 760px; - margin: 0 auto var(--space-12); -} - -.status-badge { - display: inline-block; - padding: 0.35rem 0.65rem; - border-radius: 999px; - background: #eef8f0; - color: #136f2d; - font-weight: 700; - font-size: 0.85rem; - margin-bottom: var(--space-4); -} - -h2 { - margin: 0 0 var(--space-3); -} - -.order-ref { - margin: 0 0 var(--space-4); - color: var(--color-text-muted); -} - -.message-block { - background: var(--color-neutral-100); - border: 1px solid var(--color-border); - border-radius: var(--radius-md); - padding: var(--space-5); - margin-bottom: var(--space-6); - - p { - margin: 0; - line-height: 1.45; - } - - p + p { - margin-top: var(--space-3); - } -} - -.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; - } - } - } -} diff --git a/frontend/src/app/features/order-confirmed/order-confirmed.component.ts b/frontend/src/app/features/order-confirmed/order-confirmed.component.ts deleted file mode 100644 index 8204dd0..0000000 --- a/frontend/src/app/features/order-confirmed/order-confirmed.component.ts +++ /dev/null @@ -1,50 +0,0 @@ -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'; -import { AppButtonComponent } from '../../shared/components/app-button/app-button.component'; -import { AppCardComponent } from '../../shared/components/app-card/app-card.component'; -import { QuoteEstimatorService } from '../calculator/services/quote-estimator.service'; - -@Component({ - selector: 'app-order-confirmed', - standalone: true, - imports: [CommonModule, TranslateModule, AppButtonComponent, AppCardComponent], - templateUrl: './order-confirmed.component.html', - styleUrl: './order-confirmed.component.scss' -}) -export class OrderConfirmedComponent implements OnInit { - private route = inject(ActivatedRoute); - private router = inject(Router); - private quoteService = inject(QuoteEstimatorService); - - orderId: string | null = null; - orderNumber: string | null = null; - order = signal(null); - - ngOnInit(): void { - this.orderId = this.route.snapshot.paramMap.get('orderId'); - if (!this.orderId) { - return; - } - - 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: () => { - // Keep fallback derived from UUID when API is unavailable. - } - }); - } - - goHome(): void { - this.router.navigate(['/']); - } - - private extractOrderNumber(orderId: string): string { - return orderId.split('-')[0]; - } -} diff --git a/frontend/src/app/features/order/order.component.html b/frontend/src/app/features/order/order.component.html new file mode 100644 index 0000000..eae4443 --- /dev/null +++ b/frontend/src/app/features/order/order.component.html @@ -0,0 +1,151 @@ +
+

+ {{ 'TRACKING.TITLE' | translate }} + +
#{{ getDisplayOrderNumber(order()) }} +
+

+

{{ 'TRACKING.SUBTITLE' | translate }}

+
+ +
+ +
+
+
1
+
{{ 'TRACKING.STEP_PENDING' | translate }}
+
+
+
2
+
{{ 'TRACKING.STEP_REPORTED' | translate }}
+
+
+
3
+
{{ 'TRACKING.STEP_PRODUCTION' | translate }}
+
+
+
4
+
{{ 'TRACKING.STEP_SHIPPED' | translate }}
+
+
+ + + +
+

{{ 'PAYMENT.STATUS_REPORTED_TITLE' | translate }}

+

{{ 'PAYMENT.STATUS_REPORTED_DESC' | translate }}

+
+
+ +
+
+ +
+

{{ 'PAYMENT.METHOD' | translate }}

+
+ +
+
+
+ {{ 'PAYMENT.METHOD_TWINT' | translate }} +
+
+ {{ 'PAYMENT.METHOD_BANK' | translate }} +
+
+
+ +
+
+

{{ 'PAYMENT.TWINT_TITLE' | translate }}

+
+
+ TWINT payment QR +

{{ 'PAYMENT.TWINT_DESC' | translate }}

+

{{ 'PAYMENT.BILLING_INFO_HINT' | translate }}

+
+ + {{ 'PAYMENT.TWINT_OPEN' | translate }} + +
+

{{ 'PAYMENT.TOTAL' | translate }}: {{ o.totalChf | currency:'CHF' }}

+
+
+ +
+
+

{{ 'PAYMENT.BANK_TITLE' | translate }}

+
+
+

{{ 'PAYMENT.BANK_OWNER' | translate }}: Küng, Joe

+

{{ 'PAYMENT.BANK_IBAN' | translate }}: CH74 0900 0000 1548 2158 1

+

{{ 'PAYMENT.BANK_REF' | translate }}: {{ getDisplayOrderNumber(o) }}

+

{{ 'PAYMENT.BILLING_INFO_HINT' | translate }}

+ +
+ + {{ 'PAYMENT.DOWNLOAD_QR' | translate }} + +
+
+
+ +
+ + {{ o.paymentStatus === 'REPORTED' ? ('PAYMENT.IN_VERIFICATION' | translate) : ('PAYMENT.CONFIRM' | translate) }} + +
+
+
+ +
+ +
+

{{ 'PAYMENT.SUMMARY_TITLE' | translate }}

+

#{{ getDisplayOrderNumber(o) }}

+
+ +
+
+ {{ 'PAYMENT.SUBTOTAL' | translate }} + {{ o.subtotalChf | currency:'CHF' }} +
+
+ {{ 'PAYMENT.SHIPPING' | translate }} + {{ o.shippingCostChf | currency:'CHF' }} +
+
+ {{ 'PAYMENT.SETUP_FEE' | translate }} + {{ o.setupCostChf | currency:'CHF' }} +
+
+ {{ 'PAYMENT.TOTAL' | translate }} + {{ o.totalChf | currency:'CHF' }} +
+
+
+
+
+
+
+ +
+ +

{{ 'PAYMENT.LOADING' | translate }}

+
+
+ +
+ +

{{ error() }}

+
+
+
diff --git a/frontend/src/app/features/payment/payment.component.scss b/frontend/src/app/features/order/order.component.scss similarity index 70% rename from frontend/src/app/features/payment/payment.component.scss rename to frontend/src/app/features/order/order.component.scss index 843414a..a678b56 100644 --- a/frontend/src/app/features/payment/payment.component.scss +++ b/frontend/src/app/features/order/order.component.scss @@ -234,3 +234,101 @@ margin-top: var(--space-12); text-align: center; } + +.status-timeline { + display: flex; + justify-content: space-between; + margin-bottom: var(--space-8); + position: relative; + /* padding: var(--space-6); */ /* Removed if it was here to match non-card layout */ + + &::before { + content: ''; + position: absolute; + top: 15px; + left: 12.5%; + right: 12.5%; + 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-brand); + background: var(--color-bg); + color: var(--color-brand); + } + .label { + color: var(--color-text); + font-weight: 600; + } + } + + &.completed { + .circle { + background: var(--color-brand); + border-color: var(--color-brand); + 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; + } + } + } +} diff --git a/frontend/src/app/features/payment/payment.component.ts b/frontend/src/app/features/order/order.component.ts similarity index 95% rename from frontend/src/app/features/payment/payment.component.ts rename to frontend/src/app/features/order/order.component.ts index 91dff2d..3510d31 100644 --- a/frontend/src/app/features/payment/payment.component.ts +++ b/frontend/src/app/features/order/order.component.ts @@ -8,19 +8,19 @@ import { TranslateModule } from '@ngx-translate/core'; import { environment } from '../../../environments/environment'; @Component({ - selector: 'app-payment', + selector: 'app-order', standalone: true, imports: [CommonModule, AppButtonComponent, AppCardComponent, TranslateModule], - templateUrl: './payment.component.html', - styleUrl: './payment.component.scss' + templateUrl: './order.component.html', + styleUrl: './order.component.scss' }) -export class PaymentComponent implements OnInit { +export class OrderComponent implements OnInit { private route = inject(ActivatedRoute); private router = inject(Router); private quoteService = inject(QuoteEstimatorService); orderId: string | null = null; - selectedPaymentMethod: 'twint' | 'bill' | null = null; + selectedPaymentMethod: 'twint' | 'bill' | null = 'twint'; order = signal(null); loading = signal(true); error = signal(null); diff --git a/frontend/src/app/features/payment/payment.component.html b/frontend/src/app/features/payment/payment.component.html deleted file mode 100644 index fca8b97..0000000 --- a/frontend/src/app/features/payment/payment.component.html +++ /dev/null @@ -1,129 +0,0 @@ -
-

{{ 'PAYMENT.TITLE' | translate }}

-

{{ 'CHECKOUT.SUBTITLE' | translate }}

-
- -
-
-
- -
-

{{ 'PAYMENT.STATUS_REPORTED_TITLE' | translate }}

-

{{ 'PAYMENT.STATUS_REPORTED_DESC' | translate }}

-
-
- - -
-

{{ 'PAYMENT.METHOD' | translate }}

-
- -
-
-
- {{ 'PAYMENT.METHOD_TWINT' | translate }} -
-
- {{ 'PAYMENT.METHOD_BANK' | translate }} -
-
-
- -
-
-

{{ 'PAYMENT.TWINT_TITLE' | translate }}

-
-
- TWINT payment QR -

{{ 'PAYMENT.TWINT_DESC' | translate }}

-

{{ 'PAYMENT.BILLING_INFO_HINT' | translate }}

-
- - {{ 'PAYMENT.TWINT_OPEN' | translate }} - -
-

{{ 'PAYMENT.TOTAL' | translate }}: {{ o.totalChf | currency:'CHF' }}

-
-
- -
-
-

{{ 'PAYMENT.BANK_TITLE' | translate }}

-
-
-

{{ 'PAYMENT.BANK_OWNER' | translate }}: 3D Fab Switzerland

-

{{ 'PAYMENT.BANK_IBAN' | translate }}: CH98 0000 0000 0000 0000 0

-

{{ 'PAYMENT.BANK_REF' | translate }}: {{ getDisplayOrderNumber(o) }}

-

{{ 'PAYMENT.BILLING_INFO_HINT' | translate }}

- -
- - {{ 'PAYMENT.DOWNLOAD_QR' | translate }} - -
-
-
- -
- - {{ o.paymentStatus === 'REPORTED' ? ('PAYMENT.IN_VERIFICATION' | translate) : ('PAYMENT.CONFIRM' | translate) }} - -
-
-
- -
- -
-

{{ 'PAYMENT.SUMMARY_TITLE' | translate }}

-

#{{ getDisplayOrderNumber(o) }}

-
- -
-
- {{ 'PAYMENT.SUBTOTAL' | translate }} - {{ o.subtotalChf | currency:'CHF' }} -
-
- {{ 'PAYMENT.SHIPPING' | translate }} - {{ o.shippingCostChf | currency:'CHF' }} -
-
- {{ 'PAYMENT.SETUP_FEE' | translate }} - {{ o.setupCostChf | currency:'CHF' }} -
-
- {{ 'PAYMENT.TOTAL' | translate }} - {{ o.totalChf | currency:'CHF' }} -
-
-
-
-
- -
- -

{{ 'PAYMENT.LOADING' | translate }}

-
-
- -
- -

{{ error() }}

-
-
-
diff --git a/frontend/src/app/features/shop/shop-page.component.html b/frontend/src/app/features/shop/shop-page.component.html index f43032e..6479737 100644 --- a/frontend/src/app/features/shop/shop-page.component.html +++ b/frontend/src/app/features/shop/shop-page.component.html @@ -1,12 +1,18 @@ -
-

{{ 'SHOP.TITLE' | translate }}

-

{{ 'SHOP.SUBTITLE' | translate }}

-
+
+
+
+

{{ 'SHOP.WIP_EYEBROW' | translate }}

+

{{ 'SHOP.WIP_TITLE' | translate }}

+

{{ 'SHOP.WIP_SUBTITLE' | translate }}

-
-
- @for (product of products(); track product.id) { - - } +
+ + {{ 'SHOP.WIP_CTA_CALC' | translate }} + +
+ +

{{ 'SHOP.WIP_RETURN_LATER' | translate }}

+

{{ 'SHOP.WIP_NOTE' | translate }}

+
-
+
diff --git a/frontend/src/app/features/shop/shop-page.component.scss b/frontend/src/app/features/shop/shop-page.component.scss index 507fea1..3288aec 100644 --- a/frontend/src/app/features/shop/shop-page.component.scss +++ b/frontend/src/app/features/shop/shop-page.component.scss @@ -1,7 +1,72 @@ -.hero { padding: var(--space-8) 0; text-align: center; } -.subtitle { color: var(--color-text-muted); margin-bottom: var(--space-8); } -.grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); - gap: var(--space-6); +.wip-section { + position: relative; + padding: var(--space-12) 0; + background-color: var(--color-bg); +} + +.wip-card { + max-width: 760px; + margin: 0 auto; + padding: clamp(1.4rem, 3vw, 2.4rem); + border: 1px solid var(--color-border); + border-radius: var(--radius-xl); + background: rgba(255, 255, 255, 0.95); + box-shadow: var(--shadow-lg); + text-align: center; +} + +.wip-eyebrow { + display: inline-block; + margin-bottom: var(--space-3); + padding: 0.3rem 0.7rem; + border-radius: 999px; + border: 1px solid rgba(16, 24, 32, 0.14); + font-size: 0.78rem; + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--color-secondary-600); + background: rgba(250, 207, 10, 0.28); +} + +h1 { + font-size: clamp(1.7rem, 4vw, 2.5rem); + margin-bottom: var(--space-4); + color: var(--color-text); +} + +.wip-subtitle { + max-width: 60ch; + margin: 0 auto var(--space-8); + color: var(--color-text-muted); +} + +.wip-actions { + display: flex; + gap: var(--space-4); + justify-content: center; + flex-wrap: wrap; +} + +.wip-note { + margin: var(--space-4) auto 0; + max-width: 62ch; + font-size: 0.95rem; + color: var(--color-secondary-600); +} + +.wip-return-later { + margin: var(--space-6) 0 0; + font-weight: 600; + color: var(--color-secondary-600); +} + +@media (max-width: 640px) { + .wip-section { + padding: var(--space-10) 0; + } + + .wip-actions { + flex-direction: column; + align-items: stretch; + } } diff --git a/frontend/src/app/features/shop/shop-page.component.ts b/frontend/src/app/features/shop/shop-page.component.ts index 8f89098..a2312f9 100644 --- a/frontend/src/app/features/shop/shop-page.component.ts +++ b/frontend/src/app/features/shop/shop-page.component.ts @@ -1,22 +1,14 @@ -import { Component, signal } from '@angular/core'; +import { Component } from '@angular/core'; import { CommonModule } from '@angular/common'; +import { RouterLink } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; -import { ShopService, Product } from './services/shop.service'; -import { ProductCardComponent } from './components/product-card/product-card.component'; +import { AppButtonComponent } from '../../shared/components/app-button/app-button.component'; @Component({ selector: 'app-shop-page', standalone: true, - imports: [CommonModule, TranslateModule, ProductCardComponent], + imports: [CommonModule, RouterLink, TranslateModule, AppButtonComponent], templateUrl: './shop-page.component.html', styleUrl: './shop-page.component.scss' }) -export class ShopPageComponent { - products = signal([]); - - constructor(private shopService: ShopService) { - this.shopService.getProducts().subscribe(data => { - this.products.set(data); - }); - } -} +export class ShopPageComponent {} diff --git a/frontend/src/assets/i18n/en.json b/frontend/src/assets/i18n/en.json index ddbd1e7..bb9f7a1 100644 --- a/frontend/src/assets/i18n/en.json +++ b/frontend/src/assets/i18n/en.json @@ -73,8 +73,15 @@ "SHOP": { "TITLE": "Technical solutions", "SUBTITLE": "Ready-made products solving practical problems", + "WIP_EYEBROW": "Work in progress", + "WIP_TITLE": "Shop under construction", + "WIP_SUBTITLE": "We are building a curated technical shop with products that are genuinely useful and field-tested.", + "WIP_CTA_CALC": "Check our calculator", + "WIP_RETURN_LATER": "Come back soon", + "WIP_NOTE": "We care about doing this right. In the meantime, you can get instant pricing and lead time from our calculator.", "ADD_CART": "Add to Cart", - "BACK": "Back to Shop" + "BACK": "Back to Shop", + "NOT_FOUND": "Product not found." }, "ABOUT": { "TITLE": "About Us", @@ -188,7 +195,7 @@ "BANK_REF": "Reference", "BILLING_INFO_HINT": "Add the same information used in billing.", "DOWNLOAD_QR": "Download QR-Invoice (PDF)", - "CONFIRM": "Confirm Order", + "CONFIRM": "I have completed the payment", "SUMMARY_TITLE": "Order Summary", "SUBTOTAL": "Subtotal", "SHIPPING": "Shipping", @@ -197,13 +204,15 @@ "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" + "STATUS_REPORTED_TITLE": "Order in progress", + "STATUS_REPORTED_DESC": "We have registered your operation. Your order will soon move to production.", + "IN_VERIFICATION": "Payment Reported" }, "TRACKING": { + "TITLE": "Order Status", + "SUBTITLE": "Check the status of your order and manage the payment if necessary.", "STEP_PENDING": "Pending", - "STEP_REPORTED": "Verifying", + "STEP_REPORTED": "Received", "STEP_PRODUCTION": "Production", "STEP_SHIPPED": "Shipped" }, diff --git a/frontend/src/assets/i18n/it.json b/frontend/src/assets/i18n/it.json index 92d8d7c..0ae91bf 100644 --- a/frontend/src/assets/i18n/it.json +++ b/frontend/src/assets/i18n/it.json @@ -15,14 +15,13 @@ "HERO_EYEBROW": "Stampa 3D tecnica per aziende, freelance e maker", "HERO_TITLE": "Prezzo e tempi in pochi secondi.
Dal file 3D al pezzo finito.", "HERO_LEAD": "Il calcolatore più avanzato per le tue stampe 3D: precisione assoluta e zero sorprese.", - "HERO_SUBTITLE": "Realizziamo pezzi unici e produzioni in serie. Se hai il file, il preventivo è istantaneo. Se devi ancora crearlo, il nostro team di design lo progetterà per te.", + "HERO_SUBTITLE": "Offriamo anche servizi di cad, per pezzi personalizzati!", "BTN_CALCULATE": "Calcola Preventivo", "BTN_SHOP": "Vai allo shop", "BTN_CONTACT": "Parla con noi", - "SEC_CALC_TITLE": "Preventivo immediato in pochi secondi", - "SEC_CALC_SUBTITLE": "Nessuna registrazione necessaria. Non salviamo il tuo file fino al momento dell'ordine e la stima è effettuata tramite vero slicing.", + "SEC_CALC_TITLE": "Prezzo corretto in pochi secondi", + "SEC_CALC_SUBTITLE": "Nessuna registrazione necessaria. La stima è effettuata tramite vero slicing.", "SEC_CALC_LIST_1": "Formati supportati: STL, 3MF, STEP, OBJ", - "SEC_CALC_LIST_2": "Qualità: bozza, standard, alta definizione", "CARD_CALC_EYEBROW": "Calcolo automatico", "CARD_CALC_TITLE": "Prezzo e tempi in un click", "CARD_CALC_TAG": "Senza registrazione", @@ -137,6 +136,12 @@ "SHOP": { "TITLE": "Soluzioni tecniche", "SUBTITLE": "Prodotti pronti che risolvono problemi pratici", + "WIP_EYEBROW": "Work in progress", + "WIP_TITLE": "Shop in allestimento", + "WIP_SUBTITLE": "Stiamo preparando uno shop con prodotti selezionati e funzionalità di creazione automatica!", + "WIP_CTA_CALC": "Vai al calcolatore", + "WIP_RETURN_LATER": "Torna tra un po'", + "WIP_NOTE": "Ci teniamo a fare le cose fatte bene: nel frattempo puoi calcolare subito prezzo e tempi di un file 3d con il nostro calcolatore.", "ADD_CART": "Aggiungi al Carrello", "BACK": "Torna allo Shop", "NOT_FOUND": "Prodotto non trovato." @@ -259,7 +264,7 @@ "BANK_REF": "Riferimento", "BILLING_INFO_HINT": "Aggiungi le informazioni uguali a quelle della fatturazione.", "DOWNLOAD_QR": "Scarica QR-Fattura (PDF)", - "CONFIRM": "Conferma Ordine", + "CONFIRM": "Ho completato il pagamento", "SUMMARY_TITLE": "Riepilogo Ordine", "SUBTOTAL": "Subtotale", "SHIPPING": "Spedizione", @@ -268,11 +273,13 @@ "LOADING": "Caricamento dettagli ordine...", "METHOD_TWINT": "TWINT", "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" + "STATUS_REPORTED_TITLE": "Ordine in lavorazione", + "STATUS_REPORTED_DESC": "Abbiamo registrato la tua operazione. L'ordine entrerà a breve in produzione.", + "IN_VERIFICATION": "Pagamento Segnalato" }, "TRACKING": { + "TITLE": "Stato dell'Ordine", + "SUBTITLE": "Consulta lo stato del tuo ordine e gestisci il pagamento se necessario.", "STEP_PENDING": "In attesa", "STEP_REPORTED": "In verifica", "STEP_PRODUCTION": "In Produzione",