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:
co-authored by
Cursor
parent
29ff589c5b
commit
e7e5381b80
@@ -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.
|
Read http://meshkee.com/docs/website (static-images). Env is already set.
|
||||||
|
|
||||||
Fetch slots, match by key:
|
Fetch slots, match by key:
|
||||||
- slider > duplicatable
|
- slider > duplicatable
|
||||||
- 4 people image > 4 slot
|
- our-experts > duplicatable
|
||||||
- our-services-icon > 4 slot
|
- two-service-banner > 2 slot
|
||||||
- two-banner-bg > 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).
|
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.
|
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
|
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
@@ -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`.
|
- **Note:** Tenant currently has no published workshops in API; homepage/list fall back to static samples in `src/data/site.ts`.
|
||||||
|
|
||||||
### Auth / customer portal
|
### Auth / customer portal
|
||||||
- Login icon → `https://customer.clinicsamen.com` (`customerPortalUrl` in `src/lib/config.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`)
|
- 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`
|
- Profile API proxy: `GET /api/auth/me` → Meshkee `/auth/me`
|
||||||
- Admin handoff: `POST /api/auth/handoff` → Meshkee `/auth/handoff` → `https://business.clinicsamen.com`
|
- Admin handoff: `POST /api/auth/handoff` → Meshkee `/auth/handoff` → `https://business.clinicsamen.com`
|
||||||
- Logged-in header: user name + «عزیز»; dropdown: پروفایل من، خروج، مدیریت وبسایت (admins only)
|
- Logged-in header: `{firstName lastName} عزیز` via `userDisplayName`
|
||||||
- Component: `src/components/AccountMenu.tsx`
|
- Dropdown (`AccountMenu`): پروفایل من → `/profile`; مدیریت وبسایت (admins/owners/team only, handoff ticket); خروج (clears shared cookies)
|
||||||
|
- Component: `src/components/AccountMenu.tsx` (wired in `Header` topbar)
|
||||||
|
|
||||||
## Static image slots
|
## Static image slots
|
||||||
API: `GET /tenants/{domain}/website/static-images?pageKey=home`
|
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 |
|
| key | kind | itemCount | aspectRatio | recommendedWidth | homepage section |
|
||||||
| --- | --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- | --- |
|
||||||
| `slider` | list | null | `1920:740` | 1180 | Hero slider |
|
| `slider` | list (duplicatable) | null | `1920:740` | 1180 | Hero slider |
|
||||||
| `4-people-image` | list | 4 | `325:356` | 280 | Home therapists row |
|
| `our-experts` | list (duplicatable) | null | `325:356` | 280 | Home therapists / experts row |
|
||||||
| `our-services-icon` | list | 4 | `1:1` | 110 | Services icons |
|
| `two-service-banner` | list | 2 | `540:312` | 560 | Dual promo banners under services |
|
||||||
| `two-banner-bg` | list | 2 | `540:312` | 560 | Dual promo banners |
|
|
||||||
|
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:
|
Rules:
|
||||||
- Use `slot.images` (all if `kind=list`, first if `kind=single`)
|
- Use `slot.images` (all if `kind=list`, first if `kind=single`)
|
||||||
- `url` → `<img src>`; `linkUrl` → wrap in link
|
- `url` → `<img src>`; `linkUrl` → wrap in link
|
||||||
- Empty / fail → keep local placeholders from `src/data/site.ts` / `public/images/`
|
- Empty / fail → keep local placeholders from `src/data/site.ts` / `public/images/`
|
||||||
- Use `slot.aspectRatio` (via `parseAspectRatio`)
|
- 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`
|
Helpers: `src/lib/static-images.ts`, `src/components/SlotImage.tsx`, `src/components/SlotLink.tsx`
|
||||||
Home wiring: `src/app/page.tsx`
|
Home wiring: `src/app/page.tsx`
|
||||||
|
|||||||
+16
-17
@@ -27,6 +27,7 @@ export default async function HomePage() {
|
|||||||
: getLocalBlogPosts();
|
: getLocalBlogPosts();
|
||||||
const workshops = workshopResult.items;
|
const workshops = workshopResult.items;
|
||||||
|
|
||||||
|
// slider: titleFa / titleEn / subtext unused in hero UI (image + optional linkUrl only)
|
||||||
const slider = overlaySlotImages(
|
const slider = overlaySlotImages(
|
||||||
pickSlot(slots, "slider"),
|
pickSlot(slots, "slider"),
|
||||||
slides.map((slide, index) => {
|
slides.map((slide, index) => {
|
||||||
@@ -41,8 +42,9 @@ export default async function HomePage() {
|
|||||||
"slider",
|
"slider",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// our-experts: titleFa → name, subtext → role (titleEn not shown in original therapist cards)
|
||||||
const people = overlaySlotImages(
|
const people = overlaySlotImages(
|
||||||
pickSlot(slots, "4-people-image"),
|
pickSlot(slots, "our-experts"),
|
||||||
homeTherapists.map((person, index) => ({
|
homeTherapists.map((person, index) => ({
|
||||||
id: `person-${index}`,
|
id: `person-${index}`,
|
||||||
image: person.image,
|
image: person.image,
|
||||||
@@ -50,23 +52,21 @@ export default async function HomePage() {
|
|||||||
title: person.name,
|
title: person.name,
|
||||||
text: person.role,
|
text: person.role,
|
||||||
})),
|
})),
|
||||||
"4-people-image",
|
"our-experts",
|
||||||
);
|
);
|
||||||
|
|
||||||
const serviceIcons = overlaySlotImages(
|
// Local service icons (not a Meshkee static-image slot)
|
||||||
pickSlot(slots, "our-services-icon"),
|
const serviceIcons = serviceCards.map((service, index) => ({
|
||||||
serviceCards.map((service, index) => ({
|
id: `service-${index}`,
|
||||||
id: `service-${index}`,
|
image: service.image,
|
||||||
image: service.image,
|
href: service.href,
|
||||||
href: service.href,
|
title: service.title,
|
||||||
title: service.title,
|
kicker: service.english,
|
||||||
kicker: service.english,
|
}));
|
||||||
})),
|
|
||||||
"our-services-icon",
|
|
||||||
);
|
|
||||||
|
|
||||||
|
// two-service-banner: titleFa → banner title (titleEn / subtext not shown in original banner UI)
|
||||||
const banners = overlaySlotImages(
|
const banners = overlaySlotImages(
|
||||||
pickSlot(slots, "two-banner-bg"),
|
pickSlot(slots, "two-service-banner"),
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
id: "banner-workshops",
|
id: "banner-workshops",
|
||||||
@@ -81,7 +81,7 @@ export default async function HomePage() {
|
|||||||
title: "مشاوره فردی",
|
title: "مشاوره فردی",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"two-banner-bg",
|
"two-service-banner",
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -90,8 +90,7 @@ export default async function HomePage() {
|
|||||||
<HomeSections
|
<HomeSections
|
||||||
people={people.items}
|
people={people.items}
|
||||||
peopleAspectRatio={people.aspectRatio}
|
peopleAspectRatio={people.aspectRatio}
|
||||||
serviceIcons={serviceIcons.items}
|
serviceIcons={serviceIcons}
|
||||||
serviceIconsAspectRatio={serviceIcons.aspectRatio}
|
|
||||||
banners={banners.items}
|
banners={banners.items}
|
||||||
bannersAspectRatio={banners.aspectRatio}
|
bannersAspectRatio={banners.aspectRatio}
|
||||||
workshops={workshops}
|
workshops={workshops}
|
||||||
|
|||||||
@@ -106,7 +106,11 @@ export default function AccountMenu() {
|
|||||||
|
|
||||||
if (!isAuthenticated || !user) {
|
if (!isAuthenticated || !user) {
|
||||||
return (
|
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 />
|
<UserIcon />
|
||||||
<span>ورود / ثبتنام</span>
|
<span>ورود / ثبتنام</span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export default function EntityTabs({
|
|||||||
<div className="login-prompt">
|
<div className="login-prompt">
|
||||||
<p className="login-prompt__title">برای ثبت نظر وارد شوید</p>
|
<p className="login-prompt__title">برای ثبت نظر وارد شوید</p>
|
||||||
<p className="login-prompt__text">نظرات فقط برای کاربران واردشده فعال است.</p>
|
<p className="login-prompt__text">نظرات فقط برای کاربران واردشده فعال است.</p>
|
||||||
<a href={customerPortalUrl} className="btn btn-primary">
|
<a href={`${customerPortalUrl}/login`} className="btn btn-primary">
|
||||||
ورود / ثبتنام
|
ورود / ثبتنام
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ type Props = {
|
|||||||
people: SlotMediaItem[];
|
people: SlotMediaItem[];
|
||||||
peopleAspectRatio?: string;
|
peopleAspectRatio?: string;
|
||||||
serviceIcons: SlotMediaItem[];
|
serviceIcons: SlotMediaItem[];
|
||||||
serviceIconsAspectRatio?: string;
|
|
||||||
banners: SlotMediaItem[];
|
banners: SlotMediaItem[];
|
||||||
bannersAspectRatio?: string;
|
bannersAspectRatio?: string;
|
||||||
workshops: Workshop[];
|
workshops: Workshop[];
|
||||||
@@ -25,14 +24,12 @@ export default function HomeSections({
|
|||||||
people,
|
people,
|
||||||
peopleAspectRatio,
|
peopleAspectRatio,
|
||||||
serviceIcons,
|
serviceIcons,
|
||||||
serviceIconsAspectRatio,
|
|
||||||
banners,
|
banners,
|
||||||
bannersAspectRatio,
|
bannersAspectRatio,
|
||||||
workshops,
|
workshops,
|
||||||
articles,
|
articles,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const peopleRatio = parseAspectRatio(peopleAspectRatio) ?? "325 / 356";
|
const peopleRatio = parseAspectRatio(peopleAspectRatio) ?? "325 / 356";
|
||||||
const iconRatio = parseAspectRatio(serviceIconsAspectRatio) ?? "1 / 1";
|
|
||||||
const bannerRatio = parseAspectRatio(bannersAspectRatio) ?? "540 / 312";
|
const bannerRatio = parseAspectRatio(bannersAspectRatio) ?? "540 / 312";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -129,7 +126,7 @@ export default function HomeSections({
|
|||||||
<div className="service-grid">
|
<div className="service-grid">
|
||||||
{serviceIcons.map((item) => (
|
{serviceIcons.map((item) => (
|
||||||
<SlotLink key={item.id} href={item.href || "/#services"} className="service-card">
|
<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
|
<SlotImage
|
||||||
src={item.image}
|
src={item.image}
|
||||||
fallbackSrc={item.fallbackSrc}
|
fallbackSrc={item.fallbackSrc}
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export default function WorkshopRegistration({
|
|||||||
) : (
|
) : (
|
||||||
<div className="login-prompt login-prompt--inline">
|
<div className="login-prompt login-prompt--inline">
|
||||||
<p className="login-prompt__title">برای ثبتنام در کارگاه وارد شوید</p>
|
<p className="login-prompt__title">برای ثبتنام در کارگاه وارد شوید</p>
|
||||||
<a href={customerPortalUrl} className="btn btn-primary">
|
<a href={`${customerPortalUrl}/login`} className="btn btn-primary">
|
||||||
ورود / ثبتنام
|
ورود / ثبتنام
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -55,26 +55,17 @@ export const STATIC_IMAGE_SLOTS: StaticImageSlotCatalogItem[] = [
|
|||||||
recommendedWidth: 1180,
|
recommendedWidth: 1180,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "4-people-image",
|
key: "our-experts",
|
||||||
label: "Four people images",
|
label: "Our experts",
|
||||||
kind: "list",
|
kind: "list",
|
||||||
pageKey: "home",
|
pageKey: "home",
|
||||||
aspectRatio: "325:356",
|
aspectRatio: "325:356",
|
||||||
itemCount: 4,
|
itemCount: null,
|
||||||
recommendedWidth: 280,
|
recommendedWidth: 280,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "our-services-icon",
|
key: "two-service-banner",
|
||||||
label: "Our services icons",
|
label: "Two service banners",
|
||||||
kind: "list",
|
|
||||||
pageKey: "home",
|
|
||||||
aspectRatio: "1:1",
|
|
||||||
itemCount: 4,
|
|
||||||
recommendedWidth: 110,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "two-banner-bg",
|
|
||||||
label: "Two banner backgrounds",
|
|
||||||
kind: "list",
|
kind: "list",
|
||||||
pageKey: "home",
|
pageKey: "home",
|
||||||
aspectRatio: "540:312",
|
aspectRatio: "540:312",
|
||||||
|
|||||||
Reference in New Issue
Block a user