Wire the product CTA to store-loan-methods calculate so shoppers can pick a term and see the plan, and render English model runs under Montserrat so fanum fonts stop painting 0-9 as Persian digits.
Co-authored-by: Cursor <cursoragent@cursor.com>
The first row's h-[70px] / lg:h-25 only existed to give the old large
logo room, so they are replaced with pt-5. Logo is h-8 (32px).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Header, footer and og:image now read public/images/logo.png via
LOGO_SRC in config.ts. The API's /website/favicon returned another
business's mark, so getFavicon()/BrandAssets are removed and the tab
icon is the bundled favicon.png. Footer logo is now 70px like the
header (108px was sized for the old square mark; the new 200x48
wordmark would be 450px wide).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Header row height (lg:h-32 -> lg:h-25) and the logo's own height
(h-[108px] -> h-[70px]) both come down together so the row doesn't
carry leftover empty space around the smaller mark. Footer logo is
unchanged (still 108px) — the two are sized independently.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Blog card thumbnails (homepage magazine carousel, /blog listing, and
the "more posts" carousel) are now square instead of 16:9.
- Fallback blog cards (src/data/fallbackBlogs.ts) were previously
plain, non-clickable divs since /blogs is still empty on this
tenant and no real detail page existed for them. They now link to a
real local /blog/{id}/{slug} page via getFallbackBlogById() — image
and title are both inside the link. That page shows the real
title/image/category we have plus a "coming soon" note instead of
fabricating article body content.
- Homepage magazine section's fallback posts now link to their own
detail page instead of generically to /blog.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replaces the previous server-cart implementation (which wrongly called
the customer-dashboard-only /businesses/{id}/cart* API) with the
documented guest-cart architecture:
- src/lib/guestCart.ts: storage key/cookie name, cookie attributes,
line shape, and encodeGuestCart() copied verbatim from AI_PROMPT.md
- CartProvider/CartButton: pure client state (localStorage + mirrored
cookie), no bearer auth, no businessId
- CartButton's "ادامه" builds the exact documented Continue URL,
branching on hasCustomerSessionCookie() to either
customer.<domain>/checkout/cart?guestCart=<encoded> or
.../login?redirect=<encoded checkout path>
- Auth cookie corrected to the real documented name
(meshkee_customer_access_token, possibly chunked) via a shared
reassembleChunkedCookie() helper used by both auth.ts (server) and
authUser.ts (client)
- ProductBuyPanel/product pages switched to the documented
GET .../store-items/by-product/{id} endpoint (getStoreItemByProduct)
- isInStock() treats stockQuantity === null as unlimited stock, not
out-of-stock, per spec
- AGENTS.md updated to describe this architecture and remove the
incorrect server-cart documentation
Manually verified in-browser: badge count, popup lines/qty/remove/
total all update correctly against injected guest-cart data; tsc,
eslint, and next build all pass clean.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Neither workshops nor portfolio is in this tenant's enabledModules
(["products","blog","finance"]) - same reasoning as before, page/nav entry
withheld until either is actually enabled.
Shopping cart (new):
- CartProvider (client context): live cart state, add/update/remove/refresh,
reads AUTH_TOKEN_COOKIE, calls the real /businesses/{id}/cart* endpoints
(bearer-only, no guest cart - by design, not a gap)
- CartButton: header/bottom-nav icon with a live count badge and a popup
preview; "ادامه" hands off to the customer portal's own cart/checkout,
since this storefront has no checkout UI (address/payment) to hand off to
internally - out of scope for this pass
- ProductBuyPanel: quantity stepper, wired to CartProvider.addItem() instead
of its own ad-hoc fetch+token logic, toast on success
- Cart/CartItem types are a best-effort guess at an undocumented response
shape (OpenAPI gives no field-level schema) - unverified against real data
since store-items are still empty; read every field defensively
Store-items pages (new): /store-items (flat listing of purchasable variants,
always priced unlike /products) and /store-items/{id} (detail - no slug,
resolves the variant's productId and reuses the product's own gallery/
description/technical-info/comments/related-products, just with that
variant preselected).
Contact form: now shows a toast + inline success text and resets while
staying on screen, instead of swapping to a separate "thank you" panel.
New shared providers: ToastProvider (useToast) and CartProvider, both
mounted once in layout.tsx.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
.swiper-pagination-bullet{background:#fff} had the same specificity as
swiper's own .swiper-pagination-bullet{background:var(...)} rule and lost
the cascade depending on chunk order, so dots rendered swiper's actual
defaults (black inactive, iOS-blue active) instead of white.
Fix: set swiper's own theming variables (--swiper-pagination-bullet-
inactive-color, --swiper-pagination-color, etc.) scoped to .hero-dots
instead of fighting the background property directly - this is swiper's
documented way to theme bullets and is cascade-order-proof. The active
pill shape (22px, 4px radius) has no variable of its own, so that rule
stays as a plain override, scoped under .hero-dots for higher specificity
instead of !important.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- CategoryCarousel: caption under each circle now comes from the slot
image's own titleFa (as the business typed it) instead of the resolved
category's API name - identity resolution via linkUrl slug is still used
for the href, just not for the visible label
- Replace the hover ring (box-shadow) with a real 1px border - the ring
rendered oddly against Swiper's transformed slide track, looking like it
"leaked" into the row above; a border participates in normal box layout
and doesn't have that problem (border-box sizing means zero layout shift)
- Header/footer logo: h-9 (36px) -> h-[108px], 3x on request; header row
height bumped to fit. Note: the currently-uploaded (wrong-business) logo
has a lot of baked-in padding, so it still reads small even at 3x - that's
the asset, not the sizing
- Re-verified the static-image-slot wiring end to end against the original
spec (url->img, linkUrl->href, aspectRatio from API, placeholder on
empty/fail) - all still holds
Also add a Deployment section to AGENTS.md: this repo's whole point is
CI/CD from origin/main, and local commits sitting unpushed (as 3 commits
were, until now) look indistinguishable from unfixed bugs to whoever is
testing the deployed site.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The homepage category carousel and installments highlights matched
slot.images[i] to categoryList[i] by array index, assuming the business's
CMS upload order lines up with this site's own category sort order. It
doesn't - live data showed a laptop photo landing on "موبایل" and "لوازم
برقی" appearing on the site despite no photo ever being uploaded for it,
purely from index luck. titleFa isn't reliable either: the same laptop image
was titled "تبلت" in the CMS, contradicting its own linkUrl.
Fix: resolve each image's category from the `category` query param on its
own linkUrl (categorySlugFromLinkUrl() in slug.ts, matched against
ApiCategory.slug via the new findNodeBySlug()). The homepage carousel is now
fully slot-driven - it iterates slot.images (in the CMS's own sortOrder) and
simply omits any category with no matching image, per explicit product
direction, rather than falling back to a placeholder. Installments keeps its
curated HIGHLIGHT_IDS list with a placeholder for uncovered categories,
since that page always shows the same fixed set, but now uses the same
identity-based lookup instead of position.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Enlarge category carousel thumbnails (90px -> 104-140px responsive) and
confirm the slider/categories/banner static-image slots are all reading
correctly from the CMS (verified live, not a wiring bug)
- Fix installments page dropping "هندزفری و هدفون": it's a level-3 category,
not level-2, so level2.find() silently missed it - use findNode() over the
whole tree instead
- Extend gen-category-placeholders.mjs to generate a placeholder for every
category at every level (was level-2 only), needed for the fix above
- Add /about, /contact, /installments pages
- About: real copy transcribed from the live moalem.shop theme, real
product photos from the API, text-only brand strip (no scraped logos)
- Contact: ContactForm.tsx posts to the real POST /contact-submissions,
branch cards with key-less Google Maps embeds
- Installments: flat brand-colored hero (no photo/gradient), category
highlights reusing the same categories slot images as the homepage
- Replace the placeholder header/footer phone number with the real one, add
real branch addresses and Instagram/Telegram links (src/data/site.ts),
pulled from the live theme's footer
- Wire logo/favicon to GET /website/favicon (getFavicon() in api.ts) instead
of the static bundled files, so a dashboard upload now shows automatically;
layout.tsx metadata is now generateMetadata() to support this. The tenant's
uploaded logo/favicon currently belongs to a different business - the
wiring is correct and will self-correct once that's fixed in the dashboard
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>