Commit Graph
472 Commits
Author SHA1 Message Date
hamedandClaude Opus 5 51c21938eb feat(booking): give the booking flow a dark palette derived from its own colours
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 <html> 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) <noreply@anthropic.com>
2026-08-01 16:33:39 +03:30
hamedandClaude Opus 5 519de26839 fix(theme): make dark mode actually apply to the public pages, and test the modals
Tailwind was configured with darkMode: "class" while next-themes writes
data-theme="dark" on the public pages. Every dark: utility on the public
site — 111 of them — compiled to a selector that never matched, which is why
the booking flow stayed white in dark mode. The variant strategy now accepts
both .dark (the panel) and [data-theme="dark"] (the public pages), so neither
provider had to change and 66 dark rules now compile against the real
attribute.

The cancel and reschedule modals get tests, the first component tests in this
repo. They pin the things that would be silently wrong: the penalty comes from
the server before anything is cancelled, the free window says "no penalty"
rather than showing a zero, confirming actually sends the request (the old
dialog's confirm button only closed it), a failed preview does not block the
cancellation, slots are requested with exclude_appointment_uuid so the
patient's own hour is not shown as taken, and the reschedule sends only the
start time because the server owns the duration.

The amount assertion deliberately checks the number and unit rather than the
digit shape — numberToArStyle uses the ar-AE locale and its output depends on
the ICU data in the environment.

lib/getStateInfo.test.js had been failing since before this work: an unknown
host falls through to the representation API, so the test made a real network
call and timed out after five seconds. It mocks lib/req now. The suite is
fully green for the first time: 158 tests.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 16:06:49 +03:30
hamedandClaude Opus 5 4c912dc6c1 feat(panel): let a patient actually cancel or move their own appointment
The site's own copy has been promising cancellation for a while —
lib/specialtyContent.js and components/appointment/information/Detail.js both
tell users they can cancel from "نوبت‌های من" and get a refund up to five hours
before the visit. The UI never could. The cancel modal existed, but its confirm
button called handleClose: it closed the dialog and sent nothing. And the whole
button row it lived in (ButtonData.js) had been commented out since the PDF
download commit, so it was not even reachable.

Cancelling now goes through POST /appointment/{uuid}/cancel and shows the
penalty from /cancellation-preview before the confirm — the same calculation
the cancel itself runs, so the number the patient sees is the number they are
charged. If the preview fails, the dialog says so rather than blocking; the
cancellation is still allowed.

Rescheduling is new and service-aware. It asks for slots with
exclude_appointment_uuid, so the patient's own hour counts as free rather than
showing as taken, and it sends only the start time — the server computes the
duration. Sending a client-side duration would mean two parallel calculations,
and the day a service's minutes change the appointment would move with a stale
one. The services on the appointment are carried over unchanged.

Both actions only appear for an appointment that is still in the future and not
already cancelled, and both refresh the list afterwards so a just-cancelled
appointment stops showing as confirmed.

The day strip in the reschedule modal is its own small component rather than
the booking flow's DatePicker: that one reads the doctor uuid from route params
and would be undefined inside the dashboard.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 14:32:10 +03:30
hamedandClaude Opus 5 ae958eee73 feat(booking): show services and duration on the user panel appointment
A patient who booked in service mode could not see which services they had
reserved or how long the appointment was. Both now appear on the list card and in
both detail layouts, reading service_items and service_total_minutes from
GET /api/v1/appointments/user.

Every field is behind an explicit guard. Slot-mode appointments carry none of
them, and an unguarded map would crash the card for every slot-mode appointment,
taking the whole panel with it. A reserve entry shows its services but not a
duration, because it has no time.

Covered by Card.test.jsx: slot-mode appointments render unchanged, absent fields
(older backend) do not crash, reserve hides the duration, and a zero duration
produces no row.

Task: clinicpro/docs/new_feture/taskes/task-00b-nobat724-service-mode/

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 15:53:18 +03:30
hamedandClaude Opus 5 f3659f827e feat(booking): take service duration from the backend, not a parallel client sum
The service picker summed duration_minutes itself while the backend already
returns total_duration_minutes. Two sources of truth: when the formula changes to
solo/additional minutes, the site would keep showing the old number and the
patient would see a duration that does not match their appointment.

The picker runs before the date step and appointment-service-slots needs a date,
so it is called with today. total_duration_minutes does not depend on the date —
the backend computes it before touching that day's shifts, so the number is right
even when today is closed and start_times comes back empty.

The label reads "مدت تقریبی" until the server number arrives, then "مدت کل".
A missing field or a failed request falls back to the client sum with a
console.warn rather than blanking the step.

Staleness is derived from the selection key instead of reset in the effect body,
which also clears the set-state-in-effect lint warning.

Task: clinicpro/docs/new_feture/taskes/task-00b-nobat724-service-mode/

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 15:50:07 +03:30
hamed 8a24579a50 refactor: remove FEATURE flags for doctor ratings and sharing in components 2026-07-30 09:16:13 +03:30
hamed e87a0bae9b feat: add feature toggles for doctor share and ratings functionality 2026-07-30 09:05:45 +03:30
hamed eb83762016 Refactor blog components for improved tag filtering and related content display
- Added `getBlogTagFacets` API call to fetch blog tags based on city scope.
- Updated `BlogsPage` to read selected tag from URL and handle tag changes with URL updates.
- Modified `Title` component to display tags from the new API and reflect active tag state.
- Enhanced breadcrumb navigation to link categories to their respective pages.
- Adjusted related content section to display articles from the same category and fixed layout issues.
- Corrected heading hierarchy across various components for better SEO compliance.
- Ensured consistent styling and spacing in related content items.
2026-07-29 14:23:37 +03:30
hamed cea8982e6d feat: add script to generate default blog cover image
- Introduced a new script `make-blog-cover.mjs` to create a default cover image for blog posts.
- The image is generated in PNG format with dimensions 1200x630, suitable for Open Graph.
- Utilizes the site's branding colors and logo from `public/nobat724.svg`.
- Includes custom font styling using the Vazirmatn font.
- The generated image is saved to `public/assets/images/blog-default-cover.png`.
2026-07-27 19:07:30 +03:30
hamed b8514e64cf feat(breadcrumb): implement BreadcrumbList structure and validation for SEO compliance 2026-07-24 10:39:41 +03:30
hamed 0a2a2bfba3 fix(datePicker): hide next button for inactive months on larger screens 2026-07-23 19:43:02 +03:30
hamed ac3a84d55d feat: implement search URL building and enhance search functionality in Fields and RedirectLink components 2026-07-20 12:28:52 +03:30
hamed 30c3fe8637 feat: enhance SEO for listing and specialty pages, fix search autocomplete issue 2026-07-20 10:26:03 +03:30
hamedandClaude Fable 5 daf38c8631 feat(maintenance): show maintenance page when the API is in maintenance
The backend now answers 503 with code MAINTENANCE_MODE while maintenance is
on. Without this change a visitor got a red error toast over a broken page
client-side, and a silently empty page server-side, because fetchReq discards
the status and returns null on any failure.

- lib/maintenance.js detects the state by BOTH status 503 and the error code;
  a bare 503 can come from a reverse proxy and is not maintenance
- The axios interceptor checks it before the 401 branch, so a maintenance
  response never triggers the refresh-token path or logs the user out
- fetchReq redirects to /maintenance, with a silentMaintenance opt-out used by
  getStateInfo: that one runs inside generateMetadata and while rendering the
  maintenance page itself, where a redirect is either ineffective or loops
- redirect() works by throwing, so the try/catch blocks in the doctors,
  clinics and specialties pages now rethrow NEXT_REDIRECT instead of
  swallowing it
- clinicApi.js handles 503 too; it previously rendered maintenance as a clinic
  with zero doctors
- The page reuses the existing 404 design and is marked noindex

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 22:01:45 +03:30
hamed 1522ee8b73 feat: enhance clinic contact information display with new helper functions for phone, city, and state 2026-07-19 16:44:06 +03:30
hamed 10b5a19576 feat: add MapView component for displaying maps with markers 2026-07-19 16:43:59 +03:30
hamed e8a7bdf2fc feat: enhance doctor and clinic components with doctorTitle helper and improve rendering logic 2026-07-19 16:17:24 +03:30
hamed 29b8819021 refactor(docker): update Dockerfile for Next.js 15 and enhance build stages 2026-07-19 13:19:43 +03:30
hamed 4d7b87ee1a feat(blog): enhance metadata generation and blog fetching by city context 2026-07-19 09:02:14 +03:30
hamed c64a4a7d69 refactor(specialties): simplify specialty link generation logic 2026-07-19 07:52:58 +03:30
hamed fd48b48613 feat: add canonical URL handling and entity quality checks
- Implemented canonical URL strategies for city-specific domains and entities.
- Added helper functions for domain and city resolution.
- Created tests for canonical URL generation and domain resolution.
- Introduced entity quality checks for doctors and clinics to ensure meaningful content.
- Developed unique introductory texts for listing pages to avoid duplicate content.
- Established robots.txt policies for listing pages to manage indexing based on user filters.
- Enhanced specialty content with dynamic introductions and FAQs to improve SEO.
2026-07-19 07:52:50 +03:30
hamedandClaude Fable 5 36816eded2 fix(doctor): derive booking state from booking_locations
The profile decided "نوبت‌دهی غیرفعال است" from `doctor.active` alone, while the
page already had `booking_locations` — the more precise source, since the backend
only returns locations that are genuinely bookable. The two could disagree, and
for a doctor bookable only at a clinic they did.

A shared `bookingState` helper now drives both the desktop card and the mobile
bar: any location means bookable, the label comes from the earliest
`next_available_at`, and locations with no capacity yet read as "فعلاً نوبت خالی
ندارد" rather than disabled. With no locations at all it falls back to the
previous `doctor.active` / `free_turn` fields.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 16:25:37 +03:30
hamedandClaude Opus 4.8 b37096048c feat(booking): show only locations that can actually be booked that day
The site offered a "personal practice" for a doctor who has no personal address
at all — the schedule existed but its shifts pointed at the clinic's address, so
there was nowhere to go. The backend now filters those out; this consumes the
filtered contract and adds the per-day dimension.

- getBookingLocations takes an optional date and the appointment page refetches
  on it, merging available_on_date into the existing list rather than replacing
  it, so browsing the calendar never resets the user's choice.
- The browsed day had to be lifted out of the Date step: selectedDate is only
  set once a slot is confirmed, far too late to drive availability.
- A location closed on the chosen day renders disabled with «در این روز نوبت
  ندارد», and when every location is closed the step says so instead of showing
  an empty slot list. If the already-selected location closes, a notice appears
  with a link back to the picker — silently showing nothing was the failure mode
  worth avoiding.
- Doctor profile: workLocation in the Physician JSON-LD is limited to addresses
  that appear in booking_locations, since schema.org presents them as places a
  patient can attend. The address card still lists the others — they are real
  practice details — tagged «بدون نوبت‌دهی آنلاین».

Verified end-to-end with a temporary unused address on the test doctor: the
visible card listed both and tagged the unused one, while workLocation carried
only the bookable one. The row was removed afterwards.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 14:44:49 +03:30
hamedandClaude Opus 4.8 eba0c6a5ba feat(booking): let patients choose the booking location
A doctor now has one booking schedule per context — the personal practice plus
one per clinic — and every booking endpoint takes an optional clinic_uuid where
omitting it means the personal practice, not a wildcard. This site sent none, so
a clinic-only doctor showed no availability at all and a doctor working in both
places silently booked into the wrong one.

- services/response.js: getBookingLocations + clinic_uuid on slots,
  service-slots, booking-services and month-availability. The manual query
  building is kept so the service_item_uuids[] serialisation does not change.
- AppointmentPage owns the selected location; booking_mode and services are
  derived from it instead of a separate getBookingServices call, which drops a
  request. Changing location clears the selected service, slot and date, since
  a service from one location cannot be booked into another.
- New LocationSelect step, shown only when there is more than one location.
  The list arrives sorted by earliest free slot, so the first item is the
  default and is not re-sorted here.
- DatePicker drops its month cache when the location changes; otherwise the
  previous location's disabled days stayed on the calendar.
- The slot address now comes from the selected location rather than
  doctor.address, which does not contain clinic addresses.
- clinic_uuid rides through to the appointment payload, and
  /appointment/[doctorId]?clinic_uuid=… preselects a location.
- Doctor page JSON-LD gains availableService from the bookable services.
  openingHoursSpecification still needs a public weekly-hours endpoint.

Removed the dead locateVisit state, which was initialised true and never unset.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 13:48:31 +03:30
hamed 6f9640cea6 fix(payment): adjust date formatting to use UTC offset for consistency 2026-07-16 00:30:20 +03:30
hamed 165a0a2240 feat(appointment): implement service-based booking flow with service selection and slot adaptation 2026-07-15 23:48:40 +03:30
hamed 07d6526f74 fix(doctors): ensure active filter is correctly parsed as a number 2026-07-14 11:25:43 +03:30
hamed 58f05f8f4f feat(login): add redirect parameter to login links for better user experience 2026-07-12 10:06:15 +03:30
hamed 42ac6645d5 feat(doctor profile): implement unclaimed doctor handling and UI adjustments 2026-07-12 10:00:03 +03:30
hamedandClaude Opus 4.8 ed985f55fb feat(doctor map): replace Google Maps iframe with Leaflet (OSM)
The Google Maps iframe was blocked by CSP (frame-src falls back to
default-src 'self'). Switched the doctor-page location map to react-leaflet
with OpenStreetMap tiles — no iframe, and CSP already allows https image
tiles (img-src 'self' https:). Marker icons are bundled from the leaflet
package (no CDN). MapView is dynamically imported (ssr:false) since Leaflet
needs window.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 15:29:17 +03:30
hamedandClaude Opus 4.8 813225ff6d fix(doctor claim): mobile field is the account number, read-only
Mobile is prefilled from the logged-in user's cookie (userInfo.mobile_number)
and rendered read-only; removed it from editable form state/validation and
send that number in the claim body. Backend already enforces it must match
the account, so the field can't diverge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 15:16:27 +03:30
hamedandClaude Opus 4.8 f584f581e5 fix(auth): correct login detection + return to origin after login
- isUserLoggedIn() checked the access_token cookie, which is never set
  (access_token lives in memory / tokenStore; only userInfo + uuid are
  cookies). It therefore always returned false — the claim modal (and
  comment auth checks) kept showing the login prompt even when logged in.
  Now reads the userInfo cookie.
- Claim modal login link carries ?redirect=<current path>; after OTP login
  SendReq returns to that path (guarded to internal "/..." only, blocks
  protocol-relative //) instead of always going to "/".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 15:12:58 +03:30
hamedandClaude Opus 4.8 4fed9a8c57 feat(doctor): fix map render + claim modal captcha/mobile + owner delete
- Doctor page map: the visible locations card read doctor.address (empty
  from the detail endpoint) while coordinates live in the separately
  fetched addresses. Thread `addresses` (with map.latitude/longitude) down
  page → DoctorPage → DetailDoctor → Locations; card hidden when empty
- Claim modal: updated info-box text ("نوبت‌های این پروفایل عمومی و غیرخاص
  هستند")، added mobile field (validated, must match account), added ALTCHA
  widget (submit disabled until captcha resolves; payload sent as `altcha`)
- Owner delete: services.deleteDoctor + a guarded two-step "حذف این پروفایل"
  in the claim success screen (owner enforced server-side; 403/409 shown)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 15:04:28 +03:30
hamedandClaude Opus 4.8 d8ab26b4b7 feat(doctor): claim-profile section + modal for unclaimed IRIMC-imported doctors
- ClaimProfileSection (components/doctor/claim): shown only when
  doctor.owner_status === "unclaimed"; banner explains the profile is not
  yet managed by the doctor, button "تأیید و مدیریت این پروفایل"
- Modal: login prompt when logged out; otherwise first/last name,
  national code, Jalali birth-date (existing JalaliDatePicker) — posts to
  POST api/v1/doctor/{uuid}/claim (identity verified server-side via API.ir;
  no client call to API.ir, no token exposure)
- States: loading, per-field validation, server error (Persian envelope
  message), double-submit guard, success welcome message + redirect
- Shared component across main domain and all representative subdomains
- services/response.js: getDoctorClaimInfo / postDoctorClaim

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 11:44:40 +03:30
hamed 2fd0eec156 fix: update Altcha component to properly set attributes and handle state changes 2026-07-10 14:55:12 +03:30
hamed 58a69e3345 fix: remove unused FA_STRINGS and update attribute setting for language in Altcha component 2026-07-10 11:57:15 +03:30
hamed 9a3e84cc85 fix: refactor Altcha component to properly handle custom element registration and attribute setting 2026-07-10 11:42:26 +03:30
hamed 778876b1f7 Refactor code structure for improved readability and maintainability 2026-07-10 11:27:17 +03:30
hamed a56b7e8de5 fix: update handleChange to use optional chaining and add "use client" directive in Connect component 2026-07-08 18:24:33 +03:30
hamed ee47b535d8 Add empty AST cache file for version 0.8.44 with non-object root data 2026-07-08 17:38:32 +03:30
hamed 156d97ac03 fix: streamline className formatting in LayoutRegister and LogInPage components 2026-07-08 00:05:46 +03:30
hamed b04bb45ca1 Implement multi-domain SEO improvements:
- Add metadata to login and login-verify pages to prevent indexing.
- Update robots.txt to disallow additional sensitive paths.
- Enhance sitemap generation to filter by city and include accurate last modified dates.
- Refactor canonical URL generation to support multi-domain architecture, ensuring self-canonicalization for city domains.
- Remove deprecated CanonicalHandler component and streamline canonical URL handling.
- Introduce safe JSON-LD output to prevent XSS vulnerabilities.
- Add payment layout with appropriate metadata to prevent indexing.
- Conduct a comprehensive technical SEO audit and implement necessary fixes across the application.
2026-07-05 15:47:47 +03:30
hamed 522caf6b1b feat: implement PosterLight component and enhance SiteLogo functionality for improved poster rendering 2026-07-04 23:02:20 +03:30
hamed 39c09bfcd5 feat: integrate html-to-image library for enhanced image generation and QR code rendering 2026-07-04 22:39:33 +03:30
hamed 3c019932bd feat: redesign doctor sharing poster layout and enhance download functionality with dynamic file naming 2026-07-04 22:25:49 +03:30
hamed 199c843b08 feat: enhance poster download functionality by ensuring images load before PDF generation and adding QR readiness check 2026-07-04 21:50:57 +03:30
hamed 97cc4fdc69 feat: add domain parameter to sendCode for OTP requests to include city site name 2026-07-04 11:01:56 +03:30
hamed f5e06cfc13 fix: correct spelling of 'canceled' in payment status across components 2026-07-03 10:56:04 +03:30
hamed 90d4a871f5 feat: enhance payment flow with automatic redirection to appointment history after successful payment 2026-07-03 10:45:35 +03:30
hamed 5bec4848a2 feat: unify currency display to toman across the application 2026-07-03 10:31:16 +03:30