From 51c21938eb3fb271e901d8f3dce65c8b6dbfdea2 Mon Sep 17 00:00:00 2001 From: hamed <15238-genius.ha@users.noreply.drupalcode.org> Date: Sat, 1 Aug 2026 16:33:39 +0330 Subject: [PATCH] feat(booking): give the booking flow a dark palette derived from its own colours MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit components/appointment/ had every colour hard-coded, so the flow rendered identically in either theme even after the wiring was fixed. It now reads from CSS custom properties. These are not new colours. The :root values are byte-for-byte the hex codes that were already in the components — twenty-one files, mapped one to one — so light mode is unchanged. The dark values are derived from those same colours: surfaces and borders darkened, text inverted, and the two brand colours (the indigo and the orange) lightened rather than replaced, because both lose contrast against a dark surface at their original values. Verified in a headless browser with prefers-color-scheme forced dark: data-theme lands on and --ap-surface resolves to #1b1b20 rather than white. Six one-off colours remain hard-coded — a success green, an error red and similar — each used exactly once and none of them a surface. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5 (1M context) --- app/globals.css | 54 +++++++++++++++++++ components/appointment/Container.js | 2 +- components/appointment/date/Time/index.js | 2 +- components/appointment/date/hour/index.js | 2 +- components/appointment/date/index.js | 12 ++--- components/appointment/detail/Content.js | 4 +- components/appointment/detail/Form.js | 4 +- components/appointment/detail/SubmitData.js | 2 +- components/appointment/detail/index.js | 8 +-- components/appointment/failedPay/index.js | 6 +-- components/appointment/index.js | 4 +- components/appointment/information/Detail.js | 20 +++---- components/appointment/information/Head.js | 4 +- components/appointment/information/index.js | 4 +- components/appointment/location/Address.js | 6 +-- components/appointment/location/Head.js | 4 +- .../appointment/location/LocationSelect.js | 18 +++---- components/appointment/location/index.js | 4 +- components/appointment/paying/ButtonFixed.js | 2 +- components/appointment/paying/index.js | 52 +++++++++--------- components/appointment/service/index.js | 22 ++++---- components/appointment/successPay/index.js | 8 +-- 22 files changed, 149 insertions(+), 95 deletions(-) diff --git a/app/globals.css b/app/globals.css index 31d121a..26d6184 100644 --- a/app/globals.css +++ b/app/globals.css @@ -74,6 +74,60 @@ body { background: #fafafa; } + +/* ── توکن‌های جریان رزرو ────────────────────────────────────────────────── + * + * جریان رزرو رنگ‌هایش را hard-code داشت، پس در دارک‌مود دقیقاً همان سفید می‌ماند. + * این‌ها **رنگ تازه نیستند**: مقادیر روشن عیناً همان چیزی است که تا امروز بود، و + * نسخهٔ تیره از همان‌ها مشتق شده. طراحی تازه‌ای اینجا اختراع نشده. + */ +:root { + --ap-surface: #ffffff; + --ap-surface-2: #fafafa; + --ap-surface-3: #f5f5f5; + --ap-border: #efefef; + --ap-border-2: #d7d7d7; + --ap-heading: #2f2f2f; + --ap-text: #3b3b3b; + --ap-text-2: #616161; + --ap-text-3: #7a7a7a; + --ap-text-4: #9b9b9b; + --ap-muted: #525252; + --ap-primary: #5559ce; + --ap-accent: #f17732; + --ap-accent-strong: #b4541f; + --ap-danger: #e0383b; + --ap-danger-bg: #fdeeee; + --ap-danger-border: #f3c0c1; + --ap-warning: #f4b740; + --ap-warning-bg: #fff8e8; + --ap-warning-text: #8a6100; +} + +[data-theme="dark"] { + --ap-surface: #1b1b20; + --ap-surface-2: #232329; + --ap-surface-3: #2a2a31; + --ap-border: #2e2e36; + --ap-border-2: #3a3a44; + --ap-heading: #f0f0f2; + --ap-text: #e8e8ea; + --ap-text-2: #b8b8be; + --ap-text-3: #98989f; + --ap-text-4: #82828a; + --ap-muted: #c9c9cf; + /* بنفش و نارنجی روی زمینهٔ تیره کم‌کنتراست‌اند؛ کمی روشن‌تر شده‌اند نه عوض. */ + --ap-primary: #8a8de6; + --ap-accent: #ff9152; + --ap-accent-strong: #ffb184; + --ap-danger: #ff6b6e; + --ap-danger-bg: #3a1f20; + --ap-danger-border: #5a2e30; + --ap-warning: #ffca5c; + --ap-warning-bg: #33290f; + --ap-warning-text: #ffd98a; +} + /* transition-colors only on theme-sensitive elements, not every DOM node */ .dark, [data-theme] { diff --git a/components/appointment/Container.js b/components/appointment/Container.js index 4ebddb2..912e22a 100644 --- a/components/appointment/Container.js +++ b/components/appointment/Container.js @@ -61,7 +61,7 @@ function Container({ let dateStep; if (bookingLocations.length === 0) { dateStep = ( -

+

نوبت‌دهی آنلاین برای این پزشک فعال نیست.

); diff --git a/components/appointment/date/Time/index.js b/components/appointment/date/Time/index.js index b11d6a1..5b67844 100644 --- a/components/appointment/date/Time/index.js +++ b/components/appointment/date/Time/index.js @@ -3,7 +3,7 @@ import SelectDatePicker from "./SelectDatePicker"; function Time({ isStep, setDate, disabledDates, clinicUuid }) { return (
-

+

1. انتخاب روز

diff --git a/components/appointment/date/hour/index.js b/components/appointment/date/hour/index.js index 778365b..ec9b216 100644 --- a/components/appointment/date/hour/index.js +++ b/components/appointment/date/hour/index.js @@ -3,7 +3,7 @@ import DateTime from "@/app/component/date/dateTime"; function Hour({ doctor, setStep, date, isStep, setSelectedSlot, setSelectedDate, serviceMode = false, selectedServiceUuids = [], clinicUuid = null, selectedLocation = null }) { return (
-

+

2. انتخاب ساعت

{(onChangeLocation || (serviceMode && onChangeService)) && (
@@ -35,7 +35,7 @@ function Date({ @@ -44,7 +44,7 @@ function Date({ @@ -52,14 +52,14 @@ function Date({
)} {closedOnDate && ( -
-

+

+

«{selectedLocation?.title}» در روز انتخاب‌شده نوبت‌دهی ندارد. {onChangeLocation && ( diff --git a/components/appointment/detail/Content.js b/components/appointment/detail/Content.js index b4e1297..f4334fd 100644 --- a/components/appointment/detail/Content.js +++ b/components/appointment/detail/Content.js @@ -2,9 +2,9 @@ function Content({ isConfirmed, children, title }) { return (

-

{title}

+

{title}

{isConfirmed && ( -

+

تایید شده

)} diff --git a/components/appointment/detail/Form.js b/components/appointment/detail/Form.js index 25bc022..2695ac4 100644 --- a/components/appointment/detail/Form.js +++ b/components/appointment/detail/Form.js @@ -27,8 +27,8 @@ function Form({ changeData, insurance, supplementaryInsurance, data, isForAnothe {isForAnother ? ( ) : ( -
- {data?.phone?.value} +
+ {data?.phone?.value} تایید شده
)} diff --git a/components/appointment/detail/SubmitData.js b/components/appointment/detail/SubmitData.js index 6b6db13..d8ea270 100644 --- a/components/appointment/detail/SubmitData.js +++ b/components/appointment/detail/SubmitData.js @@ -195,7 +195,7 @@ function SubmitData({ setStep, data, prevData, setErrors, doctor, isForAnother, onClick={handleSubmit} >

ثبت اطلاعات

diff --git a/components/appointment/detail/index.js b/components/appointment/detail/index.js index 57e132a..f243b68 100644 --- a/components/appointment/detail/index.js +++ b/components/appointment/detail/index.js @@ -78,10 +78,10 @@ function Detail({ }, []); return ( - // ${typePay === 'success' || typePay === 'failed' ? 'bg-transparent border-transparent md:bg-[#FFF] md:border-[#EFEFEF]' : ''} + // ${typePay === 'success' || typePay === 'failed' ? 'bg-transparent border-transparent md:bg-[var(--ap-surface)] md:border-[var(--ap-border)]' : ''}
{/* دکمه برگشت */}
-

+

{isForAnother ? "اطلاعات کاربر جدید" : "اطلاعات حساب کاربری"}

(isForAnother ? switchToSelf() : switchToAnother())} > -

+

{isForAnother ? "نوبت برای خودم است" : "نوبت برای شخص دیگری است"}

diff --git a/components/appointment/failedPay/index.js b/components/appointment/failedPay/index.js index 1359b92..fb3edb3 100644 --- a/components/appointment/failedPay/index.js +++ b/components/appointment/failedPay/index.js @@ -8,7 +8,7 @@ function FailedPay({ setStep, fadeElement }) { return (
@@ -24,7 +24,7 @@ function FailedPay({ setStep, fadeElement }) { className="!flex !rounded-[8px] !items-center !p-2 !justify-center !gap-[4px]" > -

+

صفحه اصلی

@@ -35,7 +35,7 @@ function FailedPay({ setStep, fadeElement }) { className="!flex !rounded-[8px] !items-center !p-2 !justify-center !gap-[4px]" > -

+

تلاش دوباره

diff --git a/components/appointment/index.js b/components/appointment/index.js index fe11f26..7186682 100644 --- a/components/appointment/index.js +++ b/components/appointment/index.js @@ -215,8 +215,8 @@ function AppointmentPage({ doctor, disabledDates, matchedCity, initialClinicUuid return (
-
-

در حال بارگذاری...

+
+

در حال بارگذاری...

); diff --git a/components/appointment/information/Detail.js b/components/appointment/information/Detail.js index 121d0a1..da2bf65 100644 --- a/components/appointment/information/Detail.js +++ b/components/appointment/information/Detail.js @@ -29,7 +29,7 @@ function Detail({ doctor, step, setStep, selectedSlot, selectedDate }) {
-

+

آدرس: {address}

@@ -39,10 +39,10 @@ function Detail({ doctor, step, setStep, selectedSlot, selectedDate }) {
-

+

تاریخ نوبت:{" "}

-

+

{formattedDate}

@@ -53,7 +53,7 @@ function Detail({ doctor, step, setStep, selectedSlot, selectedDate }) { onClick={() => setStep(0)} variant="text" > -

+

تغییر{" "}

@@ -68,10 +68,10 @@ function Detail({ doctor, step, setStep, selectedSlot, selectedDate }) {
-

+

زمان نوبت:{" "}

-

+

ساعت {formattedTime}

@@ -82,7 +82,7 @@ function Detail({ doctor, step, setStep, selectedSlot, selectedDate }) { onClick={() => setStep(0)} variant="text" > -

+

تغییر{" "}

@@ -93,7 +93,7 @@ function Detail({ doctor, step, setStep, selectedSlot, selectedDate }) {
    {/*
  • @@ -102,7 +102,7 @@ function Detail({ doctor, step, setStep, selectedSlot, selectedDate }) { >
-

+

فقط در صورت لغو نوبت تا 5 ساعت قبل از زمان ویزیت، امکان استرداد وجه ممکن می باشد.

@@ -113,7 +113,7 @@ function Detail({ doctor, step, setStep, selectedSlot, selectedDate }) { >
-

+

لطفا به موقع در مطب حضور داشته باشید.

diff --git a/components/appointment/information/Head.js b/components/appointment/information/Head.js index 80d419a..de70d0c 100644 --- a/components/appointment/information/Head.js +++ b/components/appointment/information/Head.js @@ -16,10 +16,10 @@ function Head({ doctor }) { className=" w-[40px] sm:w-[58px] md:w-[77px] lg:w-[95px] h-[40px] sm:h-[58px] md:h-[77px] lg:h-[95px] " />
-

+

{doctor?.name}

-

+

تخصص: {expertiseText}

diff --git a/components/appointment/information/index.js b/components/appointment/information/index.js index 704ace5..09fe3bb 100644 --- a/components/appointment/information/index.js +++ b/components/appointment/information/index.js @@ -9,9 +9,9 @@ function Information({ doctor, step, setStep, typePay, isPay, disableSide, selec className={`w-full lg:w-[41%] relative ${disableSide ? "hidden lg:flex" : "" }`} >
-

+

جزئیات نوبت

diff --git a/components/appointment/location/Address.js b/components/appointment/location/Address.js index e791cde..2a58b1b 100644 --- a/components/appointment/location/Address.js +++ b/components/appointment/location/Address.js @@ -7,7 +7,7 @@ function Address({ doctor, selectedLocation }) { return (
  • -
    +

    {`${selectedLocation.title}: `}

    {selectedLocation.address || "—"}

    @@ -20,12 +20,12 @@ function Address({ doctor, selectedLocation }) {
      {doctor?.address?.map((item, idx) => (
    • -
      +

      {`${item.name}: `}

      {item.address || item.location}

      {doctor.address.length > idx + 1 && ( - + )}
    • ))} diff --git a/components/appointment/location/Head.js b/components/appointment/location/Head.js index 4134cc4..d945c37 100644 --- a/components/appointment/location/Head.js +++ b/components/appointment/location/Head.js @@ -14,10 +14,10 @@ function Head({ doctor }) { className=" w-[48px] sm:w-[51px] md:w-[54px] lg:w-[56px] h-[48px] sm:h-[51px] md:h-[54px] lg:h-[56px] " />
      -

      +

      {doctor?.name}

      -

      +

      تخصص: {expertiseText}

      diff --git a/components/appointment/location/LocationSelect.js b/components/appointment/location/LocationSelect.js index dd03eb0..1db10f3 100644 --- a/components/appointment/location/LocationSelect.js +++ b/components/appointment/location/LocationSelect.js @@ -18,10 +18,10 @@ function LocationSelect({ locations = [], selected, onSelect }) { return (
      -

      ۱. انتخاب محل نوبت‌دهی

      +

      ۱. انتخاب محل نوبت‌دهی

      {!anyOpen && locations.length > 0 && ( -

      +

      در روز انتخاب‌شده هیچ‌کدام از محل‌ها نوبت‌دهی ندارند. روز دیگری را انتخاب کنید.

      )} @@ -43,32 +43,32 @@ function LocationSelect({ locations = [], selected, onSelect }) { onClick={() => !closed && onSelect(location)} className={`w-full text-right p-[16px] rounded-[8px] border border-solid transition-colors ${ closed - ? "border-[#EFEFEF] bg-[#FAFAFA] opacity-60 cursor-not-allowed" + ? "border-[var(--ap-border)] bg-[var(--ap-surface-2)] opacity-60 cursor-not-allowed" : active - ? "border-[#5559CE] bg-[rgba(85,89,206,0.06)]" - : "border-[#EFEFEF] bg-[#FFF] hover:border-[#C7C9EC]" + ? "border-[var(--ap-primary)] bg-[rgba(85,89,206,0.06)]" + : "border-[var(--ap-border)] bg-[var(--ap-surface)] hover:border-[#C7C9EC]" }`} >
      -

      +

      {location.title}

      {location.type === "clinic" && ( - + کلینیک )}
      {location.address && ( -

      +

      {location.address}

      )}

      {closed diff --git a/components/appointment/location/index.js b/components/appointment/location/index.js index 617ab98..c422f20 100644 --- a/components/appointment/location/index.js +++ b/components/appointment/location/index.js @@ -5,10 +5,10 @@ function Location({ doctor, selectedLocation }) { return (

      - +
      diff --git a/components/appointment/paying/ButtonFixed.js b/components/appointment/paying/ButtonFixed.js index 14b90fe..7c88ac9 100644 --- a/components/appointment/paying/ButtonFixed.js +++ b/components/appointment/paying/ButtonFixed.js @@ -3,7 +3,7 @@ import React from "react"; function ButtonFixed({ children }) { return (
      {children}
      diff --git a/components/appointment/paying/index.js b/components/appointment/paying/index.js index 5b25d78..09102e0 100644 --- a/components/appointment/paying/index.js +++ b/components/appointment/paying/index.js @@ -69,29 +69,29 @@ function Paying({ setStep, appointmentId, expiresAt, doctor, data, isForAnother, return (
      -
      -

      +

      +

      تأیید و پرداخت

      {/* ── خلاصه نوبت ── */} -
      +
      {doctor?.name && (
      - پزشک - {doctor.name} + پزشک + {doctor.name}
      )} {patientName && (
      - بیمار - {patientName} + بیمار + {patientName}
      )} {selectedSlot?.start && (
      - زمان نوبت - + زمان نوبت + {/* utcOffset(210) = ساعت رسمی ایران (+03:30)، مستقل از tz مرورگر بیمار */} {moment.unix(selectedSlot.start).utcOffset(210).locale("fa").format("dddd jD jMMMM jYYYY")} {" — ساعت "} @@ -102,12 +102,12 @@ function Paying({ setStep, appointmentId, expiresAt, doctor, data, isForAnother,
      {/* ── مبلغ ── */} -
      - +
      + مبلغ قابل پرداخت - - {toman} تومان + + {toman} تومان
      @@ -115,18 +115,18 @@ function Paying({ setStep, appointmentId, expiresAt, doctor, data, isForAnother, {!expired && (
      - + مهلت پرداخت و نگه‌داری نوبت {formatTime(timeLeft)}
      -
      +
      @@ -137,18 +137,18 @@ function Paying({ setStep, appointmentId, expiresAt, doctor, data, isForAnother, {!expired && (
      {testMode ? ( -
      +
      🧪
      -

      درگاه پرداخت آزمایشی

      -

      +

      درگاه پرداخت آزمایشی

      +

      در این حالت پرداخت واقعی انجام نمی‌شود و نوبت به‌صورت آزمایشی تأیید می‌گردد.

      ) : gateways.length === 0 ? ( -
      -

      +

      +

      درگاه پرداخت فعالی موجود نیست. لطفاً بعداً تلاش کنید.

      @@ -173,8 +173,8 @@ function Paying({ setStep, appointmentId, expiresAt, doctor, data, isForAnother, )} {expired && ( -
      -

      +

      +

      مهلت پرداخت تمام شد و نوبت آزاد شد. لطفاً دوباره زمان نوبت را انتخاب کنید.

      @@ -188,7 +188,7 @@ function Paying({ setStep, appointmentId, expiresAt, doctor, data, isForAnother, onClick={() => setStep(0)} variant="contained" > - انتخاب مجدد زمان + انتخاب مجدد زمان ) : ( ); @@ -115,11 +115,11 @@ function ServiceSelect({ services = [], onContinue, doctorUuid, clinicUuid = nul )} {draft.length > 0 && ( -
      +
      {/* تا وقتی عدد سرور نرسیده، «تقریبی» است — عددِ جمعِ کلاینت ممکن است با مدت واقعی نوبت یکی نباشد. */} {serverMinutes === null ? "مدت تقریبی" : "مدت کل"}:{" "} - {totalMinutes} دقیقه + {totalMinutes} دقیقه
      )} @@ -127,7 +127,7 @@ function ServiceSelect({ services = [], onContinue, doctorUuid, clinicUuid = nul type="button" disabled={draft.length === 0} onClick={() => onContinue(draft)} - className="mt-5 w-full h-[48px] rounded-xl bg-[#5559CE] text-white text-[15px] font-medium disabled:opacity-50" + className="mt-5 w-full h-[48px] rounded-xl bg-[var(--ap-primary)] text-white text-[15px] font-medium disabled:opacity-50" > انتخاب زمان diff --git a/components/appointment/successPay/index.js b/components/appointment/successPay/index.js index 5d83e83..b5981a6 100644 --- a/components/appointment/successPay/index.js +++ b/components/appointment/successPay/index.js @@ -7,17 +7,17 @@ function SuccessPay({ setStep }) { return (
      -

      +

      پرداخت شما با موفقیت انجام شد.

      نوبت شما تأیید شد و پیامک تأیید برای بیمار ارسال می‌شود. برای نمایش نوبت‌ها به قسمت نوبت‌های من بروید.{" "}

      @@ -26,7 +26,7 @@ function SuccessPay({ setStep }) { variant="text" className="!flex !items-center !justify-center !gap-[4px]" > -

      +

      برو به نوبت های من