almost all ready

This commit is contained in:
2026-05-14 01:48:15 +02:00
parent 1b53c1c199
commit 2e1bec0e8b
29 changed files with 5442 additions and 642 deletions
+157
View File
@@ -0,0 +1,157 @@
package templates
import (
"fmt"
"git.ma-al.com/goc_marek/ps_shop/internal/viewmodel"
)
templ CartPage(data viewmodel.CartPageData, cssPath string, jsPath string) {
@Layout("Cart", cssPath, jsPath, data.Menu, data.Locale, data.Cart.TotalItems) {
<main class="min-h-screen overflow-hidden bg-[radial-gradient(circle_at_top,_rgba(245,158,11,0.22),_transparent_34%),radial-gradient(circle_at_bottom_left,_rgba(120,53,15,0.22),_transparent_36%),linear-gradient(180deg,#0c0a09,#1c1917)]">
<div class="pointer-events-none absolute inset-x-0 top-0 h-72 bg-[linear-gradient(145deg,rgba(251,191,36,0.12),transparent_58%)]"></div>
<div class="mx-auto flex w-full max-w-[104rem] flex-col gap-10 px-6 py-10 lg:px-8">
<header class="relative overflow-hidden rounded-[2.2rem] border border-amber-500/20 bg-[linear-gradient(135deg,rgba(28,25,23,0.96),rgba(120,53,15,0.34))] p-8 shadow-[0_32px_120px_rgba(41,24,10,0.48)] lg:p-10">
<div class="absolute -right-14 top-[-3.5rem] h-44 w-44 rounded-full bg-amber-300/10 blur-3xl"></div>
<div class="absolute bottom-[-4rem] left-[-2rem] h-40 w-40 rounded-full bg-orange-500/10 blur-3xl"></div>
<div class="relative flex flex-col gap-8 lg:flex-row lg:items-end lg:justify-between">
<div class="max-w-2xl">
<p class="text-xs uppercase tracking-[0.38em] text-amber-300/80">Cart overview</p>
<h1 class="mt-4 font-serif text-4xl text-stone-50 lg:text-5xl">Everything ready for checkout.</h1>
<p class="mt-4 max-w-xl text-sm leading-7 text-stone-300 lg:text-base">Review quantities, adjust variants, and keep the final order state visible before handing off checkout to PrestaShop.</p>
</div>
<div class="grid gap-3 sm:grid-cols-2 lg:min-w-[20rem]">
<div class="rounded-[1.4rem] border border-white/10 bg-white/5 px-5 py-4 backdrop-blur">
<p class="text-[0.68rem] uppercase tracking-[0.28em] text-stone-500">Line items</p>
<p class="mt-2 text-3xl font-semibold text-stone-50">{ fmt.Sprintf("%d", len(data.Cart.Items)) }</p>
</div>
<div class="rounded-[1.4rem] border border-amber-400/20 bg-amber-300/10 px-5 py-4 backdrop-blur">
<p class="text-[0.68rem] uppercase tracking-[0.28em] text-amber-100/80">Units total</p>
<p class="mt-2 text-3xl font-semibold text-stone-50">{ fmt.Sprintf("%d", data.Cart.TotalItems) }</p>
</div>
</div>
</div>
<div class="relative mt-8 flex flex-wrap items-center gap-3 text-sm text-stone-300">
if data.Customer != nil {
<span class="rounded-full border border-white/10 bg-black/15 px-4 py-2">{ fmt.Sprintf("%s %s", data.Customer.FirstName, data.Customer.LastName) }</span>
} else {
<span class="rounded-full border border-white/10 bg-black/15 px-4 py-2">Guest session</span>
}
<span class="rounded-full border border-white/10 bg-black/15 px-4 py-2">{ "Subtotal " + moneyWithCurrency(data.Cart.SubtotalTaxIncl, cartCurrencySign(data), cartCurrencyCode(data)) }</span>
</div>
</header>
<section class="grid gap-8 xl:grid-cols-[minmax(0,1.55fr)_24rem]">
<div class="rounded-[2rem] border border-stone-800 bg-stone-900/72 p-5 shadow-[0_24px_80px_rgba(0,0,0,0.35)] lg:p-6">
if len(data.Cart.Items) == 0 {
<div class="rounded-[1.75rem] border border-dashed border-amber-400/30 bg-[linear-gradient(180deg,rgba(245,158,11,0.08),rgba(28,25,23,0.32))] p-12 text-center">
<p class="text-sm uppercase tracking-[0.28em] text-amber-300">Empty</p>
<p class="mt-4 text-3xl font-semibold text-stone-50">Your cart is empty.</p>
<p class="mx-auto mt-3 max-w-md text-sm leading-7 text-stone-300">Browse categories or return to a product page to add items and build a full order before checkout.</p>
</div>
} else {
<div class="mb-4 hidden grid-cols-[minmax(0,1fr)_11rem] gap-4 px-4 text-[0.68rem] uppercase tracking-[0.28em] text-stone-500 md:grid">
<div>Product</div>
<div class="text-right">Summary</div>
</div>
<div class="space-y-4">
for _, item := range data.Cart.Items {
<article class="group overflow-hidden rounded-[1.7rem] border border-stone-800 bg-[linear-gradient(180deg,rgba(41,37,36,0.88),rgba(28,25,23,0.96))] p-5 shadow-[inset_0_1px_0_rgba(255,255,255,0.04)] transition duration-300 hover:border-amber-400/30 hover:bg-[linear-gradient(180deg,rgba(120,53,15,0.22),rgba(28,25,23,0.98))] md:p-6">
<div class="flex flex-col gap-5 md:flex-row md:items-start md:justify-between">
if item.ImageURL != "" {
<div class="overflow-hidden rounded-[1.35rem] border border-stone-800 bg-stone-950/70 shadow-[0_16px_40px_rgba(0,0,0,0.24)] md:w-32 md:shrink-0">
<img class="block h-32 w-full object-cover" src={ item.ImageURL } alt={ item.Name }/>
</div>
}
<div class="min-w-0 flex-1">
<div class="flex flex-wrap items-center gap-2">
<p class="text-[0.68rem] uppercase tracking-[0.3em] text-amber-300">Product</p>
if cartItemAttributeLabel(item) != "" {
<span class="rounded-full border border-amber-300/20 bg-amber-300/10 px-2.5 py-1 text-[0.65rem] uppercase tracking-[0.16em] text-amber-100/80">{ cartItemAttributeLabel(item) }</span>
}
</div>
if item.URL != "" {
<a class="mt-3 block truncate text-2xl font-semibold text-stone-50 underline-offset-4 transition group-hover:text-amber-100 hover:underline" href={ item.URL }>{ item.Name }</a>
} else {
<h2 class="mt-3 text-2xl font-semibold text-stone-50">{ item.Name }</h2>
}
<div class="mt-4 flex flex-wrap gap-2 text-xs text-stone-400">
<span class="rounded-full border border-white/10 bg-black/20 px-3 py-1.5">{ fmt.Sprintf("Qty %d", item.Quantity) }</span>
<span class="rounded-full border border-white/10 bg-black/20 px-3 py-1.5">{ "Net " + moneyWithCurrency(item.UnitPrice, item.CurrencySign, item.CurrencyCode) }</span>
<span class="rounded-full border border-white/10 bg-black/20 px-3 py-1.5">{ taxLabel(item.TaxRate) }</span>
</div>
if cartItemAttributeLabel(item) != "" {
<p class="mt-3 text-sm text-stone-300">{ cartItemAttributeLabel(item) }</p>
}
</div>
<div class="rounded-[1.25rem] border border-stone-800 bg-stone-950/70 px-4 py-4 text-left md:min-w-[11rem] md:text-right">
<p class="text-[0.68rem] uppercase tracking-[0.24em] text-stone-500">Line total</p>
<p class="mt-2 text-2xl font-semibold text-stone-50">{ moneyWithCurrency(item.LineTotalTaxIncl, item.CurrencySign, item.CurrencyCode) }</p>
<p class="mt-1 text-xs uppercase tracking-[0.2em] text-stone-500">{ conversionRateLabel(item.ConversionRate, item.CurrencyCode) }</p>
</div>
</div>
<div class="mt-6 flex flex-col gap-3 border-t border-stone-800 pt-5 md:flex-row md:items-center md:justify-between">
<form class="flex flex-wrap items-center gap-3" method="post" action={ localizedCartPath(data.Locale) }>
<input type="hidden" name="action" value="update"/>
<input type="hidden" name="id_product" value={ fmt.Sprintf("%d", item.ProductID) }/>
<input type="hidden" name="id_product_attribute" value={ fmt.Sprintf("%d", item.ProductAttributeID) }/>
<input type="hidden" name="id_customization" value={ fmt.Sprintf("%d", item.CustomizationID) }/>
<label class="text-[0.75rem] uppercase tracking-[0.24em] text-stone-400" for={ fmt.Sprintf("qty-%d-%d", item.ProductID, item.ProductAttributeID) }>Quantity</label>
<div class="flex items-center overflow-hidden rounded-full border border-stone-700 bg-stone-950">
<input class="w-20 bg-transparent px-4 py-2.5 text-center text-sm text-stone-100 outline-none ring-0" id={ fmt.Sprintf("qty-%d-%d", item.ProductID, item.ProductAttributeID) } type="number" min="1" name="qty" value={ fmt.Sprintf("%d", item.Quantity) }/>
</div>
<button class="rounded-full border border-amber-300/40 bg-amber-300/10 px-4 py-2.5 text-xs font-semibold uppercase tracking-[0.22em] text-amber-100 transition hover:bg-amber-300 hover:text-stone-950" type="submit">
Update line
</button>
</form>
<form method="post" action={ localizedCartPath(data.Locale) }>
<input type="hidden" name="action" value="delete"/>
<input type="hidden" name="id_product" value={ fmt.Sprintf("%d", item.ProductID) }/>
<input type="hidden" name="id_product_attribute" value={ fmt.Sprintf("%d", item.ProductAttributeID) }/>
<input type="hidden" name="id_customization" value={ fmt.Sprintf("%d", item.CustomizationID) }/>
<button class="rounded-full border border-stone-700 bg-stone-950 px-4 py-2.5 text-xs font-semibold uppercase tracking-[0.22em] text-stone-200 transition hover:border-rose-300/30 hover:bg-rose-300 hover:text-stone-950" type="submit">
Remove
</button>
</form>
</div>
</article>
}
</div>
}
</div>
<aside class="xl:sticky xl:top-8 xl:self-start">
<div class="overflow-hidden rounded-[2rem] border border-amber-500/20 bg-[linear-gradient(180deg,rgba(245,158,11,0.12),rgba(28,25,23,0.92)_34%,rgba(28,25,23,0.98))] p-8 shadow-[0_24px_80px_rgba(41,24,10,0.4)]">
<p class="text-xs uppercase tracking-[0.3em] text-amber-200">Summary</p>
<p class="mt-3 text-sm leading-7 text-stone-300">A compact snapshot of the current order before checkout moves to the native Presta flow.</p>
<div class="mt-8 space-y-3">
<div class="flex items-center justify-between rounded-2xl border border-white/10 bg-black/15 px-4 py-3 text-sm text-stone-200">
<span>Total items</span>
<span class="font-semibold text-stone-50">{ fmt.Sprintf("%d", data.Cart.TotalItems) }</span>
</div>
<div class="flex items-center justify-between rounded-2xl border border-white/10 bg-black/15 px-4 py-3 text-sm text-stone-200">
<span>Unique lines</span>
<span class="font-semibold text-stone-50">{ fmt.Sprintf("%d", len(data.Cart.Items)) }</span>
</div>
<div class="flex items-center justify-between rounded-2xl border border-white/10 bg-black/15 px-4 py-3 text-sm text-stone-200">
<span>Subtotal net</span>
<span class="font-semibold text-stone-50">{ moneyWithCurrency(data.Cart.Subtotal, cartCurrencySign(data), cartCurrencyCode(data)) }</span>
</div>
<div class="flex items-center justify-between rounded-[1.4rem] border border-amber-300/20 bg-amber-300/10 px-5 py-4 text-lg font-semibold text-stone-50">
<span>Subtotal gross</span>
<span>{ moneyWithCurrency(data.Cart.SubtotalTaxIncl, cartCurrencySign(data), cartCurrencyCode(data)) }</span>
</div>
</div>
<a class="mt-8 inline-flex w-full items-center justify-center rounded-full bg-amber-300 px-5 py-3.5 text-sm font-semibold uppercase tracking-[0.22em] text-stone-950 transition hover:bg-amber-200" href={ data.ShopBaseURL + "/order" }>
Checkout in PrestaShop
</a>
<a class="mt-3 inline-flex w-full items-center justify-center rounded-full border border-white/10 px-5 py-3 text-xs font-semibold uppercase tracking-[0.24em] text-stone-200 transition hover:border-amber-300/40 hover:text-stone-50" href="/">
Continue browsing
</a>
</div>
</aside>
</section>
</div>
</main>
}
}
+565
View File
@@ -0,0 +1,565 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.1001
package templates
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import (
"fmt"
"git.ma-al.com/goc_marek/ps_shop/internal/viewmodel"
)
func CartPage(data viewmodel.CartPageData, cssPath string, jsPath string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<main class=\"min-h-screen overflow-hidden bg-[radial-gradient(circle_at_top,_rgba(245,158,11,0.22),_transparent_34%),radial-gradient(circle_at_bottom_left,_rgba(120,53,15,0.22),_transparent_36%),linear-gradient(180deg,#0c0a09,#1c1917)]\"><div class=\"pointer-events-none absolute inset-x-0 top-0 h-72 bg-[linear-gradient(145deg,rgba(251,191,36,0.12),transparent_58%)]\"></div><div class=\"mx-auto flex w-full max-w-[104rem] flex-col gap-10 px-6 py-10 lg:px-8\"><header class=\"relative overflow-hidden rounded-[2.2rem] border border-amber-500/20 bg-[linear-gradient(135deg,rgba(28,25,23,0.96),rgba(120,53,15,0.34))] p-8 shadow-[0_32px_120px_rgba(41,24,10,0.48)] lg:p-10\"><div class=\"absolute -right-14 top-[-3.5rem] h-44 w-44 rounded-full bg-amber-300/10 blur-3xl\"></div><div class=\"absolute bottom-[-4rem] left-[-2rem] h-40 w-40 rounded-full bg-orange-500/10 blur-3xl\"></div><div class=\"relative flex flex-col gap-8 lg:flex-row lg:items-end lg:justify-between\"><div class=\"max-w-2xl\"><p class=\"text-xs uppercase tracking-[0.38em] text-amber-300/80\">Cart overview</p><h1 class=\"mt-4 font-serif text-4xl text-stone-50 lg:text-5xl\">Everything ready for checkout.</h1><p class=\"mt-4 max-w-xl text-sm leading-7 text-stone-300 lg:text-base\">Review quantities, adjust variants, and keep the final order state visible before handing off checkout to PrestaShop.</p></div><div class=\"grid gap-3 sm:grid-cols-2 lg:min-w-[20rem]\"><div class=\"rounded-[1.4rem] border border-white/10 bg-white/5 px-5 py-4 backdrop-blur\"><p class=\"text-[0.68rem] uppercase tracking-[0.28em] text-stone-500\">Line items</p><p class=\"mt-2 text-3xl font-semibold text-stone-50\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", len(data.Cart.Items)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/cart.templ`, Line: 26, Col: 102}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</p></div><div class=\"rounded-[1.4rem] border border-amber-400/20 bg-amber-300/10 px-5 py-4 backdrop-blur\"><p class=\"text-[0.68rem] uppercase tracking-[0.28em] text-amber-100/80\">Units total</p><p class=\"mt-2 text-3xl font-semibold text-stone-50\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.Cart.TotalItems))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/cart.templ`, Line: 30, Col: 102}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</p></div></div></div><div class=\"relative mt-8 flex flex-wrap items-center gap-3 text-sm text-stone-300\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Customer != nil {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<span class=\"rounded-full border border-white/10 bg-black/15 px-4 py-2\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s %s", data.Customer.FirstName, data.Customer.LastName))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/cart.templ`, Line: 36, Col: 150}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</span> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<span class=\"rounded-full border border-white/10 bg-black/15 px-4 py-2\">Guest session</span> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<span class=\"rounded-full border border-white/10 bg-black/15 px-4 py-2\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs("Subtotal " + moneyWithCurrency(data.Cart.SubtotalTaxIncl, cartCurrencySign(data), cartCurrencyCode(data)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/cart.templ`, Line: 40, Col: 186}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</span></div></header><section class=\"grid gap-8 xl:grid-cols-[minmax(0,1.55fr)_24rem]\"><div class=\"rounded-[2rem] border border-stone-800 bg-stone-900/72 p-5 shadow-[0_24px_80px_rgba(0,0,0,0.35)] lg:p-6\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if len(data.Cart.Items) == 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<div class=\"rounded-[1.75rem] border border-dashed border-amber-400/30 bg-[linear-gradient(180deg,rgba(245,158,11,0.08),rgba(28,25,23,0.32))] p-12 text-center\"><p class=\"text-sm uppercase tracking-[0.28em] text-amber-300\">Empty</p><p class=\"mt-4 text-3xl font-semibold text-stone-50\">Your cart is empty.</p><p class=\"mx-auto mt-3 max-w-md text-sm leading-7 text-stone-300\">Browse categories or return to a product page to add items and build a full order before checkout.</p></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<div class=\"mb-4 hidden grid-cols-[minmax(0,1fr)_11rem] gap-4 px-4 text-[0.68rem] uppercase tracking-[0.28em] text-stone-500 md:grid\"><div>Product</div><div class=\"text-right\">Summary</div></div><div class=\"space-y-4\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, item := range data.Cart.Items {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<article class=\"group overflow-hidden rounded-[1.7rem] border border-stone-800 bg-[linear-gradient(180deg,rgba(41,37,36,0.88),rgba(28,25,23,0.96))] p-5 shadow-[inset_0_1px_0_rgba(255,255,255,0.04)] transition duration-300 hover:border-amber-400/30 hover:bg-[linear-gradient(180deg,rgba(120,53,15,0.22),rgba(28,25,23,0.98))] md:p-6\"><div class=\"flex flex-col gap-5 md:flex-row md:items-start md:justify-between\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if item.ImageURL != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<div class=\"overflow-hidden rounded-[1.35rem] border border-stone-800 bg-stone-950/70 shadow-[0_16px_40px_rgba(0,0,0,0.24)] md:w-32 md:shrink-0\"><img class=\"block h-32 w-full object-cover\" src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(item.ImageURL)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/cart.templ`, Line: 63, Col: 76}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\" alt=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(item.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/cart.templ`, Line: 63, Col: 94}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\"></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<div class=\"min-w-0 flex-1\"><div class=\"flex flex-wrap items-center gap-2\"><p class=\"text-[0.68rem] uppercase tracking-[0.3em] text-amber-300\">Product</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if cartItemAttributeLabel(item) != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<span class=\"rounded-full border border-amber-300/20 bg-amber-300/10 px-2.5 py-1 text-[0.65rem] uppercase tracking-[0.16em] text-amber-100/80\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(cartItemAttributeLabel(item))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/cart.templ`, Line: 70, Col: 187}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if item.URL != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<a class=\"mt-3 block truncate text-2xl font-semibold text-stone-50 underline-offset-4 transition group-hover:text-amber-100 hover:underline\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 templ.SafeURL
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinURLErrs(item.URL)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/cart.templ`, Line: 74, Col: 169}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(item.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/cart.templ`, Line: 74, Col: 183}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<h2 class=\"mt-3 text-2xl font-semibold text-stone-50\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(item.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/cart.templ`, Line: 76, Col: 78}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "</h2>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "<div class=\"mt-4 flex flex-wrap gap-2 text-xs text-stone-400\"><span class=\"rounded-full border border-white/10 bg-black/20 px-3 py-1.5\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("Qty %d", item.Quantity))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/cart.templ`, Line: 79, Col: 125}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</span> <span class=\"rounded-full border border-white/10 bg-black/20 px-3 py-1.5\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs("Net " + moneyWithCurrency(item.UnitPrice, item.CurrencySign, item.CurrencyCode))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/cart.templ`, Line: 80, Col: 169}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</span> <span class=\"rounded-full border border-white/10 bg-black/20 px-3 py-1.5\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(taxLabel(item.TaxRate))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/cart.templ`, Line: 81, Col: 111}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "</span></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if cartItemAttributeLabel(item) != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<p class=\"mt-3 text-sm text-stone-300\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(cartItemAttributeLabel(item))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/cart.templ`, Line: 84, Col: 82}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "</div><div class=\"rounded-[1.25rem] border border-stone-800 bg-stone-950/70 px-4 py-4 text-left md:min-w-[11rem] md:text-right\"><p class=\"text-[0.68rem] uppercase tracking-[0.24em] text-stone-500\">Line total</p><p class=\"mt-2 text-2xl font-semibold text-stone-50\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(moneyWithCurrency(item.LineTotalTaxIncl, item.CurrencySign, item.CurrencyCode))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/cart.templ`, Line: 89, Col: 145}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "</p><p class=\"mt-1 text-xs uppercase tracking-[0.2em] text-stone-500\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(conversionRateLabel(item.ConversionRate, item.CurrencyCode))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/cart.templ`, Line: 90, Col: 139}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "</p></div></div><div class=\"mt-6 flex flex-col gap-3 border-t border-stone-800 pt-5 md:flex-row md:items-center md:justify-between\"><form class=\"flex flex-wrap items-center gap-3\" method=\"post\" action=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var19 templ.SafeURL
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinURLErrs(localizedCartPath(data.Locale))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/cart.templ`, Line: 94, Col: 112}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "\"><input type=\"hidden\" name=\"action\" value=\"update\"> <input type=\"hidden\" name=\"id_product\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var20 string
templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", item.ProductID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/cart.templ`, Line: 96, Col: 92}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "\"> <input type=\"hidden\" name=\"id_product_attribute\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", item.ProductAttributeID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/cart.templ`, Line: 97, Col: 111}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "\"> <input type=\"hidden\" name=\"id_customization\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var22 string
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", item.CustomizationID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/cart.templ`, Line: 98, Col: 104}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "\"> <label class=\"text-[0.75rem] uppercase tracking-[0.24em] text-stone-400\" for=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var23 string
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("qty-%d-%d", item.ProductID, item.ProductAttributeID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/cart.templ`, Line: 99, Col: 156}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "\">Quantity</label><div class=\"flex items-center overflow-hidden rounded-full border border-stone-700 bg-stone-950\"><input class=\"w-20 bg-transparent px-4 py-2.5 text-center text-sm text-stone-100 outline-none ring-0\" id=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var24 string
templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("qty-%d-%d", item.ProductID, item.ProductAttributeID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/cart.templ`, Line: 101, Col: 185}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "\" type=\"number\" min=\"1\" name=\"qty\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var25 string
templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", item.Quantity))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/cart.templ`, Line: 101, Col: 261}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "\"></div><button class=\"rounded-full border border-amber-300/40 bg-amber-300/10 px-4 py-2.5 text-xs font-semibold uppercase tracking-[0.22em] text-amber-100 transition hover:bg-amber-300 hover:text-stone-950\" type=\"submit\">Update line</button></form><form method=\"post\" action=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var26 templ.SafeURL
templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinURLErrs(localizedCartPath(data.Locale))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/cart.templ`, Line: 107, Col: 70}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "\"><input type=\"hidden\" name=\"action\" value=\"delete\"> <input type=\"hidden\" name=\"id_product\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var27 string
templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", item.ProductID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/cart.templ`, Line: 109, Col: 92}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "\"> <input type=\"hidden\" name=\"id_product_attribute\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var28 string
templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", item.ProductAttributeID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/cart.templ`, Line: 110, Col: 111}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "\"> <input type=\"hidden\" name=\"id_customization\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var29 string
templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", item.CustomizationID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/cart.templ`, Line: 111, Col: 104}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "\"> <button class=\"rounded-full border border-stone-700 bg-stone-950 px-4 py-2.5 text-xs font-semibold uppercase tracking-[0.22em] text-stone-200 transition hover:border-rose-300/30 hover:bg-rose-300 hover:text-stone-950\" type=\"submit\">Remove</button></form></div></article>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "</div><aside class=\"xl:sticky xl:top-8 xl:self-start\"><div class=\"overflow-hidden rounded-[2rem] border border-amber-500/20 bg-[linear-gradient(180deg,rgba(245,158,11,0.12),rgba(28,25,23,0.92)_34%,rgba(28,25,23,0.98))] p-8 shadow-[0_24px_80px_rgba(41,24,10,0.4)]\"><p class=\"text-xs uppercase tracking-[0.3em] text-amber-200\">Summary</p><p class=\"mt-3 text-sm leading-7 text-stone-300\">A compact snapshot of the current order before checkout moves to the native Presta flow.</p><div class=\"mt-8 space-y-3\"><div class=\"flex items-center justify-between rounded-2xl border border-white/10 bg-black/15 px-4 py-3 text-sm text-stone-200\"><span>Total items</span> <span class=\"font-semibold text-stone-50\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var30 string
templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.Cart.TotalItems))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/cart.templ`, Line: 130, Col: 92}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "</span></div><div class=\"flex items-center justify-between rounded-2xl border border-white/10 bg-black/15 px-4 py-3 text-sm text-stone-200\"><span>Unique lines</span> <span class=\"font-semibold text-stone-50\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var31 string
templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", len(data.Cart.Items)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/cart.templ`, Line: 134, Col: 92}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "</span></div><div class=\"flex items-center justify-between rounded-2xl border border-white/10 bg-black/15 px-4 py-3 text-sm text-stone-200\"><span>Subtotal net</span> <span class=\"font-semibold text-stone-50\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var32 string
templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(moneyWithCurrency(data.Cart.Subtotal, cartCurrencySign(data), cartCurrencyCode(data)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/cart.templ`, Line: 138, Col: 138}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "</span></div><div class=\"flex items-center justify-between rounded-[1.4rem] border border-amber-300/20 bg-amber-300/10 px-5 py-4 text-lg font-semibold text-stone-50\"><span>Subtotal gross</span> <span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var33 string
templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(moneyWithCurrency(data.Cart.SubtotalTaxIncl, cartCurrencySign(data), cartCurrencyCode(data)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/cart.templ`, Line: 142, Col: 109}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "</span></div></div><a class=\"mt-8 inline-flex w-full items-center justify-center rounded-full bg-amber-300 px-5 py-3.5 text-sm font-semibold uppercase tracking-[0.22em] text-stone-950 transition hover:bg-amber-200\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var34 templ.SafeURL
templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinURLErrs(data.ShopBaseURL + "/order")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/cart.templ`, Line: 145, Col: 237}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "\">Checkout in PrestaShop</a> <a class=\"mt-3 inline-flex w-full items-center justify-center rounded-full border border-white/10 px-5 py-3 text-xs font-semibold uppercase tracking-[0.24em] text-stone-200 transition hover:border-amber-300/40 hover:text-stone-50\" href=\"/\">Continue browsing</a></div></aside></section></div></main>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = Layout("Cart", cssPath, jsPath, data.Menu, data.Locale, data.Cart.TotalItems).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate
+28 -25
View File
@@ -7,14 +7,14 @@ import (
)
templ CategoryPage(data viewmodel.CategoryPageData, cssPath string, jsPath string) {
@Layout(data.Category.Name, cssPath, jsPath, data.Menu, data.Locale) {
@Layout(data.Category.Name, cssPath, jsPath, data.Menu, data.Locale, layoutCartItems(data.CartSummary)) {
<main class="min-h-screen bg-[radial-gradient(circle_at_top_left,_rgba(34,197,94,0.18),_transparent_35%),linear-gradient(180deg,#0b1020,#111827)]">
<div class="mx-auto flex max-w-7xl flex-col gap-10 px-6 py-10 lg:px-8">
<div class="mx-auto flex w-full max-w-[104rem] flex-col gap-10 px-6 py-10 lg:px-8">
<header class="rounded-[2rem] border border-emerald-500/20 bg-white/5 p-8 backdrop-blur">
<p class="text-xs uppercase tracking-[0.32em] text-emerald-300">Category</p>
<h1 class="mt-4 font-serif text-4xl text-white">{ data.Category.Name }</h1>
<div class="mt-4 flex items-center justify-between gap-6 text-sm text-slate-300">
<p>{ fmt.Sprintf("Products loaded: %d", len(data.Category.Products)) }</p>
<p>{ fmt.Sprintf("Products %d-%d of %d", categoryPageStart(data.Pagination), categoryPageEnd(data.Pagination, len(data.Category.Products)), data.Pagination.TotalItems) }</p>
if data.Customer != nil {
<p>{ fmt.Sprintf("%s %s", data.Customer.FirstName, data.Customer.LastName) }</p>
} else {
@@ -28,14 +28,22 @@ templ CategoryPage(data viewmodel.CategoryPageData, cssPath string, jsPath strin
}
</header>
<section class="grid gap-5 md:grid-cols-2 xl:grid-cols-3">
<section class="grid gap-5 md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4">
for _, product := range data.Category.Products {
<article class="group rounded-[1.75rem] border border-white/10 bg-slate-900/70 p-6 shadow-[0_24px_80px_rgba(0,0,0,0.35)] transition hover:-translate-y-1 hover:border-emerald-400/40">
if product.ImageURL != "" {
<a class="mb-5 block overflow-hidden rounded-[1.25rem] border border-white/10 bg-slate-950/70 shadow-[0_16px_40px_rgba(0,0,0,0.24)]" href={ product.URL }>
<img class="block h-64 w-full object-cover transition duration-500 group-hover:scale-[1.03]" src={ product.ImageURL } alt={ product.Name }/>
</a>
}
<p class="text-xs uppercase tracking-[0.28em] text-emerald-300">Product</p>
<h2 class="mt-3 text-2xl font-semibold text-white">{ product.Name }</h2>
<p class="mt-4 text-sm leading-7 text-slate-300">{ product.Description }</p>
<p class="mt-4 text-sm leading-7 text-slate-300">{ truncatedPlainTextHTML(product.ShortDescription, 220) }</p>
<div class="mt-8 flex items-center justify-between gap-4">
<p class="text-2xl font-semibold text-white">{ fmt.Sprintf("%.2f", product.Price) }</p>
<div>
<p class="text-2xl font-semibold text-white">{ moneyWithCurrency(product.PriceTaxIncl, product.CurrencySign, product.CurrencyCode) }</p>
<p class="mt-1 text-xs uppercase tracking-[0.2em] text-slate-400">{ taxLabel(product.TaxRate) } · { conversionRateLabel(product.ConversionRate, product.CurrencyCode) }</p>
</div>
<a class="rounded-full border border-emerald-400/40 px-4 py-2 text-xs font-semibold uppercase tracking-[0.22em] text-emerald-200 transition hover:bg-emerald-300 hover:text-slate-950" href={ product.URL }>
View Product
</a>
@@ -43,26 +51,21 @@ templ CategoryPage(data viewmodel.CategoryPageData, cssPath string, jsPath strin
</article>
}
</section>
if data.Session != nil {
<section class="rounded-[2rem] border border-white/10 bg-slate-950/70 p-8">
<p class="text-xs uppercase tracking-[0.28em] text-emerald-300">Go Cookie Debug</p>
<div class="mt-6 grid gap-6 lg:grid-cols-2">
<div>
<p class="text-sm font-semibold text-white">Raw Cookie</p>
<pre class="mt-3 overflow-x-auto rounded-2xl bg-black/30 p-4 text-xs leading-6 text-slate-300">{ data.Session.RawCookie }</pre>
</div>
<div>
<p class="text-sm font-semibold text-white">Decoded Values</p>
<pre class="mt-3 overflow-x-auto rounded-2xl bg-black/30 p-4 text-xs leading-6 text-slate-300">
for _, line := range sessionCookieLines(data.Session) {
{ line }
{"\n"}
}
</pre>
</div>
if data.Pagination.TotalPages > 1 {
<nav class="flex flex-col items-center justify-between gap-4 rounded-[1.75rem] border border-white/10 bg-slate-950/50 px-6 py-5 text-sm text-slate-300 md:flex-row">
<div>
<p class="text-xs uppercase tracking-[0.24em] text-emerald-300">Page</p>
<p class="mt-1">{ fmt.Sprintf("%d of %d", data.Pagination.Page, data.Pagination.TotalPages) }</p>
</div>
</section>
<div class="flex items-center gap-3">
if data.Pagination.PrevURL != "" {
<a class="rounded-full border border-white/10 px-4 py-2 font-semibold uppercase tracking-[0.2em] text-slate-200 transition hover:border-emerald-400/40 hover:text-white" href={ data.Pagination.PrevURL }>Previous</a>
}
if data.Pagination.NextURL != "" {
<a class="rounded-full border border-emerald-400/40 px-4 py-2 font-semibold uppercase tracking-[0.2em] text-emerald-200 transition hover:bg-emerald-300 hover:text-slate-950" href={ data.Pagination.NextURL }>Next</a>
}
</div>
</nav>
}
</div>
</main>
+172 -83
View File
@@ -47,7 +47,7 @@ func CategoryPage(data viewmodel.CategoryPageData, cssPath string, jsPath string
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<main class=\"min-h-screen bg-[radial-gradient(circle_at_top_left,_rgba(34,197,94,0.18),_transparent_35%),linear-gradient(180deg,#0b1020,#111827)]\"><div class=\"mx-auto flex max-w-7xl flex-col gap-10 px-6 py-10 lg:px-8\"><header class=\"rounded-[2rem] border border-emerald-500/20 bg-white/5 p-8 backdrop-blur\"><p class=\"text-xs uppercase tracking-[0.32em] text-emerald-300\">Category</p><h1 class=\"mt-4 font-serif text-4xl text-white\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<main class=\"min-h-screen bg-[radial-gradient(circle_at_top_left,_rgba(34,197,94,0.18),_transparent_35%),linear-gradient(180deg,#0b1020,#111827)]\"><div class=\"mx-auto flex w-full max-w-[104rem] flex-col gap-10 px-6 py-10 lg:px-8\"><header class=\"rounded-[2rem] border border-emerald-500/20 bg-white/5 p-8 backdrop-blur\"><p class=\"text-xs uppercase tracking-[0.32em] text-emerald-300\">Category</p><h1 class=\"mt-4 font-serif text-4xl text-white\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -65,9 +65,9 @@ func CategoryPage(data viewmodel.CategoryPageData, cssPath string, jsPath string
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("Products loaded: %d", len(data.Category.Products)))
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("Products %d-%d of %d", categoryPageStart(data.Pagination), categoryPageEnd(data.Pagination, len(data.Category.Products)), data.Pagination.TotalItems))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/category.templ`, Line: 17, Col: 74}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/category.templ`, Line: 17, Col: 173}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
@@ -119,126 +119,215 @@ func CategoryPage(data viewmodel.CategoryPageData, cssPath string, jsPath string
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</header><section class=\"grid gap-5 md:grid-cols-2 xl:grid-cols-3\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</header><section class=\"grid gap-5 md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, product := range data.Category.Products {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<article class=\"group rounded-[1.75rem] border border-white/10 bg-slate-900/70 p-6 shadow-[0_24px_80px_rgba(0,0,0,0.35)] transition hover:-translate-y-1 hover:border-emerald-400/40\"><p class=\"text-xs uppercase tracking-[0.28em] text-emerald-300\">Product</p><h2 class=\"mt-3 text-2xl font-semibold text-white\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<article class=\"group rounded-[1.75rem] border border-white/10 bg-slate-900/70 p-6 shadow-[0_24px_80px_rgba(0,0,0,0.35)] transition hover:-translate-y-1 hover:border-emerald-400/40\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(product.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/category.templ`, Line: 35, Col: 72}
if product.ImageURL != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<a class=\"mb-5 block overflow-hidden rounded-[1.25rem] border border-white/10 bg-slate-950/70 shadow-[0_16px_40px_rgba(0,0,0,0.24)]\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 templ.SafeURL
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinURLErrs(product.URL)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/category.templ`, Line: 35, Col: 159}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\"><img class=\"block h-64 w-full object-cover transition duration-500 group-hover:scale-[1.03]\" src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(product.ImageURL)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/category.templ`, Line: 36, Col: 124}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\" alt=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(product.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/category.templ`, Line: 36, Col: 145}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "\"></a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<p class=\"text-xs uppercase tracking-[0.28em] text-emerald-300\">Product</p><h2 class=\"mt-3 text-2xl font-semibold text-white\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</h2><p class=\"mt-4 text-sm leading-7 text-slate-300\">")
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(product.Name)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(product.Description)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/category.templ`, Line: 36, Col: 77}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</p><div class=\"mt-8 flex items-center justify-between gap-4\"><p class=\"text-2xl font-semibold text-white\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.2f", product.Price))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/category.templ`, Line: 38, Col: 89}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</p><a class=\"rounded-full border border-emerald-400/40 px-4 py-2 text-xs font-semibold uppercase tracking-[0.22em] text-emerald-200 transition hover:bg-emerald-300 hover:text-slate-950\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 templ.SafeURL
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinURLErrs(product.URL)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/category.templ`, Line: 39, Col: 209}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/category.templ`, Line: 40, Col: 72}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "\">View Product</a></div></article>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</section>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Session != nil {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<section class=\"rounded-[2rem] border border-white/10 bg-slate-950/70 p-8\"><p class=\"text-xs uppercase tracking-[0.28em] text-emerald-300\">Go Cookie Debug</p><div class=\"mt-6 grid gap-6 lg:grid-cols-2\"><div><p class=\"text-sm font-semibold text-white\">Raw Cookie</p><pre class=\"mt-3 overflow-x-auto rounded-2xl bg-black/30 p-4 text-xs leading-6 text-slate-300\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</h2><p class=\"mt-4 text-sm leading-7 text-slate-300\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(data.Session.RawCookie)
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(truncatedPlainTextHTML(product.ShortDescription, 220))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/category.templ`, Line: 53, Col: 127}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/category.templ`, Line: 41, Col: 111}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</pre></div><div><p class=\"text-sm font-semibold text-white\">Decoded Values</p><pre class=\"mt-3 overflow-x-auto rounded-2xl bg-black/30 p-4 text-xs leading-6 text-slate-300\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</p><div class=\"mt-8 flex items-center justify-between gap-4\"><div><p class=\"text-2xl font-semibold text-white\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, line := range sessionCookieLines(data.Session) {
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(line)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/category.templ`, Line: 59, Col: 16}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs("\n")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/category.templ`, Line: 60, Col: 15}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(moneyWithCurrency(product.PriceTaxIncl, product.CurrencySign, product.CurrencyCode))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/category.templ`, Line: 44, Col: 139}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "</pre></div></div></section>")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</p><p class=\"mt-1 text-xs uppercase tracking-[0.2em] text-slate-400\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(taxLabel(product.TaxRate))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/category.templ`, Line: 45, Col: 102}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, " · ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(conversionRateLabel(product.ConversionRate, product.CurrencyCode))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/category.templ`, Line: 45, Col: 175}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</p></div><a class=\"rounded-full border border-emerald-400/40 px-4 py-2 text-xs font-semibold uppercase tracking-[0.22em] text-emerald-200 transition hover:bg-emerald-300 hover:text-slate-950\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var14 templ.SafeURL
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinURLErrs(product.URL)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/category.templ`, Line: 47, Col: 209}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "\">View Product</a></div></article>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</div></main>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "</section>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Pagination.TotalPages > 1 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "<nav class=\"flex flex-col items-center justify-between gap-4 rounded-[1.75rem] border border-white/10 bg-slate-950/50 px-6 py-5 text-sm text-slate-300 md:flex-row\"><div><p class=\"text-xs uppercase tracking-[0.24em] text-emerald-300\">Page</p><p class=\"mt-1\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d of %d", data.Pagination.Page, data.Pagination.TotalPages))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/category.templ`, Line: 58, Col: 98}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</p></div><div class=\"flex items-center gap-3\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Pagination.PrevURL != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<a class=\"rounded-full border border-white/10 px-4 py-2 font-semibold uppercase tracking-[0.2em] text-slate-200 transition hover:border-emerald-400/40 hover:text-white\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var16 templ.SafeURL
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinURLErrs(data.Pagination.PrevURL)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/category.templ`, Line: 62, Col: 207}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "\">Previous</a> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if data.Pagination.NextURL != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<a class=\"rounded-full border border-emerald-400/40 px-4 py-2 font-semibold uppercase tracking-[0.2em] text-emerald-200 transition hover:bg-emerald-300 hover:text-slate-950\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var17 templ.SafeURL
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinURLErrs(data.Pagination.NextURL)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/category.templ`, Line: 65, Col: 212}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "\">Next</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "</div></nav>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "</div></main>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = Layout(data.Category.Name, cssPath, jsPath, data.Menu, data.Locale).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
templ_7745c5c3_Err = Layout(data.Category.Name, cssPath, jsPath, data.Menu, data.Locale, layoutCartItems(data.CartSummary)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
+7 -2
View File
@@ -2,7 +2,7 @@ package templates
import pscatalog "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/catalog"
templ Layout(title string, cssPath string, jsPath string, menu []pscatalog.MenuItem, locale pscatalog.HeaderLocaleData) {
templ Layout(title string, cssPath string, jsPath string, menu []pscatalog.MenuItem, locale pscatalog.HeaderLocaleData, cartItems int64) {
<!doctype html>
<html lang={ pageLanguage(locale) }>
<head>
@@ -57,7 +57,12 @@ templ Layout(title string, cssPath string, jsPath string, menu []pscatalog.MenuI
}
<div class="header-actions">
<a class="nav-icon" href="#" aria-label="Search">⌕</a>
<a class="nav-icon" href="#" aria-label="Cart">🛒</a>
<a class="nav-icon relative" href={ localizedCartPath(locale) } aria-label="Cart">
🛒
if cartItems > 0 {
<span class="absolute -right-2 -top-2 inline-flex min-h-5 min-w-5 items-center justify-center rounded-full bg-amber-300 px-1.5 text-[0.65rem] font-semibold leading-none text-stone-950">{ cartItems }</span>
}
</a>
</div>
</div>
</div>
+282 -17
View File
@@ -1,14 +1,19 @@
package templates
import (
"sort"
"fmt"
"html"
"regexp"
"strconv"
"strings"
pscart "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/cart"
pscatalog "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/catalog"
pscookie "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/cookie"
"git.ma-al.com/goc_marek/ps_shop/internal/viewmodel"
)
var htmlTagPattern = regexp.MustCompile(`<[^>]+>`)
func menuListClass(depth int) string {
if depth == 0 {
return "flex min-w-0 flex-col gap-2 text-sm"
@@ -79,20 +84,280 @@ func menuPanelID(id int64) string {
return "mega-menu-panel-" + strconv.FormatInt(id, 10)
}
func sessionCookieLines(session *pscookie.SessionContext) []string {
if session == nil || len(session.Values) == 0 {
return nil
func moneyWithCurrency(amount float64, sign, code string) string {
formatted := fmt.Sprintf("%.2f", amount)
sign = strings.TrimSpace(sign)
switch {
case sign != "":
return formatted + " " + sign
default:
return formatted
}
keys := make([]string, 0, len(session.Values))
for key := range session.Values {
keys = append(keys, key)
}
sort.Strings(keys)
lines := make([]string, 0, len(keys))
for _, key := range keys {
lines = append(lines, key+"="+session.Values[key])
}
return lines
}
func taxLabel(rate float64) string {
return fmt.Sprintf("Tax %.2f%%", rate)
}
func conversionRateLabel(rate float64, code string) string {
code = strings.TrimSpace(code)
if code == "" {
return fmt.Sprintf("Rate %.6f", rate)
}
return fmt.Sprintf("Rate %.6f %s", rate, code)
}
func localizedCartPath(locale pscatalog.HeaderLocaleData) string {
code := strings.ToLower(strings.TrimSpace(locale.CurrentLanguage.Code))
if code == "" {
return "/cart"
}
return "/" + code + "/cart"
}
func plainTextHTML(value string) string {
value = strings.TrimSpace(value)
if value == "" {
return ""
}
value = htmlTagPattern.ReplaceAllString(value, " ")
value = html.UnescapeString(value)
return strings.Join(strings.Fields(value), " ")
}
func truncatedPlainTextHTML(value string, maxChars int) string {
value = plainTextHTML(value)
if value == "" || maxChars <= 0 {
return value
}
runes := []rune(value)
if len(runes) <= maxChars {
return value
}
cut := strings.TrimSpace(string(runes[:maxChars]))
if idx := strings.LastIndex(cut, " "); idx >= maxChars/2 {
cut = strings.TrimSpace(cut[:idx])
}
if cut == "" {
return value
}
return cut + "..."
}
func combinationAttributeLabel(publicName, group string) string {
publicName = strings.TrimSpace(publicName)
if publicName != "" {
return publicName
}
return strings.TrimSpace(group)
}
func isHexColor(value string) bool {
value = strings.TrimSpace(value)
if value == "" {
return false
}
if !strings.HasPrefix(value, "#") {
value = "#" + value
}
if len(value) != 7 {
return false
}
for _, r := range value[1:] {
if (r < '0' || r > '9') && (r < 'a' || r > 'f') && (r < 'A' || r > 'F') {
return false
}
}
return true
}
func cartCurrencyCode(data viewmodel.CartPageData) string {
if code := cartCurrencyField(data.Cart.Items, func(item pscart.Item) string { return item.CurrencyCode }); code != "" {
return code
}
return ""
}
func cartCurrencySign(data viewmodel.CartPageData) string {
if sign := cartCurrencyField(data.Cart.Items, func(item pscart.Item) string { return item.CurrencySign }); sign != "" {
return sign
}
return ""
}
func cartCurrencyField(items []pscart.Item, pick func(pscart.Item) string) string {
for _, item := range items {
value := strings.TrimSpace(pick(item))
if value != "" {
return value
}
}
return ""
}
func cartItemAttributeLabel(item pscart.Item) string {
if len(item.Attributes) == 0 {
return ""
}
parts := make([]string, 0, len(item.Attributes))
for _, attribute := range item.Attributes {
group := strings.TrimSpace(attribute.Group)
value := strings.TrimSpace(attribute.Value)
if value == "" {
continue
}
if group != "" {
parts = append(parts, group+": "+value)
continue
}
parts = append(parts, value)
}
return strings.Join(parts, " • ")
}
func layoutCartItems(summary *pscart.Summary) int64 {
if summary == nil || summary.TotalItems <= 0 {
return 0
}
return summary.TotalItems
}
func categoryPageStart(pagination viewmodel.CategoryPagination) int64 {
if pagination.TotalItems <= 0 || pagination.Page <= 0 || pagination.PerPage <= 0 {
return 0
}
return int64((pagination.Page-1)*pagination.PerPage) + 1
}
func categoryPageEnd(pagination viewmodel.CategoryPagination, loaded int) int64 {
if pagination.TotalItems <= 0 || loaded <= 0 {
return 0
}
end := categoryPageStart(pagination) + int64(loaded) - 1
if end > pagination.TotalItems {
return pagination.TotalItems
}
return end
}
type productVariantGroupView struct {
Key string
Label string
GroupType string
Options []productVariantOptionView
}
type productVariantOptionView struct {
Value string
ColorStyle string
CombinationIDs string
Selected bool
}
func productVariantGroups(combinations []pscatalog.ProductCombination, defaultID int64) []productVariantGroupView {
groups := make([]productVariantGroupView, 0)
groupIndex := make(map[string]int)
optionIndex := make(map[string]map[string]int)
for _, combination := range combinations {
for _, attribute := range combination.Attributes {
label := combinationAttributeLabel(attribute.PublicName, attribute.Group)
if label == "" {
continue
}
key := strings.ToLower(strings.TrimSpace(label))
idx, exists := groupIndex[key]
if !exists {
groups = append(groups, productVariantGroupView{
Key: "variant-group-" + strconv.Itoa(len(groups)),
Label: label,
GroupType: normalizedGroupType(attribute.GroupType),
Options: make([]productVariantOptionView, 0),
})
idx = len(groups) - 1
groupIndex[key] = idx
optionIndex[key] = make(map[string]int)
}
optionKey := strings.ToLower(strings.TrimSpace(attribute.Value)) + "|" + normalizedColorStyle(attribute.Color)
optIdx, exists := optionIndex[key][optionKey]
if !exists {
groups[idx].Options = append(groups[idx].Options, productVariantOptionView{
Value: attribute.Value,
ColorStyle: normalizedColorStyle(attribute.Color),
CombinationIDs: strconv.FormatInt(combination.ID, 10),
Selected: combination.ID == defaultID,
})
optionIndex[key][optionKey] = len(groups[idx].Options) - 1
continue
}
option := &groups[idx].Options[optIdx]
option.CombinationIDs += "," + strconv.FormatInt(combination.ID, 10)
if combination.ID == defaultID {
option.Selected = true
}
}
}
return groups
}
func normalizedGroupType(value string) string {
value = strings.ToLower(strings.TrimSpace(value))
switch value {
case "color", "radio", "select":
return value
default:
return "select"
}
}
func normalizedColorStyle(value string) string {
value = strings.TrimSpace(value)
if value == "" {
return ""
}
if !strings.HasPrefix(value, "#") {
value = "#" + value
}
if !isHexColor(value) {
return ""
}
return value
}
func variantColorOptionClass(selected bool) string {
if selected {
return "inline-flex min-h-10 min-w-10 items-center justify-center border border-stone-900 p-0.5 ring-1 ring-stone-900 transition"
}
return "inline-flex min-h-10 min-w-10 items-center justify-center border border-stone-300 p-0.5 transition hover:border-stone-700"
}
func variantRadioOptionClass(selected bool) string {
if selected {
return "rounded-full border border-stone-900 bg-stone-900 px-4 py-2 text-sm font-medium text-stone-50 transition"
}
return "rounded-full border border-stone-300 bg-white px-4 py-2 text-sm font-medium text-stone-900 transition hover:border-stone-700"
}
func variantSelectOptionClass(selected bool) string {
if selected {
return "w-full rounded-2xl bg-stone-900 px-4 py-3 text-left text-sm font-medium text-stone-50 transition"
}
return "w-full rounded-2xl px-4 py-3 text-left text-sm font-medium text-stone-700 transition hover:bg-stone-100 hover:text-stone-950"
}
func selectedVariantOptionValue(options []productVariantOptionView) string {
for _, option := range options {
if option.Selected {
return option.Value
}
}
if len(options) == 0 {
return ""
}
return options[0].Value
}
+361 -325
View File
File diff suppressed because it is too large Load Diff
+218 -28
View File
@@ -7,9 +7,9 @@ import (
)
templ ProductPage(data viewmodel.ProductPageData, cssPath string, jsPath string) {
@Layout(data.Product.Name, cssPath, jsPath, data.Menu, data.Locale) {
@Layout(data.Product.Name, cssPath, jsPath, data.Menu, data.Locale, layoutCartItems(data.CartSummary)) {
<main class="min-h-screen bg-[radial-gradient(circle_at_top,_rgba(245,158,11,0.28),_transparent_40%),linear-gradient(180deg,#0c0a09,#1c1917)]">
<div class="mx-auto flex max-w-6xl flex-col gap-12 px-6 py-10 lg:px-8">
<div class="mx-auto flex w-full max-w-[104rem] flex-col gap-12 px-6 py-10 lg:px-8">
<header class="flex items-center justify-between border-b border-stone-800 pb-6">
<a class="text-sm uppercase tracking-[0.32em] text-amber-300" href={ data.ShopBaseURL }>Prestashop Proxy</a>
<div class="text-right text-sm text-stone-400">
@@ -24,55 +24,245 @@ templ ProductPage(data viewmodel.ProductPageData, cssPath string, jsPath string)
</div>
</header>
<section class="grid gap-8 lg:grid-cols-[1.1fr_0.9fr]">
<section class="grid gap-8 lg:grid-cols-[1fr_1fr]">
<div class="rounded-3xl border border-stone-800 bg-stone-900/70 p-8 shadow-2xl shadow-amber-950/20">
<p class="text-xs uppercase tracking-[0.28em] text-stone-500">Product</p>
if data.Product.ImageURL != "" {
<button class="mt-5 block w-full overflow-hidden rounded-[2rem] border border-stone-800 bg-stone-950/60 text-left shadow-[0_24px_60px_rgba(0,0,0,0.28)]" type="button" data-gallery-open>
<img class="block h-80 w-full object-cover md:h-[28rem]" src={ data.Product.ImageURL } alt={ data.Product.Name } data-product-main-image="" data-default-image={ data.Product.ImageURL }/>
</button>
}
if len(data.Product.GalleryImages) > 1 {
<div class="mt-4 rounded-[1.6rem] border border-stone-800 bg-stone-950/55 p-3" data-product-thumb-carousel>
<div class="flex items-center gap-3">
<button class="inline-flex h-11 w-11 shrink-0 items-center justify-center rounded-full border border-stone-700 bg-stone-950/80 text-lg text-stone-100 transition hover:border-amber-400/40 hover:text-amber-200" type="button" aria-label="Previous thumbnails" data-product-thumb-prev>
</button>
<div class="min-w-0 flex-1 overflow-hidden" data-product-thumb-viewport>
<div class="flex gap-3" data-product-thumb-track>
for i, image := range data.Product.GalleryImages {
if image.ThumbURL != "" && image.URL != "" {
<button class="group/thumb shrink-0 overflow-hidden rounded-[1.1rem] border border-stone-800 bg-stone-950/80 transition hover:border-amber-400/40" type="button" data-product-thumb-index={ fmt.Sprintf("%d", i) } data-product-thumb-large={ image.URL } data-product-thumb-large-fallback={ image.URL } data-product-thumb-alt={ data.Product.Name } data-gallery-open="">
<img class="block h-20 w-24 object-cover transition duration-300 group-hover/thumb:scale-[1.03]" src={ image.ThumbURL } alt={ data.Product.Name }/>
</button>
}
}
</div>
</div>
<button class="inline-flex h-11 w-11 shrink-0 items-center justify-center rounded-full border border-stone-700 bg-stone-950/80 text-lg text-stone-100 transition hover:border-amber-400/40 hover:text-amber-200" type="button" aria-label="Next thumbnails" data-product-thumb-next>
</button>
</div>
</div>
}
if data.CategoryURL != "" && data.Product.CategoryName != "" {
<a class="mt-4 inline-flex text-sm uppercase tracking-[0.24em] text-amber-300 underline underline-offset-4" href={ data.CategoryURL }>{ data.Product.CategoryName }</a>
}
<h1 class="mt-4 font-serif text-4xl text-stone-50">{ data.Product.Name }</h1>
<p class="mt-6 max-w-2xl text-lg leading-8 text-stone-300">{ data.Product.ShortDescription }</p>
<div class="prose prose-invert mt-8 max-w-none text-stone-300">
@templ.Raw(data.Product.Description)
</div>
</div>
<aside class="flex flex-col justify-between rounded-3xl border border-amber-500/30 bg-amber-400/10 p-8">
<div>
<p class="text-xs uppercase tracking-[0.28em] text-amber-200">Price</p>
<p class="mt-4 text-5xl font-semibold text-stone-50">{ fmt.Sprintf("%.2f", data.Product.Price) }</p>
<p class="mt-2 text-sm text-stone-300">Live data loaded from PrestaShop storage, rendered by Go.</p>
<p class="mt-4 text-5xl font-semibold text-stone-50" data-product-price-gross="" data-default-price-gross={ moneyWithCurrency(data.Product.PriceTaxIncl, data.Product.CurrencySign, data.Product.CurrencyCode) }>{ moneyWithCurrency(data.Product.PriceTaxIncl, data.Product.CurrencySign, data.Product.CurrencyCode) }</p>
<p class="mt-2 text-sm text-stone-300">Including VAT { fmt.Sprintf("%.0f%%", data.Product.TaxRate) }</p>
<p class="mt-2 text-sm text-stone-300" data-product-price-net="" data-default-price-net={ "Net " + moneyWithCurrency(data.Product.Price, data.Product.CurrencySign, data.Product.CurrencyCode) }>Net { moneyWithCurrency(data.Product.Price, data.Product.CurrencySign, data.Product.CurrencyCode) }</p>
<p class="mt-2 text-sm text-stone-300">{ conversionRateLabel(data.Product.ConversionRate, data.Product.CurrencyCode) }</p>
</div>
<form class="mt-10 flex flex-col gap-4" method="post" action={ data.ShopBaseURL + "/cart" }>
<form class="mt-10 flex flex-col gap-4" method="post" action={ localizedCartPath(data.Locale) }>
<input type="hidden" name="action" value="add"/>
<input type="hidden" name="id_product" value={ fmt.Sprintf("%d", data.Product.ID) }/>
if len(data.Product.Combinations) > 0 {
<input type="hidden" name="id_product_attribute" value={ fmt.Sprintf("%d", data.Product.DefaultAttribute) } data-variant-combination/>
<div class="rounded-[1.5rem] border border-stone-200 bg-stone-50 p-5 text-stone-950 shadow-[0_18px_40px_rgba(0,0,0,0.12)]">
<div class="hidden" aria-hidden="true">
for _, combination := range data.Product.Combinations {
<span
data-variant-combination-image={ fmt.Sprintf("%d", combination.ID) }
data-image-large={ combination.ImageURL }
data-price-gross={ moneyWithCurrency(combination.PriceTaxIncl, data.Product.CurrencySign, data.Product.CurrencyCode) }
data-price-net={ "Net " + moneyWithCurrency(combination.Price, data.Product.CurrencySign, data.Product.CurrencyCode) }></span>
}
</div>
<div class="rounded-[1.25rem] border border-stone-200 bg-stone-100 px-4 py-3">
<p class="text-[0.72rem] font-medium uppercase tracking-[0.18em] text-stone-500">Current selection</p>
<p class="mt-2 text-base font-medium text-stone-900" data-variant-selection-summary>Choose product options</p>
</div>
<div class="mt-4 space-y-5" data-variant-picker>
for _, group := range productVariantGroups(data.Product.Combinations, data.Product.DefaultAttribute) {
<div class="space-y-3">
<div class="flex items-center justify-between gap-4">
<p class="text-sm font-medium uppercase tracking-[0.16em] text-stone-700">{ group.Label }</p>
<p class="text-sm text-stone-500" data-variant-current={ group.Key }></p>
</div>
if group.GroupType == "select" {
<div class="relative" data-variant-group={ group.Key } data-variant-select="">
<button class="flex w-full items-center justify-between gap-4 border-b border-stone-900/80 px-0 py-2 text-left text-lg text-stone-900 transition hover:text-stone-700" type="button" data-variant-select-trigger="" aria-expanded="false">
<span data-variant-select-value="">{ selectedVariantOptionValue(group.Options) }</span>
<span class="text-base text-stone-700">▼</span>
</button>
<div class="absolute left-0 right-0 top-full z-20 mt-3 hidden rounded-[1.5rem] border border-stone-200 bg-white p-2 shadow-[0_24px_60px_rgba(0,0,0,0.18)]" data-variant-select-menu="">
for _, option := range group.Options {
<button class={ variantSelectOptionClass(option.Selected) } type="button" data-variant-option="" data-variant-presentation="select" data-combination-ids={ option.CombinationIDs } data-selected={ fmt.Sprintf("%t", option.Selected) }>
{ option.Value }
</button>
}
</div>
</div>
} else if group.GroupType == "color" {
<div class="mt-1 flex flex-wrap gap-1.5" data-variant-group={ group.Key }>
for _, option := range group.Options {
<button class={ variantColorOptionClass(option.Selected) } type="button" data-variant-option="" data-variant-presentation="color" data-combination-ids={ option.CombinationIDs } aria-label={ option.Value } title={ option.Value } data-selected={ fmt.Sprintf("%t", option.Selected) }>
if option.ColorStyle != "" {
<span class="block h-6 w-8 border border-stone-300" style={ "background-color:" + option.ColorStyle }></span>
} else {
<span class="px-2 text-xs font-medium text-stone-900">{ option.Value }</span>
}
</button>
}
</div>
} else {
<div class="mt-1 flex flex-wrap gap-2" data-variant-group={ group.Key }>
for _, option := range group.Options {
<button class={ variantRadioOptionClass(option.Selected) } type="button" data-variant-option="" data-variant-presentation="radio" data-combination-ids={ option.CombinationIDs } data-selected={ fmt.Sprintf("%t", option.Selected) }>
{ option.Value }
</button>
}
</div>
}
</div>
}
</div>
<p class="mt-5 text-base text-stone-700">Selected combination will be added directly to the PrestaShop cart.</p>
</div>
}
<input type="hidden" name="qty" value="1"/>
<button class="rounded-full bg-amber-300 px-5 py-3 text-sm font-semibold uppercase tracking-[0.2em] text-stone-950 transition hover:bg-amber-200" type="submit">
Add to cart in PrestaShop
Add to cart
</button>
<a class="text-sm text-stone-300 underline underline-offset-4" href={ data.ShopBaseURL + "/login" }>Account and login remain on PrestaShop</a>
</form>
</aside>
</section>
if data.Session != nil {
<section class="rounded-3xl border border-stone-800 bg-stone-950/80 p-8">
<p class="text-xs uppercase tracking-[0.28em] text-stone-500">Go Cookie Debug</p>
<div class="mt-6 grid gap-6 lg:grid-cols-2">
<div>
<p class="text-sm font-semibold text-stone-200">Raw Cookie</p>
<pre class="mt-3 overflow-x-auto rounded-2xl bg-black/30 p-4 text-xs leading-6 text-stone-300">{ data.Session.RawCookie }</pre>
</div>
<div>
<p class="text-sm font-semibold text-stone-200">Decoded Values</p>
<pre class="mt-3 overflow-x-auto rounded-2xl bg-black/30 p-4 text-xs leading-6 text-stone-300">
for _, line := range sessionCookieLines(data.Session) {
{ line }
{"\n"}
}
</pre>
</div>
if data.Product.ShortDescription != "" {
<section class="rounded-3xl border border-stone-800 bg-stone-900/70 p-8 shadow-2xl shadow-amber-950/10">
<div class="flex flex-col gap-2 border-b border-stone-800 pb-5">
<p class="text-xs uppercase tracking-[0.28em] text-amber-300">Summary</p>
<h2 class="font-serif text-3xl text-stone-50">At a glance</h2>
</div>
<p class="mt-8 max-w-none text-lg leading-8 text-stone-300">{ plainTextHTML(data.Product.ShortDescription) }</p>
</section>
}
if data.Product.Description != "" {
<section class="rounded-3xl border border-stone-800 bg-stone-900/70 p-8 shadow-2xl shadow-amber-950/10">
<div class="flex flex-col gap-2 border-b border-stone-800 pb-5">
<p class="text-xs uppercase tracking-[0.28em] text-amber-300">Description</p>
<h2 class="font-serif text-3xl text-stone-50">About this product</h2>
</div>
<div class="prose prose-invert mt-8 max-w-none text-stone-300">
@templ.Raw(data.Product.Description)
</div>
</section>
}
if len(data.Product.Features) > 0 {
<section class="rounded-3xl border border-stone-800 bg-stone-900/70 p-8 shadow-2xl shadow-amber-950/10">
<div class="flex flex-col gap-2 border-b border-stone-800 pb-5">
<p class="text-xs uppercase tracking-[0.28em] text-amber-300">Features</p>
<h2 class="font-serif text-3xl text-stone-50">Product details</h2>
</div>
<div class="mt-6 grid gap-3 md:grid-cols-2">
for _, feature := range data.Product.Features {
<div class="rounded-[1.5rem] border border-stone-800 bg-stone-950/60 px-5 py-4">
<p class="text-[0.7rem] uppercase tracking-[0.24em] text-stone-500">{ feature.Name }</p>
<p class="mt-2 text-sm leading-7 text-stone-200">{ plainTextHTML(feature.Value) }</p>
</div>
}
</div>
</section>
}
if len(data.Product.Accessories) > 0 {
<section class="rounded-3xl border border-stone-800 bg-stone-900/70 p-8 shadow-2xl shadow-amber-950/10">
<div class="flex flex-col gap-2 border-b border-stone-800 pb-5">
<p class="text-xs uppercase tracking-[0.28em] text-amber-300">Related products</p>
<h2 class="font-serif text-3xl text-stone-50">Accessories</h2>
</div>
<div class="mt-6 grid gap-5 md:grid-cols-2 xl:grid-cols-3">
for _, product := range data.Product.Accessories {
<article class="group rounded-[1.75rem] border border-stone-800 bg-stone-950/55 p-5 shadow-[0_24px_80px_rgba(0,0,0,0.28)] transition hover:-translate-y-1 hover:border-amber-400/40">
if product.ImageURL != "" {
<a class="mb-5 block overflow-hidden rounded-[1.25rem] border border-stone-800 bg-stone-950/80 shadow-[0_16px_40px_rgba(0,0,0,0.24)]" href={ product.URL }>
<img class="block h-56 w-full object-cover transition duration-500 group-hover:scale-[1.03]" src={ product.ImageURL } alt={ product.Name }/>
</a>
}
<p class="text-xs uppercase tracking-[0.28em] text-amber-300">Accessory</p>
<h3 class="mt-3 text-2xl font-semibold text-stone-50">{ product.Name }</h3>
<p class="mt-4 text-sm leading-7 text-stone-300">{ truncatedPlainTextHTML(product.ShortDescription, 180) }</p>
<div class="mt-8 flex items-center justify-between gap-4">
<div>
<p class="text-2xl font-semibold text-stone-50">{ moneyWithCurrency(product.PriceTaxIncl, product.CurrencySign, product.CurrencyCode) }</p>
<p class="mt-1 text-xs uppercase tracking-[0.2em] text-stone-500">{ taxLabel(product.TaxRate) } · { conversionRateLabel(product.ConversionRate, product.CurrencyCode) }</p>
</div>
<a class="rounded-full border border-amber-400/40 px-4 py-2 text-xs font-semibold uppercase tracking-[0.22em] text-amber-200 transition hover:bg-amber-300 hover:text-stone-950" href={ product.URL }>
View Product
</a>
</div>
</article>
}
</div>
</section>
}
if len(data.Product.GalleryImages) > 0 {
<div class="fixed inset-0 z-[70] hidden bg-black/80 px-4 py-6 backdrop-blur-sm" aria-hidden="true" data-gallery-modal>
<div class="mx-auto flex h-full w-full max-w-[104rem] flex-col rounded-[2rem] border border-stone-800 bg-[linear-gradient(180deg,rgba(28,25,23,0.98),rgba(12,10,9,0.98))] p-4 shadow-[0_32px_120px_rgba(0,0,0,0.55)] md:p-6">
<div class="flex items-center justify-between gap-4 border-b border-stone-800 pb-4">
<div>
<p class="text-xs uppercase tracking-[0.26em] text-amber-300">Gallery</p>
<p class="mt-1 text-lg font-semibold text-stone-50">{ data.Product.Name }</p>
</div>
<button class="rounded-full border border-stone-700 px-4 py-2 text-xs font-semibold uppercase tracking-[0.22em] text-stone-200 transition hover:border-amber-400/40 hover:text-stone-50" type="button" data-gallery-close>
Close
</button>
</div>
<div class="mt-5 flex min-h-0 flex-1 flex-col gap-5 lg:flex-row">
<div class="relative flex min-h-0 flex-1 items-center justify-center overflow-hidden rounded-[1.75rem] border border-stone-800 bg-stone-950/70">
<img class="max-h-full w-full object-contain" src={ data.Product.GalleryImages[0].URL } alt={ data.Product.Name } data-gallery-main/>
if len(data.Product.GalleryImages) > 1 {
<button class="absolute left-4 top-1/2 inline-flex h-11 w-11 -translate-y-1/2 items-center justify-center rounded-full border border-stone-700 bg-stone-950/75 text-lg text-stone-100 transition hover:border-amber-400/40 hover:text-amber-200" type="button" aria-label="Previous image" data-gallery-prev>
</button>
<button class="absolute right-4 top-1/2 inline-flex h-11 w-11 -translate-y-1/2 items-center justify-center rounded-full border border-stone-700 bg-stone-950/75 text-lg text-stone-100 transition hover:border-amber-400/40 hover:text-amber-200" type="button" aria-label="Next image" data-gallery-next>
</button>
}
</div>
<div class="rounded-[1.6rem] border border-stone-800 bg-stone-950/55 p-3 lg:w-44" data-gallery-thumb-carousel>
<div class="flex items-center gap-3 lg:h-full lg:flex-col">
<button class="inline-flex h-11 w-11 shrink-0 items-center justify-center rounded-full border border-stone-700 bg-stone-950/80 text-lg text-stone-100 transition hover:border-amber-400/40 hover:text-amber-200" type="button" aria-label="Previous gallery thumbnails" data-gallery-thumb-prev>
</button>
<div class="min-w-0 flex-1 overflow-hidden lg:w-full" data-gallery-thumb-viewport>
<div class="flex gap-3 lg:flex-col" data-gallery-thumb-track>
for _, image := range data.Product.GalleryImages {
if image.ThumbURL != "" && image.URL != "" {
<button class="shrink-0 overflow-hidden rounded-[1.1rem] border border-stone-800 bg-stone-950/80 transition hover:border-amber-400/40" type="button" data-gallery-thumb={ image.URL } data-gallery-alt={ data.Product.Name }>
<img class="block h-20 w-24 object-cover lg:h-24 lg:w-full" src={ image.ThumbURL } alt={ data.Product.Name }/>
</button>
}
}
</div>
</div>
<button class="inline-flex h-11 w-11 shrink-0 items-center justify-center rounded-full border border-stone-700 bg-stone-950/80 text-lg text-stone-100 transition hover:border-amber-400/40 hover:text-amber-200" type="button" aria-label="Next gallery thumbnails" data-gallery-thumb-next>
</button>
</div>
</div>
</div>
</div>
</div>
}
</div>
</main>
}
+1053 -130
View File
File diff suppressed because it is too large Load Diff