Files
soroush.asadi 9c93b4e51a
CI/CD / CI · dotnet build (push) Successful in 21s
CI/CD / Deploy · drsousan (push) Successful in 28s
feat(gallery+editor): dedicated /gallery page, homepage teaser, in-content images
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>
2026-06-11 01:26:35 +03:30

109 lines
5.1 KiB
C#

using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using DrSousan.Api.Data;
using DrSousan.Api.Models;
namespace DrSousan.Api.Pages;
public class IndexModel : PageModel
{
private readonly AppDbContext _db;
public IndexModel(AppDbContext db) => _db = db;
// Hero
public Dictionary<string, string> Hero { get; private set; } = new();
public Dictionary<string, string> About { get; private set; } = new();
public Dictionary<string, string> Contact { get; private set; } = new();
// Collections
public List<Service> Services { get; private set; } = new();
public List<GalleryItem> Gallery { get; private set; } = new();
public int GalleryTotal { get; private set; } = 0;
public List<Testimonial> Testimonials { get; private set; } = new();
public List<BlogPost> RecentPosts { get; private set; } = new();
public List<Faq> Faqs { get; private set; } = new();
public async Task OnGetAsync()
{
var settings = await _db.SiteSettings.ToListAsync();
Hero = settings.Where(s => s.Section == "hero")
.ToDictionary(s => s.Key, s => s.Value);
About = settings.Where(s => s.Section == "about")
.ToDictionary(s => s.Key, s => s.Value);
Contact = settings.Where(s => s.Section == "contact")
.ToDictionary(s => s.Key, s => s.Value);
Services = await _db.Services
.Where(s => s.IsActive)
.OrderBy(s => s.Order)
.ToListAsync();
// Homepage shows only a teaser of 3; full set lives on /gallery
var galleryQuery = _db.GalleryItems.Where(g => g.IsActive);
GalleryTotal = await galleryQuery.CountAsync();
Gallery = await galleryQuery
.OrderBy(g => g.Order)
.Take(3)
.ToListAsync();
Testimonials = await _db.Testimonials
.Where(t => t.IsActive)
.OrderByDescending(t => t.CreatedAt)
.ToListAsync();
RecentPosts = await _db.BlogPosts
.Include(p => p.Category)
.Where(p => p.IsPublished)
.OrderByDescending(p => p.PublishedAt)
.Take(3)
.ToListAsync();
Faqs = await _db.Faqs
.Where(f => f.IsActive)
.OrderBy(f => f.Order)
.ToListAsync();
// Expose site name for layout
var siteName = Hero.GetValueOrDefault("name", "دکتر سوسن آل‌طه");
ViewData["SiteName"] = siteName;
ViewData["Title"] = $"{siteName} | متخصص زیبایی پوست تهران";
}
// ── Helpers for the view ─────────────────────────────────────────────────
public static string Stars(int count)
{
var s = string.Concat(Enumerable.Repeat("⭐", count));
return string.IsNullOrEmpty(s) ? "⭐⭐⭐⭐⭐" : s;
}
public static string ServiceIcon(string title) => title switch
{
var t when t.Contains("لیزر") => "✨",
var t when t.Contains("بوتاکس") || t.Contains("فیلر") => "💉",
var t when t.Contains("مزو") => "💊",
var t when t.Contains("پاکسازی") => "🧴",
var t when t.Contains("مشاوره") => "🩺",
var t when t.Contains("هایفو") || t.Contains("HIFU") => "🔬",
var t when t.Contains("RF") => "⚡",
_ => "🌟"
};
public static string ServiceIconSvg(string title) => title switch
{
var t when t.Contains("لیزر") =>
"""<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>""",
var t when t.Contains("بوتاکس") || t.Contains("فیلر") =>
"""<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M8 12s1.5 2 4 2 4-2 4-2"/><line x1="9" y1="9" x2="9.01" y2="9"/><line x1="15" y1="9" x2="15.01" y2="9"/></svg>""",
var t when t.Contains("مزو") =>
"""<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>""",
var t when t.Contains("پاکسازی") =>
"""<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="16"/><line x1="8" y1="12" x2="16" y2="12"/></svg>""",
var t when t.Contains("مشاوره") =>
"""<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/></svg>""",
_ =>
"""<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>"""
};
}