Adds a "Measurement ID" input under admin → Site Identity. The value
is stored as identity/ga_id (existing bulk-settings endpoint, no API
change). When set, _Layout injects the GA4 gtag.js snippet into <head>
on every page (home, blog, gallery, posts). Empty value = disabled.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Editor: new 🎠 اسلایدر toolbar button — pick multiple images (min 2),
uploads them all, inserts a <div class="post-carousel" data-carousel>
block at the cursor. Editor preview shows a tidy filmstrip with the
non-functional arrows/dots hidden.
Public post page: carousel CSS (scroll-snap track) + JS that wires up
prev/next arrows, clickable dots, and native touch swipe. Single-image
blocks auto-collapse their controls.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Homepage gallery:
- Show only 3 before/after samples as a teaser (was: all items)
- Add "مشاهده گالری کامل (N نمونه)" CTA when more than 3 exist
- Remove the now-pointless category tabs from the teaser
New /gallery page:
- Full before/after grid with category filter tabs (deduped from data)
- Responsive cards with قبل/بعد labels + captions, empty state
- Added to sitemap.xml (priority 0.8)
Blog content editor:
- New 🖼 تصویر toolbar button inserts an uploaded image at the cursor
(direct upload, no forced crop) — for richer post bodies
- Responsive img styling on the public post page
Note: the filler-lab-soorat cover not showing is a data issue — that
post has an empty featuredImage in the DB (verified); re-upload + save
fixes it. The upload/save path itself is correct.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Public /blog: the handler param was named `page`, which is a reserved
route token in Razor Pages and never binds — so every page silently
showed the same first 10 posts. Renamed the query param to `pg`
([FromQuery(Name="pg")]) and updated the pagination links to match.
Admin: the posts table had no pagination and dumped all rows at once.
Added client-side pagination (10/page) with a prev/next + numbered bar
over the already-loaded posts array.
Verified: public page1=10/page2=4 with zero overlap; admin shows
‹ 1 2 › with correct row counts and active state per page.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
api() returns null on HTTP error; the save block now checks the return
value before closing the modal and showing the success toast.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Index.cshtml: siteBaseUrl was referencing itself as fallback; replaced
with literal default "https://draletaha.ir"
- Program.cs: removed UseForwardedHeaders call — ForwardedHeadersOptions
unavailable in this SDK config; SITE_BASE_URL env var handles base URL
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add UseForwardedHeaders middleware so Request.Scheme = "https" behind nginx
- Add SITE_BASE_URL env var fallback for sitemap.xml, robots.txt, and all
Razor page canonical/og URLs — set it to https://draletaha.ir in .env
- Add og:image to homepage using hero photo
- Add SITE_BASE_URL to docker-compose.yml environment block
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Backend:
- HealthRequest model: TrackingCode (DR-XXXXXX), Diagnosis,
DoctorReply, RepliedAt fields
- Runtime migration: ALTER TABLE adds 4 new columns to existing DB
- POST /api/health-request: auto-generates tracking code, returns it
- PUT /api/health-requests/{id}/reply: doctor sets diagnosis + reply
- GET /api/health-request/track/{code}: public lookup by tracking code
- GET /api/health-requests?phone=: filter history by phone number
Admin panel:
- Request table shows tracking code column (gold badge)
- Detail modal (680px): tracking code header, patient info, full message
- Previous doctor reply shown in green box if exists
- Reply form: diagnosis input + textarea for doctor message
- History panel: all requests from same phone, click to switch
- 'پاسخ / مشاهده' button opens reply modal directly
Frontend:
- After form submit: shows tracking code in green box to user
(format: DR-XXXXXX, stays visible 8 seconds)
- Box auto-hides and form resets after timeout
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1. applyCrop() — mime variable was declared INSIDE toBlob callback
but used as an argument to toBlob() (outer scope) → ReferenceError.
Fix: declare _mime, _quality, _ext BEFORE out.toBlob() call.
2. loadSiteIdentity() — crashed when identity section had no rows
(data was null/non-array). Fix: safe Array.isArray guard + catch.
3. Header logo: show logo image + | + site name side by side
when logo is configured (was showing one OR the other).
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Problem: cropper always called out.toBlob(..., 'image/jpeg') regardless
of the original file type, silently converting PNGs to JPGs.
Fix:
- openCropper() now stores file.type and file.name on the cropper object
- applyCrop() uses the stored mime type for toBlob() and the filename
- Quality param only passed for lossy formats (jpeg/webp), not for PNG/GIF
- uploadImage() accept list expanded: svg, ico allowed
- Server-side: .svg and .ico added to allowed extensions
Result: PNG stays PNG, WebP stays WebP, ICO stays ICO.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Admin panel:
- New 'هویت سایت' page under تنظیمات in sidebar
- Upload logo (PNG transparent, 200×60px recommended)
- Upload favicon (PNG/ICO, 32×32 or 64×64px)
- Live preview panel shows how logo looks in header
and how favicon looks in a browser tab mockup
- Saved to SiteSettings with section='identity', key='logo'/'favicon'
Frontend (_Layout.cshtml):
- Injects AppDbContext to load identity settings per request
- If logo is set: shows <img> in header instead of text
- If favicon is set: uses uploaded file as <link rel="icon">
- Falls back to text / favicon.ico when not configured
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Problem: two parallel booking systems:
1. 'رزرو نوبت آنلاین' contact form — never saved data (fake submit)
2. Health section inline form — saved to /api/health-request
Fix:
- Contact form now POSTs to /api/health-request (real save)
- Added ids to all form inputs so JS can read them
- Category auto-detected from service dropdown (سلامت عمومی → health)
- Health section 'درخواست...' buttons now scroll to contact form
and pre-select the right category — no duplicate inline form
- Removed the duplicate healthFormWrap + submitHealthForm()
- All reservations visible in admin under 'درخواستها'
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Every row now has a 'مشاهده' button regardless of handled status
- Opens a modal with: name, phone, email, category, date, status,
and full message text (no truncation)
- Modal includes 'علامتگذاری' button if request is still pending
- Message column in table kept short (truncated) as a preview only
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
archive.ubuntu.com is unreachable from the build server, causing
apt-get to time out and fail every build. curl was only used for
the HEALTHCHECK. Replace with a zero-dependency bash TCP check:
bash -c 'echo > /dev/tcp/localhost/8080'
No packages needed, no external network access required.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Post.cshtml.cs: load hero image, tag from SiteSettings in SetViewDataAsync
- Post.cshtml: show <img> with hero image in .doc-avatar when set,
fall back to emoji only if no image is configured
- .doc-avatar: circular crop with object-fit:cover, gold border
- doc-title now uses HeroTag from settings (not hardcoded)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1. Beauty category icon: was pink (#C2185B), now uses site primary gold
(var(--gold) / var(--gold-pale)) to match brand color
2. Dashboard now shows health requests:
- Two new stat cards: total patients + pending requests (clickable)
- 'آخرین درخواستها' mini-table showing last 6 requests
- Sidebar badge updates from dashboard load too
- loadDashboard() now fetches /api/patients + /api/health-requests
3. Blog image edit fix:
- applyCrop() now captures inputId/previewId BEFORE closeCropper()
to prevent any potential race condition when replacing images
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Backend:
- Patient model: name, phone, email, age, weight, height, gender,
blood type, disease history, allergies, medications, notes, category
- PatientVisit model: title, content, prescription, visit type,
visit/next-visit dates, linked to patient (cascade delete)
- HealthRequest model: public form submissions for beauty/health care
- Runtime SQLite migrations for all 3 new tables
- Full CRUD API: /api/patients, /api/patients/{id}/visits,
/api/health-requests (public POST + admin GET/PUT/DELETE)
Admin panel:
- 'پرونده بیماران' page: list, search, filter by category (beauty/health)
- Patient profile page: personal info + medical history + visits timeline
- Add/edit patient modal with all medical fields
- Add visit modal: type, date, clinical notes, prescription, next visit
- 'درخواستها' page: manage public health requests, mark as handled
- Badge counter for unhandled requests in sidebar
Frontend (SEO):
- New #health-care section with Schema.org MedicalClinic markup
- Two category cards: زیبایی پوست and سلامت عمومی
- Feature lists with checkmarks per category
- Inline request form that submits to /api/health-request
- Mobile responsive (single column on small screens)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Instead of overlaying text on top of the image (hard to read),
restructure each gallery card to flex-column:
- Image section (.gallery-img-wrap) on top with aspect-ratio:4/3
- Before/After labels row (.ba-labels) below the image, full text visible
- Caption (.gallery-caption) below that, with padding and border
Labels now show full text 'قبل از درمان' / 'بعد از درمان' in a clean
row under the split image — never overlapping, always readable.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The .hidden class only covered .modal-overlay and .fm-overlay.
Without the rule, display:flex on .cropper-overlay overrode .hidden
and the modal showed immediately on every page load.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Admin: all upload buttons now open a crop-before-upload modal
- Canvas-based cropper (no external library)
- Ratio presets: 1:1, 4:3, 16:9, 3:4, free
- Drag to move crop box, drag corners to resize
- Touch support for mobile
- Crops client-side then uploads the result
- Frontend gallery: ba-label (قبل/بعد) now:
- Centered horizontally (block + width 100%)
- Wraps to multiple lines (white-space:normal)
- Responsive — never overflows or gets clipped
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add "Backup database" step that copies drsousan.db out of the
running container to /opt/drsousan-backups/ before any container
changes, keeping the last 10 backups
- Replace --force-recreate (broken on this Docker version) with
explicit docker stop + docker rm before docker compose up
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
docker compose up --force-recreate only works when Compose owns the
container. If the container was started outside Compose (e.g. manually
via docker restart), Compose can't recreate it and errors with
"container name already in use". Explicitly stopping and removing it
first handles both cases cleanly.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Without this flag, the deploy fails with "container name already in use"
when a container with the same name exists from a previous run.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Gallery section now fetches /api/gallery and renders real items
instead of hardcoded placeholders
- Before+after pairs render as side-by-side split with قبل/بعد labels
- Single imageUrl items render as a standard gallery card
- Tab buttons now filter items by category via data-cat attribute
- CSS added for .before-after, .ba-half, .ba-divider, .ba-label, .gallery-caption
- Fixes applied to correct file (Index.cshtml Razor page, not root index.html)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Prevents runner workspace directory name from being used as project name,
which caused Meezi containers to be treated as orphans and stopped on deploy.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removed <br> between name parts and added white-space:nowrap.
Adjusted clamp min from 2.2rem to 1.6rem so it scales down on
small screens without wrapping.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>