diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Boilerplate.Client.Core.csproj b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Boilerplate.Client.Core.csproj index 3318f513b5..85ba6a4172 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Boilerplate.Client.Core.csproj +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Boilerplate.Client.Core.csproj @@ -18,7 +18,7 @@ - + @@ -70,7 +70,7 @@ - + diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/ConfirmMessageBox.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/ConfirmMessageBox.razor similarity index 100% rename from src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/ConfirmMessageBox.razor rename to src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/ConfirmMessageBox.razor diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/ConfirmMessageBox.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/ConfirmMessageBox.razor.cs similarity index 92% rename from src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/ConfirmMessageBox.razor.cs rename to src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/ConfirmMessageBox.razor.cs index a0bdcb6263..c4e38aa892 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/ConfirmMessageBox.razor.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/ConfirmMessageBox.razor.cs @@ -1,4 +1,4 @@ -namespace Boilerplate.Client.Core.Components.Layout; +namespace Boilerplate.Client.Core.Components; public partial class ConfirmMessageBox { diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/ConfirmMessageBox.razor.scss b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/ConfirmMessageBox.razor.scss similarity index 82% rename from src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/ConfirmMessageBox.razor.scss rename to src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/ConfirmMessageBox.razor.scss index c2d93adad1..fe74a57e21 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/ConfirmMessageBox.razor.scss +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/ConfirmMessageBox.razor.scss @@ -1,6 +1,6 @@ -@import '../../Styles/abstracts/_functions.scss'; -@import '../../Styles/abstracts/_media-queries.scss'; -@import '../../Styles/abstracts/_bit-css-variables.scss'; +@import '../Styles/abstracts/_functions.scss'; +@import '../Styles/abstracts/_media-queries.scss'; +@import '../Styles/abstracts/_bit-css-variables.scss'; .main-container { flex-grow: 1; diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/EmptyLayout.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/EmptyLayout.razor new file mode 100644 index 0000000000..ecd2281dc7 --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/EmptyLayout.razor @@ -0,0 +1,5 @@ +@inherits LayoutComponentBase + + + @Body + \ No newline at end of file diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Footer.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Footer.razor deleted file mode 100644 index a2a0524141..0000000000 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Footer.razor +++ /dev/null @@ -1,47 +0,0 @@ -@inherits AppComponentBase - - \ No newline at end of file diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Footer.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Footer.razor.cs deleted file mode 100644 index 3c29ff9b1d..0000000000 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Footer.razor.cs +++ /dev/null @@ -1,57 +0,0 @@ -//-:cnd:noEmit -namespace Boilerplate.Client.Core.Components.Layout; - -public partial class Footer -{ - [AutoInject] private Cookie cookie = default!; - [AutoInject] private IPubSubService pubSubService = default!; - [AutoInject] private BitThemeManager bitThemeManager = default!; - [AutoInject] private CultureInfoManager cultureInfoManager = default!; - [AutoInject] private IBitDeviceCoordinator bitDeviceCoordinator = default!; - - private BitDropdownItem[] cultures = default!; - - protected override async Task OnInitAsync() - { - if (CultureInfoManager.MultilingualEnabled) - { - cultures = CultureInfoManager.SupportedCultures - .Select(sc => new BitDropdownItem { Value = sc.Culture.Name, Text = sc.DisplayName }) - .ToArray(); - - SelectedCulture = CultureInfo.CurrentUICulture.Name; - } - - await base.OnInitAsync(); - } - - private string? SelectedCulture; - - private async Task OnCultureChanged() - { - if (AppPlatform.IsBlazorHybrid) - { - await StorageService.SetItem("Culture", SelectedCulture, persistent: true); - cultureInfoManager.SetCurrentCulture(SelectedCulture!); - pubSubService.Publish(PubSubMessages.CULTURE_CHANGED, SelectedCulture); - } - else - { - await cookie.Set(new() - { - Name = ".AspNetCore.Culture", - Value = Uri.EscapeDataString($"c={SelectedCulture}|uic={SelectedCulture}"), - MaxAge = 30 * 24 * 3600, - Path = "/", - Secure = AppEnvironment.IsDev() is false - }); - } - - NavigationManager.NavigateTo(NavigationManager.GetUriWithoutCulture(), forceLoad: true, replace: true); - } - - private async Task ToggleTheme() - { - await bitDeviceCoordinator.ApplyTheme(await bitThemeManager.ToggleDarkLightAsync() == "dark"); - } -} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Footer.razor.scss b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Footer.razor.scss deleted file mode 100644 index 289aee9a04..0000000000 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Footer.razor.scss +++ /dev/null @@ -1,102 +0,0 @@ -@import '../../Styles/abstracts/_vars.scss'; -@import '../../Styles/abstracts/_functions.scss'; -@import '../../Styles/abstracts/_media-queries.scss'; -@import '../../Styles/abstracts/_bit-css-variables.scss'; - -.footer { - bottom: 0; - z-index: 1; - width: 100%; - display: flex; - padding: 0.5rem; - position: fixed; - align-items: center; - height: $footerHeight; - justify-content: center; - background-color: $bit-color-background-primary; - - .bit-ios & { - padding-bottom: env(safe-area-inset-bottom); - height: calc($footerHeight + env(safe-area-inset-bottom)); - } -} - -.footer-content { - flex-grow: 1; - text-align: center; - font-size: rem2(12px); - - @include sm { - font-size: rem2(10px); - } -} - -.footer-social-lnk-grp { - display: flex; - align-items: center; - flex-flow: row nowrap; - justify-content: center; -} - -.social-lnk { - width: rem2(32px); - height: rem2(32px); - background-size: cover; - background-position: center; - background-repeat: no-repeat; - - @include sm { - width: rem2(30px); - height: rem2(30px); - } - - svg path { - fill: $bit-color-foreground-secondary; - } -} - -::deep .culture-drp { - width: rem2(104px); -} - -.theme-container { - text-align: end; - width: rem2(104px); -} - -.toggle-theme-btn { - padding: 0; - border: none; - cursor: pointer; - height: rem2(35px); - border-radius: 50%; - min-width: rem2(35px); - color: $bit-color-primary-text; - background-color: $bit-color-primary; - - .icon-container { - height: 100%; - display: flex; - flex-wrap: nowrap; - align-items: center; - justify-content: center; - - .bit-icon { - margin: 0 rem2(4px); - } - } - - &.dark-theme { - .icon-container { - padding: 2px 0 0 1px; - } - } -} - -.theme-dark .light-theme { - display: none; -} - -.theme-light .dark-theme { - display: none; -} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Header.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Header.razor deleted file mode 100644 index e665330524..0000000000 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Header.razor +++ /dev/null @@ -1,25 +0,0 @@ -@inherits AppComponentBase - - - - @if (isUserAuthenticated) - { - - } - - - - @if (isUserAuthenticated is false) - { - - @Localizer[nameof(AppStrings.TermsTitle)] - - - @Localizer[nameof(AppStrings.SignUp)] - - - @Localizer[nameof(AppStrings.SignIn)] - - } - - \ No newline at end of file diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Header.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Header.razor.cs deleted file mode 100644 index 8595aabdfe..0000000000 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Header.razor.cs +++ /dev/null @@ -1,50 +0,0 @@ -namespace Boilerplate.Client.Core.Components.Layout; - -public partial class Header -{ - private bool disposed; - private bool isUserAuthenticated; - - [Parameter] public EventCallback OnToggleMenu { get; set; } - - protected override async Task OnInitAsync() - { - AuthenticationManager.AuthenticationStateChanged += VerifyUserIsAuthenticatedOrNot; - - isUserAuthenticated = await PrerenderStateService.GetValue(async () => (await AuthenticationStateTask).User.IsAuthenticated()); - - await base.OnInitAsync(); - } - - async void VerifyUserIsAuthenticatedOrNot(Task task) - { - try - { - isUserAuthenticated = (await task).User.IsAuthenticated(); - } - catch (Exception ex) - { - ExceptionHandler.Handle(ex); - } - finally - { - await InvokeAsync(StateHasChanged); - } - } - - private async Task ToggleMenu() - { - await OnToggleMenu.InvokeAsync(); - } - - protected override async ValueTask DisposeAsync(bool disposing) - { - await base.DisposeAsync(disposing); - - if (disposed || disposing is false) return; - - AuthenticationManager.AuthenticationStateChanged -= VerifyUserIsAuthenticatedOrNot; - - disposed = true; - } -} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Header.razor.scss b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Header.razor.scss deleted file mode 100644 index fbfc033deb..0000000000 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Header.razor.scss +++ /dev/null @@ -1,65 +0,0 @@ -@import '../../Styles/abstracts/_vars.scss'; -@import '../../Styles/abstracts/_functions.scss'; -@import '../../Styles/abstracts/_media-queries.scss'; -@import '../../Styles/abstracts/_bit-css-variables.scss'; - -.header { - top: 0; - z-index: 1; - width: 100%; - display: flex; - padding: 0.5rem; - position: fixed; - align-items: end; - flex-flow: row nowrap; - justify-content: space-between; - background-color: $bit-color-background-primary; - padding-top: 0.5rem; - height: $headerHeight; - - .bit-ios & { - height: calc($headerHeight + env(safe-area-inset-top)); - } -} - -.logo-lnk { - width: 2rem; - height: 2rem; - cursor: pointer; - margin: 0 0.5rem; - border-radius: rem2(4px); - background-size: contain; - background-position: center; - background-repeat: no-repeat; - background-image: url('images/bit-logo.svg'); -} - -.header-contact { - display: flex; - align-items: center; - flex-flow: row nowrap; - justify-content: flex-start; -} - -::deep { - .header-menu { - display: none; - - @include lt-lg { - display: flex; - } - } - - .header-item { - display: flex; - padding: rem2(4px) rem2(16px); - - @include lt-lg { - padding: rem2(4px) rem2(8px); - } - - @include lt-md { - padding: rem2(4px) rem2(4px); - } - } -} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Identity/IdentityHeader.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Identity/IdentityHeader.razor new file mode 100644 index 0000000000..322e5fc9ef --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Identity/IdentityHeader.razor @@ -0,0 +1,53 @@ +@inherits AppComponentBase + + + + @if (isAnonymousPage is true) + { + + @Localizer[nameof(AppStrings.SignUp)] + + + @Localizer[nameof(AppStrings.SignIn)] + + } + else + { + + + + @Localizer[nameof(AppStrings.BackToHome)] + + + } + + + + + + @if (CultureInfoManager.MultilingualEnabled) + { + + + + + + + + + @item.Text + + + + + } + + \ No newline at end of file diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Identity/IdentityHeader.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Identity/IdentityHeader.razor.cs new file mode 100644 index 0000000000..14fd3c0f58 --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Identity/IdentityHeader.razor.cs @@ -0,0 +1,40 @@ +namespace Boilerplate.Client.Core.Components.Layout.Identity; + +public partial class IdentityHeader : AppComponentBase +{ + private BitDropdownItem[] cultures = default!; + + + [AutoInject] private IThemeService themeService = default!; + [AutoInject] private ICultureService cultureService = default!; + + + [CascadingParameter] private BitDir? currentDir { get; set; } + [CascadingParameter(Name = Parameters.CurrentUrl)] private string? currentUrl { get; set; } + [CascadingParameter(Name = Parameters.CurrentTheme)] private AppThemeType? currentTheme { get; set; } + [CascadingParameter(Name = Parameters.IsAnonymousPage)] private bool? isAnonymousPage { get; set; } + + + protected override async Task OnInitAsync() + { + if (CultureInfoManager.MultilingualEnabled) + { + cultures = CultureInfoManager.SupportedCultures + .Select(sc => new BitDropdownItem { Value = sc.Culture.Name, Text = sc.DisplayName }) + .ToArray(); + } + + await base.OnInitAsync(); + } + + + private async Task ToggleTheme() + { + await themeService.ToggleTheme(); + } + + private async Task OnCultureChanged(string? cultureName) + { + await cultureService.ChangeCulture(cultureName); + } +} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Identity/IdentityHeader.razor.scss b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Identity/IdentityHeader.razor.scss new file mode 100644 index 0000000000..876dbe12d6 --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Identity/IdentityHeader.razor.scss @@ -0,0 +1,10 @@ +@import '../../../Styles/abstracts/_functions.scss'; +@import '../../../Styles/abstracts/_bit-css-variables.scss'; + +header { + width: 100%; + padding: 1rem; + position: absolute; + height: rem2(65px); + z-index: $bit-zindex-base; +} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Identity/IdentityLayout.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Identity/IdentityLayout.razor new file mode 100644 index 0000000000..2235a17fda --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Identity/IdentityLayout.razor @@ -0,0 +1,32 @@ +@inherits LayoutComponentBase + + + + + + @if (isAnonymousPage is true) + { + + @Body + + } + else + { + + + @Body + + + + + + @* bit Boilerplate is a feature-rich project template for both Visual studio and the .NET CLI. + It comes with a lot of features right out of the box that are required for a real world application. *@ + + + + + } + + + \ No newline at end of file diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Identity/IdentityLayout.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Identity/IdentityLayout.razor.cs new file mode 100644 index 0000000000..943244297a --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Identity/IdentityLayout.razor.cs @@ -0,0 +1,6 @@ +namespace Boilerplate.Client.Core.Components.Layout.Identity; + +public partial class IdentityLayout +{ + [CascadingParameter(Name = Parameters.IsAnonymousPage)] private bool? isAnonymousPage { get; set; } +} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Identity/IdentityLayout.razor.scss b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Identity/IdentityLayout.razor.scss new file mode 100644 index 0000000000..f5214184bf --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Identity/IdentityLayout.razor.scss @@ -0,0 +1,49 @@ +@import '../../../Styles/abstracts/_functions.scss'; +@import '../../../Styles/abstracts/_media-queries.scss'; +@import '../../../Styles/abstracts/_bit-css-variables.scss'; + +main { + width: 100%; + height: 100% +} + +.panel { + width: 100%; + padding: 2rem; + min-height: 100%; + padding-top: 5rem; + background-color: $bit-color-background-secondary; +} + +.panel1 { + width: 30%; + padding: 2rem; + min-height: 100%; + min-width: 35rem; + padding-top: 5rem; + background-color: $bit-color-background-secondary; + + @include lt-md { + width: 100%; + min-width: unset; + padding-inline: 1rem; + + .form { + padding-inline: 1rem; + } + } +} + +::deep { + @include lt-md { + .panel1 { + .form { + padding: 1rem; + } + } + + .panel2 { + display: none; + } + } +} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/MainHeader.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/MainHeader.razor new file mode 100644 index 0000000000..c41583aa5b --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/MainHeader.razor @@ -0,0 +1,29 @@ +@inherits AppComponentBase + + + + + + + + @pageTitle + + @if (string.IsNullOrWhiteSpace(pageSubtitle) is false) + { + + @pageSubtitle + + } + + + + + + + diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/MainHeader.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/MainHeader.razor.cs new file mode 100644 index 0000000000..7ac72331bf --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/MainHeader.razor.cs @@ -0,0 +1,33 @@ +namespace Boilerplate.Client.Core.Components.Layout.Main; + +public partial class MainHeader : AppComponentBase +{ + private string? pageTitle; + private string? pageSubtitle; + private Action unsubscribePageTitleChanged = default!; + + + protected override async Task OnInitAsync() + { + unsubscribePageTitleChanged = PubSubService.Subscribe(PubSubMessages.PAGE_TITLE_CHANGED, async payload => + { + (pageTitle, pageSubtitle) = (ValueTuple)payload!; + + StateHasChanged(); + }); + } + + + private void OpenNavPanel() + { + PubSubService.Publish(PubSubMessages.OPEN_NAV_PANEL); + } + + + protected override async ValueTask DisposeAsync(bool disposing) + { + await base.DisposeAsync(disposing); + + unsubscribePageTitleChanged(); + } +} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/MainHeader.razor.scss b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/MainHeader.razor.scss new file mode 100644 index 0000000000..042aa39bac --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/MainHeader.razor.scss @@ -0,0 +1,30 @@ +@import '../../../Styles/abstracts/_functions.scss'; +@import '../../../Styles/abstracts/_media-queries.scss'; +@import '../../../Styles/abstracts/_bit-css-variables.scss'; + +header { + top: 0; + z-index: 1; + width: 100%; + position: sticky; + background-color: $bit-color-background-primary; +} + +::deep { + .menu-btn { + @include gt-sm { + display: none; + } + } + + .persona { + max-width: rem2(180px); + } + + .persona-details, + .menu-chevron { + @include lt-sm { + display: none; + } + } +} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/MainLayout.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/MainLayout.razor new file mode 100644 index 0000000000..5bc5494e69 --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/MainLayout.razor @@ -0,0 +1,18 @@ +@inherits LayoutComponentBase + + + + + + + + + + @Body + + + + + + + diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/MainLayout.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/MainLayout.razor.cs new file mode 100644 index 0000000000..aa6aa3effe --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/MainLayout.razor.cs @@ -0,0 +1,5 @@ +namespace Boilerplate.Client.Core.Components.Layout.Main; + +public partial class MainLayout +{ +} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/MainLayout.razor.scss b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/MainLayout.razor.scss new file mode 100644 index 0000000000..384f359708 --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/MainLayout.razor.scss @@ -0,0 +1,15 @@ +@import '../../../Styles/abstracts/_media-queries.scss'; + +main { + width: 100%; + height: 100%; +} + +.body { + width: 100%; + height: 100%; + + @include lt-md { + margin-bottom: 3.5rem + } +} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/NavMenu.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/NavMenu.razor new file mode 100644 index 0000000000..ae592b1fd8 --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/NavMenu.razor @@ -0,0 +1,50 @@ +@*+:cnd:noEmit*@ +@inherits AppComponentBase; + + + @*#if (sample == "Todo")*@ + + + + @Localizer[nameof(AppStrings.Home)] + + + + + + + @Localizer[nameof(AppStrings.TodoTitle)] + + + + + + + @Localizer[nameof(AppStrings.TermsTitle)] + + + @*#endif*@ + + @*#if (sample == "Admin")*@ + + + + @Localizer[nameof(AppStrings.Dashboard)] + + + + + + + @Localizer[nameof(AppStrings.Products)] + + + + + + + @Localizer[nameof(AppStrings.Categories)] + + + @*#endif*@ + \ No newline at end of file diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/NavMenu.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/NavMenu.razor.cs new file mode 100644 index 0000000000..9392bb74a7 --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/NavMenu.razor.cs @@ -0,0 +1,38 @@ +using Microsoft.AspNetCore.Components.Routing; + +namespace Boilerplate.Client.Core.Components.Layout.Main; + +public partial class NavMenu : IDisposable +{ + private string? currentUrl; + + protected override Task OnInitAsync() + { + SetCurrentItem(); + + NavigationManager.LocationChanged += OnLocationChanged; + + return base.OnInitAsync(); + } + + private void OnLocationChanged(object? sender, LocationChangedEventArgs e) + { + SetCurrentItem(); + StateHasChanged(); + } + + private void SetCurrentItem() + { + currentUrl = NavigationManager.Uri.Replace(NavigationManager.BaseUri, "/", StringComparison.Ordinal); + } + + private bool IsActive(string url) + { + return currentUrl == url; + } + + public void Dispose() + { + NavigationManager.LocationChanged -= OnLocationChanged; + } +} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/NavMenu.razor.scss b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/NavMenu.razor.scss new file mode 100644 index 0000000000..aae0d790f9 --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/NavMenu.razor.scss @@ -0,0 +1,27 @@ +@import '../../../Styles/abstracts/_media-queries.scss'; +@import '../../../Styles/abstracts/_bit-css-variables.scss'; + +section { + bottom: 0; + width: 100%; + display: flex; + height: 3.5rem; + position: fixed; + max-width: 100vw; + align-items: center; + justify-content: space-evenly; + background-color: $bit-color-background-primary; + border-top: 1px solid $bit-color-border-tertiary; + + @include gt-sm { + display: none; + } +} + +a { + gap: 0.25rem; + display: flex; + align-items: center; + text-decoration: none; + flex-direction: column; +} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/NavPanel.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/NavPanel.razor new file mode 100644 index 0000000000..e1b6a8b10e --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/NavPanel.razor @@ -0,0 +1,68 @@ +@inherits AppComponentBase; + +@if (isMenuOpen) +{ + +} + +@{ + var isToggled = !isMenuOpen && isMenuToggled; + var imageUrl = user.ProfileImageName is null ? null : $"{profileImageUrl}&file={user.ProfileImageName}"; +} + + + + + + + + + + + + + + + @Localizer[nameof(AppStrings.Edit)] + + + + + + + + + + + + @(isToggled ? "" : Localizer[nameof(AppStrings.SignOut)]) + + + + + + \ No newline at end of file diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/NavPanel.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/NavPanel.razor.cs new file mode 100644 index 0000000000..a6e0587a16 --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/NavPanel.razor.cs @@ -0,0 +1,86 @@ +using Boilerplate.Shared.Dtos.Identity; + +namespace Boilerplate.Client.Core.Components.Layout.Main; + +public partial class NavPanel +{ + private bool disposed; + private bool isMenuOpen; + private bool isMenuToggled; + private UserDto user = new(); + private bool isSignOutModalOpen; + private string? profileImageUrl; + private List navItems = []; + private Action unsubOpenNavPanel = default!; + private Action unsubUserDataChange = default!; + + [AutoInject] private NavigationManager navManager = default!; + + + + protected override async Task OnInitAsync() + { + CreateNavItems(); + + unsubOpenNavPanel = PubSubService.Subscribe(PubSubMessages.OPEN_NAV_PANEL, async _ => { + isMenuOpen = true; + StateHasChanged(); + }); + + unsubUserDataChange = PubSubService.Subscribe(PubSubMessages.USER_DATA_UPDATED, async payload => + { + if (payload is null) return; + user = (UserDto)payload; + StateHasChanged(); + }); + + user = (await PrerenderStateService.GetValue(() => HttpClient.GetFromJsonAsync("api/User/GetCurrentUser", AppJsonContext.Default.UserDto, CurrentCancellationToken)))!; + + var serverAddress = Configuration.GetServerAddress(); + var access_token = await PrerenderStateService.GetValue(() => AuthTokenProvider.GetAccessTokenAsync()); + profileImageUrl = $"{serverAddress}/api/Attachment/GetProfileImage?access_token={access_token}"; + } + + + private async Task DoSignOut() + { + isSignOutModalOpen = true; + await CloseMenu(); + } + + private async Task GoToProfile() + { + await CloseMenu(); + navManager.NavigateTo(Urls.ProfilePage); + } + + private async Task HandleNavItemClick(BitNavItem item) + { + if (string.IsNullOrEmpty(item.Url)) return; + + await CloseMenu(); + } + + private async Task CloseMenu() + { + isMenuOpen = false; + } + + private async Task ToggleNavPanel() + { + isMenuToggled = !isMenuToggled; + } + + + protected override async ValueTask DisposeAsync(bool disposing) + { + await base.DisposeAsync(disposing); + + if (disposed || disposing is false) return; + + unsubOpenNavPanel?.Invoke(); + unsubUserDataChange?.Invoke(); + + disposed = true; + } +} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/NavPanel.razor.items.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/NavPanel.razor.items.cs new file mode 100644 index 0000000000..a7e3480ed9 --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/NavPanel.razor.items.cs @@ -0,0 +1,77 @@ +//+:cnd:noEmit +namespace Boilerplate.Client.Core.Components.Layout.Main; + +public partial class NavPanel +{ + private void CreateNavItems() + { + navItems = + [ + new() + { + Text = Localizer[nameof(AppStrings.Home)], + IconName = BitIconName.Home, + Url = Urls.HomePage, + }, + //#if (sample == "Admin") + new() { + Text = Localizer[nameof(AppStrings.Dashboard)], + IconName = BitIconName.BarChartVerticalFill, + Url = Urls.DashboardPage, + }, + new() { + Text = Localizer[nameof(AppStrings.Products)], + IconName = BitIconName.Product, + Url = Urls.ProductsPage, + }, + new() { + Text = Localizer[nameof(AppStrings.Categories)], + IconName = BitIconName.BuildQueue, + Url = Urls.CategoriesPage, + }, + //#elif (sample == "Todo") + new() + { + Text = Localizer[nameof(AppStrings.TodoTitle)], + IconName = BitIconName.ToDoLogoOutline, + Url = Urls.TodoPage, + }, + //#endif + new() + { + Text = Localizer[nameof(AppStrings.ProfileTitle)], + IconName = BitIconName.EditContact, + Url = Urls.ProfilePage, + }, + //#if (offlineDb == true) + new() + { + Text = Localizer[nameof(AppStrings.OfflineEditProfileTitle)], + IconName = BitIconName.EditContact, + Url = Urls.OfflineEditProfilePage, + }, + //#endif + new() + { + Text = Localizer[nameof(AppStrings.TermsTitle)], + IconName = BitIconName.EntityExtraction, + Url = Urls.TermsPage, + } + ]; + + if (AppPlatform.IsBlazorHybrid) + { + // Currently, 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, the "About" page is integrated within Blazor + // hybrid projects like Client/Maui. + + navItems.Add(new() + { + Text = Localizer[nameof(AppStrings.AboutTitle)], + IconName = BitIconName.HelpMirrored, + Url = Urls.AboutPage, + }); + } + } +} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/NavPanel.razor.scss b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/NavPanel.razor.scss new file mode 100644 index 0000000000..4590d42a1c --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/NavPanel.razor.scss @@ -0,0 +1,78 @@ +@import '../../../Styles/abstracts/_media-queries.scss'; +@import '../../../Styles/abstracts/_bit-css-variables.scss'; + +section { + top: 0; + height: 100vh; + padding: 1rem; + position: sticky; + overflow: hidden auto; + min-width: var(--nav-menu-width); + max-width: var(--nav-menu-width); + + .bit-ios & { + top: calc(env(safe-area-inset-top)); + } + + &::-webkit-scrollbar { + width: 0; + } + + @include lt-md { + z-index: 3; + padding: 0; + position: fixed; + + .bit-ios & { + top: 0; + padding-top: calc(env(safe-area-inset-top)); + } + + &.closed { + display: none; + } + } +} + +.menu-overlay { + inset: 0; + z-index: 2; + width: 100%; + height: 100%; + position: fixed; + min-height: 100vh; + background-color: rgba(0, 0, 0, 0.5); + + @include gt-sm { + display: none; + } +} + +::deep { + .panel { + width: auto; + display: flex; + padding: 0.5rem; + min-height: 100%; + flex-direction: column; + background-color: $bit-color-background-secondary; + + @include lt-md { + padding: 1rem; + } + } + + .toggle-btn { + @include lt-md { + display: none; + } + } + + .persona { + width: 100%; + } + + a { + text-decoration: none; + } +} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/SignOutConfirmModal.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/SignOutConfirmModal.razor similarity index 92% rename from src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/SignOutConfirmModal.razor rename to src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/SignOutConfirmModal.razor index b1e8cc2e3a..220b34a602 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/SignOutConfirmModal.razor +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/SignOutConfirmModal.razor @@ -7,7 +7,7 @@ @Localizer[nameof(AppStrings.SignOut)] @Localizer[nameof(AppStrings.SignOutPrompt)] - + @Localizer[nameof(AppStrings.SignOut)] diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/SignOutConfirmModal.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/SignOutConfirmModal.razor.cs similarity index 90% rename from src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/SignOutConfirmModal.razor.cs rename to src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/SignOutConfirmModal.razor.cs index 97a3697a84..d46fc83ef2 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/SignOutConfirmModal.razor.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/SignOutConfirmModal.razor.cs @@ -1,4 +1,4 @@ -namespace Boilerplate.Client.Core.Components.Layout; +namespace Boilerplate.Client.Core.Components.Layout.Main; public partial class SignOutConfirmModal { diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/SignOutConfirmModal.razor.scss b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/SignOutConfirmModal.razor.scss similarity index 83% rename from src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/SignOutConfirmModal.razor.scss rename to src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/SignOutConfirmModal.razor.scss index 36d6798ca4..c641370600 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/SignOutConfirmModal.razor.scss +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/SignOutConfirmModal.razor.scss @@ -1,5 +1,5 @@ -@import '../../Styles/abstracts/_functions.scss'; -@import '../../Styles/abstracts/_media-queries.scss'; +@import '../../../Styles/abstracts/_functions.scss'; +@import '../../../Styles/abstracts/_media-queries.scss'; .modal-body { display: flex; diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/UserMenu.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/UserMenu.razor new file mode 100644 index 0000000000..21db413f31 --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/UserMenu.razor @@ -0,0 +1,90 @@ +@inherits AppComponentBase + +@{ + var imageUrl = user.ProfileImageName is null ? null : $"{profileImageUrl}&file={user.ProfileImageName}"; +} + + + + + + + + + @if (showCultures is false) + { + + + + @Localizer[nameof(AppStrings.Edit)] + + + + + @Localizer[nameof(AppStrings.ProfileTitle)] + + + + @Localizer[nameof(AppStrings.Language)] + + + + + + + @(currentTheme == AppThemeType.Light ? Localizer[nameof(AppStrings.Light)] : Localizer[nameof(AppStrings.Dark)]) + + + + + @Localizer[nameof(AppStrings.Settings)] + + + @Localizer[nameof(AppStrings.SignOut)] + + + } + else + { + + + @Localizer[nameof(AppStrings.SelectLanguage)] + + + + + + + + @item.Text + + + + + + } + + + + + \ No newline at end of file diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/UserMenu.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/UserMenu.razor.cs new file mode 100644 index 0000000000..897e076ebd --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/Main/UserMenu.razor.cs @@ -0,0 +1,75 @@ +using Boilerplate.Shared.Dtos.Identity; + +namespace Boilerplate.Client.Core.Components.Layout.Main; + +public partial class UserMenu +{ + private bool isOpen; + private bool showSignOut; + private bool showCultures; + private UserDto user = new(); + private string? currentCulture; + private string? profileImageUrl; + private Action unsubscribeUerDataUpdated = default!; + private BitChoiceGroupItem[] cultures = default!; + + [AutoInject] private Cookie cookie = default!; + [AutoInject] private CultureInfoManager cultureInfoManager = default!; + [AutoInject] private IThemeService themeService { get; set; } = default!; + [AutoInject] private ICultureService cultureService { get; set; } = default!; + + + [CascadingParameter] private BitDir? currentDir { get; set; } + [CascadingParameter(Name = Parameters.CurrentTheme)] private AppThemeType? currentTheme { get; set; } + + + protected override async Task OnInitAsync() + { + if (CultureInfoManager.MultilingualEnabled) + { + cultures = CultureInfoManager.SupportedCultures + .Select(sc => new BitChoiceGroupItem { Value = sc.Culture.Name, Text = sc.DisplayName }) + .ToArray(); + } + + unsubscribeUerDataUpdated = PubSubService.Subscribe(PubSubMessages.USER_DATA_UPDATED, async payload => + { + if (payload is null) return; + + user = (UserDto)payload; + + await InvokeAsync(StateHasChanged); + }); + + user = (await PrerenderStateService.GetValue(() => HttpClient.GetFromJsonAsync("api/User/GetCurrentUser", AppJsonContext.Default.UserDto, CurrentCancellationToken)))!; + + var serverAddress = Configuration.GetServerAddress(); + var access_token = await PrerenderStateService.GetValue(() => AuthTokenProvider.GetAccessTokenAsync()); + profileImageUrl = $"{serverAddress}/api/Attachment/GetProfileImage?access_token={access_token}"; + + await base.OnInitAsync(); + } + + private async Task OnCultureChanged(string? cultureName) + { + currentCulture = cultureName; + await cultureService.ChangeCulture(cultureName); + } + + private async Task ToggleTheme() + { + await themeService.ToggleTheme(); + } + + private async Task GoToProfile() + { + NavigationManager.NavigateTo(Urls.ProfilePage); + } + + protected override async ValueTask DisposeAsync(bool disposing) + { + await base.DisposeAsync(disposing); + + unsubscribeUerDataUpdated(); + } +} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/MainLayout.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/MainLayout.razor deleted file mode 100644 index bb7abc679c..0000000000 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/MainLayout.razor +++ /dev/null @@ -1,25 +0,0 @@ -@inherits LayoutComponentBase - -Boilerplate - - - - - - - @if (isUserAuthenticated) - { - - } - - - @Body - - - - - - - - - \ No newline at end of file diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/NavMenu.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/NavMenu.razor deleted file mode 100644 index 005e59b074..0000000000 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/NavMenu.razor +++ /dev/null @@ -1,35 +0,0 @@ -@inherits AppComponentBase; - -@if (IsMenuOpen) -{ - -} - - - - @{ - var imageUrl = user.ProfileImageName is null ? null : $"{profileImageUrl}&file={user.ProfileImageName}"; - } - - - @Localizer[nameof(AppStrings.Edit)] - - - - - @Localizer[nameof(AppStrings.SignOut)] - - - - - - - \ No newline at end of file diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/NavMenu.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/NavMenu.razor.cs deleted file mode 100644 index b2b6fc8dc9..0000000000 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/NavMenu.razor.cs +++ /dev/null @@ -1,149 +0,0 @@ -//+:cnd:noEmit -using Boilerplate.Shared.Dtos.Identity; - -namespace Boilerplate.Client.Core.Components.Layout; - -public partial class NavMenu -{ - private bool disposed; - private bool isSignOutModalOpen; - private string? profileImageUrl; - private UserDto user = new(); - private List navItems = []; - private Action unsubscribe = default!; - - [AutoInject] private NavigationManager navManager = default!; - - [Parameter] public bool IsMenuOpen { get; set; } - - [Parameter] public EventCallback IsMenuOpenChanged { get; set; } - - protected override async Task OnInitAsync() - { - navItems = - [ - new() - { - Text = Localizer[nameof(AppStrings.Home)], - IconName = BitIconName.Home, - Url = Urls.HomePage, - }, - //#if (sample == "Admin") - new() - { - Text = Localizer[nameof(AppStrings.ProductCategory)], - IconName = BitIconName.Product, - IsExpanded = true, - ChildItems = - [ - new() { - Text = Localizer[nameof(AppStrings.Dashboard)], - Url = Urls.DashboardPage, - }, - new() { - Text = Localizer[nameof(AppStrings.Products)], - Url = Urls.ProductsPage, - }, - new() { - Text = Localizer[nameof(AppStrings.Categories)], - Url = Urls.CategoriesPage, - }, - ] - }, - //#elif (sample == "Todo") - new() - { - Text = Localizer[nameof(AppStrings.TodoTitle)], - IconName = BitIconName.ToDoLogoOutline, - Url = Urls.TodoPage, - }, - //#endif - new() - { - Text = Localizer[nameof(AppStrings.ProfileTitle)], - IconName = BitIconName.EditContact, - Url = Urls.ProfilePage, - }, - //#if (offlineDb == true) - new() - { - Text = Localizer[nameof(AppStrings.OfflineEditProfileTitle)], - IconName = BitIconName.EditContact, - Url = Urls.OfflineEditProfilePage, - }, - //#endif - new() - { - Text = Localizer[nameof(AppStrings.TermsTitle)], - IconName = BitIconName.EntityExtraction, - Url = Urls.TermsPage, - } - ]; - - if (AppPlatform.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 = Urls.AboutPage, - }); - } - - unsubscribe = PubSubService.Subscribe(PubSubMessages.USER_DATA_UPDATED, async payload => - { - if (payload is null) return; - - user = (UserDto)payload; - - await InvokeAsync(StateHasChanged); - }); - - user = (await PrerenderStateService.GetValue(() => HttpClient.GetFromJsonAsync("api/User/GetCurrentUser", AppJsonContext.Default.UserDto, CurrentCancellationToken)))!; - - var serverAddress = Configuration.GetServerAddress(); - var access_token = await PrerenderStateService.GetValue(() => AuthTokenProvider.GetAccessTokenAsync()); - profileImageUrl = $"{serverAddress}/api/Attachment/GetProfileImage?access_token={access_token}"; - } - - private async Task DoSignOut() - { - isSignOutModalOpen = true; - - await CloseMenu(); - } - - private async Task GoToProfile() - { - await CloseMenu(); - navManager.NavigateTo(Urls.ProfilePage); - } - - private async Task HandleNavItemClick(BitNavItem item) - { - if (string.IsNullOrEmpty(item.Url)) return; - - await CloseMenu(); - } - - private async Task CloseMenu() - { - IsMenuOpen = false; - await IsMenuOpenChanged.InvokeAsync(false); - } - - protected override async ValueTask DisposeAsync(bool disposing) - { - await base.DisposeAsync(disposing); - - if (disposed || disposing is false) return; - - unsubscribe?.Invoke(); - - disposed = true; - } -} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/NavMenu.razor.scss b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/NavMenu.razor.scss deleted file mode 100644 index 3e63602336..0000000000 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/NavMenu.razor.scss +++ /dev/null @@ -1,80 +0,0 @@ -@import '../../Styles/abstracts/_vars.scss'; -@import '../../Styles/abstracts/_functions.scss'; -@import '../../Styles/abstracts/_media-queries.scss'; -@import '../../Styles/abstracts/_bit-css-variables.scss'; - -.main-container { - height: 100%; - display: flex; - position: sticky; - min-height: 100%; - max-height: 100vh; - overflow: hidden auto; - flex-flow: column nowrap; - justify-content: flex-start; - min-width: rem2($navMenuWidth); - background-color: $bit-color-background-primary; - top: $headerHeight; - padding-bottom: calc($headerHeight + $footerHeight); - - .bit-ios & { - top: calc($headerHeight + env(safe-area-inset-top)); - } - - &::-webkit-scrollbar { - width: 0; - } -} - -@include lt-lg { - .main-container { - z-index: 3; - position: fixed; - padding-bottom: 0; - - .bit-ios & { - top: 0; - padding-top: calc(env(safe-area-inset-top)); - } - } - - .main-container--closed { - display: none; - } -} - -.top-container { - width: 100%; - display: flex; - padding: rem2(16px); - flex-flow: column nowrap; - margin-bottom: rem2(20px); - justify-content: flex-start; - border-bottom: rem2(1px) solid $bit-color-border-secondary; -} - -.menu-overlay { - top: 0; - z-index: 2; - width: 100%; - height: 100%; - position: fixed; - min-height: 100vh; - inset-inline-start: 0; - background-color: rgba(0, 0, 0, 0.5); - - @include gt-md { - display: none; - } -} - -::deep { - a { - text-decoration: none; - } - - .persona { - margin: auto; - max-width: rem2(calc($navMenuWidth - 20px)); - } -} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/RootLayout.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/RootLayout.razor new file mode 100644 index 0000000000..9534456d16 --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/RootLayout.razor @@ -0,0 +1,20 @@ +@inherits LayoutComponentBase + + + + + + + + + + @Body + + + + + + + + + \ No newline at end of file diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/MainLayout.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/RootLayout.razor.cs similarity index 50% rename from src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/MainLayout.razor.cs rename to src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/RootLayout.razor.cs index 8f3c081f46..13d9d972fa 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/MainLayout.razor.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/RootLayout.razor.cs @@ -1,39 +1,54 @@ -using Microsoft.AspNetCore.Components.Web; +using Boilerplate.Client.Core.Components.Layout.Identity; +using Boilerplate.Client.Core.Components.Layout.Main; namespace Boilerplate.Client.Core.Components.Layout; -public partial class MainLayout : IDisposable +public partial class RootLayout : IDisposable { private bool disposed; - private bool isMenuOpen; private BitDir? currentDir; - private bool isUserAuthenticated; - private ErrorBoundary errorBoundaryRef = default!; + private string? currentUrl; + private bool? isAuthenticated; + private bool? isAnonymousPage; + private AppThemeType? currentTheme; + private Action unsubscribeThemeChange = default!; private Action unsubscribeCultureChange = default!; + + [AutoInject] private IThemeService themeService = default!; [AutoInject] private IPubSubService pubSubService = default!; [AutoInject] private AuthenticationManager authManager = default!; [AutoInject] private IExceptionHandler exceptionHandler = default!; + [AutoInject] private NavigationManager navigationManager = default!; [AutoInject] private IPrerenderStateService prerenderStateService = default!; + [CascadingParameter] public Task AuthenticationStateTask { get; set; } = default!; + protected override async Task OnInitializedAsync() { try { + navigationManager.LocationChanged += NavigationManagerLocationChanged; authManager.AuthenticationStateChanged += AuthenticationStateChanged; - - isUserAuthenticated = await prerenderStateService.GetValue(async () => (await AuthenticationStateTask).User.IsAuthenticated()); - unsubscribeCultureChange = pubSubService.Subscribe(PubSubMessages.CULTURE_CHANGED, async _ => { SetCurrentDir(); - StateHasChanged(); }); + unsubscribeThemeChange = pubSubService.Subscribe(PubSubMessages.THEME_CHANGED, async payload => + { + if (payload is null) return; + currentTheme = (AppThemeType)payload; + StateHasChanged(); + }); + + isAuthenticated = await prerenderStateService.GetValue(async () => (await AuthenticationStateTask).User.IsAuthenticated()); SetCurrentDir(); + SetCurrentUrl(); + currentTheme = await themeService.GetCurrentTheme(); await base.OnInitializedAsync(); } @@ -52,23 +67,32 @@ protected override void OnParametersSet() base.OnParametersSet(); } - private void SetCurrentDir() + + private Type GetCurrentLayout() { - var currentCulture = CultureInfo.CurrentUICulture; + return isAuthenticated is null + ? typeof(EmptyLayout) + : isAuthenticated is true + ? typeof(MainLayout) + : typeof(IdentityLayout); + } - currentDir = currentCulture.TextInfo.IsRightToLeft ? BitDir.Rtl : null; + private void SetCurrentDir() + { + currentDir = CultureInfo.CurrentUICulture.TextInfo.IsRightToLeft ? BitDir.Rtl : null; } - private void ToggleMenuHandler() + private void SetCurrentUrl() { - isMenuOpen = !isMenuOpen; + currentUrl = navigationManager.Uri.Replace(navigationManager.BaseUri, "/", StringComparison.InvariantCultureIgnoreCase); + isAnonymousPage = Urls.AnonymousPages.Any(p => currentUrl == p); } private async void AuthenticationStateChanged(Task task) { try { - isUserAuthenticated = (await task).User.IsAuthenticated(); + isAuthenticated = (await task).User.IsAuthenticated(); } catch (Exception ex) { @@ -80,6 +104,13 @@ private async void AuthenticationStateChanged(Task task) } } + private void NavigationManagerLocationChanged(object? sender, Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs e) + { + SetCurrentUrl(); + StateHasChanged(); + } + + public void Dispose() { Dispose(true); @@ -91,9 +122,12 @@ protected virtual void Dispose(bool disposing) { if (disposed || disposing is false) return; + navigationManager.LocationChanged -= NavigationManagerLocationChanged; + authManager.AuthenticationStateChanged -= AuthenticationStateChanged; - unsubscribeCultureChange?.Invoke(); + unsubscribeThemeChange(); + unsubscribeCultureChange(); disposed = true; } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/MainLayout.razor.scss b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/RootLayout.razor.scss similarity index 75% rename from src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/MainLayout.razor.scss rename to src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/RootLayout.razor.scss index 63c30e4f1f..b60b60990d 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/MainLayout.razor.scss +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/RootLayout.razor.scss @@ -6,39 +6,7 @@ .layout { width: 100%; height: 100%; -} - -main { - width: 100%; - display: flex; - min-height: 100vh; - position: relative; - box-sizing: border-box; - justify-content: flex-start; - padding-bottom: $footerHeight; background-color: $bit-color-background-primary; - padding-top: $headerHeight; - - .bit-ios & { - padding-top: calc($headerHeight + env(safe-area-inset-top)); - padding-bottom: calc($footerHeight + env(safe-area-inset-bottom)); - } -} - -.main-content { - flex-grow: 1; - display: flex; - padding: 1rem; - min-height: 100%; - flex-flow: column; - align-items: center; - justify-content: center; - width: calc(100% - $navMenuWidth); - background-color: $bit-color-background-secondary; - - @include lt-lg { - width: 100%; - } } .status-bar { @@ -59,6 +27,17 @@ main { color: transparent; height: env(safe-area-inset-top); } + + .bit-windows &, .bit-macos & { + display: flex; + height: var(--bit-status-bar-height); + } +} + +.bit-ios { + ::deep .form-message-bar { + scroll-margin-top: calc($headerHeight + env(safe-area-inset-top) + 1px); + } } ::deep { @@ -129,16 +108,10 @@ main { inset-block-start: 0; inset-inline-start: 0; border-radius: rem2(4px) rem2(4px) 0 0; - scroll-margin-top: calc($headerHeight + 1px); + scroll-margin-top: calc($headerHeight + var(--bit-status-bar-height) + 1px); } -} -.bit-ios { - ::deep .form-message-bar { - scroll-margin-top: calc($headerHeight + env(safe-area-inset-top) + 1px); + .loading-container { + text-align: center; } } - -::deep .loading-container { - text-align: center; -} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/LoadingComponent.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/LoadingComponent.razor similarity index 99% rename from src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/LoadingComponent.razor rename to src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/LoadingComponent.razor index 3d77d75ff4..22c2d90d6a 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/LoadingComponent.razor +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/LoadingComponent.razor @@ -1,15 +1,16 @@