diff --git a/.github/workflows/todo-sample.cd.yml b/.github/workflows/todo-sample.cd.yml index c610a675c3..8f816d4215 100644 --- a/.github/workflows/todo-sample.cd.yml +++ b/.github/workflows/todo-sample.cd.yml @@ -47,7 +47,7 @@ jobs: env: ServerAddress: ${{ env.SERVER_ADDRESS }} WebAppRender.BlazorMode: BlazorWebAssembly - WebAppRender.PreRenderEnabled: true + WebAppRender.PrerenderEnabled: true ApplicationInsights.ConnectionString: ${{ secrets.APPLICATION_INSIGHTS_CONNECTION_STRING }} AdsPushVapid.PublicKey: ${{ secrets.TODO_PUBLIC_VAPIDKEY }} diff --git a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razor b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razor index fbb0b823a2..6858b979df 100644 --- a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razor +++ b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razor @@ -63,7 +63,7 @@ @if (HttpContext.Request.IsCrawlerClient() is false) { - + @if (AppRenderMode.PwaEnabled) { diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppComponentBase.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppComponentBase.cs index c55f0867af..a3968fde89 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppComponentBase.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppComponentBase.cs @@ -20,7 +20,7 @@ public partial class AppComponentBase : ComponentBase, IAsyncDisposable [AutoInject] protected IPrerenderStateService PrerenderStateService = default!; /// - /// + /// /// [AutoInject] protected PubSubService PubSubService = default!; @@ -38,6 +38,8 @@ public partial class AppComponentBase : ComponentBase, IAsyncDisposable [AutoInject] protected SnackBarService SnackBarService = default!; + [AutoInject] protected ITelemetryContext TelemetryContext = default!; + private readonly CancellationTokenSource cts = new(); protected CancellationToken CurrentCancellationToken => cts.Token; diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppInitializer.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppInitializer.cs index 5e5fe8e0e9..3becead072 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppInitializer.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppInitializer.cs @@ -1,4 +1,5 @@ //+:cnd:noEmit +using Microsoft.Extensions.Logging; //#if (signalr == true) using Microsoft.AspNetCore.SignalR.Client; //#endif @@ -14,17 +15,18 @@ public partial class AppInitializer : AppComponentBase //#if (notification == true) [AutoInject] private IPushNotificationService pushNotificationService = default!; //#endif + [AutoInject] private Navigator navigator = default!; [AutoInject] private IJSRuntime jsRuntime = default!; [AutoInject] private Bit.Butil.Console console = default!; [AutoInject] private IStorageService storageService = default!; + [AutoInject] private ILogger logger = default!; [AutoInject] private SnackBarService snackBarService = default!; [AutoInject] private AuthenticationManager authManager = default!; - [AutoInject] private ITelemetryContext telemetryContext = default!; [AutoInject] private NavigationManager navigationManager = default!; [AutoInject] private CultureInfoManager cultureInfoManager = default!; [AutoInject] private IBitDeviceCoordinator bitDeviceCoordinator = default!; - protected async override Task OnInitAsync() + protected override async Task OnInitAsync() { AuthenticationManager.AuthenticationStateChanged += AuthenticationStateChanged; @@ -51,9 +53,12 @@ protected override async Task OnAfterFirstRenderAsync() { await base.OnAfterFirstRenderAsync(); + TelemetryContext.UserAgent = await navigator.GetUserAgent(); + TelemetryContext.TimeZone = await jsRuntime.GetTimeZone(); + TelemetryContext.Culture = CultureInfo.CurrentCulture.Name; if (AppPlatform.IsBlazorHybrid is false) { - telemetryContext.OS = await jsRuntime.GetBrowserPlatform(); + TelemetryContext.OS = await jsRuntime.GetBrowserPlatform(); } } @@ -62,8 +67,8 @@ private async void AuthenticationStateChanged(Task task) try { var user = (await task).User; - telemetryContext.UserId = user.IsAuthenticated() ? user.GetUserId() : null; - telemetryContext.UserSessionId = user.IsAuthenticated() ? user.GetSessionId() : null; + TelemetryContext.UserId = user.IsAuthenticated() ? user.GetUserId() : null; + TelemetryContext.UserSessionId = user.IsAuthenticated() ? user.GetSessionId() : null; //#if (signalr == true) if (InPrerenderSession is false) @@ -97,7 +102,7 @@ private async Task ConnectSignalR() hubConnection = new HubConnectionBuilder() .WithAutomaticReconnect() - .WithUrl($"{Configuration.GetServerAddress()}/app-hub?access_token={access_token}", options => + .WithUrl($"{HttpClient.BaseAddress}app-hub?access_token={access_token}", options => { options.Transports = Microsoft.AspNetCore.Http.Connections.HttpTransportType.WebSockets; // Avoid enabling long polling or Server-Sent Events. Focus on resolving the issue with WebSockets instead. @@ -123,8 +128,35 @@ private async Task ConnectSignalR() // You can also leverage IPubSubService to notify other components in the application. }); + hubConnection.Closed += HubConnectionDisconnected; + hubConnection.Reconnected += HubConnectionConnected; + hubConnection.Reconnecting += HubConnectionDisconnected; + await hubConnection.StartAsync(CurrentCancellationToken); + + await HubConnectionConnected(null); + } + + private async Task HubConnectionConnected(string? arg) + { + TelemetryContext.IsOnline = true; + logger.LogInformation("SignalR connection established."); + } + + private async Task HubConnectionDisconnected(Exception? exception) + { + TelemetryContext.IsOnline = false; + + if (exception is null) + { + logger.LogInformation("SignalR connection lost."); // Was triggered intentionally by either server or client. + } + else + { + ExceptionHandler.Handle(exception); + } } + //#endif private async Task SetupBodyClasses() @@ -162,6 +194,9 @@ protected override async ValueTask DisposeAsync(bool disposing) //#if (signalr == true) if (hubConnection is not null) { + hubConnection.Closed -= HubConnectionDisconnected; + hubConnection.Reconnected -= HubConnectionConnected; + hubConnection.Reconnecting -= HubConnectionDisconnected; await hubConnection.DisposeAsync(); } //#endif diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Authorized/Settings/SessionsSection.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Authorized/Settings/SessionsSection.razor index cd11df9352..baa204a647 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Authorized/Settings/SessionsSection.razor +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Authorized/Settings/SessionsSection.razor @@ -7,13 +7,13 @@ @Localizer[nameof(AppStrings.CurrentSession)] + SecondaryText="@currentSession.Address" + TertiaryText="@($"{currentSession.IP} - {GetLastSeenOn(currentSession.RenewedOn)}")" + Size="BitPersonaSize.Size48" + Presence="@GetPresence(currentSession.RenewedOn)" + Styles="@(new() { Image = "width:50%;height:50%" })" + ImageInitials="✓" + ImageUrl="@($"/_content/Boilerplate.Client.Core/images/os/{GetImageUrl(currentSession.Device)}")" /> } @@ -28,9 +28,9 @@ diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Authorized/Settings/SessionsSection.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Authorized/Settings/SessionsSection.razor.cs index dcaf573636..c518e41b2b 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Authorized/Settings/SessionsSection.razor.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Authorized/Settings/SessionsSection.razor.cs @@ -75,10 +75,17 @@ private static string GetImageUrl(string? device) return "apple.png"; } - private static BitPersonaPresence GetPresence(string? lastSeenOn) + private BitPersonaPresence GetPresence(DateTimeOffset renewedOn) { - return lastSeenOn == AppStrings.Online ? BitPersonaPresence.Online - : lastSeenOn == AppStrings.Recently ? BitPersonaPresence.Away - : BitPersonaPresence.Offline; + return DateTimeOffset.UtcNow - renewedOn < TimeSpan.FromMinutes(5) ? BitPersonaPresence.Online + : DateTimeOffset.UtcNow - renewedOn < TimeSpan.FromMinutes(15) ? BitPersonaPresence.Away + : BitPersonaPresence.Offline; + } + + private string GetLastSeenOn(DateTimeOffset renewedOn) + { + return DateTimeOffset.UtcNow - renewedOn < TimeSpan.FromMinutes(5) ? Localizer[nameof(AppStrings.Online)] + : DateTimeOffset.UtcNow - renewedOn < TimeSpan.FromMinutes(15) ? Localizer[nameof(AppStrings.Recently)] + : renewedOn.ToLocalTime().ToString("g"); } } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Authorized/Todo/TodoPage.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Authorized/Todo/TodoPage.razor index 532a1a0957..cdb9aee16f 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Authorized/Todo/TodoPage.razor +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Authorized/Todo/TodoPage.razor @@ -86,7 +86,7 @@ Styles="@(new() { Label = todo.IsDone ? "text-decoration:line-through" : "" })" DefaultValue="todo.IsDone" OnChange="() => ToggleIsDone(todo)" /> - @todo.Date.ToLocalTime().ToString("yyyy MMMM dd, HH:mm:ss") + @todo.Date.ToLocalTime().ToString("F") diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Data/Migrations/20240729183448_InitialMigration.Designer.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Data/Migrations/20240729183448_InitialMigration.Designer.cs deleted file mode 100644 index 5738ce8a4f..0000000000 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Data/Migrations/20240729183448_InitialMigration.Designer.cs +++ /dev/null @@ -1,76 +0,0 @@ -// -using System; -using Boilerplate.Client.Core.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace Boilerplate.Client.Core.Data.Migrations -{ - [DbContext(typeof(OfflineDbContext))] - [Migration("20240729183448_InitialMigration")] - partial class InitialMigration - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.7"); - - modelBuilder.Entity("Boilerplate.Shared.Dtos.Identity.UserDto", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("BirthDate") - .HasColumnType("INTEGER"); - - b.Property("Email") - .HasColumnType("TEXT"); - - b.Property("FullName") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Gender") - .HasColumnType("INTEGER"); - - b.Property("Password") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("PhoneNumber") - .HasColumnType("TEXT"); - - b.Property("ProfileImageName") - .HasColumnType("TEXT"); - - b.Property("UserName") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Users"); - - b.HasData( - new - { - Id = new Guid("8ff71671-a1d6-4f97-abb9-d87d7b47d6e7"), - BirthDate = 1306790461440000000L, - Email = "test@bitplatform.dev", - FullName = "Boilerplate test account", - Gender = 2, - Password = "123456", - PhoneNumber = "+31684207362", - UserName = "test" - }); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Data/Migrations/20241030140343_InitialMigration.Designer.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Data/Migrations/20241030140343_InitialMigration.Designer.cs new file mode 100644 index 0000000000..046b42efe0 --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Data/Migrations/20241030140343_InitialMigration.Designer.cs @@ -0,0 +1,72 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Boilerplate.Client.Core.Data.Migrations; + +[DbContext(typeof(OfflineDbContext))] +[Migration("20241030140343_InitialMigration")] +partial class InitialMigration +{ + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.0"); + + modelBuilder.Entity("Boilerplate.Shared.Dtos.Identity.UserDto", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BirthDate") + .HasColumnType("INTEGER"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("FullName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Gender") + .HasColumnType("INTEGER"); + + b.Property("Password") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("ProfileImageName") + .HasColumnType("TEXT"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Users"); + + b.HasData( + new + { + Id = new Guid("8ff71671-a1d6-4f97-abb9-d87d7b47d6e7"), + BirthDate = 1306790461440000000L, + Email = "test@bitplatform.dev", + FullName = "Boilerplate test account", + Gender = 0, + Password = "123456", + PhoneNumber = "+31684207362", + UserName = "test" + }); + }); +#pragma warning restore 612, 618 + } +} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Data/Migrations/20240729183448_InitialMigration.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Data/Migrations/20241030140343_InitialMigration.cs similarity index 96% rename from src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Data/Migrations/20240729183448_InitialMigration.cs rename to src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Data/Migrations/20241030140343_InitialMigration.cs index a62a7b6db8..d859fcd8ef 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Data/Migrations/20240729183448_InitialMigration.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Data/Migrations/20241030140343_InitialMigration.cs @@ -33,7 +33,7 @@ protected override void Up(MigrationBuilder migrationBuilder) migrationBuilder.InsertData( table: "Users", columns: new[] { "Id", "BirthDate", "Email", "FullName", "Gender", "Password", "PhoneNumber", "ProfileImageName", "UserName" }, - values: new object[] { new Guid("8ff71671-a1d6-4f97-abb9-d87d7b47d6e7"), 1306790461440000000L, "test@bitplatform.dev", "Boilerplate test account", 2, "123456", "+31684207362", null, "test" }); + values: new object[] { new Guid("8ff71671-a1d6-4f97-abb9-d87d7b47d6e7"), 1306790461440000000L, "test@bitplatform.dev", "Boilerplate test account", 0, "123456", "+31684207362", null, "test" }); } /// diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Data/Migrations/OfflineDbContextModelSnapshot.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Data/Migrations/OfflineDbContextModelSnapshot.cs index 23b5541582..13cca205c7 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Data/Migrations/OfflineDbContextModelSnapshot.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Data/Migrations/OfflineDbContextModelSnapshot.cs @@ -12,7 +12,7 @@ partial class OfflineDbContextModelSnapshot : ModelSnapshot protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.7"); + modelBuilder.HasAnnotation("ProductVersion", "8.0.0"); modelBuilder.Entity("Boilerplate.Shared.Dtos.Identity.UserDto", b => { @@ -58,7 +58,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) BirthDate = 1306790461440000000L, Email = "test@bitplatform.dev", FullName = "Boilerplate test account", - Gender = 2, + Gender = 0, Password = "123456", PhoneNumber = "+31684207362", UserName = "test" diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/IJSRuntimeExtensions.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/IJSRuntimeExtensions.cs index 48518d9a2f..0f67bd17ca 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/IJSRuntimeExtensions.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/IJSRuntimeExtensions.cs @@ -13,6 +13,11 @@ public static ValueTask GetBrowserPlatform(this IJSRuntime jsRuntime) return jsRuntime.InvokeAsync("App.getPlatform"); } + public static ValueTask GetTimeZone(this IJSRuntime jsRuntime) + { + return jsRuntime.InvokeAsync("App.getTimeZone"); + } + public static ValueTask ApplyBodyElementClasses(this IJSRuntime jsRuntime, List cssClasses, Dictionary cssVariables) { return jsRuntime.InvokeVoidAsync("App.applyBodyElementClasses", cssClasses, cssVariables); diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Scripts/app.ts b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Scripts/app.ts index c30141ce57..51a3499a82 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Scripts/app.ts +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Scripts/app.ts @@ -9,6 +9,10 @@ class App { return (navigator as any).userAgentData?.platform || navigator?.platform; } + public static getTimeZone(): string { + return Intl.DateTimeFormat().resolvedOptions().timeZone; + } + //#if (notification == true) public static async getDeviceInstallation(vapidPublicKey: string) { if (await Notification.requestPermission() != "granted") diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/AppTelemetryContext.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/AppTelemetryContext.cs index 4b7085ebdf..2ed22276d9 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/AppTelemetryContext.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/AppTelemetryContext.cs @@ -1,4 +1,5 @@ -using System.Runtime.InteropServices; +//+:cnd:noEmit +using System.Runtime.InteropServices; namespace Boilerplate.Client.Core.Services; @@ -15,4 +16,14 @@ public class AppTelemetryContext : ITelemetryContext public virtual string? AppVersion { get; set; } = typeof(AppTelemetryContext).Assembly.GetName().Version?.ToString(); public virtual string? WebView { get; set; } + + public virtual string? UserAgent { get; set; } + + public string? TimeZone { get; set; } + + public string? Culture { get; set; } = CultureInfo.CurrentCulture.Name; + + //#if (signalr == true) + public bool IsOnline { get; set; } + //#endif } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/Contracts/ITelemetryContext.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/Contracts/ITelemetryContext.cs index 1994fa7260..151724f57a 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/Contracts/ITelemetryContext.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/Contracts/ITelemetryContext.cs @@ -1,4 +1,5 @@ -namespace Boilerplate.Client.Core.Services.Contracts; +//+:cnd:noEmit +namespace Boilerplate.Client.Core.Services.Contracts; public interface ITelemetryContext { @@ -34,6 +35,14 @@ public static ITelemetryContext? Current public string? OS { get; set; } public string? AppVersion { get; set; } - public string? WebView { get; set; } + + public string? UserAgent { get; set; } + + public string? TimeZone { get; set; } + public string? Culture { get; set; } + + //#if (signalr == true) + public bool IsOnline { get; set; } + //#endif } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/ExceptionHandlerBase.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/ExceptionHandlerBase.cs index 4c3b4a4880..ce80ffe334 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/ExceptionHandlerBase.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/ExceptionHandlerBase.cs @@ -1,4 +1,4 @@ -//-:cnd:noEmit +//+:cnd:noEmit using System.Diagnostics; using Microsoft.Extensions.Logging; using System.Runtime.CompilerServices; @@ -32,6 +32,12 @@ public void Handle(Exception exp, parameters[nameof(TelemetryContext.AppSessionId)] = TelemetryContext.AppSessionId; parameters[nameof(TelemetryContext.AppVersion)] = TelemetryContext.AppVersion; parameters[nameof(TelemetryContext.OS)] = TelemetryContext.OS; + parameters[nameof(TelemetryContext.UserAgent)] = TelemetryContext.UserAgent; + parameters[nameof(TelemetryContext.TimeZone)] = TelemetryContext.TimeZone; + parameters[nameof(TelemetryContext.Culture)] = TelemetryContext.Culture; + //#if (signalr == true) + parameters[nameof(TelemetryContext.IsOnline)] = TelemetryContext.IsOnline; + //#endif if (AppPlatform.IsBlazorHybrid) { parameters[nameof(TelemetryContext.WebView)] = TelemetryContext.WebView; @@ -44,19 +50,28 @@ protected virtual void Handle(Exception exception, Dictionary pa { var isDevEnv = AppEnvironment.IsDev(); - string exceptionMessage = (exception as KnownException)?.Message ?? + using (var scope = Logger.BeginScope(parameters.ToDictionary(i => i.Key, i => i.Value ?? string.Empty))) + { + var exceptionMessage = exception.Message; + var innerException = exception.InnerException; + + while (innerException is not null) + { + exceptionMessage += $"{Environment.NewLine}{innerException.Message}"; + innerException = innerException.InnerException; + } + + Logger.LogError(exception, exceptionMessage); + } + + string displayableExceptionMessage = (exception as KnownException)?.Message ?? (isDevEnv ? exception.ToString() : Localizer[nameof(AppStrings.UnknownException)]); + MessageBoxService.Show(displayableExceptionMessage, Localizer[nameof(AppStrings.Error)]); + if (isDevEnv) { 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/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/PushNotificationServiceBase.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/PushNotificationServiceBase.cs index bff1737d24..ed43ab5e26 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/PushNotificationServiceBase.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/PushNotificationServiceBase.cs @@ -23,6 +23,12 @@ public async Task RegisterDevice(CancellationToken cancellationToken) var deviceInstallation = await GetDeviceInstallation(cancellationToken); + if (deviceInstallation is null) + { + Logger.LogInformation("Could not retrieve device installation"); // Browser's incognito mode etc. + return; + } + await pushNotificationController.RegisterDevice(deviceInstallation, cancellationToken); } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/appsettings.json b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/appsettings.json index 6ef2df5903..c78c02f339 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/appsettings.json +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/appsettings.json @@ -1,6 +1,6 @@ { //#if (api == "Integrated") - "ServerAddress": "/", + "ServerAddress": "http://localhost:5030/", //#endif //#if (IsInsideProjectTemplate) /* diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Boilerplate.Client.Maui.csproj b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Boilerplate.Client.Maui.csproj index 2fe1328118..7977ae1e6c 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Boilerplate.Client.Maui.csproj +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Boilerplate.Client.Maui.csproj @@ -30,7 +30,6 @@ BeforeBuildTasks; $(ResolveStaticWebAssetsInputsDependsOn) - false @@ -76,6 +75,7 @@ + diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/Android/AndroidManifest.xml b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/Android/AndroidManifest.xml index 458e80e9f0..160ca5411f 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/Android/AndroidManifest.xml +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/Android/AndroidManifest.xml @@ -1,4 +1,5 @@  + \ No newline at end of file diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/Android/MainActivity.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/Android/MainActivity.cs index b36da5b143..c8b9af599a 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/Android/MainActivity.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/Android/MainActivity.cs @@ -45,6 +45,9 @@ public partial class MainActivity : MauiAppCompatActivity protected override void OnCreate(Bundle? savedInstanceState) { + // https://github.com/dotnet/maui/issues/24742 + Theme?.ApplyStyle(Resource.Style.OptOutEdgeToEdgeEnforcement, force: false); + base.OnCreate(savedInstanceState); var url = Intent?.DataString; diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/Android/Resources/values/styles.xml b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/Android/Resources/values/styles.xml new file mode 100644 index 0000000000..f7567fa256 --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/Android/Resources/values/styles.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/Android/Resources/values/values-v35/styles.xml b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/Android/Resources/values/values-v35/styles.xml new file mode 100644 index 0000000000..955b6c267c --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/Android/Resources/values/values-v35/styles.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/Android/Services/AndroidPushNotificationService.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/Android/Services/AndroidPushNotificationService.cs index e1ebff36f9..a7eb06e454 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/Android/Services/AndroidPushNotificationService.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/Android/Services/AndroidPushNotificationService.cs @@ -28,7 +28,7 @@ public override async Task GetDeviceInstallation(Cancella { try { - using CancellationTokenSource cts = new(TimeSpan.FromSeconds(30)); + using CancellationTokenSource cts = new(TimeSpan.FromSeconds(15)); using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, cts.Token); while (string.IsNullOrEmpty(Token)) @@ -39,12 +39,9 @@ public override async Task GetDeviceInstallation(Cancella await Task.Delay(TimeSpan.FromSeconds(1), linkedCts.Token); } } - finally + catch (Exception exp) { - if (Token is null) - { - Logger.LogError("Unable to resolve token for FCMv1."); - } + throw new InvalidOperationException("Unable to resolve token for FCMv1.", exp); } var installation = new DeviceInstallationDto diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/MacCatalyst/Services/MacCatalystPushNotificationService.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/MacCatalyst/Services/MacCatalystPushNotificationService.cs index cb6e1f4901..6efef0f826 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/MacCatalyst/Services/MacCatalystPushNotificationService.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/MacCatalyst/Services/MacCatalystPushNotificationService.cs @@ -24,7 +24,7 @@ public async override Task IsNotificationSupported(CancellationToken cance public override async Task GetDeviceInstallation(CancellationToken cancellationToken) { - using CancellationTokenSource cts = new(TimeSpan.FromSeconds(30)); + using CancellationTokenSource cts = new(TimeSpan.FromSeconds(15)); using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, cts.Token); try @@ -37,12 +37,9 @@ public override async Task GetDeviceInstallation(Cancella await Task.Delay(TimeSpan.FromSeconds(1), linkedCts.Token); } } - finally + catch (Exception exp) { - if (Token is null) - { - Logger.LogError("Unable to resolve token for APNS."); - } + throw new InvalidOperationException("Unable to resolve token for APNS.", exp); } var installation = new DeviceInstallationDto diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/iOS/Entitlements.Debug.plist b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/iOS/Entitlements.Development.plist similarity index 100% rename from src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/iOS/Entitlements.Debug.plist rename to src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/iOS/Entitlements.Development.plist diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/iOS/Entitlements.Release.plist b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/iOS/Entitlements.Production.plist similarity index 100% rename from src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/iOS/Entitlements.Release.plist rename to src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/iOS/Entitlements.Production.plist diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/iOS/Services/iOSPushNotificationService.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/iOS/Services/iOSPushNotificationService.cs index 388630f4c2..9cd1918814 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/iOS/Services/iOSPushNotificationService.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Platforms/iOS/Services/iOSPushNotificationService.cs @@ -24,7 +24,7 @@ public async override Task IsNotificationSupported(CancellationToken cance public override async Task GetDeviceInstallation(CancellationToken cancellationToken) { - using CancellationTokenSource cts = new(TimeSpan.FromSeconds(30)); + using CancellationTokenSource cts = new(TimeSpan.FromSeconds(15)); using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, cts.Token); try @@ -37,12 +37,9 @@ public override async Task GetDeviceInstallation(Cancella await Task.Delay(TimeSpan.FromSeconds(1), linkedCts.Token); } } - finally + catch (Exception exp) { - if (Token is null) - { - Logger.LogError("Unable to resolve token for APNS."); - } + throw new InvalidOperationException("Unable to resolve token for APNS.", exp); } var installation = new DeviceInstallationDto diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Services/MauiTelemetryInitializer.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Services/MauiTelemetryInitializer.cs index f09f13cb75..9a3212603c 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Services/MauiTelemetryInitializer.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Services/MauiTelemetryInitializer.cs @@ -1,4 +1,5 @@ -using Microsoft.ApplicationInsights.Channel; +//+:cnd:noEmit +using Microsoft.ApplicationInsights.Channel; using Microsoft.ApplicationInsights.Extensibility; namespace Boilerplate.Client.Maui.Services; @@ -16,6 +17,12 @@ public void Initialize(ITelemetry telemetry) telemetry.Context.GlobalProperties[nameof(ITelemetryContext.UserSessionId)] = ITelemetryContext.Current.UserSessionId?.ToString(); telemetry.Context.GlobalProperties[nameof(ITelemetryContext.WebView)] = ITelemetryContext.Current.WebView; + telemetry.Context.GlobalProperties[nameof(ITelemetryContext.UserAgent)] = ITelemetryContext.Current.UserAgent; + telemetry.Context.GlobalProperties[nameof(ITelemetryContext.TimeZone)] = ITelemetryContext.Current.TimeZone; + telemetry.Context.GlobalProperties[nameof(ITelemetryContext.Culture)] = ITelemetryContext.Current.Culture; + //#if (signalr == true) + telemetry.Context.GlobalProperties[nameof(ITelemetryContext.IsOnline)] = ITelemetryContext.Current.IsOnline.ToString().ToLowerInvariant(); + //#endif } telemetry.Context.Session.IsFirst = VersionTracking.IsFirstLaunchEver; diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/appsettings.json b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/appsettings.json index 5ce72aad16..5fe62e7f93 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/appsettings.json +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/appsettings.json @@ -2,7 +2,7 @@ "WebAppRender": { "PrerenderEnabled": false, "BlazorMode": "BlazorServer", - "BlazorMode_Comment": "Default value of Client.Core/appsettings.Production.json is BlazorAuto" + "BlazorMode_Comment": "BlazorServer, BlazorWebAssembly and BlazorAuto. Default value of Client.Core/appsettings.Production.json is BlazorAuto" }, //#if (notification == true) "AdsPushVapid_Comment": "https://github.com/adessoTurkey-dotNET/AdsPush", diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/Services/WindowsTelemetryInitializer.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/Services/WindowsTelemetryInitializer.cs index 49cf1b2731..6d0d05fc4e 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/Services/WindowsTelemetryInitializer.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Windows/Services/WindowsTelemetryInitializer.cs @@ -1,4 +1,5 @@ -using Microsoft.ApplicationInsights.Channel; +//+:cnd:noEmit +using Microsoft.ApplicationInsights.Channel; using Microsoft.ApplicationInsights.Extensibility; namespace Boilerplate.Client.Windows.Services; @@ -16,6 +17,12 @@ public void Initialize(ITelemetry telemetry) telemetry.Context.GlobalProperties[nameof(ITelemetryContext.UserSessionId)] = ITelemetryContext.Current.UserSessionId?.ToString(); telemetry.Context.GlobalProperties[nameof(ITelemetryContext.WebView)] = ITelemetryContext.Current.WebView; + telemetry.Context.GlobalProperties[nameof(ITelemetryContext.UserAgent)] = ITelemetryContext.Current.UserAgent; + telemetry.Context.GlobalProperties[nameof(ITelemetryContext.TimeZone)] = ITelemetryContext.Current.TimeZone; + telemetry.Context.GlobalProperties[nameof(ITelemetryContext.Culture)] = ITelemetryContext.Current.Culture; + //#if (signalr == true) + telemetry.Context.GlobalProperties[nameof(ITelemetryContext.IsOnline)] = ITelemetryContext.Current.IsOnline.ToString().ToLowerInvariant(); + //#endif } } } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Identity/UserController.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Identity/UserController.cs index 986753a196..afaac9335c 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Identity/UserController.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Identity/UserController.cs @@ -44,23 +44,17 @@ public async Task> GetUserSessions(CancellationToken cancel ?? throw new ResourceNotFoundException(); return user.Sessions - .OrderByDescending(s => s.RenewedOn) .Select(us => { var dto = us.Map(); - var lastSeenDateTime = us.RenewedOn ?? us.StartedOn; + dto.RenewedOn = us.RenewedOn ?? us.StartedOn; - dto.LastSeenOn = DateTimeOffset.UtcNow - lastSeenDateTime < TimeSpan.FromMinutes(5) - ? Localizer[nameof(AppStrings.Online)] - : DateTimeOffset.UtcNow - lastSeenDateTime < TimeSpan.FromMinutes(15) - ? Localizer[nameof(AppStrings.Recently)] - : lastSeenDateTime.Humanize(culture: CultureInfo.CurrentUICulture); - - dto.IsValid = DateTimeOffset.UtcNow - lastSeenDateTime < AppSettings.Identity.RefreshTokenExpiration; + dto.IsValid = DateTimeOffset.UtcNow - dto.RenewedOn < AppSettings.Identity.RefreshTokenExpiration; return dto; }) + .OrderByDescending(us => us.RenewedOn) .ToList(); } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Program.Services.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Program.Services.cs index 1d9cef9d7c..b8e2589ee5 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Program.Services.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Program.Services.cs @@ -69,7 +69,7 @@ public static void AddServerApiProjectServices(this WebApplicationBuilder builde var webClientUrl = configuration.Get()!.WebClientUrl; policy.SetIsOriginAllowed(origin => - LocalhostOriginRegex().IsMatch(origin) || + AllowedOriginsRegex().IsMatch(origin) || (string.IsNullOrEmpty(webClientUrl) is false && string.Equals(origin, webClientUrl, StringComparison.InvariantCultureIgnoreCase))) .AllowAnyHeader() .AllowAnyMethod() @@ -406,8 +406,8 @@ private static void AddSwaggerGen(WebApplicationBuilder builder) } /// - /// For either Blazor Hybrid web view or localhost in dev environment. + /// For either Blazor Hybrid web view, localhost, dev tunnels etc in dev environment. /// - [GeneratedRegex(@"^(http|https|app):\/\/(localhost|0\.0\.0\.0|0\.0\.0\.1|127\.0\.0\.1)(:\d+)?(\/.*)?$")] - private static partial Regex LocalhostOriginRegex(); + [GeneratedRegex(@"^(http|https|app):\/\/(localhost|0\.0\.0\.0|0\.0\.0\.1|127\.0\.0\.1|.*?devtunnels\.ms|.*?github\.dev)(:\d+)?(\/.*)?$")] + private static partial Regex AllowedOriginsRegex(); } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Web/.config/dotnet-tools.json b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Web/.config/dotnet-tools.json new file mode 100644 index 0000000000..40d4373bd4 --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Web/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-ef": { + "version": "8.0.10", + "commands": [ + "dotnet-ef" + ] + } + } +} \ No newline at end of file diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Dtos/Identity/UserSessionDto.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Dtos/Identity/UserSessionDto.cs index cb7b7c6d67..d2bb317b9a 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Dtos/Identity/UserSessionDto.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Dtos/Identity/UserSessionDto.cs @@ -10,7 +10,7 @@ public partial class UserSessionDto public string? Address { get; set; } - public string? LastSeenOn { get; set; } + public DateTimeOffset RenewedOn { get; set; } public bool IsValid { get; set; } } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Resources/AppStrings.nl.resx b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Resources/AppStrings.nl.resx index 6fc0f1473c..3873a39d58 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Resources/AppStrings.nl.resx +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Resources/AppStrings.nl.resx @@ -210,9 +210,6 @@ Taal selecteren - - - Instellingen Profiel @@ -806,9 +803,6 @@ 2FA - - - Het resetten van de gedeelde 2fa-sleutel moet 2fa uitschakelen totdat een 2fa-token op basis van de nieuwe gedeelde sleutel is gevalideerd.