diff --git a/.gitea/workflows/cicd.yaml b/.gitea/workflows/cicd.yaml index 829bdd3..0c975d8 100644 --- a/.gitea/workflows/cicd.yaml +++ b/.gitea/workflows/cicd.yaml @@ -21,13 +21,11 @@ jobs: java-version: '21' distribution: 'temurin' - - name: Run Tests with Maven + - name: Run Tests with Gradle run: | - echo "Installing Maven..." - apt-get update && apt-get install -y maven - echo "Starting Maven Build..." cd backend - mvn -B test + chmod +x gradlew + ./gradlew test build-and-push: needs: test-backend diff --git a/backend/Dockerfile b/backend/Dockerfile index 48f14d6..7d08ed4 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,11 +1,12 @@ # Stage 1: Build Java JAR -FROM maven:3.9-eclipse-temurin-21 AS build +FROM eclipse-temurin:21-jdk-jammy AS build WORKDIR /app -COPY pom.xml . +COPY gradle gradle +COPY gradlew build.gradle settings.gradle ./ # Download dependencies first to cache them -RUN mvn dependency:go-offline +RUN ./gradlew dependencies --no-daemon COPY src ./src -RUN mvn clean package -DskipTests +RUN ./gradlew bootJar -x test --no-daemon # Stage 2: Runtime Environment FROM eclipse-temurin:21-jre-jammy @@ -34,7 +35,7 @@ ENV SLICER_PATH="/opt/orcaslicer/AppRun" WORKDIR /app # 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 ./profiles diff --git a/frontend/src/app/features/calculator/services/quote-estimator.service.ts b/frontend/src/app/features/calculator/services/quote-estimator.service.ts index 554263a..4bde35e 100644 --- a/frontend/src/app/features/calculator/services/quote-estimator.service.ts +++ b/frontend/src/app/features/calculator/services/quote-estimator.service.ts @@ -106,7 +106,7 @@ export class QuoteEstimatorService { return { price: Math.round(totalPrice * 100) / 100, - currency: 'EUR', + currency: 'CHF', printTimeHours: Math.ceil(totalTime / 3600), // Ceil hours materialUsageGrams: Math.ceil(totalWeight), setupCost diff --git a/frontend/src/app/features/contact/components/contact-form/contact-form.component.ts b/frontend/src/app/features/contact/components/contact-form/contact-form.component.ts index 9500fc9..e7c28c0 100644 --- a/frontend/src/app/features/contact/components/contact-form/contact-form.component.ts +++ b/frontend/src/app/features/contact/components/contact-form/contact-form.component.ts @@ -28,22 +28,27 @@ interface FilePreview {
- + - +
- - - - -
- - + +
+
+ {{ 'CONTACT.TYPE_PRIVATE' | translate }} +
+
+ {{ 'CONTACT.TYPE_COMPANY' | translate }} +
-
+ + + + +
@@ -114,32 +119,54 @@ interface FilePreview { 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%; - } + 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; } + /* 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; + } + + .type-option { + padding: 8px 16px; + border-radius: var(--radius-sm); + cursor: pointer; + font-size: 0.875rem; + 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 { + display: flex; + flex-direction: column; + gap: var(--space-4); padding-left: var(--space-4); border-left: 2px solid var(--color-border); margin-bottom: var(--space-4); } + /* File Upload Styles */ .drop-zone { border: 2px dashed var(--color-border); border-radius: var(--radius-md); @@ -200,6 +227,10 @@ export class ContactFormComponent { sent = signal(false); files = signal([]); + get isCompany(): boolean { + return this.form.get('isCompany')?.value; + } + requestTypes = [ { value: 'custom', label: 'CONTACT.REQ_TYPE_CUSTOM' }, { value: 'series', label: 'CONTACT.REQ_TYPE_SERIES' }, @@ -221,21 +252,35 @@ export class ContactFormComponent { // Handle conditional validation for Company fields this.form.get('isCompany')?.valueChanges.subscribe(isCompany => { + const nameControl = this.form.get('name'); const companyNameControl = this.form.get('companyName'); const refPersonControl = this.form.get('referencePerson'); if (isCompany) { + // Company Mode: Name not required / cleared, Company defaults required + nameControl?.clearValidators(); + nameControl?.setValue(''); // Optional: clear value + companyNameControl?.setValidators([Validators.required]); refPersonControl?.setValidators([Validators.required]); } else { + // Private Mode: Name required + nameControl?.setValidators([Validators.required]); + companyNameControl?.clearValidators(); refPersonControl?.clearValidators(); } + + nameControl?.updateValueAndValidity(); companyNameControl?.updateValueAndValidity(); refPersonControl?.updateValueAndValidity(); }); } + setCompanyMode(isCompany: boolean) { + this.form.patchValue({ isCompany }); + } + onFileSelected(event: Event) { const input = event.target as HTMLInputElement; if (input.files) this.handleFiles(Array.from(input.files)); diff --git a/frontend/src/app/features/contact/contact-page.component.ts b/frontend/src/app/features/contact/contact-page.component.ts index 280b744..c0b4630 100644 --- a/frontend/src/app/features/contact/contact-page.component.ts +++ b/frontend/src/app/features/contact/contact-page.component.ts @@ -24,7 +24,7 @@ import { AppCardComponent } from '../../shared/components/app-card/app-card.comp `, styles: [` .contact-hero { - padding: 5rem 0 3.5rem; + padding: 3rem 0 2rem; background: var(--color-bg); text-align: center; } @@ -34,7 +34,7 @@ import { AppCardComponent } from '../../shared/components/app-card/app-card.comp margin: var(--space-3) auto 0; } .content { - padding: 3rem 0 5rem; + padding: 2rem 0 5rem; max-width: 800px; } `] diff --git a/frontend/src/assets/i18n/en.json b/frontend/src/assets/i18n/en.json index 93de138..ba738d4 100644 --- a/frontend/src/assets/i18n/en.json +++ b/frontend/src/assets/i18n/en.json @@ -40,7 +40,7 @@ }, "SHOP": { "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", "BACK": "Back to Shop" }, @@ -66,6 +66,8 @@ "TITLE": "Contact Us", "SEND": "Send Message", "REQ_TYPE_LABEL": "Type of Request", + "TYPE_PRIVATE": "Private", + "TYPE_COMPANY": "Business", "REQ_TYPE_CUSTOM": "Custom Quote", "REQ_TYPE_SERIES": "Series Production", "REQ_TYPE_CONSULT": "Consultation", diff --git a/frontend/src/assets/i18n/it.json b/frontend/src/assets/i18n/it.json index a11d2ee..dadfc03 100644 --- a/frontend/src/assets/i18n/it.json +++ b/frontend/src/assets/i18n/it.json @@ -45,7 +45,7 @@ }, "SHOP": { "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", "BACK": "Torna allo Shop" }, @@ -71,6 +71,8 @@ "TITLE": "Contattaci", "SEND": "Invia Messaggio", "REQ_TYPE_LABEL": "Tipo di Richiesta", + "TYPE_PRIVATE": "Privato", + "TYPE_COMPANY": "Azienda", "REQ_TYPE_CUSTOM": "Preventivo Personalizzato", "REQ_TYPE_SERIES": "Stampa in Serie", "REQ_TYPE_CONSULT": "Consulenza",