feat(web): multiple feature
This commit is contained in:
@@ -19,7 +19,7 @@ RUN apt-get update && apt-get install -y \
|
|||||||
libglib2.0-0 \
|
libglib2.0-0 \
|
||||||
libgtk-3-0 \
|
libgtk-3-0 \
|
||||||
libdbus-1-3 \
|
libdbus-1-3 \
|
||||||
libwebkit2gtk-4.1-0 \
|
libwebkit2gtk-4.0-37 \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Install OrcaSlicer
|
# Install OrcaSlicer
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'java'
|
id 'java'
|
||||||
|
id 'application'
|
||||||
id 'org.springframework.boot' version '3.4.1'
|
id 'org.springframework.boot' version '3.4.1'
|
||||||
id 'io.spring.dependency-management' version '1.1.7'
|
id 'io.spring.dependency-management' version '1.1.7'
|
||||||
}
|
}
|
||||||
@@ -13,6 +14,10 @@ java {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
application {
|
||||||
|
mainClass = 'com.printcalculator.BackendApplication'
|
||||||
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
@@ -27,3 +32,11 @@ dependencies {
|
|||||||
tasks.named('test') {
|
tasks.named('test') {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.named('bootRun') {
|
||||||
|
args = ["--spring.profiles.active=local"]
|
||||||
|
}
|
||||||
|
|
||||||
|
application {
|
||||||
|
applicationDefaultJvmArgs = ["-Dspring.profiles.active=local"]
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.printcalculator.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
|
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@Profile("local")
|
||||||
|
public class CorsConfig implements WebMvcConfigurer {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addCorsMappings(CorsRegistry registry) {
|
||||||
|
registry.addMapping("/**")
|
||||||
|
.allowedOrigins("*")
|
||||||
|
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
||||||
|
.allowedHeaders("*")
|
||||||
|
.allowCredentials(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,16 +13,15 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@CrossOrigin(origins = "*") // Allow all for development
|
|
||||||
public class QuoteController {
|
public class QuoteController {
|
||||||
|
|
||||||
private final SlicerService slicerService;
|
private final SlicerService slicerService;
|
||||||
private final QuoteCalculator quoteCalculator;
|
private final QuoteCalculator quoteCalculator;
|
||||||
|
|
||||||
// Defaults
|
// Defaults (using aliases defined in ProfileManager)
|
||||||
private static final String DEFAULT_MACHINE = "Bambu_Lab_A1_machine";
|
private static final String DEFAULT_MACHINE = "bambu_a1";
|
||||||
private static final String DEFAULT_FILAMENT = "Bambu_PLA_Basic";
|
private static final String DEFAULT_FILAMENT = "pla_basic";
|
||||||
private static final String DEFAULT_PROCESS = "Bambu_Process_0.20_Standard";
|
private static final String DEFAULT_PROCESS = "standard";
|
||||||
|
|
||||||
public QuoteController(SlicerService slicerService, QuoteCalculator quoteCalculator) {
|
public QuoteController(SlicerService slicerService, QuoteCalculator quoteCalculator) {
|
||||||
this.slicerService = slicerService;
|
this.slicerService = slicerService;
|
||||||
@@ -32,12 +31,24 @@ public class QuoteController {
|
|||||||
@PostMapping("/api/quote")
|
@PostMapping("/api/quote")
|
||||||
public ResponseEntity<QuoteResult> calculateQuote(
|
public ResponseEntity<QuoteResult> calculateQuote(
|
||||||
@RequestParam("file") MultipartFile file,
|
@RequestParam("file") MultipartFile file,
|
||||||
@RequestParam(value = "machine", defaultValue = DEFAULT_MACHINE) String machine,
|
@RequestParam(value = "machine", required = false, defaultValue = DEFAULT_MACHINE) String machine,
|
||||||
@RequestParam(value = "filament", defaultValue = DEFAULT_FILAMENT) String filament,
|
@RequestParam(value = "filament", required = false, defaultValue = DEFAULT_FILAMENT) String filament,
|
||||||
@RequestParam(value = "process", defaultValue = DEFAULT_PROCESS) String process
|
@RequestParam(value = "process", required = false) String process,
|
||||||
|
@RequestParam(value = "quality", required = false) String quality
|
||||||
) throws IOException {
|
) throws IOException {
|
||||||
|
|
||||||
return processRequest(file, machine, filament, process);
|
// Frontend sends 'quality', backend expects 'process'.
|
||||||
|
// If process is missing, try quality. If both missing, use default.
|
||||||
|
String actualProcess = process;
|
||||||
|
if (actualProcess == null || actualProcess.isEmpty()) {
|
||||||
|
if (quality != null && !quality.isEmpty()) {
|
||||||
|
actualProcess = quality;
|
||||||
|
} else {
|
||||||
|
actualProcess = DEFAULT_PROCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return processRequest(file, machine, filament, actualProcess);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/calculate/stl")
|
@PostMapping("/calculate/stl")
|
||||||
|
|||||||
@@ -13,9 +13,13 @@ import java.util.regex.Pattern;
|
|||||||
@Service
|
@Service
|
||||||
public class GCodeParser {
|
public class GCodeParser {
|
||||||
|
|
||||||
private static final Pattern TIME_PATTERN = Pattern.compile("estimated printing time = (.*)");
|
// OrcaSlicer/BambuStudio format
|
||||||
private static final Pattern FILAMENT_G_PATTERN = Pattern.compile("filament used \\[g\\] = (.*)");
|
// ; estimated printing time = 1h 2m 3s
|
||||||
private static final Pattern FILAMENT_MM_PATTERN = Pattern.compile("filament used \\[mm\\] = (.*)");
|
// ; filament used [g] = 12.34
|
||||||
|
// ; filament used [mm] = 1234.56
|
||||||
|
private static final Pattern TIME_PATTERN = Pattern.compile(";\\s*estimated printing time\\s*=\\s*(.*)");
|
||||||
|
private static final Pattern FILAMENT_G_PATTERN = Pattern.compile(";\\s*filament used \\[g\\]\\s*=\\s*(.*)");
|
||||||
|
private static final Pattern FILAMENT_MM_PATTERN = Pattern.compile(";\\s*filament used \\[mm\\]\\s*=\\s*(.*)");
|
||||||
|
|
||||||
public PrintStats parse(File gcodeFile) throws IOException {
|
public PrintStats parse(File gcodeFile) throws IOException {
|
||||||
long seconds = 0;
|
long seconds = 0;
|
||||||
@@ -25,9 +29,9 @@ public class GCodeParser {
|
|||||||
|
|
||||||
try (BufferedReader reader = new BufferedReader(new FileReader(gcodeFile))) {
|
try (BufferedReader reader = new BufferedReader(new FileReader(gcodeFile))) {
|
||||||
String line;
|
String line;
|
||||||
// Scan first 500 lines for efficiency
|
// Scan first 5000 lines for efficiency (metadata might be further down)
|
||||||
int count = 0;
|
int count = 0;
|
||||||
while ((line = reader.readLine()) != null && count < 500) {
|
while ((line = reader.readLine()) != null && count < 5000) {
|
||||||
line = line.trim();
|
line = line.trim();
|
||||||
if (!line.startsWith(";")) {
|
if (!line.startsWith(";")) {
|
||||||
count++;
|
count++;
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import java.util.Iterator;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class ProfileManager {
|
public class ProfileManager {
|
||||||
@@ -22,9 +24,31 @@ public class ProfileManager {
|
|||||||
private final String profilesRoot;
|
private final String profilesRoot;
|
||||||
private final ObjectMapper mapper;
|
private final ObjectMapper mapper;
|
||||||
|
|
||||||
|
private final Map<String, String> profileAliases;
|
||||||
|
|
||||||
public ProfileManager(@Value("${profiles.root:profiles}") String profilesRoot, ObjectMapper mapper) {
|
public ProfileManager(@Value("${profiles.root:profiles}") String profilesRoot, ObjectMapper mapper) {
|
||||||
this.profilesRoot = profilesRoot;
|
this.profilesRoot = profilesRoot;
|
||||||
this.mapper = mapper;
|
this.mapper = mapper;
|
||||||
|
this.profileAliases = new HashMap<>();
|
||||||
|
initializeAliases();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeAliases() {
|
||||||
|
// Machine Aliases
|
||||||
|
profileAliases.put("bambu_a1", "Bambu Lab A1 0.4 nozzle");
|
||||||
|
|
||||||
|
// Material Aliases
|
||||||
|
profileAliases.put("pla_basic", "Bambu PLA Basic @BBL A1");
|
||||||
|
profileAliases.put("petg_basic", "Bambu PETG Basic @BBL A1");
|
||||||
|
profileAliases.put("tpu_95a", "Bambu TPU 95A @BBL A1");
|
||||||
|
|
||||||
|
// Quality/Process Aliases
|
||||||
|
profileAliases.put("draft", "0.24mm Draft @BBL A1");
|
||||||
|
profileAliases.put("standard", "0.20mm Standard @BBL A1"); // or 0.20mm Standard @BBL A1
|
||||||
|
profileAliases.put("extra_fine", "0.08mm High Quality @BBL A1");
|
||||||
|
|
||||||
|
// Additional aliases from error logs
|
||||||
|
profileAliases.put("Bambu_Process_0.20_Standard", "0.20mm Standard @BBL A1");
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObjectNode getMergedProfile(String profileName, String type) throws IOException {
|
public ObjectNode getMergedProfile(String profileName, String type) throws IOException {
|
||||||
@@ -36,9 +60,12 @@ public class ProfileManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Path findProfileFile(String name, String type) {
|
private Path findProfileFile(String name, String type) {
|
||||||
|
// Check aliases first
|
||||||
|
String resolvedName = profileAliases.getOrDefault(name, name);
|
||||||
|
|
||||||
// Simple search: look for name.json in the profiles_root recursively
|
// Simple search: look for name.json in the profiles_root recursively
|
||||||
// Type could be "machine", "process", "filament" to narrow down, but for now global search
|
// Type could be "machine", "process", "filament" to narrow down, but for now global search
|
||||||
String filename = name.endsWith(".json") ? name : name + ".json";
|
String filename = resolvedName.endsWith(".json") ? resolvedName : resolvedName + ".json";
|
||||||
|
|
||||||
try (Stream<Path> stream = Files.walk(Paths.get(profilesRoot))) {
|
try (Stream<Path> stream = Files.walk(Paths.get(profilesRoot))) {
|
||||||
Optional<Path> found = stream
|
Optional<Path> found = stream
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ public class QuoteCalculator {
|
|||||||
);
|
);
|
||||||
|
|
||||||
List<String> notes = new ArrayList<>();
|
List<String> notes = new ArrayList<>();
|
||||||
notes.add("Generated via Dynamic Slicer (Java Backend)");
|
// notes.add("Generated via Dynamic Slicer (Java Backend)");
|
||||||
|
|
||||||
return new QuoteResult(totalPrice, "EUR", stats, breakdown, notes);
|
return new QuoteResult(totalPrice, "CHF", stats, breakdown, notes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,12 +55,16 @@ public class SlicerService {
|
|||||||
|
|
||||||
// 3. Build Command
|
// 3. Build Command
|
||||||
// --load-settings "machine.json;process.json" --load-filaments "filament.json"
|
// --load-settings "machine.json;process.json" --load-filaments "filament.json"
|
||||||
String settingsArg = mFile.getAbsolutePath() + ";" + pFile.getAbsolutePath();
|
|
||||||
|
|
||||||
List<String> command = new ArrayList<>();
|
List<String> command = new ArrayList<>();
|
||||||
command.add(slicerPath);
|
command.add(slicerPath);
|
||||||
|
|
||||||
|
// Load machine settings
|
||||||
command.add("--load-settings");
|
command.add("--load-settings");
|
||||||
command.add(settingsArg);
|
command.add(mFile.getAbsolutePath());
|
||||||
|
|
||||||
|
// Load process settings
|
||||||
|
command.add("--load-settings");
|
||||||
|
command.add(pFile.getAbsolutePath());
|
||||||
command.add("--load-filaments");
|
command.add("--load-filaments");
|
||||||
command.add(fFile.getAbsolutePath());
|
command.add(fFile.getAbsolutePath());
|
||||||
command.add("--ensure-on-bed");
|
command.add("--ensure-on-bed");
|
||||||
|
|||||||
@@ -70,6 +70,17 @@
|
|||||||
"optimization": false,
|
"optimization": false,
|
||||||
"extractLicenses": false,
|
"extractLicenses": false,
|
||||||
"sourceMap": true
|
"sourceMap": true
|
||||||
|
},
|
||||||
|
"local": {
|
||||||
|
"fileReplacements": [
|
||||||
|
{
|
||||||
|
"replace": "src/environments/environment.ts",
|
||||||
|
"with": "src/environments/environment.local.ts"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"optimization": false,
|
||||||
|
"extractLicenses": false,
|
||||||
|
"sourceMap": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -83,6 +94,9 @@
|
|||||||
},
|
},
|
||||||
"development": {
|
"development": {
|
||||||
"buildTarget": "frontend:build:development"
|
"buildTarget": "frontend:build:development"
|
||||||
|
},
|
||||||
|
"local": {
|
||||||
|
"buildTarget": "frontend:build:local"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"defaultConfiguration": "development"
|
"defaultConfiguration": "development"
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ export const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: 'contact',
|
path: 'contact',
|
||||||
loadChildren: () => import('./features/contact/contact.routes').then(m => m.CONTACT_ROUTES)
|
loadChildren: () => import('./features/contact/contact.routes').then(m => m.CONTACT_ROUTES)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
loadChildren: () => import('./features/legal/legal.routes').then(m => m.LEGAL_ROUTES)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,11 @@
|
|||||||
<span class="brand">3D fab</span>
|
<span class="brand">3D fab</span>
|
||||||
<p class="copyright">© 2026 3D fab.</p>
|
<p class="copyright">© 2026 3D fab.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col links">
|
<div class="col links">
|
||||||
<a routerLink="/privacy">{{ 'FOOTER.PRIVACY' | translate }}</a>
|
<a routerLink="/privacy">{{ 'FOOTER.PRIVACY' | translate }}</a>
|
||||||
<a routerLink="/terms">{{ 'FOOTER.TERMS' | translate }}</a>
|
<a routerLink="/terms">{{ 'FOOTER.TERMS' | translate }}</a>
|
||||||
<a routerLink="/about">{{ 'FOOTER.CONTACT' | translate }}</a>
|
<a routerLink="/contact">{{ 'FOOTER.CONTACT' | translate }}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col social">
|
<div class="col social">
|
||||||
|
|||||||
12
frontend/src/app/features/legal/legal.routes.ts
Normal file
12
frontend/src/app/features/legal/legal.routes.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Routes } from '@angular/router';
|
||||||
|
|
||||||
|
export const LEGAL_ROUTES: Routes = [
|
||||||
|
{
|
||||||
|
path: 'privacy',
|
||||||
|
loadComponent: () => import('./privacy/privacy.component').then(m => m.PrivacyComponent)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'terms',
|
||||||
|
loadComponent: () => import('./terms/terms.component').then(m => m.TermsComponent)
|
||||||
|
}
|
||||||
|
];
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<section class="legal-page">
|
||||||
|
<div class="container narrow">
|
||||||
|
<h1>{{ 'LEGAL.PRIVACY_TITLE' | translate }}</h1>
|
||||||
|
<div class="content">
|
||||||
|
<p class="intro">{{ 'LEGAL.LAST_UPDATE' | translate }}: February 2026</p>
|
||||||
|
|
||||||
|
<h2>{{ 'LEGAL.PRIVACY.SECTION_1' | translate }}</h2>
|
||||||
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
|
||||||
|
|
||||||
|
<h2>{{ 'LEGAL.PRIVACY.SECTION_2' | translate }}</h2>
|
||||||
|
<p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||||
|
|
||||||
|
<h2>{{ 'LEGAL.PRIVACY.SECTION_3' | translate }}</h2>
|
||||||
|
<p>Curabitur pretium tincidunt lacus. Nulla gravida orci a odio. Nullam varius, turpis et commodo pharetra, est eros bibendum elit, nec luctus magna felis sollicitudin mauris.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
.legal-page {
|
||||||
|
padding: 6rem 0;
|
||||||
|
min-height: 70vh;
|
||||||
|
|
||||||
|
.narrow {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 3rem;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
color: var(--color-text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-top: 2.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: var(--color-text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro {
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
line-height: 1.8;
|
||||||
|
color: var(--color-text-main);
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
frontend/src/app/features/legal/privacy/privacy.component.ts
Normal file
11
frontend/src/app/features/legal/privacy/privacy.component.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-privacy',
|
||||||
|
standalone: true,
|
||||||
|
imports: [TranslateModule],
|
||||||
|
templateUrl: './privacy.component.html',
|
||||||
|
styleUrl: './privacy.component.scss'
|
||||||
|
})
|
||||||
|
export class PrivacyComponent {}
|
||||||
18
frontend/src/app/features/legal/terms/terms.component.html
Normal file
18
frontend/src/app/features/legal/terms/terms.component.html
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<section class="legal-page">
|
||||||
|
<div class="container narrow">
|
||||||
|
<h1>{{ 'LEGAL.TERMS_TITLE' | translate }}</h1>
|
||||||
|
<div class="content">
|
||||||
|
<p class="intro">{{ 'LEGAL.LAST_UPDATE' | translate }}: February 2026</p>
|
||||||
|
|
||||||
|
<h2>{{ 'LEGAL.TERMS.SECTION_1' | translate }}</h2>
|
||||||
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
|
||||||
|
|
||||||
|
<h2>{{ 'LEGAL.TERMS.SECTION_2' | translate }}</h2>
|
||||||
|
<p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||||
|
|
||||||
|
<h2>{{ 'LEGAL.TERMS.SECTION_3' | translate }}</h2>
|
||||||
|
<p>I prodotti personalizzati e realizzati su misura tramite stampa 3D non sono soggetti al diritto di recesso, a meno di difetti di fabbricazione evidenti o errori rispetto al file fornito.</p>
|
||||||
|
<p>In caso di problemi, vi preghiamo di contattarci entro 14 giorni dalla ricezione per valutare una sostituzione o un rimborso parziale.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
37
frontend/src/app/features/legal/terms/terms.component.scss
Normal file
37
frontend/src/app/features/legal/terms/terms.component.scss
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
.legal-page {
|
||||||
|
padding: 6rem 0;
|
||||||
|
min-height: 70vh;
|
||||||
|
|
||||||
|
.narrow {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 3rem;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
color: var(--color-text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-top: 2.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: var(--color-text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro {
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
line-height: 1.8;
|
||||||
|
color: var(--color-text-main);
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
frontend/src/app/features/legal/terms/terms.component.ts
Normal file
11
frontend/src/app/features/legal/terms/terms.component.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-terms',
|
||||||
|
standalone: true,
|
||||||
|
imports: [TranslateModule],
|
||||||
|
templateUrl: './terms.component.html',
|
||||||
|
styleUrl: './terms.component.scss'
|
||||||
|
})
|
||||||
|
export class TermsComponent {}
|
||||||
@@ -103,6 +103,21 @@
|
|||||||
"ADDRESS_BIENNE": "Bienne Office, Switzerland",
|
"ADDRESS_BIENNE": "Bienne Office, Switzerland",
|
||||||
"CONTACT_US": "Contact Us"
|
"CONTACT_US": "Contact Us"
|
||||||
},
|
},
|
||||||
|
"LEGAL": {
|
||||||
|
"PRIVACY_TITLE": "Privacy Policy",
|
||||||
|
"TERMS_TITLE": "Terms and Conditions",
|
||||||
|
"LAST_UPDATE": "Last update",
|
||||||
|
"PRIVACY": {
|
||||||
|
"SECTION_1": "1. Data Collection",
|
||||||
|
"SECTION_2": "2. Purpose of Processing",
|
||||||
|
"SECTION_3": "3. Cookies and Tracking"
|
||||||
|
},
|
||||||
|
"TERMS": {
|
||||||
|
"SECTION_1": "1. Terms of Use",
|
||||||
|
"SECTION_2": "2. Orders and Payments",
|
||||||
|
"SECTION_3": "3. Refunds and Returns"
|
||||||
|
}
|
||||||
|
},
|
||||||
"CONTACT": {
|
"CONTACT": {
|
||||||
"TITLE": "Contact Us",
|
"TITLE": "Contact Us",
|
||||||
"SEND": "Send Message",
|
"SEND": "Send Message",
|
||||||
|
|||||||
@@ -82,6 +82,21 @@
|
|||||||
"ADDRESS_BIENNE": "Sede Bienne, Svizzera",
|
"ADDRESS_BIENNE": "Sede Bienne, Svizzera",
|
||||||
"CONTACT_US": "Contattaci"
|
"CONTACT_US": "Contattaci"
|
||||||
},
|
},
|
||||||
|
"LEGAL": {
|
||||||
|
"PRIVACY_TITLE": "Privacy Policy",
|
||||||
|
"TERMS_TITLE": "Termini e Condizioni",
|
||||||
|
"LAST_UPDATE": "Ultimo aggiornamento",
|
||||||
|
"PRIVACY": {
|
||||||
|
"SECTION_1": "1. Raccolta dei Dati",
|
||||||
|
"SECTION_2": "2. Finalità del Trattamento",
|
||||||
|
"SECTION_3": "3. Cookie e Tracciamento"
|
||||||
|
},
|
||||||
|
"TERMS": {
|
||||||
|
"SECTION_1": "1. Condizioni d'Uso",
|
||||||
|
"SECTION_2": "2. Ordini e Pagamenti",
|
||||||
|
"SECTION_3": "3. Rimborsi e Resi"
|
||||||
|
}
|
||||||
|
},
|
||||||
"CONTACT": {
|
"CONTACT": {
|
||||||
"TITLE": "Contattaci",
|
"TITLE": "Contattaci",
|
||||||
"SEND": "Invia Messaggio",
|
"SEND": "Invia Messaggio",
|
||||||
|
|||||||
Reference in New Issue
Block a user