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>
adaptServiceSlots labelled its single session "زمانهای خالی" and set end_time to
the last slot's *start* time, so the range shown was shorter than reality. It now
uses the last slot's end_time (falling back to start + total_duration_minutes) and
labels the session with the actual range.
Shift separation was requested but is not implementable from this payload: with a
flat start_times list, a gap between shifts is indistinguishable from a gap left
by a booked appointment. The normal step is duration + buffer, so any threshold
that splits shifts either splits every slot of a long service (step above the
threshold) or invents tabs around booked appointments. A fabricated tab claims a
shift that does not exist, which is worse than one correct tab. Real grouping
belongs to the server-side endpoint task 06 adds.
Also repairs three tests that had been red since adaptSlots changed shape: they
still asserted the old { morning, evening } contract while the function returns an
array of sessions. Suite goes from 4 failures to 1 (an unrelated pre-existing
getStateInfo network timeout).
Task: clinicpro/docs/new_feture/taskes/task-00b-nobat724-service-mode/
Slot-mode contract: adaptSlots untouched
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
- 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.
- Updated modification times and AST hashes for several files in the manifest.json.
- Added new entries for `app/api/revalidate/route.js`, `components/blog/detail/Faq.js`, `components/blog/detail/Sources.js`, and other files related to blog functionality.
- Introduced a new AST cache file for the blog frontend sync and default cover prompt, including detailed node and edge relationships.
- 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`.
- Updated modification times and AST hashes for several JavaScript files in the app/blog, app/component, app/doctor, and components/common directories.
- Added a new AST cache file for a data JSON that was skipped due to a non-object root.
Deactivated doctors are now absent from the public list, but their
/doctor/{uuid} page still loaded. getDoctor() now treats the backend's
raw `is_active === false` as not-found, so the profile 404s like the
list — matching the site's "hide inactive doctors" behavior.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The public list GET /api/v1/doctors only excluded inactive doctors
when an explicit `active` filter was passed; with no param it returned
everyone (deactivated doctors just ranked lower). Deactivated doctors
(admin toggled active_doctor_appointment off) leaked onto nobat724.
- DoctorRepository::findWithFilters: default (no `active` param) now
filters activeDoctorAppointment = true. The active=1 (bookable) and
active=0 (admin, inactive-only) escape hatches are unchanged.
- Doctor::toDetailArray: expose raw `is_active` (= activeDoctorAppointment,
independent of schedule) so public clients can 404 a deactivated
doctor's profile page; distinct from `active` (flag && has_schedule).
- Tests + docs/api/doctor.md updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Updated mtime and ast_hash for several files in manifest.json
- Added new AST cache files for the clinic contact info fix and maintenance mode client prompts
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>
- Updated community IDs for various components in graph.json to reflect correct associations.
- Added new function `getPublishedDoctors()` in app/sitemap.js and established relationships with existing functions.
- Adjusted source locations for several functions in app/sitemap.js to ensure accurate mapping.
- Enhanced canonical URL handling in lib/getCanonicalUrl.js to prevent incorrect canonicalization for paginated specialty pages.
- Updated manifest.json with new modification times and AST hashes for affected files.
- Created a new JSON file for the blog city scoping activation prompt, including nodes and edges representing the document structure.
- Added a new JSON file for the SEO post-deploy verification prompt, detailing nodes and edges related to SEO verification tasks.
P1-P11 of the live SEO audit are implemented and verified against a local
production build, so the audit file becomes a reference rather than a
task list: it now records what shipped, the root causes that differed
from the original hypotheses, and the deliberate trade-offs.
Remaining work is split into smaller prompts, ordered by dependency:
- seo-post-deploy-verification: the acceptance criteria were "curl on
production" but were only run against a local build
- blog-city-scoping-activate: blocked on the backend blog city column
- sitemap-simplify-with-city: drops the 35-sweep workaround once the
doctors list exposes city
Each names its blocking dependency and carries reference numbers so a
regression is visible.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 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>
The doctor page listed where a doctor works but never when, so search engines
had no working hours for any location. booking_locations now carries
opening_hours per context, so each MedicalClinic in the Physician JSON-LD gets
its own openingHoursSpecification, matched to the address by uuid.
Verified against the rendered page: the clinic location emits five shifts with
schema.org weekday URLs alongside the availableService entries.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>