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>
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>
- 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>
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>
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>