- 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.
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>
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>
- 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>
- 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>
Connect the existing doctor-page review UI to the rebuilt backend contract
(see clinicpro feat/rating-multidimensional).
- page.js fetches the rating aggregate alongside comments and passes
rateAggregate down to the chart (point / satisfaction / 5 dimensions).
- Submit form sends the five dimensions with doctor_uuid; comment/reply
send {doctor_uuid, comment, parent}; 401/403 ERR_RATING_NOT_ELIGIBLE
surface friendly guidance.
- Like/dislike call POST /like/{uuid} with value and update from the
response; replies render nested.
- ModalAnswer gates the submit button on GET /rate/{uuid}/eligibility.
- services/response.js: getRateEligibility, postCommentsLike(uuid, value),
drop unused patchDoctorRate. Fix hardcoded modal title; empty-state for
no comments. Remove orphaned AnswerField.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>