Update homepage static-image slots and customer login links.

Align slot catalog with slider / our-experts / two-service-banner and send guests to customer portal /login.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-10-01 21:23:22 +03:30
co-authored by Cursor
parent 29ff589c5b
commit e7e5381b80
8 changed files with 50 additions and 52 deletions
+5 -5
View File
@@ -3,11 +3,11 @@ Wire this page to clinicsamen.com, static-image slots.
Read http://meshkee.com/docs/website (static-images). Env is already set.
Fetch slots, match by key:
- slider > duplicatable
- 4 people image > 4 slot
- our-services-icon > 4 slot
- two-banner-bg > 2 slot
- slider > duplicatable
- our-experts > duplicatable
- two-service-banner > 2 slot
check the original UI, use name fa, name en and subtex according to it
Use slot.images (all of them if kind=list, first if kind=single).
url → <img src>. linkUrl → wrap in <a>. Empty or fail → keep current placeholder.
@@ -15,4 +15,4 @@ Use slot.aspectRatio from the API. Do not invent upload APIs.
If image size or aspect ratio has changed during development, please update it for statics image reader. Add this as rule
update context md for better use in other Ai and systems - update it on every prompt (If required)
update context md for better use in other Ai and systems - update it on every prompt (If required)
+16 -9
View File
@@ -59,12 +59,13 @@ Helpers: `src/lib/meshkee.ts` → `meshkeeGet`, `tenantPath`, `getTenant`, `getB
- **Note:** Tenant currently has no published workshops in API; homepage/list fall back to static samples in `src/data/site.ts`.
### Auth / customer portal
- Login icon → `https://customer.clinicsamen.com` (`customerPortalUrl` in `src/lib/config.ts`)
- Session: `src/lib/session.tsx` + shared cookies (`src/lib/shared-auth-cookie.ts`)
- Login / user icon → `https://customer.{domain}/login` (`customerPortalUrl` in `src/lib/config.ts` → `customer.clinicsamen.com`)
- Session: `src/lib/session.tsx` + shared cookies (`src/lib/shared-auth-cookie.ts`) — reads `meshkee_customer_access_token` from parent domain
- Profile API proxy: `GET /api/auth/me` → Meshkee `/auth/me`
- Admin handoff: `POST /api/auth/handoff` → Meshkee `/auth/handoff` → `https://business.clinicsamen.com`
- Logged-in header: user name + «عزیز»; dropdown: پروفایل من، خروج، مدیریت وبسایت (admins only)
- Component: `src/components/AccountMenu.tsx`
- Logged-in header: `{firstName lastName} عزیز` via `userDisplayName`
- Dropdown (`AccountMenu`): پروفایل من → `/profile`; مدیریت وبسایت (admins/owners/team only, handoff ticket); خروج (clears shared cookies)
- Component: `src/components/AccountMenu.tsx` (wired in `Header` topbar)
## Static image slots
API: `GET /tenants/{domain}/website/static-images?pageKey=home`
@@ -72,17 +73,23 @@ Catalog for dashboard Refresh: `GET /meshkee/static-image-slots` → `STATIC_IMA
| key | kind | itemCount | aspectRatio | recommendedWidth | homepage section |
| --- | --- | --- | --- | --- | --- |
| `slider` | list | null | `1920:740` | 1180 | Hero slider |
| `4-people-image` | list | 4 | `325:356` | 280 | Home therapists row |
| `our-services-icon` | list | 4 | `1:1` | 110 | Services icons |
| `two-banner-bg` | list | 2 | `540:312` | 560 | Dual promo banners |
| `slider` | list (duplicatable) | null | `1920:740` | 1180 | Hero slider |
| `our-experts` | list (duplicatable) | null | `325:356` | 280 | Home therapists / experts row |
| `two-service-banner` | list | 2 | `540:312` | 560 | Dual promo banners under services |
Field → UI mapping (match original homepage; empty API → local placeholders):
- `slider`: `url` → slide image; `linkUrl` → wrap slide. `titleFa` / `titleEn` / `subtext` not rendered in hero (image-only).
- `our-experts`: `url` → photo; `linkUrl` → card link (fallback `/ourdr`); `titleFa` → name (`h3`); `subtext` → role (`p`). `titleEn` not shown (original therapist cards have no English line).
- `two-service-banner`: `url` → banner background; `linkUrl` → CTA href; `titleFa` → banner title (`h3`). `titleEn` / `subtext` not shown on banners.
Service icons on the homepage stay local (`serviceCards` in `src/data/site.ts`) — not a Meshkee static-image slot.
Rules:
- Use `slot.images` (all if `kind=list`, first if `kind=single`)
- `url` → `<img src>`; `linkUrl` → wrap in link
- Empty / fail → keep local placeholders from `src/data/site.ts` / `public/images/`
- Use `slot.aspectRatio` (via `parseAspectRatio`)
- **If a slot’s on-page size or aspect ratio changes in CSS, update `STATIC_IMAGE_SLOTS` in the same change**
- **If a slot’s on-page size or aspect ratio changes in CSS, update `STATIC_IMAGE_SLOTS` in the same change** (Meshkee reads the catalog as the static-image source of truth)
Helpers: `src/lib/static-images.ts`, `src/components/SlotImage.tsx`, `src/components/SlotLink.tsx`
Home wiring: `src/app/page.tsx`
+16 -17
View File
@@ -27,6 +27,7 @@ export default async function HomePage() {
: getLocalBlogPosts();
const workshops = workshopResult.items;
// slider: titleFa / titleEn / subtext unused in hero UI (image + optional linkUrl only)
const slider = overlaySlotImages(
pickSlot(slots, "slider"),
slides.map((slide, index) => {
@@ -41,8 +42,9 @@ export default async function HomePage() {
"slider",
);
// our-experts: titleFa → name, subtext → role (titleEn not shown in original therapist cards)
const people = overlaySlotImages(
pickSlot(slots, "4-people-image"),
pickSlot(slots, "our-experts"),
homeTherapists.map((person, index) => ({
id: `person-${index}`,
image: person.image,
@@ -50,23 +52,21 @@ export default async function HomePage() {
title: person.name,
text: person.role,
})),
"4-people-image",
"our-experts",
);
const serviceIcons = overlaySlotImages(
pickSlot(slots, "our-services-icon"),
serviceCards.map((service, index) => ({
id: `service-${index}`,
image: service.image,
href: service.href,
title: service.title,
kicker: service.english,
})),
"our-services-icon",
);
// Local service icons (not a Meshkee static-image slot)
const serviceIcons = serviceCards.map((service, index) => ({
id: `service-${index}`,
image: service.image,
href: service.href,
title: service.title,
kicker: service.english,
}));
// two-service-banner: titleFa → banner title (titleEn / subtext not shown in original banner UI)
const banners = overlaySlotImages(
pickSlot(slots, "two-banner-bg"),
pickSlot(slots, "two-service-banner"),
[
{
id: "banner-workshops",
@@ -81,7 +81,7 @@ export default async function HomePage() {
title: "مشاوره فردی",
},
],
"two-banner-bg",
"two-service-banner",
);
return (
@@ -90,8 +90,7 @@ export default async function HomePage() {
<HomeSections
people={people.items}
peopleAspectRatio={people.aspectRatio}
serviceIcons={serviceIcons.items}
serviceIconsAspectRatio={serviceIcons.aspectRatio}
serviceIcons={serviceIcons}
banners={banners.items}
bannersAspectRatio={banners.aspectRatio}
workshops={workshops}
+5 -1
View File
@@ -106,7 +106,11 @@ export default function AccountMenu() {
if (!isAuthenticated || !user) {
return (
<a href={customerPortalUrl} className="account-menu account-menu--login" aria-label="ورود / ثبت‌نام">
<a
href={`${customerPortalUrl}/login`}
className="account-menu account-menu--login"
aria-label="ورود / ثبت‌نام"
>
<UserIcon />
<span>ورود / ثبت‌نام</span>
</a>
+1 -1
View File
@@ -74,7 +74,7 @@ export default function EntityTabs({
<div className="login-prompt">
<p className="login-prompt__title">برای ثبت نظر وارد شوید</p>
<p className="login-prompt__text">نظرات فقط برای کاربران واردشده فعال است.</p>
<a href={customerPortalUrl} className="btn btn-primary">
<a href={`${customerPortalUrl}/login`} className="btn btn-primary">
ورود / ثبت‌نام
</a>
</div>
+1 -4
View File
@@ -14,7 +14,6 @@ type Props = {
people: SlotMediaItem[];
peopleAspectRatio?: string;
serviceIcons: SlotMediaItem[];
serviceIconsAspectRatio?: string;
banners: SlotMediaItem[];
bannersAspectRatio?: string;
workshops: Workshop[];
@@ -25,14 +24,12 @@ export default function HomeSections({
people,
peopleAspectRatio,
serviceIcons,
serviceIconsAspectRatio,
banners,
bannersAspectRatio,
workshops,
articles,
}: Props) {
const peopleRatio = parseAspectRatio(peopleAspectRatio) ?? "325 / 356";
const iconRatio = parseAspectRatio(serviceIconsAspectRatio) ?? "1 / 1";
const bannerRatio = parseAspectRatio(bannersAspectRatio) ?? "540 / 312";
return (
@@ -129,7 +126,7 @@ export default function HomeSections({
<div className="service-grid">
{serviceIcons.map((item) => (
<SlotLink key={item.id} href={item.href || "/#services"} className="service-card">
<div className="service-icon" style={{ aspectRatio: iconRatio }}>
<div className="service-icon" style={{ aspectRatio: "1 / 1" }}>
<SlotImage
src={item.image}
fallbackSrc={item.fallbackSrc}
+1 -1
View File
@@ -92,7 +92,7 @@ export default function WorkshopRegistration({
) : (
<div className="login-prompt login-prompt--inline">
<p className="login-prompt__title">برای ثبت‌نام در کارگاه وارد شوید</p>
<a href={customerPortalUrl} className="btn btn-primary">
<a href={`${customerPortalUrl}/login`} className="btn btn-primary">
ورود / ثبت‌نام
</a>
</div>
+5 -14
View File
@@ -55,26 +55,17 @@ export const STATIC_IMAGE_SLOTS: StaticImageSlotCatalogItem[] = [
recommendedWidth: 1180,
},
{
key: "4-people-image",
label: "Four people images",
key: "our-experts",
label: "Our experts",
kind: "list",
pageKey: "home",
aspectRatio: "325:356",
itemCount: 4,
itemCount: null,
recommendedWidth: 280,
},
{
key: "our-services-icon",
label: "Our services icons",
kind: "list",
pageKey: "home",
aspectRatio: "1:1",
itemCount: 4,
recommendedWidth: 110,
},
{
key: "two-banner-bg",
label: "Two banner backgrounds",
key: "two-service-banner",
label: "Two service banners",
kind: "list",
pageKey: "home",
aspectRatio: "540:312",