price function global (needs review)

This commit is contained in:
elvira
2026-08-08 15:10:37 +03:00
parent e46276a31b
commit 7677d10821
8 changed files with 65 additions and 7 deletions
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace App\Support;
class Price
{
/**
* Format a price the Greek way: comma as the decimal separator, dot as
* the thousands separator, and no decimals shown unless the amount
* actually needs them (19.00 -> "19", 19.50 -> "19,5", 19.55 -> "19,55").
*/
public static function format(float|int|string|null $amount, string $currency = '€'): ?string
{
if ($amount === null) {
return null;
}
$amount = round((float) $amount, 2);
$cents = (int) round($amount * 100);
$decimals = match (true) {
$cents % 100 === 0 => 0,
$cents % 10 === 0 => 1,
default => 2,
};
return $currency.number_format($amount, $decimals, ',', '.');
}
}
@@ -1,4 +1,5 @@
import { Controller } from '@hotwired/stimulus' import { Controller } from '@hotwired/stimulus'
import { formatPrice } from '../utils/format-price'
export default class extends Controller { export default class extends Controller {
static targets = ['price', 'image', 'swatch', 'colorName'] static targets = ['price', 'image', 'swatch', 'colorName']
@@ -30,7 +31,7 @@ export default class extends Controller {
if (!variant) return if (!variant) return
if (this.hasPriceTarget && variant.price !== null) { if (this.hasPriceTarget && variant.price !== null) {
this.priceTarget.textContent = '€' + parseFloat(variant.price).toFixed(2) this.priceTarget.textContent = formatPrice(variant.price)
} }
if (this.hasImageTarget && variant.image) { if (this.hasImageTarget && variant.image) {
+19
View File
@@ -0,0 +1,19 @@
// Mirrors App\Support\Price::format() — keep both in sync. Greek formatting:
// comma as the decimal separator, dot as the thousands separator, and no
// decimals shown unless the amount actually needs them.
// 19.00 -> "€19", 19.50 -> "€19,5", 19.55 -> "€19,55"
export function formatPrice(amount, currency = '€') {
if (amount === null || amount === undefined) return null
const value = Math.round(parseFloat(amount) * 100) / 100
const cents = Math.round(value * 100)
const decimals = cents % 100 === 0 ? 0 : (cents % 10 === 0 ? 1 : 2)
const formatted = value.toLocaleString('el-GR', {
minimumFractionDigits: decimals,
maximumFractionDigits: decimals,
})
return currency + formatted
}
@@ -0,0 +1,9 @@
@props([
'amount' => null,
'currency' => '€',
])
{{-- No wrapper element on purpose — drop this inside whatever tag/classes
the call site already uses (span, p, etc). Formatting logic lives in
App\Support\Price so it's identical everywhere it's used. --}}
{{ \App\Support\Price::format($amount, $currency) }}
@@ -28,7 +28,7 @@ class="w-full h-auto block"
<div class="flex items-baseline justify-between gap-4"> <div class="flex items-baseline justify-between gap-4">
<a href="{{ $href }}" class="font-bold text-xl leading-snug hover:text-brand">{{ $name }}</a> <a href="{{ $href }}" class="font-bold text-xl leading-snug hover:text-brand">{{ $name }}</a>
@if($price !== null) @if($price !== null)
<span class="font-bold text-xl shrink-0">$ {{ $price }}</span> <span class="font-bold text-xl shrink-0"><x-ui.price :amount="$price" /></span>
@endif @endif
</div> </div>
</div> </div>
+3 -3
View File
@@ -33,7 +33,7 @@ class="font-display font-semibold text-6xl lg:text-7xl leading-tight"
aria-label="Προηγούμενο προϊόν" aria-label="Προηγούμενο προϊόν"
class="absolute left-4 lg:left-8 top-1/2 -translate-y-1/2 hover:opacity-60 transition-opacity" class="absolute left-4 lg:left-8 top-1/2 -translate-y-1/2 hover:opacity-60 transition-opacity"
> >
<x-ui.icon name="arrow-left" :size="56" /> <x-ui.icon name="arrow-left" :size="62" class="[&_path]:stroke-2" />
</button> </button>
<div class="w-full "> <div class="w-full ">
@@ -63,7 +63,7 @@ class="w-full h-full object-cover"
<div class="flex items-baseline justify-between gap-4"> <div class="flex items-baseline justify-between gap-4">
<span class="font-display font-bold text-2xl">{{ $product['name'] }}</span> <span class="font-display font-bold text-2xl">{{ $product['name'] }}</span>
@if($product['price'] !== null) @if($product['price'] !== null)
<span class="font-bold text-xl shrink-0">€{{ number_format($product['price'], 2) }}</span> <span class="font-bold text-xl shrink-0"><x-ui.price :amount="$product['price']" /></span>
@endif @endif
</div> </div>
</a> </a>
@@ -76,7 +76,7 @@ class="w-full h-full object-cover"
aria-label="Επόμενο προϊόν" aria-label="Επόμενο προϊόν"
class="absolute right-4 lg:right-8 top-1/2 -translate-y-1/2 hover:opacity-60 transition-opacity" class="absolute right-4 lg:right-8 top-1/2 -translate-y-1/2 hover:opacity-60 transition-opacity"
> >
<x-ui.icon name="arrow-right" :size="56" /> <x-ui.icon name="arrow-right" :size="62" class="[&_path]:stroke-2"/>
</button> </button>
@else @else
<p class="text-neutral-500">Δεν βρέθηκαν προϊόντα.</p> <p class="text-neutral-500">Δεν βρέθηκαν προϊόντα.</p>
+1 -1
View File
@@ -152,7 +152,7 @@ class="absolute bottom-6 right-8 text-white text-sm"
@if($product->variants->first()?->prices->isNotEmpty()) @if($product->variants->first()?->prices->isNotEmpty())
<p class="text-2xl font-bold" data-product-form-target="price"> <p class="text-2xl font-bold" data-product-form-target="price">
€{{ number_format($product->variants->first()->prices->first()->price->decimal, 2) }} <x-ui.price :amount="$product->variants->first()->prices->first()->price->decimal" />
</p> </p>
@endif @endif
+1 -1
View File
@@ -152,7 +152,7 @@ class="absolute bottom-6 right-8 text-white text-sm"
@if($product->variants->first()?->prices->isNotEmpty()) @if($product->variants->first()?->prices->isNotEmpty())
<p class="text-2xl font-bold" data-product-form-target="price"> <p class="text-2xl font-bold" data-product-form-target="price">
€{{ number_format($product->variants->first()->prices->first()->price->decimal, 2) }} <x-ui.price :amount="$product->variants->first()->prices->first()->price->decimal" />
</p> </p>
@endif @endif