Commit Graph
639 Commits
Author SHA1 Message Date
hamed 8eb55679b2 feat(sitemap): optimize doctor URL fetching and add sitemap index threshold warning 2026-07-19 09:10:04 +03:30
hamed 488e5eb0a4 Add JSON files for blog city scoping and SEO post-deploy verification prompts
- 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.
2026-07-19 09:02:23 +03:30
hamed 4d7b87ee1a feat(blog): enhance metadata generation and blog fetching by city context 2026-07-19 09:02:14 +03:30
hamed 88782e70c2 test(extractEntityCityId): prioritize address over top-level city in entity extraction 2026-07-19 08:54:00 +03:30
hamedandClaude Opus 4.8 21f5f07bc6 chore(prompt): record SEO audit and split remaining work
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>
2026-07-19 08:09:21 +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 dfcf62735b chore(graphify): rebuild code graph after day-aware locations
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 14:44:51 +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 f4dd73e55e chore(graphify): rebuild code graph after opening-hours change
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 13:53:31 +03:30
hamedandClaude Opus 4.8 0eb172517f feat(seo): emit openingHoursSpecification per work location
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>
2026-07-18 13:53:28 +03:30
hamedandClaude Opus 4.8 8d0902f35c chore(graphify): rebuild code graph after booking-location change
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 13:48:53 +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
hamedandClaude Opus 4.8 effa264027 docs(prompt): add booking-locations multi-context prompt
Companion to the clinicpro context-separation change. A doctor now has one
booking schedule per context (personal practice + one per clinic), and every
booking endpoint takes an optional clinic_uuid where omitting it means the
personal practice — not a wildcard.

This site sends no clinic_uuid anywhere, so today it shows no availability at
all for clinic-only doctors and silently books into the wrong location for
doctors who work in both. The prompt covers the new
/api/v1/appointment-booking-locations contract, threading the selected location
through the booking state, and the JSON-LD follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 13:38:02 +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 d5845bf84d refactor(dateTime): streamline slot handling and improve session management 2026-07-15 19:16:24 +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
hamed ce05e464c4 Merge branch 'feature/doctor-claim' 2026-07-11 20:12:07 +03:30
hamed 8f1c63d3b1 Refactor code structure for improved readability and maintainability 2026-07-11 17:50:54 +03:30
hamed f9c1f2994e Implement feature X to enhance user experience and optimize performance 2026-07-11 17:03:38 +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 c007ffa2b3 fix(doctor page): addresses were mis-extracted from double-nested response
GET /api/v1/clinic-pro/doctor-addresses/{id} returns { data: { data: [...] } },
but getDoctorAddresses read json.data (the wrapper object, not the array), so
addresses.length/.map were undefined — the locations card + map never rendered
even when the doctor had an address. Extract json.data.data.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 15:25:39 +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 ab2ab0dea2 feat: enhance domain handling for global representatives
- Updated `getStateInfo` to fetch site context for domains not in city.json, returning `repContext` with representative details.
- Implemented caching for site context requests to optimize performance.
- Modified doctor and clinic listing pages to pass the `domain` parameter when fetching data for global representatives.
- Adjusted metadata generation in layout and pages to reflect representative branding based on `repContext`.
- Added documentation for the new functionality in `.claude/prompt/global-rep-domain-site.md`.
2026-07-09 07:34:09 +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 082f54cf4d fix: update contact information and WhatsApp numbers in city.json 2026-07-08 12:25:56 +03:30
hamed 92b8f474c1 Add SEO fields to city.json for local branding optimization
- Introduced a new `title` field for each city record, incorporating local brand names.
- Rewrote `slogan` for each city to reflect startup tone and include city keywords.
- Optimized `keywords` with relevant local and long-tail search terms.
- Ensured compliance with SEO best practices and maintained JSON structure.
2026-07-08 11:57:31 +03:30
hamed 156d97ac03 fix: streamline className formatting in LayoutRegister and LogInPage components 2026-07-08 00:05:46 +03:30
hamed a4e4228dc1 fix: update removeToken function to handle cookie removal with domain options 2026-07-07 23:52:50 +03:30
hamed 73c10f53ec fix: handle undefined slug and doctorId in cache functions 2026-07-07 11:47:44 +03:30
hamed df7fbb7530 Refactor code structure for improved readability and maintainability 2026-07-06 15:10:19 +03:30
hamedandClaude Fable 5 beb050aee2 fix(docker): force devDependencies in deps stage (--include=dev)
Coolify injects NODE_ENV=production / npm omit=dev at build time, which
made 'npm ci' skip devDependencies. babel-plugin-react-compiler is a
devDependency and Next 16's reactCompiler:true requires resolving it, so
the build failed with:
  Failed to resolve package babel-plugin-react-compiler ...
  React compiler is enabled in next.config.js.

Reproduced locally with npm_config_omit=dev (react-compiler MISSING with
plain npm ci, PRESENT with --include=dev). --include=dev overrides the
platform's omit config so all build-time devDeps are installed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 11:31:46 +03:30
hamedandClaude Fable 5 2fbd24b4aa chore: remove unused NEXT_PUBLIC_CLIENT_ID/SECRET env vars
The OAuth flow uses grant_type=mobile with a server-issued grant
(app/api/auth/token/route.js); no client_id/client_secret is sent at
runtime. These vars were dead — removed from Dockerfile args/env,
docker-compose, nixpacks, and docs. Also silences the Coolify
'variable not set' build warnings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 11:09:19 +03:30
hamedandClaude Fable 5 ab1102ec1c chore(docker): update Dockerfile for Next 16, drop duplicate lowercase dockerfile
- update stage comments to Next 16; note Turbopack default build
- npm ci --no-audit --no-fund for faster reproducible install
- add HEALTHCHECK (referenced by docker-compose, previously missing)
- parametrize node version via ARG NODE_VERSION
- remove duplicate case-variant 'dockerfile' (collided with 'Dockerfile'
  on case-insensitive FS, would be two files on Linux/Coolify)

Verified: docker build + run, all routes 200, multi-domain Host routing,
canonical in <head>, container healthcheck reports healthy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 10:37:54 +03:30
hamedandClaude Fable 5 3d861c1f2d Merge branch 'upgrade/next-16': upgrade Next.js 15->16, React 18->19
- next 15.5.7 -> 16.2.10, react 18.3.1 -> 19.2.7
- middleware.js -> proxy.js (Next 16 convention)
- next lint -> eslint@9 flat config
- enable React Compiler (Turbopack built-in babel)
- cacheComponents evaluated and left disabled (multi-domain headers() incompatibility)

Build, 69 vitest tests, and production smoke test all pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 10:21:37 +03:30
hamed e8e3d7bfd8 Add cache index and graph files for improved data handling
- Created stat-index.json to store metadata for various data files, including size, modification time, and hash.
- Added graph.html and graph.json files to the graphify-out directory for enhanced graph representation.
2026-07-06 10:12:59 +03:30
hamedandClaude Fable 5 1d2ad48138 docs: document why cacheComponents stays disabled
Enabling cacheComponents fails the build (/_not-found: uncached data
outside <Suspense>) because the multi-domain layout reads headers()
(host) in layout + generateMetadata. Safe adoption would need broad
Suspense boundaries and risks cross-domain city content leakage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 10:05:24 +03:30
hamedandClaude Fable 5 08dd9e408b feat: enable React Compiler on Next 16
Auto-memoization via babel-plugin-react-compiler (stable in Next 16),
compiled through Turbopack's built-in babel. Build and test suite pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 10:02:58 +03:30