produzione 1 #9
@@ -21,13 +21,11 @@ jobs:
|
|||||||
java-version: '21'
|
java-version: '21'
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
|
|
||||||
- name: Run Tests with Maven
|
- name: Run Tests with Gradle
|
||||||
run: |
|
run: |
|
||||||
echo "Installing Maven..."
|
|
||||||
apt-get update && apt-get install -y maven
|
|
||||||
echo "Starting Maven Build..."
|
|
||||||
cd backend
|
cd backend
|
||||||
mvn -B test
|
chmod +x gradlew
|
||||||
|
./gradlew test
|
||||||
|
|
||||||
build-and-push:
|
build-and-push:
|
||||||
needs: test-backend
|
needs: test-backend
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
# Stage 1: Build Java JAR
|
# Stage 1: Build Java JAR
|
||||||
FROM maven:3.9-eclipse-temurin-21 AS build
|
FROM eclipse-temurin:21-jdk-jammy AS build
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY pom.xml .
|
COPY gradle gradle
|
||||||
|
COPY gradlew build.gradle settings.gradle ./
|
||||||
# Download dependencies first to cache them
|
# Download dependencies first to cache them
|
||||||
RUN mvn dependency:go-offline
|
RUN ./gradlew dependencies --no-daemon
|
||||||
COPY src ./src
|
COPY src ./src
|
||||||
RUN mvn clean package -DskipTests
|
RUN ./gradlew bootJar -x test --no-daemon
|
||||||
|
|
||||||
# Stage 2: Runtime Environment
|
# Stage 2: Runtime Environment
|
||||||
FROM eclipse-temurin:21-jre-jammy
|
FROM eclipse-temurin:21-jre-jammy
|
||||||
@@ -34,7 +35,7 @@ ENV SLICER_PATH="/opt/orcaslicer/AppRun"
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
# Copy JAR from build stage
|
# Copy JAR from build stage
|
||||||
COPY --from=build /app/target/*.jar app.jar
|
COPY --from=build /app/build/libs/*.jar app.jar
|
||||||
# Copy profiles
|
# Copy profiles
|
||||||
COPY profiles ./profiles
|
COPY profiles ./profiles
|
||||||
|
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ export class QuoteEstimatorService {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
price: Math.round(totalPrice * 100) / 100,
|
price: Math.round(totalPrice * 100) / 100,
|
||||||
currency: 'EUR',
|
currency: 'CHF',
|
||||||
printTimeHours: Math.ceil(totalTime / 3600), // Ceil hours
|
printTimeHours: Math.ceil(totalTime / 3600), // Ceil hours
|
||||||
materialUsageGrams: Math.ceil(totalWeight),
|
materialUsageGrams: Math.ceil(totalWeight),
|
||||||
setupCost
|
setupCost
|
||||||
|
|||||||
@@ -28,22 +28,27 @@ interface FilePreview {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<!-- Email -->
|
<!-- Phone -->
|
||||||
<app-input formControlName="email" type="email" label="Email *" placeholder="tuo@email.com" class="col"></app-input>
|
<app-input formControlName="email" type="email" label="Email *" placeholder="tuo@email.com" class="col"></app-input>
|
||||||
<!-- Phone -->
|
<!-- Phone -->
|
||||||
<app-input formControlName="phone" type="tel" [label]="('CONTACT.PHONE' | translate)" placeholder="+39 000 000 0000" class="col"></app-input>
|
<app-input formControlName="phone" type="tel" [label]="('CONTACT.PHONE' | translate)" placeholder="+41 00 000 00 00" class="col"></app-input>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Name (Always Required) -->
|
<!-- User Type Selector (Segmented Control) -->
|
||||||
<app-input formControlName="name" label="Nome *" placeholder="Il tuo nome"></app-input>
|
<div class="user-type-selector">
|
||||||
|
<div class="type-option" [class.selected]="!isCompany" (click)="setCompanyMode(false)">
|
||||||
<!-- Company Toggle & Fields -->
|
{{ 'CONTACT.TYPE_PRIVATE' | translate }}
|
||||||
<div class="form-group checkbox-group">
|
</div>
|
||||||
<input type="checkbox" formControlName="isCompany" id="isCompany">
|
<div class="type-option" [class.selected]="isCompany" (click)="setCompanyMode(true)">
|
||||||
<label for="isCompany">{{ 'CONTACT.IS_COMPANY' | translate }}</label>
|
{{ 'CONTACT.TYPE_COMPANY' | translate }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="form.get('isCompany')?.value" class="company-fields">
|
<!-- Personal Name (Only if NOT Company) -->
|
||||||
|
<app-input *ngIf="!isCompany" formControlName="name" label="Nome *" placeholder="Il tuo nome"></app-input>
|
||||||
|
|
||||||
|
<!-- Company Fields (Only if Company) -->
|
||||||
|
<div *ngIf="isCompany" class="company-fields">
|
||||||
<app-input formControlName="companyName" [label]="('CONTACT.COMPANY_NAME' | translate) + ' *'" placeholder="Nome Azienda"></app-input>
|
<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>
|
<app-input formControlName="referencePerson" [label]="('CONTACT.REF_PERSON' | translate) + ' *'" placeholder="Persona di Riferimento"></app-input>
|
||||||
</div>
|
</div>
|
||||||
@@ -114,32 +119,54 @@ interface FilePreview {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--space-4);
|
gap: var(--space-4);
|
||||||
margin-bottom: var(--space-4);
|
margin-bottom: var(--space-4);
|
||||||
|
|
||||||
@media(min-width: 768px) {
|
@media(min-width: 768px) {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
.col { flex: 1; margin-bottom: 0; }
|
.col { flex: 1; margin-bottom: 0; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Modify direct app-input child of row if possible or target host */
|
app-input.col { width: 100%; }
|
||||||
app-input.col {
|
|
||||||
width: 100%;
|
/* User Type Selector Styles */
|
||||||
|
.user-type-selector {
|
||||||
|
display: inline-flex;
|
||||||
|
background-color: var(--color-neutral-100);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: 4px;
|
||||||
|
margin-bottom: var(--space-4);
|
||||||
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox-group {
|
.type-option {
|
||||||
flex-direction: row;
|
padding: 8px 16px;
|
||||||
align-items: center;
|
border-radius: var(--radius-sm);
|
||||||
gap: var(--space-2);
|
cursor: pointer;
|
||||||
input[type="checkbox"] { width: auto; margin: 0; }
|
font-size: 0.875rem;
|
||||||
label { margin: 0; }
|
font-weight: 500;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
&:hover { color: var(--color-text); }
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
background-color: var(--color-brand);
|
||||||
|
color: #000; /* Assuming brand color is light/yellow, black text is safer. Adjust if brand is dark. */
|
||||||
|
font-weight: 600;
|
||||||
|
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.company-fields {
|
.company-fields {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-4);
|
||||||
padding-left: var(--space-4);
|
padding-left: var(--space-4);
|
||||||
border-left: 2px solid var(--color-border);
|
border-left: 2px solid var(--color-border);
|
||||||
margin-bottom: var(--space-4);
|
margin-bottom: var(--space-4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* File Upload Styles */
|
||||||
.drop-zone {
|
.drop-zone {
|
||||||
border: 2px dashed var(--color-border);
|
border: 2px dashed var(--color-border);
|
||||||
border-radius: var(--radius-md);
|
border-radius: var(--radius-md);
|
||||||
@@ -200,6 +227,10 @@ export class ContactFormComponent {
|
|||||||
sent = signal(false);
|
sent = signal(false);
|
||||||
files = signal<FilePreview[]>([]);
|
files = signal<FilePreview[]>([]);
|
||||||
|
|
||||||
|
get isCompany(): boolean {
|
||||||
|
return this.form.get('isCompany')?.value;
|
||||||
|
}
|
||||||
|
|
||||||
requestTypes = [
|
requestTypes = [
|
||||||
{ value: 'custom', label: 'CONTACT.REQ_TYPE_CUSTOM' },
|
{ value: 'custom', label: 'CONTACT.REQ_TYPE_CUSTOM' },
|
||||||
{ value: 'series', label: 'CONTACT.REQ_TYPE_SERIES' },
|
{ value: 'series', label: 'CONTACT.REQ_TYPE_SERIES' },
|
||||||
@@ -221,21 +252,35 @@ export class ContactFormComponent {
|
|||||||
|
|
||||||
// Handle conditional validation for Company fields
|
// Handle conditional validation for Company fields
|
||||||
this.form.get('isCompany')?.valueChanges.subscribe(isCompany => {
|
this.form.get('isCompany')?.valueChanges.subscribe(isCompany => {
|
||||||
|
const nameControl = this.form.get('name');
|
||||||
const companyNameControl = this.form.get('companyName');
|
const companyNameControl = this.form.get('companyName');
|
||||||
const refPersonControl = this.form.get('referencePerson');
|
const refPersonControl = this.form.get('referencePerson');
|
||||||
|
|
||||||
if (isCompany) {
|
if (isCompany) {
|
||||||
|
// Company Mode: Name not required / cleared, Company defaults required
|
||||||
|
nameControl?.clearValidators();
|
||||||
|
nameControl?.setValue(''); // Optional: clear value
|
||||||
|
|
||||||
companyNameControl?.setValidators([Validators.required]);
|
companyNameControl?.setValidators([Validators.required]);
|
||||||
refPersonControl?.setValidators([Validators.required]);
|
refPersonControl?.setValidators([Validators.required]);
|
||||||
} else {
|
} else {
|
||||||
|
// Private Mode: Name required
|
||||||
|
nameControl?.setValidators([Validators.required]);
|
||||||
|
|
||||||
companyNameControl?.clearValidators();
|
companyNameControl?.clearValidators();
|
||||||
refPersonControl?.clearValidators();
|
refPersonControl?.clearValidators();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nameControl?.updateValueAndValidity();
|
||||||
companyNameControl?.updateValueAndValidity();
|
companyNameControl?.updateValueAndValidity();
|
||||||
refPersonControl?.updateValueAndValidity();
|
refPersonControl?.updateValueAndValidity();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setCompanyMode(isCompany: boolean) {
|
||||||
|
this.form.patchValue({ isCompany });
|
||||||
|
}
|
||||||
|
|
||||||
onFileSelected(event: Event) {
|
onFileSelected(event: Event) {
|
||||||
const input = event.target as HTMLInputElement;
|
const input = event.target as HTMLInputElement;
|
||||||
if (input.files) this.handleFiles(Array.from(input.files));
|
if (input.files) this.handleFiles(Array.from(input.files));
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import { AppCardComponent } from '../../shared/components/app-card/app-card.comp
|
|||||||
`,
|
`,
|
||||||
styles: [`
|
styles: [`
|
||||||
.contact-hero {
|
.contact-hero {
|
||||||
padding: 5rem 0 3.5rem;
|
padding: 3rem 0 2rem;
|
||||||
background: var(--color-bg);
|
background: var(--color-bg);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
@@ -34,7 +34,7 @@ import { AppCardComponent } from '../../shared/components/app-card/app-card.comp
|
|||||||
margin: var(--space-3) auto 0;
|
margin: var(--space-3) auto 0;
|
||||||
}
|
}
|
||||||
.content {
|
.content {
|
||||||
padding: 3rem 0 5rem;
|
padding: 2rem 0 5rem;
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
}
|
}
|
||||||
`]
|
`]
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
},
|
},
|
||||||
"SHOP": {
|
"SHOP": {
|
||||||
"TITLE": "Technical solutions",
|
"TITLE": "Technical solutions",
|
||||||
"SUBTITLE": "Ready-made products solving practical problems, no useless decorations.",
|
"SUBTITLE": "Ready-made products solving practical problems",
|
||||||
"ADD_CART": "Add to Cart",
|
"ADD_CART": "Add to Cart",
|
||||||
"BACK": "Back to Shop"
|
"BACK": "Back to Shop"
|
||||||
},
|
},
|
||||||
@@ -66,6 +66,8 @@
|
|||||||
"TITLE": "Contact Us",
|
"TITLE": "Contact Us",
|
||||||
"SEND": "Send Message",
|
"SEND": "Send Message",
|
||||||
"REQ_TYPE_LABEL": "Type of Request",
|
"REQ_TYPE_LABEL": "Type of Request",
|
||||||
|
"TYPE_PRIVATE": "Private",
|
||||||
|
"TYPE_COMPANY": "Business",
|
||||||
"REQ_TYPE_CUSTOM": "Custom Quote",
|
"REQ_TYPE_CUSTOM": "Custom Quote",
|
||||||
"REQ_TYPE_SERIES": "Series Production",
|
"REQ_TYPE_SERIES": "Series Production",
|
||||||
"REQ_TYPE_CONSULT": "Consultation",
|
"REQ_TYPE_CONSULT": "Consultation",
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
},
|
},
|
||||||
"SHOP": {
|
"SHOP": {
|
||||||
"TITLE": "Soluzioni tecniche",
|
"TITLE": "Soluzioni tecniche",
|
||||||
"SUBTITLE": "Prodotti pronti che risolvono problemi pratici, niente decorazioni inutili.",
|
"SUBTITLE": "Prodotti pronti che risolvono problemi pratici",
|
||||||
"ADD_CART": "Aggiungi al Carrello",
|
"ADD_CART": "Aggiungi al Carrello",
|
||||||
"BACK": "Torna allo Shop"
|
"BACK": "Torna allo Shop"
|
||||||
},
|
},
|
||||||
@@ -71,6 +71,8 @@
|
|||||||
"TITLE": "Contattaci",
|
"TITLE": "Contattaci",
|
||||||
"SEND": "Invia Messaggio",
|
"SEND": "Invia Messaggio",
|
||||||
"REQ_TYPE_LABEL": "Tipo di Richiesta",
|
"REQ_TYPE_LABEL": "Tipo di Richiesta",
|
||||||
|
"TYPE_PRIVATE": "Privato",
|
||||||
|
"TYPE_COMPANY": "Azienda",
|
||||||
"REQ_TYPE_CUSTOM": "Preventivo Personalizzato",
|
"REQ_TYPE_CUSTOM": "Preventivo Personalizzato",
|
||||||
"REQ_TYPE_SERIES": "Stampa in Serie",
|
"REQ_TYPE_SERIES": "Stampa in Serie",
|
||||||
"REQ_TYPE_CONSULT": "Consulenza",
|
"REQ_TYPE_CONSULT": "Consulenza",
|
||||||
|
|||||||
Reference in New Issue
Block a user