diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 0d4ca99..47f0f71 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -3,7 +3,7 @@ "hostRequirements": { "cpus": 4 }, - "onCreateCommand": "wget https://download.visualstudio.microsoft.com/download/pr/85bcc525-4e9c-471e-9c1d-96259aa1a315/930833ef34f66fe9ee2643b0ba21621a/dotnet-sdk-8.0.201-linux-x64.tar.gz -O $HOME/dotnet.tar.gz && export DOTNET_ROOT=$HOME/.dotnet && mkdir -p \"$DOTNET_ROOT\" && tar zxf $HOME/dotnet.tar.gz -C \"$DOTNET_ROOT\" && export PATH=$DOTNET_ROOT:$DOTNET_ROOT/tools:$PATH && dotnet dev-certs https --trust && sudo dotnet workload install wasm-tools wasm-experimental && dotnet tool install --global dotnet-ef --version 8.0.1 && dotnet ef database update --project src/Bit.TemplatePlayground.Server/Bit.TemplatePlayground.Server.csproj && dotnet build src/Client/Bit.TemplatePlayground.Client.Core/Bit.TemplatePlayground.Client.Core.csproj -t:BeforeBuildTasks --no-restore", + "onCreateCommand": "wget https://download.visualstudio.microsoft.com/download/pr/656a3402-6889-400f-927f-7f956856e58b/93750973d6eedd17c6d963658e7ec214/dotnet-sdk-8.0.203-linux-x64.tar.gz -O $HOME/dotnet.tar.gz && export DOTNET_ROOT=$HOME/.dotnet && mkdir -p \"$DOTNET_ROOT\" && tar zxf $HOME/dotnet.tar.gz -C \"$DOTNET_ROOT\" && export PATH=$DOTNET_ROOT:$DOTNET_ROOT/tools:$PATH && dotnet dev-certs https --trust && sudo dotnet workload install wasm-tools wasm-experimental && dotnet tool install --global dotnet-ef --version 8.0.3 && dotnet ef database update --project src/Bit.TemplatePlayground.Server/Bit.TemplatePlayground.Server.csproj && dotnet build src/Client/Bit.TemplatePlayground.Client.Core/Bit.TemplatePlayground.Client.Core.csproj -t:BeforeBuildTasks --no-restore", "waitFor": "onCreateCommand", "customizations": { "codespaces": { diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ac5e84..6ffbd52 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,4 +28,4 @@ jobs: node-version: 18 - name: Build - run: dotnet build Bit.TemplatePlayground.sln -c Release -p:EnableWindowsTargeting=true \ No newline at end of file + run: dotnet build Bit.TemplatePlayground.sln -c Release \ No newline at end of file diff --git a/global.json b/global.json index a5f5e20..00efd20 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.201", - "rollForward": "disable" + "version": "8.0.203", + "rollForward": "disable" } } \ No newline at end of file diff --git a/src/Bit.TemplatePlayground.Iac/Bit.TemplatePlayground.Iac.csproj b/src/Bit.TemplatePlayground.Iac/Bit.TemplatePlayground.Iac.csproj index 4dc3a46..15f89f2 100644 --- a/src/Bit.TemplatePlayground.Iac/Bit.TemplatePlayground.Iac.csproj +++ b/src/Bit.TemplatePlayground.Iac/Bit.TemplatePlayground.Iac.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/Bit.TemplatePlayground.Server/.config/dotnet-tools.json b/src/Bit.TemplatePlayground.Server/.config/dotnet-tools.json index 9d58272..d9d129c 100644 --- a/src/Bit.TemplatePlayground.Server/.config/dotnet-tools.json +++ b/src/Bit.TemplatePlayground.Server/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "dotnet-ef": { - "version": "8.0.2", + "version": "8.0.3", "commands": [ "dotnet-ef" ] diff --git a/src/Bit.TemplatePlayground.Server/Bit.TemplatePlayground.Server.csproj b/src/Bit.TemplatePlayground.Server/Bit.TemplatePlayground.Server.csproj index 371be25..a17e04a 100644 --- a/src/Bit.TemplatePlayground.Server/Bit.TemplatePlayground.Server.csproj +++ b/src/Bit.TemplatePlayground.Server/Bit.TemplatePlayground.Server.csproj @@ -2,18 +2,13 @@ net8.0 + 66B6DE78-12F7-4F5B-A1EC-180DBDEC4183 - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + @@ -25,23 +20,16 @@ - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + + + + + + + + @@ -87,8 +75,7 @@ linux-x64 - True - 44373297-7155-4EFA-9BEB-BB3FA5F85FE9 + True Linux ..\.. diff --git a/src/Bit.TemplatePlayground.Server/Components/App.razor b/src/Bit.TemplatePlayground.Server/Components/App.razor index 0bc7016..1628acf 100644 --- a/src/Bit.TemplatePlayground.Server/Components/App.razor +++ b/src/Bit.TemplatePlayground.Server/Components/App.razor @@ -36,6 +36,7 @@ { } + @if (HttpContext.Request.IsCrawlerClient() is false) diff --git a/src/Bit.TemplatePlayground.Server/Components/EmailConfirmationTemplate.razor b/src/Bit.TemplatePlayground.Server/Components/EmailConfirmationTemplate.razor index 1a2a794..d2a91c9 100644 --- a/src/Bit.TemplatePlayground.Server/Components/EmailConfirmationTemplate.razor +++ b/src/Bit.TemplatePlayground.Server/Components/EmailConfirmationTemplate.razor @@ -14,7 +14,7 @@ @EmailLocalizer[nameof(EmailStrings.ConfirmationEmailSubject)] - +
diff --git a/src/Bit.TemplatePlayground.Server/Components/ResetPasswordTemplate.razor b/src/Bit.TemplatePlayground.Server/Components/ResetPasswordTemplate.razor index 4e76ebd..9407f71 100644 --- a/src/Bit.TemplatePlayground.Server/Components/ResetPasswordTemplate.razor +++ b/src/Bit.TemplatePlayground.Server/Components/ResetPasswordTemplate.razor @@ -14,7 +14,7 @@ @EmailLocalizer[nameof(EmailStrings.ResetPasswordEmailSubject)] - +
diff --git a/src/Bit.TemplatePlayground.Server/Controllers/AttachmentController.cs b/src/Bit.TemplatePlayground.Server/Controllers/AttachmentController.cs index ea2a659..9d12a71 100644 --- a/src/Bit.TemplatePlayground.Server/Controllers/AttachmentController.cs +++ b/src/Bit.TemplatePlayground.Server/Controllers/AttachmentController.cs @@ -1,6 +1,6 @@ using Bit.TemplatePlayground.Server.Models.Identity; using ImageMagick; -using MimeTypes; +using Microsoft.AspNetCore.StaticFiles; using SystemFile = System.IO.File; namespace Bit.TemplatePlayground.Server.Controllers; @@ -13,6 +13,8 @@ public partial class AttachmentController : AppControllerBase [AutoInject] private IWebHostEnvironment webHostEnvironment = default!; + [AutoInject] private IContentTypeProvider contentTypeProvider = default!; + [HttpPost] [RequestSizeLimit(11 * 1024 * 1024 /*11MB*/)] public async Task UploadProfileImage(IFormFile? file, CancellationToken cancellationToken) @@ -134,7 +136,12 @@ public async Task GetProfileImage() if (SystemFile.Exists(filePath) is false) return new EmptyResult(); + if (contentTypeProvider.TryGetContentType(filePath, out var contentType) is false) + { + throw new InvalidOperationException(); + } + return PhysicalFile(Path.Combine(webHostEnvironment.ContentRootPath, filePath), - MimeTypeMap.GetMimeType(Path.GetExtension(filePath)), enableRangeProcessing: true); + contentType, enableRangeProcessing: true); } } diff --git a/src/Bit.TemplatePlayground.Server/Controllers/Identity/IdentityController.cs b/src/Bit.TemplatePlayground.Server/Controllers/Identity/IdentityController.cs index d2ce019..08832db 100644 --- a/src/Bit.TemplatePlayground.Server/Controllers/Identity/IdentityController.cs +++ b/src/Bit.TemplatePlayground.Server/Controllers/Identity/IdentityController.cs @@ -93,7 +93,7 @@ private async Task SendConfirmationEmail(SendConfirmationEmailRequestDto sendCon var controller = RouteData.Values["controller"]!.ToString(); - var confirmationLink = new Uri(HttpContext.Request.GetBaseUrl(), $"email-confirmation?email={HttpUtility.UrlEncode(user.Email)}&token={HttpUtility.UrlEncode(token)}"); + var confirmationLink = new Uri(HttpContext.Request.GetBaseUrl(), $"email-confirmation?email={Uri.EscapeDataString(user.Email!)}&token={Uri.EscapeDataString(token)}"); var body = await htmlRenderer.Dispatcher.InvokeAsync(async () => { @@ -205,7 +205,7 @@ public async Task SendResetPasswordEmail(SendResetPasswordEmailRequestDto sendRe var token = await userManager.GeneratePasswordResetTokenAsync(user); - var resetPasswordLink = new Uri(HttpContext.Request.GetBaseUrl(), $"reset-password?email={HttpUtility.UrlEncode(user.Email)}&token={HttpUtility.UrlEncode(token)}"); + var resetPasswordLink = new Uri(HttpContext.Request.GetBaseUrl(), $"reset-password?email={Uri.EscapeDataString(user.Email!)}&token={Uri.EscapeDataString(token)}"); var body = await htmlRenderer.Dispatcher.InvokeAsync(async () => { diff --git a/src/Bit.TemplatePlayground.Server/Program.Services.cs b/src/Bit.TemplatePlayground.Server/Program.Services.cs index 597cccb..ab4915c 100644 --- a/src/Bit.TemplatePlayground.Server/Program.Services.cs +++ b/src/Bit.TemplatePlayground.Server/Program.Services.cs @@ -12,6 +12,7 @@ using Microsoft.AspNetCore.OData; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; +using Microsoft.AspNetCore.StaticFiles; namespace Bit.TemplatePlayground.Server; @@ -64,11 +65,14 @@ private static void ConfigureServices(this WebApplicationBuilder builder) }; }); - services.AddDbContext(options => + services.AddDbContextPool(options => { + options.EnableSensitiveDataLogging(env.IsDevelopment()) + .EnableDetailedErrors(env.IsDevelopment()); + options.UseSqlite(configuration.GetConnectionString("SqliteConnectionString"), dbOptions => { - dbOptions.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); + }); }); @@ -85,6 +89,7 @@ private static void ConfigureServices(this WebApplicationBuilder builder) AddHealthChecks(builder); services.TryAddTransient(); + services.TryAddTransient(); var fluentEmailServiceBuilder = services.AddFluentEmail(appSettings.EmailSettings.DefaultFromEmail, appSettings.EmailSettings.DefaultFromName); @@ -136,7 +141,7 @@ private static void AddBlazor(WebApplicationBuilder builder) apiServerAddress = new Uri(sp.GetRequiredService().HttpContext!.Request.GetBaseUrl(), apiServerAddress); } - return new HttpClient(sp.GetRequiredKeyedService("DefaultMessageHandler")) + return new HttpClient(sp.GetRequiredKeyedService("DefaultMessageHandler")) { BaseAddress = apiServerAddress }; diff --git a/src/Bit.TemplatePlayground.Server/Resources/EmailStrings.fa.resx b/src/Bit.TemplatePlayground.Server/Resources/EmailStrings.fa.resx new file mode 100644 index 0000000..34202e7 --- /dev/null +++ b/src/Bit.TemplatePlayground.Server/Resources/EmailStrings.fa.resx @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Bit.TemplatePlayground - آدرس ایمیل خود را تایید کنید + + + تایید ایمیل + + + یا لینک زیر را به نوار آدرس مرورگر خود کپی کنید: + + + شما این پیام را دریافت می کنید زیرا اخیرا برای یک حساب Bit.TemplatePlayground ثبت نام کرده اید. + آدرس ایمیل خود را با کلیک بر روی دکمه زیر تایید کنید. + + + Bit.TemplatePlayground - رمز عبور خود را ریست کنید + + + سلام {0} + + + کسی برای تغییر رمز عبور شما لینکی درخواست کرده است. + + + رمز عبور شما تغییر نخواهد کرد تا زمانی که به لینک بالا دسترسی داشته باشید و یک رمز جدید ایجاد کنید. + + + رمز عبور خود را ریست کنید + + + Bit.TemplatePlayground + + + به Bit.TemplatePlayground خوش آمدید! + + \ No newline at end of file diff --git a/src/Bit.TemplatePlayground.Server/Services/ServerExceptionHandler.cs b/src/Bit.TemplatePlayground.Server/Services/ServerExceptionHandler.cs index c6d0cf2..e8905e2 100644 --- a/src/Bit.TemplatePlayground.Server/Services/ServerExceptionHandler.cs +++ b/src/Bit.TemplatePlayground.Server/Services/ServerExceptionHandler.cs @@ -1,4 +1,5 @@ -using System.Net; +using System.Diagnostics; +using System.Net; using System.Reflection; using System.Text.Json; using Microsoft.AspNetCore.Diagnostics; @@ -15,7 +16,7 @@ public partial class ServerExceptionHandler : IExceptionHandler public async ValueTask TryHandleAsync(HttpContext httpContext, Exception e, CancellationToken cancellationToken) { // Using the Request-Id header, one can find the log for server-related exceptions - httpContext.Response.Headers.Append(HeaderNames.RequestId, httpContext.TraceIdentifier); + httpContext.Response.Headers.Append(HeaderNames.RequestId, Activity.Current?.Id ?? httpContext.TraceIdentifier); var exception = UnWrapException(e); var knownException = exception as KnownException; diff --git a/src/Bit.TemplatePlayground.Server/appsettings.Production.json b/src/Bit.TemplatePlayground.Server/appsettings.Production.json new file mode 100644 index 0000000..544b7b4 --- /dev/null +++ b/src/Bit.TemplatePlayground.Server/appsettings.Production.json @@ -0,0 +1,3 @@ +{ + +} \ No newline at end of file diff --git a/src/Bit.TemplatePlayground.Shared/Bit.TemplatePlayground.Shared.csproj b/src/Bit.TemplatePlayground.Shared/Bit.TemplatePlayground.Shared.csproj index b22afbf..a6fd5f5 100644 --- a/src/Bit.TemplatePlayground.Shared/Bit.TemplatePlayground.Shared.csproj +++ b/src/Bit.TemplatePlayground.Shared/Bit.TemplatePlayground.Shared.csproj @@ -5,23 +5,15 @@ - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + + - - - compile; build; native; contentfiles; analyzers; buildtransitive - + + diff --git a/src/Bit.TemplatePlayground.Shared/Dtos/Identity/ResetPasswordRequestDto.cs b/src/Bit.TemplatePlayground.Shared/Dtos/Identity/ResetPasswordRequestDto.cs index 07c8848..f526165 100644 --- a/src/Bit.TemplatePlayground.Shared/Dtos/Identity/ResetPasswordRequestDto.cs +++ b/src/Bit.TemplatePlayground.Shared/Dtos/Identity/ResetPasswordRequestDto.cs @@ -17,7 +17,6 @@ public class ResetPasswordRequestDto [Display(Name = nameof(AppStrings.Password))] public string? Password { get; set; } - [NotMapped] [Required(ErrorMessage = nameof(AppStrings.RequiredAttribute_ValidationError))] [Compare(nameof(Password), ErrorMessage = nameof(AppStrings.CompareAttribute_ValidationError))] [Display(Name = nameof(AppStrings.ConfirmNewPassword))] diff --git a/src/Bit.TemplatePlayground.Shared/Dtos/Identity/SignInRequestDto.cs b/src/Bit.TemplatePlayground.Shared/Dtos/Identity/SignInRequestDto.cs index c88b92c..3514518 100644 --- a/src/Bit.TemplatePlayground.Shared/Dtos/Identity/SignInRequestDto.cs +++ b/src/Bit.TemplatePlayground.Shared/Dtos/Identity/SignInRequestDto.cs @@ -15,7 +15,7 @@ public class SignInRequestDto [Display(Name = nameof(AppStrings.Password))] public string? Password { get; set; } - [NotMapped] + [JsonIgnore] [Display(Name = nameof(AppStrings.RememberMe))] public bool RememberMe { get; set; } = true; } diff --git a/src/Bit.TemplatePlayground.Shared/Dtos/Identity/SignUpRequestDto.cs b/src/Bit.TemplatePlayground.Shared/Dtos/Identity/SignUpRequestDto.cs index 7fa1293..e566e5d 100644 --- a/src/Bit.TemplatePlayground.Shared/Dtos/Identity/SignUpRequestDto.cs +++ b/src/Bit.TemplatePlayground.Shared/Dtos/Identity/SignUpRequestDto.cs @@ -18,7 +18,6 @@ public class SignUpRequestDto public string? Password { get; set; } /// true - [NotMapped] [Range(typeof(bool), "true", "true", ErrorMessage = nameof(AppStrings.YouHaveToAcceptTerms))] [Display(Name = nameof(AppStrings.TermsAccepted))] public bool TermsAccepted { get; set; } diff --git a/src/Bit.TemplatePlayground.Shared/Resources/AppStrings.Designer.cs b/src/Bit.TemplatePlayground.Shared/Resources/AppStrings.Designer.cs index f65dd75..2e60a4d 100644 --- a/src/Bit.TemplatePlayground.Shared/Resources/AppStrings.Designer.cs +++ b/src/Bit.TemplatePlayground.Shared/Resources/AppStrings.Designer.cs @@ -60,6 +60,15 @@ internal AppStrings() { } } + /// + /// Looks up a localized string similar to About. + /// + public static string AboutTitle { + get { + return ResourceManager.GetString("AboutTitle", resourceCulture); + } + } + /// /// Looks up a localized string similar to Action. /// @@ -133,7 +142,7 @@ public static string AlreadyHaveAccountMessage { } /// - /// Looks up a localized string similar to Are you sure you want to delete {0}. + /// Looks up a localized string similar to Are you sure you want to delete {0}?. /// public static string AreYouSureWannaDelete { get { @@ -781,6 +790,15 @@ public static string No { } } + /// + /// Looks up a localized string similar to There is nothing here.. + /// + public static string NotFoundText { + get { + return ResourceManager.GetString("NotFoundText", resourceCulture); + } + } + /// /// Looks up a localized string similar to No todos yet. /// diff --git a/src/Bit.TemplatePlayground.Shared/Resources/AppStrings.fa.resx b/src/Bit.TemplatePlayground.Shared/Resources/AppStrings.fa.resx new file mode 100644 index 0000000..c930d7a --- /dev/null +++ b/src/Bit.TemplatePlayground.Shared/Resources/AppStrings.fa.resx @@ -0,0 +1,580 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + "{0}" و "{1}" مطابقت ندارند. + + + مقدار {0} یک آدرس ایمیل معتبر نیست. + + + MaxLengthAttribute must have a Length value that is greater than zero. Use MaxLength() without parameters to indicate that the string or array can have the maximum allowable length. + + + مقدار {0} باید یک نوع رشته یا لیستی با حداقل طول "{1}" باشد. + + + مقدار {0} باید بین {1} و {2} باشد. + + + مقدار {0} الزامی است. + + + درخواست نامعتبر + + + درخواست به دلیل مغایرت داشتن پردازش نشد + + + دسترسی به منابع درخواست شده ممنوع است + + + داده‌های درخواست معتبر نیستند + + + هنگام برقراری ارتباط با سرور خطایی رخ داد + + + درخواست شما فاقد اعتبارنامه معتبر احراز هویت است + + + خطای ناشناخته رخ داده است + + + رکورد توسط یک کاربر دیگر پس از دریافت داده های اصلی تغییر یافته است. عملیات لغو شد. + + + منبع یافت نشد + + + درخواست های بیش از حد + + + قادر به اتصال به سرور نیست. + + + فعال + + + همه + + + الفبایی + + + تمام شده + + + تاریخ + + + ویرایش پروفایل + + + ویرایش پروفایل آفلاین + + + نام کامل + + + شرایط + + + پروفایل با موفقیت به روز شد. + + + تایید رمز عبور جدید + + + ایمیل + + + رمز عبور + + + رمز عبور شما با موفقیت تغییر کرد. + + + لینک تایید دوباره به آدرس ایمیل شما ارسال شده است. + + + لینک ریست رمز عبور به آدرس ایمیل شما ارسال شده است. + + + تصویر کاربر پیدا نشد + + + خطا + + + شما قبلا درخواست تنظیم مجدد ایمیل رمز عبور را دارید. دوباره امتحان کنید در {0} + + + آیا شرایط را قبول دارید؟ + + + شما باید با شرایط ما موافقت کنید. + + + جنسیت + + + تاریخ تولد + + + نام + + + توضیحات + + + قيمت + + + برای ادامه باید وارد شوید. + + + نام کاربری یا رمز عبور نامعتبر است + + + شما قبلا ایمیل تایید را درخواست کرده اید. دوباره امتحان کنید در {0} + + + ایمیل شما قبلا تایید شده است. + + + عنوان + + + آیا مطمئن هستید که می خواهید {0} را حذف کنید؟ + + + خانه + + + کاربر قفل شده است. دوباره امتحان کنید در {0} + + + کاربر {0} وجود ندارد. + + + ایمیل '{0}' قبلا گرفته شده است. + + + اضافه کردن + + + در حال حاضر یک حساب کاربری دارید؟ + + + ما یک لینک تایید به آدرس ایمیل شما ارسال کرده ایم. +لطفا ایمیل خود را با کلیک بر روی لینک تایید کنید. + + + آدرس ایمیل خود را تایید کنید + + + حذف حساب + + + آیا مطمئن هستید که می خواهید حساب خود را حذف کنید؟ + + + حساب ندارید؟ + + + ویرایش + + + تایید ایمیل شکست خورد! + + + تایید ایمیل + + + ایمیل با موفقیت تایید شد! + + + هنگام بارگذاری فایل خطایی رخ داد + + + لطفا آدرس ایمیلی را که با آن ثبت نام کرده اید وارد کنید تا ما بتوانیم لینک ریست کردن رمز عبور را به آدرس ایمیل شما ارسال کنیم. + + + رمز عبور را فراموش کرده ام + + + مرد + + + غیره + + + ریپازیتوری گیتهاب + + + برو به امروز + + + برنامه blazor مالتی مد (WASM, Server, Hybrid, pre-rendering) خود را به آسانی و در سریع‌ترین زمان بسازید + + + صفحه اصلی Bit.TemplatePlayground + + + به نظر می رسد لینک تایید نامعتبر است یا منقضی شده است. + + + رمز عبور جدید + + + نه + + + ایمیل تایید را دریافت نکرده اید؟ + + + یا + + + عکس پروفایل + + + حذف + + + ارسال مجدد ایمیل + + + ریست رمز عبور + + + ذخیره + + + ورود به برنامه + + + خروج از سیستم + + + آیا مطمئن هستید که می خواهید خارج شوید؟ + + + ثبت نام + + + ثبت نام + + + ارسال + + + من موافقم با + + + بارگذاری یک عکس پروفایل جدید + + + بله + + + تاریخ تولد خود را انتخاب کنید + + + ریست رمز عبور + + + عملیات + + + بازگشت + + + لغو + + + اگر ایمیل را در Inbox نیافتید، فولدر اسپم / Junk خود را بررسی کنید + + + رنگ + + + رنگ سفارشی + + + حذف + + + شناسه + + + ورود به برنامه + + + ورود به سیستم به عنوان کاربر متفاوت + + + شما در حال ورود به سیستم به عنوان + + + رمز عبور را فراموش کرده اید؟ + + + زن + + + من رو به خاطر بسپار؟ + + + بروز رسانی + + + نسخه جدید در دسترس است + + + آیا می خواهید به نسخه جدید بروید؟ + + + درباره + + + افزودن یک کار + + + هنوز کاری ثبت نشده + + + جستجو در کارها... + + + کار پیدا نشد + + + کار + + + حذف کار + + + تعداد محصولات + + + تعداد محصولات ۳۰ روز گذشته + + + افزودن محصول + + + انتخاب دسته + + + تعداد دسته بندی ها + + + تعداد محصولات + + + ویرایش دسته + + + دسته جدید + + + دسته بندی ها + + + نام دسته را وارد کنید + + + نام محصول را وارد کنید + + + تعداد دسته های ۳۰ روز گذشته + + + فروش محصولات + + + این نمودار تعداد فروش هر محصول را نشان می دهد. + + + جستجو در نام + + + چارت تعداد محصولات در هر دسته + + + این نمودار تعداد محصولات در هر دسته را نشان می دهد. + + + محصولات + + + درصد محصولات هر دسته + + + این نمودار درصد محصولات هر دسته را نشان می دهد. + + + دسته ها + + + دسته ها و محصولات + + + محصولات + + + محصول پیدا نشد + + + این دسته دارای محصول است، بنابراین نمی توانید ان را حذف کنید + + + آیا مطمئن هستید که می خواهید دسته بندی {0} را حذف کنید؟ + + + آیا مطمئن هستید که می خواهید محصول {0} را حذف کنید؟ + + + حذف دسته + + + حذف محصول + + + دسته مورد نظر پیدا نشد + + + دسته + + + انتخاب کننده رنگ پیش فرض + + + ویرایش محصول + + + داشبورد + + + چیزی پیدا نشد. + + \ No newline at end of file diff --git a/src/Bit.TemplatePlayground.Shared/Resources/AppStrings.fr.resx b/src/Bit.TemplatePlayground.Shared/Resources/AppStrings.fr.resx index 618a1a6..10d624d 100644 --- a/src/Bit.TemplatePlayground.Shared/Resources/AppStrings.fr.resx +++ b/src/Bit.TemplatePlayground.Shared/Resources/AppStrings.fr.resx @@ -127,7 +127,7 @@ Le champ {0} n'est pas une adresse e-mail valide. - MaxLengthAttribute doit avoir une valeur de longueur supérieure à zéro. + MaxLengthAttribute must have a Length value that is greater than zero. Use MaxLength() without parameters to indicate that the string or array can have the maximum allowable length. Le champ {0} doit être de type chaîne ou tableau avec une longueur minimale de « {1} ». @@ -136,7 +136,7 @@ Le champ {0} doit être compris entre {1} et {2}. - L'e-mail « {0} » est déjà pris. + L'e-mail '{0}' est déjà pris. L'utilisateur est verrouillé. Réessayez dans {0} @@ -310,7 +310,7 @@ L'élément à faire est introuvable - Etes-vous sûr de vouloir supprimer {0} + Etes-vous sûr de vouloir supprimer {0}? Supprimer l'élément à faire @@ -570,4 +570,10 @@ Souhaitez-vous mettre à jour vers la nouvelle version? + + À propos + + + Il n'y a rien ici. + \ No newline at end of file diff --git a/src/Bit.TemplatePlayground.Shared/Resources/AppStrings.resx b/src/Bit.TemplatePlayground.Shared/Resources/AppStrings.resx index b442cc0..f440018 100644 --- a/src/Bit.TemplatePlayground.Shared/Resources/AppStrings.resx +++ b/src/Bit.TemplatePlayground.Shared/Resources/AppStrings.resx @@ -189,6 +189,9 @@ Edit profile + + + There is nothing here. FullName @@ -263,7 +266,7 @@ Title - Are you sure you want to delete {0} + Are you sure you want to delete {0}? Home @@ -451,6 +454,9 @@ Please confirm your email by clicking on the link. Would you like to update to the new version? + + + About Products count diff --git a/src/Bit.TemplatePlayground.Shared/Resources/IdentityStrings.fa.resx b/src/Bit.TemplatePlayground.Shared/Resources/IdentityStrings.fa.resx new file mode 100644 index 0000000..950a58c --- /dev/null +++ b/src/Bit.TemplatePlayground.Shared/Resources/IdentityStrings.fa.resx @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + منبع تغییر داده شده بوده، شکست همزمانی + + + یک خطای ناشناخته رخ داده است. + + + ایمیل '{0}' قبلا گرفته شده است. + + + نام نقش '{0}' قبلا گرفته شده است. + + + نام کاربری '{0}' قبلا گرفته شده است. + + + ایمیل '{0}' نامعتبر است. + + + نام نقش '{0}' نامعتبر است. + + + توکن نامعتبر. + + + نام کاربری '{0}' نامعتبر است، فقط می تواند حاوی حروف یا ارقام باشد. + + + در حال حاضر یک کاربر با این لاگین وجود دارد. + + + رمز عبور نادرست. + + + رمز عبور باید حداقل یک رقم ('۰'-'۹') داشته باشند. + + + رمز عبور باید حداقل یک حروف کوچک ('a'-'z') داشته باشند. + + + رمزهای عبور باید حداقل یک کاراکتر غیر عددی داشته باشند. + + + کلمه عبور باید حداقل یک بزرگ ('A'-'Z') داشته باشد. + + + رمزهای عبور باید حداقل {0} حرف باشند. + + + بازیابی کد شکست خورد. + + + کاربر در حال حاضر دارای یک مجموعه رمز عبور است. + + + کاربر در حال حاضر در نقش '{0}' است. + + + قفل کردن برای این کاربر فعال نیست. + + + کاربر در نقش '{0}' نیست. + + + رمزهای عبور باید حداقل از {0} حرف مختلف استفاده کنند. + + \ No newline at end of file diff --git a/src/Bit.TemplatePlayground.Shared/Services/CultureInfoManager.cs b/src/Bit.TemplatePlayground.Shared/Services/CultureInfoManager.cs index cbf6b4b..7c86b42 100644 --- a/src/Bit.TemplatePlayground.Shared/Services/CultureInfoManager.cs +++ b/src/Bit.TemplatePlayground.Shared/Services/CultureInfoManager.cs @@ -1,4 +1,7 @@ -namespace Bit.TemplatePlayground.Shared.Services; +using System.Reflection; + +namespace Bit.TemplatePlayground.Shared.Services; + public class CultureInfoManager { public static (string name, string code) DefaultCulture { get; } = ("English", "en-US"); @@ -8,7 +11,7 @@ public class CultureInfoManager ("English US", "en-US"), ("English UK", "en-GB"), ("Française", "fr-FR"), - // ("فارسی", "fa-IR"), // To add more languages, you've to provide resx files. You might also put some efforts to change your app flow direction based on CultureInfo.CurrentUICulture.TextInfo.IsRightToLeft + ("فارسی", "fa-IR"), // To add more languages, you've to provide resx files. You might also put some efforts to change your app flow direction based on CultureInfo.CurrentUICulture.TextInfo.IsRightToLeft ]; public static CultureInfo CreateCultureInfo(string cultureInfoId) @@ -47,9 +50,19 @@ public string GetCurrentCulture(string? preferredCulture = null) /// public static CultureInfo CustomizeCultureInfoForFaCulture(CultureInfo cultureInfo) { + cultureInfo.GetType().GetField("_calendar", BindingFlags.NonPublic | BindingFlags.Instance)!.SetValue(cultureInfo, new PersianCalendar()); + cultureInfo.DateTimeFormat.AMDesignator = "ق.ظ"; cultureInfo.DateTimeFormat.PMDesignator = "ب.ظ"; cultureInfo.DateTimeFormat.ShortDatePattern = "yyyy/MM/dd"; + cultureInfo.DateTimeFormat.AbbreviatedDayNames = + [ + "ی", "د", "س", "چ", "پ", "ج", "ش" + ]; + cultureInfo.DateTimeFormat.ShortestDayNames = + [ + "ی", "د", "س", "چ", "پ", "ج", "ش" + ]; return cultureInfo; } diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Bit.TemplatePlayground.Client.Core.csproj b/src/Client/Bit.TemplatePlayground.Client.Core/Bit.TemplatePlayground.Client.Core.csproj index d058c91..c0e5c54 100644 --- a/src/Client/Bit.TemplatePlayground.Client.Core/Bit.TemplatePlayground.Client.Core.csproj +++ b/src/Client/Bit.TemplatePlayground.Client.Core/Bit.TemplatePlayground.Client.Core.csproj @@ -16,22 +16,16 @@ - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + + + + + + + @@ -66,7 +60,7 @@ - + diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Components/AppComponentBase.cs b/src/Client/Bit.TemplatePlayground.Client.Core/Components/AppComponentBase.cs index 72e8653..4105cc5 100644 --- a/src/Client/Bit.TemplatePlayground.Client.Core/Components/AppComponentBase.cs +++ b/src/Client/Bit.TemplatePlayground.Client.Core/Components/AppComponentBase.cs @@ -2,7 +2,7 @@ namespace Bit.TemplatePlayground.Client.Core.Components; -public partial class AppComponentBase : ComponentBase, IDisposable +public partial class AppComponentBase : ComponentBase, IAsyncDisposable { [AutoInject] protected IJSRuntime JSRuntime = default!; @@ -183,9 +183,19 @@ public virtual Func WrapHandled(Func func) }; } - public virtual void Dispose() + public async ValueTask DisposeAsync() { - cts.Cancel(); - cts.Dispose(); + await DisposeAsync(true); + + GC.SuppressFinalize(this); + } + + protected virtual async ValueTask DisposeAsync(bool disposing) + { + if (disposing) + { + cts.Cancel(); + cts.Dispose(); + } } } diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Components/AppDataAnnotationsValidator.cs b/src/Client/Bit.TemplatePlayground.Client.Core/Components/AppDataAnnotationsValidator.cs index add603c..792f3df 100644 --- a/src/Client/Bit.TemplatePlayground.Client.Core/Components/AppDataAnnotationsValidator.cs +++ b/src/Client/Bit.TemplatePlayground.Client.Core/Components/AppDataAnnotationsValidator.cs @@ -11,7 +11,7 @@ namespace Bit.TemplatePlayground.Client.Core.Components; /// on the corresponding class instead of using `ErrorResourceType` on each property. Check out for an example. /// However, you need to use instead of in Blazor EditForms for this method to work. /// -public partial class AppDataAnnotationsValidator : AppComponentBase, IDisposable +public partial class AppDataAnnotationsValidator : AppComponentBase { private static readonly PropertyInfo otherPropertyNamePropertyInfo = typeof(CompareAttribute).GetProperty(nameof(CompareAttribute.OtherPropertyDisplayName))!; @@ -43,7 +43,7 @@ private void OnFieldChanged(object? sender, FieldChangedEventArgs eventArgs) if (propertyInfo is null) return; var propertyValue = propertyInfo.GetValue(fieldIdentifier.Model); - var validationContext = new ValidationContext(fieldIdentifier.Model, serviceProvider, items: null) + var propValidationContext = new ValidationContext(fieldIdentifier.Model, serviceProvider, items: null) { MemberName = propertyInfo.Name }; @@ -56,6 +56,11 @@ private void OnFieldChanged(object? sender, FieldChangedEventArgs eventArgs) var resourceType = dtoResourceTypeAttr.ResourceType; var stringLocalizer = stringLocalizerFactory.Create(resourceType); var validationAttributes = propertyInfo.GetCustomAttributes(); + var displayAttribute = propertyInfo.GetCustomAttribute(); + if (displayAttribute is not null) + { + displayAttribute.ResourceType ??= resourceType; + } foreach (var attribute in validationAttributes) { @@ -68,28 +73,30 @@ private void OnFieldChanged(object? sender, FieldChangedEventArgs eventArgs) if (string.IsNullOrWhiteSpace(attribute.ErrorMessageResourceName) is false && attribute.ErrorMessageResourceType is null) { attribute.ErrorMessageResourceType = resourceType; - var displayAttribute = propertyInfo.GetCustomAttribute(); - validationContext.DisplayName = stringLocalizer.GetString(displayAttribute?.Name ?? propertyInfo.Name); + propValidationContext.DisplayName = stringLocalizer.GetString(displayAttribute?.Name ?? propertyInfo.Name); if (attribute is CompareAttribute compareAttribute) { var otherPropertyInfoDisplayAttribute = (parent.GetProperty(compareAttribute.OtherProperty) ?? throw new InvalidOperationException($"Invalid OtherProperty {compareAttribute.OtherProperty}")).GetCustomAttribute(); + if (otherPropertyInfoDisplayAttribute is not null) + { + otherPropertyInfoDisplayAttribute.ResourceType ??= resourceType; + } otherPropertyNamePropertyInfo.SetValue(attribute, stringLocalizer.GetString(otherPropertyInfoDisplayAttribute?.Name ?? compareAttribute.OtherProperty).ToString()); } } - var result = attribute.GetValidationResult(propertyValue, validationContext); + var result = attribute.GetValidationResult(propertyValue, propValidationContext); if (result is not null) { results.Add(result); } } - } else { - Validator.TryValidateProperty(propertyValue, validationContext, results); + Validator.TryValidateProperty(propertyValue, propValidationContext, results); } validationMessageStore.Clear(fieldIdentifier); @@ -103,11 +110,11 @@ private void OnFieldChanged(object? sender, FieldChangedEventArgs eventArgs) private void OnValidationRequested(object? sender, ValidationRequestedEventArgs e) { - var validationContext = new ValidationContext(EditContext.Model, serviceProvider, items: null); + var modelValidationContext = new ValidationContext(EditContext.Model, serviceProvider, items: null); var results = new List(); - var objectType = validationContext.ObjectType; - var objectInstance = validationContext.ObjectInstance; + var objectType = modelValidationContext.ObjectType; + var objectInstance = modelValidationContext.ObjectInstance; var dtoResourceTypeAttr = objectType.GetCustomAttribute(); validationMessageStore.Clear(); @@ -121,10 +128,17 @@ private void OnValidationRequested(object? sender, ValidationRequestedEventArgs foreach (var propertyInfo in properties) { - var context = new ValidationContext(objectInstance, validationContext, validationContext.Items); - context.MemberName = propertyInfo.Name; + var propValidationContext = new ValidationContext(objectInstance, modelValidationContext, modelValidationContext.Items) + { + MemberName = propertyInfo.Name + }; var propertyValue = propertyInfo.GetValue(objectInstance); var validationAttributes = propertyInfo.GetCustomAttributes(); + var displayAttribute = propertyInfo.GetCustomAttribute(); + if (displayAttribute is not null) + { + displayAttribute.ResourceType ??= resourceType; + } foreach (var attribute in validationAttributes) { if (string.IsNullOrEmpty(attribute.ErrorMessage) is false) @@ -136,16 +150,19 @@ private void OnValidationRequested(object? sender, ValidationRequestedEventArgs if (string.IsNullOrWhiteSpace(attribute.ErrorMessageResourceName) is false && attribute.ErrorMessageResourceType is null) { attribute.ErrorMessageResourceType = resourceType; - var displayAttribute = propertyInfo.GetCustomAttribute(); - validationContext.DisplayName = stringLocalizer.GetString(displayAttribute?.Name ?? propertyInfo.Name); + propValidationContext.DisplayName = stringLocalizer.GetString(displayAttribute?.Name ?? propertyInfo.Name); if (attribute is CompareAttribute compareAttribute) { var otherPropertyInfoDisplayAttribute = (properties.FirstOrDefault(p => p.Name == compareAttribute.OtherProperty) ?? throw new InvalidOperationException($"Invalid OtherProperty {compareAttribute.OtherProperty}")).GetCustomAttribute(); + if (otherPropertyInfoDisplayAttribute is not null) + { + otherPropertyInfoDisplayAttribute.ResourceType ??= resourceType; + } otherPropertyNamePropertyInfo.SetValue(attribute, stringLocalizer.GetString(otherPropertyInfoDisplayAttribute?.Name ?? compareAttribute.OtherProperty).ToString()); } } - var result = attribute.GetValidationResult(propertyValue, context); + var result = attribute.GetValidationResult(propertyValue, propValidationContext); if (result is not null) { @@ -156,7 +173,7 @@ private void OnValidationRequested(object? sender, ValidationRequestedEventArgs } else { - Validator.TryValidateObject(EditContext.Model, validationContext, results, true); + Validator.TryValidateObject(EditContext.Model, modelValidationContext, results, true); } validationMessageStore.Clear(); @@ -179,14 +196,10 @@ private void OnValidationRequested(object? sender, ValidationRequestedEventArgs EditContext.NotifyValidationStateChanged(); } - public override void Dispose() + protected override async ValueTask DisposeAsync(bool disposing) { - Dispose(true); - GC.SuppressFinalize(this); - } + await base.DisposeAsync(disposing); - protected virtual void Dispose(bool disposing) - { if (disposed || disposing is false) return; if (EditContext is not null) diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/AppErrorBoundary.razor.scss b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/AppErrorBoundary.razor.scss index 9439cb4..8a7de3d 100644 --- a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/AppErrorBoundary.razor.scss +++ b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/AppErrorBoundary.razor.scss @@ -14,7 +14,7 @@ } .header { - text-align: right; + text-align: end; } .title { @@ -23,8 +23,8 @@ .exception { overflow: auto; - text-align: left; white-space: pre; + text-align: start; margin: rem2(24px); } diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/ConfirmMessageBox.razor b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/ConfirmMessageBox.razor index 578813b..3f7495c 100644 --- a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/ConfirmMessageBox.razor +++ b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/ConfirmMessageBox.razor @@ -11,7 +11,7 @@
@Localizer[nameof(AppStrings.Yes)] diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/Footer.razor.cs b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/Footer.razor.cs index 4c85e7d..b80a8ae 100644 --- a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/Footer.razor.cs +++ b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/Footer.razor.cs @@ -3,10 +3,10 @@ public partial class Footer { [AutoInject] private Cookie cookie = default!; + [AutoInject] private IPubSubService pubSubService = default!; [AutoInject] private BitThemeManager bitThemeManager = default!; - [AutoInject] private IBitDeviceCoordinator bitDeviceCoordinator = default!; [AutoInject] private CultureInfoManager cultureInfoManager = default!; - [AutoInject] private IPubSubService pubSubService = default!; + [AutoInject] private IBitDeviceCoordinator bitDeviceCoordinator = default!; private BitDropdownItem[] cultures = default!; diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/Footer.razor.scss b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/Footer.razor.scss index 66ea92a..2c5e67e 100644 --- a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/Footer.razor.scss +++ b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/Footer.razor.scss @@ -1,13 +1,17 @@ -@import '../../Styles/abstracts/_functions.scss'; +@import '../../Styles/abstracts/_vars.scss'; +@import '../../Styles/abstracts/_functions.scss'; @import '../../Styles/abstracts/_media-queries.scss'; @import '../../Styles/abstracts/_bit-css-variables.scss'; .footer { width: 100%; display: flex; + padding-top: 1rem; align-items: center; justify-content: center; flex-flow: column nowrap; + min-height: rem2($footerHeight); + background-color: $bit-color-background-primary; .bit-ios & { padding-bottom: env(safe-area-inset-bottom); @@ -19,8 +23,8 @@ gap: rem2(4px); display: flex; position: relative; - padding: rem2(4px) 0; align-items: center; + padding: rem2(4px) 0; min-height: rem2(54px); justify-content: center; flex-flow: column nowrap; @@ -74,21 +78,21 @@ ::deep { .culture-drp { - left: rem2(8px); width: rem2(104px); position: absolute; + inset-inline-start: rem2(8px); } } .toggle-theme-btn { - right: rem2(12px); - position: absolute; padding: 0; border: none; cursor: pointer; height: rem2(35px); - min-width: rem2(35px); + position: absolute; border-radius: 50%; + min-width: rem2(35px); + inset-inline-end: rem2(12px); color: $bit-color-primary-text; background-color: $bit-color-primary-main; @@ -106,7 +110,7 @@ &.dark-theme { .icon-container { - padding: 2px 0px 0 1px; + padding: 2px 0 0 1px; } } } diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/Header.razor.cs b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/Header.razor.cs index 0f0d913..dc7169e 100644 --- a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/Header.razor.cs +++ b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/Header.razor.cs @@ -1,6 +1,6 @@ namespace Bit.TemplatePlayground.Client.Core.Components.Layout; -public partial class Header : IDisposable +public partial class Header { private bool disposed; private bool isUserAuthenticated; @@ -37,15 +37,11 @@ private async Task ToggleMenu() await OnToggleMenu.InvokeAsync(); } - public override void Dispose() + protected override async ValueTask DisposeAsync(bool disposing) { - Dispose(true); - GC.SuppressFinalize(this); - } + await base.DisposeAsync(disposing); - protected virtual void Dispose(bool disposing) - { - if (disposed) return; + if (disposed || disposing is false) return; AuthenticationManager.AuthenticationStateChanged -= VerifyUserIsAuthenticatedOrNot; diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/Header.razor.scss b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/Header.razor.scss index 3024483..a6b83bd 100644 --- a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/Header.razor.scss +++ b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/Header.razor.scss @@ -1,4 +1,5 @@ -@import '../../Styles/abstracts/_functions.scss'; +@import '../../Styles/abstracts/_vars.scss'; +@import '../../Styles/abstracts/_functions.scss'; @import '../../Styles/abstracts/_media-queries.scss'; @import '../../Styles/abstracts/_bit-css-variables.scss'; @@ -10,11 +11,10 @@ position: fixed; align-items: center; flex-flow: row nowrap; - min-height: rem2(48px); - padding-left: rem2(16px); - padding-right: rem2(16px); padding-bottom: rem2(7px); + padding-inline: rem2(16px); justify-content: space-between; + min-height: rem2($headerHeight); box-shadow: $bit-box-shadow-callout; background-color: $bit-color-background-primary; padding-top: calc(rem2(7px) + var(--bit-status-bar-height)); @@ -48,7 +48,7 @@ ::deep { .header-menu { display: none; - margin-right: rem2(12px); + margin-inline-end: rem2(12px); @include lt-lg { display: block; diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/MainLayout.razor b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/MainLayout.razor index b1d9c99..00f792b 100644 --- a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/MainLayout.razor +++ b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/MainLayout.razor @@ -2,20 +2,27 @@
Bit.TemplatePlayground
-
+ +
+
-@if (isUserAuthenticated) -{ - -} +
+ @if (isUserAuthenticated) + { + + } -
-
- - @Body - -
-
-
+
+
+ + @Body + +
+
+
+ +
- + +
+
\ No newline at end of file diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/MainLayout.razor.cs b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/MainLayout.razor.cs index 3589619..a2c6a17 100644 --- a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/MainLayout.razor.cs +++ b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/MainLayout.razor.cs @@ -6,34 +6,35 @@ public partial class MainLayout : IDisposable { private bool disposed; private bool isMenuOpen; + private BitDir? currentDir; private bool isUserAuthenticated; private ErrorBoundary errorBoundaryRef = default!; + private Action unsubscribeCultureChange = default!; - [AutoInject] private IPrerenderStateService prerenderStateService = default!; - - [AutoInject] private IExceptionHandler exceptionHandler = default!; - + [AutoInject] private IPubSubService pubSubService = default!; [AutoInject] private AuthenticationManager authManager = default!; + [AutoInject] private IExceptionHandler exceptionHandler = default!; + [AutoInject] private IPrerenderStateService prerenderStateService = default!; [CascadingParameter] public Task AuthenticationStateTask { get; set; } = default!; - protected override void OnParametersSet() - { - // TODO: we can try to recover from exception after rendering the ErrorBoundary with this line. - // but for now it's better to persist the error ui until a force refresh. - // ErrorBoundaryRef.Recover(); - - base.OnParametersSet(); - } - protected override async Task OnInitializedAsync() { try { - authManager.AuthenticationStateChanged += VerifyUserIsAuthenticatedOrNot; + authManager.AuthenticationStateChanged += IsUserAuthenticated; isUserAuthenticated = await prerenderStateService.GetValue(async () => (await AuthenticationStateTask).User.IsAuthenticated()); + unsubscribeCultureChange = pubSubService.Subscribe(PubSubMessages.CULTURE_CHANGED, async _ => + { + SetCurrentDir(); + + StateHasChanged(); + }); + + SetCurrentDir(); + await base.OnInitializedAsync(); } catch (Exception exp) @@ -42,7 +43,28 @@ protected override async Task OnInitializedAsync() } } - async void VerifyUserIsAuthenticatedOrNot(Task task) + protected override void OnParametersSet() + { + // TODO: we can try to recover from exception after rendering the ErrorBoundary with this line. + // but for now it's better to persist the error ui until a force refresh. + // ErrorBoundaryRef.Recover(); + + base.OnParametersSet(); + } + + private void SetCurrentDir() + { + var currentCulture = CultureInfo.CurrentCulture; + + currentDir = currentCulture.TextInfo.IsRightToLeft ? BitDir.Rtl : null; + } + + private void ToggleMenuHandler() + { + isMenuOpen = !isMenuOpen; + } + + private async void IsUserAuthenticated(Task task) { try { @@ -58,22 +80,20 @@ async void VerifyUserIsAuthenticatedOrNot(Task task) } } - private void ToggleMenuHandler() - { - isMenuOpen = !isMenuOpen; - } - public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { - if (disposed) return; + if (disposed || disposing is false) return; + + authManager.AuthenticationStateChanged -= IsUserAuthenticated; - authManager.AuthenticationStateChanged -= VerifyUserIsAuthenticatedOrNot; + unsubscribeCultureChange(); disposed = true; } diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/MainLayout.razor.scss b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/MainLayout.razor.scss index 1855711..070233b 100644 --- a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/MainLayout.razor.scss +++ b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/MainLayout.razor.scss @@ -3,36 +3,43 @@ @import '../../Styles/abstracts/_media-queries.scss'; @import '../../Styles/abstracts/_bit-css-variables.scss'; -.main { +main { width: 100%; display: flex; min-height: 100vh; - flex-flow: column; position: relative; - background-color: $bit-color-background-secondary; - padding-top: calc(rem2(48px) + var(--bit-status-bar-height)); + box-sizing: border-box; + justify-content: flex-start; + padding-top: calc(rem2($headerHeight) + var(--bit-status-bar-height)); .bit-ios & { - padding-top: calc(rem2(48px) + env(safe-area-inset-top)); + padding-top: calc(rem2($headerHeight) + env(safe-area-inset-top)); } } -.main--authenticated { +.site-content { + width: 100%; display: flex; + min-height: 100vh; flex-flow: column; - margin-left: rem2($navMenuWidth); - width: calc(100% - rem2($navMenuWidth)); + position: relative; + background-color: $bit-color-background-secondary; - @include lt-lg { - width: 100%; - margin-left: 0; + &.authenticated { + width: calc(100% - rem2($navMenuWidth)); + + @include lt-lg { + width: 100%; + } } } + + .main-content { flex-grow: 1; display: flex; - padding: 1rem; + padding: 2rem; flex-flow: column; align-items: center; justify-content: center; @@ -87,7 +94,7 @@ .form-input-error, .validation-message { - text-align: left; + text-align: start; font-size: rem2(12px); line-height: rem2(16px); color: $bit-color-state-error; @@ -99,9 +106,9 @@ } .form-message-bar { - top: 0; - left: 0; position: absolute; + inset-block-start: 0; + inset-inline-start: 0; border-radius: rem2(4px) rem2(4px) 0 0; } } diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/MessageBox.razor.cs b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/MessageBox.razor.cs index dc074e6..5910dbb 100644 --- a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/MessageBox.razor.cs +++ b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/MessageBox.razor.cs @@ -1,33 +1,18 @@ namespace Bit.TemplatePlayground.Client.Core.Components.Layout; -public partial class MessageBox : IDisposable +public partial class MessageBox { private bool isOpen; - private string? title; private string? body; + private string? title; + private Action? unsubscribe; + private bool disposed = false; private TaskCompletionSource? tcs; - private async Task OnCloseClick() - { - isOpen = false; - tcs?.SetResult(null); - tcs = null; - } - - private async Task OnOkClick() - { - isOpen = false; - tcs?.SetResult(null); - tcs = null; - } - - Action? dispose; - bool disposed = false; - protected override Task OnInitAsync() { - dispose = PubSubService.Subscribe(PubSubMessages.SHOW_MESSAGE, async args => + unsubscribe = PubSubService.Subscribe(PubSubMessages.SHOW_MESSAGE, async args => { (var message, string title, TaskCompletionSource tcs) = ((string message, string title, TaskCompletionSource tcs))args!; await (this.tcs?.Task ?? Task.CompletedTask); @@ -50,19 +35,29 @@ await InvokeAsync(() => }); } - public override void Dispose() + private async Task OnCloseClick() + { + isOpen = false; + tcs?.SetResult(null); + tcs = null; + } + + private async Task OnOkClick() { - Dispose(true); - GC.SuppressFinalize(this); + isOpen = false; + tcs?.SetResult(null); + tcs = null; } - protected virtual void Dispose(bool disposing) + protected override async ValueTask DisposeAsync(bool disposing) { + await base.DisposeAsync(true); + if (disposed || disposing is false) return; tcs?.TrySetResult(null); tcs = null; - dispose?.Invoke(); + unsubscribe?.Invoke(); disposed = true; } diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/NavMenu.razor.cs b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/NavMenu.razor.cs index 8c4ea99..36f9a52 100644 --- a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/NavMenu.razor.cs +++ b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/NavMenu.razor.cs @@ -1,9 +1,8 @@ -using Bit.TemplatePlayground.Client.Core.Controllers.Identity; -using Bit.TemplatePlayground.Shared.Dtos.Identity; +using Bit.TemplatePlayground.Shared.Dtos.Identity; namespace Bit.TemplatePlayground.Client.Core.Components.Layout; -public partial class NavMenu : IDisposable +public partial class NavMenu { private bool disposed; private bool isSignOutModalOpen; @@ -23,13 +22,13 @@ protected override async Task OnInitAsync() { navItems = [ - new BitNavItem + new() { Text = Localizer[nameof(AppStrings.Home)], IconName = BitIconName.Home, Url = "/", }, - new BitNavItem + new() { Text = Localizer[nameof(AppStrings.ProductCategory)], IconName = BitIconName.Product, @@ -50,13 +49,13 @@ protected override async Task OnInitAsync() }, ] }, - new BitNavItem + new() { Text = Localizer[nameof(AppStrings.EditProfileTitle)], IconName = BitIconName.EditContact, Url = "/edit-profile", }, - new BitNavItem + new() { Text = Localizer[nameof(AppStrings.TermsTitle)], IconName = BitIconName.EntityExtraction, @@ -64,6 +63,20 @@ protected override async Task OnInitAsync() } ]; + if (AppRenderMode.IsBlazorHybrid) + { + // Presently, the About page is absent from the Client/Core project, rendering it inaccessible on the web platform. + // In order to exhibit a sample page that grants direct access to native functionalities without dependence on dependency injection (DI) or publish-subscribe patterns, + // about page is integrated within Blazor hybrid projects like Client/Maui. + + navItems.Add(new() + { + Text = Localizer[nameof(AppStrings.AboutTitle)], + IconName = BitIconName.HelpMirrored, + Url = "/about", + }); + } + unsubscribe = PubSubService.Subscribe(PubSubMessages.PROFILE_UPDATED, async payload => { if (payload is null) return; @@ -114,14 +127,10 @@ private async Task CloseMenu() await IsMenuOpenChanged.InvokeAsync(false); } - public override void Dispose() + protected override async ValueTask DisposeAsync(bool disposing) { - Dispose(true); - GC.SuppressFinalize(this); - } + await base.DisposeAsync(disposing); - protected virtual void Dispose(bool disposing) - { if (disposed || disposing is false) return; unsubscribe?.Invoke(); diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/NavMenu.razor.scss b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/NavMenu.razor.scss index dc7eac7..9cf5437 100644 --- a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/NavMenu.razor.scss +++ b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/NavMenu.razor.scss @@ -4,30 +4,36 @@ @import '../../Styles/abstracts/_bit-css-variables.scss'; .main-container { - left: 0; z-index: 16; height: 100%; display: flex; - position: fixed; + position: sticky; min-height: 100%; max-height: 100vh; overflow: hidden auto; flex-flow: column nowrap; - width: rem2($navMenuWidth); justify-content: flex-start; + min-width: rem2($navMenuWidth); + padding-bottom: rem2($headerHeight); background-color: $bit-color-background-primary; - padding-top: calc(rem2(48px) + var(--bit-status-bar-height)); + top: calc(rem2($headerHeight) + var(--bit-status-bar-height)); .bit-ios & { - padding-top: calc(rem2(48px) + env(safe-area-inset-top)); + top: calc(rem2($headerHeight) + env(safe-area-inset-top)); } -} -.main-container--closed { - display: none; + &::-webkit-scrollbar { + width: 0; + } +} - @include gt-md { - display: flex; +@include lt-lg { + .main-container { + position: fixed; + } + + .main-container--closed { + display: none; } } @@ -42,17 +48,16 @@ } .menu-overlay { - left: 0; width: 100%; z-index: 15; - display: none; - top: rem2(48px); position: fixed; - height: calc(100vh - #{rem2(48px)}); + inset-inline-start: 0; + top: rem2($headerHeight); background-color: rgba(0, 0, 0, 0.5); + height: calc(100vh - #{rem2($headerHeight)}); - @include lt-lg { - display: block; + @include gt-md { + display: none; } } diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/SignOutConfirmModal.razor.scss b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/SignOutConfirmModal.razor.scss index 0a9eddf..36d6798 100644 --- a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/SignOutConfirmModal.razor.scss +++ b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Layout/SignOutConfirmModal.razor.scss @@ -15,9 +15,9 @@ } .modal-close-btn-container { - top: 0; - right: 0; position: absolute; + inset-inline-end: 0; + inset-block-start: 0; } .modal-title { diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Categories/AddOrEditCategoryPage.razor b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Categories/AddOrEditCategoryPage.razor index f37216e..1018ef0 100644 --- a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Categories/AddOrEditCategoryPage.razor +++ b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Categories/AddOrEditCategoryPage.razor @@ -55,7 +55,7 @@
@Localizer[nameof(AppStrings.Color)]
-
+
@foreach (var color in new[] { "#FFCD56", "#FF6384", "#4BC0C0", "#FF9124", "#2B88D8", "#C7E0F4" }) {
@@ -68,10 +68,11 @@
- + Label="@Localizer[nameof(AppStrings.BirthDate)]" + GoToTodayTitle="@Localizer[nameof(AppStrings.GoToToday)]" + Placeholder="@Localizer[nameof(AppStrings.SelectBirthDate)]" />
diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Identity/EditProfilePage.razor.scss b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Identity/EditProfilePage.razor.scss index b682076..14f8e44 100644 --- a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Identity/EditProfilePage.razor.scss +++ b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Identity/EditProfilePage.razor.scss @@ -41,9 +41,9 @@ } .form-message-bar { - top: 0; - left: 0; position: absolute; + inset-block-start: 0; + inset-inline-start: 0; border-radius: rem2(4px) rem2(4px) 0 0; } diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Identity/EmailConfirmationPage.razor.cs b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Identity/EmailConfirmationPage.razor.cs index 0d8f470..418749d 100644 --- a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Identity/EmailConfirmationPage.razor.cs +++ b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Identity/EmailConfirmationPage.razor.cs @@ -36,7 +36,7 @@ protected override async Task OnAfterFirstRenderAsync() private void RedirectToSignIn() { - NavigationManager.NavigateTo($"/sign-in?email={Email}"); + NavigationManager.NavigateTo($"/sign-in?email={Uri.EscapeDataString(Email ?? string.Empty)}"); } private async Task DoResendLink() diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Identity/ResetPasswordPage.razor.cs b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Identity/ResetPasswordPage.razor.cs index ed6f43b..032b484 100644 --- a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Identity/ResetPasswordPage.razor.cs +++ b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Identity/ResetPasswordPage.razor.cs @@ -27,7 +27,7 @@ protected override async Task OnInitAsync() private void RedirectToSignIn() { - NavigationManager.NavigateTo($"/sign-in?email={Email}"); + NavigationManager.NavigateTo($"/sign-in?email={Uri.EscapeDataString(Email ?? string.Empty)}"); } private async Task DoSubmit() diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/NotFoundPage.razor b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/NotFoundPage.razor index ff2a8b0..6d76493 100644 --- a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/NotFoundPage.razor +++ b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/NotFoundPage.razor @@ -4,5 +4,5 @@

404

-
There is nothing here.
+
@Localizer[nameof(AppStrings.NotFoundText)]
\ No newline at end of file diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Products/AddOrEditProductModal.razor b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Products/AddOrEditProductModal.razor index 6425d35..714e4d3 100644 --- a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Products/AddOrEditProductModal.razor +++ b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Products/AddOrEditProductModal.razor @@ -37,11 +37,10 @@
- +
diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Products/AddOrEditProductModal.razor.scss b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Products/AddOrEditProductModal.razor.scss index 1d6e450..791d03a 100644 --- a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Products/AddOrEditProductModal.razor.scss +++ b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Products/AddOrEditProductModal.razor.scss @@ -18,10 +18,10 @@ } @include sm { - left: 0; - bottom: 0; width: 100%; position: fixed; + inset-block-end: 0; + inset-inline-start: 0; border-radius: rem2(8px) rem2(8px) 0 0; } } diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Products/ProductsPage.razor.scss b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Products/ProductsPage.razor.scss index ee65eb6..510cddc 100644 --- a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Products/ProductsPage.razor.scss +++ b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Products/ProductsPage.razor.scss @@ -95,7 +95,7 @@ ::deep tr td:first-child { font-size: rem2(14px); line-height: rem2(20px); - padding-left: rem2(12px); + padding-inline-start: rem2(12px); } ::deep .col-options-button { diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/TermsPage.razor.scss b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/TermsPage.razor.scss index c5eb271..9dc1d60 100644 --- a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/TermsPage.razor.scss +++ b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/TermsPage.razor.scss @@ -4,6 +4,7 @@ .page-container { width: 100%; display: flex; + direction: ltr; align-items: center; align-self: flex-start; flex-flow: column nowrap; @@ -34,7 +35,7 @@ .main-section-title { width: 100%; - text-align: left; + text-align: start; font-weight: bold; font-size: rem2(34px); line-height: rem2(56px); @@ -85,7 +86,7 @@ .sub-section-title { width: 100%; - text-align: left; + text-align: start; font-weight: 600; font-size: rem2(24px); line-height: rem2(36px); diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Extensions/IConfigurationBuilderExtensions.cs b/src/Client/Bit.TemplatePlayground.Client.Core/Extensions/IConfigurationBuilderExtensions.cs index a429503..d466a29 100644 --- a/src/Client/Bit.TemplatePlayground.Client.Core/Extensions/IConfigurationBuilderExtensions.cs +++ b/src/Client/Bit.TemplatePlayground.Client.Core/Extensions/IConfigurationBuilderExtensions.cs @@ -11,7 +11,19 @@ public static void AddClientConfigurations(this IConfigurationBuilder builder) if (BuildConfiguration.IsDebug()) { - builder.AddJsonStream(assembly.GetManifestResourceStream("Bit.TemplatePlayground.Client.Core.appsettings.Development.json")!); + var settings = assembly.GetManifestResourceStream("Bit.TemplatePlayground.Client.Core.appsettings.Development.json"); + if (settings is not null) + { + builder.AddJsonStream(settings); + } + } + else + { + var settings = assembly.GetManifestResourceStream("Bit.TemplatePlayground.Client.Core.appsettings.Production.json"); + if (settings is not null) + { + builder.AddJsonStream(settings); + } } } } diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Extensions/IServiceCollectionExtensions.cs b/src/Client/Bit.TemplatePlayground.Client.Core/Extensions/IServiceCollectionExtensions.cs index 35e4a33..d92f3fe 100644 --- a/src/Client/Bit.TemplatePlayground.Client.Core/Extensions/IServiceCollectionExtensions.cs +++ b/src/Client/Bit.TemplatePlayground.Client.Core/Extensions/IServiceCollectionExtensions.cs @@ -15,11 +15,11 @@ public static IServiceCollection AddClientCoreProjectServices(this IServiceColle services.TryAddTransient(); services.TryAddTransient(); - services.TryAddKeyedTransient("DefaultMessageHandler"); + services.TryAddKeyedTransient("DefaultMessageHandler"); services.TryAddTransient(); services.TryAddTransient(); services.TryAddTransient(); - services.TryAddTransient(); + services.TryAddScoped(); services.AddScoped(); // Use 'Add' instead of 'TryAdd' to override the aspnetcore's default AuthenticationStateProvider. services.TryAddScoped(sp => (AuthenticationManager)sp.GetRequiredService()); diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Services/ExceptionHandlerBase.cs b/src/Client/Bit.TemplatePlayground.Client.Core/Services/ExceptionHandlerBase.cs index 1610316..7d3d741 100644 --- a/src/Client/Bit.TemplatePlayground.Client.Core/Services/ExceptionHandlerBase.cs +++ b/src/Client/Bit.TemplatePlayground.Client.Core/Services/ExceptionHandlerBase.cs @@ -1,4 +1,6 @@ using System.Diagnostics; +using System.Text; +using Microsoft.Extensions.Logging; namespace Bit.TemplatePlayground.Client.Core.Services; @@ -7,8 +9,19 @@ public abstract partial class ExceptionHandlerBase : IExceptionHandler [AutoInject] protected readonly IStringLocalizer Localizer = default!; [AutoInject] protected readonly MessageBoxService MessageBoxService = default!; [AutoInject] protected Bit.Butil.Console Console = default!; + [AutoInject] protected ILogger Logger = default!; - public virtual void Handle(Exception exception, IDictionary? parameters = null) + public void Handle(Exception exp, IDictionary? parameters = null) + { + if (exp is TaskCanceledException) + return; + + parameters ??= new Dictionary(); + + Handle(exp, parameters.ToDictionary(i => i.Key, i => i.Value ?? string.Empty)); + } + + protected virtual void Handle(Exception exception, Dictionary parameters) { var isDebug = BuildConfiguration.IsDebug(); @@ -17,17 +30,24 @@ public virtual void Handle(Exception exception, IDictionary? pa if (isDebug) { - if (OperatingSystem.IsBrowser() || AppRenderMode.IsBlazorHybrid) + if (AppRenderMode.IsBlazorHybrid) { - _ = Console.Error(exceptionMessage); - } - else - { - _ = System.Console.Out.WriteLineAsync(exceptionMessage); + StringBuilder errorInfo = new(); + errorInfo.AppendLine(exceptionMessage); + foreach (var item in parameters) + { + errorInfo.AppendLine($"{item.Key}: {item.Value}"); + } + _ = Console.Error(errorInfo.ToString()); } Debugger.Break(); } + using (var scope = Logger.BeginScope(parameters.ToDictionary(i => i.Key, i => i.Value ?? string.Empty))) + { + Logger.LogError(exception, exceptionMessage); + } + _ = MessageBoxService.Show(exceptionMessage, Localizer[nameof(AppStrings.Error)]); } } diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Styles/abstracts/_vars.scss b/src/Client/Bit.TemplatePlayground.Client.Core/Styles/abstracts/_vars.scss index e7c04cc..7bdcede 100644 --- a/src/Client/Bit.TemplatePlayground.Client.Core/Styles/abstracts/_vars.scss +++ b/src/Client/Bit.TemplatePlayground.Client.Core/Styles/abstracts/_vars.scss @@ -1 +1,3 @@ -$navMenuWidth: 240px; +$navMenuWidth: 256px; +$headerHeight: 48px; +$footerHeight: 86px; \ No newline at end of file diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Styles/app.scss b/src/Client/Bit.TemplatePlayground.Client.Core/Styles/app.scss index ad16ef7..097e207 100644 --- a/src/Client/Bit.TemplatePlayground.Client.Core/Styles/app.scss +++ b/src/Client/Bit.TemplatePlayground.Client.Core/Styles/app.scss @@ -32,7 +32,19 @@ p { user-select: none; } - &.bit-windows * { + &.bit-windows *, a { -webkit-user-drag: none; } } + +div[dir=rtl] { + .bitdatagrid-paginator { + .go-next, .go-last { + transform: scaleX(1); + } + + .go-previous, .go-first { + transform: scaleX(-1); + } + } +} diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/appsettings.Production.json b/src/Client/Bit.TemplatePlayground.Client.Core/appsettings.Production.json new file mode 100644 index 0000000..544b7b4 --- /dev/null +++ b/src/Client/Bit.TemplatePlayground.Client.Core/appsettings.Production.json @@ -0,0 +1,3 @@ +{ + +} \ No newline at end of file diff --git a/src/Client/Bit.TemplatePlayground.Client.Maui/Bit.TemplatePlayground.Client.Maui.csproj b/src/Client/Bit.TemplatePlayground.Client.Maui/Bit.TemplatePlayground.Client.Maui.csproj index 08edafb..d836c61 100644 --- a/src/Client/Bit.TemplatePlayground.Client.Maui/Bit.TemplatePlayground.Client.Maui.csproj +++ b/src/Client/Bit.TemplatePlayground.Client.Maui/Bit.TemplatePlayground.Client.Maui.csproj @@ -16,7 +16,7 @@ com.companyname.bit.templateplayground - AC77D3A3-5437-4DF7-8F4A-1FF3C5F089D3 + 978898C4-26E6-403D-BFDD-5F74BE27A943 1.0 @@ -57,7 +57,7 @@ - + @@ -82,14 +82,10 @@ - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + @@ -98,12 +94,16 @@ - - - - - - + + + + + + + + + + + 12.0