feat : add version 0.19.22.38 , fix zarinpall issue

release
Amir Hossein Khademi 2024-04-03 10:40:42 +03:30
parent e86aa9966e
commit 08c116aece
18 changed files with 404 additions and 57 deletions

View File

@ -65,13 +65,12 @@
<MudNavGroup Title="فروشگاه من" Expanded="false" <MudNavGroup Title="فروشگاه من" Expanded="false"
Icon="@Icons.Material.Outlined.Settings"> Icon="@Icons.Material.Outlined.Settings">
<MudNavLink Href="management/faqs" Icon="@Icons.Material.Filled.ManageAccounts">سوالات متداول</MudNavLink>
<MudNavLink Href="faqs" Icon="@Icons.Material.Filled.ManageAccounts">سوالات متداول</MudNavLink>
</MudNavGroup> </MudNavGroup>
<MudNavGroup Title="تنظیماتـــ" Expanded="false" <MudNavGroup Title="تنظیماتـــ" Expanded="false"
Icon="@Icons.Material.Outlined.Settings"> Icon="@Icons.Material.Outlined.Settings">
<MudNavLink Href="management/shop" Icon="@Icons.Material.Filled.Shop2">فروشگاه</MudNavLink>
<MudNavLink Href="users" Icon="@Icons.Material.Filled.ManageAccounts">نقش ها و کاربران</MudNavLink> <MudNavLink Href="users" Icon="@Icons.Material.Filled.ManageAccounts">نقش ها و کاربران</MudNavLink>
</MudNavGroup> </MudNavGroup>
</MudNavMenu> </MudNavMenu>

View File

@ -6,7 +6,7 @@ public static class StorageFileExtension
{ {
public static string GetLink(this StorageFileSDto file) public static string GetLink(this StorageFileSDto file)
{ {
var link = $"https://storage.vesmook.com/{file.FileLocation}"; var link = $"https://storage.vesmeh.com/{file.FileLocation}";
return link; return link;
} }
} }

View File

@ -3,8 +3,8 @@
public static class Address public static class Address
{ {
#if DEBUG #if DEBUG
//public static string BaseAddress = "http://localhost:32770/api"; public static string BaseAddress = "http://localhost:32770/api";
public static string BaseAddress = "https://api.vesmeh.com/api"; //public static string BaseAddress = "https://api.vesmeh.com/api";
#else #else
public static string BaseAddress = "https://api.vesmeh.com/api"; public static string BaseAddress = "https://api.vesmeh.com/api";
#endif #endif
@ -25,4 +25,6 @@ public static class Address
public static string ScraperController => $"{BaseAddress}/scraper"; public static string ScraperController => $"{BaseAddress}/scraper";
public static string NewsletterMemberController => $"{BaseAddress}/newsletter/member"; public static string NewsletterMemberController => $"{BaseAddress}/newsletter/member";
public static string DashboardController => $"{BaseAddress}/dashboard"; public static string DashboardController => $"{BaseAddress}/dashboard";
public static string SettingController => $"{BaseAddress}/setting";
public static string DistrictController => $"{BaseAddress}/district";
} }

View File

@ -5,8 +5,8 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest> <ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest>
<AssemblyVersion>0.17.20.30</AssemblyVersion> <AssemblyVersion>0.17.21.31</AssemblyVersion>
<FileVersion>0.17.20.30</FileVersion> <FileVersion>0.17.21.31</FileVersion>
<AssemblyName>$(MSBuildProjectName)</AssemblyName> <AssemblyName>$(MSBuildProjectName)</AssemblyName>
</PropertyGroup> </PropertyGroup>
@ -85,6 +85,8 @@
<Using Include="NetinaShop.Domain.Entities.ProductCategories" /> <Using Include="NetinaShop.Domain.Entities.ProductCategories" />
<Using Include="NetinaShop.Domain.Entities.Products" /> <Using Include="NetinaShop.Domain.Entities.Products" />
<Using Include="NetinaShop.Domain.Enums" /> <Using Include="NetinaShop.Domain.Enums" />
<Using Include="NetinaShop.Domain.MartenEntities.Pages" />
<Using Include="NetinaShop.Domain.Models.Districts" />
<Using Include="Newtonsoft.Json" /> <Using Include="Newtonsoft.Json" />
<Using Include="Refit" /> <Using Include="Refit" />
<Using Include="System.Collections.ObjectModel" /> <Using Include="System.Collections.ObjectModel" />

View File

@ -1,4 +1,4 @@
@page "/faqs" @page "/management/faqs"
@attribute [Microsoft.AspNetCore.Authorization.Authorize] @attribute [Microsoft.AspNetCore.Authorization.Authorize]
@inject IDialogService DialogService @inject IDialogService DialogService

View File

@ -1,6 +1,4 @@
using NetinaShop.Domain.Entities.Pages; namespace NetinaShop.AdminPanel.PWA.Pages;
namespace NetinaShop.AdminPanel.PWA.Pages;
public class FaqManagementPageViewModel : BaseViewModel<FAQPage> public class FaqManagementPageViewModel : BaseViewModel<FAQPage>
{ {

View File

@ -0,0 +1,148 @@
@page "/management/shop"
@using NetinaShop.Domain.Models.Districts
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@inject IDialogService DialogService
@inject NavigationManager NavigationManager
@inject ISnackbar Snackbar
@inject IUserUtility UserUtility
@inject IRestWrapper RestWrapper
<MudStack class="w-full p-8 h-screen bg-[--mud-palette-background-grey]">
<MudGrid>
<MudItem xs="12">
<MudPaper class="px-5 py-5">
<MudStack Row="true">
<MudStack class="mb-5 mx-2">
<MudText Typo="Typo.h4">فروشـــــگاه من</MudText>
<MudText Typo="Typo.caption">شما می توانید اطلاعات فروشگاه خود را ویرایش نمایید</MudText>
</MudStack>
<MudSpacer/>
<BaseButtonUi Size="Size.Large"
OnClickCallback="ViewModel.SubmitShopSettingAsync"
class="mt-2 mb-8 w-64 rounded-md"
IsProcessing="@ViewModel.IsProcessing"
Icon="@Icons.Material.Outlined.Check"
Content="ثبتـــ اطلاعات" Variant="Variant.Filled" Color="Color.Success" />
</MudStack>
<MudGrid>
<MudItem xs="12" sm="4">
<MudTextField T="string" @bind-Value="@ViewModel.ShopSetting.Name" Variant="Variant.Outlined" Label="نام فروشاه"></MudTextField>
</MudItem>
<MudItem xs="12" sm="4">
<MudTextField T="string" @bind-Value="@ViewModel.ShopSetting.Slogan" Variant="Variant.Outlined" Label="شعار فروشگاه"></MudTextField>
</MudItem>
<MudItem xs="12" sm="4">
<MudTextField T="string" @bind-Value="@ViewModel.ShopSetting.Description" Variant="Variant.Outlined" Label="توضیحات"></MudTextField>
</MudItem>
<MudItem xs="12" sm="5">
<MudTextField T="double" @bind-Value="@ViewModel.ShopSetting.TaxesFee" Variant="Variant.Outlined" Label="مقدار مالیات"></MudTextField>
</MudItem>
<MudItem xs="12" sm="5">
<MudTextField T="double" @bind-Value="@ViewModel.ShopSetting.ServiceFee" Variant="Variant.Outlined" Label="مقدار سرویس"></MudTextField>
</MudItem>
<MudItem xs="12" sm="2">
<MudSelect T="bool" Variant="Variant.Outlined" Label="سرویس درصدی می باشد ؟">
<MudSelectItem T="bool" Value="true">بلی</MudSelectItem>
<MudSelectItem T="bool" Value="true">خیر</MudSelectItem>
</MudSelect>
</MudItem>
<MudItem xs="12" sm="3">
<MudAutocomplete Required="true" ToStringFunc="dto => dto.Name" @bind-Value="@ViewModel.SelectedProvince"
SearchFunc="@ViewModel.SearchProvinceAsync"
T="Province"
Label="استان"
Variant="Variant.Outlined">
<ProgressIndicatorInPopoverTemplate>
<MudList Clickable="false">
<MudListItem>
<div class="flex flex-row w-full mx-auto">
<MudProgressCircular class="my-auto mr-1 -ml-4" Size="Size.Small" Indeterminate="true" />
<p class="font-bold my-1 mx-auto text-md">منتظر بمانید</p>
</div>
</MudListItem>
</MudList>
</ProgressIndicatorInPopoverTemplate>
<ItemTemplate Context="e">
<p>@e.Name</p>
</ItemTemplate>
</MudAutocomplete>
</MudItem>
<MudItem xs="12" sm="3">
<MudAutocomplete Required="true" ToStringFunc="dto => dto.Name" @bind-Value="@ViewModel.SelectedCity"
SearchFunc="@ViewModel.SearchCityAsync"
T="City"
Label="شهر"
Variant="Variant.Outlined">
<ProgressIndicatorInPopoverTemplate>
<MudList Clickable="false">
<MudListItem>
<div class="flex flex-row w-full mx-auto">
<MudProgressCircular class="my-auto mr-1 -ml-4" Size="Size.Small" Indeterminate="true" />
<p class="font-bold my-1 mx-auto text-md">منتظر بمانید</p>
</div>
</MudListItem>
</MudList>
</ProgressIndicatorInPopoverTemplate>
<ItemTemplate Context="e">
<p>@e.Name</p>
</ItemTemplate>
</MudAutocomplete>
</MudItem>
<MudItem xs="12" sm="6">
<MudTextField T="string" @bind-Value="@ViewModel.ShopSetting.Address" Variant="Variant.Outlined" Label="آدرس فروشگاه"></MudTextField>
</MudItem>
<MudItem xs="12" sm="6">
<MudTextField T="string" @bind-Value="@ViewModel.ShopSetting.ENamad" Variant="Variant.Outlined" Label="آدرس نماد اعتماد"></MudTextField>
</MudItem>
</MudGrid>
</MudPaper>
<MudPaper class="px-5 mt-8 py-5">
<MudStack Row="true">
<MudStack class="mb-5 mx-2">
<MudText Typo="Typo.h4">تنظیمات درگاه پرداخت</MudText>
<MudText Typo="Typo.caption">شما می توانید اطلاعات درگاه پرداخت خود را ویرایش نمایید</MudText>
</MudStack>
<MudSpacer />
<BaseButtonUi Size="Size.Large" class="mt-2 mb-8 w-64 rounded-md"
OnClickCallback="ViewModel.SubmitPaymentSettingAsync"
IsProcessing="@ViewModel.IsProcessing"
Icon="@Icons.Material.Outlined.Check" Content="ثبتـــ اطلاعات"
Variant="Variant.Filled" Color="Color.Success" />
</MudStack>
<MudGrid>
<MudItem xs="12" sm="6">
<MudTextField T="string" @bind-Value="@ViewModel.PaymentSetting.ZarinPalApiKey" Variant="Variant.Outlined" Label="کد مرچنت زرین پال"></MudTextField>
</MudItem>
<MudItem xs="12" sm="6">
<MudTextField T="string" @bind-Value="@ViewModel.PaymentSetting.Shaba" Variant="Variant.Outlined" Label="شماره شبا پشتیبانی"></MudTextField>
</MudItem>
</MudGrid>
</MudPaper>
</MudItem>
</MudGrid>
</MudStack>
@code
{
public ShopManagementPageViewModel ViewModel { get; set; }
protected override async Task OnInitializedAsync()
{
ViewModel = new ShopManagementPageViewModel(NavigationManager, Snackbar, UserUtility, RestWrapper, DialogService);
await ViewModel.InitializeAsync();
await base.OnInitializedAsync();
}
}

View File

@ -0,0 +1,182 @@
using NetinaShop.Domain.MartenEntities.Settings;
using NetinaShop.Domain.Models.Districts;
namespace NetinaShop.AdminPanel.PWA.Pages;
public class ShopManagementPageViewModel : BaseViewModel
{
private readonly NavigationManager _navigationManager;
private readonly ISnackbar _snackbar;
private readonly IUserUtility _userUtility;
private readonly IDialogService _dialogService;
private readonly IRestWrapper _restWrapper;
public ShopSetting ShopSetting { get; set; } = new ShopSetting();
public PaymentSetting PaymentSetting { get; set; } = new PaymentSetting();
public List<City> Cities = new List<City>();
public City SelectedCity = new City();
public List<Province> Provinces = new List<Province>();
public Province SelectedProvince = new Province();
public ShopManagementPageViewModel(NavigationManager navigationManager, ISnackbar snackbar, IUserUtility userUtility, IRestWrapper restWrapper, IDialogService dialogService)
{
_navigationManager = navigationManager;
_snackbar = snackbar;
_userUtility = userUtility;
_restWrapper = restWrapper;
_dialogService = dialogService;
}
public override async Task InitializeAsync()
{
try
{
IsProcessing = true;
var token = await _userUtility.GetBearerTokenAsync();
if (token == null)
throw new Exception("Token is null");
ShopSetting = await _restWrapper.SettingRestApi.GetSettingAsync<ShopSetting>("ShopSetting", token);
if (ShopSetting.CityId != 0)
SelectedCity = new City { Id = ShopSetting.CityId, Name = ShopSetting.City };
if (ShopSetting.ProvinceId != 0)
SelectedProvince = new Province { Id = ShopSetting.ProvinceId, Name = ShopSetting.Province };
PaymentSetting = await _restWrapper.SettingRestApi.GetSettingAsync<PaymentSetting>("PaymentSetting", token);
}
catch (ApiException e)
{
var exe = await e.GetContentAsAsync<ApiResult>();
_snackbar.Add(exe != null ? exe.Message : e.Content, Severity.Error);
}
catch (Exception ex)
{
_snackbar.Add(ex.Message, Severity.Error);
}
finally
{
IsProcessing = false;
}
await base.InitializeAsync();
}
public async Task<IEnumerable<City>> SearchCityAsync(string city)
{
try
{
if (SelectedProvince.Id == default)
throw new Exception("لطفا استان را انتخاب کنید");
Cities = await _restWrapper.DistrictApiRest.GetCitiesAsync(SelectedProvince.Id);
if (city.IsNullOrEmpty())
return Cities;
return Cities.Where(c => c.Name.Contains(city));
}
catch (ApiException ex)
{
var exe = await ex.GetContentAsAsync<ApiResult>();
if (exe != null)
_snackbar.Add(exe.Message, Severity.Error);
_snackbar.Add(ex.Content, Severity.Error);
return Cities;
}
catch (Exception e)
{
_snackbar.Add(e.Message, Severity.Error);
return Cities;
}
}
public async Task<IEnumerable<Province>> SearchProvinceAsync(string province)
{
try
{
Provinces = await _restWrapper.DistrictApiRest.GetProvincesAsync();
if (province.IsNullOrEmpty())
return Provinces;
return Provinces.Where(c => c.Name.Contains(province));
}
catch (ApiException ex)
{
var exe = await ex.GetContentAsAsync<ApiResult>();
if (exe != null)
_snackbar.Add(exe.Message, Severity.Error);
_snackbar.Add(ex.Content, Severity.Error);
return Provinces;
}
catch (Exception e)
{
_snackbar.Add(e.Message, Severity.Error);
return Provinces;
}
}
public async Task SubmitShopSettingAsync()
{
try
{
if (SelectedProvince.Id == default)
throw new Exception("لطفا استان را انتخاب کنید");
if (SelectedCity.Id == default)
throw new Exception("لطفا شهر را انتخاب نمایید");
ShopSetting.CityId = SelectedCity.Id;
ShopSetting.ProvinceId = SelectedProvince.Id;
ShopSetting.City = SelectedCity.Name;
ShopSetting.Province = SelectedProvince.Name;
var token = await _userUtility.GetBearerTokenAsync();
if (token == null) throw new Exception("Token is null");
await _restWrapper.SettingRestApi.PostSettingAsync("ShopSetting", ShopSetting, token);
_snackbar.Add("تنظیمات فروشگاه با موفقیت به روز شد", Severity.Success);
}
catch (ApiException ex)
{
var exe = await ex.GetContentAsAsync<ApiResult>();
_snackbar.Add(exe != null ? exe.Message : ex.Content, Severity.Error);
}
catch (Exception e)
{
_snackbar.Add(e.Message, Severity.Error);
}
finally
{
IsProcessing = false;
}
}
public async Task SubmitPaymentSettingAsync()
{
try
{
var token = await _userUtility.GetBearerTokenAsync();
if (token == null) throw new Exception("Token is null");
await _restWrapper.SettingRestApi.PostSettingAsync("PaymentSetting", PaymentSetting, token);
_snackbar.Add("تنظیمات فروشگاه با موفقیت به روز شد", Severity.Success);
}
catch (ApiException ex)
{
var exe = await ex.GetContentAsAsync<ApiResult>();
_snackbar.Add(exe != null ? exe.Message : ex.Content, Severity.Error);
}
catch (Exception e)
{
_snackbar.Add(e.Message, Severity.Error);
}
finally
{
IsProcessing = false;
}
}
}

View File

@ -1,4 +1,4 @@
@page "/users" @page "/setting/user"
@using NetinaShop.Domain.Entities.Users @using NetinaShop.Domain.Entities.Users
@attribute [Microsoft.AspNetCore.Authorization.Authorize] @attribute [Microsoft.AspNetCore.Authorization.Authorize]

View File

@ -0,0 +1,13 @@
namespace NetinaShop.AdminPanel.PWA.Services.RestServices;
public interface IDistrictApiRest
{
[Get("/city")]
public Task<List<City>> GetCitiesAsync();
[Get("/city")]
public Task<List<City>> GetCitiesAsync([Query]int provinceId);
[Get("/province")]
public Task<List<Province>> GetProvincesAsync();
}

View File

@ -4,10 +4,10 @@ public interface IPageRestApi
{ {
[Get("/type/{type}")] [Get("/type/{type}")]
Task<BasePageEntitySDto> ReadByType([Query] string type, [Header("Authorization")] string authorization); Task<BasePageSDto> ReadByType([Query] string type, [Header("Authorization")] string authorization);
[Get("/{id}")] [Get("/{id}")]
Task<BasePageEntitySDto> ReadById(Guid id, [Header("Authorization")] string authorization); Task<BasePageSDto> ReadById(Guid id, [Header("Authorization")] string authorization);
[Post("")] [Post("")]
Task CreatePage([Body] PageActionRequestDto request, [Header("Authorization")] string authorization); Task CreatePage([Body] PageActionRequestDto request, [Header("Authorization")] string authorization);

View File

@ -21,4 +21,6 @@ public interface IRestWrapper
public IPageRestApi PageRestApi { get; } public IPageRestApi PageRestApi { get; }
public IScraperRestApi ScraperRestApi { get; } public IScraperRestApi ScraperRestApi { get; }
public IDashboardApiRest DashboardApiRest { get; } public IDashboardApiRest DashboardApiRest { get; }
public ISettingRestApi SettingRestApi { get; }
public IDistrictApiRest DistrictApiRest { get; }
} }

View File

@ -0,0 +1,10 @@
namespace NetinaShop.AdminPanel.PWA.Services.RestServices;
public interface ISettingRestApi
{
[Get("/{settingName}")]
public Task<TSetting> GetSettingAsync<TSetting>(string settingName, [Header("Authorization")] string authorization) where TSetting : class;
[Post("/{settingName}")]
public Task PostSettingAsync<TSetting>(string settingName,[Body]TSetting setting , [Header("Authorization")]string authorization) where TSetting : class;
}

View File

@ -32,6 +32,7 @@ public class RestWrapper : IRestWrapper
public IPaymentRestApi PaymentRestApi => RestService.For<IPaymentRestApi>(Address.PaymentController, setting); public IPaymentRestApi PaymentRestApi => RestService.For<IPaymentRestApi>(Address.PaymentController, setting);
public IPageRestApi PageRestApi => RestService.For<IPageRestApi>(Address.PageController, setting); public IPageRestApi PageRestApi => RestService.For<IPageRestApi>(Address.PageController, setting);
public IScraperRestApi ScraperRestApi => RestService.For<IScraperRestApi>(Address.ScraperController, setting); public IScraperRestApi ScraperRestApi => RestService.For<IScraperRestApi>(Address.ScraperController, setting);
public ISettingRestApi SettingRestApi => RestService.For<ISettingRestApi>(Address.SettingController, setting);
public IDashboardApiRest DashboardApiRest => RestService.For<IDashboardApiRest>(Address.DashboardController, setting); public IDashboardApiRest DashboardApiRest => RestService.For<IDashboardApiRest>(Address.DashboardController, setting);
public IDistrictApiRest DistrictApiRest => RestService.For<IDistrictApiRest>(Address.DistrictController, setting);
} }

View File

@ -1,37 +0,0 @@
namespace NetinaShop.AdminPanel.PWA.Utilities.Models;
public class CustomEventHelper
{
private readonly Func<EventArgs, Task> _callback;
public CustomEventHelper(Func<EventArgs, Task> callback)
{
_callback = callback;
}
[JSInvokable]
public Task OnCustomEvent(EventArgs args) => _callback(args);
}
public class CustomEventInterop : IDisposable
{
private readonly IJSRuntime _jsRuntime;
private DotNetObjectReference<CustomEventHelper> Reference;
public CustomEventInterop(IJSRuntime jsRuntime)
{
_jsRuntime = jsRuntime;
}
public ValueTask<string> SetupCustomEventCallback(Func<EventArgs, Task> callback)
{
Reference = DotNetObjectReference.Create(new CustomEventHelper(callback));
// addCustomEventListener will be a js function we create later
return _jsRuntime.InvokeAsync<string>("addCustomEventListener", Reference);
}
public void Dispose()
{
Reference?.Dispose();
}
}

View File

@ -1,6 +1,6 @@
{ {
"version": "0.17.19.26", "version": "0.19.22.38",
"versionNumber": 0.171926, "versionNumber": 0.192238,
"versionName": "چرتکه", "versionName": "چرتکه",
"description": "", "description": "",
"features": [ "features": [
@ -8,10 +8,14 @@
"تکمیل پروسه سفارش گیری", "تکمیل پروسه سفارش گیری",
"قابلیت افزودن تصویر به برند ها", "قابلیت افزودن تصویر به برند ها",
"قابلیت افزودن تصویر به دسته بندی محصولات", "قابلیت افزودن تصویر به دسته بندی محصولات",
"تغییر دیالوگ پرسشی" "تغییر دیالوگ پرسشی",
"افزودن تنظیمات درگاه",
"افزودن بخش تنظیمات فروشگاه"
], ],
"bugFixes": [ "bugFixes": [
"حل مشکلات امنیتی", "حل مشکلات امنیتی",
"رفع مشکلات رسپانسیو" "رفع مشکلات رسپانسیو",
"رفع مشکل دریافت تصاویر",
"رفع مشکل عدم اتصال به درگاه"
] ]
} }

View File

@ -1072,6 +1072,10 @@ input:checked + .toggle-bg {
margin-left: 2.5rem; margin-left: 2.5rem;
margin-right: 2.5rem; margin-right: 2.5rem;
} }
.mx-2 {
margin-left: 0.5rem;
margin-right: 0.5rem;
}
.mx-3 { .mx-3 {
margin-left: 0.75rem; margin-left: 0.75rem;
margin-right: 0.75rem; margin-right: 0.75rem;
@ -1674,6 +1678,10 @@ input:checked + .toggle-bg {
padding-top: 0.75rem; padding-top: 0.75rem;
padding-bottom: 0.75rem; padding-bottom: 0.75rem;
} }
.py-5 {
padding-top: 1.25rem;
padding-bottom: 1.25rem;
}
.py-8 { .py-8 {
padding-top: 2rem; padding-top: 2rem;
padding-bottom: 2rem; padding-bottom: 2rem;

View File

@ -1150,6 +1150,11 @@ input:checked + .toggle-bg {
margin-right: 2.5rem; margin-right: 2.5rem;
} }
.mx-2 {
margin-left: 0.5rem;
margin-right: 0.5rem;
}
.mx-3 { .mx-3 {
margin-left: 0.75rem; margin-left: 0.75rem;
margin-right: 0.75rem; margin-right: 0.75rem;
@ -1190,6 +1195,11 @@ input:checked + .toggle-bg {
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.my-5 {
margin-top: 1.25rem;
margin-bottom: 1.25rem;
}
.my-auto { .my-auto {
margin-top: auto; margin-top: auto;
margin-bottom: auto; margin-bottom: auto;
@ -1932,6 +1942,11 @@ input:checked + .toggle-bg {
padding-bottom: 0.75rem; padding-bottom: 0.75rem;
} }
.py-5 {
padding-top: 1.25rem;
padding-bottom: 1.25rem;
}
.py-8 { .py-8 {
padding-top: 2rem; padding-top: 2rem;
padding-bottom: 2rem; padding-bottom: 2rem;