From 42aff5aee9e574504816f2e646afc715b29ab4b2 Mon Sep 17 00:00:00 2001 From: Alireza Hassani Date: Sun, 4 Oct 2026 13:21:51 +0330 Subject: [PATCH] Update carousels, card hover, and IranBay branding. Co-authored-by: Cursor --- .env.example | 16 + .gitignore | 42 + AGENTS.md | 9 + CLAUDE.md | 1 + README.md | 13 + app/about/about.module.css | 369 + app/about/page.tsx | 117 + app/api/meshkee/categories/route.ts | 107 + app/auth/handoff/layout.tsx | 52 + app/auth/handoff/page.tsx | 23 + .../[slug]/blog/[postSlug]/blog.module.css | 196 + .../[slug]/blog/[postSlug]/page.tsx | 133 + app/businesses/[slug]/page.module.css | 9 + app/businesses/[slug]/page.tsx | 52 + .../portfolios/[portfolioSlug]/page.tsx | 92 + .../[portfolioSlug]/portfolio.module.css | 82 + app/businesses/businesses.module.css | 409 ++ app/businesses/page.tsx | 162 + app/contact/contact.module.css | 325 + app/contact/page.tsx | 137 + app/favicon.ico | Bin 0 -> 25931 bytes app/globals.css | 274 + app/home.module.css | 815 +++ app/layout.tsx | 23 + app/page.tsx | 451 ++ app/register/page.module.css | 9 + app/register/page.tsx | 24 + app/shared.module.css | 65 + components/ArticleBody.module.css | 19 + components/ArticleBody.tsx | 36 + components/BusinessDetail.module.css | 806 +++ components/BusinessDetail.tsx | 455 ++ components/BusinessRegistration.module.css | 644 ++ components/BusinessRegistration.tsx | 541 ++ components/CategoryMultiSelect.module.css | 229 + components/CategoryMultiSelect.tsx | 263 + components/CommentCarousel.tsx | 73 + components/ContactFaq.tsx | 37 + components/DetailBanner.module.css | 70 + components/DetailBanner.tsx | 37 + components/Footer.module.css | 105 + components/Footer.tsx | 61 + components/Header.module.css | 434 ++ components/Header.tsx | 517 ++ components/MegaMenu.module.css | 153 + components/MegaMenu.tsx | 105 + components/PortfolioGallery.module.css | 67 + components/PortfolioGallery.tsx | 48 + components/ProducerCard.module.css | 259 + components/ProducerCard.tsx | 77 + components/RequireAuth.tsx | 81 + components/ShareLikeActions.module.css | 26 + components/ShareLikeActions.tsx | 16 + data/business.ts | 229 + data/details.ts | 116 + data/home.ts | 292 + data/pages.ts | 97 + eslint.config.mjs | 18 + lib/auth-user.ts | 88 + lib/marketplace.ts | 44 + lib/mega-menu.ts | 156 + lib/meshkee-categories.ts | 76 + lib/session.ts | 80 + lib/website-carousels.ts | 69 + lib/website-media.ts | 56 + next.config.ts | 33 + package-lock.json | 6144 +++++++++++++++++ package.json | 24 + public/images/about/clients/apple.png | Bin 0 -> 31926 bytes public/images/about/clients/audi.png | Bin 0 -> 66079 bytes public/images/about/clients/bar.png | Bin 0 -> 195653 bytes public/images/about/clients/bmw.png | Bin 0 -> 14639 bytes public/images/about/clients/mercedes.png | Bin 0 -> 55699 bytes public/images/about/clients/tesla.png | Bin 0 -> 7266 bytes public/images/about/hero-card.png | Bin 0 -> 246552 bytes public/images/about/register.png | Bin 0 -> 144311 bytes public/images/about/responsive.png | Bin 0 -> 346938 bytes public/images/about/support.png | Bin 0 -> 73465 bytes public/images/auth/ship.png | Bin 0 -> 90239 bytes public/images/banners/pistachio.png | Bin 0 -> 222595 bytes public/images/banners/plane.png | Bin 0 -> 135675 bytes public/images/banners/saffron.png | Bin 0 -> 625385 bytes public/images/banners/shampoo.png | Bin 0 -> 247730 bytes public/images/banners/ship.png | Bin 0 -> 642161 bytes public/images/banners/tv.png | Bin 0 -> 379480 bytes public/images/blog/hero.jpg | Bin 0 -> 897000 bytes public/images/blog/thumb.jpg | Bin 0 -> 1128478 bytes public/images/business/blog.jpg | Bin 0 -> 1128478 bytes public/images/business/gallery.jpg | Bin 0 -> 1128478 bytes public/images/business/hero.jpg | Bin 0 -> 174644 bytes public/images/business/hero2.jpg | Bin 0 -> 174644 bytes public/images/business/logo.png | Bin 0 -> 719444 bytes public/images/business/logo2.png | Bin 0 -> 719444 bytes public/images/business/map.png | Bin 0 -> 2210657 bytes public/images/business/portfolio-1.jpg | Bin 0 -> 812426 bytes public/images/business/portfolio-2.jpg | Bin 0 -> 322597 bytes public/images/business/portfolio-3.jpg | Bin 0 -> 519716 bytes public/images/business/product-alt.png | Bin 0 -> 338164 bytes public/images/business/product-necklace.png | Bin 0 -> 290560 bytes public/images/business/product-ring.png | Bin 0 -> 121897 bytes public/images/categories/chair.png | Bin 0 -> 118152 bytes public/images/categories/clothes.png | Bin 0 -> 1619905 bytes public/images/categories/food.png | Bin 0 -> 258758 bytes public/images/categories/fruits.png | Bin 0 -> 232041 bytes public/images/categories/industry.png | Bin 0 -> 273992 bytes public/images/categories/services.png | Bin 0 -> 307609 bytes public/images/categories/shoes.png | Bin 0 -> 471133 bytes public/images/categories/washer.png | Bin 0 -> 101118 bytes public/images/contact/map.png | Bin 0 -> 2836779 bytes public/images/hero/furniture.png | Bin 0 -> 74157 bytes public/images/hero/ice-cream.png | Bin 0 -> 392666 bytes public/images/hero/rug.png | Bin 0 -> 65404 bytes public/images/icons/add.svg | 5 + public/images/icons/arrow.png | Bin 0 -> 2342 bytes public/images/icons/begin-arrow.png | Bin 0 -> 2342 bytes public/images/icons/bullet.svg | 3 + public/images/icons/calendar.svg | 5 + public/images/icons/cart.svg | 7 + public/images/icons/category.svg | 5 + public/images/icons/chevron.svg | 5 + public/images/icons/eye.svg | 5 + public/images/icons/facebook.png | Bin 0 -> 12450 bytes public/images/icons/gear.svg | 5 + public/images/icons/heart.svg | 5 + public/images/icons/hero-chevron.svg | 5 + public/images/icons/home-outline.svg | 5 + public/images/icons/home.svg | 5 + public/images/icons/instagram.png | Bin 0 -> 100746 bytes public/images/icons/join-add.svg | 5 + public/images/icons/logo.png | Bin 0 -> 16362 bytes public/images/icons/menu.svg | 7 + public/images/icons/search.svg | 5 + public/images/icons/share.svg | 5 + public/images/icons/telegram.png | Bin 0 -> 12560 bytes public/images/icons/user-body.svg | 5 + public/images/icons/user-head.svg | 5 + public/images/icons/user.svg | 14 + public/images/pages/about-ref.jpg | Bin 0 -> 63523 bytes public/images/pages/about-ref.png | Bin 0 -> 63523 bytes public/images/pages/businesses-ref.jpg | Bin 0 -> 100022 bytes public/images/pages/contact-ref.jpg | Bin 0 -> 95384 bytes public/images/pages/contact-ref.png | Bin 0 -> 95384 bytes public/images/portfolio/main.jpg | Bin 0 -> 2661260 bytes public/images/portfolio/thumb-1.jpg | Bin 0 -> 1093303 bytes public/images/portfolio/thumb-2.jpg | Bin 0 -> 930213 bytes public/images/portfolio/thumb-3.jpg | Bin 0 -> 1689754 bytes public/images/producers/cover-bakery.png | Bin 0 -> 4905607 bytes public/images/producers/cover-clothes.png | Bin 0 -> 361450 bytes public/images/producers/cover-jewelry.png | Bin 0 -> 174644 bytes public/images/producers/cover-laundry.png | Bin 0 -> 538810 bytes public/images/producers/logo-arad.png | Bin 0 -> 178557 bytes public/images/producers/logo-ayoubi.png | Bin 0 -> 719444 bytes public/images/producers/logo-sani.png | Bin 0 -> 19536 bytes public/images/producers/logo-simode.png | Bin 0 -> 56884 bytes public/images/register/side-step1.png | Bin 0 -> 595074 bytes public/images/register/side-step2.png | Bin 0 -> 2243608 bytes public/images/register/side-step3.png | Bin 0 -> 462493 bytes public/images/register/type-corporates.png | Bin 0 -> 18433 bytes public/images/register/type-industrial.png | Bin 0 -> 73171 bytes public/images/register/type-store.png | Bin 0 -> 13215 bytes public/images/register/upload.png | Bin 0 -> 4759 bytes tsconfig.json | 34 + 162 files changed, 16828 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 AGENTS.md create mode 100644 CLAUDE.md create mode 100644 README.md create mode 100644 app/about/about.module.css create mode 100644 app/about/page.tsx create mode 100644 app/api/meshkee/categories/route.ts create mode 100644 app/auth/handoff/layout.tsx create mode 100644 app/auth/handoff/page.tsx create mode 100644 app/businesses/[slug]/blog/[postSlug]/blog.module.css create mode 100644 app/businesses/[slug]/blog/[postSlug]/page.tsx create mode 100644 app/businesses/[slug]/page.module.css create mode 100644 app/businesses/[slug]/page.tsx create mode 100644 app/businesses/[slug]/portfolios/[portfolioSlug]/page.tsx create mode 100644 app/businesses/[slug]/portfolios/[portfolioSlug]/portfolio.module.css create mode 100644 app/businesses/businesses.module.css create mode 100644 app/businesses/page.tsx create mode 100644 app/contact/contact.module.css create mode 100644 app/contact/page.tsx create mode 100644 app/favicon.ico create mode 100644 app/globals.css create mode 100644 app/home.module.css create mode 100644 app/layout.tsx create mode 100644 app/page.tsx create mode 100644 app/register/page.module.css create mode 100644 app/register/page.tsx create mode 100644 app/shared.module.css create mode 100644 components/ArticleBody.module.css create mode 100644 components/ArticleBody.tsx create mode 100644 components/BusinessDetail.module.css create mode 100644 components/BusinessDetail.tsx create mode 100644 components/BusinessRegistration.module.css create mode 100644 components/BusinessRegistration.tsx create mode 100644 components/CategoryMultiSelect.module.css create mode 100644 components/CategoryMultiSelect.tsx create mode 100644 components/CommentCarousel.tsx create mode 100644 components/ContactFaq.tsx create mode 100644 components/DetailBanner.module.css create mode 100644 components/DetailBanner.tsx create mode 100644 components/Footer.module.css create mode 100644 components/Footer.tsx create mode 100644 components/Header.module.css create mode 100644 components/Header.tsx create mode 100644 components/MegaMenu.module.css create mode 100644 components/MegaMenu.tsx create mode 100644 components/PortfolioGallery.module.css create mode 100644 components/PortfolioGallery.tsx create mode 100644 components/ProducerCard.module.css create mode 100644 components/ProducerCard.tsx create mode 100644 components/RequireAuth.tsx create mode 100644 components/ShareLikeActions.module.css create mode 100644 components/ShareLikeActions.tsx create mode 100644 data/business.ts create mode 100644 data/details.ts create mode 100644 data/home.ts create mode 100644 data/pages.ts create mode 100644 eslint.config.mjs create mode 100644 lib/auth-user.ts create mode 100644 lib/marketplace.ts create mode 100644 lib/mega-menu.ts create mode 100644 lib/meshkee-categories.ts create mode 100644 lib/session.ts create mode 100644 lib/website-carousels.ts create mode 100644 lib/website-media.ts create mode 100644 next.config.ts create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 public/images/about/clients/apple.png create mode 100644 public/images/about/clients/audi.png create mode 100644 public/images/about/clients/bar.png create mode 100644 public/images/about/clients/bmw.png create mode 100644 public/images/about/clients/mercedes.png create mode 100644 public/images/about/clients/tesla.png create mode 100644 public/images/about/hero-card.png create mode 100644 public/images/about/register.png create mode 100644 public/images/about/responsive.png create mode 100644 public/images/about/support.png create mode 100644 public/images/auth/ship.png create mode 100644 public/images/banners/pistachio.png create mode 100644 public/images/banners/plane.png create mode 100644 public/images/banners/saffron.png create mode 100644 public/images/banners/shampoo.png create mode 100644 public/images/banners/ship.png create mode 100644 public/images/banners/tv.png create mode 100644 public/images/blog/hero.jpg create mode 100644 public/images/blog/thumb.jpg create mode 100644 public/images/business/blog.jpg create mode 100644 public/images/business/gallery.jpg create mode 100644 public/images/business/hero.jpg create mode 100644 public/images/business/hero2.jpg create mode 100644 public/images/business/logo.png create mode 100644 public/images/business/logo2.png create mode 100644 public/images/business/map.png create mode 100644 public/images/business/portfolio-1.jpg create mode 100644 public/images/business/portfolio-2.jpg create mode 100644 public/images/business/portfolio-3.jpg create mode 100644 public/images/business/product-alt.png create mode 100644 public/images/business/product-necklace.png create mode 100644 public/images/business/product-ring.png create mode 100644 public/images/categories/chair.png create mode 100644 public/images/categories/clothes.png create mode 100644 public/images/categories/food.png create mode 100644 public/images/categories/fruits.png create mode 100644 public/images/categories/industry.png create mode 100644 public/images/categories/services.png create mode 100644 public/images/categories/shoes.png create mode 100644 public/images/categories/washer.png create mode 100644 public/images/contact/map.png create mode 100644 public/images/hero/furniture.png create mode 100644 public/images/hero/ice-cream.png create mode 100644 public/images/hero/rug.png create mode 100644 public/images/icons/add.svg create mode 100644 public/images/icons/arrow.png create mode 100644 public/images/icons/begin-arrow.png create mode 100644 public/images/icons/bullet.svg create mode 100644 public/images/icons/calendar.svg create mode 100644 public/images/icons/cart.svg create mode 100644 public/images/icons/category.svg create mode 100644 public/images/icons/chevron.svg create mode 100644 public/images/icons/eye.svg create mode 100644 public/images/icons/facebook.png create mode 100644 public/images/icons/gear.svg create mode 100644 public/images/icons/heart.svg create mode 100644 public/images/icons/hero-chevron.svg create mode 100644 public/images/icons/home-outline.svg create mode 100644 public/images/icons/home.svg create mode 100644 public/images/icons/instagram.png create mode 100644 public/images/icons/join-add.svg create mode 100644 public/images/icons/logo.png create mode 100644 public/images/icons/menu.svg create mode 100644 public/images/icons/search.svg create mode 100644 public/images/icons/share.svg create mode 100644 public/images/icons/telegram.png create mode 100644 public/images/icons/user-body.svg create mode 100644 public/images/icons/user-head.svg create mode 100644 public/images/icons/user.svg create mode 100644 public/images/pages/about-ref.jpg create mode 100644 public/images/pages/about-ref.png create mode 100644 public/images/pages/businesses-ref.jpg create mode 100644 public/images/pages/contact-ref.jpg create mode 100644 public/images/pages/contact-ref.png create mode 100644 public/images/portfolio/main.jpg create mode 100644 public/images/portfolio/thumb-1.jpg create mode 100644 public/images/portfolio/thumb-2.jpg create mode 100644 public/images/portfolio/thumb-3.jpg create mode 100644 public/images/producers/cover-bakery.png create mode 100644 public/images/producers/cover-clothes.png create mode 100644 public/images/producers/cover-jewelry.png create mode 100644 public/images/producers/cover-laundry.png create mode 100644 public/images/producers/logo-arad.png create mode 100644 public/images/producers/logo-ayoubi.png create mode 100644 public/images/producers/logo-sani.png create mode 100644 public/images/producers/logo-simode.png create mode 100644 public/images/register/side-step1.png create mode 100644 public/images/register/side-step2.png create mode 100644 public/images/register/side-step3.png create mode 100644 public/images/register/type-corporates.png create mode 100644 public/images/register/type-industrial.png create mode 100644 public/images/register/type-store.png create mode 100644 public/images/register/upload.png create mode 100644 tsconfig.json diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..a73d377 --- /dev/null +++ b/.env.example @@ -0,0 +1,16 @@ +NEXT_PUBLIC_DASHBOARD_URL=http://localhost:3002 +NEXT_PUBLIC_ADMIN_DASHBOARD_URL=http://localhost:3002 +NEXT_PUBLIC_BUSINESS_DASHBOARD_URL=http://business.localhost:3002 +NEXT_PUBLIC_WEBSITE_URL=http://localhost:3001 +NEXT_PUBLIC_CUSTOMER_DASHBOARD_URL=http://customer.localhost:3002 +NEXT_PUBLIC_ROOT_DOMAIN=localhost +# Meshkee — always remote (do not point at a local Meshkee) +NEXT_PUBLIC_MESHKEE_API_URL=https://api.meshkee.com/api/v1 + +# Meshkee (used by /api/meshkee/categories) +MESHKEE_API_URL=https://api.meshkee.com/api/v1 +# Prefer cell + password (access tokens expire quickly). Optional static token: +# MESHKEE_API_TOKEN= +# MESHKEE_SUPERADMIN_CELL= +# MESHKEE_SUPERADMIN_PASSWORD= +NEXT_PUBLIC_MESHKEE_TENANT_DOMAIN=iranbay.com diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7b8da95 --- /dev/null +++ b/.gitignore @@ -0,0 +1,42 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* +!.env.example + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..643577d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,9 @@ + + +# This is NOT the Next.js you know + +This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices. + +This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean. + + diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..43c994c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..38446c7 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# IranBay Website + +Public Next.js site. From this folder: + +```bash +cp .env.example .env.local +npm install +npm run dev +``` + +Open [http://localhost:3000](http://localhost:3000). + +Account login is on the dashboard app (`NEXT_PUBLIC_DASHBOARD_URL`, default `http://localhost:3002`). Business registration stays at `/register`. diff --git a/app/about/about.module.css b/app/about/about.module.css new file mode 100644 index 0000000..9b25a20 --- /dev/null +++ b/app/about/about.module.css @@ -0,0 +1,369 @@ +.page { + background: var(--color-white); +} + +.container { + max-width: var(--container-width); + margin: 0 auto; + padding: 0 var(--page-pad); +} + +.wide { + max-width: 1512px; + margin: 0 auto; + padding: 0 var(--page-pad); +} + +.intro { + display: grid; + grid-template-columns: minmax(240px, 0.85fr) minmax(0, 1.15fr); + gap: 48px; + align-items: center; + padding: 56px 0 72px; +} + +.visual { + position: relative; + width: min(100%, 380px); + aspect-ratio: 466 / 634; + margin-inline: auto 0; +} + +.visualBack { + position: absolute; + inset: 0 18% 12% 0; + background: var(--color-card); + border-radius: 32px; +} + +.visualCard { + position: absolute; + left: 14%; + top: 12%; + width: 86%; + height: 88%; + border-radius: 32px; + object-fit: cover; + box-shadow: 0 18px 40px rgba(0, 0, 0, 0.08); +} + +.kicker { + font-size: clamp(22px, 2.2vw, 35px); + font-weight: 300; + color: #434343; + margin-bottom: 8px; +} + +.title { + font-size: clamp(34px, 3.4vw, 55px); + font-weight: 700; + line-height: 1.1; + margin-bottom: 28px; +} + +.title span { + display: block; +} + +.copy { + font-size: 16px; + font-weight: 400; + line-height: 1.45; + color: #bababa; + margin-bottom: 18px; + max-width: 650px; + text-align: justify; +} + +.cta { + margin-top: 18px; + height: 34px; + padding: 0 16px; + min-width: 207px; + font-size: 12px; + letter-spacing: 2.4px; +} + +.clients { + padding: 24px 0 56px; +} + +.clientBar { + margin-top: 36px; + background: #f8f8f8; + border-radius: 5px; + min-height: 160px; + display: grid; + grid-template-columns: repeat(5, minmax(0, 1fr)); + align-items: center; + gap: 24px; + padding: 24px 40px; +} + +.clientLogo { + width: 100%; + max-height: 96px; + object-fit: contain; + margin: 0 auto; +} + +.story { + display: grid; + grid-template-columns: minmax(240px, 0.85fr) minmax(0, 1.15fr); + gap: 40px; + padding: 40px 0 72px; + align-items: start; +} + +.story .cta { + margin-top: 24px; +} + +.comments { + padding: 24px 0 72px; + text-align: center; +} + +.commentHead { + display: grid; + grid-template-columns: auto 1fr auto; + align-items: center; + gap: 16px; + margin-bottom: 8px; +} + +.commentHead .sectionHead { + margin: 0; +} + +.arrows { + display: flex; + gap: 6px; +} + +.arrowBtn { + width: 38px; + height: 36px; + background: var(--color-card); + border-radius: 5px; + display: flex; + align-items: center; + justify-content: center; +} + +.arrowBtn img { + width: 8px; + height: 14px; + object-fit: contain; +} + +.arrowPrev { + transform: rotate(180deg); +} + +.commentCount { + font-size: 13px; + letter-spacing: 1px; + color: var(--color-soft); + min-width: 48px; + text-align: right; +} + +.commentTrack { + animation: commentIn 0.35s ease; +} + +@keyframes commentIn { + from { + opacity: 0; + transform: translateY(8px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.quote { + max-width: 900px; + margin: 28px auto 18px; + font-size: 18px; + line-height: 1.45; + color: #bababa; +} + +.meta { + font-size: 18px; + color: #5f5f5f; +} + +.dots { + margin-top: 28px; + display: flex; + justify-content: center; + gap: 8px; +} + +.dot, +.dotActive { + width: 8px; + height: 8px; + border-radius: 50%; + background: #d8d8d8; + padding: 0; +} + +.dotActive { + background: var(--color-black); +} + +.services { + padding: 24px 0 96px; +} + +.serviceGrid { + margin-top: 48px; + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 28px; + align-items: end; +} + +.serviceCard { + position: relative; + min-height: 190px; + display: grid; + grid-template-columns: auto 1fr; + align-items: end; + gap: 8px; + padding: 24px 16px 18px 0; +} + +.serviceCard::before { + content: ""; + position: absolute; + left: 52px; + right: 0; + bottom: 18px; + height: 135px; + background: #f8f8f8; + border-radius: 25px; + z-index: 0; +} + +.serviceCard img { + position: relative; + z-index: 1; + display: block; + width: auto; + height: 160px; + max-width: 150px; + object-fit: contain; + margin-left: 0; + filter: drop-shadow(0 4px 10px rgba(0, 0, 0, 0.12)); +} + +.serviceCopy { + position: relative; + z-index: 1; + align-self: center; + padding: 8px 12px 8px 4px; + text-align: left; +} + +.serviceCopy h3 { + font-size: clamp(20px, 1.8vw, 30px); + font-weight: 400; + letter-spacing: 1px; + text-transform: uppercase; + line-height: 1.1; +} + +.serviceCopy p { + margin-top: 6px; + font-size: 11px; + font-weight: 300; + letter-spacing: 1.2px; + text-transform: uppercase; + color: var(--color-black); +} + +.support .serviceCopy h3 { + color: #575f50; +} + +.register .serviceCopy h3 { + color: #b68968; +} + +.responsive .serviceCopy h3 { + color: #474039; +} + +.sectionHead { + text-align: center; +} + +.sectionTitle { + font-size: clamp(24px, 2.2vw, 35px); + font-weight: 400; + letter-spacing: 14px; + text-transform: uppercase; +} + +.sectionSub { + margin-top: 8px; + font-size: 12px; + font-weight: 300; + letter-spacing: 4.02px; + text-transform: uppercase; + color: var(--color-soft); +} + +@media (max-width: 980px) { + .intro, + .story { + grid-template-columns: 1fr; + } + + .visual { + margin: 0 auto; + } + + .commentHead { + grid-template-columns: 1fr; + justify-items: center; + gap: 14px; + } + + .commentCount { + text-align: center; + } + + .serviceGrid { + grid-template-columns: 1fr; + max-width: 420px; + margin-left: auto; + margin-right: auto; + } + + .clientBar { + grid-template-columns: repeat(3, 1fr); + min-height: auto; + } +} + +@media (max-width: 720px) { + .clientBar { + grid-template-columns: repeat(2, 1fr); + padding: 20px; + } + + .sectionTitle { + letter-spacing: 6px; + } + + .serviceCopy { + padding-left: 4px; + } +} diff --git a/app/about/page.tsx b/app/about/page.tsx new file mode 100644 index 0000000..8ff689e --- /dev/null +++ b/app/about/page.tsx @@ -0,0 +1,117 @@ +import Header from "@/components/Header"; +import Footer from "@/components/Footer"; +import CommentCarousel from "@/components/CommentCarousel"; +import { aboutCopy, clients, services } from "@/data/pages"; +import styles from "./about.module.css"; + +export const metadata = { + title: "About Us | IranBay", +}; + +export default function AboutPage() { + return ( +
+
+
+
+
+
+
+ IranBay +
+
+

{aboutCopy.kicker}

+

+ {aboutCopy.title.map((line) => ( + {line} + ))} +

+ {aboutCopy.paragraphs.map((p) => ( +

+ {p} +

+ ))} + + Contact us + +
+
+
+ +
+
+
+

All clinets

+

+ some of our most crowded categories +

+
+
+ {clients.map((client) => ( + {client.name} + ))} +
+
+
+ +
+
+
+

{aboutCopy.kicker}

+

+ {aboutCopy.title.map((line) => ( + {line} + ))} +

+
+
+

{aboutCopy.paragraphs[0]}

+ + Contact us + +
+
+
+ +
+ +
+ +
+
+
+

Our services

+

+ some of our most crowded categories +

+
+
+ {services.map((item) => ( +
+ +
+

{item.title}

+

{item.subtitle}

+
+
+ ))} +
+
+
+
+
+
+ ); +} diff --git a/app/api/meshkee/categories/route.ts b/app/api/meshkee/categories/route.ts new file mode 100644 index 0000000..1558460 --- /dev/null +++ b/app/api/meshkee/categories/route.ts @@ -0,0 +1,107 @@ +import { NextRequest, NextResponse } from "next/server"; + +const MESHKEE_API_URL = + process.env.MESHKEE_API_URL?.replace(/\/$/, "") || + process.env.NEXT_PUBLIC_MESHKEE_API_URL?.replace(/\/$/, "") || + "https://api.meshkee.com/api/v1"; + +type CachedToken = { token: string; expiresAt: number }; + +let cachedToken: CachedToken | null = null; + +function tokenExpiry(token: string) { + try { + const payload = JSON.parse( + Buffer.from(token.split(".")[1] ?? "", "base64url").toString(), + ) as { exp?: number }; + if (typeof payload.exp === "number") return payload.exp * 1000; + } catch { + /* ignore malformed tokens */ + } + return 0; +} + +function usableStaticToken() { + const token = process.env.MESHKEE_API_TOKEN?.trim(); + if (!token) return null; + const expiresAt = tokenExpiry(token); + if (expiresAt && expiresAt < Date.now() + 15_000) return null; + return token; +} + +async function serviceToken(): Promise { + const now = Date.now(); + if (cachedToken && cachedToken.expiresAt > now + 15_000) { + return cachedToken.token; + } + + const staticToken = usableStaticToken(); + if (staticToken) return staticToken; + + const cellNumber = process.env.MESHKEE_SUPERADMIN_CELL?.trim(); + const password = process.env.MESHKEE_SUPERADMIN_PASSWORD?.trim(); + if (!cellNumber || !password) return null; + + const response = await fetch(`${MESHKEE_API_URL}/auth/login`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ cellNumber, password }), + cache: "no-store", + }); + if (!response.ok) return null; + + const payload = (await response.json().catch(() => null)) as { + accessToken?: string; + } | null; + const token = payload?.accessToken?.trim(); + if (!token) return null; + + cachedToken = { + token, + expiresAt: tokenExpiry(token) || now + 10 * 60 * 1000, + }; + return token; +} + +async function authHeader(request: NextRequest): Promise { + const incoming = request.headers.get("authorization")?.trim(); + if (incoming) return incoming; + + const token = await serviceToken(); + return token ? `Bearer ${token}` : null; +} + +export async function GET(request: NextRequest) { + const auth = await authHeader(request); + if (!auth) { + return NextResponse.json( + { + message: + "Meshkee categories need auth. Set MESHKEE_API_TOKEN or sign in on the dashboard first.", + }, + { status: 503 }, + ); + } + + const q = request.nextUrl.searchParams.get("q")?.trim() ?? ""; + const params = new URLSearchParams(); + if (q) params.set("q", q); + + const response = await fetch( + `${MESHKEE_API_URL}/business-categories?${params}`, + { + headers: { Authorization: auth }, + cache: "no-store", + }, + ); + + const payload = await response.json().catch(() => null); + if (!response.ok) { + return NextResponse.json( + payload ?? { message: `Meshkee error (${response.status})` }, + { status: response.status }, + ); + } + + return NextResponse.json(payload); +} diff --git a/app/auth/handoff/layout.tsx b/app/auth/handoff/layout.tsx new file mode 100644 index 0000000..14d7c04 --- /dev/null +++ b/app/auth/handoff/layout.tsx @@ -0,0 +1,52 @@ +import type { ReactNode } from "react"; + +/** + * Install Meshkee session from the URL hash before the register page reads tokens. + * Used after customer-dashboard login with ?next=https://…/register + */ +const HANDOFF_BOOT = ` +(function () { + try { + var raw = location.hash ? location.hash.replace(/^#/, "") : ""; + if (!raw) return; + var p = new URLSearchParams(raw); + var access = p.get("access"); + var refresh = p.get("refresh"); + var next = p.get("next") || "/register"; + if (!access || !refresh) return; + + localStorage.setItem("marketplace_access_token", access); + localStorage.setItem("marketplace_refresh_token", refresh); + + var root = "localhost"; + try { + document.cookie = + "marketplace_access_token=" + + encodeURIComponent(access) + + "; Path=/; Max-Age=1209600; SameSite=Lax; Domain=" + + root; + document.cookie = + "marketplace_refresh_token=" + + encodeURIComponent(refresh) + + "; Path=/; Max-Age=1209600; SameSite=Lax; Domain=" + + root; + } catch (e) {} + + var target = next.charAt(0) === "/" ? next : "/" + next; + location.replace(target); + } catch (e) {} +})(); +`; + +export default function AuthHandoffLayout({ + children, +}: { + children: ReactNode; +}) { + return ( + <> +