This commit is contained in:
2026-05-12 05:08:39 +02:00
parent ee054955b9
commit c99065496b
28 changed files with 2045 additions and 120 deletions
+14 -13
View File
@@ -14,19 +14,19 @@ import (
"github.com/labstack/echo/v4"
"prestaproxy/internal/assets"
"prestaproxy/internal/http/handlers"
appmiddleware "prestaproxy/internal/http/middleware"
httpproxy "prestaproxy/internal/http/proxy"
pscart "prestaproxy/internal/prestashop/cart"
pscatalog "prestaproxy/internal/prestashop/catalog"
psconfig "prestaproxy/internal/prestashop/config"
pscookie "prestaproxy/internal/prestashop/cookie"
pscustomer "prestaproxy/internal/prestashop/customer"
psroutes "prestaproxy/internal/prestashop/routes"
pssession "prestaproxy/internal/prestashop/session"
"prestaproxy/internal/render"
"prestaproxy/internal/store"
"git.ma-al.com/goc_marek/ps_shop/internal/assets"
"git.ma-al.com/goc_marek/ps_shop/internal/http/handlers"
appmiddleware "git.ma-al.com/goc_marek/ps_shop/internal/http/middleware"
httpproxy "git.ma-al.com/goc_marek/ps_shop/internal/http/proxy"
pscart "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/cart"
pscatalog "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/catalog"
psconfig "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/config"
pscookie "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/cookie"
pscustomer "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/customer"
psroutes "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/routes"
pssession "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/session"
"git.ma-al.com/goc_marek/ps_shop/internal/render"
"git.ma-al.com/goc_marek/ps_shop/internal/store"
)
func main() {
@@ -95,6 +95,7 @@ func run() error {
render.New(assetManifest),
cfg,
productRoute,
categoryRoute,
)
proxyHandler, err := httpproxy.New(cfg.PrestaShopProxyTarget)
+1 -1
View File
@@ -1,4 +1,4 @@
module prestaproxy
module git.ma-al.com/goc_marek/ps_shop
go 1.25.0
+33 -43
View File
@@ -8,44 +8,43 @@ import (
"github.com/labstack/echo/v4"
"gorm.io/gorm"
appmiddleware "prestaproxy/internal/http/middleware"
pscart "prestaproxy/internal/prestashop/cart"
pscatalog "prestaproxy/internal/prestashop/catalog"
psconfig "prestaproxy/internal/prestashop/config"
pscustomer "prestaproxy/internal/prestashop/customer"
psroutes "prestaproxy/internal/prestashop/routes"
"prestaproxy/internal/render"
"prestaproxy/internal/viewmodel"
appmiddleware "git.ma-al.com/goc_marek/ps_shop/internal/http/middleware"
pscart "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/cart"
pscatalog "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/catalog"
psconfig "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/config"
pscustomer "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/customer"
psroutes "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/routes"
"git.ma-al.com/goc_marek/ps_shop/internal/render"
"git.ma-al.com/goc_marek/ps_shop/internal/viewmodel"
)
const categorySlugContextKey = "category_slug"
const categoryIDContextKey = "category_id"
type CategoryHandler struct {
catalog *pscatalog.Service
customers *pscustomer.Service
carts *pscart.Service
renderer *render.Engine
config psconfig.Config
products *psroutes.ProductRoute
catalog *pscatalog.Service
customers *pscustomer.Service
carts *pscart.Service
renderer *render.Engine
config psconfig.Config
products *psroutes.ProductRoute
categories *psroutes.CategoryRoute
}
func NewCategoryHandler(catalog *pscatalog.Service, customers *pscustomer.Service, carts *pscart.Service, renderer *render.Engine, cfg psconfig.Config, products *psroutes.ProductRoute) *CategoryHandler {
func NewCategoryHandler(catalog *pscatalog.Service, customers *pscustomer.Service, carts *pscart.Service, renderer *render.Engine, cfg psconfig.Config, products *psroutes.ProductRoute, categories *psroutes.CategoryRoute) *CategoryHandler {
return &CategoryHandler{
catalog: catalog,
customers: customers,
carts: carts,
renderer: renderer,
config: cfg,
products: products,
catalog: catalog,
customers: customers,
carts: carts,
renderer: renderer,
config: cfg,
products: products,
categories: categories,
}
}
func (h *CategoryHandler) Show(c echo.Context) error {
session := appmiddleware.GetSession(c)
if session == nil {
session = appmiddleware.GetSession(c)
}
if h == nil || h.catalog == nil || h.renderer == nil {
return echo.NewHTTPError(http.StatusInternalServerError, "category handler is not initialized")
}
@@ -91,6 +90,16 @@ func (h *CategoryHandler) Show(c echo.Context) error {
ShopBaseURL: h.config.PrestaShopBaseURL,
}
assignCategoryProductLinks(c.Request(), h.products, &page)
menu, err := loadMenu(c.Request(), h.catalog, h.categories, languageID, shopID)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "menu query failed: "+err.Error())
}
page.Menu = menu
locale, err := loadHeaderLocale(c.Request(), h.catalog, session, languageID)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "locale query failed: "+err.Error())
}
page.Locale = locale
if err := h.renderer.Category(c.Response(), c.Request(), page); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "category render failed: "+err.Error())
@@ -141,22 +150,3 @@ func assignCategoryProductLinks(req *http.Request, route *psroutes.ProductRoute,
})
}
}
func requestLanguagePrefix(req *http.Request) string {
if req == nil || req.URL == nil {
return ""
}
path := strings.Trim(req.URL.Path, "/")
if path == "" {
return ""
}
first := path
if idx := strings.IndexByte(path, '/'); idx >= 0 {
first = path[:idx]
}
first = strings.TrimSpace(first)
if len(first) < 2 || len(first) > 5 {
return ""
}
return "/" + first
}
+151
View File
@@ -0,0 +1,151 @@
package handlers
import (
"net/http"
"net/url"
"strconv"
"strings"
pscatalog "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/catalog"
pscookie "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/cookie"
psroutes "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/routes"
)
func loadMenu(req *http.Request, catalog *pscatalog.Service, route *psroutes.CategoryRoute, languageID int64, shopID int64) ([]pscatalog.MenuItem, error) {
if catalog == nil || route == nil {
return nil, nil
}
menu, err := catalog.GetCategoryMenu(req.Context(), languageID, shopID)
if err != nil {
return nil, err
}
assignMenuLinks(req, route, menu)
return menu, nil
}
func assignMenuLinks(req *http.Request, route *psroutes.CategoryRoute, items []pscatalog.MenuItem) {
langPrefix := requestLanguagePrefix(req)
for i := range items {
items[i].URL = route.BuildPath(psroutes.CategoryURLData{
ID: items[i].ID,
Slug: items[i].Slug,
LanguagePrefix: langPrefix,
})
if len(items[i].Children) > 0 {
assignMenuLinks(req, route, items[i].Children)
}
}
}
func loadHeaderLocale(req *http.Request, catalog *pscatalog.Service, session *pscookie.SessionContext, languageID int64) (pscatalog.HeaderLocaleData, error) {
if catalog == nil || req == nil {
return pscatalog.HeaderLocaleData{}, nil
}
var currencyID int64
countryISO := ""
if session != nil {
currencyID = int64Default(session.CurrencyID, 0)
countryISO = strings.TrimSpace(session.Values["iso_code_country"])
}
locale, err := catalog.GetHeaderLocale(req.Context(), languageID, currencyID, countryISO)
if err != nil {
return pscatalog.HeaderLocaleData{}, err
}
assignLanguageSwitchLinks(req, &locale)
assignMarketSwitchLinks(req, &locale)
return locale, nil
}
func assignLanguageSwitchLinks(req *http.Request, locale *pscatalog.HeaderLocaleData) {
if req == nil || req.URL == nil || locale == nil || len(locale.Languages) == 0 {
return
}
basePath := stripLanguagePrefix(req.URL.Path, locale.Languages)
rawQuery := req.URL.RawQuery
for i := range locale.Languages {
code := strings.ToLower(strings.TrimSpace(locale.Languages[i].Code))
path := "/" + code
if basePath != "/" {
path += basePath
}
if rawQuery != "" {
path += "?" + rawQuery
}
locale.Languages[i].URL = path
}
}
func assignMarketSwitchLinks(req *http.Request, locale *pscatalog.HeaderLocaleData) {
if req == nil || req.URL == nil || locale == nil || len(locale.Countries) == 0 {
return
}
for i := range locale.Countries {
marketCode := strings.ToUpper(strings.TrimSpace(locale.Countries[i].Code))
if marketCode == "" || locale.Countries[i].CurrencyID == 0 {
continue
}
query := req.URL.Query()
query.Set("market", strconv.FormatInt(locale.Countries[i].ID, 10)+":"+marketCode+":"+strconv.FormatInt(locale.Countries[i].CurrencyID, 10))
locale.Countries[i].URL = rebuildURL(req.URL.Path, query)
}
}
func requestLanguagePrefix(req *http.Request) string {
if req == nil || req.URL == nil {
return ""
}
path := strings.Trim(req.URL.Path, "/")
if path == "" {
return ""
}
first := path
if idx := strings.IndexByte(path, '/'); idx >= 0 {
first = path[:idx]
}
first = strings.TrimSpace(first)
if len(first) < 2 || len(first) > 5 {
return ""
}
return "/" + first
}
func stripLanguagePrefix(path string, languages []pscatalog.LocaleOption) string {
if path == "" {
return "/"
}
codes := make(map[string]struct{}, len(languages))
for _, language := range languages {
code := strings.ToLower(strings.TrimSpace(language.Code))
if code != "" {
codes[code] = struct{}{}
}
}
trimmed := strings.Trim(path, "/")
if trimmed == "" {
return "/"
}
parts := strings.Split(trimmed, "/")
if _, ok := codes[strings.ToLower(parts[0])]; ok {
parts = parts[1:]
}
if len(parts) == 0 {
return "/"
}
return "/" + strings.Join(parts, "/")
}
func rebuildURL(path string, query url.Values) string {
if path == "" {
path = "/"
}
encoded := query.Encode()
if encoded == "" {
return path
}
return path + "?" + encoded
}
+18 -11
View File
@@ -8,14 +8,14 @@ import (
"github.com/labstack/echo/v4"
"gorm.io/gorm"
appmiddleware "prestaproxy/internal/http/middleware"
pscart "prestaproxy/internal/prestashop/cart"
pscatalog "prestaproxy/internal/prestashop/catalog"
psconfig "prestaproxy/internal/prestashop/config"
pscustomer "prestaproxy/internal/prestashop/customer"
psroutes "prestaproxy/internal/prestashop/routes"
"prestaproxy/internal/render"
"prestaproxy/internal/viewmodel"
appmiddleware "git.ma-al.com/goc_marek/ps_shop/internal/http/middleware"
pscart "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/cart"
pscatalog "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/catalog"
psconfig "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/config"
pscustomer "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/customer"
psroutes "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/routes"
"git.ma-al.com/goc_marek/ps_shop/internal/render"
"git.ma-al.com/goc_marek/ps_shop/internal/viewmodel"
)
const productSlugContextKey = "product_slug"
@@ -43,9 +43,6 @@ func NewProductHandler(products *pscatalog.Service, customers *pscustomer.Servic
func (h *ProductHandler) Show(c echo.Context) error {
session := appmiddleware.GetSession(c)
if session == nil {
session = appmiddleware.GetSession(c)
}
if h == nil || h.products == nil || h.renderer == nil {
return echo.NewHTTPError(http.StatusInternalServerError, "product handler is not initialized")
}
@@ -91,6 +88,16 @@ func (h *ProductHandler) Show(c echo.Context) error {
CartSummary: cartSummary,
ShopBaseURL: h.config.PrestaShopBaseURL,
}
menu, err := loadMenu(c.Request(), h.products, h.categories, languageID, shopID)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "menu query failed: "+err.Error())
}
page.Menu = menu
locale, err := loadHeaderLocale(c.Request(), h.products, session, languageID)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "locale query failed: "+err.Error())
}
page.Locale = locale
return h.renderer.Product(c.Response(), c.Request(), page)
}
+1 -1
View File
@@ -1,7 +1,7 @@
package middleware
import (
"prestaproxy/internal/prestashop/cookie"
"git.ma-al.com/goc_marek/ps_shop/internal/prestashop/cookie"
"github.com/labstack/echo/v4"
)
+123 -3
View File
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"hash/crc32"
"net"
"net/http"
"net/url"
"path"
@@ -12,8 +13,8 @@ import (
"github.com/labstack/echo/v4"
psconfig "prestaproxy/internal/prestashop/config"
pscookie "prestaproxy/internal/prestashop/cookie"
psconfig "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/config"
pscookie "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/cookie"
)
type AnonymousSessionInitializer interface {
@@ -62,6 +63,7 @@ func Session(cfg psconfig.Config, codec pscookie.Codec, initializer AnonymousSes
}
if ownedRoute {
applyRequestLanguage(session, resolveRequestLanguageID(c.Request().Context(), c.Request(), session, languageResolver))
applyRequestMarket(session, requestMarketSelection(c.Request()))
}
if ownedRoute && shouldSetSessionCookie(rawCookie, session) {
encoded, err := codec.Encode(session)
@@ -70,6 +72,9 @@ func Session(cfg psconfig.Config, codec pscookie.Codec, initializer AnonymousSes
}
session.RawCookie = encoded
setPrestaShopCookie(c.Request(), c.Response(), ownership.ProductPrefixes, cookieName, encoded)
if redirectURL, ok := clearMarketSelectionURL(c.Request()); ok {
return c.Redirect(http.StatusSeeOther, redirectURL)
}
}
SetSession(c, session)
@@ -78,6 +83,12 @@ func Session(cfg psconfig.Config, codec pscookie.Codec, initializer AnonymousSes
}
}
type marketSelection struct {
CountryID int64
CountryISO string
CurrencyID int64
}
func resolveRequestLanguageID(ctx context.Context, req *http.Request, session *pscookie.SessionContext, resolver LanguageResolver) int64 {
if resolver == nil {
return 0
@@ -177,6 +188,50 @@ func applyRequestLanguage(session *pscookie.SessionContext, languageID int64) {
session.RawCookie = ""
}
func applyRequestMarket(session *pscookie.SessionContext, selection marketSelection) {
if session == nil || selection.CountryISO == "" || selection.CurrencyID == 0 {
return
}
currentCountry := ""
currentCurrency := int64(0)
currentCountryID := int64(0)
if session.Values != nil {
currentCountry = strings.ToUpper(strings.TrimSpace(session.Values["iso_code_country"]))
if session.CurrencyID != nil {
currentCurrency = *session.CurrencyID
}
currentCountryID, _ = strconv.ParseInt(session.Values["id_country"], 10, 64)
}
if currentCountry == selection.CountryISO && currentCurrency == selection.CurrencyID && currentCountryID == selection.CountryID {
return
}
if session.Values == nil {
session.Values = map[string]string{}
}
session.CurrencyID = int64Ptr(selection.CurrencyID)
session.Values["iso_code_country"] = selection.CountryISO
if selection.CountryID > 0 {
session.Values["id_country"] = strconv.FormatInt(selection.CountryID, 10)
session.OrderedKeys = ensureOrderedKey(session.OrderedKeys, "id_country", 5)
}
session.Values["id_currency"] = strconv.FormatInt(selection.CurrencyID, 10)
session.OrderedKeys = ensureOrderedKey(session.OrderedKeys, "iso_code_country", 4)
session.OrderedKeys = ensureOrderedKey(session.OrderedKeys, "id_currency", 6)
if !session.IsLoggedIn {
if checksum := anonymousSessionChecksum(session, sessionLanguageID(session)); checksum != "" {
session.Values["checksum"] = checksum
session.OrderedKeys = ensureOrderedKey(session.OrderedKeys, "checksum", len(session.OrderedKeys))
}
}
session.Plaintext = ""
session.RawCookie = ""
}
func sessionLanguageID(session *pscookie.SessionContext) int64 {
if session == nil || session.LanguageID == nil {
return 0
@@ -237,6 +292,67 @@ func int64Ptr(value int64) *int64 {
return &v
}
func requestMarketSelection(req *http.Request) marketSelection {
if req == nil || req.URL == nil {
return marketSelection{}
}
raw := strings.TrimSpace(req.URL.Query().Get("market"))
if raw == "" {
return marketSelection{}
}
parts := strings.Split(raw, ":")
if len(parts) != 2 && len(parts) != 3 {
return marketSelection{}
}
selection := marketSelection{}
var countryISO string
var currencyValue string
if len(parts) == 3 {
countryID, err := strconv.ParseInt(strings.TrimSpace(parts[0]), 10, 64)
if err != nil || countryID == 0 {
return marketSelection{}
}
selection.CountryID = countryID
countryISO = strings.ToUpper(strings.TrimSpace(parts[1]))
currencyValue = parts[2]
} else {
countryISO = strings.ToUpper(strings.TrimSpace(parts[0]))
currencyValue = parts[1]
}
currencyID, err := strconv.ParseInt(strings.TrimSpace(currencyValue), 10, 64)
if err != nil || currencyID == 0 {
return marketSelection{}
}
if len(countryISO) < 2 || len(countryISO) > 5 {
return marketSelection{}
}
selection.CountryISO = countryISO
selection.CurrencyID = currencyID
return selection
}
func clearMarketSelectionURL(req *http.Request) (string, bool) {
if req == nil || req.URL == nil {
return "", false
}
query := req.URL.Query()
if query.Get("market") == "" {
return "", false
}
query.Del("market")
cleanPath := req.URL.Path
if cleanPath == "" {
cleanPath = "/"
}
if encoded := query.Encode(); encoded != "" {
return cleanPath + "?" + encoded, true
}
return cleanPath, true
}
func setPrestaShopCookie(req *http.Request, res *echo.Response, ownedPrefixes []string, name, value string) {
http.SetCookie(res.Writer, &http.Cookie{
Name: name,
@@ -277,7 +393,11 @@ func requestCookieDomain(req *http.Request) string {
return ""
}
if parsed, err := url.Parse("http://" + host); err == nil {
return parsed.Hostname()
host = parsed.Hostname()
}
host = strings.TrimSpace(strings.TrimPrefix(host, "."))
if host == "" || strings.EqualFold(host, "localhost") || net.ParseIP(host) != nil {
return ""
}
return host
}
+260
View File
@@ -55,6 +55,32 @@ type CategoryProductCard struct {
EAN13 string
}
type MenuItem struct {
ID int64
ParentID int64
Name string
Slug string
Depth int
URL string `gorm:"-"`
Children []MenuItem `gorm:"-"`
}
type LocaleOption struct {
ID int64
CurrencyID int64 `gorm:"column:currency_id"`
Label string
Code string
Meta string
URL string `gorm:"-"`
}
type HeaderLocaleData struct {
CurrentLanguage LocaleOption
CurrentCountry LocaleOption
Languages []LocaleOption
Countries []LocaleOption
}
type Service struct {
db *gorm.DB
prefix string
@@ -239,3 +265,237 @@ func (s *Service) ResolveLanguageID(ctx context.Context, req *http.Request, fall
}
return row.ID
}
func (s *Service) GetCategoryMenu(ctx context.Context, languageID int64, shopID int64) ([]MenuItem, error) {
rootCategoryID, err := s.rootCategoryID(ctx)
if err != nil {
return nil, err
}
query := fmt.Sprintf(`
WITH RECURSIVE category_tree AS (
SELECT c.id_category AS id,
c.id_parent AS parent_id,
cl.name AS name,
cl.link_rewrite AS slug,
0 AS depth
FROM %scategory c
JOIN %scategory_shop cs ON cs.id_category = c.id_category
JOIN %scategory_lang cl ON cl.id_category = c.id_category
WHERE c.id_parent = ?
AND c.active = 1
AND cs.id_shop = ?
AND cl.id_lang = ?
UNION ALL
SELECT c.id_category AS id,
c.id_parent AS parent_id,
cl.name AS name,
cl.link_rewrite AS slug,
tree.depth + 1 AS depth
FROM %scategory c
JOIN %scategory_shop cs ON cs.id_category = c.id_category
JOIN %scategory_lang cl ON cl.id_category = c.id_category
JOIN category_tree tree ON tree.id = c.id_parent
WHERE c.active = 1
AND cs.id_shop = ?
AND cl.id_lang = ?
)
SELECT id, parent_id, name, slug, depth
FROM category_tree
ORDER BY depth ASC, parent_id ASC, name ASC
`, s.prefix, s.prefix, s.prefix, s.prefix, s.prefix, s.prefix)
var flat []MenuItem
if err := s.db.WithContext(ctx).Raw(strings.TrimSpace(query), rootCategoryID, shopID, languageID, shopID, languageID).Scan(&flat).Error; err != nil {
return nil, err
}
if len(flat) == 0 {
return nil, nil
}
nodes := make(map[int64]MenuItem, len(flat))
childrenByParent := make(map[int64][]int64, len(flat))
for i := range flat {
item := flat[i]
nodes[item.ID] = item
childrenByParent[item.ParentID] = append(childrenByParent[item.ParentID], item.ID)
}
rootIDs := childrenByParent[rootCategoryID]
roots := make([]MenuItem, 0, len(rootIDs))
for _, id := range rootIDs {
if item, ok := buildMenuTree(id, nodes, childrenByParent); ok {
roots = append(roots, item)
}
}
return roots, nil
}
func (s *Service) GetHeaderLocale(ctx context.Context, languageID int64, currencyID int64, countryISO string) (HeaderLocaleData, error) {
var locale HeaderLocaleData
defaultCurrencyID, err := s.defaultCurrencyID(ctx)
if err != nil {
return HeaderLocaleData{}, err
}
languageQuery := fmt.Sprintf(`
SELECT id_lang AS id,
name AS label,
UPPER(iso_code) AS code,
COALESCE(NULLIF(language_code, ''), UPPER(iso_code)) AS meta
FROM %slang
WHERE active = 1
ORDER BY id_lang ASC
`, s.prefix)
if err := s.db.WithContext(ctx).Raw(strings.TrimSpace(languageQuery)).Scan(&locale.Languages).Error; err != nil {
return HeaderLocaleData{}, err
}
hasCountryCurrency, err := s.columnExists(ctx, s.prefix+"country", "id_currency")
if err != nil {
return HeaderLocaleData{}, err
}
var countryQuery string
var countryArgs []any
if hasCountryCurrency {
countryQuery = fmt.Sprintf(`
SELECT c.id_country AS id,
COALESCE(c.id_currency, ?) AS currency_id,
cl.name AS label,
UPPER(c.iso_code) AS code,
TRIM(CONCAT(COALESCE(UPPER(cur.iso_code), ''), ' ', COALESCE(cur.sign, ''))) AS meta
FROM %scountry c
JOIN %scountry_lang cl ON cl.id_country = c.id_country
LEFT JOIN %scurrency cur ON cur.id_currency = c.id_currency
WHERE c.active = 1
AND cl.id_lang = ?
ORDER BY cl.name ASC
`, s.prefix, s.prefix, s.prefix)
countryArgs = []any{defaultCurrencyID, languageID}
} else {
countryQuery = fmt.Sprintf(`
SELECT c.id_country AS id,
? AS currency_id,
cl.name AS label,
UPPER(c.iso_code) AS code,
UPPER(c.iso_code) AS meta
FROM %scountry c
JOIN %scountry_lang cl ON cl.id_country = c.id_country
WHERE c.active = 1
AND cl.id_lang = ?
ORDER BY cl.name ASC
`, s.prefix, s.prefix)
countryArgs = []any{defaultCurrencyID, languageID}
}
if err := s.db.WithContext(ctx).Raw(strings.TrimSpace(countryQuery), countryArgs...).Scan(&locale.Countries).Error; err != nil {
return HeaderLocaleData{}, err
}
locale.CurrentLanguage = pickLocaleOptionByID(locale.Languages, languageID)
locale.CurrentCountry = pickLocaleOptionByCode(locale.Countries, countryISO)
return locale, nil
}
func (s *Service) rootCategoryID(ctx context.Context) (int64, error) {
var row struct {
Value string `gorm:"column:value"`
}
query := fmt.Sprintf("SELECT value FROM %sconfiguration WHERE name = 'PS_ROOT_CATEGORY' LIMIT 1", s.prefix)
if err := s.db.WithContext(ctx).Raw(query).Scan(&row).Error; err != nil {
return 0, err
}
id := parseInt64(row.Value)
if id == 0 {
return 1, nil
}
return id, nil
}
func (s *Service) defaultCurrencyID(ctx context.Context) (int64, error) {
var row struct {
Value string `gorm:"column:value"`
}
query := fmt.Sprintf("SELECT value FROM %sconfiguration WHERE name = 'PS_CURRENCY_DEFAULT' LIMIT 1", s.prefix)
if err := s.db.WithContext(ctx).Raw(query).Scan(&row).Error; err != nil {
return 0, err
}
id := parseInt64(row.Value)
if id == 0 {
return 1, nil
}
return id, nil
}
func (s *Service) columnExists(ctx context.Context, tableName string, columnName string) (bool, error) {
var count int64
query := `
SELECT COUNT(*)
FROM information_schema.columns
WHERE table_schema = DATABASE()
AND table_name = ?
AND column_name = ?
`
if err := s.db.WithContext(ctx).Raw(strings.TrimSpace(query), tableName, columnName).Scan(&count).Error; err != nil {
return false, err
}
return count > 0, nil
}
func parseInt64(value string) int64 {
var n int64
for _, r := range value {
if r < '0' || r > '9' {
return 0
}
n = n*10 + int64(r-'0')
}
return n
}
func buildMenuTree(id int64, nodes map[int64]MenuItem, childrenByParent map[int64][]int64) (MenuItem, bool) {
item, ok := nodes[id]
if !ok {
return MenuItem{}, false
}
childIDs := childrenByParent[id]
if len(childIDs) == 0 {
return item, true
}
item.Children = make([]MenuItem, 0, len(childIDs))
for _, childID := range childIDs {
child, ok := buildMenuTree(childID, nodes, childrenByParent)
if ok {
item.Children = append(item.Children, child)
}
}
return item, true
}
func pickLocaleOptionByID(options []LocaleOption, id int64) LocaleOption {
for _, option := range options {
if option.ID == id && id != 0 {
return option
}
}
if len(options) > 0 {
return options[0]
}
return LocaleOption{}
}
func pickLocaleOptionByCode(options []LocaleOption, code string) LocaleOption {
code = strings.ToUpper(strings.TrimSpace(code))
for _, option := range options {
if option.Code == code && code != "" {
return option
}
}
if len(options) > 0 {
return options[0]
}
if code == "" {
return LocaleOption{}
}
return LocaleOption{Code: code, Label: code, Meta: code}
}
+1 -1
View File
@@ -13,7 +13,7 @@ import (
"regexp"
"strings"
pscookie "prestaproxy/internal/prestashop/cookie"
pscookie "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/cookie"
)
type Config struct {
+1 -1
View File
@@ -10,7 +10,7 @@ import (
"strings"
"time"
pscookie "prestaproxy/internal/prestashop/cookie"
pscookie "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/cookie"
"gorm.io/gorm"
)
+3 -3
View File
@@ -3,9 +3,9 @@ package render
import (
"net/http"
"prestaproxy/internal/assets"
"prestaproxy/internal/viewmodel"
"prestaproxy/templates"
"git.ma-al.com/goc_marek/ps_shop/internal/assets"
"git.ma-al.com/goc_marek/ps_shop/internal/viewmodel"
"git.ma-al.com/goc_marek/ps_shop/templates"
)
type Engine struct {
+6 -4
View File
@@ -1,14 +1,16 @@
package viewmodel
import (
pscart "prestaproxy/internal/prestashop/cart"
pscatalog "prestaproxy/internal/prestashop/catalog"
pscookie "prestaproxy/internal/prestashop/cookie"
pscustomer "prestaproxy/internal/prestashop/customer"
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"
pscustomer "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/customer"
)
type CategoryPageData struct {
Category pscatalog.CategoryPageData
Menu []pscatalog.MenuItem
Locale pscatalog.HeaderLocaleData
Session *pscookie.SessionContext
Customer *pscustomer.Profile
CartSummary *pscart.Summary
+6 -4
View File
@@ -1,15 +1,17 @@
package viewmodel
import (
pscart "prestaproxy/internal/prestashop/cart"
pscatalog "prestaproxy/internal/prestashop/catalog"
pscookie "prestaproxy/internal/prestashop/cookie"
pscustomer "prestaproxy/internal/prestashop/customer"
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"
pscustomer "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/customer"
)
type ProductPageData struct {
Product pscatalog.ProductPageData
CategoryURL string
Menu []pscatalog.MenuItem
Locale pscatalog.HeaderLocaleData
Session *pscookie.SessionContext
Customer *pscustomer.Profile
CartSummary *pscart.Summary
+3 -3
View File
@@ -1,9 +1,9 @@
{
"name": "prestaproxy",
"name": "ps_shop",
"private": true,
"scripts": {
"build:js": "bun build ./web/src/app.js --outdir ./web/dist --naming app.js",
"build:css": "bunx tailwindcss -i ./web/src/app.css -o ./web/dist/app.css --minify",
"build:js": "bun build ./web/src/app.js --outfile ./web/dist/app.js --target browser --minify",
"build:css": "env BUN_TMPDIR=/tmp bunx tailwindcss -i ./web/src/app.css -o ./web/dist/app.css --minify",
"build:manifest": "bun ./web/write-manifest.mjs",
"build": "bun run build:js && bun run build:css && bun run build:manifest",
"dev": "bun run build"
+2 -2
View File
@@ -3,11 +3,11 @@ package templates
import (
"fmt"
"prestaproxy/internal/viewmodel"
"git.ma-al.com/goc_marek/ps_shop/internal/viewmodel"
)
templ CategoryPage(data viewmodel.CategoryPageData, cssPath string, jsPath string) {
@Layout(data.Category.Name, cssPath, jsPath) {
@Layout(data.Category.Name, cssPath, jsPath, data.Menu, data.Locale) {
<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">
+2 -2
View File
@@ -11,7 +11,7 @@ import templruntime "github.com/a-h/templ/runtime"
import (
"fmt"
"prestaproxy/internal/viewmodel"
"git.ma-al.com/goc_marek/ps_shop/internal/viewmodel"
)
func CategoryPage(data viewmodel.CategoryPageData, cssPath string, jsPath string) templ.Component {
@@ -187,7 +187,7 @@ func CategoryPage(data viewmodel.CategoryPageData, cssPath string, jsPath string
}
return nil
})
templ_7745c5c3_Err = Layout(data.Category.Name, cssPath, jsPath).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
templ_7745c5c3_Err = Layout(data.Category.Name, cssPath, jsPath, data.Menu, data.Locale).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
+180 -3
View File
@@ -1,8 +1,10 @@
package templates
templ Layout(title string, cssPath string, jsPath string) {
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) {
<!doctype html>
<html lang="en">
<html lang={ pageLanguage(locale) }>
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
@@ -10,8 +12,183 @@ templ Layout(title string, cssPath string, jsPath string) {
<link rel="stylesheet" href={ cssPath }/>
<script type="module" src={ jsPath } defer></script>
</head>
<body class="bg-stone-950 text-stone-100 antialiased">
<body class="min-h-screen text-stone-900 antialiased">
<header class="site-header">
<div class="utility-bar">
<div class="site-container flex flex-wrap items-center justify-between gap-3 py-3 text-[0.92rem] text-stone-700">
<div class="flex flex-wrap items-center gap-x-6 gap-y-2">
<a class="inline-flex items-center gap-2 transition hover:text-amber-600" href="mailto:info@9b-plus.com">
<span class="text-base">✉</span>
<span>info@9b-plus.com</span>
</a>
<a class="inline-flex items-center gap-2 transition hover:text-amber-600" href="tel:+420533312341">
<span class="text-base">☎</span>
<span>+420 533 312 341</span>
</a>
</div>
if hasHeaderLocale(locale) {
<div class="header-locale">
@LocalePicker("Market", locale.CurrentCountry, locale.Countries, true)
@LocalePicker("Language", locale.CurrentLanguage, locale.Languages, true)
</div>
}
</div>
</div>
<div class="main-nav">
<div class="site-container header-bar">
<a class="brand-mark shrink-0" href="/">
<span class="brand-mark__strong">9b</span>
<span class="brand-mark__accent">plus</span>
</a>
<div class="ml-auto flex items-center gap-3 lg:hidden">
<button class="menu-toggle" type="button" aria-expanded="false" aria-controls="site-menu" data-menu-toggle>
Menu
</button>
</div>
if len(menu) > 0 {
<nav class="menu-panel hidden basis-full lg:ml-10 lg:flex lg:min-w-0 lg:flex-1 lg:basis-auto lg:items-center lg:justify-center" id="site-menu" data-menu-panel>
<div class="w-full lg:hidden">
@MenuTree(menu, 0)
</div>
<div class="hidden w-full lg:block">
@MegaMenuBar(menu)
</div>
</nav>
}
<div class="header-actions">
<a class="nav-icon" href="#" aria-label="Search">⌕</a>
<a class="nav-icon" href="#" aria-label="Cart">🛒</a>
</div>
</div>
</div>
</header>
{ children... }
</body>
</html>
}
templ LocalePicker(title string, current pscatalog.LocaleOption, options []pscatalog.LocaleOption, navigable bool) {
<details class="locale-picker">
<summary class="locale-picker__summary">
<span class="locale-picker__value">
if current.Label != "" {
{ current.Label }
} else {
{ title }
}
</span>
if current.Code != "" {
<span class="locale-picker__code">{ current.Code }</span>
}
<span class="locale-picker__chevron">⌄</span>
</summary>
if len(options) > 0 {
<div class="locale-picker__panel">
<p class="locale-picker__title">{ title }</p>
<ul class="space-y-1">
for _, option := range options {
<li>
if navigable && option.URL != "" {
<a class={ localeOptionClass(option, current) } href={ option.URL }>
<span>{ option.Label }</span>
if option.Meta != "" {
<span class="locale-picker__item-meta">{ option.Meta }</span>
} else if option.Code != "" {
<span class="locale-picker__item-meta">{ option.Code }</span>
}
</a>
} else {
<span class={ localeOptionClass(option, current) }>
<span>{ option.Label }</span>
if option.Meta != "" {
<span class="locale-picker__item-meta">{ option.Meta }</span>
}
</span>
}
</li>
}
</ul>
</div>
}
</details>
}
templ MenuTree(items []pscatalog.MenuItem, depth int) {
<ul class={ menuListClass(depth) }>
for _, item := range items {
<li class={ menuItemClass(depth, len(item.Children) > 0) }>
<a class={ menuLinkClass(depth) } href={ item.URL }>
{ item.Name }
if depth == 0 && len(item.Children) > 0 {
<span class="text-[0.65rem] text-amber-300/80">+</span>
}
</a>
if len(item.Children) > 0 {
@MenuTree(item.Children, depth + 1)
}
</li>
}
</ul>
}
templ MegaMenuBar(items []pscatalog.MenuItem) {
<ul class="desktop-nav">
for _, item := range items {
<li class={ desktopNavItemClass(len(item.Children) > 0) }>
if len(item.Children) > 0 {
<div class="desktop-nav__item">
<a
class={ desktopNavLinkClass() }
href={ item.URL }
aria-label={ "Open " + item.Name + " submenu" }
aria-expanded="false"
aria-controls={ menuPanelID(item.ID) }
data-mega-trigger
data-mega-target={ menuPanelID(item.ID) }>{ item.Name }</a>
<button
class="desktop-nav__toggle"
type="button"
aria-label={ "Open " + item.Name + " submenu" }
aria-expanded="false"
aria-controls={ menuPanelID(item.ID) }
data-mega-trigger
data-mega-target={ menuPanelID(item.ID) }>
</button>
</div>
} else {
<a class={ desktopNavLinkClass() } href={ item.URL }>{ item.Name }</a>
}
if len(item.Children) > 0 {
@MegaMenu(item.ID, item.URL, item.Name, item.Children)
}
</li>
}
</ul>
}
templ MegaMenu(id int64, href string, label string, columns []pscatalog.MenuItem) {
<div class="mega-menu hidden" id={ menuPanelID(id) } data-mega-menu>
<div class="mega-menu__grid">
for _, column := range columns {
<div class="mega-menu__column">
<a class="mega-menu__heading" href={ column.URL }>{ column.Name }</a>
if len(column.Children) > 0 {
<ul class="space-y-3">
for _, child := range column.Children {
<li>
<a class="mega-menu__link" href={ child.URL }>{ child.Name }</a>
</li>
}
</ul>
} else {
<a class="mega-menu__link" href={ column.URL }>View all</a>
}
</div>
}
<div class="mega-menu__all">
<a class="mega-menu__all-link" href={ href }>{ label }</a>
</div>
</div>
</div>
}
+78
View File
@@ -0,0 +1,78 @@
package templates
import (
"strconv"
"strings"
pscatalog "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/catalog"
)
func menuListClass(depth int) string {
if depth == 0 {
return "flex min-w-0 flex-col gap-2 text-sm"
}
return "mt-2 space-y-2 border-l border-stone-200 pl-4 text-sm"
}
func menuLinkClass(depth int) string {
if depth == 0 {
return "inline-flex items-center gap-2 px-2 py-2 text-[1.02rem] font-medium text-stone-900 transition hover:text-amber-600"
}
return "inline-flex items-center gap-2 text-stone-700 transition hover:text-amber-600"
}
func menuItemClass(depth int, hasChildren bool) string {
if depth == 0 && hasChildren {
return "min-w-0"
}
return "min-w-0"
}
func desktopNavItemClass(hasChildren bool) string {
if hasChildren {
return "desktop-nav__entry desktop-nav__entry--has-children"
}
return "desktop-nav__entry"
}
func desktopNavLinkClass() string {
return "desktop-nav__link"
}
func hasHeaderLocale(locale pscatalog.HeaderLocaleData) bool {
return len(locale.Languages) > 0 || len(locale.Countries) > 0
}
func pageLanguage(locale pscatalog.HeaderLocaleData) string {
code := strings.TrimSpace(locale.CurrentLanguage.Code)
if code == "" {
return "en"
}
return strings.ToLower(code)
}
func localeOptionClass(option pscatalog.LocaleOption, current pscatalog.LocaleOption) string {
base := "locale-picker__item"
if option.ID != 0 && option.ID == current.ID {
return base + " locale-picker__item--active"
}
if option.Code != "" && option.Code == current.Code {
return base + " locale-picker__item--active"
}
return base
}
func productInitial(name string) string {
name = strings.TrimSpace(name)
if name == "" {
return "P"
}
return strings.ToUpper(name[:1])
}
func menuPanelID(id int64) string {
if id <= 0 {
return "mega-menu-panel"
}
return "mega-menu-panel-" + strconv.FormatInt(id, 10)
}
+900 -14
View File
@@ -8,7 +8,9 @@ package templates
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func Layout(title string, cssPath string, jsPath string) templ.Component {
import pscatalog "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/catalog"
func Layout(title string, cssPath string, jsPath string, menu []pscatalog.MenuItem, locale pscatalog.HeaderLocaleData) 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 {
@@ -29,46 +31,107 @@ func Layout(title string, cssPath string, jsPath string) templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><title>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title)
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(pageLanguage(locale))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 9, Col: 17}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 7, Col: 34}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</title><link rel=\"stylesheet\" href=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><title>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 templ.SafeURL
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinURLErrs(cssPath)
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 10, Col: 40}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 11, Col: 17}
}
_, 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, 3, "\"><script type=\"module\" src=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</title><link rel=\"stylesheet\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(jsPath)
var templ_7745c5c3_Var4 templ.SafeURL
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinURLErrs(cssPath)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 11, Col: 37}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 12, Col: 40}
}
_, 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, 4, "\" defer></script></head><body class=\"bg-stone-950 text-stone-100 antialiased\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\"><script type=\"module\" src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(jsPath)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 13, Col: 37}
}
_, 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, "\" defer></script></head><body class=\"min-h-screen text-stone-900 antialiased\"><header class=\"site-header\"><div class=\"utility-bar\"><div class=\"site-container flex flex-wrap items-center justify-between gap-3 py-3 text-[0.92rem] text-stone-700\"><div class=\"flex flex-wrap items-center gap-x-6 gap-y-2\"><a class=\"inline-flex items-center gap-2 transition hover:text-amber-600\" href=\"mailto:info@9b-plus.com\"><span class=\"text-base\">✉</span> <span>info@9b-plus.com</span></a> <a class=\"inline-flex items-center gap-2 transition hover:text-amber-600\" href=\"tel:+420533312341\"><span class=\"text-base\">☎</span> <span>+420 533 312 341</span></a></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if hasHeaderLocale(locale) {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<div class=\"header-locale\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = LocalePicker("Market", locale.CurrentCountry, locale.Countries, true).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = LocalePicker("Language", locale.CurrentLanguage, locale.Languages, true).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</div></div><div class=\"main-nav\"><div class=\"site-container header-bar\"><a class=\"brand-mark shrink-0\" href=\"/\"><span class=\"brand-mark__strong\">9b</span> <span class=\"brand-mark__accent\">plus</span></a><div class=\"ml-auto flex items-center gap-3 lg:hidden\"><button class=\"menu-toggle\" type=\"button\" aria-expanded=\"false\" aria-controls=\"site-menu\" data-menu-toggle>Menu</button></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if len(menu) > 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<nav class=\"menu-panel hidden basis-full lg:ml-10 lg:flex lg:min-w-0 lg:flex-1 lg:basis-auto lg:items-center lg:justify-center\" id=\"site-menu\" data-menu-panel><div class=\"w-full lg:hidden\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = MenuTree(menu, 0).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</div><div class=\"hidden w-full lg:block\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = MegaMenuBar(menu).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</div></nav>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<div class=\"header-actions\"><a class=\"nav-icon\" href=\"#\" aria-label=\"Search\">⌕</a> <a class=\"nav-icon\" href=\"#\" aria-label=\"Cart\">🛒</a></div></div></div></header>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -76,7 +139,830 @@ func Layout(title string, cssPath string, jsPath string) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</body></html>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func LocalePicker(title string, current pscatalog.LocaleOption, options []pscatalog.LocaleOption, navigable bool) 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_Var6 := templ.GetChildren(ctx)
if templ_7745c5c3_Var6 == nil {
templ_7745c5c3_Var6 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<details class=\"locale-picker\"><summary class=\"locale-picker__summary\"><span class=\"locale-picker__value\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if current.Label != "" {
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(current.Label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 75, Col: 20}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 77, Col: 12}
}
_, 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, "</span> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if current.Code != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<span class=\"locale-picker__code\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(current.Code)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 81, Col: 52}
}
_, 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, "<span class=\"locale-picker__chevron\">⌄</span></summary> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if len(options) > 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<div class=\"locale-picker__panel\"><p class=\"locale-picker__title\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 87, Col: 43}
}
_, 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, "</p><ul class=\"space-y-1\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, option := range options {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if navigable && option.URL != "" {
var templ_7745c5c3_Var11 = []any{localeOptionClass(option, current)}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var11...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<a class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var11).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 1, Col: 0}
}
_, 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, "\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 templ.SafeURL
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinURLErrs(option.URL)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 92, Col: 73}
}
_, 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, 24, "\"><span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(option.Label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 93, Col: 29}
}
_, 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, 25, "</span> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if option.Meta != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<span class=\"locale-picker__item-meta\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(option.Meta)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 95, Col: 62}
}
_, 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>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else if option.Code != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<span class=\"locale-picker__item-meta\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(option.Code)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 97, Col: 62}
}
_, 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, "</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
var templ_7745c5c3_Var17 = []any{localeOptionClass(option, current)}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var17...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<span class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var17).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 1, Col: 0}
}
_, 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, "\"><span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var19 string
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(option.Label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 102, Col: 29}
}
_, 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, "</span> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if option.Meta != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<span class=\"locale-picker__item-meta\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var20 string
templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(option.Meta)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 104, Col: 62}
}
_, 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, 35, "</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "</li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "</ul></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "</details>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func MenuTree(items []pscatalog.MenuItem, depth int) 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_Var21 := templ.GetChildren(ctx)
if templ_7745c5c3_Var21 == nil {
templ_7745c5c3_Var21 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
var templ_7745c5c3_Var22 = []any{menuListClass(depth)}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var22...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "<ul class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var23 string
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var22).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 1, Col: 0}
}
_, 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, 41, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, item := range items {
var templ_7745c5c3_Var24 = []any{menuItemClass(depth, len(item.Children) > 0)}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var24...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "<li class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var25 string
templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var24).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 1, Col: 0}
}
_, 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, 43, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var26 = []any{menuLinkClass(depth)}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var26...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "<a class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var27 string
templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var26).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 1, Col: 0}
}
_, 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, 45, "\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var28 templ.SafeURL
templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinURLErrs(item.URL)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 120, Col: 53}
}
_, 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, 46, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var29 string
templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(item.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 121, Col: 16}
}
_, 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, 47, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if depth == 0 && len(item.Children) > 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "<span class=\"text-[0.65rem] text-amber-300/80\">+</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "</a> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if len(item.Children) > 0 {
templ_7745c5c3_Err = MenuTree(item.Children, depth+1).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "</li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "</ul>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func MegaMenuBar(items []pscatalog.MenuItem) 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_Var30 := templ.GetChildren(ctx)
if templ_7745c5c3_Var30 == nil {
templ_7745c5c3_Var30 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "<ul class=\"desktop-nav\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, item := range items {
var templ_7745c5c3_Var31 = []any{desktopNavItemClass(len(item.Children) > 0)}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var31...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "<li class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var32 string
templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var31).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 1, Col: 0}
}
_, 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, 54, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if len(item.Children) > 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "<div class=\"desktop-nav__item\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var33 = []any{desktopNavLinkClass()}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var33...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "<a class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var34 string
templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var33).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 1, Col: 0}
}
_, 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, 57, "\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var35 templ.SafeURL
templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinURLErrs(item.URL)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 142, Col: 22}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "\" aria-label=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var36 string
templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs("Open " + item.Name + " submenu")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 143, Col: 52}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "\" aria-expanded=\"false\" aria-controls=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var37 string
templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(menuPanelID(item.ID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 145, Col: 43}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "\" data-mega-trigger data-mega-target=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var38 string
templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(menuPanelID(item.ID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 147, Col: 46}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var39 string
templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinStringErrs(item.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 147, Col: 60}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "</a> <button class=\"desktop-nav__toggle\" type=\"button\" aria-label=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var40 string
templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinStringErrs("Open " + item.Name + " submenu")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 151, Col: 52}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "\" aria-expanded=\"false\" aria-controls=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var41 string
templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.JoinStringErrs(menuPanelID(item.ID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 153, Col: 43}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var41))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "\" data-mega-trigger data-mega-target=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var42 string
templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.JoinStringErrs(menuPanelID(item.ID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 155, Col: 46}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var42))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "\">⌄</button></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
var templ_7745c5c3_Var43 = []any{desktopNavLinkClass()}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var43...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "<a class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var44 string
templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var43).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var44))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var45 templ.SafeURL
templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.JoinURLErrs(item.URL)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 160, Col: 55}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var45))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var46 string
templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.JoinStringErrs(item.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 160, Col: 69}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var46))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "</a> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if len(item.Children) > 0 {
templ_7745c5c3_Err = MegaMenu(item.ID, item.URL, item.Name, item.Children).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "</li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "</ul>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func MegaMenu(id int64, href string, label string, columns []pscatalog.MenuItem) 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_Var47 := templ.GetChildren(ctx)
if templ_7745c5c3_Var47 == nil {
templ_7745c5c3_Var47 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "<div class=\"mega-menu hidden\" id=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var48 string
templ_7745c5c3_Var48, templ_7745c5c3_Err = templ.JoinStringErrs(menuPanelID(id))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 171, Col: 51}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var48))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "\" data-mega-menu><div class=\"mega-menu__grid\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, column := range columns {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "<div class=\"mega-menu__column\"><a class=\"mega-menu__heading\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var49 templ.SafeURL
templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.JoinURLErrs(column.URL)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 175, Col: 52}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var49))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var50 string
templ_7745c5c3_Var50, templ_7745c5c3_Err = templ.JoinStringErrs(column.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 175, Col: 68}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var50))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "</a> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if len(column.Children) > 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "<ul class=\"space-y-3\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, child := range column.Children {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "<li><a class=\"mega-menu__link\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var51 templ.SafeURL
templ_7745c5c3_Var51, templ_7745c5c3_Err = templ.JoinURLErrs(child.URL)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 180, Col: 52}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var51))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var52 string
templ_7745c5c3_Var52, templ_7745c5c3_Err = templ.JoinStringErrs(child.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 180, Col: 67}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var52))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, "</a></li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, "</ul>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "<a class=\"mega-menu__link\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var53 templ.SafeURL
templ_7745c5c3_Var53, templ_7745c5c3_Err = templ.JoinURLErrs(column.URL)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 185, Col: 50}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var53))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 83, "\">View all</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "<div class=\"mega-menu__all\"><a class=\"mega-menu__all-link\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var54 templ.SafeURL
templ_7745c5c3_Var54, templ_7745c5c3_Err = templ.JoinURLErrs(href)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 190, Col: 46}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var54))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 86, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var55 string
templ_7745c5c3_Var55, templ_7745c5c3_Err = templ.JoinStringErrs(label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/layout.templ`, Line: 190, Col: 56}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var55))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "</a></div></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
+2 -2
View File
@@ -3,11 +3,11 @@ package templates
import (
"fmt"
"prestaproxy/internal/viewmodel"
"git.ma-al.com/goc_marek/ps_shop/internal/viewmodel"
)
templ ProductPage(data viewmodel.ProductPageData, cssPath string, jsPath string) {
@Layout(data.Product.Name, cssPath, jsPath) {
@Layout(data.Product.Name, cssPath, jsPath, data.Menu, data.Locale) {
<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">
<header class="flex items-center justify-between border-b border-stone-800 pb-6">
+2 -2
View File
@@ -11,7 +11,7 @@ import templruntime "github.com/a-h/templ/runtime"
import (
"fmt"
"prestaproxy/internal/viewmodel"
"git.ma-al.com/goc_marek/ps_shop/internal/viewmodel"
)
func ProductPage(data viewmodel.ProductPageData, cssPath string, jsPath string) templ.Component {
@@ -235,7 +235,7 @@ func ProductPage(data viewmodel.ProductPageData, cssPath string, jsPath string)
}
return nil
})
templ_7745c5c3_Err = Layout(data.Product.Name, cssPath, jsPath).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
templ_7745c5c3_Err = Layout(data.Product.Name, cssPath, jsPath, data.Menu, data.Locale).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
+1 -1
View File
@@ -1 +1 @@
exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1
exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1
BIN
View File
Binary file not shown.
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
document.documentElement.dataset.js="ready";
var l=document.documentElement;l.dataset.js="ready";var a=document.querySelector("[data-menu-toggle]"),d=document.querySelector("[data-menu-panel]"),r=[...document.querySelectorAll("[data-mega-trigger]")],c=[...document.querySelectorAll("[data-mega-menu]")];if(a&&d){let t=()=>{d.classList.add("hidden"),a.setAttribute("aria-expanded","false")},i=()=>{d.classList.remove("hidden"),a.setAttribute("aria-expanded","true")};a.addEventListener("click",()=>{if(a.getAttribute("aria-expanded")==="true"){t();return}i()}),window.addEventListener("resize",()=>{if(window.innerWidth>=1024){d.classList.remove("hidden"),a.setAttribute("aria-expanded","true");return}t()})}if(r.length>0&&c.length>0){let t=()=>{r.forEach((e)=>{e.setAttribute("aria-expanded","false"),e.closest(".desktop-nav__entry")?.classList.remove("desktop-nav__entry--open")}),c.forEach((e)=>{e.classList.add("hidden")})},i=(e)=>{let n=e.dataset.megaTarget;if(!n)return;t(),e.setAttribute("aria-expanded","true"),e.closest(".desktop-nav__entry")?.classList.add("desktop-nav__entry--open"),document.getElementById(n)?.classList.remove("hidden")};r.forEach((e)=>{e.addEventListener("click",(n)=>{if(window.innerWidth<1024)return;let s=e.getAttribute("aria-expanded")==="true";if(e instanceof HTMLAnchorElement&&s)return;if(n.preventDefault(),s){t();return}i(e)})}),document.addEventListener("click",(e)=>{if(window.innerWidth<1024)return;let n=e.target;if(!(n instanceof Node))return;let s=r.some((o)=>o.contains(n)),m=c.some((o)=>o.contains(n));if(!s&&!m)t()}),document.addEventListener("keydown",(e)=>{if(e.key==="Escape")t()}),window.addEventListener("resize",()=>{if(window.innerWidth<1024)t()})}var u=document.querySelector("button[type='submit']");if(u)u.addEventListener("mouseenter",()=>{l.style.setProperty("--cta-glow","0 0 0 4px rgba(252, 211, 77, 0.12)")}),u.addEventListener("mouseleave",()=>{l.style.setProperty("--cta-glow","0 0 0 0 rgba(0,0,0,0)")});
+1 -1
View File
@@ -1,4 +1,4 @@
{
"app.css": "/dist/app.css",
"app.js": "/dist/app.js"
}
}
+158 -3
View File
@@ -8,15 +8,170 @@
}
body {
font-family: "IBM Plex Sans", sans-serif;
font-family:
"IBM Plex Sans",
"Avenir Next",
"Segoe UI",
sans-serif;
background:
radial-gradient(circle at top left, rgba(241, 196, 110, 0.24), transparent 28%),
radial-gradient(circle at top right, rgba(157, 217, 210, 0.16), transparent 34%),
linear-gradient(180deg, #fbfaf6 0%, #f5efe3 55%, #f7f3ea 100%);
@apply text-stone-900;
}
h1, h2, h3 {
font-family: "Cormorant Garamond", serif;
h1,
h2,
h3 {
font-family:
"Cormorant Garamond",
"IBM Plex Sans",
serif;
}
}
@layer components {
.site-container {
@apply mx-auto w-full max-w-7xl px-4 sm:px-5 lg:px-8;
}
.site-header {
@apply sticky top-0 z-40 backdrop-blur;
}
.utility-bar {
background: linear-gradient(90deg, rgba(20, 33, 61, 0.98), rgba(37, 58, 89, 0.94));
@apply border-b border-white/10 text-stone-100;
}
.header-locale {
@apply flex w-full flex-wrap items-center justify-start gap-2 text-sm text-stone-100 sm:w-auto sm:justify-end;
}
.locale-picker {
@apply relative w-full sm:w-auto;
}
.locale-picker__summary {
@apply flex w-full cursor-pointer list-none items-center justify-between gap-2 rounded-full border border-white/0 px-3 py-1.5 text-stone-100 transition hover:border-white/20 hover:bg-white/10 hover:text-white sm:w-auto sm:justify-start;
}
.locale-picker[open] .locale-picker__summary {
@apply border-white/20 bg-white/10 text-white;
}
.locale-picker__summary::-webkit-details-marker {
display: none;
}
.locale-picker__value {
@apply font-medium;
}
.locale-picker__code,
.locale-picker__item-meta,
.locale-picker__chevron {
@apply text-[0.74rem] uppercase tracking-[0.16em] text-stone-300/80;
}
.locale-picker__panel {
@apply left-0 right-0 z-50 mt-3 min-w-[13rem] rounded-3xl border border-white/70 bg-white/95 p-3 text-stone-900 shadow-[0_22px_48px_rgba(20,33,61,0.18)] backdrop-blur sm:absolute sm:left-auto sm:right-0;
}
.locale-picker__title {
@apply mb-2 px-2 text-[0.68rem] font-semibold uppercase tracking-[0.2em] text-stone-400;
}
.locale-picker__item {
@apply flex w-full items-center justify-between rounded-2xl px-3 py-2 text-sm text-stone-700 transition hover:bg-stone-100 hover:text-stone-900;
}
.locale-picker__item--active {
@apply bg-stone-100 font-medium text-stone-900;
}
.main-nav {
background: rgba(255, 251, 245, 0.82);
@apply border-b border-white/60 shadow-[0_10px_30px_rgba(20,33,61,0.08)];
}
.header-bar {
@apply flex flex-wrap items-center gap-4 py-4 sm:gap-5 sm:py-5 lg:flex-nowrap;
}
.brand-mark {
@apply inline-flex items-end gap-0.5 text-4xl font-black leading-none tracking-tight text-black transition-transform duration-300 hover:-translate-y-0.5 sm:text-5xl;
}
.brand-mark__accent {
@apply text-amber-500;
}
.menu-toggle {
@apply inline-flex h-11 items-center justify-center rounded-full border border-stone-300/70 bg-white/80 px-5 text-xs font-semibold uppercase tracking-[0.28em] text-stone-900 transition hover:border-amber-500 hover:text-amber-600;
}
.menu-panel {
@apply order-last w-full rounded-3xl border border-white/70 bg-white/95 p-4 shadow-[0_16px_34px_rgba(20,33,61,0.12)] backdrop-blur lg:order-none lg:rounded-none lg:border-0 lg:bg-transparent lg:p-0 lg:shadow-none;
}
.desktop-nav {
@apply flex items-center justify-center gap-8 xl:gap-12;
}
.desktop-nav__link {
@apply inline-flex items-center py-4 text-[1.04rem] font-medium text-stone-800 transition hover:text-amber-600;
}
.desktop-nav__toggle {
@apply inline-flex h-8 w-5 items-center justify-center text-sm text-stone-400 transition hover:text-amber-600;
}
.desktop-nav__entry--open .desktop-nav__link,
.desktop-nav__entry--open .desktop-nav__toggle {
@apply text-amber-600;
}
.mega-menu {
background: linear-gradient(180deg, rgba(255, 252, 247, 0.98), rgba(252, 248, 240, 0.98));
@apply absolute inset-x-0 top-full z-50 border-t border-white/70 shadow-[0_28px_60px_rgba(20,33,61,0.16)] backdrop-blur;
}
.mega-menu__grid {
@apply site-container grid grid-cols-2 gap-x-12 gap-y-10 py-10 lg:grid-cols-4 xl:grid-cols-5;
}
.mega-menu__heading {
@apply mb-4 inline-flex text-sm font-semibold uppercase tracking-[0.12em] text-stone-900 transition hover:text-amber-600;
}
.mega-menu__link {
@apply inline-flex text-[1.02rem] leading-7 text-stone-700 transition hover:translate-x-1 hover:text-amber-600;
}
.mega-menu__all {
@apply col-span-full flex items-start justify-end border-t border-stone-200 pt-5;
}
.mega-menu__all-link {
@apply inline-flex text-sm font-semibold uppercase tracking-[0.18em] text-amber-600 transition hover:text-amber-700;
}
.header-actions {
@apply ml-0 flex items-center gap-4 text-2xl text-stone-900 lg:ml-auto;
}
.nav-icon {
@apply flex h-11 w-11 items-center justify-center rounded-full bg-white/70 text-[1.35rem] shadow-[0_10px_24px_rgba(20,33,61,0.08)] transition hover:-translate-y-0.5 hover:text-amber-600;
}
@media (max-width: 1023px) {
.desktop-nav,
.mega-menu {
display: none;
}
}
button[type='submit'] {
box-shadow: var(--cta-glow);
}
+96
View File
@@ -1,6 +1,102 @@
const root = document.documentElement;
root.dataset.js = "ready";
const menuToggle = document.querySelector("[data-menu-toggle]");
const menuPanel = document.querySelector("[data-menu-panel]");
const megaTriggers = [...document.querySelectorAll("[data-mega-trigger]")];
const megaMenus = [...document.querySelectorAll("[data-mega-menu]")];
if (menuToggle && menuPanel) {
const closeMenu = () => {
menuPanel.classList.add("hidden");
menuToggle.setAttribute("aria-expanded", "false");
};
const openMenu = () => {
menuPanel.classList.remove("hidden");
menuToggle.setAttribute("aria-expanded", "true");
};
menuToggle.addEventListener("click", () => {
const expanded = menuToggle.getAttribute("aria-expanded") === "true";
if (expanded) {
closeMenu();
return;
}
openMenu();
});
window.addEventListener("resize", () => {
if (window.innerWidth >= 1024) {
menuPanel.classList.remove("hidden");
menuToggle.setAttribute("aria-expanded", "true");
return;
}
closeMenu();
});
}
if (megaTriggers.length > 0 && megaMenus.length > 0) {
const closeMegaMenus = () => {
megaTriggers.forEach((trigger) => {
trigger.setAttribute("aria-expanded", "false");
trigger.closest(".desktop-nav__entry")?.classList.remove("desktop-nav__entry--open");
});
megaMenus.forEach((menu) => {
menu.classList.add("hidden");
});
};
const openMegaMenu = (trigger) => {
const target = trigger.dataset.megaTarget;
if (!target) return;
closeMegaMenus();
trigger.setAttribute("aria-expanded", "true");
trigger.closest(".desktop-nav__entry")?.classList.add("desktop-nav__entry--open");
document.getElementById(target)?.classList.remove("hidden");
};
megaTriggers.forEach((trigger) => {
trigger.addEventListener("click", (event) => {
if (window.innerWidth < 1024) return;
const expanded = trigger.getAttribute("aria-expanded") === "true";
const isLinkTrigger = trigger instanceof HTMLAnchorElement;
if (isLinkTrigger && expanded) {
return;
}
event.preventDefault();
if (expanded) {
closeMegaMenus();
return;
}
openMegaMenu(trigger);
});
});
document.addEventListener("click", (event) => {
if (window.innerWidth < 1024) return;
const target = event.target;
if (!(target instanceof Node)) return;
const insideTrigger = megaTriggers.some((trigger) => trigger.contains(target));
const insideMenu = megaMenus.some((menu) => menu.contains(target));
if (!insideTrigger && !insideMenu) {
closeMegaMenus();
}
});
document.addEventListener("keydown", (event) => {
if (event.key === "Escape") {
closeMegaMenus();
}
});
window.addEventListener("resize", () => {
if (window.innerWidth < 1024) {
closeMegaMenus();
}
});
}
const cartButton = document.querySelector("button[type='submit']");
if (cartButton) {
cartButton.addEventListener("mouseenter", () => {