diff --git a/src/FrontOffice.Main/App.razor.cs b/src/FrontOffice.Main/App.razor.cs index 4b99d91..c638ac6 100644 --- a/src/FrontOffice.Main/App.razor.cs +++ b/src/FrontOffice.Main/App.razor.cs @@ -17,8 +17,8 @@ public partial class App { await base.OnInitializedAsync(); - // Check app version and clear cache if needed - await AppVersionService.CheckVersionAndClearCacheIfNeededAsync(); + // Check app version and show update dialog if needed + await CheckAndShowVersionUpdateAsync(); // Check for referral code in URL query parameters var uri = Navigation.ToAbsoluteUri(Navigation.Uri); @@ -58,5 +58,77 @@ public partial class App } } } -} + /// + /// بررسی نسخه و نمایش دیالوگ آپدیت در صورت نیاز + /// فقط برای کاربران لاگین شده + /// + private async Task CheckAndShowVersionUpdateAsync() + { + try + { + // فقط برای کاربران لاگین شده + if (!await AuthService.IsAuthenticatedAsync()) + return; + + // forDialog: true یعنی اگه skip کرده ولی یک روز گذشته، دوباره نشون بده + var versionCheck = await AppVersionService.CheckVersionAsync(forDialog: true); + + if (!versionCheck.HasNewVersion || string.IsNullOrEmpty(versionCheck.NewVersion)) + return; + + // ثبت زمان نمایش دیالوگ + await AppVersionService.RecordDialogShownAsync(); + + // تنظیمات دیالوگ + var dialogOptions = new DialogOptions + { + BackdropClick = !versionCheck.IsForceUpdate, // اگر force باشه نتونه بیرون کلیک کنه + CloseOnEscapeKey = !versionCheck.IsForceUpdate, + CloseButton = !versionCheck.IsForceUpdate, + MaxWidth = MaxWidth.Small, + FullWidth = true + }; + + var parameters = new DialogParameters + { + { x => x.OldVersion, versionCheck.OldVersion }, + { x => x.NewVersion, versionCheck.NewVersion }, + { x => x.ReleaseNotes, versionCheck.ReleaseNotes }, + { x => x.UpdateMessage, versionCheck.UpdateMessage }, + { x => x.IsForceUpdate, versionCheck.IsForceUpdate } + }; + + var dialog = await DialogService.ShowAsync("بروزرسانی", parameters, dialogOptions); + var result = await dialog.Result; + + if (result is { Canceled: false, Data: bool shouldUpdate }) + { + if (shouldUpdate) + { + // کاربر خواست آپدیت کنه + await AppVersionService.ApplyUpdateAsync(versionCheck.NewVersion); + + // رفرش صفحه + Navigation.NavigateTo(Navigation.Uri, forceLoad: true); + } + else + { + // کاربر گفت بعداً (فقط وقتی force نیست) + await AppVersionService.SkipVersionAsync(versionCheck.NewVersion); + } + } + else if (versionCheck.IsForceUpdate) + { + // اگر force بود و دیالوگ بسته شد، باز هم آپدیت کن + await AppVersionService.ApplyUpdateAsync(versionCheck.NewVersion); + Navigation.NavigateTo(Navigation.Uri, forceLoad: true); + } + } + catch (Exception ex) + { + // در صورت خطا، لاگ کن ولی ادامه بده + Console.WriteLine($"Error checking version: {ex.Message}"); + } + } +} \ No newline at end of file diff --git a/src/FrontOffice.Main/Pages/_Host.cshtml b/src/FrontOffice.Main/Pages/_Host.cshtml index f99261c..6925fc6 100644 --- a/src/FrontOffice.Main/Pages/_Host.cshtml +++ b/src/FrontOffice.Main/Pages/_Host.cshtml @@ -106,6 +106,29 @@ // اجرای اولیه (حتی اگر کاربر اسکرول نکرده) requestAnimationFrame(update); } + + // پاک کردن کش مرورگر (Cache Storage و Service Worker) + window.clearBrowserCache = async function() { + // پاک کردن Cache Storage (Service Worker cache) + if ('caches' in window) { + const cacheNames = await caches.keys(); + await Promise.all( + cacheNames.map(cacheName => caches.delete(cacheName)) + ); + console.log('Cache Storage cleared:', cacheNames.length, 'caches deleted'); + } + + // پاک کردن Service Worker registration + if ('serviceWorker' in navigator) { + const registrations = await navigator.serviceWorker.getRegistrations(); + await Promise.all( + registrations.map(registration => registration.unregister()) + ); + console.log('Service Workers unregistered:', registrations.length); + } + + return true; + }; diff --git a/src/FrontOffice.Main/Shared/MainLayout.razor b/src/FrontOffice.Main/Shared/MainLayout.razor index 4d31492..04f6f40 100644 --- a/src/FrontOffice.Main/Shared/MainLayout.razor +++ b/src/FrontOffice.Main/Shared/MainLayout.razor @@ -63,11 +63,32 @@ @* *@ + @* دکمه بروزرسانی *@ + @if (_hasUpdate) + { + + + + } پروفایل + @if (_hasUpdate) + { + + بروزرسانی (@_newVersion) + + + } + + @(_isDark ? "حالت روشن" : "حالت تاریک") + + خروج از حساب } @@ -75,10 +96,10 @@ { + } - - @@ -114,6 +135,19 @@ { پروفایل + @if (_hasUpdate) + { + + بروزرسانی (@_newVersion) + + } + + @(_isDark ? "حالت روشن" : "حالت تاریک") + خروج از حساب @@ -122,6 +156,11 @@ { ورود + + @(_isDark ? "حالت روشن" : "حالت تاریک") + } diff --git a/src/FrontOffice.Main/Shared/MainLayout.razor.cs b/src/FrontOffice.Main/Shared/MainLayout.razor.cs index ec1dfd3..1735bce 100644 --- a/src/FrontOffice.Main/Shared/MainLayout.razor.cs +++ b/src/FrontOffice.Main/Shared/MainLayout.razor.cs @@ -16,11 +16,17 @@ public partial class MainLayout : IDisposable private bool _isAuthenticated; private string? _email; private int _cartCount; + + // متغیرهای بروزرسانی + private bool _hasUpdate; + private string? _newVersion; + private VersionCheckResult? _versionCheckResult; [Inject] private ILocalStorageService LocalStorage { get; set; } = default!; [Inject] private AuthService AuthService { get; set; } = default!; [Inject] private AuthDialogService AuthDialogService { get; set; } = default!; [Inject] private CartService CartService { get; set; } = default!; + [Inject] private AppVersionService AppVersionService { get; set; } = default!; private void ToggleTheme() => _isDark = !_isDark; private void ToggleDrawer() => _drawerOpen = !_drawerOpen; @@ -42,12 +48,83 @@ public partial class MainLayout : IDisposable await CartService.EnsureInitializedAsync(); CartService.OnChange += OnCartChanged; _cartCount = CartService.Count; + + // چک کردن بروزرسانی (بدون نمایش popup - فقط برای نشون دادن آیکون) + await CheckForUpdateSilentlyAsync(); } StateHasChanged(); } } + /// + /// چک کردن بروزرسانی بدون نمایش popup + /// + private async Task CheckForUpdateSilentlyAsync() + { + try + { + _versionCheckResult = await AppVersionService.CheckVersionAsync(ignoreSkipped: true); + + if (_versionCheckResult.HasNewVersion && !string.IsNullOrEmpty(_versionCheckResult.NewVersion)) + { + _hasUpdate = true; + _newVersion = _versionCheckResult.NewVersion; + } + } + catch + { + // در صورت خطا نادیده بگیر + } + } + + /// + /// باز کردن دیالوگ بروزرسانی + /// + private async Task OpenUpdateDialog() + { + if (_versionCheckResult == null || string.IsNullOrEmpty(_versionCheckResult.NewVersion)) + return; + + // ثبت زمان نمایش دیالوگ + await AppVersionService.RecordDialogShownAsync(); + + var dialogOptions = new DialogOptions + { + BackdropClick = !_versionCheckResult.IsForceUpdate, + CloseOnEscapeKey = !_versionCheckResult.IsForceUpdate, + CloseButton = !_versionCheckResult.IsForceUpdate, + MaxWidth = MaxWidth.Small, + FullWidth = true + }; + + var parameters = new DialogParameters + { + { x => x.OldVersion, _versionCheckResult.OldVersion }, + { x => x.NewVersion, _versionCheckResult.NewVersion }, + { x => x.ReleaseNotes, _versionCheckResult.ReleaseNotes }, + { x => x.UpdateMessage, _versionCheckResult.UpdateMessage }, + { x => x.IsForceUpdate, _versionCheckResult.IsForceUpdate } + }; + + var dialog = await DialogService.ShowAsync("بروزرسانی", parameters, dialogOptions); + var result = await dialog.Result; + + if (result is { Canceled: false, Data: bool shouldUpdate }) + { + if (shouldUpdate) + { + await AppVersionService.ApplyUpdateAsync(_versionCheckResult.NewVersion); + Navigation.NavigateTo(Navigation.Uri, forceLoad: true); + } + else + { + await AppVersionService.SkipVersionAsync(_versionCheckResult.NewVersion); + // نمایش آپدیت همچنان باقی بمونه (hasUpdate true بمونه) + } + } + } + private void OnCartChanged() { _cartCount = CartService.Count; diff --git a/src/FrontOffice.Main/Shared/ReleaseNotesDialog.razor b/src/FrontOffice.Main/Shared/ReleaseNotesDialog.razor new file mode 100644 index 0000000..b4742a0 --- /dev/null +++ b/src/FrontOffice.Main/Shared/ReleaseNotesDialog.razor @@ -0,0 +1,130 @@ +@using FrontOffice.Main.Utilities +@using MudBlazor + + + + + + + @if (IsForceUpdate) + { + بروزرسانی اجباری + } + else + { + نسخه جدید موجود است + } + + + + + + + @* اطلاعات نسخه *@ + + + نسخه فعلی: + @(OldVersion ?? "---") + + + + نسخه جدید: + @NewVersion + + + + @* پیام آپدیت *@ + @if (!string.IsNullOrWhiteSpace(UpdateMessage)) + { + + @UpdateMessage + + } + + @* یادداشت‌های انتشار *@ + @if (!string.IsNullOrWhiteSpace(ReleaseNotes)) + { + تغییرات این نسخه: + + @((MarkupString)ReleaseNotes) + + } + + @if (IsForceUpdate) + { + + + این بروزرسانی اجباری است و باید انجام شود. + + + } + + + + + @if (!IsForceUpdate) + { + + بعداً + + } + + @if (_isUpdating) + { + + در حال بروزرسانی... + } + else + { + بروزرسانی + } + + + + +@code { + [CascadingParameter] + private IMudDialogInstance MudDialog { get; set; } = default!; + + [Parameter] + public string? OldVersion { get; set; } + + [Parameter] + public string NewVersion { get; set; } = string.Empty; + + [Parameter] + public string? ReleaseNotes { get; set; } + + [Parameter] + public string? UpdateMessage { get; set; } + + [Parameter] + public bool IsForceUpdate { get; set; } + + private bool _isUpdating; + + private async Task OnUpdateClicked() + { + _isUpdating = true; + StateHasChanged(); + + // کمی صبر کنیم برای UI + await Task.Delay(500); + + // برگردوندن نتیجه "آپدیت" + MudDialog.Close(DialogResult.Ok(true)); + } + + private void OnSkipClicked() + { + // برگردوندن نتیجه "بعداً" + MudDialog.Close(DialogResult.Ok(false)); + } +} diff --git a/src/FrontOffice.Main/Utilities/AppVersionService.cs b/src/FrontOffice.Main/Utilities/AppVersionService.cs index f301685..43165bd 100644 --- a/src/FrontOffice.Main/Utilities/AppVersionService.cs +++ b/src/FrontOffice.Main/Utilities/AppVersionService.cs @@ -1,8 +1,50 @@ using FrontOffice.BFF.Configuration.Protobuf.Protos.AppVersion; using Blazored.LocalStorage; +using Microsoft.JSInterop; namespace FrontOffice.Main.Utilities; +/// +/// نتیجه بررسی نسخه اپلیکیشن +/// +public class VersionCheckResult +{ + /// + /// آیا نسخه جدیدی وجود دارد + /// + public bool HasNewVersion { get; set; } + + /// + /// آیا آپدیت اجباری است (force update) + /// + public bool IsForceUpdate { get; set; } + + /// + /// نسخه فعلی (local) + /// + public string? OldVersion { get; set; } + + /// + /// نسخه جدید (server) + /// + public string? NewVersion { get; set; } + + /// + /// یادداشت‌های انتشار + /// + public string? ReleaseNotes { get; set; } + + /// + /// پیام آپدیت + /// + public string? UpdateMessage { get; set; } + + /// + /// آیا کش باید پاک شود + /// + public bool RequiresCacheClear { get; set; } +} + /// /// سرویس مدیریت نسخه اپلیکیشن و کش /// وقتی نسخه جدید منتشر بشه، این سرویس متوجه میشه و کش رو پاک می‌کنه @@ -11,31 +53,44 @@ public class AppVersionService { private readonly AppVersionContract.AppVersionContractClient _client; private readonly ILocalStorageService _localStorage; + private readonly IJSRuntime _jsRuntime; private readonly ILogger _logger; - private const string APP_NAME = "FrontOffice"; + private const string APP_NAME = "KaraBazarApp"; private const string LOCAL_VERSION_KEY = "app_version"; private const string LAST_CHECK_KEY = "app_version_last_check"; + private const string SKIPPED_VERSION_KEY = "app_version_skipped"; + private const string LAST_DIALOG_SHOWN_KEY = "app_version_last_dialog_shown"; public AppVersionService( AppVersionContract.AppVersionContractClient client, ILocalStorageService localStorage, + IJSRuntime jsRuntime, ILogger logger) { _client = client; _localStorage = localStorage; + _jsRuntime = jsRuntime; _logger = logger; } /// - /// بررسی نسخه اپلیکیشن و پاک کردن کش در صورت نیاز + /// بررسی نسخه اپلیکیشن و برگرداندن نتیجه /// - public async Task CheckVersionAndClearCacheIfNeededAsync() + /// اگر true باشه، نسخه‌های skip شده هم نشون داده میشن (برای آیکون منو) + /// اگر true باشه، چک میکنه که یک روز از آخرین نمایش گذشته (برای popup) + public async Task CheckVersionAsync(bool ignoreSkipped = false, bool forDialog = false) { + var result = new VersionCheckResult(); + try { // دریافت نسخه فعلی از localStorage var localVersion = await _localStorage.GetItemAsStringAsync(LOCAL_VERSION_KEY); + var skippedVersion = await _localStorage.GetItemAsStringAsync(SKIPPED_VERSION_KEY); + var lastDialogShown = await _localStorage.GetItemAsStringAsync(LAST_DIALOG_SHOWN_KEY); + + result.OldVersion = localVersion; // بررسی نسخه از سرور var response = await _client.GetAppVersionAsync(new GetAppVersionRequest @@ -47,32 +102,119 @@ public class AppVersionService if (!response.Found) { _logger.LogWarning("App version not found on server for {AppName}", APP_NAME); - return; + return result; } var serverVersion = response.CurrentVersion; + result.NewVersion = serverVersion; + result.ReleaseNotes = response.ReleaseNotes; + result.UpdateMessage = response.UpdateMessage; + result.RequiresCacheClear = response.RequiresFullCacheClear; + result.IsForceUpdate = response.RequiresUpdate; // اگر نسخه کمتر از حداقل باشه - // اگر نسخه جدید باشه یا پاک کردن کش لازم باشه - if (localVersion != serverVersion || response.RequiresFullCacheClear) + // آیا نسخه جدید داریم؟ + if (localVersion != serverVersion) { + result.HasNewVersion = true; + + // اگر این نسخه رو قبلاً skip کرده و force نیست + if (!result.IsForceUpdate && skippedVersion == serverVersion && !ignoreSkipped) + { + // برای popup: چک کن یک روز گذشته یا نه + if (forDialog) + { + var oneDayPassed = await HasOneDayPassedSinceLastDialogAsync(lastDialogShown); + if (!oneDayPassed) + { + result.HasNewVersion = false; + } + // اگه یک روز گذشته، HasNewVersion = true میمونه + } + else + { + result.HasNewVersion = false; + } + } + _logger.LogInformation( - "New version detected: {OldVersion} -> {NewVersion}, CacheClear: {CacheClear}", - localVersion ?? "null", serverVersion, response.RequiresFullCacheClear); - - // پاک کردن کش - await ClearAllCacheAsync(); - - // ذخیره نسخه جدید - await _localStorage.SetItemAsStringAsync(LOCAL_VERSION_KEY, serverVersion); - await _localStorage.SetItemAsStringAsync(LAST_CHECK_KEY, DateTime.UtcNow.ToString("O")); - - _logger.LogInformation("Cache cleared and version updated to {Version}", serverVersion); + "Version check: {OldVersion} -> {NewVersion}, Force: {Force}, Skipped: {Skipped}", + localVersion ?? "null", serverVersion, result.IsForceUpdate, skippedVersion); } } catch (Exception ex) { _logger.LogError(ex, "Error checking app version"); } + + return result; + } + + /// + /// چک کردن اینکه یک روز از آخرین نمایش دیالوگ گذشته یا نه + /// + private async Task HasOneDayPassedSinceLastDialogAsync(string? lastDialogShown) + { + if (string.IsNullOrEmpty(lastDialogShown)) + return true; // اگه هیچوقت نشون نداده، نشون بده + + if (DateTime.TryParse(lastDialogShown, out var lastShown)) + { + return DateTime.UtcNow.Subtract(lastShown).TotalDays >= 1; + } + + return true; + } + + /// + /// ثبت زمان نمایش دیالوگ + /// + public async Task RecordDialogShownAsync() + { + await _localStorage.SetItemAsStringAsync(LAST_DIALOG_SHOWN_KEY, DateTime.UtcNow.ToString("O")); + } + + /// + /// اعمال آپدیت - پاک کردن کش و ذخیره نسخه جدید + /// + public async Task ApplyUpdateAsync(string newVersion) + { + try + { + _logger.LogInformation("Applying update to version {Version}", newVersion); + + // پاک کردن کش + await ClearAllCacheAsync(); + + // ذخیره نسخه جدید + await _localStorage.SetItemAsStringAsync(LOCAL_VERSION_KEY, newVersion); + await _localStorage.SetItemAsStringAsync(LAST_CHECK_KEY, DateTime.UtcNow.ToString("O")); + + // پاک کردن نسخه skip شده + await _localStorage.RemoveItemAsync(SKIPPED_VERSION_KEY); + + _logger.LogInformation("Update applied successfully to {Version}", newVersion); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error applying update"); + throw; + } + } + + /// + /// Skip کردن این نسخه (بعداً) + /// + public async Task SkipVersionAsync(string version) + { + try + { + await _localStorage.SetItemAsStringAsync(SKIPPED_VERSION_KEY, version); + _logger.LogInformation("Version {Version} skipped by user", version); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error skipping version"); + } } /// @@ -82,33 +224,14 @@ public class AppVersionService { try { - // لیست کلیدهایی که باید حفظ بشن (مثل توکن و اطلاعات کاربر) - var keysToKeep = new HashSet - { - "access_token", - "refresh_token", - "user_info", - "user_roles" - }; + // پاک کردن کش مرورگر (Cache Storage و Service Worker) + await _jsRuntime.InvokeVoidAsync("clearBrowserCache"); - // دریافت همه کلیدها - var allKeys = await _localStorage.KeysAsync(); - - // پاک کردن کلیدهایی که در لیست حفظ نیستن - foreach (var key in allKeys) - { - if (!keysToKeep.Contains(key) && key != LOCAL_VERSION_KEY) - { - await _localStorage.RemoveItemAsync(key); - } - } - - _logger.LogInformation("Local storage cache cleared, {Count} keys removed", - allKeys.Count() - keysToKeep.Count); + _logger.LogInformation("Browser cache cleared successfully"); } catch (Exception ex) { - _logger.LogError(ex, "Error clearing cache"); + _logger.LogError(ex, "Error clearing browser cache"); } }