feat(front-end): new logo edited
This commit is contained in:
@@ -2,6 +2,7 @@ package com.printcalculator.dto;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public record ShopProductDetailDto(
|
||||
@@ -25,6 +26,8 @@ public record ShopProductDetailDto(
|
||||
List<ShopProductVariantOptionDto> variants,
|
||||
PublicMediaUsageDto primaryImage,
|
||||
List<PublicMediaUsageDto> images,
|
||||
ShopProductModelDto model3d
|
||||
ShopProductModelDto model3d,
|
||||
String publicPath,
|
||||
Map<String, String> localizedPaths
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.printcalculator.dto;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public record ShopProductSummaryDto(
|
||||
@@ -15,6 +16,8 @@ public record ShopProductSummaryDto(
|
||||
BigDecimal priceToChf,
|
||||
ShopProductVariantOptionDto defaultVariant,
|
||||
PublicMediaUsageDto primaryImage,
|
||||
ShopProductModelDto model3d
|
||||
ShopProductModelDto model3d,
|
||||
String publicPath,
|
||||
Map<String, String> localizedPaths
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -399,6 +399,7 @@ public class PublicShopCatalogService {
|
||||
Map<String, String> variantColorHexByMaterialAndColor,
|
||||
String language) {
|
||||
List<PublicMediaUsageDto> images = productMediaBySlug.getOrDefault(productMediaUsageKey(entry.product()), List.of());
|
||||
Map<String, String> localizedPaths = ShopPublicPathSupport.buildLocalizedProductPaths(entry.product());
|
||||
return new ShopProductSummaryDto(
|
||||
entry.product().getId(),
|
||||
entry.product().getSlug(),
|
||||
@@ -415,7 +416,9 @@ public class PublicShopCatalogService {
|
||||
resolvePriceTo(entry.variants()),
|
||||
toVariantDto(entry.defaultVariant(), entry.defaultVariant(), variantColorHexByMaterialAndColor, language),
|
||||
selectPrimaryMedia(images),
|
||||
toProductModelDto(entry)
|
||||
toProductModelDto(entry),
|
||||
localizedPaths.getOrDefault(normalizeLanguage(language), localizedPaths.get("it")),
|
||||
localizedPaths
|
||||
);
|
||||
}
|
||||
|
||||
@@ -426,6 +429,7 @@ public class PublicShopCatalogService {
|
||||
List<PublicMediaUsageDto> images = productMediaBySlug.getOrDefault(productMediaUsageKey(entry.product()), List.of());
|
||||
String localizedSeoTitle = entry.product().getSeoTitleForLanguage(language);
|
||||
String localizedSeoDescription = entry.product().getSeoDescriptionForLanguage(language);
|
||||
Map<String, String> localizedPaths = ShopPublicPathSupport.buildLocalizedProductPaths(entry.product());
|
||||
return new ShopProductDetailDto(
|
||||
entry.product().getId(),
|
||||
entry.product().getSlug(),
|
||||
@@ -453,7 +457,9 @@ public class PublicShopCatalogService {
|
||||
.toList(),
|
||||
selectPrimaryMedia(images),
|
||||
images,
|
||||
toProductModelDto(entry)
|
||||
toProductModelDto(entry),
|
||||
localizedPaths.getOrDefault(normalizeLanguage(language), localizedPaths.get("it")),
|
||||
localizedPaths
|
||||
);
|
||||
}
|
||||
|
||||
@@ -514,6 +520,22 @@ public class PublicShopCatalogService {
|
||||
return raw;
|
||||
}
|
||||
|
||||
private String normalizeLanguage(String language) {
|
||||
String normalized = trimToNull(language);
|
||||
if (normalized == null) {
|
||||
return "it";
|
||||
}
|
||||
normalized = normalized.toLowerCase(Locale.ROOT);
|
||||
int separatorIndex = normalized.indexOf('-');
|
||||
if (separatorIndex > 0) {
|
||||
normalized = normalized.substring(0, separatorIndex);
|
||||
}
|
||||
return switch (normalized) {
|
||||
case "en", "de", "fr" -> normalized;
|
||||
default -> "it";
|
||||
};
|
||||
}
|
||||
|
||||
private ShopProductModelDto toProductModelDto(ProductEntry entry) {
|
||||
if (entry.modelAsset() == null) {
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.printcalculator.service.shop;
|
||||
|
||||
import com.printcalculator.entity.ShopProduct;
|
||||
|
||||
import java.text.Normalizer;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
final class ShopPublicPathSupport {
|
||||
private static final String PRODUCT_ROUTE_PREFIX = "/shop/p/";
|
||||
|
||||
private ShopPublicPathSupport() {
|
||||
}
|
||||
|
||||
static String buildProductPathSegment(ShopProduct product, String language) {
|
||||
String localizedName = product.getNameForLanguage(language);
|
||||
String idPrefix = productIdPrefix(product.getId());
|
||||
String tail = firstNonBlank(slugify(localizedName), slugify(product.getSlug()), "product");
|
||||
return idPrefix.isBlank() ? tail : idPrefix + "-" + tail;
|
||||
}
|
||||
|
||||
static Map<String, String> buildLocalizedProductPaths(ShopProduct product) {
|
||||
Map<String, String> localizedPaths = new LinkedHashMap<>();
|
||||
for (String language : ShopProduct.SUPPORTED_LANGUAGES) {
|
||||
localizedPaths.put(language, "/" + language + PRODUCT_ROUTE_PREFIX + buildProductPathSegment(product, language));
|
||||
}
|
||||
return localizedPaths;
|
||||
}
|
||||
|
||||
static String productIdPrefix(UUID productId) {
|
||||
if (productId == null) {
|
||||
return "";
|
||||
}
|
||||
String raw = productId.toString().trim().toLowerCase(Locale.ROOT);
|
||||
int dashIndex = raw.indexOf('-');
|
||||
if (dashIndex > 0) {
|
||||
return raw.substring(0, dashIndex);
|
||||
}
|
||||
return raw.length() >= 8 ? raw.substring(0, 8) : raw;
|
||||
}
|
||||
|
||||
static String slugify(String rawValue) {
|
||||
String safeValue = rawValue == null ? "" : rawValue;
|
||||
String normalized = Normalizer.normalize(safeValue, Normalizer.Form.NFD)
|
||||
.replaceAll("\\p{M}+", "")
|
||||
.toLowerCase(Locale.ROOT)
|
||||
.replaceAll("[^a-z0-9]+", "-")
|
||||
.replaceAll("^-+|-+$", "")
|
||||
.replaceAll("-{2,}", "-");
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private static String firstNonBlank(String... values) {
|
||||
if (values == null) {
|
||||
return null;
|
||||
}
|
||||
for (String value : values) {
|
||||
if (value != null && !value.isBlank()) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.Normalizer;
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
@@ -19,7 +18,6 @@ import java.time.OffsetDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
@@ -31,6 +29,12 @@ public class ShopSitemapService {
|
||||
private static final List<String> SUPPORTED_LANGUAGES = ShopProduct.SUPPORTED_LANGUAGES;
|
||||
private static final String DEFAULT_LANGUAGE = "it";
|
||||
private static final DateTimeFormatter LASTMOD_FORMATTER = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
|
||||
private static final Map<String, String> HREFLANG_BY_LANGUAGE = Map.of(
|
||||
"it", "it-CH",
|
||||
"en", "en-CH",
|
||||
"de", "de-CH",
|
||||
"fr", "fr-CH"
|
||||
);
|
||||
|
||||
private final ShopCategoryRepository shopCategoryRepository;
|
||||
private final ShopProductRepository shopProductRepository;
|
||||
@@ -130,7 +134,7 @@ public class ShopSitemapService {
|
||||
|
||||
Map<String, String> hrefByLanguage = new LinkedHashMap<>();
|
||||
for (String language : SUPPORTED_LANGUAGES) {
|
||||
String publicSegment = localizedProductPathSegment(product, language);
|
||||
String publicSegment = ShopPublicPathSupport.buildProductPathSegment(product, language);
|
||||
hrefByLanguage.put(language, frontendBaseUrl + "/" + language + "/shop/p/" + pathEncodeSegment(publicSegment));
|
||||
}
|
||||
|
||||
@@ -169,7 +173,7 @@ public class ShopSitemapService {
|
||||
continue;
|
||||
}
|
||||
xml.append(" <xhtml:link rel=\"alternate\" hreflang=\"")
|
||||
.append(language)
|
||||
.append(HREFLANG_BY_LANGUAGE.getOrDefault(language, language))
|
||||
.append("\" href=\"")
|
||||
.append(xmlEscape(href))
|
||||
.append("\" />\n");
|
||||
@@ -186,48 +190,6 @@ public class ShopSitemapService {
|
||||
xml.append(" </url>\n");
|
||||
}
|
||||
|
||||
private String localizedProductPathSegment(ShopProduct product, String language) {
|
||||
String localizedName = product.getNameForLanguage(language);
|
||||
String idPrefix = productIdPrefix(product.getId());
|
||||
String tail = firstNonBlank(slugify(localizedName), slugify(product.getSlug()), "product");
|
||||
return idPrefix.isBlank() ? tail : idPrefix + "-" + tail;
|
||||
}
|
||||
|
||||
private String productIdPrefix(UUID productId) {
|
||||
if (productId == null) {
|
||||
return "";
|
||||
}
|
||||
String raw = productId.toString().trim().toLowerCase(Locale.ROOT);
|
||||
int dashIndex = raw.indexOf('-');
|
||||
if (dashIndex > 0) {
|
||||
return raw.substring(0, dashIndex);
|
||||
}
|
||||
return raw.length() >= 8 ? raw.substring(0, 8) : raw;
|
||||
}
|
||||
|
||||
static String slugify(String rawValue) {
|
||||
String safeValue = rawValue == null ? "" : rawValue;
|
||||
String normalized = Normalizer.normalize(safeValue, Normalizer.Form.NFD)
|
||||
.replaceAll("\\p{M}+", "")
|
||||
.toLowerCase(Locale.ROOT)
|
||||
.replaceAll("[^a-z0-9]+", "-")
|
||||
.replaceAll("^-+|-+$", "")
|
||||
.replaceAll("-{2,}", "-");
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private String firstNonBlank(String... values) {
|
||||
if (values == null) {
|
||||
return null;
|
||||
}
|
||||
for (String value : values) {
|
||||
if (value != null && !value.isBlank()) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String pathEncodeSegment(String rawSegment) {
|
||||
String safeSegment = rawSegment == null ? "" : rawSegment;
|
||||
return URLEncoder.encode(safeSegment, StandardCharsets.UTF_8).replace("+", "%20");
|
||||
|
||||
Reference in New Issue
Block a user