Update
This commit is contained in:
25
src/BackOffice/BackOffice.sln
Normal file
25
src/BackOffice/BackOffice.sln
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.10.35027.167
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BackOffice", "BackOffice\BackOffice.csproj", "{CD6DF182-2FBE-4C17-8B2C-CC25F488A8C0}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{CD6DF182-2FBE-4C17-8B2C-CC25F488A8C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{CD6DF182-2FBE-4C17-8B2C-CC25F488A8C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CD6DF182-2FBE-4C17-8B2C-CC25F488A8C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CD6DF182-2FBE-4C17-8B2C-CC25F488A8C0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {43173B65-5703-4FEB-B360-0D88A0F9CA16}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
30
src/BackOffice/BackOffice/App.razor
Normal file
30
src/BackOffice/BackOffice/App.razor
Normal file
@@ -0,0 +1,30 @@
|
||||
@using BackOffice.Shared
|
||||
@using Microsoft.AspNetCore.Components.Authorization
|
||||
<CascadingAuthenticationState>
|
||||
<Router AppAssembly="@typeof(App).Assembly">
|
||||
<Found Context="routeData">
|
||||
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
|
||||
<NotAuthorized>
|
||||
@{
|
||||
Task.Delay(0).ContinueWith(p =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Navigation.NavigateTo(RouteConstance.Login);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
});
|
||||
}
|
||||
</NotAuthorized>
|
||||
</AuthorizeRouteView>
|
||||
</Found>
|
||||
<NotFound>
|
||||
<PageTitle>یافت نشد</PageTitle>
|
||||
<LayoutView Layout="@typeof(MainLayout)">
|
||||
<p role="alert">آدرس مورد نظر یافت نشد</p>
|
||||
</LayoutView>
|
||||
</NotFound>
|
||||
</Router>
|
||||
</CascadingAuthenticationState>
|
||||
45
src/BackOffice/BackOffice/BackOffice.csproj
Normal file
45
src/BackOffice/BackOffice/BackOffice.csproj
Normal file
@@ -0,0 +1,45 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest>
|
||||
<BlazorCacheBootResources>false</BlazorCacheBootResources>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Common\NewFolder\**" />
|
||||
<Content Remove="Common\NewFolder\**" />
|
||||
<EmbeddedResource Remove="Common\NewFolder\**" />
|
||||
<None Remove="Common\NewFolder\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
|
||||
|
||||
<PackageReference Include="Foursat.BackOffice.BFF.Otp.Protobuf" Version="0.0.111" />
|
||||
|
||||
<PackageReference Include="FourSat.BackOffice.BFF.Package.Protobuf" Version="0.0.111" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.12.1" />
|
||||
<PackageReference Include="DateTimeConverterCL" Version="1.0.0" />
|
||||
<PackageReference Include="Grpc.Core" Version="2.46.6" />
|
||||
<PackageReference Include="Grpc.Net.Client" Version="2.65.0" />
|
||||
<PackageReference Include="Grpc.Net.Client.Web" Version="2.65.0" />
|
||||
<PackageReference Include="Mapster" Version="7.4.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.18" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="7.0.20" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.18" PrivateAssets="all" />
|
||||
|
||||
<PackageReference Include="MudBlazor" Version="7.16.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.8.0" />
|
||||
<PackageReference Include="Tizzani.MudBlazor.HtmlEditor" Version="2.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,8 @@
|
||||
<MudDateRangePicker @ref="_picker" Label="@Label" AutoClose="false" Culture="@GetPersianCulture()" TitleDateFormat="dddd, dd MMMM" @bind-DateRange="_dateRange">
|
||||
<PickerActions>
|
||||
<MudButton Class="mr-auto align-self-start" OnClick="OnClickClear">پاک کردن</MudButton>
|
||||
<MudButton OnClick="@(() => _picker.CloseAsync(false))">لغو</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="OnClickOK">تایید</MudButton>
|
||||
</PickerActions>
|
||||
</MudDateRangePicker>
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
|
||||
namespace BackOffice.Common.BaseComponents;
|
||||
|
||||
public partial class DateRangePicker
|
||||
{
|
||||
private DateRange _dateRange = new();
|
||||
|
||||
private MudDateRangePicker _picker;
|
||||
[Parameter] public string Label { get; set; } = "انتخاب بازه زمانی";
|
||||
[Parameter] public DateTime? DefaultStart { get; set; }
|
||||
[Parameter] public DateTime? DefaultEnd { get; set; }
|
||||
[Parameter] public EventCallback<DateRange> OnChanged { get; set; }
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
if (DefaultStart.HasValue)
|
||||
_dateRange.Start = DefaultStart.Value.Date;
|
||||
|
||||
if (DefaultEnd.HasValue)
|
||||
_dateRange.End = DefaultEnd.Value.Date;
|
||||
}
|
||||
public CultureInfo GetPersianCulture()
|
||||
{
|
||||
var culture = new CultureInfo("fa-IR");
|
||||
DateTimeFormatInfo formatInfo = culture.DateTimeFormat;
|
||||
formatInfo.AbbreviatedDayNames = new[] { "ی", "د", "س", "چ", "پ", "ج", "ش" };
|
||||
formatInfo.DayNames = new[] { "یکشنبه", "دوشنبه", "سه شنبه", "چهار شنبه", "پنجشنبه", "جمعه", "شنبه" };
|
||||
var monthNames = new[]
|
||||
{
|
||||
"فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن",
|
||||
"اسفند",
|
||||
"",
|
||||
};
|
||||
formatInfo.AbbreviatedMonthNames =
|
||||
formatInfo.MonthNames =
|
||||
formatInfo.MonthGenitiveNames = formatInfo.AbbreviatedMonthGenitiveNames = monthNames;
|
||||
formatInfo.AMDesignator = "ق.ظ";
|
||||
formatInfo.PMDesignator = "ب.ظ";
|
||||
formatInfo.ShortDatePattern = "yyyy/MM/dd";
|
||||
formatInfo.LongDatePattern = "dddd, dd MMMM,yyyy";
|
||||
formatInfo.FirstDayOfWeek = DayOfWeek.Saturday;
|
||||
Calendar cal = new PersianCalendar();
|
||||
FieldInfo fieldInfo = culture.GetType().GetField("calendar", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
fieldInfo?.SetValue(culture, cal);
|
||||
FieldInfo info = formatInfo.GetType().GetField("calendar", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
info?.SetValue(formatInfo, cal);
|
||||
culture.NumberFormat.NumberDecimalSeparator = "/";
|
||||
culture.NumberFormat.DigitSubstitution = DigitShapes.NativeNational;
|
||||
culture.NumberFormat.NumberNegativePattern = 0;
|
||||
return culture;
|
||||
}
|
||||
private async Task OnClickOK()
|
||||
{
|
||||
_picker.CloseAsync();
|
||||
await OnChanged.InvokeAsync(_picker.DateRange);
|
||||
}
|
||||
private async Task OnClickClear()
|
||||
{
|
||||
_picker.CloseAsync();
|
||||
await OnChanged.InvokeAsync(_picker.DateRange);
|
||||
}
|
||||
}
|
||||
12
src/BackOffice/BackOffice/Common/BaseComponents/Image.razor
Normal file
12
src/BackOffice/BackOffice/Common/BaseComponents/Image.razor
Normal file
@@ -0,0 +1,12 @@
|
||||
@using MudBlazor
|
||||
@inherits MudImage
|
||||
|
||||
@if (string.IsNullOrWhiteSpace(src))
|
||||
{
|
||||
<MudSkeleton Class="@Class" Style="@Style" SkeletonType="SkeletonType.Rectangle" Height="@(Height != null ? $"{Height}px" : "100%")" Width="@(Width != null ? $"{Width}px" : "100%")" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudImage Src="@src" Alt="@Alt" Elevation="@Elevation" Class="@Class" Fluid="@Fluid" Height="@Height" ObjectFit="@ObjectFit" ObjectPosition="@ObjectPosition" Style="@(Style + ";cursor:pointer;")" Tag="@Tag" Width="@Width" @onclick="async()=>await OnClick.InvokeAsync()" />
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using MudBlazor;
|
||||
|
||||
namespace BackOffice.Common.BaseComponents
|
||||
{
|
||||
|
||||
public partial class Image
|
||||
{
|
||||
private string src = string.Empty;
|
||||
[Parameter] public EventCallback OnClick { get; set; }
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
await base.OnParametersSetAsync();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(Src))
|
||||
{
|
||||
src = Src.Contains("data:") ? Src : "https://dl.afrino.co" + Src;
|
||||
}
|
||||
|
||||
}
|
||||
//protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
//{
|
||||
// await base.OnAfterRenderAsync(firstRender);
|
||||
// if (firstRender)
|
||||
// {
|
||||
// if (!string.IsNullOrWhiteSpace(Src))
|
||||
// {
|
||||
// src = await FileManagement.GetFileBase64(Src);
|
||||
// StateHasChanged();
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
|
||||
using BackOffice.BFF.Package.Protobuf.Protos.Package;
|
||||
using BackOffice.Common.Utilities;
|
||||
using Blazored.LocalStorage;
|
||||
using Google.Protobuf.Reflection;
|
||||
using Grpc.Core;
|
||||
using Grpc.Core.Interceptors;
|
||||
using Grpc.Net.Client;
|
||||
using Grpc.Net.Client.Web;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
|
||||
using MudBlazor.Services;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using static MudBlazor.Colors;
|
||||
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
public static class ConfigureServices
|
||||
{
|
||||
public static IServiceCollection AddCommonServices(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddBlazoredLocalStorageAsSingleton(config =>
|
||||
{
|
||||
config.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
|
||||
config.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
|
||||
config.JsonSerializerOptions.IgnoreReadOnlyProperties = true;
|
||||
config.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
|
||||
config.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
|
||||
config.JsonSerializerOptions.ReadCommentHandling = JsonCommentHandling.Skip;
|
||||
config.JsonSerializerOptions.WriteIndented = false;
|
||||
});
|
||||
services.AddAuthorizationCore();
|
||||
services.AddScoped<AuthenticationStateProvider, ApiAuthenticationStateProvider>();
|
||||
services.AddSingleton<ITokenProvider, AppTokenProvider>();
|
||||
services.AddSingleton<ITokenHandler, TokenHandler>();
|
||||
services.AddMudServices();
|
||||
services.AddGrpcServices(configuration);
|
||||
return services;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static IServiceCollection AddGrpcServices(this IServiceCollection services, IConfiguration configuration) //
|
||||
{
|
||||
var baseUri = configuration["GwUrl"];
|
||||
Console.WriteLine();
|
||||
Console.WriteLine(baseUri);
|
||||
var httpClient = new HttpClient(new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler()));
|
||||
httpClient.Timeout = TimeSpan.FromMinutes(10); // TODO Check Timeout
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
var channel = CreateAuthenticatedChannel(baseUri, httpClient, serviceProvider);
|
||||
|
||||
|
||||
|
||||
services.AddTransient(sp => new PackageContract.PackageContractClient(channel));
|
||||
//services.AddTransient(sp => new OtpContract.OtpContractClient(channel));
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
private static CallInvoker CreateAuthenticatedChannel(string address, HttpClient httpClient, IServiceProvider serviceProvider)
|
||||
{
|
||||
var credentials = CallCredentials.FromInterceptor(async (context, metadata) =>
|
||||
{
|
||||
var provider = serviceProvider.GetRequiredService<ITokenProvider>();
|
||||
// var accessToken = await provider.RequestAccessToken();
|
||||
// accessToken.TryGetToken(out var token);
|
||||
var token = await provider.GetTokenAsync();
|
||||
if (!string.IsNullOrEmpty(token))
|
||||
{
|
||||
// Console.WriteLine($"Authorization Bearer {token.Value}");
|
||||
metadata.Add("Authorization", $"Bearer {token}");
|
||||
}
|
||||
|
||||
await Task.CompletedTask;
|
||||
});
|
||||
|
||||
// SslCredentials is used here because this channel is using TLS.
|
||||
// CallCredentials can't be used with ChannelCredentials.Insecure on non-TLS channels.
|
||||
var channel = GrpcChannel.ForAddress(address, new GrpcChannelOptions
|
||||
{
|
||||
UnsafeUseInsecureChannelCallCredentials = true,
|
||||
Credentials = ChannelCredentials.Create(new SslCredentials(), credentials),
|
||||
HttpClient = httpClient,
|
||||
MaxReceiveMessageSize = 1000 * 1024 * 1024, // 1 GB
|
||||
MaxSendMessageSize = 1000 * 1024 * 1024 // 1 GB
|
||||
});
|
||||
var invoker = channel.Intercept(new ErrorHandlerInterceptor());
|
||||
return invoker;
|
||||
}
|
||||
}
|
||||
14
src/BackOffice/BackOffice/Common/Models/WorkHoursDto.cs
Normal file
14
src/BackOffice/BackOffice/Common/Models/WorkHoursDto.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace BackOffice.Common.Models;
|
||||
|
||||
public class WorkHoursDto
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public string Name { get; set; }
|
||||
public bool Closed { get; set; }
|
||||
public List<Hours> Hours { get; set; }
|
||||
}
|
||||
public class Hours
|
||||
{
|
||||
public string OpenTime { get; set; }
|
||||
public string CloseTime { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
using Blazored.LocalStorage;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
|
||||
namespace BackOffice.Common.Utilities;
|
||||
|
||||
public class ApiAuthenticationStateProvider : AuthenticationStateProvider
|
||||
{
|
||||
private readonly ILocalStorageService _localStorage;
|
||||
|
||||
public ApiAuthenticationStateProvider(ILocalStorageService localStorage)
|
||||
{
|
||||
_localStorage = localStorage;
|
||||
}
|
||||
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
var savedToken = await _localStorage.GetItemAsync<string>(GlobalConstants.JwtTokenKey);
|
||||
if (string.IsNullOrWhiteSpace(savedToken))
|
||||
{
|
||||
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
|
||||
}
|
||||
var handler = new JwtSecurityTokenHandler();
|
||||
var token = handler.ReadJwtToken(savedToken);
|
||||
|
||||
|
||||
var AuthenticationState = new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(token.Claims, "jwt")));
|
||||
|
||||
return AuthenticationState;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
|
||||
}
|
||||
}
|
||||
|
||||
public void MarkUserAsAuthenticated(string email)
|
||||
{
|
||||
var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, email) }, "apiauth"));
|
||||
var authState = Task.FromResult(new AuthenticationState(authenticatedUser));
|
||||
NotifyAuthenticationStateChanged(authState);
|
||||
}
|
||||
|
||||
public void MarkUserAsLoggedOut()
|
||||
{
|
||||
var anonymousUser = new ClaimsPrincipal(new ClaimsIdentity());
|
||||
var authState = Task.FromResult(new AuthenticationState(anonymousUser));
|
||||
NotifyAuthenticationStateChanged(authState);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using BackOffice.Common.Utilities;
|
||||
using Blazored.LocalStorage;
|
||||
|
||||
namespace BackOffice.Common.Utilities;
|
||||
|
||||
public class AppTokenProvider : ITokenProvider
|
||||
{
|
||||
private readonly ILocalStorageService _localStorage;
|
||||
private string _token;
|
||||
|
||||
public AppTokenProvider(ILocalStorageService localStorage)
|
||||
{
|
||||
_localStorage = localStorage;
|
||||
}
|
||||
|
||||
public async Task<string> GetTokenAsync()
|
||||
{
|
||||
if (_token == null)
|
||||
{
|
||||
var authorizationToken = await _localStorage.GetItemAsync<string>(GlobalConstants.JwtTokenKey);
|
||||
if (!string.IsNullOrEmpty(authorizationToken))
|
||||
_token = authorizationToken.ToString().Replace("Bearer ", "");
|
||||
}
|
||||
|
||||
return _token;
|
||||
}
|
||||
}
|
||||
17
src/BackOffice/BackOffice/Common/Utilities/Enums.cs
Normal file
17
src/BackOffice/BackOffice/Common/Utilities/Enums.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Google.Protobuf.Reflection;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace BackOffice.Common.Utilities;
|
||||
|
||||
public class Enums
|
||||
{
|
||||
public enum PrivacyTypeEnum
|
||||
{
|
||||
[Display(Name = "شخصی")]
|
||||
Individual,
|
||||
[Display(Name = "گروهی")]
|
||||
Group,
|
||||
[Display(Name = "همه")]
|
||||
All
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using Grpc.Core.Interceptors;
|
||||
using Grpc.Core;
|
||||
|
||||
namespace BackOffice.Common.Utilities;
|
||||
|
||||
public class ErrorHandlerInterceptor : Interceptor
|
||||
{
|
||||
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
|
||||
TRequest request,
|
||||
ClientInterceptorContext<TRequest, TResponse> context,
|
||||
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
|
||||
{
|
||||
var call = continuation(request, context);
|
||||
|
||||
return new AsyncUnaryCall<TResponse>(
|
||||
HandleResponse(call.ResponseAsync),
|
||||
call.ResponseHeadersAsync,
|
||||
call.GetStatus,
|
||||
call.GetTrailers,
|
||||
call.Dispose);
|
||||
}
|
||||
|
||||
private async Task<TResponse> HandleResponse<TResponse>(Task<TResponse> inner)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await inner;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GlobalConstants.ConstSnackbar.Add(ex.Message, severity: MudBlazor.Severity.Error);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
195
src/BackOffice/BackOffice/Common/Utilities/Extensions.cs
Normal file
195
src/BackOffice/BackOffice/Common/Utilities/Extensions.cs
Normal file
@@ -0,0 +1,195 @@
|
||||
using HtmlAgilityPack;
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace BackOffice.Common.Utilities;
|
||||
|
||||
public static class Extensions
|
||||
{
|
||||
public static string ExtractUserFriendlyMessage(this string errorMessage)
|
||||
{
|
||||
// کلیدواژهای که بعد از آن بخش مورد نظر شروع میشود
|
||||
string keyword = "Exception:";
|
||||
|
||||
// بررسی وجود کلیدواژه در پیام خطا
|
||||
int keywordIndex = errorMessage.IndexOf(keyword);
|
||||
|
||||
if (keywordIndex >= 0)
|
||||
{
|
||||
// استخراج بخش بعد از کلیدواژه
|
||||
string userFriendlyMessage = errorMessage.Substring(keywordIndex + keyword.Length).Trim();
|
||||
if (userFriendlyMessage.EndsWith(")"))
|
||||
{
|
||||
userFriendlyMessage = userFriendlyMessage.Substring(0, userFriendlyMessage.Length - 2);
|
||||
}
|
||||
return userFriendlyMessage;
|
||||
}
|
||||
|
||||
// اگر کلیدواژه وجود نداشت، کل پیام خطا برگردانده شود
|
||||
return errorMessage;
|
||||
}
|
||||
public static string ToDashString(this string? text) => string.IsNullOrWhiteSpace(text) ? "-" : text;
|
||||
public static string ToThousands(this double? digit) => digit == null ? "0" : digit.Value.ToString("N0");
|
||||
public static string ToThousands(this float? digit) => digit == null ? "0" : digit.Value.ToString("N0");
|
||||
public static string ToThousands(this int? digit) => digit == null ? "0" : digit.Value.ToString("N0");
|
||||
public static string ToThousands(this long? digit) => digit == null ? "0" : digit.Value.ToString("N0");
|
||||
public static string ToThousands(this double digit) => digit.ToString("N0");
|
||||
public static string ToThousands(this float digit) => digit.ToString("N0");
|
||||
public static string ToThousands(this int digit) => digit.ToString("N0");
|
||||
public static string ToThousands(this long digit) => digit.ToString("N0");
|
||||
public static string ToCurrency(this string price) => price + " ریال";
|
||||
public static DateTime UnixTimeStampToDateTime(this long unixTimeStamp)
|
||||
{
|
||||
// Unix timestamp is seconds past epoch
|
||||
DateTime dateTime = new(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
|
||||
dateTime = dateTime.AddMilliseconds(unixTimeStamp).ToLocalTime();
|
||||
return dateTime;
|
||||
}
|
||||
public static string PersianToEnglish(this string persianStr)
|
||||
{
|
||||
return persianStr.Replace("۰", "0")
|
||||
.Replace("۱", "1")
|
||||
.Replace("۲", "2")
|
||||
.Replace("۳", "3")
|
||||
.Replace("۴", "4")
|
||||
.Replace("۵", "5")
|
||||
.Replace("۶", "6")
|
||||
.Replace("۷", "7")
|
||||
.Replace("۸", "8")
|
||||
.Replace("۹", "9");
|
||||
}
|
||||
public static string ArabicToPersian(this string arabicStr)
|
||||
{
|
||||
return arabicStr
|
||||
.Replace("ک", "ك")
|
||||
.Replace("ی", "ي");
|
||||
}
|
||||
public static string Truncate(this string value, int maxLength, bool isAppendDots = false)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value)) return value;
|
||||
return value.Length <= maxLength ? value : isAppendDots ? value.Substring(0, maxLength) + "..." : value.Substring(0, maxLength);
|
||||
}
|
||||
|
||||
public static string DiffDateTime(this DateTime dateTime)
|
||||
{
|
||||
const int SECOND = 1;
|
||||
const int MINUTE = 60 * SECOND;
|
||||
const int HOUR = 60 * MINUTE;
|
||||
const int DAY = 24 * HOUR;
|
||||
const int MONTH = 30 * DAY;
|
||||
var ts = new TimeSpan(DateTime.Now.Ticks - dateTime.Ticks);
|
||||
double delta = Math.Abs(ts.TotalSeconds);
|
||||
if (delta < 1 * MINUTE)
|
||||
{
|
||||
return ts.Seconds == 1 ? "لحظه ای قبل" : ts.Seconds + " ثانیه قبل";
|
||||
}
|
||||
if (delta < 2 * MINUTE)
|
||||
{
|
||||
return "یک دقیقه قبل";
|
||||
}
|
||||
if (delta < 45 * MINUTE)
|
||||
{
|
||||
return ts.Minutes + " دقیقه قبل";
|
||||
}
|
||||
if (delta < 90 * MINUTE)
|
||||
{
|
||||
return "یک ساعت قبل";
|
||||
}
|
||||
if (delta < 24 * HOUR)
|
||||
{
|
||||
return ts.Hours + " ساعت قبل";
|
||||
}
|
||||
if (delta < 48 * HOUR)
|
||||
{
|
||||
return "دیروز";
|
||||
}
|
||||
if (delta < 30 * DAY)
|
||||
{
|
||||
return ts.Days + " روز قبل";
|
||||
}
|
||||
if (delta < 12 * MONTH)
|
||||
{
|
||||
int months = Convert.ToInt32(Math.Floor((double)ts.Days / 30));
|
||||
return months <= 1 ? "یک ماه قبل" : months + " ماه قبل";
|
||||
}
|
||||
int years = Convert.ToInt32(Math.Floor((double)ts.Days / 365));
|
||||
return years <= 1 ? "یک سال قبل" : years + " سال قبل";
|
||||
}
|
||||
public static bool GenericEquals<T>(this T value1, T value2)
|
||||
{
|
||||
if (value1 == null && value2 == null)
|
||||
return true;
|
||||
|
||||
if (value1 != null && value2 == null || value1 == null && value2 != null)
|
||||
return false;
|
||||
|
||||
foreach (var item in value1.GetType().GetProperties())
|
||||
{
|
||||
var val1 = item.GetValue(value1);
|
||||
var val2 = value2.GetType().GetProperty(item.Name).GetValue(value2);
|
||||
|
||||
if (Convert.GetTypeCode(val1) == TypeCode.Object)
|
||||
continue;
|
||||
|
||||
var strVal = Convert.ToString(val1);
|
||||
var strVal2 = Convert.ToString(val2);
|
||||
if (!strVal.Equals(strVal2))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static string GetDisplayName(this Enum enumValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
return enumValue.GetType().GetMember(enumValue.ToString()).First().GetCustomAttribute<DisplayAttribute>()?.GetName();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
public static void ChangeLoading(ref bool loading, ref bool toLoading)
|
||||
{
|
||||
loading = toLoading;
|
||||
}
|
||||
//public static bool IsValidURL(this string source)
|
||||
//{
|
||||
// Uri uri = null;
|
||||
// if (!Uri.TryCreate(source, UriKind.Absolute, out uri) || null == uri)
|
||||
// return false;
|
||||
// else
|
||||
// return true;
|
||||
//}
|
||||
public static bool IsValidUrl(this string webSiteUrl)
|
||||
{
|
||||
if (webSiteUrl.StartsWith("www."))
|
||||
{
|
||||
webSiteUrl = "http://" + webSiteUrl;
|
||||
}
|
||||
|
||||
return Uri.TryCreate(webSiteUrl, UriKind.Absolute, out Uri uriResult)
|
||||
&& (uriResult.Scheme == Uri.UriSchemeHttp
|
||||
|| uriResult.Scheme == Uri.UriSchemeHttps) && uriResult.Host.Replace("www.", "").Split('.').Count() > 1 && uriResult.HostNameType == UriHostNameType.Dns && uriResult.Host.Length > uriResult.Host.LastIndexOf(".") + 1 && 100 >= webSiteUrl.Length;
|
||||
}
|
||||
|
||||
public static string HtmlToText(this string html)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(html))
|
||||
return string.Empty;
|
||||
|
||||
HtmlDocument doc = new HtmlDocument();
|
||||
doc.LoadHtml(html);
|
||||
string text = doc.DocumentNode.InnerText;
|
||||
|
||||
return WebUtility.HtmlDecode(text).Trim(); // ← این خط مهمه
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using MudBlazor;
|
||||
|
||||
namespace BackOffice.Common.Utilities;
|
||||
|
||||
public static class GlobalConstants
|
||||
{
|
||||
public const string DateFormat = "MMM dd, yyyy";
|
||||
public const string DateTimeFormat = "MMM dd, yyyy - HH:mm";
|
||||
public static string JwtTokenKey = "AuthToken";
|
||||
public const string SuccessMsg = "با موفقیت انجام شد";
|
||||
public static ISnackbar ConstSnackbar;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace BackOffice.Common.Utilities;
|
||||
|
||||
public interface ITokenProvider
|
||||
{
|
||||
Task<string> GetTokenAsync();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace BackOffice.Common.Utilities;
|
||||
|
||||
public static class RouteConstance
|
||||
{
|
||||
public const string HomePage = "/";
|
||||
public const string Login = "/Login/";
|
||||
public const string VerifyCodePage = "/VerifyCodePage/";
|
||||
public const string Package = "/PackagePage/";
|
||||
}
|
||||
52
src/BackOffice/BackOffice/Common/Utilities/TokenHandler.cs
Normal file
52
src/BackOffice/BackOffice/Common/Utilities/TokenHandler.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace BackOffice.Common.Utilities;
|
||||
|
||||
public interface ITokenHandler
|
||||
{
|
||||
bool IsDifferent(string oldToken, string newToken);
|
||||
bool IsBlocked(string newToken);
|
||||
IEnumerable<Claim> GetUserClaims(string token);
|
||||
}
|
||||
public class TokenHandler : ITokenHandler
|
||||
{
|
||||
public bool IsDifferent(string oldToken, string newToken)
|
||||
{
|
||||
oldToken = oldToken.Replace("\"", "");
|
||||
newToken = newToken.Replace("\"", "");
|
||||
var handler = new JwtSecurityTokenHandler();
|
||||
var oldJwtSecurityToken = handler.ReadJwtToken(oldToken);
|
||||
var newJwtSecurityToken = handler.ReadJwtToken(newToken);
|
||||
if (newJwtSecurityToken.Claims.Count() != oldJwtSecurityToken.Claims.Count())
|
||||
return true;
|
||||
|
||||
int differences = 0;
|
||||
foreach (var claim in oldJwtSecurityToken.Claims)
|
||||
{
|
||||
if (claim.Type == "exp")
|
||||
continue;
|
||||
if (!newJwtSecurityToken.Claims.Any(x => x.Type == claim.Type && x.Value == claim.Value))
|
||||
{
|
||||
differences++;
|
||||
}
|
||||
}
|
||||
return differences > 0;
|
||||
}
|
||||
public bool IsBlocked(string newToken)
|
||||
{
|
||||
newToken = newToken.Replace("\"", "");
|
||||
var handler = new JwtSecurityTokenHandler();
|
||||
var newJwtSecurityToken = handler.ReadJwtToken(newToken);
|
||||
return newJwtSecurityToken.Claims.Any(x => x.Type == "IsBlocked" && x.Value == "True");
|
||||
}
|
||||
|
||||
public IEnumerable<Claim> GetUserClaims(string token)
|
||||
{
|
||||
token = token.Replace("\"", "");
|
||||
var handler = new JwtSecurityTokenHandler();
|
||||
var newJwtSecurityToken = handler.ReadJwtToken(token);
|
||||
return newJwtSecurityToken.Claims;
|
||||
}
|
||||
}
|
||||
6
src/BackOffice/BackOffice/Common/Utilities/UrlUtility.cs
Normal file
6
src/BackOffice/BackOffice/Common/Utilities/UrlUtility.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace BackOffice.Common.Utilities;
|
||||
|
||||
public static class UrlUtility
|
||||
{
|
||||
public static string DownloadUrl { get; set; }
|
||||
}
|
||||
3
src/BackOffice/BackOffice/Pages/Index.razor
Normal file
3
src/BackOffice/BackOffice/Pages/Index.razor
Normal file
@@ -0,0 +1,3 @@
|
||||
@page "/"
|
||||
@attribute [AllowAnonymous]
|
||||
<h1>Hello, world!</h1>
|
||||
21
src/BackOffice/BackOffice/Pages/Login/LoginPage.razor
Normal file
21
src/BackOffice/BackOffice/Pages/Login/LoginPage.razor
Normal file
@@ -0,0 +1,21 @@
|
||||
@using BackOffice.Shared;
|
||||
@layout EmptyLayout
|
||||
@attribute [Route(RouteConstance.Login)]
|
||||
@attribute [AllowAnonymous]
|
||||
|
||||
|
||||
<MudStack Style="width:100%;height:80vh;" AlignItems="AlignItems.Center" Justify="Justify.Center">
|
||||
<MudPaper Elevation="4" Width="35%" Height="200px" Class="mx-auto my-auto pa-4">
|
||||
<MudForm @ref="@_form" Model="@_request" Validation="@(_requestValidator.ValidateValue)">
|
||||
|
||||
<MudStack>
|
||||
<MudTextField T="string" Variant="Variant.Outlined" Label="شماره موبایل" @bind-Value="@_request.Mobile" For="() => _request.Mobile" HelperText="لطفا شماره موبایل خود را وارد کنید"></MudTextField>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" Disabled="_isLoading" OnClick="OnSubmitClick" ButtonType="ButtonType.Button" Style="cursor:pointer;">ارسال کد</MudButton>
|
||||
</MudStack>
|
||||
</MudForm>
|
||||
|
||||
</MudPaper>
|
||||
</MudStack>
|
||||
|
||||
|
||||
|
||||
41
src/BackOffice/BackOffice/Pages/Login/LoginPage.razor.cs
Normal file
41
src/BackOffice/BackOffice/Pages/Login/LoginPage.razor.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using BackOffice.BFF.Otp.Protobuf.Protos.Otp;
|
||||
using BackOffice.BFF.Otp.Protobuf.Validator;
|
||||
using BackOffice.Common.Utilities;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace BackOffice.Pages.Login;
|
||||
public partial class LoginPage
|
||||
{
|
||||
private bool _isLoading;
|
||||
private SendOtpRequest _request = new();
|
||||
private SendOtpRequestValidator _requestValidator = new();
|
||||
private MudForm _form;
|
||||
|
||||
[Inject] public OtpContract.OtpContractClient OtpContract { get; set; }
|
||||
private async Task OnSubmitClick()
|
||||
{
|
||||
Console.WriteLine(OtpContract == null);
|
||||
await _form.Validate();
|
||||
if (!_form.IsValid)
|
||||
return;
|
||||
|
||||
_isLoading = true;
|
||||
StateHasChanged();
|
||||
|
||||
_request.Mobile = _request.Mobile.PersianToEnglish();
|
||||
try
|
||||
{
|
||||
await OtpContract.SendOtpAsync(_request);
|
||||
Navigation.NavigateTo(RouteConstance.VerifyCodePage + _request.Mobile);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add(message: ex.Message, severity: Severity.Error, null);
|
||||
}
|
||||
|
||||
_isLoading = false;
|
||||
StateHasChanged();
|
||||
|
||||
}
|
||||
}
|
||||
20
src/BackOffice/BackOffice/Pages/Login/VerifyCodePage.razor
Normal file
20
src/BackOffice/BackOffice/Pages/Login/VerifyCodePage.razor
Normal file
@@ -0,0 +1,20 @@
|
||||
@using BackOffice.Shared
|
||||
@layout EmptyLayout
|
||||
@attribute [Route(RouteConstance.VerifyCodePage + "{Mobile}")]
|
||||
@attribute [AllowAnonymous]
|
||||
|
||||
|
||||
<MudStack Style="width:100%;height:100vh;" AlignItems="AlignItems.Center" Justify="Justify.Center">
|
||||
<MudPaper Elevation="4" Width="35%" Height="230px" Class="mx-auto my-auto pa-4">
|
||||
<MudForm @ref="@_form" Model="@_request" Validation="@(_requestValidator.ValidateValue)">
|
||||
<MudStack Spacing="5">
|
||||
<MudTextField T="string" @bind-Value="@_request.Code" For="() => _request.Code" Variant="Variant.Outlined" Label="رمز پویا" HelperText="لطفا کد شش رقمی دریافتی را وارد کنید" InputType="InputType.Telephone" />
|
||||
<MudButton Color="Color.Primary" Variant="Variant.Filled" Disabled="_isLoading" OnClick="OnSubmitClick">ورود به حساب کاربری</MudButton>
|
||||
<MudButton Color="Color.Primary" Variant="Variant.Text" Disabled="@(_currentCount > 1 || _isLoading)" OnClick="OnResendOtpClick">@(_currentCount < 1 ? "ارسال مجدد رمز پویا" : $"ارسال مجدد رمز پویا {_currentCount}")</MudButton>
|
||||
</MudStack>
|
||||
</MudForm>
|
||||
|
||||
</MudPaper>
|
||||
</MudStack>
|
||||
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
using BackOffice.BFF.Otp.Protobuf.Protos.Otp;
|
||||
using BackOffice.BFF.Otp.Protobuf.Validator;
|
||||
using BackOffice.Common.Utilities;
|
||||
using Blazored.LocalStorage;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace BackOffice.Pages.Login;
|
||||
public partial class VerifyCodePage
|
||||
{
|
||||
[Parameter]
|
||||
public string Mobile { get; set; }
|
||||
|
||||
private bool _isLoading;
|
||||
private VerifyOtpCodeRequest _request = new();
|
||||
private VerifyOtpCodeRequestValidator _requestValidator = new();
|
||||
private MudForm _form;
|
||||
private Timer _timer;
|
||||
private int _currentCount = 120;
|
||||
|
||||
[Inject]
|
||||
public OtpContract.OtpContractClient OtpContract { get; set; }
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
StartTimer();
|
||||
}
|
||||
private async Task OnSubmitClick()
|
||||
{
|
||||
await _form.Validate();
|
||||
if (!_form.IsValid)
|
||||
return;
|
||||
|
||||
_isLoading = true;
|
||||
StateHasChanged();
|
||||
|
||||
_request.Mobile = Mobile.PersianToEnglish();
|
||||
_request.Code = _request.Code.PersianToEnglish();
|
||||
|
||||
try
|
||||
{
|
||||
var token = await OtpContract.VerifyOtpCodeAsync(_request);
|
||||
await LocalStorageService.SetItemAsync(GlobalConstants.JwtTokenKey, token.Token);
|
||||
Navigation.NavigateTo(RouteConstance.HomePage, forceLoad: true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add(message: ex.Message, severity: Severity.Error, null);
|
||||
}
|
||||
|
||||
_isLoading = false;
|
||||
StateHasChanged();
|
||||
|
||||
|
||||
}
|
||||
private async Task OnResendOtpClick()
|
||||
{
|
||||
_isLoading = true;
|
||||
StateHasChanged();
|
||||
|
||||
try
|
||||
{
|
||||
await OtpContract.SendOtpAsync(request: new()
|
||||
{
|
||||
Mobile = Mobile
|
||||
});
|
||||
StartTimer();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add(message: ex.Message, severity: Severity.Error, null);
|
||||
}
|
||||
|
||||
_isLoading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private void StartTimer()
|
||||
{
|
||||
_currentCount = 120;
|
||||
_timer = new Timer(new TimerCallback(_ =>
|
||||
{
|
||||
if (_currentCount > 0)
|
||||
{
|
||||
_currentCount--;
|
||||
InvokeAsync(() =>
|
||||
{
|
||||
StateHasChanged();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
_timer.Dispose();
|
||||
}
|
||||
}), null, 1000, 1000);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
@using BackOffice.BFF.Package.Protobuf.Protos.Package
|
||||
@using BackOffice.Common.BaseComponents
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Tizzani.MudBlazor.HtmlEditor
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudStack>
|
||||
<MudStack Justify="Justify.Center" AlignItems="AlignItems.Center">
|
||||
<Image Src="@_srcImage" Width="200" Height="100" ObjectPosition="ObjectPosition.Center" ObjectFit="ObjectFit.Cover" />
|
||||
|
||||
<MudFileUpload T="IBrowserFile" Accept="image/*" FilesChanged="OnImageFileSelect">
|
||||
<ActivatorContent>
|
||||
<MudButton HtmlTag="label"
|
||||
Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
ButtonType="ButtonType.Button"
|
||||
StartIcon="@Icons.Material.Filled.Image"
|
||||
Style="cursor:pointer;">
|
||||
انتخاب تصویر
|
||||
</MudButton>
|
||||
</ActivatorContent>
|
||||
<SelectedTemplate>
|
||||
@if (context != null)
|
||||
{
|
||||
<MudText Class="mt-2" Typo="Typo.subtitle2">
|
||||
<MudTooltip Text="@context.Name" Inline="true">
|
||||
@context.Name
|
||||
</MudTooltip>
|
||||
</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Class="mt-2" Typo="Typo.subtitle2">فایلی انتخاب نشده</MudText>
|
||||
}
|
||||
</SelectedTemplate>
|
||||
</MudFileUpload>
|
||||
</MudStack>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" @bind-Value="Model.Title" Disabled="_isLoading" Label="عنوان" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudHtmlEditor @bind-Html="Model.Description">
|
||||
<MudHtmlToolbarOptions InsertImage="false" /> <!-- This will exclude the "insert image" toolbar option -->
|
||||
</MudHtmlEditor>
|
||||
</MudItem>
|
||||
|
||||
</MudStack>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel" Disabled="_isLoading">لغو</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="CallCreateMethod" Disabled="_isLoading">ثبت </MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,75 @@
|
||||
@using BackOffice.BFF.Package.Protobuf.Protos.Package
|
||||
@using BackOffice.Common.BaseComponents
|
||||
@using DataModel = BackOffice.BFF.Package.Protobuf.Protos.Package.GetAllPackageByFilterResponseModel
|
||||
|
||||
|
||||
<MudDataGrid T="DataModel" ServerData="@(new Func<GridState<DataModel>, Task<GridData<DataModel>>>(ServerReload))"
|
||||
Hover="true" @ref="_gridData" Height="72vh">
|
||||
<ColGroup>
|
||||
<col />
|
||||
<col />
|
||||
<col />
|
||||
<col style="width: 58px;" />
|
||||
</ColGroup>
|
||||
<Columns>
|
||||
<PropertyColumn Property="x => x.Id" Title="شناسه" />
|
||||
|
||||
<PropertyColumn Property="x => x.Title" Title="عنوان" CellStyle="text-wrap: nowrap;" HeaderStyle="text-wrap: nowrap;">
|
||||
<CellTemplate>
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center">
|
||||
<Image Src="@context.Item.ImagePath" Width="25" Height="25" ObjectPosition="ObjectPosition.Center" ObjectFit="ObjectFit.Fill" />
|
||||
@if (string.IsNullOrWhiteSpace(context.Item.Title))
|
||||
{
|
||||
<MudText Typo="Typo.inherit">-</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
<MudTooltip Text="@(context.Item.Title)" Arrow="true" Style="@($"{(context.Item.Title.Length < 70 ? string.Empty : "width:600px;")}")">
|
||||
<MudText Typo="Typo.inherit" Style="text-wrap: nowrap;">
|
||||
@(context.Item.Title.Truncate(20, true))
|
||||
</MudText>
|
||||
</MudTooltip>
|
||||
}
|
||||
</MudStack>
|
||||
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="x => x.Description" Title="توضیحات" CellStyle="text-wrap: nowrap;" HeaderStyle="text-wrap: nowrap;">
|
||||
<CellTemplate>
|
||||
@if (string.IsNullOrWhiteSpace(context.Item.Description))
|
||||
{
|
||||
<MudText Typo="Typo.inherit">-</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
<MudTooltip Text="@(context.Item.Description.HtmlToText())" Arrow="true" Style="@($"{(context.Item.Description.Length < 70 ? string.Empty : "width:600px;")}")">
|
||||
<MudText Typo="Typo.inherit" Style="text-wrap: nowrap;">
|
||||
@(context.Item.Description.HtmlToText().Truncate(20, true))
|
||||
</MudText>
|
||||
</MudTooltip>
|
||||
}
|
||||
</CellTemplate>
|
||||
</PropertyColumn>
|
||||
|
||||
<TemplateColumn StickyLeft="true" Title="عملیات" CellStyle="text-wrap: nowrap;" HeaderStyle="text-wrap: nowrap;">
|
||||
<CellTemplate>
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center">
|
||||
<MudTooltip Text="ویرایش">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.EditNote" Size="Size.Small" ButtonType="ButtonType.Button" OnClick="@(() => Update(context.Item.Adapt<DataModel>()))" Style="cursor:pointer;" />
|
||||
</MudTooltip>
|
||||
|
||||
<MudTooltip Text="آرشیو">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.DeleteOutline" Size="Size.Small" ButtonType="ButtonType.Button" OnClick="@(() => OnDelete(context.Item))" Style="cursor:pointer;" />
|
||||
</MudTooltip>
|
||||
</MudStack>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
</Columns>
|
||||
<PagerContent>
|
||||
<MudDataGridPager T="DataModel" PageSizeOptions=@(new int[] { 30, 60, 90 }) InfoFormat="سطر {first_item} تا {last_item} از {all_items}" RowsPerPageString="تعداد سطرهای صفحه" />
|
||||
</PagerContent>
|
||||
</MudDataGrid>
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
using BackOffice.BFF.Package.Protobuf.Protos.Package;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using HtmlAgilityPack;
|
||||
using Mapster;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
using static Google.Rpc.Context.AttributeContext.Types;
|
||||
using DataModel = BackOffice.BFF.Package.Protobuf.Protos.Package.GetAllPackageByFilterResponseModel;
|
||||
|
||||
namespace BackOffice.Pages.Package.Components;
|
||||
|
||||
public partial class PackageDataTable
|
||||
{
|
||||
|
||||
[Inject] public PackageContract.PackageContractClient PackageContract { get; set; }
|
||||
private bool _isLoading = true;
|
||||
private MudDataGrid<DataModel> _gridData;
|
||||
|
||||
private GetAllPackageByFilterRequest _request = new() { Filter = new() };
|
||||
private async Task<GridData<DataModel>> ServerReload(GridState<DataModel> state)
|
||||
{
|
||||
_request.Filter ??= new();
|
||||
_request.PaginationState ??= new();
|
||||
_request.PaginationState.PageNumber = state.Page + 1;
|
||||
_request.PaginationState.PageSize = state.PageSize;
|
||||
|
||||
var result = await PackageContract.GetAllPackageByFilterAsync(_request);
|
||||
if (result != null && result.Models != null && result.Models.Any())
|
||||
{
|
||||
return new GridData<DataModel>() { Items = result.Models.ToList(), TotalItems = (int)result.MetaData.TotalCount };
|
||||
}
|
||||
|
||||
return new GridData<DataModel>();
|
||||
}
|
||||
public async Task Update(DataModel model)
|
||||
{
|
||||
var parameters = new DialogParameters<UpdateDialog> { { x => x.Model, model.Adapt<UpdatePackageRequest>() } };
|
||||
|
||||
var dialog = await DialogService.ShowAsync<UpdateDialog>($"ویرایش پکیج", parameters, new DialogOptions() { CloseButton = true, FullWidth = true, MaxWidth = MaxWidth.Small });
|
||||
var result = await dialog.Result;
|
||||
|
||||
if (!result.Canceled)
|
||||
{
|
||||
ReLoadData();
|
||||
//Reload Data
|
||||
Snackbar.Add("عملیات با موفقیت انجام شد", Severity.Success);
|
||||
|
||||
}
|
||||
}
|
||||
private async Task OnDelete(DataModel model)
|
||||
{
|
||||
var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Small };
|
||||
bool? result = await DialogService.ShowMessageBox(
|
||||
"اخطار",
|
||||
"آیا از حذف این مورد مطمئن هستید؟",
|
||||
yesText: "حذف", cancelText: "لغو",
|
||||
options: options);
|
||||
if (result != null && result.Value)
|
||||
{
|
||||
await PackageContract.DeletePackageAsync(new()
|
||||
{
|
||||
Id = model.Id
|
||||
});
|
||||
ReLoadData();
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
public async void ReLoadData()
|
||||
{
|
||||
if (_gridData != null)
|
||||
await _gridData.ReloadServerData();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
@using BackOffice.BFF.Package.Protobuf.Protos.Package
|
||||
@using BackOffice.Common.BaseComponents
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Tizzani.MudBlazor.HtmlEditor
|
||||
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudStack>
|
||||
<MudStack Justify="Justify.Center" AlignItems="AlignItems.Center">
|
||||
<Image Src="@_srcImage" Width="200" Height="100" ObjectPosition="ObjectPosition.Center" ObjectFit="ObjectFit.Cover" />
|
||||
|
||||
<MudFileUpload T="IBrowserFile" Accept="image/*" FilesChanged="OnImageFileSelect">
|
||||
<ActivatorContent>
|
||||
<MudButton HtmlTag="label"
|
||||
Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.Image"
|
||||
ButtonType="ButtonType.Button"
|
||||
Style="cursor:pointer;">
|
||||
انتخاب تصویر
|
||||
</MudButton>
|
||||
</ActivatorContent>
|
||||
<SelectedTemplate>
|
||||
@if (context != null)
|
||||
{
|
||||
<MudText Class="mt-2" Typo="Typo.subtitle2">
|
||||
<MudTooltip Text="@context.Name" Inline="true">
|
||||
@context.Name
|
||||
</MudTooltip>
|
||||
</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Class="mt-2" Typo="Typo.subtitle2">فایلی انتخاب نشده</MudText>
|
||||
}
|
||||
</SelectedTemplate>
|
||||
</MudFileUpload>
|
||||
</MudStack>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" @bind-Value="Model.Title" Disabled="_isLoading" Label="عنوان" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudHtmlEditor @bind-Html="Model.Description">
|
||||
<MudHtmlToolbarOptions InsertImage="false" /> <!-- This will exclude the "insert image" toolbar option -->
|
||||
</MudHtmlEditor>
|
||||
</MudItem>
|
||||
</MudStack>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel" Disabled="_isLoading">لغو</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="CallUpdateMethod" Disabled="_isLoading">ثبت</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,36 @@
|
||||
@attribute [Route(RouteConstance.Package)]
|
||||
|
||||
@using BackOffice.BFF.Package.Protobuf.Protos.Package
|
||||
@using BackOffice.Pages.Package.Components
|
||||
|
||||
<MudStack>
|
||||
<MudPaper Elevation="1" Class="pa-4">
|
||||
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
|
||||
<MudText>مدیریت پکیج</MudText>
|
||||
<MudStack Spacing="2" Row="true" Justify="Justify.Center" AlignItems="AlignItems.Center">
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" Size="Size.Large" ButtonType="ButtonType.Button" OnClick="CreateNew" Style="cursor:pointer;">افزودن</MudButton>
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
<BackOffice.Pages.Package.Components.PackageDataTable @ref="_table" />
|
||||
</MudStack>
|
||||
|
||||
|
||||
@code {
|
||||
|
||||
private PackageDataTable _table;
|
||||
|
||||
public async Task CreateNew()
|
||||
{
|
||||
var dialog = await DialogService.ShowAsync<CreateDialog>($"افزودن پکیج", new DialogParameters<CreateDialog>() { { x => x.Model, new CreateNewPackageRequest() } }, new DialogOptions() { CloseButton = true, FullWidth = true, MaxWidth = MaxWidth.Small });
|
||||
var result = await dialog.Result;
|
||||
if (!result.Canceled)
|
||||
{
|
||||
_table.ReLoadData();
|
||||
Snackbar.Add("عملیات با موفقیت انجام شد", Severity.Success);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
15
src/BackOffice/BackOffice/Program.cs
Normal file
15
src/BackOffice/BackOffice/Program.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using BackOffice;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using MudBlazor.Services;
|
||||
|
||||
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||
builder.RootComponents.Add<App>("#app");
|
||||
builder.RootComponents.Add<HeadOutlet>("head::after");
|
||||
builder.Services.AddCommonServices(builder.Configuration);
|
||||
|
||||
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
|
||||
|
||||
|
||||
await builder.Build().RunAsync();
|
||||
38
src/BackOffice/BackOffice/Properties/launchSettings.json
Normal file
38
src/BackOffice/BackOffice/Properties/launchSettings.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"iisSettings": {
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:21136",
|
||||
"sslPort": 44321
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||
"applicationUrl": "http://localhost:5018",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||
"applicationUrl": "https://localhost:7106;http://localhost:5018",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
36
src/BackOffice/BackOffice/Shared/EmptyLayout.razor
Normal file
36
src/BackOffice/BackOffice/Shared/EmptyLayout.razor
Normal file
@@ -0,0 +1,36 @@
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<MudRTLProvider RightToLeft="true">
|
||||
<MudThemeProvider Theme="CustomTheme" />
|
||||
<MudDialogProvider />
|
||||
<MudSnackbarProvider />
|
||||
|
||||
<MudLayout>
|
||||
<MudMainContent>
|
||||
<MudStack>
|
||||
@Body
|
||||
</MudStack>
|
||||
</MudMainContent>
|
||||
</MudLayout>
|
||||
</MudRTLProvider>
|
||||
|
||||
|
||||
@code {
|
||||
|
||||
|
||||
MudTheme CustomTheme = new MudTheme()
|
||||
{
|
||||
Typography = new Typography()
|
||||
{
|
||||
Default = new Default()
|
||||
{
|
||||
FontFamily = new[] { "IRANSans" }
|
||||
|
||||
}
|
||||
},
|
||||
LayoutProperties = new()
|
||||
{
|
||||
DrawerWidthRight = "250px"
|
||||
},
|
||||
};
|
||||
}
|
||||
36
src/BackOffice/BackOffice/Shared/MainLayout.razor
Normal file
36
src/BackOffice/BackOffice/Shared/MainLayout.razor
Normal file
@@ -0,0 +1,36 @@
|
||||
@using BackOffice.Shared
|
||||
@using Microsoft.AspNetCore.Components.Authorization
|
||||
@inherits LayoutComponentBase
|
||||
@attribute [AllowAnonymous]
|
||||
|
||||
<MudRTLProvider RightToLeft="true">
|
||||
<MudThemeProvider Theme="CustomTheme" />
|
||||
<MudDialogProvider />
|
||||
<MudSnackbarProvider />
|
||||
<MudPopoverProvider />
|
||||
<MudLayout>
|
||||
<MudAppBar>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="@((e) => DrawerToggle())" />
|
||||
مدیریت
|
||||
<MudSpacer />
|
||||
<AuthorizeView>
|
||||
<Authorized>
|
||||
<MudText Class="mx-3 py-0" Typo="Typo.overline">@(context.User.Claims?.FirstOrDefault(x => x.Type == "sub")?.Value)</MudText>
|
||||
<MudBadge Color="Color.Warning" Visible="false" Overlap="true" Dot="true" Bordered="false">
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.Person4" Color="Color.Inherit" Style="border: 1px solid #5e4df9;" />
|
||||
</MudBadge>
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
</MudAppBar>
|
||||
<MudDrawer @bind-Open="@_drawerOpen" Breakpoint="Breakpoint.Lg" Elevation="1" Variant="@DrawerVariant.Responsive">
|
||||
<NavMenu />
|
||||
</MudDrawer>
|
||||
<MudMainContent>
|
||||
<MudStack Class="pa-4 mt-5">
|
||||
@Body
|
||||
</MudStack>
|
||||
</MudMainContent>
|
||||
</MudLayout>
|
||||
</MudRTLProvider>
|
||||
|
||||
|
||||
27
src/BackOffice/BackOffice/Shared/MainLayout.razor.cs
Normal file
27
src/BackOffice/BackOffice/Shared/MainLayout.razor.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using MudBlazor;
|
||||
|
||||
namespace BackOffice.Shared;
|
||||
public partial class MainLayout
|
||||
{
|
||||
bool _drawerOpen = true;
|
||||
private string Details { get; set; }
|
||||
|
||||
void DrawerToggle()
|
||||
{
|
||||
_drawerOpen = !_drawerOpen;
|
||||
}
|
||||
|
||||
MudTheme CustomTheme = new MudTheme()
|
||||
{
|
||||
Typography = new Typography()
|
||||
{
|
||||
Default = new Default()
|
||||
{
|
||||
FontFamily = new[] { "IRANSans" }
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
25
src/BackOffice/BackOffice/Shared/NavMenu.razor
Normal file
25
src/BackOffice/BackOffice/Shared/NavMenu.razor
Normal file
@@ -0,0 +1,25 @@
|
||||
@using BackOffice.BFF.Package.Protobuf.Protos.Package
|
||||
@using Microsoft.AspNetCore.Components.Authorization
|
||||
|
||||
<MudNavMenu Bordered="true">
|
||||
<MudNavLink Match="NavLinkMatch.Prefix" Href="/">داشبورد</MudNavLink>
|
||||
<AuthorizeView Roles="Administrator">
|
||||
<Authorized>
|
||||
<MudNavLink Match="NavLinkMatch.Prefix" Href="@(RouteConstance.Package)">مدیریت پکیج</MudNavLink>
|
||||
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
|
||||
|
||||
<MudNavLink Match="NavLinkMatch.Prefix" OnClick="Signout">خروج از حساب</MudNavLink>
|
||||
</MudNavMenu>
|
||||
|
||||
|
||||
|
||||
@code {
|
||||
public async Task Signout()
|
||||
{
|
||||
await LocalStorageService.RemoveItemAsync("AuthToken");
|
||||
Navigation.NavigateTo("/Login");
|
||||
}
|
||||
}
|
||||
0
src/BackOffice/BackOffice/Shared/NavMenu.razor.css
Normal file
0
src/BackOffice/BackOffice/Shared/NavMenu.razor.css
Normal file
22
src/BackOffice/BackOffice/_Imports.razor
Normal file
22
src/BackOffice/BackOffice/_Imports.razor
Normal file
@@ -0,0 +1,22 @@
|
||||
@using System.Net.Http
|
||||
@using System.Net.Http.Json
|
||||
@using Blazored.LocalStorage
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using Microsoft.AspNetCore.Components.WebAssembly.Http
|
||||
@using Microsoft.JSInterop
|
||||
@using BackOffice
|
||||
@using BackOffice.Common.Utilities
|
||||
@using MudBlazor
|
||||
@using Mapster
|
||||
@using DateTimeConverterCL
|
||||
|
||||
|
||||
@attribute [Authorize(Roles = "Administrator, Admin, Author")]
|
||||
|
||||
@inject MudBlazor.IDialogService DialogService
|
||||
@inject MudBlazor.ISnackbar Snackbar
|
||||
@inject IJSRuntime jsRuntime
|
||||
@inject NavigationManager Navigation
|
||||
@inject ILocalStorageService LocalStorageService
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"DetailedErrors": true,
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
9
src/BackOffice/BackOffice/wwwroot/appsettings.json
Normal file
9
src/BackOffice/BackOffice/wwwroot/appsettings.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"GwUrl": "https://localhost:6468",
|
||||
//"GwUrl": "https://localhost:6468",
|
||||
"Authentication": {
|
||||
//"Authority": "https://localhost:5001",
|
||||
"Authority": "https://ids.afrino.co/",
|
||||
"ClientId": "client_backoffice_spa"
|
||||
}
|
||||
}
|
||||
82
src/BackOffice/BackOffice/wwwroot/css/app.css
Normal file
82
src/BackOffice/BackOffice/wwwroot/css/app.css
Normal file
@@ -0,0 +1,82 @@
|
||||
@font-face {
|
||||
font-family: IRANSans;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url('../fonts/eot/IRANSansWeb(FaNum)_Medium.eot');
|
||||
src: url('../fonts/eot/IRANSansWeb(FaNum)_Medium.eot?#iefix') format('embedded-opentype'), /* IE6-8 */
|
||||
url('../fonts/woff2/IRANSansWeb(FaNum)_Medium.woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/
|
||||
url('../fonts/woff/IRANSansWeb(FaNum)_Medium.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/
|
||||
url('../fonts/ttf/IRANSansWeb(FaNum)_Medium.ttf') format('truetype');
|
||||
}
|
||||
|
||||
.loading-progress {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 8rem;
|
||||
height: 8rem;
|
||||
margin: 20vh auto 1rem auto;
|
||||
}
|
||||
|
||||
.loading-progress circle {
|
||||
fill: none;
|
||||
stroke: #e0e0e0;
|
||||
stroke-width: 0.6rem;
|
||||
transform-origin: 50% 50%;
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.loading-progress circle:last-child {
|
||||
stroke: #1b6ec2;
|
||||
stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
|
||||
transition: stroke-dasharray 0.05s ease-in-out;
|
||||
}
|
||||
|
||||
.loading-progress-text {
|
||||
font-family: "Vazir";
|
||||
direction: rtl;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
inset: calc(20vh + 3.25rem) 0 auto 0.2rem;
|
||||
}
|
||||
|
||||
.loading-progress-text:after {
|
||||
content: var(--blazor-load-percentage-text, "بارگذاری ...");
|
||||
}
|
||||
|
||||
.mud-input.mud-input-outlined {
|
||||
background-color: var(--mud-palette-surface);
|
||||
}
|
||||
|
||||
h1:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#blazor-error-ui {
|
||||
background: lightyellow;
|
||||
bottom: 0;
|
||||
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
|
||||
display: none;
|
||||
left: 0;
|
||||
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#blazor-error-ui .dismiss {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
top: 0.5rem;
|
||||
}
|
||||
|
||||
.blazor-error-boundary {
|
||||
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
|
||||
padding: 1rem 1rem 1rem 3.7rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.blazor-error-boundary::after {
|
||||
content: "An error has occurred."
|
||||
}
|
||||
BIN
src/BackOffice/BackOffice/wwwroot/favicon.png
Normal file
BIN
src/BackOffice/BackOffice/wwwroot/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
src/BackOffice/BackOffice/wwwroot/icon-192.png
Normal file
BIN
src/BackOffice/BackOffice/wwwroot/icon-192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
BIN
src/BackOffice/BackOffice/wwwroot/icon-512.png
Normal file
BIN
src/BackOffice/BackOffice/wwwroot/icon-512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
44
src/BackOffice/BackOffice/wwwroot/index.html
Normal file
44
src/BackOffice/BackOffice/wwwroot/index.html
Normal file
@@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>مدیریت آفرینو</title>
|
||||
<base href="/" />
|
||||
<link href="css/app.css" rel="stylesheet" />
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
<link href="manifest.json" rel="manifest" />
|
||||
<link rel="apple-touch-icon" sizes="512x512" href="icon-512.png" />
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="icon-192.png" />
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
|
||||
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
|
||||
<link href="_content/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.css" rel="stylesheet" />
|
||||
|
||||
<!-- If you add any scoped CSS files, uncomment the following to load them
|
||||
<link href="BackOffice.styles.css" rel="stylesheet" /> -->
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app">
|
||||
<svg class="loading-progress">
|
||||
<circle r="40%" cx="50%" cy="50%" />
|
||||
<circle r="40%" cx="50%" cy="50%" />
|
||||
</svg>
|
||||
<div class="loading-progress-text"></div>
|
||||
</div>
|
||||
|
||||
<div id="blazor-error-ui">
|
||||
An unhandled error has occurred.
|
||||
<a href="" class="reload">Reload</a>
|
||||
<a class="dismiss">🗙</a>
|
||||
</div>
|
||||
<script src="_content/MudBlazor/MudBlazor.min.js"></script>
|
||||
<script src="/js/main.js"></script>
|
||||
<script src="/js/quill.js"></script>
|
||||
<script src="_content/Tizzani.MudBlazor.HtmlEditor/quill-blot-formatter.min.js"></script> <!-- optional; for image resize -->
|
||||
<script src="_framework/blazor.webassembly.js"></script>
|
||||
<script>navigator.serviceWorker.register('service-worker.js');</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
8
src/BackOffice/BackOffice/wwwroot/js/main.js
Normal file
8
src/BackOffice/BackOffice/wwwroot/js/main.js
Normal file
@@ -0,0 +1,8 @@
|
||||
function jsSaveAsFile(filename, byteBase64) {
|
||||
var link = document.createElement('a');
|
||||
link.download = filename;
|
||||
link.href = "data:application/octet-stream;base64," + byteBase64;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
3
src/BackOffice/BackOffice/wwwroot/js/quill.js
Normal file
3
src/BackOffice/BackOffice/wwwroot/js/quill.js
Normal file
File diff suppressed because one or more lines are too long
21
src/BackOffice/BackOffice/wwwroot/manifest.json
Normal file
21
src/BackOffice/BackOffice/wwwroot/manifest.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "Afrino",
|
||||
"short_name": "Afrino",
|
||||
"start_url": "./",
|
||||
"display": "standalone",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#03173d",
|
||||
"prefer_related_applications": false,
|
||||
"icons": [
|
||||
{
|
||||
"src": "icon-512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
},
|
||||
{
|
||||
"src": "icon-192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
}
|
||||
]
|
||||
}
|
||||
4
src/BackOffice/BackOffice/wwwroot/service-worker.js
Normal file
4
src/BackOffice/BackOffice/wwwroot/service-worker.js
Normal file
@@ -0,0 +1,4 @@
|
||||
// In development, always fetch from the network and do not enable offline support.
|
||||
// This is because caching would make development more difficult (changes would not
|
||||
// be reflected on the first load after each change).
|
||||
self.addEventListener('fetch', () => { });
|
||||
@@ -0,0 +1,47 @@
|
||||
// Caution! Be sure you understand the caveats before publishing an application with
|
||||
// offline support. See https://aka.ms/blazor-offline-considerations
|
||||
|
||||
self.importScripts('./service-worker-assets.js');
|
||||
self.addEventListener('install', event => event.waitUntil(onInstall(event)));
|
||||
self.addEventListener('activate', event => event.waitUntil(onActivate(event)));
|
||||
self.addEventListener('fetch', event => event.respondWith(onFetch(event)));
|
||||
|
||||
const cacheNamePrefix = 'offline-cache-';
|
||||
const cacheName = `${cacheNamePrefix}${self.assetsManifest.version}`;
|
||||
const offlineAssetsInclude = [ /\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/, /\.blat$/, /\.dat$/ ];
|
||||
const offlineAssetsExclude = [ /^service-worker\.js$/ ];
|
||||
|
||||
async function onInstall(event) {
|
||||
console.info('Service worker: Install');
|
||||
|
||||
// Fetch and cache all matching items from the assets manifest
|
||||
const assetsRequests = self.assetsManifest.assets
|
||||
.filter(asset => offlineAssetsInclude.some(pattern => pattern.test(asset.url)))
|
||||
.filter(asset => !offlineAssetsExclude.some(pattern => pattern.test(asset.url)))
|
||||
.map(asset => new Request(asset.url/*, { integrity: asset.hash, cache: 'no-cache' }*/));
|
||||
await caches.open(cacheName).then(cache => cache.addAll(assetsRequests));
|
||||
}
|
||||
|
||||
async function onActivate(event) {
|
||||
console.info('Service worker: Activate');
|
||||
|
||||
// Delete unused caches
|
||||
const cacheKeys = await caches.keys();
|
||||
await Promise.all(cacheKeys
|
||||
.filter(key => key.startsWith(cacheNamePrefix) && key !== cacheName)
|
||||
.map(key => caches.delete(key)));
|
||||
}
|
||||
|
||||
async function onFetch(event) {
|
||||
let cachedResponse = null;
|
||||
if (event.request.method === 'GET') {
|
||||
// For all navigation requests, try to serve index.html from cache
|
||||
const shouldServeIndexHtml = event.request.mode === 'navigate';
|
||||
|
||||
const request = shouldServeIndexHtml ? 'index.html' : event.request;
|
||||
const cache = await caches.open(cacheName);
|
||||
cachedResponse = await cache.match(request);
|
||||
}
|
||||
|
||||
return cachedResponse || fetch(event.request);
|
||||
}
|
||||
Reference in New Issue
Block a user