feat: Add Licenses page + SitePageSettingsService
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 6m18s
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 6m18s
- SitePageSettingsService: new gRPC client for simplified page settings - Licenses.razor: new page showing license certificates from CMS - RouteConstants: added Licenses route - Footer + MainLayout: added Licenses navigation link - Proto NuGet updated to 0.0.180
This commit is contained in:
@@ -22,6 +22,7 @@ using CMSMicroservice.Protobuf.Protos.NetworkMembership;
|
|||||||
using CMSMicroservice.Protobuf.Protos.Commission;
|
using CMSMicroservice.Protobuf.Protos.Commission;
|
||||||
using CMSMicroservice.Protobuf.Protos.AppVersion;
|
using CMSMicroservice.Protobuf.Protos.AppVersion;
|
||||||
using CMSMicroservice.Protobuf.Protos.SitePage;
|
using CMSMicroservice.Protobuf.Protos.SitePage;
|
||||||
|
using CMSMicroservice.Protobuf.Protos.SitePageSettings;
|
||||||
using CMSMicroservice.Protobuf.Protos.BlogPost;
|
using CMSMicroservice.Protobuf.Protos.BlogPost;
|
||||||
using CMSMicroservice.Protobuf.Protos.BlogCategory;
|
using CMSMicroservice.Protobuf.Protos.BlogCategory;
|
||||||
using CMSMicroservice.Protobuf.Protos.DiscountProduct;
|
using CMSMicroservice.Protobuf.Protos.DiscountProduct;
|
||||||
@@ -83,6 +84,8 @@ public static class ConfigureServices
|
|||||||
services.AddScoped<DiscountProductService>();
|
services.AddScoped<DiscountProductService>();
|
||||||
services.AddScoped<DiscountCartService>();
|
services.AddScoped<DiscountCartService>();
|
||||||
services.AddScoped<DiscountOrderService>();
|
services.AddScoped<DiscountOrderService>();
|
||||||
|
// Site Page Settings Service (simplified page management)
|
||||||
|
services.AddScoped<SitePageSettingsService>();
|
||||||
// Device detection: very light, dependency-free
|
// Device detection: very light, dependency-free
|
||||||
services.AddTransient<IDeviceDetector, DeviceDetector>();
|
services.AddTransient<IDeviceDetector, DeviceDetector>();
|
||||||
// PDF generation (Chromium only)
|
// 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.Commission.CommissionContract.CommissionContractClient>);
|
||||||
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.AppVersion.AppVersionContract.AppVersionContractClient>);
|
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.AppVersion.AppVersionContract.AppVersionContractClient>);
|
||||||
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.SitePage.SitePageContract.SitePageContractClient>);
|
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.BlogPost.BlogPostContract.BlogPostContractClient>);
|
||||||
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.BlogCategory.BlogCategoryContract.BlogCategoryContractClient>);
|
services.AddScoped(CreateAuthenticatedClient<CMSMicroservice.Protobuf.Protos.BlogCategory.BlogCategoryContract.BlogCategoryContractClient>);
|
||||||
// Image Resolver Service
|
// Image Resolver Service
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="DateTimeConverterCL" Version="1.0.0" />
|
<PackageReference Include="DateTimeConverterCL" Version="1.0.0" />
|
||||||
<!-- Replace all FrontOffice.BFF protobuf packages with CMS protobuf -->
|
<!-- 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" />-->
|
<!--<ProjectReference Include="../../../CMS/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj" />-->
|
||||||
<PackageReference Include="MudBlazor" Version="8.14.0" />
|
<PackageReference Include="MudBlazor" Version="8.14.0" />
|
||||||
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
|
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -55,6 +55,7 @@
|
|||||||
<MudLink Href="@(RouteConstants.FAQ.Index)" Class="footer-link">سوالات متداول</MudLink>
|
<MudLink Href="@(RouteConstants.FAQ.Index)" Class="footer-link">سوالات متداول</MudLink>
|
||||||
<MudLink Href="@(RouteConstants.Contact.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.About.Index)" Class="footer-link">درباره ما</MudLink>
|
||||||
|
<MudLink Href="@(RouteConstants.Licenses.Index)" Class="footer-link">مجوزها</MudLink>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
|
|
||||||
|
|||||||
@@ -73,6 +73,7 @@
|
|||||||
<MudLink Href="@(RouteConstants.Blog.Index)" Typo="Typo.subtitle1" Class="mud-link">بلاگ</MudLink>
|
<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.Contact.Index)" Typo="Typo.subtitle1" Class="mud-link">ارتباط با ما</MudLink>
|
||||||
<MudLink Href="@(RouteConstants.About.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>
|
||||||
|
|
||||||
<div class="d-flex align-center gap-2">
|
<div class="d-flex align-center gap-2">
|
||||||
@@ -191,6 +192,9 @@
|
|||||||
<MudLink Href="@(RouteConstants.Contact.Index)" Typo="Typo.subtitle1" OnClick="() => _drawerOpen=false">
|
<MudLink Href="@(RouteConstants.Contact.Index)" Typo="Typo.subtitle1" OnClick="() => _drawerOpen=false">
|
||||||
ارتباط با ما
|
ارتباط با ما
|
||||||
</MudLink>
|
</MudLink>
|
||||||
|
<MudLink Href="@(RouteConstants.Licenses.Index)" Typo="Typo.subtitle1" OnClick="() => _drawerOpen=false">
|
||||||
|
مجوزها
|
||||||
|
</MudLink>
|
||||||
|
|
||||||
<MudDivider Class="my-2"/>
|
<MudDivider Class="my-2"/>
|
||||||
@if (_isAuthenticated)
|
@if (_isAuthenticated)
|
||||||
|
|||||||
@@ -67,6 +67,11 @@ public static class RouteConstants
|
|||||||
public const string Index = "/contact";
|
public const string Index = "/contact";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class Licenses
|
||||||
|
{
|
||||||
|
public const string Index = "/licenses";
|
||||||
|
}
|
||||||
|
|
||||||
public static class Blog
|
public static class Blog
|
||||||
{
|
{
|
||||||
public const string Index = "/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; }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user