produzione 1 #9
@@ -20,6 +20,10 @@ export const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: 'about',
|
path: 'about',
|
||||||
loadChildren: () => import('./features/about/about.routes').then(m => m.ABOUT_ROUTES)
|
loadChildren: () => import('./features/about/about.routes').then(m => m.ABOUT_ROUTES)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'contact',
|
||||||
|
loadChildren: () => import('./features/contact/contact.routes').then(m => m.CONTACT_ROUTES)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,11 @@
|
|||||||
<a routerLink="/" class="brand">3D <span class="highlight">fab</span></a>
|
<a routerLink="/" class="brand">3D <span class="highlight">fab</span></a>
|
||||||
|
|
||||||
<nav class="nav-links">
|
<nav class="nav-links">
|
||||||
|
<a routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">{{ 'NAV.HOME' | translate }}</a>
|
||||||
<a routerLink="/cal" routerLinkActive="active" [routerLinkActiveOptions]="{exact: false}">{{ 'NAV.CALCULATOR' | translate }}</a>
|
<a routerLink="/cal" routerLinkActive="active" [routerLinkActiveOptions]="{exact: false}">{{ 'NAV.CALCULATOR' | translate }}</a>
|
||||||
<a routerLink="/shop" routerLinkActive="active">{{ 'NAV.SHOP' | translate }}</a>
|
<a routerLink="/shop" routerLinkActive="active">{{ 'NAV.SHOP' | translate }}</a>
|
||||||
<a routerLink="/about" routerLinkActive="active">{{ 'NAV.ABOUT' | translate }}</a>
|
<a routerLink="/about" routerLinkActive="active">{{ 'NAV.ABOUT' | translate }}</a>
|
||||||
|
<a routerLink="/contact" routerLinkActive="active">{{ 'NAV.CONTACT' | translate }}</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { ContactFormComponent } from './components/contact-form/contact-form.component';
|
|
||||||
import { AppCardComponent } from '../../shared/components/app-card/app-card.component';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-about-page',
|
selector: 'app-about-page',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [TranslateModule, ContactFormComponent, AppCardComponent],
|
imports: [TranslateModule],
|
||||||
template: `
|
template: `
|
||||||
<section class="about-hero">
|
<section class="about-hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@@ -37,14 +36,25 @@ import { AppCardComponent } from '../../shared/components/app-card/app-card.comp
|
|||||||
|
|
||||||
<h3>{{ 'ABOUT.TARGET_TITLE' | translate }}</h3>
|
<h3>{{ 'ABOUT.TARGET_TITLE' | translate }}</h3>
|
||||||
<p class="text-muted">{{ 'ABOUT.TARGET_TEXT' | translate }}</p>
|
<p class="text-muted">{{ 'ABOUT.TARGET_TEXT' | translate }}</p>
|
||||||
|
|
||||||
|
<h3>{{ 'ABOUT.TEAM_TITLE' | translate }}</h3>
|
||||||
|
<div class="team-grid">
|
||||||
|
<div class="team-member">
|
||||||
|
<div class="placeholder-img"></div>
|
||||||
|
<p>Member 1</p>
|
||||||
|
</div>
|
||||||
|
<div class="team-member">
|
||||||
|
<div class="placeholder-img"></div>
|
||||||
|
<p>Member 2</p>
|
||||||
|
</div>
|
||||||
|
<div class="team-member">
|
||||||
|
<div class="placeholder-img"></div>
|
||||||
|
<p>Member 3</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="contact">
|
|
||||||
<app-card>
|
|
||||||
<h2>{{ 'ABOUT.CONTACT_US' | translate }}</h2>
|
|
||||||
<app-contact-form></app-contact-form>
|
|
||||||
</app-card>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
styles: [`
|
styles: [`
|
||||||
@@ -91,6 +101,22 @@ import { AppCardComponent } from '../../shared/components/app-card/app-card.comp
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
.text-muted { color: var(--color-text-muted); }
|
.text-muted { color: var(--color-text-muted); }
|
||||||
|
.team-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||||
|
gap: var(--space-4);
|
||||||
|
margin-top: var(--space-4);
|
||||||
|
}
|
||||||
|
.team-member {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.placeholder-img {
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
background: var(--color-neutral-100);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
margin-bottom: var(--space-2);
|
||||||
|
}
|
||||||
`]
|
`]
|
||||||
})
|
})
|
||||||
export class AboutPageComponent {}
|
export class AboutPageComponent {}
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
import { Component, signal } from '@angular/core';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
|
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
|
||||||
import { AppInputComponent } from '../../../../shared/components/app-input/app-input.component';
|
|
||||||
import { AppButtonComponent } from '../../../../shared/components/app-button/app-button.component';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-contact-form',
|
|
||||||
standalone: true,
|
|
||||||
imports: [CommonModule, ReactiveFormsModule, TranslateModule, AppInputComponent, AppButtonComponent],
|
|
||||||
template: `
|
|
||||||
<form [formGroup]="form" (ngSubmit)="onSubmit()">
|
|
||||||
<app-input formControlName="name" label="Nome" placeholder="Il tuo nome"></app-input>
|
|
||||||
<app-input formControlName="email" type="email" label="Email" placeholder="tuo@email.com"></app-input>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Messaggio</label>
|
|
||||||
<textarea formControlName="message" class="form-control" rows="4"></textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="actions">
|
|
||||||
<app-button type="submit" [disabled]="form.invalid || sent()">
|
|
||||||
{{ sent() ? 'Inviato!' : ('ABOUT.SEND' | translate) }}
|
|
||||||
</app-button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
`,
|
|
||||||
styles: [`
|
|
||||||
.form-group { display: flex; flex-direction: column; margin-bottom: var(--space-4); }
|
|
||||||
label { font-size: 0.875rem; font-weight: 500; margin-bottom: var(--space-2); color: var(--color-text); }
|
|
||||||
.form-control {
|
|
||||||
padding: 0.5rem 0.75rem;
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
width: 100%;
|
|
||||||
background: var(--color-bg-card);
|
|
||||||
color: var(--color-text);
|
|
||||||
font-family: inherit;
|
|
||||||
&:focus { outline: none; border-color: var(--color-brand); }
|
|
||||||
}
|
|
||||||
`]
|
|
||||||
})
|
|
||||||
export class ContactFormComponent {
|
|
||||||
form: FormGroup;
|
|
||||||
sent = signal(false);
|
|
||||||
|
|
||||||
constructor(private fb: FormBuilder) {
|
|
||||||
this.form = this.fb.group({
|
|
||||||
name: ['', Validators.required],
|
|
||||||
email: ['', [Validators.required, Validators.email]],
|
|
||||||
message: ['', Validators.required]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onSubmit() {
|
|
||||||
if (this.form.valid) {
|
|
||||||
// Mock submit
|
|
||||||
this.sent.set(true);
|
|
||||||
setTimeout(() => {
|
|
||||||
this.sent.set(false);
|
|
||||||
this.form.reset();
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,306 @@
|
|||||||
|
import { Component, signal, effect } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { AppInputComponent } from '../../../../shared/components/app-input/app-input.component';
|
||||||
|
import { AppButtonComponent } from '../../../../shared/components/app-button/app-button.component';
|
||||||
|
|
||||||
|
interface FilePreview {
|
||||||
|
file: File;
|
||||||
|
url?: string;
|
||||||
|
type: 'image' | 'pdf' | '3d' | 'other';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-contact-form',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule, ReactiveFormsModule, TranslateModule, AppInputComponent, AppButtonComponent],
|
||||||
|
template: `
|
||||||
|
<form [formGroup]="form" (ngSubmit)="onSubmit()">
|
||||||
|
<!-- Request Type -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{ 'CONTACT.REQ_TYPE_LABEL' | translate }} *</label>
|
||||||
|
<select formControlName="requestType" class="form-control">
|
||||||
|
<option *ngFor="let type of requestTypes" [value]="type.value">
|
||||||
|
{{ type.label | translate }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<!-- Email -->
|
||||||
|
<app-input formControlName="email" type="email" label="Email *" placeholder="tuo@email.com" class="col"></app-input>
|
||||||
|
<!-- Phone -->
|
||||||
|
<app-input formControlName="phone" type="tel" [label]="('CONTACT.PHONE' | translate)" placeholder="+39 000 000 0000" class="col"></app-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Name (Always Required) -->
|
||||||
|
<app-input formControlName="name" label="Nome *" placeholder="Il tuo nome"></app-input>
|
||||||
|
|
||||||
|
<!-- Company Toggle & Fields -->
|
||||||
|
<div class="form-group checkbox-group">
|
||||||
|
<input type="checkbox" formControlName="isCompany" id="isCompany">
|
||||||
|
<label for="isCompany">{{ 'CONTACT.IS_COMPANY' | translate }}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="form.get('isCompany')?.value" class="company-fields">
|
||||||
|
<app-input formControlName="companyName" [label]="('CONTACT.COMPANY_NAME' | translate) + ' *'" placeholder="Nome Azienda"></app-input>
|
||||||
|
<app-input formControlName="referencePerson" [label]="('CONTACT.REF_PERSON' | translate) + ' *'" placeholder="Persona di Riferimento"></app-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Messaggio *</label>
|
||||||
|
<textarea formControlName="message" class="form-control" rows="4"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- File Upload Section -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{ 'CONTACT.UPLOAD_LABEL' | translate }}</label>
|
||||||
|
<p class="hint">{{ 'CONTACT.UPLOAD_HINT' | translate }}</p>
|
||||||
|
|
||||||
|
<div class="drop-zone" (click)="fileInput.click()"
|
||||||
|
(dragover)="onDragOver($event)" (drop)="onDrop($event)">
|
||||||
|
<input #fileInput type="file" multiple (change)="onFileSelected($event)" hidden
|
||||||
|
accept=".jpg,.jpeg,.png,.pdf,.stl,.step,.stp,.3mf,.obj">
|
||||||
|
<p>{{ 'CONTACT.DROP_FILES' | translate }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="file-grid" *ngIf="files().length > 0">
|
||||||
|
<div class="file-item" *ngFor="let file of files(); let i = index">
|
||||||
|
<button type="button" class="remove-btn" (click)="removeFile(i)">×</button>
|
||||||
|
<img *ngIf="file.type === 'image'" [src]="file.url" class="preview-img">
|
||||||
|
<div *ngIf="file.type !== 'image'" class="file-icon">
|
||||||
|
<span *ngIf="file.type === 'pdf'">PDF</span>
|
||||||
|
<span *ngIf="file.type === '3d'">3D</span>
|
||||||
|
</div>
|
||||||
|
<div class="file-name" [title]="file.file.name">{{ file.file.name }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<app-button type="submit" [disabled]="form.invalid || sent()">
|
||||||
|
{{ sent() ? 'Inviato!' : ('CONTACT.SEND' | translate) }}
|
||||||
|
</app-button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
`,
|
||||||
|
styles: [`
|
||||||
|
.form-group { display: flex; flex-direction: column; margin-bottom: var(--space-4); }
|
||||||
|
label { font-size: 0.875rem; font-weight: 500; margin-bottom: var(--space-2); color: var(--color-text); }
|
||||||
|
.hint { font-size: 0.75rem; color: var(--color-text-muted); margin-bottom: var(--space-2); }
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
width: 100%;
|
||||||
|
background: var(--color-bg-card);
|
||||||
|
color: var(--color-text);
|
||||||
|
font-family: inherit;
|
||||||
|
&:focus { outline: none; border-color: var(--color-brand); }
|
||||||
|
}
|
||||||
|
|
||||||
|
select.form-control {
|
||||||
|
appearance: none;
|
||||||
|
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right 1rem center;
|
||||||
|
background-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-4);
|
||||||
|
margin-bottom: var(--space-4);
|
||||||
|
|
||||||
|
@media(min-width: 768px) {
|
||||||
|
flex-direction: row;
|
||||||
|
.col { flex: 1; margin-bottom: 0; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modify direct app-input child of row if possible or target host */
|
||||||
|
app-input.col {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-group {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-2);
|
||||||
|
input[type="checkbox"] { width: auto; margin: 0; }
|
||||||
|
label { margin: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.company-fields {
|
||||||
|
padding-left: var(--space-4);
|
||||||
|
border-left: 2px solid var(--color-border);
|
||||||
|
margin-bottom: var(--space-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop-zone {
|
||||||
|
border: 2px dashed var(--color-border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: var(--space-6);
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
transition: all 0.2s;
|
||||||
|
&:hover { border-color: var(--color-brand); color: var(--color-brand); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
|
||||||
|
gap: var(--space-3);
|
||||||
|
margin-top: var(--space-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item {
|
||||||
|
position: relative;
|
||||||
|
background: var(--color-neutral-100);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
padding: var(--space-2);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-img {
|
||||||
|
width: 100%; height: 100%; object-fit: cover; position: absolute; top:0; left:0;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-icon {
|
||||||
|
font-weight: 700; color: var(--color-text-muted); font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-name {
|
||||||
|
font-size: 0.65rem; color: var(--color-text); white-space: nowrap; overflow: hidden;
|
||||||
|
text-overflow: ellipsis; width: 100%; text-align: center; position: absolute; bottom: 2px;
|
||||||
|
padding: 0 4px; z-index: 2; background: rgba(255,255,255,0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-btn {
|
||||||
|
position: absolute; top: 2px; right: 2px; z-index: 10;
|
||||||
|
background: rgba(0,0,0,0.5); color: white; border: none; border-radius: 50%;
|
||||||
|
width: 18px; height: 18px; font-size: 12px; cursor: pointer;
|
||||||
|
display: flex; align-items: center; justify-content: center; line-height: 1;
|
||||||
|
&:hover { background: red; }
|
||||||
|
}
|
||||||
|
`]
|
||||||
|
})
|
||||||
|
export class ContactFormComponent {
|
||||||
|
form: FormGroup;
|
||||||
|
sent = signal(false);
|
||||||
|
files = signal<FilePreview[]>([]);
|
||||||
|
|
||||||
|
requestTypes = [
|
||||||
|
{ value: 'custom', label: 'CONTACT.REQ_TYPE_CUSTOM' },
|
||||||
|
{ value: 'series', label: 'CONTACT.REQ_TYPE_SERIES' },
|
||||||
|
{ value: 'consult', label: 'CONTACT.REQ_TYPE_CONSULT' },
|
||||||
|
{ value: 'question', label: 'CONTACT.REQ_TYPE_QUESTION' }
|
||||||
|
];
|
||||||
|
|
||||||
|
constructor(private fb: FormBuilder) {
|
||||||
|
this.form = this.fb.group({
|
||||||
|
requestType: ['custom', Validators.required],
|
||||||
|
name: ['', Validators.required],
|
||||||
|
email: ['', [Validators.required, Validators.email]],
|
||||||
|
phone: [''],
|
||||||
|
message: ['', Validators.required],
|
||||||
|
isCompany: [false],
|
||||||
|
companyName: [''],
|
||||||
|
referencePerson: ['']
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle conditional validation for Company fields
|
||||||
|
this.form.get('isCompany')?.valueChanges.subscribe(isCompany => {
|
||||||
|
const companyNameControl = this.form.get('companyName');
|
||||||
|
const refPersonControl = this.form.get('referencePerson');
|
||||||
|
|
||||||
|
if (isCompany) {
|
||||||
|
companyNameControl?.setValidators([Validators.required]);
|
||||||
|
refPersonControl?.setValidators([Validators.required]);
|
||||||
|
} else {
|
||||||
|
companyNameControl?.clearValidators();
|
||||||
|
refPersonControl?.clearValidators();
|
||||||
|
}
|
||||||
|
companyNameControl?.updateValueAndValidity();
|
||||||
|
refPersonControl?.updateValueAndValidity();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onFileSelected(event: Event) {
|
||||||
|
const input = event.target as HTMLInputElement;
|
||||||
|
if (input.files) this.handleFiles(Array.from(input.files));
|
||||||
|
}
|
||||||
|
|
||||||
|
onDragOver(event: DragEvent) {
|
||||||
|
event.preventDefault(); event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
onDrop(event: DragEvent) {
|
||||||
|
event.preventDefault(); event.stopPropagation();
|
||||||
|
if (event.dataTransfer?.files) this.handleFiles(Array.from(event.dataTransfer.files));
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFiles(newFiles: File[]) {
|
||||||
|
const currentFiles = this.files();
|
||||||
|
if (currentFiles.length + newFiles.length > 15) {
|
||||||
|
alert("Max 15 files limit reached.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
newFiles.forEach(file => {
|
||||||
|
const type = this.getFileType(file);
|
||||||
|
const preview: FilePreview = { file, type };
|
||||||
|
|
||||||
|
if (type === 'image') {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
preview.url = e.target?.result as string;
|
||||||
|
this.files.update(files => [...files]);
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}
|
||||||
|
this.files.update(files => [...files, preview]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeFile(index: number) {
|
||||||
|
this.files.update(files => files.filter((_, i) => i !== index));
|
||||||
|
}
|
||||||
|
|
||||||
|
getFileType(file: File): 'image' | 'pdf' | '3d' | 'other' {
|
||||||
|
if (file.type.startsWith('image/')) return 'image';
|
||||||
|
if (file.type === 'application/pdf') return 'pdf';
|
||||||
|
const ext = file.name.split('.').pop()?.toLowerCase();
|
||||||
|
if (['stl', 'step', 'stp', '3mf', 'obj'].includes(ext || '')) return '3d';
|
||||||
|
return 'other';
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit() {
|
||||||
|
if (this.form.valid) {
|
||||||
|
const formData = {
|
||||||
|
...this.form.value,
|
||||||
|
files: this.files().map(f => f.file)
|
||||||
|
};
|
||||||
|
console.log('Form Submit:', formData);
|
||||||
|
|
||||||
|
this.sent.set(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
this.sent.set(false);
|
||||||
|
this.form.reset({ requestType: 'custom', isCompany: false });
|
||||||
|
this.files.set([]);
|
||||||
|
}, 3000);
|
||||||
|
} else {
|
||||||
|
this.form.markAllAsTouched();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
42
frontend/src/app/features/contact/contact-page.component.ts
Normal file
42
frontend/src/app/features/contact/contact-page.component.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { ContactFormComponent } from './components/contact-form/contact-form.component';
|
||||||
|
import { AppCardComponent } from '../../shared/components/app-card/app-card.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-contact-page',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule, TranslateModule, ContactFormComponent, AppCardComponent],
|
||||||
|
template: `
|
||||||
|
<section class="contact-hero">
|
||||||
|
<div class="container">
|
||||||
|
<h1>{{ 'CONTACT.TITLE' | translate }}</h1>
|
||||||
|
<p class="subtitle">Siamo qui per aiutarti. Compila il modulo sottostante per qualsiasi richiesta.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="container content">
|
||||||
|
<app-card>
|
||||||
|
<app-contact-form></app-contact-form>
|
||||||
|
</app-card>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
styles: [`
|
||||||
|
.contact-hero {
|
||||||
|
padding: 5rem 0 3.5rem;
|
||||||
|
background: var(--color-bg);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.subtitle {
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
max-width: 640px;
|
||||||
|
margin: var(--space-3) auto 0;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
padding: 3rem 0 5rem;
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
`]
|
||||||
|
})
|
||||||
|
export class ContactPageComponent {}
|
||||||
8
frontend/src/app/features/contact/contact.routes.ts
Normal file
8
frontend/src/app/features/contact/contact.routes.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { Routes } from '@angular/router';
|
||||||
|
|
||||||
|
export const CONTACT_ROUTES: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
loadComponent: () => import('./contact-page.component').then(m => m.ContactPageComponent)
|
||||||
|
}
|
||||||
|
];
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
{
|
{
|
||||||
"NAV": {
|
"NAV": {
|
||||||
|
"HOME": "Home",
|
||||||
"CALCULATOR": "Calculator",
|
"CALCULATOR": "Calculator",
|
||||||
"SHOP": "Shop",
|
"SHOP": "Shop",
|
||||||
"ABOUT": "About"
|
"ABOUT": "About",
|
||||||
|
"CONTACT": "Contact Us"
|
||||||
},
|
},
|
||||||
"FOOTER": {
|
"FOOTER": {
|
||||||
"PRIVACY": "Privacy",
|
"PRIVACY": "Privacy",
|
||||||
@@ -58,7 +60,22 @@
|
|||||||
"SERVICE_4": "File verification and optimization for printing",
|
"SERVICE_4": "File verification and optimization for printing",
|
||||||
"TARGET_TITLE": "Who is it for",
|
"TARGET_TITLE": "Who is it for",
|
||||||
"TARGET_TEXT": "Small businesses, freelancers, makers and customers looking for a ready-made product from the shop.",
|
"TARGET_TEXT": "Small businesses, freelancers, makers and customers looking for a ready-made product from the shop.",
|
||||||
"CONTACT_US": "Contact Us",
|
"TEAM_TITLE": "Our Team"
|
||||||
"SEND": "Send Message"
|
},
|
||||||
|
"CONTACT": {
|
||||||
|
"TITLE": "Contact Us",
|
||||||
|
"SEND": "Send Message",
|
||||||
|
"REQ_TYPE_LABEL": "Type of Request",
|
||||||
|
"REQ_TYPE_CUSTOM": "Custom Quote",
|
||||||
|
"REQ_TYPE_SERIES": "Series Production",
|
||||||
|
"REQ_TYPE_CONSULT": "Consultation",
|
||||||
|
"REQ_TYPE_QUESTION": "General Questions",
|
||||||
|
"PHONE": "Phone",
|
||||||
|
"IS_COMPANY": "Are you a company?",
|
||||||
|
"COMPANY_NAME": "Company Name",
|
||||||
|
"REF_PERSON": "Reference Person",
|
||||||
|
"UPLOAD_LABEL": "Attachments",
|
||||||
|
"UPLOAD_HINT": "Max 15 files. Supported: Images, PDF, STL, STEP, 3MF, OBJ",
|
||||||
|
"DROP_FILES": "Drop files here or click to upload"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
{
|
{
|
||||||
"NAV": {
|
"NAV": {
|
||||||
|
"HOME": "Home",
|
||||||
"CALCULATOR": "Calcolatore",
|
"CALCULATOR": "Calcolatore",
|
||||||
"SHOP": "Shop",
|
"SHOP": "Shop",
|
||||||
"ABOUT": "Chi Siamo"
|
"ABOUT": "Chi Siamo",
|
||||||
|
"CONTACT": "Contattaci"
|
||||||
},
|
},
|
||||||
"FOOTER": {
|
"FOOTER": {
|
||||||
"PRIVACY": "Privacy",
|
"PRIVACY": "Privacy",
|
||||||
@@ -63,7 +65,22 @@
|
|||||||
"SERVICE_4": "Verifica file e ottimizzazione per la stampa",
|
"SERVICE_4": "Verifica file e ottimizzazione per la stampa",
|
||||||
"TARGET_TITLE": "Per chi è",
|
"TARGET_TITLE": "Per chi è",
|
||||||
"TARGET_TEXT": "Piccole aziende, freelance, smanettoni e clienti che cercano un prodotto già pronto dallo shop.",
|
"TARGET_TEXT": "Piccole aziende, freelance, smanettoni e clienti che cercano un prodotto già pronto dallo shop.",
|
||||||
"CONTACT_US": "Contattaci",
|
"TEAM_TITLE": "Il Nostro Team"
|
||||||
"SEND": "Invia Messaggio"
|
},
|
||||||
|
"CONTACT": {
|
||||||
|
"TITLE": "Contattaci",
|
||||||
|
"SEND": "Invia Messaggio",
|
||||||
|
"REQ_TYPE_LABEL": "Tipo di Richiesta",
|
||||||
|
"REQ_TYPE_CUSTOM": "Preventivo Personalizzato",
|
||||||
|
"REQ_TYPE_SERIES": "Stampa in Serie",
|
||||||
|
"REQ_TYPE_CONSULT": "Consulenza",
|
||||||
|
"REQ_TYPE_QUESTION": "Domande Generali",
|
||||||
|
"PHONE": "Telefono",
|
||||||
|
"IS_COMPANY": "Sei un'azienda?",
|
||||||
|
"COMPANY_NAME": "Ragione Sociale",
|
||||||
|
"REF_PERSON": "Persona di Riferimento",
|
||||||
|
"UPLOAD_LABEL": "Allegati",
|
||||||
|
"UPLOAD_HINT": "Max 15 file. Supportati: Immagini, PDF, STL, STEP, 3MF, OBJ",
|
||||||
|
"DROP_FILES": "Trascina qui i file o clicca per caricare"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user