dev #29

Merged
JoeKung merged 30 commits from dev into main 2026-03-09 09:58:45 +01:00
12 changed files with 162 additions and 14 deletions
Showing only changes of commit a4b85b01bd - Show all commits

View File

@@ -1,5 +1,6 @@
import { Component } from '@angular/core';
import { Component, inject } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { SeoService } from './core/services/seo.service';
@Component({
selector: 'app-root',
@@ -8,4 +9,6 @@ import { RouterOutlet } from '@angular/router';
templateUrl: './app.component.html',
styleUrl: './app.component.scss',
})
export class AppComponent {}
export class AppComponent {
private readonly seoService = inject(SeoService);
}

View File

@@ -5,6 +5,11 @@ const appChildRoutes: Routes = [
path: '',
loadComponent: () =>
import('./features/home/home.component').then((m) => m.HomeComponent),
data: {
seoTitle: '3D fab | Stampa 3D su misura',
seoDescription:
'Servizio di stampa 3D con preventivo online immediato per prototipi, piccole serie e pezzi personalizzati.',
},
},
{
path: 'calculator',
@@ -12,21 +17,42 @@ const appChildRoutes: Routes = [
import('./features/calculator/calculator.routes').then(
(m) => m.CALCULATOR_ROUTES,
),
data: {
seoTitle: 'Calcolatore preventivo stampa 3D | 3D fab',
seoDescription:
'Carica il file 3D e ottieni prezzo e tempi in pochi secondi con slicing reale.',
},
},
{
path: 'shop',
loadChildren: () =>
import('./features/shop/shop.routes').then((m) => m.SHOP_ROUTES),
data: {
seoTitle: 'Shop 3D fab',
seoDescription:
'Catalogo prodotti stampati in 3D e soluzioni tecniche pronte all uso.',
seoRobots: 'noindex, nofollow',
},
},
{
path: 'about',
loadChildren: () =>
import('./features/about/about.routes').then((m) => m.ABOUT_ROUTES),
data: {
seoTitle: 'Chi siamo | 3D fab',
seoDescription:
'Scopri il team 3D fab e il laboratorio di stampa 3D con sedi in Ticino e Bienne.',
},
},
{
path: 'contact',
loadChildren: () =>
import('./features/contact/contact.routes').then((m) => m.CONTACT_ROUTES),
data: {
seoTitle: 'Contatti | 3D fab',
seoDescription:
'Contatta 3D fab per preventivi, supporto tecnico e richieste personalizzate di stampa 3D.',
},
},
{
path: 'checkout/cad',
@@ -34,6 +60,10 @@ const appChildRoutes: Routes = [
import('./features/checkout/checkout.component').then(
(m) => m.CheckoutComponent,
),
data: {
seoTitle: 'Checkout | 3D fab',
seoRobots: 'noindex, nofollow',
},
},
{
path: 'checkout',
@@ -41,16 +71,28 @@ const appChildRoutes: Routes = [
import('./features/checkout/checkout.component').then(
(m) => m.CheckoutComponent,
),
data: {
seoTitle: 'Checkout | 3D fab',
seoRobots: 'noindex, nofollow',
},
},
{
path: 'order/:orderId',
loadComponent: () =>
import('./features/order/order.component').then((m) => m.OrderComponent),
data: {
seoTitle: 'Ordine | 3D fab',
seoRobots: 'noindex, nofollow',
},
},
{
path: 'co/:orderId',
loadComponent: () =>
import('./features/order/order.component').then((m) => m.OrderComponent),
data: {
seoTitle: 'Ordine | 3D fab',
seoRobots: 'noindex, nofollow',
},
},
{
path: '',
@@ -61,6 +103,10 @@ const appChildRoutes: Routes = [
path: 'admin',
loadChildren: () =>
import('./features/admin/admin.routes').then((m) => m.ADMIN_ROUTES),
data: {
seoTitle: 'Admin | 3D fab',
seoRobots: 'noindex, nofollow',
},
},
{
path: '**',

View File

@@ -2,5 +2,13 @@ import { Routes } from '@angular/router';
import { AboutPageComponent } from './about-page.component';
export const ABOUT_ROUTES: Routes = [
{ path: '', component: AboutPageComponent },
{
path: '',
component: AboutPageComponent,
data: {
seoTitle: 'Chi siamo | 3D fab',
seoDescription:
'Siamo un laboratorio di stampa 3D orientato a prototipi, ricambi e produzioni su misura.',
},
},
];

View File

@@ -3,10 +3,24 @@ import { CalculatorPageComponent } from './calculator-page.component';
export const CALCULATOR_ROUTES: Routes = [
{ path: '', redirectTo: 'basic', pathMatch: 'full' },
{ path: 'basic', component: CalculatorPageComponent, data: { mode: 'easy' } },
{
path: 'basic',
component: CalculatorPageComponent,
data: {
mode: 'easy',
seoTitle: 'Calcolatore stampa 3D base | 3D fab',
seoDescription:
'Calcola rapidamente il prezzo della tua stampa 3D in modalita base.',
},
},
{
path: 'advanced',
component: CalculatorPageComponent,
data: { mode: 'advanced' },
data: {
mode: 'advanced',
seoTitle: 'Calcolatore stampa 3D avanzato | 3D fab',
seoDescription:
'Configura parametri avanzati e ottieni un preventivo preciso con slicing reale.',
},
},
];

View File

@@ -45,6 +45,12 @@
<p>{{ result().notes }}</p>
</div>
}
@if (recalculationRequired()) {
<div class="recalc-banner">
Hai modificato i parametri di stampa. Ricalcola il preventivo prima di
procedere con l'ordine.
</div>
}
<div class="divider"></div>
@@ -104,7 +110,10 @@
<div class="actions-right">
@if (!hasQuantityOverLimit()) {
<app-button (click)="proceed.emit()">
<app-button
[disabled]="recalculationRequired()"
(click)="proceed.emit()"
>
{{ "QUOTE.PROCEED_ORDER" | translate }}
</app-button>
} @else {
@@ -112,6 +121,11 @@
"QUOTE.MAX_QTY_NOTICE" | translate: { max: directOrderLimit }
}}</small>
}
@if (recalculationRequired()) {
<small class="limit-note">
Ricalcola il preventivo per riattivare il checkout.
</small>
}
</div>
</div>
</app-card>

View File

@@ -184,3 +184,14 @@
white-space: pre-wrap; /* Preserve line breaks */
}
}
.recalc-banner {
margin-top: var(--space-4);
margin-bottom: var(--space-4);
padding: var(--space-3);
border: 1px solid #f0c95a;
background: #fff8e1;
border-radius: var(--radius-md);
color: #6f5b1a;
font-size: 0.9rem;
}

View File

@@ -35,6 +35,7 @@ export class QuoteResultComponent implements OnDestroy {
readonly quantityAutoRefreshMs = 2000;
result = input.required<QuoteResult>();
recalculationRequired = input<boolean>(false);
consult = output<void>();
proceed = output<void>();
itemChange = output<{
@@ -43,6 +44,12 @@ export class QuoteResultComponent implements OnDestroy {
fileName: string;
quantity: number;
}>();
itemQuantityPreviewChange = output<{
id?: string;
index: number;
fileName: string;
quantity: number;
}>();
// Local mutable state for items to handle quantity changes
items = signal<QuoteItem[]>([]);
@@ -87,6 +94,13 @@ export class QuoteResultComponent implements OnDestroy {
return updated;
});
this.itemQuantityPreviewChange.emit({
id: item.id,
index,
fileName: item.fileName,
quantity: normalizedQty,
});
this.scheduleQuantityRefresh(index, key);
}

View File

@@ -5,5 +5,10 @@ export const CONTACT_ROUTES: Routes = [
path: '',
loadComponent: () =>
import('./contact-page.component').then((m) => m.ContactPageComponent),
data: {
seoTitle: 'Contatti | 3D fab',
seoDescription:
'Richiedi informazioni, preventivi personalizzati o supporto per progetti di stampa 3D.',
},
},
];

View File

@@ -37,28 +37,31 @@
<div class="cap-cards">
<app-card>
<div class="card-image-placeholder">
<img src="assets/images/home/prototipi.jpg" alt="" />
<img src="assets/images/home/prototipi.jpg" alt="Prototipazione 3D" />
</div>
<h3>{{ "HOME.CAP_1_TITLE" | translate }}</h3>
<p class="text-muted">{{ "HOME.CAP_1_TEXT" | translate }}</p>
</app-card>
<app-card>
<div class="card-image-placeholder">
<img src="assets/images/home/original-vs-3dprinted.jpg" alt="" />
<img
src="assets/images/home/original-vs-3dprinted.jpg"
alt="Confronto pezzo originale e stampato in 3D"
/>
</div>
<h3>{{ "HOME.CAP_2_TITLE" | translate }}</h3>
<p class="text-muted">{{ "HOME.CAP_2_TEXT" | translate }}</p>
</app-card>
<app-card>
<div class="card-image-placeholder">
<img src="assets/images/home/serie.jpg" alt="" />
<img src="assets/images/home/serie.jpg" alt="Piccole serie stampate in 3D" />
</div>
<h3>{{ "HOME.CAP_3_TITLE" | translate }}</h3>
<p class="text-muted">{{ "HOME.CAP_3_TEXT" | translate }}</p>
</app-card>
<app-card>
<div class="card-image-placeholder">
<img src="assets/images/home/cad.jpg" alt="" />
<img src="assets/images/home/cad.jpg" alt="Servizio CAD per stampa 3D" />
</div>
<h3>{{ "HOME.CAP_4_TITLE" | translate }}</h3>
<p class="text-muted">{{ "HOME.CAP_4_TEXT" | translate }}</p>

View File

@@ -5,10 +5,20 @@ export const LEGAL_ROUTES: Routes = [
path: 'privacy',
loadComponent: () =>
import('./privacy/privacy.component').then((m) => m.PrivacyComponent),
data: {
seoTitle: 'Privacy Policy | 3D fab',
seoDescription:
'Informativa privacy di 3D fab: trattamento dati, finalita e contatti.',
},
},
{
path: 'terms',
loadComponent: () =>
import('./terms/terms.component').then((m) => m.TermsComponent),
data: {
seoTitle: 'Termini e condizioni | 3D fab',
seoDescription:
'Termini e condizioni del servizio di stampa 3D e del calcolatore preventivi.',
},
},
];

View File

@@ -3,6 +3,22 @@ import { ShopPageComponent } from './shop-page.component';
import { ProductDetailComponent } from './product-detail.component';
export const SHOP_ROUTES: Routes = [
{ path: '', component: ShopPageComponent },
{ path: ':id', component: ProductDetailComponent },
{
path: '',
component: ShopPageComponent,
data: {
seoTitle: 'Shop 3D fab',
seoDescription:
'Lo shop 3D fab e in allestimento. Intanto puoi usare il calcolatore per ottenere un preventivo.',
seoRobots: 'noindex, nofollow',
},
},
{
path: ':id',
component: ProductDetailComponent,
data: {
seoTitle: 'Prodotto | 3D fab',
seoRobots: 'noindex, nofollow',
},
},
];

View File

@@ -2,8 +2,12 @@
<html lang="it">
<head>
<meta charset="utf-8" />
<meta name="robots" content="noindex, nofollow" />
<title>3D fab</title>
<title>3D fab | Stampa 3D su misura</title>
<meta
name="description"
content="Stampa 3D su misura con preventivo online immediato. Carica il file, scegli materiale e qualità, ricevi prezzo e tempi in pochi secondi."
/>
<meta name="robots" content="index, follow" />
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/png" href="assets/images/Fav-icon.png" />