Compare commits

..

6 Commits

Author SHA1 Message Date
Unchained 622e7f3642 Merge branch 'dev'
Build and Deploy / build (push) Successful in 0s
2026-04-09 12:02:34 +02:00
Unchained 864008af16 fix: unify free shipping threshold to 10000 RSD across all pages
- Create centralized shipping config file (src/lib/config/shipping.ts)
- Update CartDrawer.tsx: change hardcoded 5000 to FREE_SHIPPING_THRESHOLD_RSD constant
- Update TickerBar.tsx: change hardcoded 10000 to use centralized constant
- Ensure uniform free shipping messaging across all pages and localizations
2026-04-09 11:55:55 +02:00
Unchained 011994b36e Merge branch 'feature/programmatic-seo' into dev 2026-04-09 11:37:31 +02:00
Unchained 261fd36579 Merge dev into master
Build and Deploy / build (push) Successful in 1s
Includes canonical URL fix and build workflow improvements
2026-04-06 15:42:50 +02:00
Unchained 09725c6b0d Merge feature/canonical-url-fix into dev
Fix canonical URL redirect errors by always including locale prefix
2026-04-06 15:40:56 +02:00
Unchained 57bae7ed6f fix: canonical URLs to always include locale prefix
Fixed Canonical Redirect Error in Google Search Console by ensuring
canonical URLs always include the locale prefix (/sr, /en, /de, /fr)
instead of omitting it for the default locale.

Changes:
- layout.tsx: Fixed canonical and hreflang URL generation
- page.tsx: Fixed homepage canonical URL
- contact/page.tsx: Fixed contact page canonical URL
- about/page.tsx: Fixed about page canonical URL
- products/page.tsx: Fixed products page canonical URL
- products/[slug]/page.tsx: Fixed product detail canonical URL

Before: /contact (when locale=sr)
After: /sr/contact (all locales)

Fixes: Canonical Redirect Error in Google Search Console
2026-04-06 15:32:24 +02:00
9 changed files with 63 additions and 11 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ export async function generateMetadata({ params }: AboutPageProps): Promise<Meta
const metadata = getPageMetadata(validLocale as Locale);
const keywords = getPageKeywords(validLocale as Locale, 'about');
const localePrefix = validLocale === DEFAULT_LOCALE ? "" : `/${validLocale}`;
const localePrefix = `/${validLocale}`;
const canonicalUrl = `${baseUrl}${localePrefix}/about`;
return {
+1 -1
View File
@@ -16,7 +16,7 @@ export async function generateMetadata({ params }: ContactPageProps): Promise<Me
const metadata = getPageMetadata(validLocale as Locale);
const keywords = getPageKeywords(validLocale as Locale, 'contact');
const localePrefix = validLocale === DEFAULT_LOCALE ? "" : `/${validLocale}`;
const localePrefix = `/${validLocale}`;
const canonicalUrl = `${baseUrl}${localePrefix}/contact`;
return {
+2 -2
View File
@@ -21,11 +21,11 @@ export async function generateMetadata({
}): Promise<Metadata> {
const { locale } = await params;
const validLocale = isValidLocale(locale) ? locale : DEFAULT_LOCALE;
const localePrefix = validLocale === DEFAULT_LOCALE ? "" : `/${locale}`;
const localePrefix = `/${locale}`;
const languages: Record<string, string> = {};
for (const loc of SUPPORTED_LOCALES) {
const prefix = loc === DEFAULT_LOCALE ? "" : `/${loc}`;
const prefix = `/${loc}`;
languages[loc] = `${baseUrl}${prefix}`;
}
+2 -2
View File
@@ -29,8 +29,8 @@ export async function generateMetadata({ params }: { params: Promise<{ locale: s
setRequestLocale(validLocale);
// Build canonical URL
const localePrefix = validLocale === DEFAULT_LOCALE ? "" : `/${validLocale}`;
const canonicalUrl = `${baseUrl}${localePrefix || '/'}`;
const localePrefix = `/${validLocale}`;
const canonicalUrl = `${baseUrl}${localePrefix}`;
return {
title: metadata.home.title,
+1 -1
View File
@@ -57,7 +57,7 @@ export async function generateMetadata({ params }: ProductPageProps): Promise<Me
const secondaryKeywords = keywords.secondary.map(replaceTemplate);
// Build canonical URL
const localePrefix = validLocale === DEFAULT_LOCALE ? "" : `/${validLocale}`;
const localePrefix = `/${validLocale}`;
const canonicalUrl = `${baseUrl}${localePrefix}/products/${slug}`;
// Get product image for OpenGraph
+1 -1
View File
@@ -24,7 +24,7 @@ export async function generateMetadata({ params }: ProductsPageProps): Promise<M
const keywords = getPageKeywords(validLocale as Locale, 'products');
// Build canonical URL
const localePrefix = validLocale === DEFAULT_LOCALE ? "" : `/${validLocale}`;
const localePrefix = `/${validLocale}`;
const canonicalUrl = `${baseUrl}${localePrefix}/products`;
return {
+3 -2
View File
@@ -9,6 +9,7 @@ import { useTranslations, useLocale } from "next-intl";
import { useSaleorCheckoutStore } from "@/stores/saleorCheckoutStore";
import { formatPrice } from "@/lib/saleor";
import { useAnalytics } from "@/lib/analytics";
import { FREE_SHIPPING_THRESHOLD_RSD } from "@/lib/config/shipping";
export default function CartDrawer() {
const t = useTranslations("Cart");
@@ -251,9 +252,9 @@ export default function CartDrawer() {
</span>
</div>
{(checkout?.subtotalPrice?.gross?.amount || 0) < 5000 && (
{(checkout?.subtotalPrice?.gross?.amount || 0) < FREE_SHIPPING_THRESHOLD_RSD && (
<p className="text-xs text-[#666666] text-center">
{t("freeShippingOver", { amount: formatPrice(5000) })}
{t("freeShippingOver", { amount: formatPrice(FREE_SHIPPING_THRESHOLD_RSD) })}
</p>
)}
</div>
+2 -1
View File
@@ -1,10 +1,11 @@
"use client";
import { motion } from "framer-motion";
import { FREE_SHIPPING_THRESHOLD_RSD } from "@/lib/config/shipping";
export default function TickerBar() {
const items = [
"Free shipping on orders over 10000 RSD",
`Free shipping on orders over ${FREE_SHIPPING_THRESHOLD_RSD} RSD`,
"Natural ingredients",
"Cruelty-free",
"Handmade with love",
+50
View File
@@ -0,0 +1,50 @@
/**
* Shipping configuration
* Centralized configuration for shipping rules and thresholds
*/
/**
* Free shipping threshold in RSD (Serbian Dinar)
* Orders above this amount qualify for free shipping
*/
export const FREE_SHIPPING_THRESHOLD_RSD = 10000;
/**
* Default shipping cost in RSD when order is below threshold
*/
export const DEFAULT_SHIPPING_COST_RSD = 500;
/**
* Currency code for shipping calculations
*/
export const SHIPPING_CURRENCY = "RSD";
/**
* Check if an order qualifies for free shipping
* @param orderTotal - The total amount of the order
* @returns boolean indicating if order qualifies for free shipping
*/
export function qualifiesForFreeShipping(orderTotal: number): boolean {
return orderTotal >= FREE_SHIPPING_THRESHOLD_RSD;
}
/**
* Calculate shipping cost based on order total
* @param orderTotal - The total amount of the order
* @returns Shipping cost (0 if qualifies for free shipping)
*/
export function calculateShippingCost(orderTotal: number): number {
return qualifiesForFreeShipping(orderTotal) ? 0 : DEFAULT_SHIPPING_COST_RSD;
}
/**
* Get the remaining amount needed for free shipping
* @param orderTotal - The current order total
* @returns Amount needed to reach free shipping threshold (0 if already qualified)
*/
export function getRemainingForFreeShipping(orderTotal: number): number {
if (qualifiesForFreeShipping(orderTotal)) {
return 0;
}
return FREE_SHIPPING_THRESHOLD_RSD - orderTotal;
}