diff --git a/src/BackOffice/BackOffice.csproj b/src/BackOffice/BackOffice.csproj
index 026414b..358c6ee 100644
--- a/src/BackOffice/BackOffice.csproj
+++ b/src/BackOffice/BackOffice.csproj
@@ -116,7 +116,7 @@
-
+
diff --git a/src/BackOffice/Common/Configure/ConfigureService.cs b/src/BackOffice/Common/Configure/ConfigureService.cs
index b933d23..f2f9d62 100644
--- a/src/BackOffice/Common/Configure/ConfigureService.cs
+++ b/src/BackOffice/Common/Configure/ConfigureService.cs
@@ -12,6 +12,7 @@ using Foursat.BackOffice.BFF.ClubMembership.Protos;
using Foursat.BackOffice.BFF.Configuration.Protos;
using Foursat.BackOffice.BFF.NetworkMembership.Protos;
using Foursat.BackOffice.BFF.Health.Protobuf;
+using BackOffice.BFF.Configuration.Protobuf.Protos.AppVersion;
// TODO: Create these proto projects - temporarily disabled
// using BackOffice.BFF.DiscountProduct.Protobuf.Protos.DiscountProduct;
@@ -68,6 +69,7 @@ public static class ConfigureServices
// Application Services
services.AddScoped();
+ services.AddScoped();
// TODO: Re-enable when proto projects are created
// services.AddScoped();
// services.AddScoped();
@@ -116,6 +118,7 @@ public static class ConfigureServices
services.AddTransient(sp => new ClubMembershipContract.ClubMembershipContractClient(sp.GetRequiredService()));
services.AddTransient(sp => new ConfigurationContract.ConfigurationContractClient(sp.GetRequiredService()));
services.AddTransient(sp => new HealthContract.HealthContractClient(sp.GetRequiredService()));
+ services.AddTransient(sp => new AppVersionContract.AppVersionContractClient(sp.GetRequiredService()));
// TODO: Re-enable when proto projects are created
// Discount Shop Services
diff --git a/src/BackOffice/Pages/Settings/AppVersions.razor b/src/BackOffice/Pages/Settings/AppVersions.razor
new file mode 100644
index 0000000..3e815f0
--- /dev/null
+++ b/src/BackOffice/Pages/Settings/AppVersions.razor
@@ -0,0 +1,310 @@
+@page "/settings/app-versions"
+@attribute [Authorize]
+
+@using BackOffice.Services.AppVersion
+@using BackOffice.Pages.Settings.Components
+@inject IAppVersionService AppVersionService
+@inject IDialogService DialogService
+@inject ISnackbar Snackbar
+
+مدیریت نسخه اپلیکیشنها
+
+
+
+
+ مدیریت نسخه اپلیکیشنها
+
+
+ مدیریت نسخههای اپلیکیشنهای موبایل و کنترل بهروزرسانی اجباری
+
+
+
+
+
+
+
+ بارگذاری مجدد
+
+
+
+ @if (_loading)
+ {
+
+ }
+
+
+
+
+
+
+
+
+
+ @context.Item.AppNameDisplay
+
+
+
+
+
+
+
+ v@context.Item.CurrentVersion
+
+
+
+
+
+
+ @if (!string.IsNullOrEmpty(context.Item.MinRequiredVersion))
+ {
+
+ v@context.Item.MinRequiredVersion
+
+ }
+ else
+ {
+ -
+ }
+
+
+
+
+
+ @if (context.Item.RequiresFullCacheClear)
+ {
+
+ }
+ else
+ {
+
+ }
+
+
+
+
+
+ @if (context.Item.IsActive)
+ {
+ فعال
+ }
+ else
+ {
+ غیرفعال
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @* Info Cards *@
+
+ @foreach (var version in _versions.Where(v => v.IsActive))
+ {
+
+
+
+
+
+
+
+
+
+ @version.AppNameDisplay
+ @version.AppName
+
+
+
+
+
+
+
+
+ نسخه فعلی:
+ v@version.CurrentVersion
+
+
+
+ حداقل نسخه:
+ @if (!string.IsNullOrEmpty(version.MinRequiredVersion))
+ {
+ v@version.MinRequiredVersion
+ }
+ else
+ {
+ تنظیم نشده
+ }
+
+
+ @if (version.RequiresFullCacheClear)
+ {
+
+ نیاز به پاکسازی کامل کش دارد
+
+ }
+
+ @if (!string.IsNullOrEmpty(version.UpdateMessage))
+ {
+
+ پیام بهروزرسانی:
+ @version.UpdateMessage
+ }
+
+
+
+
+ }
+
+
+
+@code {
+ private List _versions = new();
+ private bool _loading = false;
+ private bool _includeInactive = false;
+
+ protected override async Task OnInitializedAsync()
+ {
+ await LoadVersions();
+ }
+
+ private async Task LoadVersions()
+ {
+ _loading = true;
+ try
+ {
+ _versions = await AppVersionService.GetAllAsync(_includeInactive);
+ Snackbar.Add("اطلاعات نسخهها بارگذاری شد", Severity.Success);
+ }
+ catch (Exception ex)
+ {
+ Snackbar.Add($"خطا در بارگذاری: {ex.Message}", Severity.Error);
+ }
+ finally
+ {
+ _loading = false;
+ }
+ }
+
+ private async Task OpenEditDialog(AppVersionDto version)
+ {
+ var parameters = new DialogParameters
+ {
+ { x => x.Model, new UpdateAppVersionDto
+ {
+ AppName = version.AppName,
+ CurrentVersion = version.CurrentVersion,
+ MinRequiredVersion = version.MinRequiredVersion,
+ RequiresFullCacheClear = version.RequiresFullCacheClear,
+ UpdateMessage = version.UpdateMessage,
+ ReleaseNotes = version.ReleaseNotes
+ }
+ }
+ };
+
+ var options = new DialogOptions
+ {
+ CloseButton = true,
+ MaxWidth = MaxWidth.Medium,
+ FullWidth = true
+ };
+
+ var dialog = await DialogService.ShowAsync(
+ $"ویرایش نسخه {version.AppNameDisplay}",
+ parameters,
+ options);
+
+ var result = await dialog.Result;
+
+ if (result is { Canceled: false, Data: UpdateAppVersionDto dto })
+ {
+ try
+ {
+ await AppVersionService.UpdateAsync(dto);
+ Snackbar.Add("نسخه با موفقیت بهروزرسانی شد", Severity.Success);
+ await LoadVersions();
+ }
+ catch (Exception ex)
+ {
+ Snackbar.Add($"خطا در بهروزرسانی: {ex.Message}", Severity.Error);
+ }
+ }
+ }
+
+ private void OpenDetailsDialog(AppVersionDto version)
+ {
+ var parameters = new DialogParameters
+ {
+ { "ContentText", BuildDetailsContent(version) },
+ { "ButtonText", "بستن" },
+ { "Color", Color.Primary }
+ };
+
+ DialogService.Show($"جزئیات {version.AppNameDisplay}", parameters);
+ }
+
+ private string BuildDetailsContent(AppVersionDto version)
+ {
+ var lines = new List
+ {
+ $"نام اپلیکیشن: {version.AppName}",
+ $"نسخه فعلی: {version.CurrentVersion}",
+ $"حداقل نسخه: {version.MinRequiredVersion ?? "تنظیم نشده"}",
+ $"نیاز به پاکسازی کش: {(version.RequiresFullCacheClear ? "بله" : "خیر")}",
+ $"وضعیت: {(version.IsActive ? "فعال" : "غیرفعال")}",
+ "",
+ "پیام بهروزرسانی:",
+ version.UpdateMessage ?? "ندارد",
+ "",
+ "یادداشتهای انتشار:",
+ version.ReleaseNotes ?? "ندارد",
+ "",
+ $"تاریخ ایجاد: {version.Created?.ToString("yyyy/MM/dd HH:mm") ?? "-"}",
+ $"آخرین تغییر: {version.LastModified?.ToString("yyyy/MM/dd HH:mm") ?? "-"}"
+ };
+ return string.Join(Environment.NewLine, lines);
+ }
+
+ private string GetAppIcon(string appName) => appName switch
+ {
+ "FoursatMarketApp" => Icons.Material.Filled.ShoppingCart,
+ "FoursatClubApp" => Icons.Material.Filled.Groups,
+ _ => Icons.Material.Filled.PhoneAndroid
+ };
+
+ private Color GetAppColor(string appName) => appName switch
+ {
+ "FoursatMarketApp" => Color.Primary,
+ "FoursatClubApp" => Color.Secondary,
+ _ => Color.Default
+ };
+}
diff --git a/src/BackOffice/Pages/Settings/Components/AppVersionEditDialog.razor b/src/BackOffice/Pages/Settings/Components/AppVersionEditDialog.razor
new file mode 100644
index 0000000..e3231b9
--- /dev/null
+++ b/src/BackOffice/Pages/Settings/Components/AppVersionEditDialog.razor
@@ -0,0 +1,83 @@
+@using BackOffice.Services.AppVersion
+@inject ISnackbar Snackbar
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ انصراف
+
+
+ ذخیره
+
+
+
+
+@code {
+ [CascadingParameter]
+ private IMudDialogInstance MudDialog { get; set; } = null!;
+
+ [Parameter]
+ public UpdateAppVersionDto Model { get; set; } = new();
+
+ private MudForm? _form;
+ private bool _isValid;
+
+ private void Cancel() => MudDialog.Cancel();
+
+ private void Submit()
+ {
+ if (_isValid)
+ {
+ MudDialog.Close(DialogResult.Ok(Model));
+ }
+ }
+}
diff --git a/src/BackOffice/Services/AppVersion/AppVersionService.cs b/src/BackOffice/Services/AppVersion/AppVersionService.cs
new file mode 100644
index 0000000..4a6b26d
--- /dev/null
+++ b/src/BackOffice/Services/AppVersion/AppVersionService.cs
@@ -0,0 +1,90 @@
+using BackOffice.BFF.Configuration.Protobuf.Protos.AppVersion;
+using Google.Protobuf.WellKnownTypes;
+
+namespace BackOffice.Services.AppVersion;
+
+public class AppVersionService : IAppVersionService
+{
+ private readonly AppVersionContract.AppVersionContractClient _client;
+
+ public AppVersionService(AppVersionContract.AppVersionContractClient client)
+ {
+ _client = client;
+ }
+
+ public async Task> GetAllAsync(bool includeInactive = false)
+ {
+ var request = new GetAllAppVersionsRequest
+ {
+ IncludeInactive = includeInactive
+ };
+
+ var response = await _client.GetAllAppVersionsAsync(request);
+
+ return response.Items.Select(item => new AppVersionDto
+ {
+ Id = item.Id,
+ AppName = item.AppName,
+ CurrentVersion = item.CurrentVersion,
+ MinRequiredVersion = item.MinRequiredVersion,
+ RequiresFullCacheClear = item.RequiresFullCacheClear,
+ UpdateMessage = item.UpdateMessage,
+ ReleaseNotes = item.ReleaseNotes,
+ IsActive = item.IsActive,
+ Created = item.Created?.ToDateTime(),
+ LastModified = item.LastModified?.ToDateTime()
+ }).ToList();
+ }
+
+ public async Task GetByNameAsync(string appName)
+ {
+ var request = new GetAppVersionRequest
+ {
+ AppName = appName
+ };
+
+ var response = await _client.GetAppVersionAsync(request);
+
+ if (!response.Found || response.Item == null)
+ return null;
+
+ var item = response.Item;
+ return new AppVersionDto
+ {
+ Id = item.Id,
+ AppName = item.AppName,
+ CurrentVersion = item.CurrentVersion,
+ MinRequiredVersion = item.MinRequiredVersion,
+ RequiresFullCacheClear = item.RequiresFullCacheClear,
+ UpdateMessage = item.UpdateMessage,
+ ReleaseNotes = item.ReleaseNotes,
+ IsActive = item.IsActive,
+ Created = item.Created?.ToDateTime(),
+ LastModified = item.LastModified?.ToDateTime()
+ };
+ }
+
+ public async Task UpdateAsync(UpdateAppVersionDto dto)
+ {
+ var request = new UpdateAppVersionRequest
+ {
+ AppName = dto.AppName,
+ CurrentVersion = dto.CurrentVersion,
+ RequiresFullCacheClear = dto.RequiresFullCacheClear
+ };
+
+ if (!string.IsNullOrWhiteSpace(dto.MinRequiredVersion))
+ request.MinRequiredVersion = dto.MinRequiredVersion;
+
+ if (!string.IsNullOrWhiteSpace(dto.UpdateMessage))
+ request.UpdateMessage = dto.UpdateMessage;
+
+ if (!string.IsNullOrWhiteSpace(dto.ReleaseNotes))
+ request.ReleaseNotes = dto.ReleaseNotes;
+
+ if (!string.IsNullOrWhiteSpace(dto.UpdateReason))
+ request.UpdateReason = dto.UpdateReason;
+
+ await _client.UpdateAppVersionAsync(request);
+ }
+}
diff --git a/src/BackOffice/Services/AppVersion/IAppVersionService.cs b/src/BackOffice/Services/AppVersion/IAppVersionService.cs
new file mode 100644
index 0000000..fa3dd37
--- /dev/null
+++ b/src/BackOffice/Services/AppVersion/IAppVersionService.cs
@@ -0,0 +1,41 @@
+namespace BackOffice.Services.AppVersion;
+
+public interface IAppVersionService
+{
+ Task> GetAllAsync(bool includeInactive = false);
+ Task GetByNameAsync(string appName);
+ Task UpdateAsync(UpdateAppVersionDto dto);
+}
+
+public class AppVersionDto
+{
+ public long Id { get; set; }
+ public string AppName { get; set; } = string.Empty;
+ public string AppNameDisplay => GetDisplayName(AppName);
+ public string CurrentVersion { get; set; } = string.Empty;
+ public string MinRequiredVersion { get; set; } = string.Empty;
+ public bool RequiresFullCacheClear { get; set; }
+ public string UpdateMessage { get; set; } = string.Empty;
+ public string ReleaseNotes { get; set; } = string.Empty;
+ public bool IsActive { get; set; }
+ public DateTime? Created { get; set; }
+ public DateTime? LastModified { get; set; }
+
+ private static string GetDisplayName(string appName) => appName switch
+ {
+ "FoursatMarketApp" => "اپ مارکت فورست",
+ "FoursatClubApp" => "اپ باشگاه فورست",
+ _ => appName
+ };
+}
+
+public class UpdateAppVersionDto
+{
+ public string AppName { get; set; } = string.Empty;
+ public string CurrentVersion { get; set; } = string.Empty;
+ public string? MinRequiredVersion { get; set; }
+ public bool RequiresFullCacheClear { get; set; }
+ public string? UpdateMessage { get; set; }
+ public string? ReleaseNotes { get; set; }
+ public string? UpdateReason { get; set; }
+}