merge: kub-stage → production (1:1 square images + site pages + landing animation fix)
Build and Deploy to Production / build-and-deploy (push) Has been cancelled
Build and Deploy to Production / build-and-deploy (push) Has been cancelled
This commit is contained in:
@@ -22,6 +22,7 @@ using CMSMicroservice.Protobuf.Protos.NetworkMembership;
|
||||
using CMSMicroservice.Protobuf.Protos.Commission;
|
||||
using CMSMicroservice.Protobuf.Protos.AppVersion;
|
||||
using CMSMicroservice.Protobuf.Protos.SitePage;
|
||||
using CMSMicroservice.Protobuf.Protos.SitePageSettings;
|
||||
using CMSMicroservice.Protobuf.Protos.BlogPost;
|
||||
using CMSMicroservice.Protobuf.Protos.BlogCategory;
|
||||
using CMSMicroservice.Protobuf.Protos.DiscountProduct;
|
||||
@@ -83,6 +84,8 @@ public static class ConfigureServices
|
||||
services.AddScoped<DiscountProductService>();
|
||||
services.AddScoped<DiscountCartService>();
|
||||
services.AddScoped<DiscountOrderService>();
|
||||
// Site Page Settings Service (simplified page management)
|
||||
services.AddScoped<SitePageSettingsService>();
|
||||
// Device detection: very light, dependency-free
|
||||
services.AddTransient<IDeviceDetector, DeviceDetector>();
|
||||
// PDF generation (Chromium only)
|
||||
@@ -133,6 +136,7 @@ public static class ConfigureServices
|
||||
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.Commission.CommissionContract.CommissionContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.AppVersion.AppVersionContract.AppVersionContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.SitePage.SitePageContract.SitePageContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<SitePageSettingsContract.SitePageSettingsContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.BlogPost.BlogPostContract.BlogPostContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.BlogCategory.BlogCategoryContract.BlogCategoryContractClient>);
|
||||
// Image Resolver Service
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DateTimeConverterCL" Version="1.0.0" />
|
||||
<!-- Replace all FrontOffice.BFF protobuf packages with CMS protobuf -->
|
||||
<PackageReference Include="Foursat.CMSMicroservice.Protobuf" Version="0.0.179" />
|
||||
<PackageReference Include="Foursat.CMSMicroservice.Protobuf" Version="0.0.180" />
|
||||
<!--<ProjectReference Include="../../../CMS/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj" />-->
|
||||
<PackageReference Include="MudBlazor" Version="8.14.0" />
|
||||
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@attribute [Route(RouteConstants.About.Index)]
|
||||
@inject NavigationManager Navigation
|
||||
@inject SitePageService SitePageService
|
||||
@inject SitePageSettingsService PageSettingsService
|
||||
|
||||
<PageTitle>درباره ما | کارا بازار سلامت</PageTitle>
|
||||
|
||||
@@ -66,40 +66,22 @@ else
|
||||
<MudPaper Elevation="2" Class="pa-6 text-center h-100">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Visibility" Size="Size.Large" Color="Color.Primary" Class="mb-4" />
|
||||
<MudText Typo="Typo.h5" Class="mb-3">
|
||||
@(_visionSection?.Title ?? "چشمانداز")
|
||||
@(_settings?.VisionTitle ?? "چشمانداز")
|
||||
</MudText>
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary">
|
||||
@(_settings?.VisionText ?? "تبدیل شدن به پیشروترین پلتفرم مدیریت تیمهای فروش در منطقه، با ارائه راهکارهای هوشمند و کاربرپسند برای کسبوکارهای کوچک و بزرگ.")
|
||||
</MudText>
|
||||
@if (!string.IsNullOrWhiteSpace(_visionSection?.HtmlContent))
|
||||
{
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary">
|
||||
@((MarkupString)_visionSection.HtmlContent)
|
||||
</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary">
|
||||
تبدیل شدن به پیشروترین پلتفرم مدیریت تیمهای فروش در منطقه، با ارائه راهکارهای هوشمند و کاربرپسند برای کسبوکارهای کوچک و بزرگ.
|
||||
</MudText>
|
||||
}
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudPaper Elevation="2" Class="pa-6 text-center h-100">
|
||||
<MudIcon Icon="@Icons.Material.Filled.TrackChanges" Size="Size.Large" Color="Color.Success" Class="mb-4" />
|
||||
<MudText Typo="Typo.h5" Class="mb-3">
|
||||
@(_missionSection?.Title ?? "مأموریت")
|
||||
@(_settings?.MissionTitle ?? "مأموریت")
|
||||
</MudText>
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary">
|
||||
@(_settings?.MissionText ?? "توانمندسازی کسبوکارها از طریق فناوریهای نوین، سادهسازی فرآیندهای پیچیده و ایجاد فرصتهای جدید برای رشد و توسعه پایدار.")
|
||||
</MudText>
|
||||
@if (!string.IsNullOrWhiteSpace(_missionSection?.HtmlContent))
|
||||
{
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary">
|
||||
@((MarkupString)_missionSection.HtmlContent)
|
||||
</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary">
|
||||
توانمندسازی کسبوکارها از طریق فناوریهای نوین، سادهسازی فرآیندهای پیچیده و ایجاد کارا بازار سلامتهای جدید برای رشد و توسعه پایدار.
|
||||
</MudText>
|
||||
}
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
@@ -111,21 +93,21 @@ else
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<MudText Typo="Typo.h3" Align="Align.Center" Class="mb-8">ارزشهای ما</MudText>
|
||||
<MudGrid Spacing="3" Justify="Justify.Center">
|
||||
@if (_valueSections.Any())
|
||||
@if (_valueImages.Any())
|
||||
{
|
||||
@foreach (var value in _valueSections)
|
||||
@foreach (var value in _valueImages)
|
||||
{
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-xl text-center">
|
||||
@if (!string.IsNullOrWhiteSpace(value.IconName))
|
||||
{
|
||||
<MudIcon Icon="@value.IconName" Size="Size.Large" Color="Color.Primary" Class="mb-3" />
|
||||
<MudIcon Icon="@($"Icons.Material.Filled.{value.IconName}")" Size="Size.Large" Color="Color.Primary" Class="mb-3" />
|
||||
}
|
||||
<MudText Typo="Typo.h6" Class="mb-2">@value.Title</MudText>
|
||||
@if (!string.IsNullOrWhiteSpace(value.HtmlContent))
|
||||
@if (!string.IsNullOrWhiteSpace(value.Description))
|
||||
{
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">
|
||||
@((MarkupString)value.HtmlContent)
|
||||
@value.Description
|
||||
</MudText>
|
||||
}
|
||||
</MudPaper>
|
||||
@@ -199,9 +181,9 @@ else
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<MudText Typo="Typo.h3" Align="Align.Center" Class="mb-8">تیم ما</MudText>
|
||||
<MudGrid Spacing="4" Justify="Justify.Center">
|
||||
@if (_teamSections.Any())
|
||||
@if (_teamImages.Any())
|
||||
{
|
||||
@foreach (var member in _teamSections)
|
||||
@foreach (var member in _teamImages)
|
||||
{
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudPaper Elevation="2" Class="pa-4 text-center">
|
||||
@@ -222,10 +204,10 @@ else
|
||||
{
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary mb-2">@member.Subtitle</MudText>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(member.HtmlContent))
|
||||
@if (!string.IsNullOrWhiteSpace(member.Description))
|
||||
{
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">
|
||||
@((MarkupString)member.HtmlContent)
|
||||
@member.Description
|
||||
</MudText>
|
||||
}
|
||||
</MudPaper>
|
||||
|
||||
@@ -5,24 +5,22 @@ namespace FrontOffice.Main.Pages;
|
||||
public partial class About
|
||||
{
|
||||
private bool _loading = true;
|
||||
private SitePageDto? _page;
|
||||
private SitePageSectionDto? _visionSection;
|
||||
private SitePageSectionDto? _missionSection;
|
||||
private List<SitePageSectionDto> _valueSections = new();
|
||||
private List<SitePageSectionDto> _teamSections = new();
|
||||
private PageSettingsDto? _page;
|
||||
private AboutSettings? _settings;
|
||||
private List<PageSettingsImageDto> _valueImages = new();
|
||||
private List<PageSettingsImageDto> _teamImages = new();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_page = await SitePageService.GetByKeyAsync("about");
|
||||
_page = await PageSettingsService.GetPageAsync("about");
|
||||
|
||||
if (_page != null)
|
||||
{
|
||||
_visionSection = _page.GetSection("vision");
|
||||
_missionSection = _page.GetSection("mission");
|
||||
_valueSections = _page.GetSections("value-").ToList();
|
||||
_teamSections = _page.GetSections("team-").ToList();
|
||||
_settings = _page.GetSettings<AboutSettings>();
|
||||
_valueImages = _page.GetImages("values");
|
||||
_teamImages = _page.GetImages("team");
|
||||
}
|
||||
}
|
||||
catch
|
||||
@@ -34,4 +32,16 @@ public partial class About
|
||||
_loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private class AboutSettings
|
||||
{
|
||||
public string? VisionTitle { get; set; }
|
||||
public string? VisionText { get; set; }
|
||||
public string? VisionIcon { get; set; }
|
||||
public string? MissionTitle { get; set; }
|
||||
public string? MissionText { get; set; }
|
||||
public string? MissionIcon { get; set; }
|
||||
public string? ValuesTitle { get; set; }
|
||||
public string? TeamTitle { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
@attribute [Route(RouteConstants.Contact.Index)]
|
||||
@inject SitePageService SitePageService
|
||||
@inject SitePageSettingsService PageSettingsService
|
||||
|
||||
<PageTitle>ارتباط با ما | کارا بازار سلامت</PageTitle>
|
||||
|
||||
@@ -133,7 +133,7 @@
|
||||
<div>
|
||||
<MudText Typo="Typo.body2" >آدرس</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">
|
||||
@(_contactAddress ?? "کرج مهرویلا میدان مادر ساختمان بزرگمهر طبقه ۴ واحد ۱۶")
|
||||
@(_settings?.Address ?? "کرج مهرویلا میدان مادر ساختمان بزرگمهر طبقه ۴ واحد ۱۶")
|
||||
</MudText>
|
||||
</div>
|
||||
</MudStack>
|
||||
@@ -142,7 +142,7 @@
|
||||
<MudIcon Icon="@Icons.Material.Filled.Phone" Color="Color.Success" Size="Size.Large" />
|
||||
<div>
|
||||
<MudText Typo="Typo.body2" >تلفن</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">@(_contactPhone ?? "026-34233563")</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">@(_settings?.Phone ?? "026-34233563")</MudText>
|
||||
</div>
|
||||
</MudStack>
|
||||
|
||||
@@ -150,7 +150,7 @@
|
||||
<MudIcon Icon="@Icons.Material.Filled.Email" Color="Color.Info" Size="Size.Large" />
|
||||
<div>
|
||||
<MudText Typo="Typo.body2" >ایمیل</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">@(_contactEmail ?? "info@kbs1.co")</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">@(_settings?.Email ?? "info@kbs1.co")</MudText>
|
||||
</div>
|
||||
</MudStack>
|
||||
|
||||
@@ -159,7 +159,7 @@
|
||||
<div>
|
||||
<MudText Typo="Typo.body2" >ساعات کاری</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">
|
||||
@((MarkupString)(_contactHours ?? "شنبه تا پنجشنبه<br />۹ صبح تا ۶ عصر"))
|
||||
@((MarkupString)(_settings?.WorkingHours ?? "شنبه تا پنجشنبه<br />۹ صبح تا ۶ عصر"))
|
||||
</MudText>
|
||||
</div>
|
||||
</MudStack>
|
||||
@@ -174,7 +174,7 @@
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Inherit"
|
||||
StartIcon="@Icons.Custom.Brands.Telegram"
|
||||
Href="@(_socialTelegram ?? "https://t.me/kbs1")"
|
||||
Href="@(_settings?.TelegramUrl ?? "https://t.me/kbs1")"
|
||||
Target="_blank"
|
||||
FullWidth="true">
|
||||
تلگرام
|
||||
@@ -183,7 +183,7 @@
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Inherit"
|
||||
StartIcon="@Icons.Custom.Brands.Instagram"
|
||||
Href="@(_socialInstagram ?? "https://instagram.com/kbs1")"
|
||||
Href="@(_settings?.InstagramUrl ?? "https://instagram.com/kbs1")"
|
||||
Target="_blank"
|
||||
FullWidth="true">
|
||||
اینستاگرام
|
||||
@@ -192,7 +192,7 @@
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Inherit"
|
||||
StartIcon="@Icons.Custom.Brands.LinkedIn"
|
||||
Href="@(_socialLinkedin ?? "https://linkedin.com/company/kbs1")"
|
||||
Href="@(_settings?.LinkedinUrl ?? "https://linkedin.com/company/kbs1")"
|
||||
Target="_blank"
|
||||
FullWidth="true">
|
||||
لینکدین
|
||||
@@ -201,7 +201,7 @@
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Inherit"
|
||||
StartIcon="@Icons.Custom.Brands.WhatsApp"
|
||||
Href="@(_socialWhatsapp ?? "https://wa.me/989123456789")"
|
||||
Href="@(_settings?.WhatsappUrl ?? "https://wa.me/989123456789")"
|
||||
Target="_blank"
|
||||
FullWidth="true">
|
||||
واتساپ
|
||||
@@ -226,7 +226,7 @@
|
||||
<MudIcon Icon="@Icons.Material.Filled.Map" Size="Size.Large" Style="color: white;" />
|
||||
<MudText Typo="Typo.h6" Style="color: white;">نقشه موقعیت مکانی</MudText>
|
||||
<MudText Typo="Typo.body2" Style="color: rgba(255,255,255,0.8);" Align="Align.Center">
|
||||
@(_contactAddress ?? "کرج مهرویلا میدان مادر ساختمان بزرگمهر طبقه ۴ واحد ۱۶")
|
||||
@(_settings?.Address ?? "کرج مهرویلا میدان مادر ساختمان بزرگمهر طبقه ۴ واحد ۱۶")
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</div>
|
||||
|
||||
@@ -12,52 +12,19 @@ public partial class Contact
|
||||
private bool _isSubmitting;
|
||||
private readonly ContactFormValidator _contactFormValidator = new();
|
||||
|
||||
// Dynamic CMS data
|
||||
private SitePageDto? _page;
|
||||
private string? _contactAddress;
|
||||
private string? _contactPhone;
|
||||
private string? _contactEmail;
|
||||
private string? _contactHours;
|
||||
private string? _socialTelegram;
|
||||
private string? _socialInstagram;
|
||||
private string? _socialLinkedin;
|
||||
private string? _socialWhatsapp;
|
||||
// Dynamic CMS data (new simplified system)
|
||||
private PageSettingsDto? _page;
|
||||
private ContactSettings? _settings;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_page = await SitePageService.GetByKeyAsync("contact");
|
||||
_page = await PageSettingsService.GetPageAsync("contact");
|
||||
|
||||
if (_page != null)
|
||||
{
|
||||
// Load contact info from CMS section
|
||||
var contactInfoSection = _page.GetSection("contact-info");
|
||||
if (contactInfoSection != null)
|
||||
{
|
||||
var contactData = contactInfoSection.GetExtraData<ContactInfoData>();
|
||||
if (contactData != null)
|
||||
{
|
||||
_contactAddress = contactData.Address;
|
||||
_contactPhone = contactData.Phone;
|
||||
_contactEmail = contactData.Email;
|
||||
_contactHours = contactData.Hours;
|
||||
}
|
||||
}
|
||||
|
||||
// Load social media links from CMS section
|
||||
var socialSection = _page.GetSection("social-media");
|
||||
if (socialSection != null)
|
||||
{
|
||||
var socialData = socialSection.GetExtraData<SocialMediaData>();
|
||||
if (socialData != null)
|
||||
{
|
||||
_socialTelegram = socialData.Telegram;
|
||||
_socialInstagram = socialData.Instagram;
|
||||
_socialLinkedin = socialData.Linkedin;
|
||||
_socialWhatsapp = socialData.Whatsapp;
|
||||
}
|
||||
}
|
||||
_settings = _page.GetSettings<ContactSettings>();
|
||||
}
|
||||
}
|
||||
catch
|
||||
@@ -111,12 +78,12 @@ public partial class Contact
|
||||
|
||||
private void CallSupport()
|
||||
{
|
||||
Snackbar.Add($"شماره تماس: {_contactPhone ?? "026-34233563"}", Severity.Info);
|
||||
Snackbar.Add($"شماره تماس: {_settings?.Phone ?? "026-34233563"}", Severity.Info);
|
||||
}
|
||||
|
||||
private void SendEmail()
|
||||
{
|
||||
Snackbar.Add($"ایمیل: {_contactEmail ?? "info@kbs1.co"}", Severity.Info);
|
||||
Snackbar.Add($"ایمیل: {_settings?.Email ?? "info@kbs1.co"}", Severity.Info);
|
||||
}
|
||||
|
||||
public class ContactForm
|
||||
@@ -142,20 +109,16 @@ public partial class Contact
|
||||
}
|
||||
}
|
||||
|
||||
// DTO classes for ExtraData JSON deserialization
|
||||
private class ContactInfoData
|
||||
// DTO class for SettingsJson deserialization
|
||||
private class ContactSettings
|
||||
{
|
||||
public string? Address { get; set; }
|
||||
public string? Phone { get; set; }
|
||||
public string? Email { get; set; }
|
||||
public string? Hours { get; set; }
|
||||
}
|
||||
|
||||
private class SocialMediaData
|
||||
{
|
||||
public string? Telegram { get; set; }
|
||||
public string? Instagram { get; set; }
|
||||
public string? Linkedin { get; set; }
|
||||
public string? Whatsapp { get; set; }
|
||||
public string? WorkingHours { get; set; }
|
||||
public string? TelegramUrl { get; set; }
|
||||
public string? InstagramUrl { get; set; }
|
||||
public string? LinkedinUrl { get; set; }
|
||||
public string? WhatsappUrl { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@
|
||||
<MudTd>
|
||||
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||
<AppImage Path="@GetImageUrl(context.ImageUrl)" Alt="@context.Title"
|
||||
ImgWidth="64" ImgHeight="64" Class="product-thumb" />
|
||||
ImgWidth="64" ImgHeight="64" Class="rounded-lg" ObjectFit="ObjectFit.Cover" />
|
||||
<MudText>@context.Title</MudText>
|
||||
</MudStack>
|
||||
</MudTd>
|
||||
@@ -82,7 +82,7 @@
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||
<AppImage Path="@GetImageUrl(item.ImageUrl)" Alt="@item.Title"
|
||||
ImgWidth="50" ImgHeight="50" Class="rounded-circle" />
|
||||
ImgWidth="50" ImgHeight="50" Class="rounded-lg" ObjectFit="ObjectFit.Cover" />
|
||||
<MudText Typo="Typo.subtitle2">@item.Title</MudText>
|
||||
</MudStack>
|
||||
<MudChip T="string" Color="Color.Error" Variant="Variant.Outlined" Size="Size.Small">
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
<MudStack Spacing="1">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<AppImage Path="@GetImageUrl(item.ImageUrl)" Alt="@item.Title"
|
||||
ImgWidth="40" ImgHeight="40" Class="rounded-circle" />
|
||||
ImgWidth="40" ImgHeight="40" Class="rounded-lg" ObjectFit="ObjectFit.Cover" />
|
||||
<MudText Typo="Typo.subtitle2" Style="flex:1; margin:0 8px;">@item.Title</MudText>
|
||||
<MudText Typo="Typo.subtitle2">@FormatPrice(item.FinalPrice)</MudText>
|
||||
</MudStack>
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
<MudItem xs="12" md="5">
|
||||
<MudPaper Class="pa-2 rounded-xl" Elevation="1">
|
||||
<AppImage Path="@_selectedImage" Alt="@_product.Title"
|
||||
ObjectFit="ObjectFit.Contain"
|
||||
Style="width:100%; max-height:400px; border-radius:12px;" />
|
||||
ObjectFit="ObjectFit.Cover"
|
||||
Style="width:100%; aspect-ratio:1/1; border-radius:12px;" />
|
||||
|
||||
@if (_product.Images.Count > 1)
|
||||
{
|
||||
|
||||
@@ -77,9 +77,9 @@
|
||||
<MudItem xs="6" sm="6" md="3"
|
||||
onclick="@(() => NavigateToProduct(p.Id))">
|
||||
<MudCard Class="rounded-lg h-100 d-flex flex-column overflow-hidden"
|
||||
Style="cursor:pointer;height: 300px">
|
||||
Style="cursor:pointer;">
|
||||
<MudCardContent Class="d-flex flex-column pa-1 h-100">
|
||||
<div style="height: 60%;background-image: url('@(string.IsNullOrWhiteSpace(p.ThumbnailUrl) ? "/images/product-placeholder.svg" : p.ThumbnailUrl)');background-size: cover; background-position: center;border-radius: 0.5rem; position: relative;">
|
||||
<div style="aspect-ratio:1/1;width:100%;background-image: url('@(string.IsNullOrWhiteSpace(p.ThumbnailUrl) ? "/images/product-placeholder.svg" : p.ThumbnailUrl)');background-size: cover; background-position: center;border-radius: 0.5rem; position: relative;">
|
||||
@if (p.RemainingCount <= 0)
|
||||
{
|
||||
<div style="position:absolute;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.5);display:flex;align-items:center;justify-content:center;border-radius:0.5rem;">
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
</MudChip>
|
||||
|
||||
<MudText Typo="Typo.h1" Class="hero-title" Style="max-width:680px;">
|
||||
رشد تیم، فروش واقعی، پاداش شفاف
|
||||
@(_pageData?.HeroTitle ?? "رشد تیم، فروش واقعی، پاداش شفاف")
|
||||
</MudText>
|
||||
|
||||
<MudText Typo="Typo.body1" Class="hero-subtitle" Style="max-width:520px;">
|
||||
با دعوت از دوستان، از خریدهای واقعی محصولات سلامت پاداش بگیرید. ثبتنام سریع، داشبورد لحظهای.
|
||||
@(_pageData?.HeroSubtitle ?? "با دعوت از دوستان، از خریدهای واقعی محصولات سلامت پاداش بگیرید. ثبتنام سریع، داشبورد لحظهای.")
|
||||
</MudText>
|
||||
|
||||
<MudStack Row="true" Spacing="2" Class="mt-2 flex-wrap" Justify="Justify.Center">
|
||||
@@ -27,13 +27,13 @@
|
||||
Class="rounded-pill hero-cta-primary"
|
||||
Size="Size.Large"
|
||||
OnClick="NavigateToRegistrationWizard">
|
||||
شروع رایگان
|
||||
@(_settings?.HeroButtonPrimaryText ?? "شروع رایگان")
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Class="rounded-pill hero-cta-outline"
|
||||
Size="Size.Large"
|
||||
OnClick="@(() => Navigation.NavigateTo("/blog"))">
|
||||
آخرین اخبار
|
||||
@(_settings?.HeroButtonSecondaryText ?? "آخرین اخبار")
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
<section class="section-landing">
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<div class="text-center mb-8 fade-in-up">
|
||||
<MudText Typo="Typo.h3">سه گام تا شروع</MudText>
|
||||
<MudText Typo="Typo.h3">@(_settings?.StepsTitle ?? "سه گام تا شروع")</MudText>
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary mt-2">
|
||||
بدون پیچیدگی، سریع و آسان
|
||||
</MudText>
|
||||
@@ -128,7 +128,7 @@
|
||||
<section class="section-landing" style="background:var(--mud-palette-background-gray);">
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<div class="text-center mb-8 fade-in-up">
|
||||
<MudText Typo="Typo.h3">چرا کارا بازار سلامت؟</MudText>
|
||||
<MudText Typo="Typo.h3">@(_settings?.FeaturesTitle ?? "چرا کارا بازار سلامت؟")</MudText>
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary mt-2" Style="max-width:540px;margin:0 auto;">
|
||||
ابزارهایی ساده و قدرتمند برای رشد کسبوکار شما
|
||||
</MudText>
|
||||
@@ -259,7 +259,7 @@
|
||||
<section class="section-landing">
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<div class="text-center mb-8 fade-in-up">
|
||||
<MudText Typo="Typo.h3">اعتماد مشتریان</MudText>
|
||||
<MudText Typo="Typo.h3">@(_settings?.TestimonialsTitle ?? "اعتماد مشتریان")</MudText>
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary mt-2">
|
||||
بخشی از تجربه استفاده از «کارا بازار سلامت»
|
||||
</MudText>
|
||||
@@ -299,7 +299,7 @@
|
||||
<section class="section-landing" style="background:var(--mud-palette-background-gray);">
|
||||
<MudContainer MaxWidth="MaxWidth.Medium">
|
||||
<div class="text-center mb-8 fade-in-up">
|
||||
<MudText Typo="Typo.h3">سوالات متداول</MudText>
|
||||
<MudText Typo="Typo.h3">@(_settings?.FaqTitle ?? "سوالات متداول")</MudText>
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary mt-2">
|
||||
پاسخ به سوالات رایج شما
|
||||
</MudText>
|
||||
@@ -326,16 +326,16 @@
|
||||
<div class="cta-banner pa-8 pa-md-12 fade-in-up">
|
||||
<MudStack AlignItems="AlignItems.Center" Spacing="3">
|
||||
<MudText Typo="Typo.h3" Align="Align.Center" Class="dash-hero-name">
|
||||
آماده شروع هستید؟
|
||||
@(_settings?.CtaTitle ?? "آماده شروع هستید؟")
|
||||
</MudText>
|
||||
<MudText Typo="Typo.body1" Align="Align.Center" Class="hero-subtitle">
|
||||
همین الان ثبتنام کنید و از مزایای کارا بازار سلامت بهرهمند شوید.
|
||||
@(_settings?.CtaDescription ?? "همین الان ثبتنام کنید و از مزایای کارا بازار سلامت بهرهمند شوید.")
|
||||
</MudText>
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Size="Size.Large"
|
||||
Class="rounded-pill mt-2 hero-cta-primary"
|
||||
OnClick="NavigateToRegistrationWizard">
|
||||
شروع رایگان
|
||||
@(_settings?.CtaButtonText ?? "شروع رایگان")
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</div>
|
||||
|
||||
@@ -9,21 +9,53 @@ public partial class Index : IDisposable
|
||||
{
|
||||
[Inject] private BlogPostService BlogPostService { get; set; } = default!;
|
||||
[Inject] private AuthService AuthService { get; set; } = default!;
|
||||
[Inject] private SitePageSettingsService PageSettingsService { get; set; } = default!;
|
||||
|
||||
// ── CMS page data ──
|
||||
private PageSettingsDto? _pageData;
|
||||
private LandingSettings? _settings;
|
||||
|
||||
// ── Latest blog posts (loaded from CMS) ──
|
||||
private List<BlogPostCardDto> _latestPosts = new();
|
||||
|
||||
// ── Data lists (populated from DB or fallback) ──
|
||||
private List<(string Icon, string Text)> _trustBadges = new();
|
||||
private List<(string Title, string Desc)> _steps = new();
|
||||
private List<(string Icon, string Title, string Desc, int Delay)> _features = new();
|
||||
private List<StatItem> _stats = new();
|
||||
private List<TestimonialItem> _testimonials = new();
|
||||
private List<QA> _faqs = new();
|
||||
|
||||
// Track whether animations need re-initialization after data load
|
||||
private bool _dataLoaded;
|
||||
private bool _animationsInitialized;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
MainService.OnChangeHandler += OnStateChanged;
|
||||
|
||||
// Load landing page settings from CMS
|
||||
try
|
||||
{
|
||||
_pageData = await PageSettingsService.GetPageAsync("landing");
|
||||
if (_pageData != null)
|
||||
{
|
||||
_settings = _pageData.GetSettings<LandingSettings>();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fallback: settings remain null → defaults below
|
||||
}
|
||||
|
||||
PopulateFromSettings();
|
||||
_dataLoaded = true;
|
||||
|
||||
// Load latest published blog posts
|
||||
// اول پینشدهها، اگر کافی نبود آخرین پستها اضافه شود
|
||||
try
|
||||
{
|
||||
_latestPosts = await BlogPostService.GetFeaturedPostsAsync(2);
|
||||
|
||||
// اگر فقط ۱ پینشده داریم، ۱ پست آخر هم اضافه کن
|
||||
|
||||
if (_latestPosts.Count < 2)
|
||||
{
|
||||
var result = await BlogPostService.GetPublishedPostsAsync(page: 1, pageSize: 2);
|
||||
@@ -44,10 +76,162 @@ public partial class Index : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateFromSettings()
|
||||
{
|
||||
// ── Trust badges ──
|
||||
if (_settings?.TrustBadges?.Any() == true)
|
||||
{
|
||||
_trustBadges = _settings.TrustBadges
|
||||
.Select(b => (ResolveIcon(b.IconName), b.Text ?? ""))
|
||||
.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
_trustBadges = new()
|
||||
{
|
||||
(Icons.Material.Outlined.Timer, "ثبتنام زیر ۲ دقیقه"),
|
||||
(Icons.Material.Outlined.SupportAgent, "پشتیبانی ۷×۲۴"),
|
||||
(Icons.Material.Outlined.Lock, "پرداخت ایمن"),
|
||||
(Icons.Material.Outlined.Verified, "تضمین کیفیت"),
|
||||
};
|
||||
}
|
||||
|
||||
// ── Steps ──
|
||||
if (_settings?.Steps?.Any() == true)
|
||||
{
|
||||
_steps = _settings.Steps
|
||||
.Select(s => (s.Title ?? "", s.Description ?? ""))
|
||||
.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
_steps = new()
|
||||
{
|
||||
("ثبتنام و احراز هویت", "یک حساب بسازید، شماره موبایل را تأیید و اطلاعات هویتی را تکمیل کنید."),
|
||||
("دعوت دوستان", "لینک دعوت اختصاصی خود را با دوستان و آشنایان به اشتراک بگذارید."),
|
||||
("دریافت پاداش", "از خریدهای واقعی اعضای تیمتان پاداش شفاف و لحظهای دریافت کنید."),
|
||||
};
|
||||
}
|
||||
|
||||
// ── Features ──
|
||||
if (_settings?.Features?.Any() == true)
|
||||
{
|
||||
_features = _settings.Features
|
||||
.Select((f, i) => (ResolveIcon(f.IconName), f.Title ?? "", f.Description ?? "", i * 100))
|
||||
.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
_features = new()
|
||||
{
|
||||
(Icons.Material.Outlined.VerifiedUser, "ثبتنام سریع و ساده", "در چند گام کوتاه حساب بسازید و شروع کنید.", 0),
|
||||
(Icons.Material.Outlined.StarBorder, "پاداشهای شفاف", "قوانین روشن، دسترسی آسان به سوابق و گزارشها.", 100),
|
||||
(Icons.Material.Outlined.Devices, "طراحی واکنشگرا", "تجربهای روان در موبایل و دسکتاپ.", 200),
|
||||
(Icons.Material.Outlined.Groups, "تیمسازی هوشمند", "ساختار درختی شبکه و مدیریت تیمهای فروش.", 300),
|
||||
(Icons.Material.Outlined.Insights, "گزارشهای لحظهای", "داشبورد پویا برای مشاهده عملکرد و کمیسیون.", 400),
|
||||
(Icons.Material.Outlined.Lock, "امنیت بالا", "رمزنگاری اطلاعات و احراز هویت چندمرحلهای.", 500),
|
||||
};
|
||||
}
|
||||
|
||||
// ── Stats ──
|
||||
if (_settings?.Stats?.Any() == true)
|
||||
{
|
||||
_stats = _settings.Stats
|
||||
.Select((s, i) => new StatItem(
|
||||
$"stat-{i}",
|
||||
$"{s.Value}{s.Suffix}",
|
||||
s.Value,
|
||||
s.Suffix ?? "",
|
||||
ResolveColor(s.Color),
|
||||
s.Label ?? ""))
|
||||
.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
_stats = new()
|
||||
{
|
||||
new("stat-growth", "+۵۰٪", 50, "%+", Color.Success, "رشد میانگین تیم"),
|
||||
new("stat-uptime", "۹۹.۹٪", 99.9, "%", Color.Primary, "آپتایم سرویس"),
|
||||
new("stat-deploy", "۳ روز", 3, " روز", Color.Warning, "میانگین زمان استقرار"),
|
||||
new("stat-coverage", "+۲۰ کشور", 20, "+", Color.Secondary, "پوشش ارسال کد"),
|
||||
};
|
||||
}
|
||||
|
||||
// ── Testimonials ──
|
||||
if (_settings?.Testimonials?.Any() == true)
|
||||
{
|
||||
_testimonials = _settings.Testimonials
|
||||
.Select((t, i) => new TestimonialItem(t.Quote ?? "", t.Name ?? "", t.Role ?? "", i * 150))
|
||||
.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
_testimonials = new()
|
||||
{
|
||||
new("با کارا بازار سلامت، محاسبه کارمزدها و پایش تیمها بدون اکسل و دردسر انجام میشود.", "شرکت سینا نت", "مدیر عملیات", 0),
|
||||
new("شجرهنامه بصری و گزارشهای دقیق باعث شد رشد تیم را لحظهای ببینیم.", "هولدینگ آریانا", "مدیر فروش", 150),
|
||||
new("سادگی ثبتنام و شفافیت پاداشها مهمترین مزیت این پلتفرم است.", "گروه بهداشتی نوین", "مدیر توسعه", 300),
|
||||
};
|
||||
}
|
||||
|
||||
// ── FAQs ──
|
||||
if (_settings?.Faqs?.Any() == true)
|
||||
{
|
||||
_faqs = _settings.Faqs
|
||||
.Select(f => new QA(f.Question ?? "", f.Answer ?? ""))
|
||||
.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
_faqs = new()
|
||||
{
|
||||
new("دامنه اختصاصی دارم؛ قابل اتصال است؟", "بله، پشت دامنه و گواهی SSL خودتان مستقر میشود."),
|
||||
new("با دیتابیس خودم کار میکند؟", "کاملاً. SQL Server، PostgreSQL و MySQL پشتیبانی میشود."),
|
||||
new("چه درگاههایی پشتیبانی میشود؟", "Stripe و PayPal یا درگاه اختصاصی از طریق وبهوکها."),
|
||||
new("میتوانم دادهها را خروجی بگیرم؟", "هر زمان از داشبورد ادمین خروجی CSV/Excel بگیرید."),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolve icon name from SettingsJson to MudBlazor icon string.
|
||||
/// Falls back to a generic icon.
|
||||
/// </summary>
|
||||
private static string ResolveIcon(string? iconName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(iconName)) return Icons.Material.Outlined.Info;
|
||||
|
||||
// Try to get from MudBlazor Icons via reflection
|
||||
var field = typeof(Icons.Material.Outlined).GetField(iconName,
|
||||
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
|
||||
if (field != null) return (string)(field.GetValue(null) ?? Icons.Material.Outlined.Info);
|
||||
|
||||
// Also try Filled
|
||||
field = typeof(Icons.Material.Filled).GetField(iconName,
|
||||
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
|
||||
if (field != null) return (string)(field.GetValue(null) ?? Icons.Material.Outlined.Info);
|
||||
|
||||
return Icons.Material.Outlined.Info;
|
||||
}
|
||||
|
||||
private static Color ResolveColor(string? colorName) => colorName?.ToLowerInvariant() switch
|
||||
{
|
||||
"primary" => Color.Primary,
|
||||
"secondary" => Color.Secondary,
|
||||
"success" => Color.Success,
|
||||
"warning" => Color.Warning,
|
||||
"error" => Color.Error,
|
||||
"info" => Color.Info,
|
||||
_ => Color.Default
|
||||
};
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
// Init/re-init scroll animations after data is loaded and rendered
|
||||
if ((firstRender || (_dataLoaded && !_animationsInitialized)) && _steps.Any())
|
||||
{
|
||||
_animationsInitialized = true;
|
||||
|
||||
// Init scroll-triggered fade-in animations
|
||||
await JS.InvokeVoidAsync("initScrollAnimations");
|
||||
|
||||
@@ -88,65 +272,40 @@ public partial class Index : IDisposable
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
// ── Trust badges ──
|
||||
private readonly List<(string Icon, string Text)> _trustBadges = new()
|
||||
{
|
||||
(Icons.Material.Outlined.Timer, "ثبتنام زیر ۲ دقیقه"),
|
||||
(Icons.Material.Outlined.SupportAgent, "پشتیبانی ۷×۲۴"),
|
||||
(Icons.Material.Outlined.Lock, "پرداخت ایمن"),
|
||||
(Icons.Material.Outlined.Verified, "تضمین کیفیت"),
|
||||
};
|
||||
|
||||
// ── How it works steps ──
|
||||
private readonly List<(string Title, string Desc)> _steps = new()
|
||||
{
|
||||
("ثبتنام و احراز هویت", "یک حساب بسازید، شماره موبایل را تأیید و اطلاعات هویتی را تکمیل کنید."),
|
||||
("دعوت دوستان", "لینک دعوت اختصاصی خود را با دوستان و آشنایان به اشتراک بگذارید."),
|
||||
("دریافت پاداش", "از خریدهای واقعی اعضای تیمتان پاداش شفاف و لحظهای دریافت کنید."),
|
||||
};
|
||||
|
||||
// ── Feature cards data (6 cards) ──
|
||||
private readonly List<(string Icon, string Title, string Desc, int Delay)> _features = new()
|
||||
{
|
||||
(Icons.Material.Outlined.VerifiedUser, "ثبتنام سریع و ساده", "در چند گام کوتاه حساب بسازید و شروع کنید.", 0),
|
||||
(Icons.Material.Outlined.StarBorder, "پاداشهای شفاف", "قوانین روشن، دسترسی آسان به سوابق و گزارشها.", 100),
|
||||
(Icons.Material.Outlined.Devices, "طراحی واکنشگرا", "تجربهای روان در موبایل و دسکتاپ.", 200),
|
||||
(Icons.Material.Outlined.Groups, "تیمسازی هوشمند", "ساختار درختی شبکه و مدیریت تیمهای فروش.", 300),
|
||||
(Icons.Material.Outlined.Insights, "گزارشهای لحظهای", "داشبورد پویا برای مشاهده عملکرد و کمیسیون.", 400),
|
||||
(Icons.Material.Outlined.Lock, "امنیت بالا", "رمزنگاری اطلاعات و احراز هویت چندمرحلهای.", 500),
|
||||
};
|
||||
|
||||
// ── Stats data (animated counters) ──
|
||||
private readonly List<StatItem> _stats = new()
|
||||
{
|
||||
new("stat-growth", "+۵۰٪", 50, "%+", Color.Success, "رشد میانگین تیم"),
|
||||
new("stat-uptime", "۹۹.۹٪", 99.9, "%", Color.Primary, "آپتایم سرویس"),
|
||||
new("stat-deploy", "۳ روز", 3, " روز", Color.Warning, "میانگین زمان استقرار"),
|
||||
new("stat-coverage", "+۲۰ کشور", 20, "+", Color.Secondary, "پوشش ارسال کد"),
|
||||
};
|
||||
|
||||
// ── Testimonials ──
|
||||
private readonly List<TestimonialItem> _testimonials = new()
|
||||
{
|
||||
new("با کارا بازار سلامت، محاسبه کارمزدها و پایش تیمها بدون اکسل و دردسر انجام میشود.", "شرکت سینا نت", "مدیر عملیات", 0),
|
||||
new("شجرهنامه بصری و گزارشهای دقیق باعث شد رشد تیم را لحظهای ببینیم.", "هولدینگ آریانا", "مدیر فروش", 150),
|
||||
new("سادگی ثبتنام و شفافیت پاداشها مهمترین مزیت این پلتفرم است.", "گروه بهداشتی نوین", "مدیر توسعه", 300),
|
||||
};
|
||||
|
||||
// ── FAQ data ──
|
||||
private readonly List<QA> _faqs = new()
|
||||
{
|
||||
new("دامنه اختصاصی دارم؛ قابل اتصال است؟", "بله، پشت دامنه و گواهی SSL خودتان مستقر میشود."),
|
||||
new("با دیتابیس خودم کار میکند؟", "کاملاً. SQL Server، PostgreSQL و MySQL پشتیبانی میشود."),
|
||||
new("چه درگاههایی پشتیبانی میشود؟", "Stripe و PayPal یا درگاه اختصاصی از طریق وبهوکها."),
|
||||
new("میتوانم دادهها را خروجی بگیرم؟", "هر زمان از داشبورد ادمین خروجی CSV/Excel بگیرید."),
|
||||
};
|
||||
|
||||
// ── Records ──
|
||||
private record QA(string Q, string A);
|
||||
private record StatItem(string ElementId, string Display, double Target, string Suffix, Color Color, string Label);
|
||||
private record TestimonialItem(string Quote, string Name, string Role, int Delay);
|
||||
|
||||
// ── SettingsJson DTO ──
|
||||
private class LandingSettings
|
||||
{
|
||||
public string? HeroButtonPrimaryText { get; set; }
|
||||
public string? HeroButtonSecondaryText { get; set; }
|
||||
public List<TrustBadgeItem>? TrustBadges { get; set; }
|
||||
public string? StepsTitle { get; set; }
|
||||
public List<StepItem>? Steps { get; set; }
|
||||
public string? FeaturesTitle { get; set; }
|
||||
public List<FeatureItem>? Features { get; set; }
|
||||
public string? StatsTitle { get; set; }
|
||||
public List<StatSettingItem>? Stats { get; set; }
|
||||
public string? TestimonialsTitle { get; set; }
|
||||
public List<TestimonialSettingItem>? Testimonials { get; set; }
|
||||
public string? FaqTitle { get; set; }
|
||||
public List<FaqItem>? Faqs { get; set; }
|
||||
public string? CtaTitle { get; set; }
|
||||
public string? CtaDescription { get; set; }
|
||||
public string? CtaButtonText { get; set; }
|
||||
public bool? FeaturedBlogEnabled { get; set; }
|
||||
}
|
||||
|
||||
private class TrustBadgeItem { public string? IconName { get; set; } public string? Text { get; set; } }
|
||||
private class StepItem { public string? Title { get; set; } public string? Description { get; set; } }
|
||||
private class FeatureItem { public string? IconName { get; set; } public string? Title { get; set; } public string? Description { get; set; } }
|
||||
private class StatSettingItem { public string? Label { get; set; } public double Value { get; set; } public string? Suffix { get; set; } public string? Color { get; set; } }
|
||||
private class TestimonialSettingItem { public string? Quote { get; set; } public string? Name { get; set; } public string? Role { get; set; } }
|
||||
private class FaqItem { public string? Question { get; set; } public string? Answer { get; set; } }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
MainService.OnChangeHandler -= OnStateChanged;
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
@attribute [Route(RouteConstants.Licenses.Index)]
|
||||
@inject SitePageSettingsService PageSettingsService
|
||||
|
||||
<PageTitle>مجوزها و گواهینامهها | کارا بازار سلامت</PageTitle>
|
||||
|
||||
@if (_loading)
|
||||
{
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-16">
|
||||
<LoadingState />
|
||||
</MudContainer>
|
||||
}
|
||||
else
|
||||
{
|
||||
<!-- Hero Section -->
|
||||
<section class="licenses-hero-section py-16">
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<MudStack Spacing="3" AlignItems="AlignItems.Center" Class="text-center">
|
||||
<MudChip T="string" Color="Color.Secondary" Variant="Variant.Filled">مجوزها و گواهینامهها</MudChip>
|
||||
<MudText Typo="Typo.h2" Class="mb-3">
|
||||
@(_pageData?.HeroTitle ?? "مجوزها و گواهینامههای رسمی")
|
||||
</MudText>
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary" Style="max-width: 700px;">
|
||||
@(_pageData?.HeroSubtitle ?? "ما با دریافت مجوزهای رسمی و تأییدیههای معتبر، اطمینان شما را در استفاده از خدمات خود جلب کردهایم.")
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</MudContainer>
|
||||
</section>
|
||||
|
||||
<MudStack Spacing="8" Class="pb-12">
|
||||
|
||||
<!-- Licenses Gallery -->
|
||||
<section class="py-12">
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<MudText Typo="Typo.h3" Align="Align.Center" Class="mb-8">مجوزهای ما</MudText>
|
||||
@if (_licenseImages.Any())
|
||||
{
|
||||
<MudGrid Spacing="4" Justify="Justify.Center">
|
||||
@foreach (var license in _licenseImages)
|
||||
{
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudCard Elevation="2" Class="h-100">
|
||||
<MudCardMedia Image="@license.ImagePath" Height="250" />
|
||||
<MudCardContent>
|
||||
@if (!string.IsNullOrWhiteSpace(license.Title))
|
||||
{
|
||||
<MudText Typo="Typo.h6" Align="Align.Center" Class="mb-1">@license.Title</MudText>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(license.Description))
|
||||
{
|
||||
<MudText Typo="Typo.body2" Align="Align.Center" Class="mud-text-secondary">@license.Description</MudText>
|
||||
}
|
||||
</MudCardContent>
|
||||
@if (!string.IsNullOrWhiteSpace(license.LinkUrl))
|
||||
{
|
||||
<MudCardActions Class="justify-center">
|
||||
<MudButton Variant="Variant.Text"
|
||||
Color="Color.Primary"
|
||||
Href="@license.LinkUrl"
|
||||
Target="_blank"
|
||||
StartIcon="@Icons.Material.Filled.OpenInNew">
|
||||
مشاهده جزئیات
|
||||
</MudButton>
|
||||
</MudCardActions>
|
||||
}
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
}
|
||||
</MudGrid>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudAlert Severity="Severity.Info" Variant="Variant.Text" Class="my-8" Dense="true">
|
||||
اطلاعات مجوزها بهزودی بارگذاری خواهد شد.
|
||||
</MudAlert>
|
||||
}
|
||||
</MudContainer>
|
||||
</section>
|
||||
|
||||
<!-- CTA Section -->
|
||||
<section class="py-12">
|
||||
<MudContainer MaxWidth="MaxWidth.Medium">
|
||||
<MudPaper Elevation="3" Class="pa-8 rounded-xl text-center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.VerifiedUser" Size="Size.Large" Color="Color.Primary" Class="mb-4" />
|
||||
<MudText Typo="Typo.h4" Class="mb-3">نیاز به اطلاعات بیشتر دارید؟</MudText>
|
||||
<MudText Typo="Typo.body1" Class="mud-text-secondary mb-6">
|
||||
برای دریافت اطلاعات بیشتر درباره مجوزها و گواهینامههای ما، با ما تماس بگیرید.
|
||||
</MudText>
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
Size="Size.Large"
|
||||
OnClick="() => Navigation.NavigateTo(RouteConstants.Contact.Index)">
|
||||
تماس با ما
|
||||
</MudButton>
|
||||
</MudPaper>
|
||||
</MudContainer>
|
||||
</section>
|
||||
|
||||
</MudStack>
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using FrontOffice.Main.Utilities;
|
||||
|
||||
namespace FrontOffice.Main.Pages;
|
||||
|
||||
public partial class Licenses
|
||||
{
|
||||
private bool _loading = true;
|
||||
private PageSettingsDto? _pageData;
|
||||
private List<PageSettingsImageDto> _licenseImages = new();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_pageData = await PageSettingsService.GetPageAsync("licenses");
|
||||
|
||||
if (_pageData != null)
|
||||
{
|
||||
_licenseImages = _pageData.GetImages("licenses");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fallback: page remains null → hardcoded content will render
|
||||
}
|
||||
finally
|
||||
{
|
||||
_loading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@
|
||||
<MudStack Spacing="1">
|
||||
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||
<AppImage Path="@GetProductImageUrl(context.ImageUrl)" Alt="@context.Title"
|
||||
ImgWidth="64" ImgHeight="64" Class="product-thumb" />
|
||||
ImgWidth="64" ImgHeight="64" Class="rounded-lg" ObjectFit="ObjectFit.Cover" />
|
||||
<MudText>@context.Title</MudText>
|
||||
</MudStack>
|
||||
@if (context.Discount > 0 || !string.IsNullOrWhiteSpace(context.Created) || !string.IsNullOrWhiteSpace(context.Description))
|
||||
@@ -87,7 +87,7 @@
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||
<AppImage Path="@GetProductImageUrl(item.ImageUrl)" Alt="@item.Title"
|
||||
ImgWidth="50" ImgHeight="50" Class="rounded-circle" />
|
||||
ImgWidth="50" ImgHeight="50" Class="rounded-lg" ObjectFit="ObjectFit.Cover" />
|
||||
<MudText Typo="Typo.subtitle2">@item.Title</MudText>
|
||||
</MudStack>
|
||||
@if (item.Discount > 0)
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
<MudStack Spacing="1">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<AppImage Path="@GetProductImageUrl(item.ImageUrl)" Alt="@item.Title"
|
||||
ImgWidth="48" ImgHeight="48" Class="rounded-circle" />
|
||||
ImgWidth="48" ImgHeight="48" Class="rounded-lg" ObjectFit="ObjectFit.Cover" />
|
||||
<MudText Typo="Typo.subtitle2">@item.Title</MudText>
|
||||
<MudText Typo="Typo.subtitle2">@FormatPrice(item.LineTotal)</MudText>
|
||||
</MudStack>
|
||||
|
||||
@@ -47,7 +47,7 @@ else
|
||||
<MudTd>
|
||||
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||
<AppImage Path="@GetProductImageUrl(context.ProductThumbnailPath)" Alt="@context.ProductTitle"
|
||||
ImgWidth="60" ImgHeight="60" Class="rounded-circle" />
|
||||
ImgWidth="60" ImgHeight="60" Class="rounded-lg" ObjectFit="ObjectFit.Cover" />
|
||||
<MudText>@context.ProductTitle</MudText>
|
||||
</MudStack>
|
||||
</MudTd>
|
||||
@@ -66,7 +66,7 @@ else
|
||||
<MudStack Spacing="1">
|
||||
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
|
||||
<AppImage Path="@GetProductImageUrl(it.ProductThumbnailPath)" Alt="@it.ProductTitle"
|
||||
ImgWidth="60" ImgHeight="60" Class="rounded-circle" />
|
||||
ImgWidth="60" ImgHeight="60" Class="rounded-lg" ObjectFit="ObjectFit.Cover" />
|
||||
<MudText>@it.ProductTitle</MudText>
|
||||
</MudStack>
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||||
|
||||
@@ -33,7 +33,7 @@ else
|
||||
{
|
||||
<AppImage Path="@MainImageUrl" Alt="@MainImageAlt"
|
||||
ObjectFit="ObjectFit.Cover"
|
||||
Style="width:100%; max-height:400px; border-radius:12px;" />
|
||||
Style="width:100%; aspect-ratio:1/1; border-radius:12px;" />
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -120,11 +120,11 @@
|
||||
<MudItem xs="6" sm="6" md="3"
|
||||
onclick="@(() => Navigation.NavigateTo($"{RouteConstants.Store.ProductDetail}{p.Id}"))">
|
||||
<MudCard Class="rounded-lg h-100 d-flex flex-column overflow-hidden"
|
||||
Style="cursor:pointer;height: 300px">
|
||||
Style="cursor:pointer;">
|
||||
|
||||
|
||||
<MudCardContent Class="d-flex flex-column pa-1 h-100">
|
||||
<div style="height: 60%;background-image: url('@p.ImageUrl');background-size: cover; background-position: center;border-radius: 0.5rem; position: relative;">
|
||||
<div style="aspect-ratio:1/1;width:100%;background-image: url('@p.ImageUrl');background-size: cover; background-position: center;border-radius: 0.5rem; position: relative;">
|
||||
@if (p.RemainingCount <= 0)
|
||||
{
|
||||
<div style="position:absolute;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.5);display:flex;align-items:center;justify-content:center;border-radius:0.5rem;">
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
<MudLink Href="@(RouteConstants.FAQ.Index)" Class="footer-link">سوالات متداول</MudLink>
|
||||
<MudLink Href="@(RouteConstants.Contact.Index)" Class="footer-link">ارتباط با ما</MudLink>
|
||||
<MudLink Href="@(RouteConstants.About.Index)" Class="footer-link">درباره ما</MudLink>
|
||||
<MudLink Href="@(RouteConstants.Licenses.Index)" Class="footer-link">مجوزها</MudLink>
|
||||
</MudStack>
|
||||
</MudItem>
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
</MudHidden>
|
||||
|
||||
<div class="d-flex align-center gap-2">
|
||||
<MudLink Href="@(RouteConstants.Main.MainPage)" Underline="Underline.None">
|
||||
<MudLink Href="@(_isAuthenticated ? RouteConstants.Profile.Index : RouteConstants.Main.MainPage)" Underline="Underline.None">
|
||||
<MudStack Row="true" Spacing="3" AlignItems="AlignItems.Center">
|
||||
<MudHidden Breakpoint="Breakpoint.MdAndUp" Invert="true">
|
||||
<MudImage ObjectFit="ObjectFit.Cover"
|
||||
@@ -73,6 +73,7 @@
|
||||
<MudLink Href="@(RouteConstants.Blog.Index)" Typo="Typo.subtitle1" Class="mud-link">بلاگ</MudLink>
|
||||
<MudLink Href="@(RouteConstants.Contact.Index)" Typo="Typo.subtitle1" Class="mud-link">ارتباط با ما</MudLink>
|
||||
<MudLink Href="@(RouteConstants.About.Index)" Typo="Typo.subtitle1" Class="mud-link">درباره ما</MudLink>
|
||||
<MudLink Href="@(RouteConstants.Licenses.Index)" Typo="Typo.subtitle1" Class="mud-link">مجوزها</MudLink>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-center gap-2">
|
||||
@@ -191,6 +192,9 @@
|
||||
<MudLink Href="@(RouteConstants.Contact.Index)" Typo="Typo.subtitle1" OnClick="() => _drawerOpen=false">
|
||||
ارتباط با ما
|
||||
</MudLink>
|
||||
<MudLink Href="@(RouteConstants.Licenses.Index)" Typo="Typo.subtitle1" OnClick="() => _drawerOpen=false">
|
||||
مجوزها
|
||||
</MudLink>
|
||||
|
||||
<MudDivider Class="my-2"/>
|
||||
@if (_isAuthenticated)
|
||||
@@ -281,9 +285,9 @@
|
||||
else
|
||||
{
|
||||
@* ── Default bottom nav (outside stores) ── *@
|
||||
<MudLink Href="@(RouteConstants.Profile.Index)" Class="bottom-nav-item">
|
||||
<MudLink Href="@(_isAuthenticated ? RouteConstants.Profile.Index : RouteConstants.Main.MainPage)" Class="bottom-nav-item">
|
||||
<MudIcon Icon="@Icons.Material.Outlined.Home" Size="Size.Medium" />
|
||||
<MudText Typo="Typo.caption">خانه</MudText>
|
||||
<MudText Typo="Typo.caption">@(_isAuthenticated ? "داشبورد" : "خانه")</MudText>
|
||||
</MudLink>
|
||||
@if (_isAuthenticated)
|
||||
{
|
||||
|
||||
@@ -67,6 +67,11 @@ public static class RouteConstants
|
||||
public const string Index = "/contact";
|
||||
}
|
||||
|
||||
public static class Licenses
|
||||
{
|
||||
public const string Index = "/licenses";
|
||||
}
|
||||
|
||||
public static class Blog
|
||||
{
|
||||
public const string Index = "/blog";
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
using System.Text.Json;
|
||||
using CMSMicroservice.Protobuf.Protos.SitePageSettings;
|
||||
|
||||
namespace FrontOffice.Main.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// سرویس سادهشده مدیریت صفحات سایت
|
||||
/// ۴ صفحه ثابت: landing, about, contact, licenses
|
||||
/// </summary>
|
||||
public class SitePageSettingsService
|
||||
{
|
||||
private readonly SitePageSettingsContract.SitePageSettingsContractClient _client;
|
||||
|
||||
public SitePageSettingsService(SitePageSettingsContract.SitePageSettingsContractClient client)
|
||||
{
|
||||
_client = client;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// دریافت تنظیمات صفحه با کلید
|
||||
/// </summary>
|
||||
public async Task<PageSettingsDto?> GetPageAsync(string pageKey)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _client.GetPageSettingsAsync(
|
||||
new GetPageSettingsRequest { PageKey = pageKey });
|
||||
|
||||
if (response == null || response.Id <= 0) return null;
|
||||
|
||||
return MapToDto(response);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
#if DEBUG
|
||||
Console.WriteLine($"SitePageSettingsService.GetPageAsync error: {ex.Message}");
|
||||
#endif
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static PageSettingsDto MapToDto(PageSettingsResponse response) => new()
|
||||
{
|
||||
Id = response.Id,
|
||||
PageKey = response.PageKey,
|
||||
Title = response.Title,
|
||||
MetaDescription = response.MetaDescription,
|
||||
HeroTitle = response.HeroTitle,
|
||||
HeroSubtitle = response.HeroSubtitle,
|
||||
HeroImagePath = response.HeroImagePath,
|
||||
IsActive = response.IsActive,
|
||||
SettingsJson = response.SettingsJson,
|
||||
Images = response.Images.Select(i => new PageSettingsImageDto
|
||||
{
|
||||
Id = i.Id,
|
||||
ImageGroup = i.ImageGroup,
|
||||
Title = i.Title,
|
||||
Subtitle = i.Subtitle,
|
||||
Description = i.Description,
|
||||
ImagePath = i.ImagePath,
|
||||
ThumbnailPath = i.ThumbnailPath,
|
||||
LinkUrl = i.LinkUrl,
|
||||
IconName = i.IconName,
|
||||
SortOrder = i.SortOrder,
|
||||
IsActive = i.IsActive
|
||||
}).OrderBy(i => i.SortOrder).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
// ── DTOs ──
|
||||
|
||||
public class PageSettingsDto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string PageKey { get; set; } = string.Empty;
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string? MetaDescription { get; set; }
|
||||
public string? HeroTitle { get; set; }
|
||||
public string? HeroSubtitle { get; set; }
|
||||
public string? HeroImagePath { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
public string? SettingsJson { get; set; }
|
||||
public List<PageSettingsImageDto> Images { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// دریافت تصاویر یک گروه خاص
|
||||
/// </summary>
|
||||
public List<PageSettingsImageDto> GetImages(string group) =>
|
||||
Images.Where(i => i.ImageGroup == group && i.IsActive)
|
||||
.OrderBy(i => i.SortOrder).ToList();
|
||||
|
||||
/// <summary>
|
||||
/// دریافت تنظیمات JSON به عنوان یک نوع مشخص
|
||||
/// </summary>
|
||||
public T? GetSettings<T>() where T : class
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(SettingsJson)) return null;
|
||||
try
|
||||
{
|
||||
return JsonSerializer.Deserialize<T>(SettingsJson, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
});
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class PageSettingsImageDto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string ImageGroup { get; set; } = string.Empty;
|
||||
public string? Title { get; set; }
|
||||
public string? Subtitle { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public string ImagePath { get; set; } = string.Empty;
|
||||
public string? ThumbnailPath { get; set; }
|
||||
public string? LinkUrl { get; set; }
|
||||
public string? IconName { get; set; }
|
||||
public int SortOrder { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"GwUrl": "https://cms.kbs2.ir",
|
||||
"DownloadUrl": "",
|
||||
"EncryptionSettings": {
|
||||
"Key": "kmcQ3XTmH4mrdh8VHziuscyf8LLYjG//Kyni81nH/0E=",
|
||||
"IV": "1wyF3Tt142MOkCpIyCxh/g=="
|
||||
},
|
||||
"SignalR": {
|
||||
"HubPath": "/hubs/token-notification"
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
// "GwUrl": "https://localhost:34781",
|
||||
"GwUrl": "https://localhost:32846",
|
||||
"DownloadUrl": "",
|
||||
"EncryptionSettings": {
|
||||
|
||||
Reference in New Issue
Block a user