@@ -0,0 +1,25 @@ | |||||
**/.classpath | |||||
**/.dockerignore | |||||
**/.env | |||||
**/.git | |||||
**/.gitignore | |||||
**/.project | |||||
**/.settings | |||||
**/.toolstarget | |||||
**/.vs | |||||
**/.vscode | |||||
**/*.*proj.user | |||||
**/*.dbmdl | |||||
**/*.jfm | |||||
**/azds.yaml | |||||
**/bin | |||||
**/charts | |||||
**/docker-compose* | |||||
**/Dockerfile* | |||||
**/node_modules | |||||
**/npm-debug.log | |||||
**/obj | |||||
**/secrets.dev.yaml | |||||
**/values.dev.yaml | |||||
LICENSE | |||||
README.md |
@@ -0,0 +1,63 @@ | |||||
############################################################################### | |||||
# Set default behavior to automatically normalize line endings. | |||||
############################################################################### | |||||
* text=auto | |||||
############################################################################### | |||||
# Set default behavior for command prompt diff. | |||||
# | |||||
# This is need for earlier builds of msysgit that does not have it on by | |||||
# default for csharp files. | |||||
# Note: This is only used by command line | |||||
############################################################################### | |||||
#*.cs diff=csharp | |||||
############################################################################### | |||||
# Set the merge driver for project and solution files | |||||
# | |||||
# Merging from the command prompt will add diff markers to the files if there | |||||
# are conflicts (Merging from VS is not affected by the settings below, in VS | |||||
# the diff markers are never inserted). Diff markers may cause the following | |||||
# file extensions to fail to load in VS. An alternative would be to treat | |||||
# these files as binary and thus will always conflict and require user | |||||
# intervention with every merge. To do so, just uncomment the entries below | |||||
############################################################################### | |||||
#*.sln merge=binary | |||||
#*.csproj merge=binary | |||||
#*.vbproj merge=binary | |||||
#*.vcxproj merge=binary | |||||
#*.vcproj merge=binary | |||||
#*.dbproj merge=binary | |||||
#*.fsproj merge=binary | |||||
#*.lsproj merge=binary | |||||
#*.wixproj merge=binary | |||||
#*.modelproj merge=binary | |||||
#*.sqlproj merge=binary | |||||
#*.wwaproj merge=binary | |||||
############################################################################### | |||||
# behavior for image files | |||||
# | |||||
# image files are treated as binary by default. | |||||
############################################################################### | |||||
#*.jpg binary | |||||
#*.png binary | |||||
#*.gif binary | |||||
############################################################################### | |||||
# diff behavior for common document formats | |||||
# | |||||
# Convert binary document formats to text before diffing them. This feature | |||||
# is only available from the command line. Turn it on by uncommenting the | |||||
# entries below. | |||||
############################################################################### | |||||
#*.doc diff=astextplain | |||||
#*.DOC diff=astextplain | |||||
#*.docx diff=astextplain | |||||
#*.DOCX diff=astextplain | |||||
#*.dot diff=astextplain | |||||
#*.DOT diff=astextplain | |||||
#*.pdf diff=astextplain | |||||
#*.PDF diff=astextplain | |||||
#*.rtf diff=astextplain | |||||
#*.RTF diff=astextplain |
@@ -0,0 +1,364 @@ | |||||
## Ignore Visual Studio temporary files, build results, and | |||||
## files generated by popular Visual Studio add-ons. | |||||
## | |||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore | |||||
# User-specific files | |||||
*.rsuser | |||||
*.suo | |||||
*.user | |||||
*.userosscache | |||||
*.sln.docstates | |||||
*.crt | |||||
# User-specific files (MonoDevelop/Xamarin Studio) | |||||
*.userprefs | |||||
# Mono auto generated files | |||||
mono_crash.* | |||||
# Build results | |||||
[Dd]ebug/ | |||||
[Dd]ebugPublic/ | |||||
[Rr]elease/ | |||||
[Rr]eleases/ | |||||
x64/ | |||||
x86/ | |||||
[Ww][Ii][Nn]32/ | |||||
[Aa][Rr][Mm]/ | |||||
[Aa][Rr][Mm]64/ | |||||
bld/ | |||||
[Bb]in/ | |||||
[Oo]bj/ | |||||
[Oo]ut/ | |||||
[Ll]og/ | |||||
[Ll]ogs/ | |||||
# Visual Studio 2015/2017 cache/options directory | |||||
.vs/ | |||||
# Uncomment if you have tasks that create the project's static files in wwwroot | |||||
#wwwroot/ | |||||
# Visual Studio 2017 auto generated files | |||||
Generated\ Files/ | |||||
# MSTest test Results | |||||
[Tt]est[Rr]esult*/ | |||||
[Bb]uild[Ll]og.* | |||||
# NUnit | |||||
*.VisualState.xml | |||||
TestResult.xml | |||||
nunit-*.xml | |||||
# Build Results of an ATL Project | |||||
[Dd]ebugPS/ | |||||
[Rr]eleasePS/ | |||||
dlldata.c | |||||
# Benchmark Results | |||||
BenchmarkDotNet.Artifacts/ | |||||
# .NET Core | |||||
project.lock.json | |||||
project.fragment.lock.json | |||||
artifacts/ | |||||
# ASP.NET Scaffolding | |||||
ScaffoldingReadMe.txt | |||||
# StyleCop | |||||
StyleCopReport.xml | |||||
# Files built by Visual Studio | |||||
*_i.c | |||||
*_p.c | |||||
*_h.h | |||||
*.ilk | |||||
*.meta | |||||
*.obj | |||||
*.iobj | |||||
*.pch | |||||
*.pdb | |||||
*.ipdb | |||||
*.pgc | |||||
*.pgd | |||||
*.rsp | |||||
*.sbr | |||||
*.tlb | |||||
*.tli | |||||
*.tlh | |||||
*.tmp | |||||
*.tmp_proj | |||||
*_wpftmp.csproj | |||||
*.log | |||||
*.vspscc | |||||
*.vssscc | |||||
.builds | |||||
*.pidb | |||||
*.svclog | |||||
*.scc | |||||
# Chutzpah Test files | |||||
_Chutzpah* | |||||
# Visual C++ cache files | |||||
ipch/ | |||||
*.aps | |||||
*.ncb | |||||
*.opendb | |||||
*.opensdf | |||||
*.sdf | |||||
*.cachefile | |||||
*.VC.db | |||||
*.VC.VC.opendb | |||||
# Visual Studio profiler | |||||
*.psess | |||||
*.vsp | |||||
*.vspx | |||||
*.sap | |||||
# Visual Studio Trace Files | |||||
*.e2e | |||||
# TFS 2012 Local Workspace | |||||
$tf/ | |||||
# Guidance Automation Toolkit | |||||
*.gpState | |||||
# ReSharper is a .NET coding add-in | |||||
_ReSharper*/ | |||||
*.[Rr]e[Ss]harper | |||||
*.DotSettings.user | |||||
# TeamCity is a build add-in | |||||
_TeamCity* | |||||
# DotCover is a Code Coverage Tool | |||||
*.dotCover | |||||
# AxoCover is a Code Coverage Tool | |||||
.axoCover/* | |||||
!.axoCover/settings.json | |||||
# Coverlet is a free, cross platform Code Coverage Tool | |||||
coverage*.json | |||||
coverage*.xml | |||||
coverage*.info | |||||
# Visual Studio code coverage results | |||||
*.coverage | |||||
*.coveragexml | |||||
# NCrunch | |||||
_NCrunch_* | |||||
.*crunch*.local.xml | |||||
nCrunchTemp_* | |||||
# MightyMoose | |||||
*.mm.* | |||||
AutoTest.Net/ | |||||
# Web workbench (sass) | |||||
.sass-cache/ | |||||
# Installshield output folder | |||||
[Ee]xpress/ | |||||
# DocProject is a documentation generator add-in | |||||
DocProject/buildhelp/ | |||||
DocProject/Help/*.HxT | |||||
DocProject/Help/*.HxC | |||||
DocProject/Help/*.hhc | |||||
DocProject/Help/*.hhk | |||||
DocProject/Help/*.hhp | |||||
DocProject/Help/Html2 | |||||
DocProject/Help/html | |||||
# Click-Once directory | |||||
publish/ | |||||
# Publish Web Output | |||||
*.[Pp]ublish.xml | |||||
*.azurePubxml | |||||
# Note: Comment the next line if you want to checkin your web deploy settings, | |||||
# but database connection strings (with potential passwords) will be unencrypted | |||||
*.pubxml | |||||
*.publishproj | |||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to | |||||
# checkin your Azure Web App publish settings, but sensitive information contained | |||||
# in these scripts will be unencrypted | |||||
PublishScripts/ | |||||
# NuGet Packages | |||||
*.nupkg | |||||
# NuGet Symbol Packages | |||||
*.snupkg | |||||
# The packages folder can be ignored because of Package Restore | |||||
**/[Pp]ackages/* | |||||
# except build/, which is used as an MSBuild target. | |||||
!**/[Pp]ackages/build/ | |||||
# Uncomment if necessary however generally it will be regenerated when needed | |||||
#!**/[Pp]ackages/repositories.config | |||||
# NuGet v3's project.json files produces more ignorable files | |||||
*.nuget.props | |||||
*.nuget.targets | |||||
# Microsoft Azure Build Output | |||||
csx/ | |||||
*.build.csdef | |||||
# Microsoft Azure Emulator | |||||
ecf/ | |||||
rcf/ | |||||
# Windows Store app package directories and files | |||||
AppPackages/ | |||||
BundleArtifacts/ | |||||
Package.StoreAssociation.xml | |||||
_pkginfo.txt | |||||
*.appx | |||||
*.appxbundle | |||||
*.appxupload | |||||
# Visual Studio cache files | |||||
# files ending in .cache can be ignored | |||||
*.[Cc]ache | |||||
# but keep track of directories ending in .cache | |||||
!?*.[Cc]ache/ | |||||
# Others | |||||
ClientBin/ | |||||
~$* | |||||
*~ | |||||
*.dbmdl | |||||
*.dbproj.schemaview | |||||
*.jfm | |||||
*.pfx | |||||
*.publishsettings | |||||
orleans.codegen.cs | |||||
# Including strong name files can present a security risk | |||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424) | |||||
#*.snk | |||||
# Since there are multiple workflows, uncomment next line to ignore bower_components | |||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) | |||||
#bower_components/ | |||||
# RIA/Silverlight projects | |||||
Generated_Code/ | |||||
# Backup & report files from converting an old project file | |||||
# to a newer Visual Studio version. Backup files are not needed, | |||||
# because we have git ;-) | |||||
_UpgradeReport_Files/ | |||||
Backup*/ | |||||
UpgradeLog*.XML | |||||
UpgradeLog*.htm | |||||
ServiceFabricBackup/ | |||||
*.rptproj.bak | |||||
# SQL Server files | |||||
*.mdf | |||||
*.ldf | |||||
*.ndf | |||||
# Business Intelligence projects | |||||
*.rdl.data | |||||
*.bim.layout | |||||
*.bim_*.settings | |||||
*.rptproj.rsuser | |||||
*- [Bb]ackup.rdl | |||||
*- [Bb]ackup ([0-9]).rdl | |||||
*- [Bb]ackup ([0-9][0-9]).rdl | |||||
# Microsoft Fakes | |||||
FakesAssemblies/ | |||||
# GhostDoc plugin setting file | |||||
*.GhostDoc.xml | |||||
# Node.js Tools for Visual Studio | |||||
.ntvs_analysis.dat | |||||
node_modules/ | |||||
# Visual Studio 6 build log | |||||
*.plg | |||||
# Visual Studio 6 workspace options file | |||||
*.opt | |||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) | |||||
*.vbw | |||||
# Visual Studio LightSwitch build output | |||||
**/*.HTMLClient/GeneratedArtifacts | |||||
**/*.DesktopClient/GeneratedArtifacts | |||||
**/*.DesktopClient/ModelManifest.xml | |||||
**/*.Server/GeneratedArtifacts | |||||
**/*.Server/ModelManifest.xml | |||||
_Pvt_Extensions | |||||
# Paket dependency manager | |||||
.paket/paket.exe | |||||
paket-files/ | |||||
# FAKE - F# Make | |||||
.fake/ | |||||
# CodeRush personal settings | |||||
.cr/personal | |||||
# Python Tools for Visual Studio (PTVS) | |||||
__pycache__/ | |||||
*.pyc | |||||
# Cake - Uncomment if you are using it | |||||
# tools/** | |||||
# !tools/packages.config | |||||
# Tabs Studio | |||||
*.tss | |||||
# Telerik's JustMock configuration file | |||||
*.jmconfig | |||||
# BizTalk build output | |||||
*.btp.cs | |||||
*.btm.cs | |||||
*.odx.cs | |||||
*.xsd.cs | |||||
# OpenCover UI analysis results | |||||
OpenCover/ | |||||
# Azure Stream Analytics local run output | |||||
ASALocalRun/ | |||||
# MSBuild Binary and Structured Log | |||||
*.binlog | |||||
# NVidia Nsight GPU debugger configuration file | |||||
*.nvuser | |||||
# MFractors (Xamarin productivity tool) working folder | |||||
.mfractor/ | |||||
# Local History for Visual Studio | |||||
.localhistory/ | |||||
# BeatPulse healthcheck temp database | |||||
healthchecksdb | |||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017 | |||||
MigrationBackup/ | |||||
# Ionide (cross platform F# VS Code tools) working folder | |||||
.ionide/ | |||||
# Fody - auto-generated XML schema | |||||
FodyWeavers.xsd | |||||
Panel/Properties/launchSettings.json |
@@ -0,0 +1,376 @@ | |||||
# Default ignored files | |||||
/shelf/ | |||||
/workspace.xml | |||||
# Rider ignored files | |||||
/modules.xml | |||||
/projectSettingsUpdater.xml | |||||
/.idea.DadYar.iml | |||||
/contentModel.xml | |||||
# Editor-based HTTP Client requests | |||||
/httpRequests/ | |||||
# Datasource local storage ignored files | |||||
/dataSources/ | |||||
/dataSources.local.xml | |||||
## Ignore Visual Studio temporary files, build results, and | |||||
## files generated by popular Visual Studio add-ons. | |||||
## | |||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore | |||||
# User-specific files | |||||
*.rsuser | |||||
*.suo | |||||
*.user | |||||
*.userosscache | |||||
*.sln.docstates | |||||
# User-specific files (MonoDevelop/Xamarin Studio) | |||||
*.userprefs | |||||
# Mono auto generated files | |||||
mono_crash.* | |||||
# Build results | |||||
[Dd]ebug/ | |||||
[Dd]ebugPublic/ | |||||
[Rr]elease/ | |||||
[Rr]eleases/ | |||||
x64/ | |||||
x86/ | |||||
[Ww][Ii][Nn]32/ | |||||
[Aa][Rr][Mm]/ | |||||
[Aa][Rr][Mm]64/ | |||||
bld/ | |||||
[Bb]in/ | |||||
[Oo]bj/ | |||||
[Oo]ut/ | |||||
[Ll]og/ | |||||
[Ll]ogs/ | |||||
# Visual Studio 2015/2017 cache/options directory | |||||
.vs/ | |||||
# Uncomment if you have tasks that create the project's static files in wwwroot | |||||
#wwwroot/ | |||||
# Visual Studio 2017 auto generated files | |||||
Generated\ Files/ | |||||
# MSTest test Results | |||||
[Tt]est[Rr]esult*/ | |||||
[Bb]uild[Ll]og.* | |||||
# NUnit | |||||
*.VisualState.xml | |||||
TestResult.xml | |||||
nunit-*.xml | |||||
# Build Results of an ATL Project | |||||
[Dd]ebugPS/ | |||||
[Rr]eleasePS/ | |||||
dlldata.c | |||||
# Benchmark Results | |||||
BenchmarkDotNet.Artifacts/ | |||||
# .NET Core | |||||
project.lock.json | |||||
project.fragment.lock.json | |||||
artifacts/ | |||||
# ASP.NET Scaffolding | |||||
ScaffoldingReadMe.txt | |||||
# StyleCop | |||||
StyleCopReport.xml | |||||
# Files built by Visual Studio | |||||
*_i.c | |||||
*_p.c | |||||
*_h.h | |||||
*.ilk | |||||
*.meta | |||||
*.obj | |||||
*.iobj | |||||
*.pch | |||||
*.pdb | |||||
*.ipdb | |||||
*.pgc | |||||
*.pgd | |||||
*.rsp | |||||
*.sbr | |||||
*.tlb | |||||
*.tli | |||||
*.tlh | |||||
*.tmp | |||||
*.tmp_proj | |||||
*_wpftmp.csproj | |||||
*.log | |||||
*.vspscc | |||||
*.vssscc | |||||
.builds | |||||
*.pidb | |||||
*.svclog | |||||
*.scc | |||||
# Chutzpah Test files | |||||
_Chutzpah* | |||||
# Visual C++ cache files | |||||
ipch/ | |||||
*.aps | |||||
*.ncb | |||||
*.opendb | |||||
*.opensdf | |||||
*.sdf | |||||
*.cachefile | |||||
*.VC.db | |||||
*.VC.VC.opendb | |||||
# Visual Studio profiler | |||||
*.psess | |||||
*.vsp | |||||
*.vspx | |||||
*.sap | |||||
# Visual Studio Trace Files | |||||
*.e2e | |||||
# TFS 2012 Local Workspace | |||||
$tf/ | |||||
# Guidance Automation Toolkit | |||||
*.gpState | |||||
# ReSharper is a .NET coding add-in | |||||
_ReSharper*/ | |||||
*.[Rr]e[Ss]harper | |||||
*.DotSettings.user | |||||
# TeamCity is a build add-in | |||||
_TeamCity* | |||||
# DotCover is a Code Coverage Tool | |||||
*.dotCover | |||||
# AxoCover is a Code Coverage Tool | |||||
.axoCover/* | |||||
!.axoCover/settings.json | |||||
# Coverlet is a free, cross platform Code Coverage Tool | |||||
coverage*.json | |||||
coverage*.xml | |||||
coverage*.info | |||||
# Visual Studio code coverage results | |||||
*.coverage | |||||
*.coveragexml | |||||
# NCrunch | |||||
_NCrunch_* | |||||
.*crunch*.local.xml | |||||
nCrunchTemp_* | |||||
# MightyMoose | |||||
*.mm.* | |||||
AutoTest.Net/ | |||||
# Web workbench (sass) | |||||
.sass-cache/ | |||||
# Installshield output folder | |||||
[Ee]xpress/ | |||||
# DocProject is a documentation generator add-in | |||||
DocProject/buildhelp/ | |||||
DocProject/Help/*.HxT | |||||
DocProject/Help/*.HxC | |||||
DocProject/Help/*.hhc | |||||
DocProject/Help/*.hhk | |||||
DocProject/Help/*.hhp | |||||
DocProject/Help/Html2 | |||||
DocProject/Help/html | |||||
# Click-Once directory | |||||
publish/ | |||||
# Publish Web Output | |||||
*.[Pp]ublish.xml | |||||
*.azurePubxml | |||||
# Note: Comment the next line if you want to checkin your web deploy settings, | |||||
# but database connection strings (with potential passwords) will be unencrypted | |||||
*.pubxml | |||||
*.publishproj | |||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to | |||||
# checkin your Azure Web App publish settings, but sensitive information contained | |||||
# in these scripts will be unencrypted | |||||
PublishScripts/ | |||||
# NuGet Packages | |||||
*.nupkg | |||||
# NuGet Symbol Packages | |||||
*.snupkg | |||||
# The packages folder can be ignored because of Package Restore | |||||
**/[Pp]ackages/* | |||||
# except build/, which is used as an MSBuild target. | |||||
!**/[Pp]ackages/build/ | |||||
# Uncomment if necessary however generally it will be regenerated when needed | |||||
#!**/[Pp]ackages/repositories.config | |||||
# NuGet v3's project.json files produces more ignorable files | |||||
*.nuget.props | |||||
*.nuget.targets | |||||
# Microsoft Azure Build Output | |||||
csx/ | |||||
*.build.csdef | |||||
# Microsoft Azure Emulator | |||||
ecf/ | |||||
rcf/ | |||||
# Windows Store app package directories and files | |||||
AppPackages/ | |||||
BundleArtifacts/ | |||||
Package.StoreAssociation.xml | |||||
_pkginfo.txt | |||||
*.appx | |||||
*.appxbundle | |||||
*.appxupload | |||||
# Visual Studio cache files | |||||
# files ending in .cache can be ignored | |||||
*.[Cc]ache | |||||
# but keep track of directories ending in .cache | |||||
!?*.[Cc]ache/ | |||||
# Others | |||||
ClientBin/ | |||||
~$* | |||||
*~ | |||||
*.dbmdl | |||||
*.dbproj.schemaview | |||||
*.jfm | |||||
*.pfx | |||||
*.publishsettings | |||||
orleans.codegen.cs | |||||
# Including strong name files can present a security risk | |||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424) | |||||
#*.snk | |||||
# Since there are multiple workflows, uncomment next line to ignore bower_components | |||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) | |||||
#bower_components/ | |||||
# RIA/Silverlight projects | |||||
Generated_Code/ | |||||
# Backup & report files from converting an old project file | |||||
# to a newer Visual Studio version. Backup files are not needed, | |||||
# because we have git ;-) | |||||
_UpgradeReport_Files/ | |||||
Backup*/ | |||||
UpgradeLog*.XML | |||||
UpgradeLog*.htm | |||||
ServiceFabricBackup/ | |||||
*.rptproj.bak | |||||
# SQL Server files | |||||
*.mdf | |||||
*.ldf | |||||
*.ndf | |||||
# Business Intelligence projects | |||||
*.rdl.data | |||||
*.bim.layout | |||||
*.bim_*.settings | |||||
*.rptproj.rsuser | |||||
*- [Bb]ackup.rdl | |||||
*- [Bb]ackup ([0-9]).rdl | |||||
*- [Bb]ackup ([0-9][0-9]).rdl | |||||
# Microsoft Fakes | |||||
FakesAssemblies/ | |||||
# GhostDoc plugin setting file | |||||
*.GhostDoc.xml | |||||
# Node.js Tools for Visual Studio | |||||
.ntvs_analysis.dat | |||||
node_modules/ | |||||
# Visual Studio 6 build log | |||||
*.plg | |||||
# Visual Studio 6 workspace options file | |||||
*.opt | |||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) | |||||
*.vbw | |||||
# Visual Studio LightSwitch build output | |||||
**/*.HTMLClient/GeneratedArtifacts | |||||
**/*.DesktopClient/GeneratedArtifacts | |||||
**/*.DesktopClient/ModelManifest.xml | |||||
**/*.Server/GeneratedArtifacts | |||||
**/*.Server/ModelManifest.xml | |||||
_Pvt_Extensions | |||||
# Paket dependency manager | |||||
.paket/paket.exe | |||||
paket-files/ | |||||
# FAKE - F# Make | |||||
.fake/ | |||||
# CodeRush personal settings | |||||
.cr/personal | |||||
# Python Tools for Visual Studio (PTVS) | |||||
__pycache__/ | |||||
*.pyc | |||||
# Cake - Uncomment if you are using it | |||||
# tools/** | |||||
# !tools/packages.config | |||||
# Tabs Studio | |||||
*.tss | |||||
# Telerik's JustMock configuration file | |||||
*.jmconfig | |||||
# BizTalk build output | |||||
*.btp.cs | |||||
*.btm.cs | |||||
*.odx.cs | |||||
*.xsd.cs | |||||
# OpenCover UI analysis results | |||||
OpenCover/ | |||||
# Azure Stream Analytics local run output | |||||
ASALocalRun/ | |||||
# MSBuild Binary and Structured Log | |||||
*.binlog | |||||
# NVidia Nsight GPU debugger configuration file | |||||
*.nvuser | |||||
# MFractors (Xamarin productivity tool) working folder | |||||
.mfractor/ | |||||
# Local History for Visual Studio | |||||
.localhistory/ | |||||
# BeatPulse healthcheck temp database | |||||
healthchecksdb | |||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017 | |||||
MigrationBackup/ | |||||
# Ionide (cross platform F# VS Code tools) working folder | |||||
.ionide/ | |||||
# Fody - auto-generated XML schema | |||||
FodyWeavers.xsd |
@@ -0,0 +1,12 @@ | |||||
{ | |||||
"version": 1, | |||||
"isRoot": true, | |||||
"tools": { | |||||
"dotnet-ef": { | |||||
"version": "5.0.10", | |||||
"commands": [ | |||||
"dotnet-ef" | |||||
] | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,62 @@ | |||||
<Project Sdk="Microsoft.NET.Sdk.Web"> | |||||
<PropertyGroup> | |||||
<TargetFramework>net8.0</TargetFramework> | |||||
<UserSecretsId>cb5a9876-4532-46d5-b632-ec5d04c898d0</UserSecretsId> | |||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS> | |||||
</PropertyGroup> | |||||
<ItemGroup> | |||||
<Content Remove="wwwroot\UploadedFiles\48780aa1-716a-4d3f-b280-56b072dc73ee.png" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<PackageReference Include="AutoMapper" Version="14.0.0" /> | |||||
<PackageReference Include="coverlet.collector" Version="6.0.4"> | |||||
<PrivateAssets>all</PrivateAssets> | |||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | |||||
</PackageReference> | |||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.8" /> | |||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.8" /> | |||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="8.0.8" /> | |||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.8" /> | |||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.8" /> | |||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.8"> | |||||
<PrivateAssets>all</PrivateAssets> | |||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | |||||
</PackageReference> | |||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" /> | |||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" /> | |||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> | |||||
<PackageReference Include="QRCoder" Version="1.6.0" /> | |||||
<PackageReference Include="Scrutor.AspNetCore" Version="3.3.0" /> | |||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" /> | |||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.2.0" /> | |||||
<PackageReference Include="System.Drawing.Common" Version="8.0.8" /> | |||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.1.0" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<ProjectReference Include="..\Core\Core.csproj" /> | |||||
<ProjectReference Include="..\Domain\Domain.csproj" /> | |||||
<ProjectReference Include="..\Infrastructure\Infrastructure.csproj" /> | |||||
<ProjectReference Include="..\Models\Models.csproj" /> | |||||
<ProjectReference Include="..\Services\Services.csproj" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<EditorConfigFiles Remove="D:\projects\DadYar\git\Api\.editorconfig" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<None Include="D:\projects\DadYar\git\Api\.editorconfig" /> | |||||
<None Include="wwwroot\UploadedFiles\48780aa1-716a-4d3f-b280-56b072dc73ee.png" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<InternalsVisibleTo Include="Tests" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<Folder Include="UploadedFiles\" /> | |||||
</ItemGroup> | |||||
</Project> |
@@ -0,0 +1,28 @@ | |||||
using AutoMapper; | |||||
using Domain.BaseData; | |||||
using Domain.Identity; | |||||
using Models.BaseData; | |||||
using Models.Identity; | |||||
namespace Api | |||||
{ | |||||
public class AutoMapperConfig : Profile | |||||
{ | |||||
public AutoMapperConfig() | |||||
{ | |||||
// Add as many of these lines as you need to map your objects | |||||
CreateMap<Account, AccountModel>(); | |||||
CreateMap<Account, CheckLoginResponse>(); | |||||
CreateMap<Plant, PlantViewModel>(); | |||||
CreateMap<Plant, SavePlantViewModel>(); | |||||
CreateMap<SavePlantViewModel, Plant>(); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,96 @@ | |||||
using Domain.Identity; | |||||
using Infrastructure; | |||||
using Microsoft.AspNetCore.Http; | |||||
using Microsoft.AspNetCore.Mvc; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
using Models.Identity; | |||||
using Services.Identity; | |||||
using System; | |||||
using System.Threading.Tasks; | |||||
using Microsoft.AspNetCore.Cors; | |||||
using Microsoft.AspNetCore.Hosting; | |||||
namespace Api.Controllers | |||||
{ | |||||
[ApiController] | |||||
[EnableCors("AllowAll")] | |||||
public class AuthController : BaseController | |||||
{ | |||||
private readonly IAuthService authService; | |||||
private readonly IWebHostEnvironment _hostingEnvironment; | |||||
public AuthController(IServiceProvider serviceProvider) : base(serviceProvider) | |||||
{ | |||||
authService=serviceProvider.GetService<IAuthService>(); | |||||
} | |||||
[HttpPost] | |||||
[ProducesResponseType(typeof(OtpRequestResult), StatusCodes.Status200OK)] | |||||
public async Task<IActionResult> SignUp([FromBody] SignUpModel model) | |||||
{ | |||||
var existed = authService.GetByUsername(model.Email) != null; | |||||
if (existed) | |||||
{ | |||||
return BadRequest("Email is already registered!"); | |||||
} | |||||
var account = new Account | |||||
{ | |||||
Email = model.Email, | |||||
Username = model.Email, | |||||
Password = model.Password, | |||||
Name = model.Name | |||||
}; | |||||
authService.Add(account); | |||||
var token = await authService.GenerateLoginToken(account); | |||||
return Ok(new OtpRequestResult()); | |||||
} | |||||
[HttpPost] | |||||
[ProducesResponseType(typeof(PlainToken), StatusCodes.Status200OK)] | |||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)] | |||||
public async Task<IActionResult> Login([FromBody] LoginModel model) | |||||
{ | |||||
var account = await authService.GetByUsernameAndPassword(model.Username, model.Password); | |||||
if (account == null) return Unauthorized(); | |||||
var loginToken = await authService.GenerateLoginToken(account); | |||||
var plainToken = await authService.GeneratePlainToken(account, loginToken.Token); | |||||
if (plainToken == null) return Unauthorized(); | |||||
return Ok(plainToken); | |||||
} | |||||
[HttpGet] | |||||
[ProducesResponseType(typeof(CheckLoginResponse), statusCode: StatusCodes.Status200OK)] | |||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)] | |||||
public IActionResult Check() | |||||
{ | |||||
var u = User; | |||||
try | |||||
{ | |||||
var res = Mapper.Map<CheckLoginResponse>(CurrentAccount); | |||||
try | |||||
{ | |||||
res.IsDeveloper = User.IsInRole(Consts.Developer); | |||||
} | |||||
catch | |||||
{ | |||||
} | |||||
return Ok(res); | |||||
} | |||||
catch (Exception) | |||||
{ | |||||
return Unauthorized(); | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,175 @@ | |||||
using System; | |||||
using System.ComponentModel; | |||||
using System.IO; | |||||
using System.Security.Claims; | |||||
using System.Threading.Tasks; | |||||
using AutoMapper; | |||||
using Azure.Core; | |||||
using Domain.Identity; | |||||
using Microsoft.AspNetCore.Cors; | |||||
using Microsoft.AspNetCore.Hosting; | |||||
using Microsoft.AspNetCore.Http; | |||||
using Microsoft.AspNetCore.Mvc; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
using Microsoft.Extensions.Hosting; | |||||
using Services.Identity; | |||||
using SixLabors.ImageSharp; | |||||
using SixLabors.ImageSharp.Formats; | |||||
using SixLabors.ImageSharp.Formats.Png; | |||||
using SixLabors.ImageSharp.Processing; | |||||
using Image = SixLabors.ImageSharp.Image; | |||||
namespace Api.Controllers | |||||
{ | |||||
[Route("api/[controller]/[action]")] | |||||
[ApiController] | |||||
[EnableCors("AllowAll")] | |||||
public class BaseController : ControllerBase | |||||
{ | |||||
private readonly AutoMapper.IMapper _mapper; | |||||
private readonly IWebHostEnvironment _environment; | |||||
private readonly IAuthService authService; | |||||
private readonly IServiceProvider serviceProvider; | |||||
//response messages | |||||
public BaseController(IServiceProvider serviceProvider) | |||||
{ | |||||
_mapper = serviceProvider.GetService<IMapper>(); | |||||
_environment = serviceProvider.GetService<IWebHostEnvironment>(); | |||||
authService = serviceProvider.GetService<IAuthService>(); | |||||
} | |||||
public IMapper Mapper => _mapper; | |||||
private T GetClaim<T>(string type) | |||||
{ | |||||
var value = User.FindFirstValue(type) ?? ""; | |||||
var converter = TypeDescriptor.GetConverter(typeof(T)); | |||||
try | |||||
{ | |||||
return (T)converter.ConvertFromString(value); | |||||
} | |||||
catch | |||||
{ | |||||
return default; | |||||
} | |||||
} | |||||
private Account _currentAccount; | |||||
public Account CurrentAccount | |||||
{ | |||||
get | |||||
{ | |||||
if (_currentAccount == null) | |||||
_currentAccount = authService.GetByUsername(CurrentClaim.Username); | |||||
return _currentAccount; | |||||
} | |||||
} | |||||
private CustomUser GetUser() | |||||
{ | |||||
return new CustomUser | |||||
{ | |||||
Id = GetClaim<Guid>(ITokenIssuer.Claims.Id), | |||||
Name = GetClaim<string>(ITokenIssuer.Claims.Name), | |||||
Username = GetClaim<string>(ITokenIssuer.Claims.Username), | |||||
Roles = GetClaim<string>(ClaimTypes.Role).Split(',') | |||||
}; | |||||
} | |||||
public CustomUser CurrentClaim => GetUser(); | |||||
/// <summary> | |||||
/// get images as byte array and returns maximun 1000 pixle base64 | |||||
/// </summary> | |||||
/// <param name="inStream"></param> | |||||
/// <returns></returns> | |||||
[ApiExplorerSettings(IgnoreApi = true)] | |||||
public byte[] ResizeImage(byte[] inStream, int maxWidth = 1000) | |||||
{ | |||||
using (var ms = new MemoryStream()) | |||||
{ | |||||
using (Image image = Image.Load(inStream)) | |||||
{ | |||||
if (image.Width < maxWidth) | |||||
{ | |||||
image.SaveAsPng(ms); | |||||
return ms.ToArray(); | |||||
} | |||||
var ratio = (double)(maxWidth) / image.Width; | |||||
int width = image.Width; | |||||
int height = (int)(image.Height * ratio); | |||||
image.Mutate(x => x.Resize(width, height)); | |||||
image.SaveAsPng(ms); | |||||
return ms.ToArray(); | |||||
} | |||||
} | |||||
} | |||||
[ApiExplorerSettings(IgnoreApi = true)] | |||||
private string GetUniqueFileName(string fileName) | |||||
{ | |||||
fileName = Path.GetFileName(fileName); | |||||
return string.Concat(Path.GetFileNameWithoutExtension(fileName) | |||||
, "_" | |||||
, Guid.NewGuid().ToString().AsSpan(0, 4) | |||||
, Path.GetExtension(fileName)); | |||||
} | |||||
/// <summary> | |||||
/// Saves base64 as a PNG with max 1500px width or height | |||||
/// </summary> | |||||
/// <param name="fileContent">base64 image</param> | |||||
/// <param name="fileName">New file=empty</param> | |||||
/// <returns>File Name(all of files stores in UploadedFiles folder </returns> | |||||
[ApiExplorerSettings(IgnoreApi = true)] | |||||
protected string SaveImageFromBase64(string fileContent, string fileName = "") | |||||
{ | |||||
fileName = string.IsNullOrEmpty(fileName) ? Guid.NewGuid().ToString() + ".png" : fileName; | |||||
string outputPath = $"{_environment.ContentRootPath}\\UploadedFiles\\"; | |||||
if (!System.IO.Directory.Exists(outputPath)) | |||||
System.IO.Directory.CreateDirectory(outputPath); | |||||
// Decode Base64 string to byte array | |||||
byte[] imageBytes = Convert.FromBase64String(fileContent); | |||||
using (MemoryStream ms = new MemoryStream(imageBytes)) | |||||
using (Image image = Image.Load(ms)) | |||||
{ | |||||
// Resize if width > 1500, keeping aspect ratio | |||||
if (image.Width >= image.Height) | |||||
{ | |||||
if (image.Width > 1500) | |||||
{ | |||||
int newWidth = 1500; | |||||
int newHeight = (int)((double)newWidth / image.Width * image.Height); | |||||
image.Mutate(x => x.Resize(newWidth, newHeight)); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
if (image.Height > 1500) | |||||
{ | |||||
int newHeight = 1500; | |||||
int newWidth = (int)((double)newHeight / image.Height * image.Width); | |||||
image.Mutate(x => x.Resize(newWidth, newHeight)); | |||||
} | |||||
} | |||||
// Save as PNG | |||||
image.Save($"{outputPath}{fileName}", new PngEncoder()); | |||||
return fileName; | |||||
} | |||||
} | |||||
} | |||||
public class CustomUser | |||||
{ | |||||
public string Name { get; set; } | |||||
public string Username { get; set; } | |||||
public Guid Id { get; set; } | |||||
public string[] Roles { get; set; } | |||||
} | |||||
} |
@@ -0,0 +1,55 @@ | |||||
using Domain.BaseData; | |||||
using Infrastructure; | |||||
using Microsoft.AspNetCore.Authorization; | |||||
using Microsoft.AspNetCore.Mvc; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
using Models.BaseData; | |||||
using Services.BaseData; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
namespace Api.Controllers | |||||
{ | |||||
[Authorize(Roles = Consts.Developer)] | |||||
public class PlantsController : BaseController | |||||
{ | |||||
private readonly IPlantsService plantsService; | |||||
public PlantsController(IServiceProvider serviceProvider) : base(serviceProvider) | |||||
{ | |||||
plantsService = serviceProvider.GetService<IPlantsService>(); | |||||
} | |||||
[HttpGet] | |||||
public IActionResult List() | |||||
{ | |||||
var dbList = plantsService.GetQueryable().ToList(); | |||||
return Ok(Mapper.Map<List<PlantViewModel>>(dbList)); | |||||
} | |||||
[HttpPost] | |||||
public IActionResult Add(SavePlantViewModel model) | |||||
{ | |||||
var dbModel = Mapper.Map<Plant>(model); | |||||
var imageName=SaveImageFromBase64(model.Base64Icon); | |||||
dbModel.ImageFileName = imageName; | |||||
plantsService.Add(dbModel); | |||||
return Ok(); | |||||
} | |||||
[HttpPut] | |||||
public IActionResult Update(PlantViewModel model) | |||||
{ | |||||
var dbModel = plantsService.GetById(model.Id.Value); | |||||
Mapper.Map(model, dbModel); | |||||
plantsService.Update(dbModel); | |||||
return Ok(); | |||||
} | |||||
[HttpDelete("{id}")] | |||||
public IActionResult Delete(Guid id) | |||||
{ | |||||
plantsService.Delete(id); | |||||
return Ok(); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,97 @@ | |||||
using Api.Start; | |||||
using Core.Db; | |||||
using Microsoft.AspNetCore.Builder; | |||||
using Microsoft.EntityFrameworkCore; | |||||
using Microsoft.OpenApi.Models; | |||||
using System.Reflection; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
using System.Collections.Generic; | |||||
using Microsoft.Extensions.FileProviders; | |||||
using System.IO; | |||||
var builder = WebApplication.CreateBuilder(args); | |||||
var Configuration = builder.Configuration; | |||||
//MVC: | |||||
builder.Services.AddControllersWithViews(); | |||||
//Swagger: | |||||
builder.Services.AddSwaggerGen(c => | |||||
{ | |||||
c.SwaggerDoc("v1", new OpenApiInfo { Title = "API", Version = "v1" }); | |||||
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme | |||||
{ | |||||
Description = @"JWT Authorization header using the bearer scheme. | |||||
Enter 'Bearer' [space] and then your token in the text input below. | |||||
Example: 'Bearer 12345abcdef'", | |||||
Name = "Authorization", | |||||
In = ParameterLocation.Header, | |||||
Scheme = "Bearer", | |||||
Type = SecuritySchemeType.Http | |||||
}); | |||||
c.AddSecurityRequirement(new OpenApiSecurityRequirement | |||||
{{new OpenApiSecurityScheme | |||||
{ | |||||
Reference = new OpenApiReference | |||||
{ | |||||
Type = ReferenceType.SecurityScheme, | |||||
Id = "Bearer" | |||||
}, | |||||
Name = "Bearer", | |||||
In = ParameterLocation.Header, | |||||
}, new List<string>() | |||||
}}); | |||||
} | |||||
); | |||||
#region Required Services & DI | |||||
builder.Services.RegisterDependencies(Configuration); | |||||
builder.Services.RegisterAuth(Configuration); | |||||
#endregion | |||||
builder.Services.AddCors(builder => | |||||
{ | |||||
builder.AddPolicy("AllowAll", options => | |||||
{ | |||||
options.AllowAnyHeader() | |||||
.AllowAnyOrigin() | |||||
.AllowAnyMethod() | |||||
.AllowAnyHeader(); | |||||
}); | |||||
}); | |||||
var app = builder.Build(); | |||||
app.UseHttpsRedirection(); | |||||
app.UseRouting(); | |||||
app.UseCors("AllowAll"); | |||||
app.ConfigureAuthentication(); | |||||
app.UseSwagger(); | |||||
app.UseSwaggerUI(); | |||||
app.MapControllerRoute( | |||||
name: "default", | |||||
pattern: "{controller=Home}/{action=Index}/{id?}"); | |||||
app.MapControllerRoute( | |||||
name: "pages", | |||||
pattern: "{controller=Home}/{pageName}"); | |||||
app.UseDeveloperExceptionPage(); | |||||
app.UseStaticFiles(); | |||||
app.UseStaticFiles(new StaticFileOptions | |||||
{ | |||||
FileProvider = new PhysicalFileProvider( | |||||
Path.Combine(builder.Environment.ContentRootPath, "UploadedFiles")), | |||||
RequestPath = "/UploadedFiles" | |||||
}); | |||||
app.Run(); |
@@ -0,0 +1,38 @@ | |||||
{ | |||||
"profiles": { | |||||
"IIS Express": { | |||||
"commandName": "IISExpress", | |||||
"launchBrowser": true, | |||||
"launchUrl": "swagger", | |||||
"environmentVariables": { | |||||
"ASPNETCORE_ENVIRONMENT": "Development" | |||||
} | |||||
}, | |||||
"Api": { | |||||
"commandName": "Project", | |||||
"launchBrowser": true, | |||||
"launchUrl": "swagger", | |||||
"environmentVariables": { | |||||
"ASPNETCORE_ENVIRONMENT": "Development" | |||||
}, | |||||
"dotnetRunMessages": "true", | |||||
"applicationUrl": "https://localhost:5001;http://localhost:5000" | |||||
}, | |||||
"Docker": { | |||||
"commandName": "Docker", | |||||
"launchBrowser": true, | |||||
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger", | |||||
"publishAllPorts": true, | |||||
"useSSL": true | |||||
} | |||||
}, | |||||
"$schema": "http://json.schemastore.org/launchsettings.json", | |||||
"iisSettings": { | |||||
"windowsAuthentication": false, | |||||
"anonymousAuthentication": true, | |||||
"iisExpress": { | |||||
"applicationUrl": "http://localhost:21424", | |||||
"sslPort": 44331 | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,23 @@ | |||||
using System.Collections.Generic; | |||||
using Microsoft.OpenApi.Models; | |||||
using Swashbuckle.AspNetCore.SwaggerGen; | |||||
namespace Api.Start | |||||
{ | |||||
public class AddRequiredHeaderParameter : IOperationFilter | |||||
{ | |||||
public void Apply(OpenApiOperation operation, OperationFilterContext context) | |||||
{ | |||||
if (operation.Parameters == null) | |||||
operation.Parameters = new List<OpenApiParameter>(); | |||||
operation.Parameters.Add(new OpenApiParameter | |||||
{ | |||||
Name = "X-Version", | |||||
In = ParameterLocation.Header, | |||||
Required = false, | |||||
}); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,51 @@ | |||||
using Microsoft.AspNetCore.Authentication.JwtBearer; | |||||
using Microsoft.AspNetCore.Builder; | |||||
using Microsoft.Extensions.Configuration; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
using Microsoft.IdentityModel.Tokens; | |||||
using System; | |||||
using System.Security.Claims; | |||||
using System.Text; | |||||
namespace Api.Start | |||||
{ | |||||
public static class AuthRegister | |||||
{ | |||||
public static void RegisterAuth(this IServiceCollection services, IConfiguration configuration) | |||||
{ | |||||
services.AddAuthentication(options => | |||||
{ | |||||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; | |||||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; | |||||
}).AddJwtBearer(options => | |||||
{ | |||||
var secret = configuration.GetValue<string>("Jwt:Key"); | |||||
var issuer = configuration.GetValue<string>("Jwt:Issuer"); | |||||
options.IncludeErrorDetails = true; | |||||
options.TokenValidationParameters = new TokenValidationParameters | |||||
{ | |||||
ValidateIssuer = true, | |||||
ValidIssuer = issuer, | |||||
ValidateAudience = true, | |||||
ValidAudience = issuer, | |||||
ValidateIssuerSigningKey = true, | |||||
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secret)), | |||||
ValidateLifetime = true, | |||||
ClockSkew = TimeSpan.Zero, | |||||
RoleClaimType = ClaimTypes.Role, | |||||
SaveSigninToken = true, | |||||
}; | |||||
}); | |||||
} | |||||
public static void ConfigureAuthentication(this IApplicationBuilder app) | |||||
{ | |||||
app.UseAuthentication(); | |||||
app.UseAuthorization(); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,32 @@ | |||||
using Core.Db; | |||||
using Microsoft.EntityFrameworkCore; | |||||
using System.Reflection; | |||||
using AutoMapper; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
using Microsoft.AspNetCore.Http; | |||||
using Microsoft.Extensions.Configuration; | |||||
namespace Api.Start | |||||
{ | |||||
public static class DI | |||||
{ | |||||
public static void RegisterDependencies(this IServiceCollection services, IConfiguration configuration) | |||||
{ | |||||
services.AddDbContext<PanakDbContext>(options => options.UseSqlServer(configuration.GetConnectionString("PanakContext"))); | |||||
services.AddScoped<DbContext, PanakDbContext>(); | |||||
services.Scan(scan => scan | |||||
.FromAssemblies(typeof(Services.Identity.IAuthService).GetTypeInfo().Assembly) | |||||
.AddClasses() | |||||
.AsImplementedInterfaces() | |||||
.WithScopedLifetime()); | |||||
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); | |||||
var mapperConfig = new MapperConfiguration(mc => | |||||
{ | |||||
mc.AddProfile(new AutoMapperConfig()); | |||||
}); | |||||
var mapper = mapperConfig.CreateMapper(); | |||||
services.AddSingleton(mapper); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,47 @@ | |||||
using Microsoft.AspNetCore.Builder; | |||||
using Microsoft.AspNetCore.Hosting; | |||||
using Microsoft.AspNetCore.StaticFiles; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
using Microsoft.Extensions.Hosting; | |||||
using Newtonsoft.Json; | |||||
using System; | |||||
using System.Text.Json.Serialization; | |||||
namespace Api.Start | |||||
{ | |||||
public static class MvcRegistrar | |||||
{ | |||||
public static void RegisterMvc(this IServiceCollection services) | |||||
{ | |||||
services.AddCors(); | |||||
services.AddControllers(); | |||||
services.AddControllers().AddJsonOptions(options => | |||||
{ | |||||
options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles; | |||||
}); | |||||
} | |||||
public static void ConfigureMvc(this IApplicationBuilder app, IWebHostEnvironment env) | |||||
{ | |||||
app.UseCors(x => x | |||||
.WithOrigins("https://imasdk.googleapis.com") | |||||
.WithMethods("GET", "POST") | |||||
.AllowAnyHeader() | |||||
.SetIsOriginAllowed(origin => true) | |||||
.AllowCredentials() | |||||
.SetPreflightMaxAge(TimeSpan.FromDays(1)) | |||||
); | |||||
if (env.IsDevelopment()) | |||||
{ | |||||
app.UseDeveloperExceptionPage(); | |||||
} | |||||
var provider = new FileExtensionContentTypeProvider(); | |||||
//provider.Mappings[".apk"] = "application/vnd.android.package-archive"; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,62 @@ | |||||
using Microsoft.AspNetCore.Builder; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
using Microsoft.OpenApi.Models; | |||||
using System.Collections.Generic; | |||||
namespace Api.Start | |||||
{ | |||||
public static class SwaggerRegistrar | |||||
{ | |||||
public static void RegisterSwagger(this IServiceCollection services) | |||||
{ | |||||
services.AddSwaggerGen(c => | |||||
{ | |||||
c.SwaggerDoc("v1", new OpenApiInfo { Title = "API", Version = "v1" }); | |||||
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme | |||||
{ | |||||
Description = @"JWT Authorization header using the bearer scheme. | |||||
Enter 'Bearer' [space] and then your token in the text input below. | |||||
Example: 'Bearer 12345abcdef'", | |||||
Name = "Authorization", | |||||
In = ParameterLocation.Header, | |||||
Scheme = "Bearer", | |||||
Type = SecuritySchemeType.Http | |||||
}); | |||||
c.AddSecurityRequirement(new OpenApiSecurityRequirement | |||||
{{new OpenApiSecurityScheme | |||||
{ | |||||
Reference = new OpenApiReference | |||||
{ | |||||
Type = ReferenceType.SecurityScheme, | |||||
Id = "Bearer" | |||||
}, | |||||
Name = "Bearer", | |||||
In = ParameterLocation.Header, | |||||
}, new List<string>() | |||||
}}); | |||||
c.OperationFilter<AddRequiredHeaderParameter>(); | |||||
}); | |||||
} | |||||
public static void ConfigureSwagger(this IApplicationBuilder app) | |||||
{ | |||||
app.UseSwagger(c => | |||||
{ | |||||
c.RouteTemplate = "api-docs/{documentName}/swagger.json"; | |||||
}); | |||||
app.UseSwaggerUI(c => | |||||
{ | |||||
c.SwaggerEndpoint("/api-docs/v1/swagger.json", "API V1.0"); | |||||
c.DocumentTitle = "Api"; | |||||
c.EnableDeepLinking(); | |||||
c.EnableValidator(); | |||||
}); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,9 @@ | |||||
{ | |||||
"Logging": { | |||||
"LogLevel": { | |||||
"Default": "Information", | |||||
"Microsoft": "Warning", | |||||
"Microsoft.Hosting.Lifetime": "Information" | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,17 @@ | |||||
{ | |||||
"Logging": { | |||||
"LogLevel": { | |||||
"Default": "Information", | |||||
"Microsoft": "Warning", | |||||
"Microsoft.Hosting.Lifetime": "Information" | |||||
} | |||||
}, | |||||
"AllowedHosts": "*", | |||||
"ConnectionStrings": { | |||||
"PanakContext": "Data Source=uk-rm.drawise.net;Initial Catalog=SensoFarm;User ID=sa;Password=H007512$$nes;TrustServerCertificate=True;" | |||||
}, | |||||
"Jwt": { | |||||
"Key": "2659063f-c71e-4cae-9cf6-f3b3e8b77ffe", | |||||
"Issuer": "Panak Identity" | |||||
} | |||||
} |
@@ -0,0 +1,62 @@ | |||||
using AbrBlazorTools; | |||||
using Microsoft.AspNetCore.Components; | |||||
using Models.Identity; | |||||
using Newtonsoft.Json; | |||||
namespace ApiUtils; | |||||
public class UserUtils : IUserUtils | |||||
{ | |||||
private readonly IHttpClientWithLoginToken _client; | |||||
public UserUtils(IHttpClientWithLoginToken client) | |||||
{ | |||||
_client = client; | |||||
} | |||||
public async Task<HttpResponseMessage> SendVerificationRequest(object otpModel) | |||||
{ | |||||
return await _client.HttpPost("/api/AUTH/Login",otpModel); | |||||
} | |||||
public async Task<HttpResponseMessage> SendSignUpRequest(object accountModel) | |||||
{ | |||||
return await _client.HttpPost("/api/AUTH/SignUp",accountModel); | |||||
} | |||||
public async Task<HttpResponseMessage> LoginWithPasswordRequest(object loginModel) | |||||
{ | |||||
return await _client.HttpPost("/api/AUTH/LoginWithPassword",loginModel); | |||||
} | |||||
public async Task<HttpResponseMessage> CheckLogin(bool goToLogin = true) | |||||
{ | |||||
return await _client.HttpGet("/api/AUTH/CheckLogin",true,goToLogin); | |||||
} | |||||
public async Task<HttpResponseMessage> RefreshToken(object refreshTokenModel) | |||||
{ | |||||
return await _client.HttpPost("/api/Auth/refresh", refreshTokenModel); | |||||
} | |||||
public async Task<CheckLoginResponse> SaveEmailAndName(NameAndEmailModel model) | |||||
{ | |||||
var res=await _client.HttpPost("/api/auth/SetNameAndEmail", model); | |||||
if (!res.IsSuccessStatusCode) | |||||
return null; | |||||
return JsonConvert.DeserializeObject<CheckLoginResponse>(await res.Content.ReadAsStringAsync()); | |||||
} | |||||
} | |||||
public interface IUserUtils | |||||
{ | |||||
Task<HttpResponseMessage> SendVerificationRequest(object otpModel); | |||||
Task<HttpResponseMessage> LoginWithPasswordRequest(object loginModel); | |||||
Task<HttpResponseMessage> SendSignUpRequest(object accountModel); | |||||
Task<HttpResponseMessage> CheckLogin(bool goToLogin = true); | |||||
Task<HttpResponseMessage> RefreshToken(object refreshTokenModel); | |||||
Task<CheckLoginResponse> SaveEmailAndName(NameAndEmailModel model); | |||||
} |
@@ -0,0 +1,12 @@ | |||||
<Router AppAssembly="@typeof(App).Assembly"> | |||||
<Found Context="routeData"> | |||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" /> | |||||
<FocusOnNavigate RouteData="@routeData" Selector="h1" /> | |||||
</Found> | |||||
<NotFound> | |||||
<PageTitle>Not found</PageTitle> | |||||
<LayoutView Layout="@typeof(MainLayout)"> | |||||
<p role="alert">Sorry, there's nothing at this address.</p> | |||||
</LayoutView> | |||||
</NotFound> | |||||
</Router> |
@@ -0,0 +1,36 @@ | |||||
<Project Sdk="Microsoft.NET.Sdk.Razor"> | |||||
<PropertyGroup> | |||||
<TargetFramework>net8.0</TargetFramework> | |||||
<Nullable>disable</Nullable> | |||||
<ImplicitUsings>enable</ImplicitUsings> | |||||
<AddRazorSupportForMvc>true</AddRazorSupportForMvc> | |||||
</PropertyGroup> | |||||
<ItemGroup> | |||||
<Content Remove="wwwroot\assets\react.svg" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<None Include="wwwroot\assets\react.svg" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<SupportedPlatform Include="browser" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" /> | |||||
<PackageReference Include="CurrieTechnologies.Razor.SweetAlert2" Version="5.6.0" /> | |||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.8" /> | |||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.8" /> | |||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<ProjectReference Include="..\Models\Models.csproj" /> | |||||
</ItemGroup> | |||||
</Project> |
@@ -0,0 +1,37 @@ | |||||
| |||||
using Microsoft.AspNetCore.Components; | |||||
namespace BootStrapComponents | |||||
{ | |||||
public class BaseBootStrapComponent: ComponentBase | |||||
{ | |||||
[Parameter] | |||||
public TextAlignment TextAlignment { get; set; } = TextAlignment.None; | |||||
[Parameter] | |||||
public RenderFragment ChildContent { set; get; } | |||||
public IEnumerable<string> AutoClass() | |||||
{ | |||||
if (TextAlignment != TextAlignment.None) yield return "text-" + TextAlignment.ToString(); | |||||
yield return ""; | |||||
} | |||||
[Parameter] | |||||
public string AdditionalClass { set; get; } | |||||
} | |||||
public enum TextAlignment | |||||
{ | |||||
None, | |||||
left, | |||||
center, | |||||
right, | |||||
justify | |||||
} | |||||
public enum BackColor | |||||
{ | |||||
primary, | |||||
info, | |||||
danger, | |||||
white, | |||||
} | |||||
} |
@@ -0,0 +1,15 @@ | |||||
@namespace BootStrapComponents | |||||
@inherits BaseBootStrapComponent | |||||
<button @onclick="DoOnClick" class="btn btn-@BackColor @(string.Join(" ",AutoClass())) @AdditionalClass"> | |||||
@ChildContent | |||||
</button> | |||||
@code { | |||||
[Parameter] | |||||
public BackColor BackColor { set; get; } = BackColor.primary; | |||||
[Parameter] | |||||
public EventCallback OnClick { set; get; } = new(); | |||||
public async Task DoOnClick() | |||||
{ | |||||
await OnClick.InvokeAsync(); | |||||
} | |||||
} |
@@ -0,0 +1,109 @@ | |||||
namespace BootStrapComponents | |||||
{ | |||||
public static class BT | |||||
{ | |||||
public static string RoundedBorder => "rounded"; | |||||
public static string PullRight => "pull-right"; | |||||
public static string PullLeft => "pull-left"; | |||||
public static string PullCenter => "pull-center"; | |||||
public static string TextAlign(TextAlignment textAlignment) => "text-" + textAlignment; | |||||
public static string CenterBlock => "mx-auto d-block"; | |||||
public static string Button(ColorType colorType, bool outline = false) => "btn btn" + (outline ? "-outline" : "") + "-" + colorType; | |||||
public static string Background(ColorType colorType) => "bg-" + colorType; | |||||
public static string TextColor(ColorType colorType) => "text-" + colorType; | |||||
#region Position | |||||
public static string Position(Postion p) => p.ToString().ToLower().Replace("_", "-"); | |||||
#endregion | |||||
#region Flex | |||||
public static string FlexDisplay => "d-flex"; | |||||
public static string FlexJustifyAround => "d-flex justify-content-around"; | |||||
public static string FlexJustifyBetween => "d-flex justify-content-between"; | |||||
#endregion | |||||
#region Shadows | |||||
public static string RegularShadow => "shadow"; | |||||
public static string SmallShadow => "shadow-sm"; | |||||
public static string LargeShadow => "shadow-lg"; | |||||
#endregion | |||||
#region Opacity | |||||
public static string Opacity75 => "opacity-75"; | |||||
public static string Opacity50 => "opacity-50"; | |||||
public static string Opacity25 => "opacity-25"; | |||||
#endregion | |||||
#region Containers | |||||
public static string FormFloating => "form-floating"; | |||||
public static string Row => "row"; | |||||
public static string Col(int i) => $"col-{i}"; | |||||
public static string DisplayBlock => "d-block"; | |||||
#endregion | |||||
#region Margins | |||||
public static string Margin(int m) => $"m-{m}"; | |||||
public static string MarginLeft(int m) => $"ml-{m}"; | |||||
public static string MarginRight(int m) => $"mr-{m}"; | |||||
public static string MarginBottom(int m) => $"mb-{m}"; | |||||
public static string MarginTop(int m) => $"mt-{m}"; | |||||
public static string Padding(int m) => $"p-{m}"; | |||||
public static string PaddingLeft(int m) => $"pl-{m}"; | |||||
public static string PaddingRight(int m) => $"pr-{m}"; | |||||
public static string PaddingBottom(int m) => $"pb-{m}"; | |||||
public static string PaddingTop(int m) => $"pt-{m}"; | |||||
#endregion | |||||
public static class Icons | |||||
{ | |||||
public static string Copy => "bi bi-clipboard-fill"; | |||||
} | |||||
#region Form controls | |||||
public static string Input => "form-control"; | |||||
#endregion | |||||
} | |||||
public static class Styles | |||||
{ | |||||
public static string VerticalSelfAlign(TextAlignment alignment) => $"align-self:{alignment};"; | |||||
public static string ToRightGradientBackColor(string color) => $"background-image:linear-gradient(to right, {color}, transparent)"; | |||||
public static string ToLeftGradientBackColor(string color) => $"background-image:linear-gradient(to left, {color}, transparent)"; | |||||
} | |||||
public enum ColorType | |||||
{ | |||||
primary, | |||||
secondary, | |||||
success, | |||||
danger, | |||||
warning, | |||||
info, | |||||
light, | |||||
dark, | |||||
white | |||||
} | |||||
public enum Postion | |||||
{ | |||||
fixed_top, | |||||
sticky_top, | |||||
fixed_bottom, | |||||
sticky_bottom, | |||||
Position_Static, | |||||
Position_relative, | |||||
Position_absolute, | |||||
Position_Fixed, | |||||
Position_sticky | |||||
} | |||||
} |
@@ -0,0 +1,8 @@ | |||||
@namespace BootStrapComponents | |||||
@inherits BaseBootStrapComponent | |||||
<div class="jumbotron @(string.Join(" ",AutoClass())) @AdditionalClass"> | |||||
@ChildContent | |||||
</div> | |||||
@code { | |||||
} |
@@ -0,0 +1,42 @@ | |||||
using AbrBlazorTools; | |||||
using ApiUtils; | |||||
using Blazored.LocalStorage; | |||||
using BlazorPages.Tools; | |||||
using CurrieTechnologies.Razor.SweetAlert2; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace BlazorPages | |||||
{ | |||||
public static class Dependency | |||||
{ | |||||
public static void Inject(this IServiceCollection Services, bool isMobileApp = false) | |||||
{ | |||||
Statics.IsFromMobileApp = isMobileApp; | |||||
#region Tools | |||||
///libs: | |||||
Services.AddBlazoredLocalStorage(config => config.JsonSerializerOptions.WriteIndented = true); | |||||
Services.AddSweetAlert2(); | |||||
//////// | |||||
Services.AddScoped<ILoadingSpinner, LoadingSpinner>(); | |||||
Services.AddScoped<IBrowserTools, BrowserTools>(); | |||||
Services.AddScoped<IHttpClientWithLoginToken, HttpClientWithLoginToken>(); | |||||
Services.AddScoped<ISwalUtils, SwalUtils>(); | |||||
#endregion | |||||
#region API | |||||
Services.AddScoped<IUserUtils, UserUtils>(); | |||||
#endregion | |||||
Services.AddScoped(sp => new HttpClient()); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,8 @@ | |||||
using Microsoft.AspNetCore.Components; | |||||
namespace Components | |||||
{ | |||||
public class BaseComponent:ComponentBase | |||||
{ | |||||
} | |||||
} |
@@ -0,0 +1,37 @@ | |||||
@namespace Components | |||||
<div class="@BT.FormFloating @CssClass"> | |||||
<input id="nametxt" placeholder="@PlaceHolder" type="@Type" @bind="@BindingValue" class="@BT.Input @BT.MarginTop(TopMargin)" @bind:event="oninput" /> | |||||
<label for="nametxt">@PlaceHolder</label> | |||||
</div> | |||||
@code { | |||||
[Parameter] | |||||
public string PlaceHolder { set; get; } | |||||
private string _value; | |||||
[Parameter] | |||||
public string BindingValue | |||||
{ | |||||
get => _value; | |||||
set | |||||
{ | |||||
if (_value == value) return; | |||||
_value = value; | |||||
BindingValueChanged.InvokeAsync(value); | |||||
ChangeMethod.InvokeAsync(); | |||||
} | |||||
} | |||||
[Parameter] | |||||
public EventCallback<string> BindingValueChanged { get; set; } | |||||
[Parameter] | |||||
public string Type { set; get; } = "text"; | |||||
[Parameter] | |||||
public string CssClass{ set; get; } | |||||
[Parameter] | |||||
public int TopMargin { set; get; } = 3; | |||||
[Parameter] public EventCallback ChangeMethod { get; set; } = new(); | |||||
} |
@@ -0,0 +1,56 @@ | |||||
@using BootStrapComponents | |||||
@using Components | |||||
@using Models.Identity | |||||
@using Newtonsoft.Json | |||||
@using BlazorPages.Tools | |||||
@page "/" | |||||
@layout LayoutWithoutMenu | |||||
<img src="/assets/images/logo.png" class="@BT.CenterBlock m-2" style="max-width:30%;" /> | |||||
@if (DataGotten) | |||||
{ | |||||
<button class="@BT.CenterBlock @BT.Button(ColorType.primary,outline:true) @BT.Background(ColorType.light) w-50 h-1" | |||||
style="margin-top:calc(50vh - 10em);" | |||||
@onclick="@(()=>NavManager.NavigateTo("/login"))"> | |||||
ورود و ثبت نام | |||||
</button> | |||||
} | |||||
<div class="@BT.Row @BT.TextColor(ColorType.white) @BT.TextAlign(TextAlignment.center)" style="position:sticky;height:200px; top:calc(100vh - 200px);"> | |||||
<hr class="@BT.TextColor(ColorType.white)" /> | |||||
<p> | |||||
© تمامی حقوق برای حقتو محفوظ میباشد | |||||
</p> | |||||
<br /> | |||||
<p> | |||||
ورژن 1.0 | |||||
</p> | |||||
</div> | |||||
@code { | |||||
public bool DataGotten { set; get; } | |||||
protected override async void OnInitialized() | |||||
{ | |||||
var hasToken = await LocalStorage.ContainKeyAsync(Statics.LoginTokenKey); | |||||
if (hasToken) | |||||
{ | |||||
var token = await LocalStorage.GetItemAsStringAsync(Statics.LoginTokenKey); | |||||
var res = await _userUtils.CheckLogin(goToLogin: false); | |||||
var result = | |||||
JsonConvert.DeserializeObject<CheckLoginResponse>( | |||||
await res.Content.ReadAsStringAsync()); | |||||
if (res.IsSuccessStatusCode) | |||||
{ | |||||
await LoadingSpinner.HideLoading(); | |||||
Statics.CurrentLoginInfo = result; | |||||
if (!result.HasName) | |||||
NavManager.NavigateTo("/NameAndEmail"); | |||||
else | |||||
NavManager.NavigateTo("/dashboard"); | |||||
} | |||||
} | |||||
DataGotten = true; | |||||
StateHasChanged(); | |||||
} | |||||
} |
@@ -0,0 +1,128 @@ | |||||
@page "/login" | |||||
@using BootStrapComponents | |||||
@using Components | |||||
@using Models.Identity | |||||
@layout LayoutWithoutMenu | |||||
<img src="/assets/images/logo.png" class="@BT.CenterBlock m-2" style="max-width:30%;" /> | |||||
<style> | |||||
div.login-fields-container { | |||||
margin-top: 5vh; | |||||
border-radius: 2em; | |||||
border-top: solid 0.2em orange; | |||||
height: 90vh; | |||||
padding: 1em; | |||||
} | |||||
</style> | |||||
<div class="@BT.Background(ColorType.white) login-fields-container"> | |||||
<EditForm Model="@OtpLoginModel" OnValidSubmit="Submit"> | |||||
<DataAnotationsValidator /> | |||||
<p class="@BT.CenterBlock @BT.TextAlign(TextAlignment.center) h4" style="margin-top:2vh"> | |||||
ورود / ثبت نام | |||||
</p> | |||||
<p class="@(BT.TextAlign(TextAlignment.center)) @BT.TextColor(ColorType.secondary) @BT.MarginTop(5) h6"> | |||||
@if (!WaitForOtp) | |||||
{ | |||||
<span> | |||||
لطفا شماره همراه خود را وارد کنید. | |||||
</span> | |||||
} | |||||
else | |||||
{ | |||||
<span> | |||||
لطفا کد تائید پیامک شده را وارد کنید. | |||||
</span> | |||||
} | |||||
</p> | |||||
@if (!WaitForOtp) | |||||
{ | |||||
<span> | |||||
<input placeholder="تلفن همراه" @bind="OtpLoginModel.Username" class="@BT.Input @BT.MarginTop(5)" type="tel"> | |||||
</span> | |||||
} | |||||
else | |||||
{ | |||||
<span> | |||||
<input placeholder="کد تائید" @bind="LoginModel.Password" class="@BT.Input @BT.MarginTop(5)" type="number"> | |||||
</span> | |||||
} | |||||
<p class="h6 @BT.TextAlign(TextAlignment.center)" style="font-size:2vh;margin-top:5vh;"> | |||||
ورود شما به معنای پذیرش | |||||
<a href="/login"> | |||||
شرایط حقتو و قوانین حریم خصوصی | |||||
</a> | |||||
است | |||||
</p> | |||||
<button type="submit" class="@BT.Button(ColorType.primary) @BT.CenterBlock" style="margin-top:5vh"> | |||||
ورود | |||||
</button> | |||||
<div class="@BT.Row @BT.TextAlign(TextAlignment.center)" style="margin-top:12vh"> | |||||
<hr class="@BT.TextColor(ColorType.warning)" /> | |||||
<p> | |||||
© تمامی حقوق برای حقتو محفوظ میباشد | |||||
</p> | |||||
<br /> | |||||
<p> | |||||
ورژن 1.1 | |||||
</p> | |||||
</div> | |||||
</EditForm> | |||||
</div> | |||||
<style> | |||||
.form { | |||||
display: flex; | |||||
flex-direction: column; | |||||
align-items: center; | |||||
align-content: center; | |||||
} | |||||
.form-control { | |||||
margin: 5px 5px; | |||||
} | |||||
</style> | |||||
@code { | |||||
private bool WaitForOtpMode { get; set; } = true; | |||||
protected OTPLoginModel OtpLoginModel { get; set; } = new(); | |||||
protected bool WaitForOtp { set; get; } = false; | |||||
public async void Submit() | |||||
{ | |||||
if (WaitForOtp) | |||||
await RequestLogin(); | |||||
else | |||||
await RequestVerificationCode(); | |||||
} | |||||
protected async Task RequestVerificationCode() | |||||
{ | |||||
var response = await _userUtils.SendVerificationRequest(OtpLoginModel); | |||||
if (response.IsSuccessStatusCode) | |||||
WaitForOtp = true; | |||||
StateHasChanged(); | |||||
} | |||||
protected LoginModel LoginModel { get; set; } = new(); | |||||
protected async Task RequestLogin() | |||||
{ | |||||
LoginModel.Username = OtpLoginModel.Username; | |||||
var response = await _userUtils.LoginWithPasswordRequest(LoginModel); | |||||
if (response.IsSuccessStatusCode) | |||||
{ | |||||
var result = await response.Content.ReadFromJsonAsync<Models.Identity.PlainToken>(); | |||||
await LocalStorage.SetItemAsync(Statics.LoginTokenKey, result.AccessToken); | |||||
NavManager.NavigateTo("/dashboard"); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,33 @@ | |||||
<div class="modal @(Visible? "show":"") " style="display: @(Visible? "block;":"none")" id="@DialogId" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true"> | |||||
<div class="modal-dialog"> | |||||
<div class="modal-content"> | |||||
<div class="modal-header"> | |||||
<h1 class="modal-title fs-5 " id="exampleModalLabel">@Title</h1> | |||||
<button type="button" class="btn-close" style="margin: 0 0;" data-bs-dismiss="modal" aria-label="Close" @onclick="ToggleShowDialog"> </button> | |||||
</div> | |||||
<div class="modal-body"> | |||||
@DialogBody | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
@code | |||||
{ | |||||
[Parameter] | |||||
public bool Visible { get; set; } | |||||
[Parameter] | |||||
public string DialogId { get; set; } = "exampleModal"; | |||||
[Parameter] | |||||
public string Title { get; set; } | |||||
[Parameter] | |||||
public RenderFragment DialogBody { get; set; } | |||||
private void ToggleShowDialog() | |||||
{ | |||||
Visible = false; | |||||
StateHasChanged(); | |||||
} | |||||
} |
@@ -0,0 +1,15 @@ | |||||
@typeparam T | |||||
<div class="row"> | |||||
<div class="col form-control-box"> | |||||
<input type="text" class="form-control" placeholder="عنوان" > | |||||
@* <ValidationMessage For="@(() => FieldOfModel)"/> *@ | |||||
</div> | |||||
</div> | |||||
@code { | |||||
[Parameter] | |||||
public string FieldOfModel { get; set; } | |||||
[Parameter] | |||||
public EventCallback<T> FieldOfModelChanged { get; set; } | |||||
} |
@@ -0,0 +1 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-emoji-heart-eyes" viewBox="0 0 16 16"><path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" /><path d="M11.315 10.014a.5.5 0 0 1 .548.736A4.498 4.498 0 0 1 7.965 13a4.498 4.498 0 0 1-3.898-2.25.5.5 0 0 1 .548-.736h.005l.017.005.067.015.252.055c.215.046.515.108.857.169.693.124 1.522.242 2.152.242.63 0 1.46-.118 2.152-.242a26.58 26.58 0 0 0 1.109-.224l.067-.015.017-.004.005-.002zM4.756 4.566c.763-1.424 4.02-.12.952 3.434-4.496-1.596-2.35-4.298-.952-3.434zm6.488 0c1.398-.864 3.544 1.838-.952 3.434-3.067-3.554.19-4.858.952-3.434z" /></svg> |
@@ -0,0 +1,56 @@ | |||||
@inherits LayoutComponentBase | |||||
@using Components | |||||
@using Newtonsoft.Json | |||||
@using Models.Identity | |||||
@inject ILoadingSpinner _LoadingSpinner | |||||
<RoundContainerContainer Padding="0" BackroundImage="true"> | |||||
@Body | |||||
</RoundContainerContainer> | |||||
@code | |||||
{ | |||||
protected override async void OnAfterRender(bool firstRender) | |||||
{ | |||||
if (firstRender) | |||||
await CheckLogin(); | |||||
base.OnAfterRender(firstRender); | |||||
} | |||||
async Task CheckLogin() | |||||
{ | |||||
Console.WriteLine("" + Statics.LastCheck); | |||||
Console.WriteLine("checking login..."); | |||||
Statics.LastCheck = DateTime.Now; | |||||
var hasToken = await LocalStorage.ContainKeyAsync(Statics.LoginTokenKey); | |||||
if (hasToken) | |||||
{ | |||||
var token = await LocalStorage.GetItemAsStringAsync(Statics.LoginTokenKey); | |||||
var res = await _userUtils.CheckLogin(goToLogin: false); | |||||
if (res.IsSuccessStatusCode) | |||||
{ | |||||
var result = | |||||
JsonConvert.DeserializeObject<CheckLoginResponse>( | |||||
await res.Content.ReadAsStringAsync()); | |||||
await _LoadingSpinner.HideLoading(); | |||||
Statics.CurrentLoginInfo = result; | |||||
if (!result.HasName) | |||||
NavManager.NavigateTo("/NameAndEmail"); | |||||
else | |||||
NavManager.NavigateTo("/dashboard"); | |||||
} | |||||
await _LoadingSpinner.HideLoading(); | |||||
StateHasChanged(); | |||||
} | |||||
} | |||||
private async void SignOut() | |||||
{ | |||||
await LocalStorage.RemoveItemAsync(Statics.LoginTokenKey); | |||||
await LocalStorage.RemoveItemAsync(Statics.RefreshTokenKey); | |||||
NavManager.NavigateTo("/login"); | |||||
} | |||||
} |
@@ -0,0 +1,197 @@ | |||||
@using Components | |||||
@using Newtonsoft.Json | |||||
@using Models.Identity | |||||
@inject ILoadingSpinner _LoadingSpinner | |||||
@inherits LayoutComponentBase | |||||
<RoundContainerContainer Padding="0" Scroll=true> | |||||
<style> | |||||
div.scroll-div { | |||||
background-color: #F1F1F1; | |||||
overflow-y: hidden; | |||||
height: calc(100vh - 122px) | |||||
} | |||||
@@media only screen and (min-width: 600px) { | |||||
div.scroll-div { | |||||
height: calc(100vh - 120px) | |||||
} | |||||
} | |||||
</style> | |||||
<div class="@BT.FlexJustifyAround" style="flex-direction:column" @onclick="@(()=>ToggleMenu(close:true))"> | |||||
@*Top Header*@ | |||||
<div class="@BT.Row @BT.Background(ColorType.white) @BT.Margin(0) @BT.Padding(2) @BT.SmallShadow" style="height:60px;"> | |||||
<div style="@Styles.VerticalSelfAlign(TextAlignment.center)" class="@BT.Col(6) @BT.TextAlign(TextAlignment.left)"> | |||||
<img role="button" @onclick="@(()=>ToggleMenu(close:false))" src="/assets/images/Hamburger_icon.svg" width="30"> | |||||
<img src="/assets/images/logo-blue.png" alt="" width="60"> | |||||
</div> | |||||
<div class="@BT.Col(6) @BT.TextAlign(TextAlignment.right)"> | |||||
</div> | |||||
</div> | |||||
@*end of Top Header*@ | |||||
<div class="scroll-div @BT.Row @BT.Margin(0) @BT.DisplayBlock"> | |||||
@Body | |||||
</div> | |||||
@*Footer*@ | |||||
<div class="@BT.Background(ColorType.white) @(BT.Row) @(BT.TextAlign(TextAlignment.center)) @(BT.SmallShadow) " | |||||
style="height:60px;font-size:12px;width:100%;margin:0 auto;"> | |||||
<div @onclick="@(()=>NavManager.NavigateTo("/dashboard"))" role="button" style="@(Styles.VerticalSelfAlign(TextAlignment.center)) " class="@(BT.Col(4)) @(BT.TextAlign(TextAlignment.right))"> | |||||
<img src="/assets/images/ico-dashboard.png" alt="" width="26"> | |||||
<br /> | |||||
داشبورد | |||||
</div> | |||||
<div @onclick="@(()=>NavManager.NavigateTo("/Suit/Subject/Level1"))" role="button" style="@(Styles.VerticalSelfAlign(TextAlignment.center))" class="@(BT.Col(4)) @(BT.TextAlign(TextAlignment.right))"> | |||||
<img src="/assets/images/ico-add.png" alt="" width="26"> | |||||
<br /> | |||||
دعوی جدید | |||||
</div> | |||||
<div role="button" style="@(Styles.VerticalSelfAlign(TextAlignment.center))" class="@(BT.Col(4)) @(BT.TextAlign(TextAlignment.right))"> | |||||
<img src="/assets/images/ico-user.png" alt="" width="26"> | |||||
<br /> | |||||
پروفایل | |||||
</div> | |||||
</div> | |||||
@*End Of Footer*@ | |||||
</div> | |||||
<div class="menu @(BT.Background(ColorType.dark)) @BT.Position(Postion.Position_Fixed)" | |||||
style="top:auto;height:100vh;top:0;width:@(MenuWidth)%;max-width:450px;"> | |||||
@if (!MenuIsChanging && MenuWidth > 10) | |||||
{ | |||||
<a href="/about">درباره حقتو</a> | |||||
<a href="/Privacy">حریم خصوصی</a> | |||||
<a href="/Contact">تماس با ما</a> | |||||
} | |||||
</div> | |||||
</RoundContainerContainer> | |||||
<style> | |||||
div.menu a { | |||||
line-height: 1.5em; | |||||
padding:10px; | |||||
border-bottom: 1px white solid; | |||||
width: 100%; | |||||
text-decoration: none; | |||||
font-size: 1.5em; | |||||
opacity: 1; | |||||
color: white; | |||||
display: block; | |||||
} | |||||
</style> | |||||
@code | |||||
{ | |||||
public int MenuWidth { set; get; } = 0; | |||||
public bool ShowMenu { set; get; } = false; | |||||
public bool MenuIsChanging { set; get; } = false; | |||||
protected async Task ToggleMenu(bool close = false) | |||||
{ | |||||
if (MenuIsChanging) | |||||
return; | |||||
MenuIsChanging = true; | |||||
if (close) | |||||
{ | |||||
Console.WriteLine("closing menu"); | |||||
while (MenuWidth > 0) | |||||
{ | |||||
MenuWidth = Math.Max(MenuWidth -= 2, 0); | |||||
StateHasChanged(); | |||||
await Task.Delay(5); | |||||
} | |||||
MenuIsChanging = false; | |||||
return; | |||||
} | |||||
else | |||||
{ | |||||
Console.WriteLine("opening menu"); | |||||
while (MenuWidth < 60) | |||||
{ | |||||
Console.WriteLine(MenuWidth); | |||||
MenuWidth = Math.Min(MenuWidth += 2, 60); | |||||
StateHasChanged(); | |||||
await Task.Delay(5); | |||||
} | |||||
MenuIsChanging = false; | |||||
} | |||||
} | |||||
protected override async void OnAfterRender(bool firstRender) | |||||
{ | |||||
if (firstRender) | |||||
await CheckLogin(); | |||||
base.OnAfterRender(firstRender); | |||||
NavManager.LocationChanged += HideMenu; | |||||
} | |||||
private void HideMenu(object sender, LocationChangedEventArgs e) | |||||
{ | |||||
MenuWidth = 0; | |||||
StateHasChanged(); | |||||
} | |||||
async Task CheckLogin() | |||||
{ | |||||
Console.WriteLine("" + Statics.LastCheck); | |||||
if (Statics.LastCheck.HasValue && DateTime.Now.Subtract(Statics.LastCheck.Value).TotalMinutes < 5) | |||||
{ | |||||
Console.WriteLine("less than 5 minutes login check"); | |||||
base.OnInitialized(); | |||||
return; | |||||
} | |||||
Console.WriteLine("checking login..."); | |||||
Statics.LastCheck = DateTime.Now; | |||||
var hasToken = await LocalStorage.ContainKeyAsync(Statics.LoginTokenKey); | |||||
if (!hasToken) | |||||
NavManager.NavigateTo("/"); | |||||
else | |||||
{ | |||||
var token = await LocalStorage.GetItemAsStringAsync(Statics.LoginTokenKey); | |||||
var res = await _userUtils.CheckLogin(); | |||||
if (res.StatusCode == System.Net.HttpStatusCode.Unauthorized) | |||||
{ | |||||
var refreshRes = await _userUtils.RefreshToken(new RefreshTokenModel | |||||
{ | |||||
Token = token | |||||
}); | |||||
if (refreshRes.IsSuccessStatusCode) | |||||
{ | |||||
var newToken = JsonConvert.DeserializeObject<PlainToken>(await refreshRes.Content.ReadAsStringAsync()); | |||||
Statics.LoginTokenKey = newToken.AccessToken; | |||||
Statics.RefreshTokenKey = newToken.RefreshToken; | |||||
await LocalStorage.SetItemAsync(Statics.LoginTokenKey, Statics.LoginTokenKey); | |||||
await LocalStorage.SetItemAsync(Statics.RefreshTokenKey, Statics.RefreshTokenKey); | |||||
} | |||||
} | |||||
if (!res.IsSuccessStatusCode) | |||||
{ | |||||
await _LoadingSpinner.HideLoading(); | |||||
await _sweetAlertService.ShowSwal(title: "", message: "نشست منقضی شد،لطفا مجددا وارد شوید", SweetAlertIcon.Error); | |||||
NavManager.NavigateTo("/login"); | |||||
} | |||||
else | |||||
{ | |||||
var result = | |||||
JsonConvert.DeserializeObject<CheckLoginResponse>( | |||||
await res.Content.ReadAsStringAsync()); | |||||
Statics.CurrentLoginInfo = result; | |||||
} | |||||
await _LoadingSpinner.HideLoading(); | |||||
StateHasChanged(); | |||||
} | |||||
} | |||||
private async void SignOut() | |||||
{ | |||||
await LocalStorage.RemoveItemAsync(Statics.LoginTokenKey); | |||||
await LocalStorage.RemoveItemAsync(Statics.RefreshTokenKey); | |||||
NavManager.NavigateTo("/login"); | |||||
} | |||||
} |
@@ -0,0 +1,81 @@ | |||||
.page { | |||||
position: relative; | |||||
display: flex; | |||||
flex-direction: column; | |||||
} | |||||
main { | |||||
flex: 1; | |||||
} | |||||
.sidebar { | |||||
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); | |||||
} | |||||
.top-row { | |||||
background-color: #f7f7f7; | |||||
border-bottom: 1px solid #d6d5d5; | |||||
justify-content: flex-end; | |||||
height: 3.5rem; | |||||
display: flex; | |||||
align-items: center; | |||||
} | |||||
.top-row ::deep a, .top-row ::deep .btn-link { | |||||
white-space: nowrap; | |||||
margin-left: 1.5rem; | |||||
text-decoration: none; | |||||
} | |||||
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover { | |||||
text-decoration: underline; | |||||
} | |||||
.top-row ::deep a:first-child { | |||||
overflow: hidden; | |||||
text-overflow: ellipsis; | |||||
} | |||||
@media (max-width: 640.98px) { | |||||
.top-row:not(.auth) { | |||||
display: none; | |||||
} | |||||
.top-row.auth { | |||||
justify-content: space-between; | |||||
} | |||||
.top-row ::deep a, .top-row ::deep .btn-link { | |||||
margin-left: 0; | |||||
} | |||||
} | |||||
@media (min-width: 641px) { | |||||
.page { | |||||
flex-direction: row; | |||||
} | |||||
.sidebar { | |||||
width: 250px; | |||||
height: 100vh; | |||||
position: sticky; | |||||
top: 0; | |||||
} | |||||
.top-row { | |||||
position: sticky; | |||||
top: 0; | |||||
z-index: 1; | |||||
} | |||||
.top-row.auth ::deep a:first-child { | |||||
flex: 1; | |||||
text-align: right; | |||||
width: 0; | |||||
} | |||||
.top-row, article { | |||||
padding-left: 2rem !important; | |||||
padding-right: 1.5rem !important; | |||||
} | |||||
} |
@@ -0,0 +1,70 @@ | |||||
| |||||
<div class="top-row ps-3 navbar navbar-dark"> | |||||
<div class="container-fluid"> | |||||
<a class="navbar-brand" href="">حق تو</a> | |||||
<button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu"> | |||||
<span class="navbar-toggler-icon"></span> | |||||
</button> | |||||
</div> | |||||
</div> | |||||
<div @onclick="ToggleNavMenu"> | |||||
<nav class="flex-column"> | |||||
<div class="nav-item px-3"> | |||||
<NavLink class="nav-link" href="" Match="NavLinkMatch.All"> | |||||
<span class="oi oi-home" aria-hidden="true"></span> خانه | |||||
</NavLink> | |||||
</div> | |||||
<div class="nav-item px-3"> | |||||
<NavLink class="nav-link" href="Subjects"> | |||||
<span class="oi oi-list-rich" aria-hidden="true"></span> موضوعات | |||||
</NavLink> | |||||
</div> | |||||
<div class="nav-item px-3"> | |||||
<NavLink class="nav-link" href="Words/word"> | |||||
<span class="oi oi-list-rich" aria-hidden="true"></span>دیکشنری | |||||
</NavLink> | |||||
</div> | |||||
<div class="nav-item px-3"> | |||||
<NavLink class="nav-link" href="Words/bills"> | |||||
<span class="oi oi-list-rich" aria-hidden="true"></span>لوایح و اسناد | |||||
</NavLink> | |||||
</div> | |||||
<div class="nav-item px-3"> | |||||
<NavLink class="nav-link" href="Questions"> | |||||
<span class="oi oi-list-rich" aria-hidden="true"></span> سوالات | |||||
</NavLink> | |||||
</div> | |||||
<div class="nav-item px-3"> | |||||
<NavLink class="nav-link" href="SuitTrees"> | |||||
<span class="oi oi-list-rich" aria-hidden="true"></span> درختان شکایت | |||||
</NavLink> | |||||
</div> | |||||
<div class="nav-item px-3"> | |||||
<NavLink class="nav-link" @onclick="Signout"> | |||||
<span class="oi oi-account-logout" aria-hidden="true"></span> خروج | |||||
</NavLink> | |||||
</div> | |||||
</nav> | |||||
</div> | |||||
@code { | |||||
private bool collapseNavMenu = true; | |||||
private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null; | |||||
private void ToggleNavMenu() | |||||
{ | |||||
collapseNavMenu = !collapseNavMenu; | |||||
} | |||||
private async Task Signout() | |||||
{ | |||||
var confirm = await SwalUtils.ShowSwal(title: "", message: "از خروج اطمینان دارید؟", icon: SweetAlertIcon.Question); | |||||
if (!confirm.IsConfirmed) | |||||
return; | |||||
await LocalStorage.RemoveItemAsync(Statics.LoginTokenKey); | |||||
NavManager.NavigateTo("/Login"); | |||||
} | |||||
} |
@@ -0,0 +1,65 @@ | |||||
.navbar-toggler { | |||||
background-color: rgba(255, 255, 255, 0.1); | |||||
} | |||||
.top-row { | |||||
height: 3.5rem; | |||||
background-color: rgba(0,0,0,0.4); | |||||
} | |||||
.navbar-brand { | |||||
font-size: 1.1rem; | |||||
} | |||||
.oi { | |||||
width: 2rem; | |||||
font-size: 1.1rem; | |||||
vertical-align: text-top; | |||||
top: -2px; | |||||
} | |||||
.nav-item { | |||||
font-size: 0.9rem; | |||||
padding-bottom: 0.5rem; | |||||
} | |||||
.nav-item:first-of-type { | |||||
padding-top: 1rem; | |||||
} | |||||
.nav-item:last-of-type { | |||||
padding-bottom: 1rem; | |||||
} | |||||
.nav-item ::deep a { | |||||
color: #d7d7d7; | |||||
border-radius: 4px; | |||||
height: 3rem; | |||||
display: flex; | |||||
align-items: center; | |||||
line-height: 3rem; | |||||
} | |||||
.nav-item ::deep a.active { | |||||
background-color: rgba(255,255,255,0.25); | |||||
color: white; | |||||
} | |||||
.nav-item ::deep a:hover { | |||||
background-color: rgba(255,255,255,0.1); | |||||
color: white; | |||||
} | |||||
@media (min-width: 641px) { | |||||
.navbar-toggler { | |||||
display: none; | |||||
} | |||||
.collapse { | |||||
/* Never collapse the sidebar for wide screens */ | |||||
display: block; | |||||
} | |||||
} | |||||
@@ -0,0 +1,25 @@ | |||||
<button type="button" class="btn @CustomStyleClass" data-bs-toggle="modal" data-bs-target="@DialogId" @onclick="OnclickFunc"> | |||||
@ButtonName | |||||
</button> | |||||
@code | |||||
{ | |||||
[Parameter] | |||||
public EventCallback OnclickFunc { get; set; } | |||||
[Parameter] | |||||
public string ButtonName { get; set; } | |||||
[Parameter] | |||||
public string CustomStyleClass { get; set; } = "btn-primary"; | |||||
[Parameter] | |||||
public string DialogId { get; set; } = "#exampleModal"; | |||||
protected override void OnInitialized() | |||||
{ | |||||
if (DialogId[0] != '#') | |||||
DialogId = $"#{DialogId}"; | |||||
base.OnInitialized(); | |||||
} | |||||
} |
@@ -0,0 +1,7 @@ | |||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary navbar-fixed-top" style="direction:@(Statics.Culture==Statics.CultureType.En?"ltr":"rtl")"> | |||||
<div class="container-fluid"> | |||||
<div class="d-flex align-content-center"> | |||||
<h1 class="app-title">SensoFarm</h1> | |||||
</div> | |||||
</div> | |||||
</nav> |
@@ -0,0 +1,65 @@ | |||||
using Microsoft.JSInterop; | |||||
namespace BlazorPages.Tools | |||||
{ | |||||
public class BrowserTools : IBrowserTools | |||||
{ | |||||
private readonly IJSRuntime _js; | |||||
public BrowserTools(IJSRuntime js) | |||||
{ | |||||
_js = js; | |||||
} | |||||
public async Task<BrowserDimension> GetDimensions() | |||||
{ | |||||
return await _js.InvokeAsync<BrowserDimension>("methods.getDimensions"); | |||||
} | |||||
public async Task<ValueTask> CopyText(string text) | |||||
{ | |||||
return _js.InvokeVoidAsync("navigator.clipboard.writeText", text); | |||||
} | |||||
public async Task PrepareResponsiveTable() | |||||
{ | |||||
await _js.InvokeVoidAsync("methods.PrepareResponsiveTable"); | |||||
} | |||||
public async Task<string> GetInputValueById(string id) | |||||
{ | |||||
return await _js.InvokeAsync<string>("AbrMethods.getInputValue", id); | |||||
} | |||||
public async Task<string> GetInputValueByIdAndReplacePersianNumbers(string id) | |||||
{ | |||||
var text = await GetInputValueById(id); | |||||
var digits = new string[] { "۰", "۱", "۲", "۳", "۴", "۵", "۶", "۷", "۸", "۹" }; | |||||
var english = new string[] { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" }; | |||||
for (var i = 0; i < digits.Length; i++) | |||||
text = text.Replace(digits[i], english[i]); | |||||
return text; | |||||
} | |||||
public async Task ScrollToEnd() | |||||
{ | |||||
await _js.InvokeVoidAsync("methods.ScrollToEnd"); | |||||
} | |||||
} | |||||
public class BrowserDimension | |||||
{ | |||||
public int Width { get; set; } | |||||
public int Height { get; set; } | |||||
} | |||||
public interface IBrowserTools | |||||
{ | |||||
Task<BrowserDimension> GetDimensions(); | |||||
Task<ValueTask> CopyText(string text); | |||||
Task PrepareResponsiveTable(); | |||||
Task<string> GetInputValueById(string id); | |||||
Task<string> GetInputValueByIdAndReplacePersianNumbers(string id); | |||||
Task ScrollToEnd(); | |||||
} | |||||
} |
@@ -0,0 +1,178 @@ | |||||
using System; | |||||
using System.ComponentModel; | |||||
using System.ComponentModel.DataAnnotations; | |||||
using System.Globalization; | |||||
namespace BlazorPages.Tools | |||||
{ | |||||
public static class DateTimeExtension | |||||
{ | |||||
public static string ComputedDateTime(this DateTime InsertDateTime) | |||||
{ | |||||
var persianCalender = new PersianCalendar(); | |||||
var year = persianCalender.GetYear(InsertDateTime); | |||||
var month = persianCalender.GetMonth(InsertDateTime); | |||||
var day = persianCalender.GetDayOfMonth(InsertDateTime); | |||||
var hour = persianCalender.GetHour(InsertDateTime); | |||||
var minute = persianCalender.GetMinute(InsertDateTime); | |||||
var second = persianCalender.GetSecond(InsertDateTime); | |||||
var computed = string.Format("{0}-{1}-{2}-{3}{4}{5}", year, | |||||
month < 10 ? string.Format("0{0}", month) : month.ToString(), | |||||
day < 10 ? string.Format("0{0}", day) : day.ToString(), | |||||
hour < 10 ? string.Format("0{0}", hour) : hour.ToString(), | |||||
minute < 10 ? string.Format("0{0}", minute) : minute.ToString(), | |||||
second < 10 ? string.Format("0{0}", second) : second.ToString()); | |||||
return computed; | |||||
} | |||||
public static string GetPersianDate(this DateTime value, bool showDayName, bool showMonthName, bool showTime) | |||||
{ | |||||
if (Statics.Culture == Statics.CultureType.En) | |||||
return value.ToString(); | |||||
var persianCalender = new PersianCalendar(); | |||||
var year = persianCalender.GetYear(value); | |||||
var month = persianCalender.GetMonth(value); | |||||
var day = persianCalender.GetDayOfMonth(value); | |||||
var s = string.Format(showMonthName ? "{2} {1} {0}" : "{0}/{1}/{2}", year, showMonthName ? month.GetMonthName() : month.ToString(""), day); | |||||
if (showDayName) | |||||
s = string.Format("{0} {1}", persianCalender.GetDayOfWeek(value).GetDayName(), s); | |||||
if (showTime) | |||||
s = string.Format("{0} {1}:{2}", s, value.Hour, value.Minute); | |||||
return s; | |||||
} | |||||
public static string GetPersianDayName(this DateTime value) | |||||
{ | |||||
var persianCalender = new PersianCalendar(); | |||||
var s = persianCalender.GetDayOfWeek(value).GetDayName(); | |||||
return s; | |||||
} | |||||
public static string GetPersianMonthName(this DateTime value) | |||||
{ | |||||
var persianCalender = new PersianCalendar(); | |||||
var s = persianCalender.GetMonth(value).GetMonthName(); | |||||
return s; | |||||
} | |||||
public static int GetPersianDayInt(this DateTime value) | |||||
{ | |||||
var persianCalender = new PersianCalendar(); | |||||
var s = persianCalender.GetDayOfMonth(value); | |||||
return s; | |||||
} | |||||
public static string GetPersianDate(this DateTime value) | |||||
{ | |||||
return value.GetPersianDate(true, true, true); | |||||
} | |||||
public static string GetPersianDate(this DateTime? value) | |||||
{ | |||||
return value.HasValue ? value.Value.GetPersianDate(true, true, true) : ""; | |||||
} | |||||
public static string GetPersianDate(this DateTime? value, bool showDayName) | |||||
{ | |||||
return value.HasValue ? value.Value.GetPersianDate(showDayName, false, false) : ""; | |||||
} | |||||
public static string GetPersianDate(this DateTime value, bool showDayName) | |||||
{ | |||||
return value.GetPersianDate(showDayName, false, false); | |||||
} | |||||
public static string GetPersianDate(this DateTime value, bool showDayName, bool showTime) | |||||
{ | |||||
return value.GetPersianDate(showDayName, false, showTime); | |||||
} | |||||
public static string GetPersianDateAsText(this DateTime value, bool showDayName, bool showTime) | |||||
{ | |||||
value = value.ToUniversalTime().ToLocalTime(); | |||||
var minutes = (int)DateTime.Now.Subtract(value).TotalMinutes; | |||||
var seconds = (int)DateTime.Now.Subtract(value).TotalSeconds; | |||||
if (minutes < 1) | |||||
if (Statics.Culture == Statics.CultureType.Fa) | |||||
return $"{seconds} ثانیه قبل"; | |||||
else | |||||
return $"{seconds} Second(s) ago"; | |||||
else if (minutes < 60) | |||||
if (Statics.Culture == Statics.CultureType.Fa) | |||||
return $"{minutes} دقیقه قبل"; | |||||
else | |||||
return $"{minutes} Minute(s) ago"; | |||||
return value.GetPersianDate(showDayName, false, showTime); | |||||
} | |||||
private static string GetMonthName(this int month) | |||||
{ | |||||
switch (month) | |||||
{ | |||||
case 1: | |||||
return "فروردین"; | |||||
case 2: | |||||
return "اردیبهشت"; | |||||
case 3: | |||||
return "خرداد"; | |||||
case 4: | |||||
return "تیر"; | |||||
case 5: | |||||
return "امرداد"; | |||||
case 6: | |||||
return "شهریور"; | |||||
case 7: | |||||
return "مهر"; | |||||
case 8: | |||||
return "آبان"; | |||||
case 9: | |||||
return "آذر"; | |||||
case 10: | |||||
return "دی"; | |||||
case 11: | |||||
return "بهمن"; | |||||
case 12: | |||||
return "اسفند"; | |||||
default: return ""; | |||||
} | |||||
} | |||||
public static string GetDayName(this DayOfWeek day) | |||||
{ | |||||
switch (day) | |||||
{ | |||||
case DayOfWeek.Friday: | |||||
return "جمعه"; | |||||
case DayOfWeek.Monday: | |||||
return "دوشنبه"; | |||||
case DayOfWeek.Saturday: | |||||
return "شنبه"; | |||||
case DayOfWeek.Sunday: | |||||
return "یک شنبه"; | |||||
case DayOfWeek.Thursday: | |||||
return "پنج شنبه"; | |||||
case DayOfWeek.Tuesday: | |||||
return "سه شنبه"; | |||||
case DayOfWeek.Wednesday: | |||||
return "چهار شنبه"; | |||||
default: | |||||
return ""; | |||||
} | |||||
} | |||||
} | |||||
public enum PersianDays | |||||
{ | |||||
[Display(Name = "شنبه")] | |||||
Saturday = DayOfWeek.Saturday, | |||||
[Display(Name = "یکشنبه")] | |||||
Sunday = DayOfWeek.Sunday, | |||||
[Display(Name = "دوشنبه")] | |||||
Monday = DayOfWeek.Monday, | |||||
[Display(Name = "سه شنبه")] | |||||
Tuesday = DayOfWeek.Tuesday, | |||||
[Display(Name = "چهار شنبه")] | |||||
Wednesday = DayOfWeek.Wednesday, | |||||
[Display(Name = "پنج شنبه")] | |||||
Thursday = DayOfWeek.Thursday, | |||||
[Display(Name = "چمعه")] | |||||
Friday = DayOfWeek.Friday, | |||||
} | |||||
} |
@@ -0,0 +1,234 @@ | |||||
using Blazored.LocalStorage; | |||||
using CurrieTechnologies.Razor.SweetAlert2; | |||||
using Newtonsoft.Json; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Net; | |||||
using System.Net.Http; | |||||
using System.Net.Http.Headers; | |||||
using System.Net.Http.Json; | |||||
using System.Security.Cryptography; | |||||
using System.Text; | |||||
using System.Text.Json; | |||||
using System.Threading.Tasks; | |||||
using Microsoft.AspNetCore.Components; | |||||
using BlazorPages.Tools; | |||||
namespace AbrBlazorTools | |||||
{ | |||||
public class HttpClientWithLoginToken : IHttpClientWithLoginToken | |||||
{ | |||||
private readonly HttpClient _httpClient = new HttpClient(); | |||||
private readonly ILocalStorageService _localStorage; | |||||
private readonly CurrieTechnologies.Razor.SweetAlert2.SweetAlertService _swal; | |||||
private readonly AbrBlazorTools.ILoadingSpinner _loadingSpinner; | |||||
private readonly NavigationManager _navigationManager; | |||||
private readonly IBrowserTools _browserTools; | |||||
public HttpClientWithLoginToken(ILocalStorageService localStorage, CurrieTechnologies.Razor.SweetAlert2.SweetAlertService swal | |||||
, AbrBlazorTools.ILoadingSpinner loadingSpinner, NavigationManager navigationManager, IBrowserTools browserTools) | |||||
{ | |||||
_localStorage = localStorage; | |||||
_swal = swal; | |||||
_loadingSpinner = loadingSpinner; | |||||
_navigationManager = navigationManager; | |||||
_browserTools = browserTools; | |||||
_httpClient.BaseAddress = new Uri(Statics.BaseAddress); | |||||
} | |||||
public async Task<HttpClient> GetInstance() | |||||
{ | |||||
var hasToken = await _localStorage.ContainKeyAsync(Statics.LoginTokenKey); | |||||
if (hasToken) | |||||
{ | |||||
var token = await _localStorage.GetItemAsStringAsync(Statics.LoginTokenKey); | |||||
if (token.StartsWith('"')) | |||||
token = token.Substring(1); | |||||
if (token.EndsWith('"')) | |||||
token = token.Substring(0, token.Length - 1); | |||||
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); | |||||
} | |||||
return _httpClient; | |||||
} | |||||
public async Task<HttpResponseMessage> HttpGet(string url, bool hasLoading = true, bool goToLogin = true) | |||||
{ | |||||
var res=new HttpResponseMessage(); | |||||
Console.WriteLine("Get : " + url); | |||||
if (hasLoading) | |||||
await _loadingSpinner.ShowLoading(); | |||||
var http = await GetInstance(); | |||||
try | |||||
{ | |||||
res= await http.GetAsync(url); | |||||
await _loadingSpinner.HideLoading(); | |||||
if (res.IsSuccessStatusCode) | |||||
{ | |||||
return res; | |||||
} | |||||
if (res.StatusCode == HttpStatusCode.Unauthorized) | |||||
{ | |||||
await _loadingSpinner.HideLoading(); | |||||
if (goToLogin) | |||||
{ | |||||
Console.WriteLine("خطای دسترسی"); | |||||
_navigationManager.NavigateTo("/"); | |||||
} | |||||
} | |||||
else if (res.StatusCode == HttpStatusCode.BadRequest) | |||||
{ | |||||
await _swal.FireAsync( message: res.ReasonPhrase, icon: SweetAlertIcon.Error); | |||||
} | |||||
} | |||||
catch (Exception ex) | |||||
{ | |||||
Console.WriteLine(ex); | |||||
await _loadingSpinner.HideLoading(); | |||||
await _swal.FireAsync( message: res.ReasonPhrase, icon: SweetAlertIcon.Error); | |||||
} | |||||
await _loadingSpinner.HideLoading(); | |||||
return new HttpResponseMessage { StatusCode = HttpStatusCode.BadRequest }; | |||||
} | |||||
public async Task<HttpResponseMessage> HttpDelete(string url, bool hasLoading = true) | |||||
{ | |||||
Console.WriteLine("Delete : " + url); | |||||
var res = new HttpResponseMessage(); | |||||
if (hasLoading) | |||||
await _loadingSpinner.ShowLoading(); | |||||
var http = await GetInstance(); | |||||
try | |||||
{ | |||||
res = await http.DeleteAsync(url); | |||||
await _loadingSpinner.HideLoading(); | |||||
if (res.IsSuccessStatusCode) | |||||
{ | |||||
return res; | |||||
} | |||||
if (res.StatusCode == HttpStatusCode.Unauthorized) | |||||
{ | |||||
await _loadingSpinner.HideLoading(); | |||||
_navigationManager.NavigateTo("/"); | |||||
} | |||||
else | |||||
{ | |||||
dynamic error = JsonConvert.DeserializeObject<object>(await res.Content.ReadAsStringAsync()); | |||||
await _swal.FireAsync( message: await ErrorMessage(res), icon: SweetAlertIcon.Error); | |||||
} | |||||
} | |||||
catch (Exception ex) | |||||
{ | |||||
Console.WriteLine(ex); | |||||
await _loadingSpinner.HideLoading(); | |||||
dynamic error = JsonConvert.DeserializeObject<object>(await res.Content.ReadAsStringAsync()); | |||||
await _swal.FireAsync( message: await ErrorMessage(res), icon: SweetAlertIcon.Error); | |||||
Statics.CurrentLoginInfo = new(); | |||||
} | |||||
await _loadingSpinner.HideLoading(); | |||||
return new HttpResponseMessage { StatusCode = HttpStatusCode.BadRequest }; | |||||
} | |||||
public async Task<HttpResponseMessage> HttpPut(string url, object model, bool hasLoading = true) | |||||
{ | |||||
Console.WriteLine("Put : " + url); | |||||
var res = new HttpResponseMessage(); | |||||
if (hasLoading) | |||||
await _loadingSpinner.ShowLoading(); | |||||
var http = await GetInstance(); | |||||
try | |||||
{ | |||||
var content = new StringContent(JsonConvert.SerializeObject(model), Encoding.UTF8, "application/json"); | |||||
res = await http.PutAsync(url, content); | |||||
await _loadingSpinner.HideLoading(); | |||||
if (res.IsSuccessStatusCode) | |||||
{ | |||||
return res; | |||||
} | |||||
if (res.StatusCode == HttpStatusCode.Unauthorized) | |||||
{ | |||||
await _loadingSpinner.HideLoading(); | |||||
Statics.CurrentLoginInfo = new(); | |||||
} | |||||
else | |||||
{ | |||||
await _swal.FireAsync( message: await ErrorMessage(res), icon: SweetAlertIcon.Error); | |||||
} | |||||
} | |||||
catch (Exception ex) | |||||
{ | |||||
Console.WriteLine(ex); | |||||
await _loadingSpinner.HideLoading(); | |||||
await _swal.FireAsync( message: await ErrorMessage(res), icon: SweetAlertIcon.Error); | |||||
Statics.CurrentLoginInfo = new(); | |||||
} | |||||
await _loadingSpinner.HideLoading(); | |||||
return new HttpResponseMessage { StatusCode = HttpStatusCode.BadRequest }; | |||||
} | |||||
public async Task<HttpResponseMessage> HttpPost(string url, object model, bool hasLoading = true, bool multiPart = false) | |||||
{ | |||||
Console.WriteLine("Post : " + url); | |||||
var res = new HttpResponseMessage(); | |||||
if (hasLoading) | |||||
await _loadingSpinner.ShowLoading(); | |||||
var http = await GetInstance(); | |||||
try | |||||
{ | |||||
res = new HttpResponseMessage(); | |||||
var content = new StringContent(JsonConvert.SerializeObject(model), Encoding.UTF8, "application/json"); | |||||
res = await http.PostAsync(url, content); | |||||
await _loadingSpinner.HideLoading(); | |||||
if (res.IsSuccessStatusCode) | |||||
{ | |||||
return res; | |||||
} | |||||
if (res.StatusCode == HttpStatusCode.Unauthorized) | |||||
{ | |||||
await _loadingSpinner.HideLoading(); | |||||
_navigationManager.NavigateTo("/"); | |||||
} | |||||
else | |||||
{ | |||||
await _swal.FireAsync( message: await ErrorMessage(res), icon: SweetAlertIcon.Error); | |||||
} | |||||
} | |||||
catch (Exception ex) | |||||
{ | |||||
Console.WriteLine(ex); | |||||
await _loadingSpinner.HideLoading(); | |||||
} | |||||
await _loadingSpinner.HideLoading(); | |||||
return res; | |||||
} | |||||
private async Task<string> ErrorMessage(HttpResponseMessage res) | |||||
{ | |||||
var errStr = "خطای نامشخص"; | |||||
dynamic error = JsonConvert.DeserializeObject<object>(await res.Content.ReadAsStringAsync()); | |||||
if (error.errors != null) | |||||
{ | |||||
errStr = (string)("" + error.errors); | |||||
if (errStr.Contains(":")) | |||||
errStr = errStr.Split(':')[1].Replace(":", "").Replace("{", "") | |||||
.Replace("}", "").Replace("[", "").Replace("]","").Replace("\"",""); | |||||
} | |||||
return errStr; | |||||
} | |||||
} | |||||
public interface IHttpClientWithLoginToken | |||||
{ | |||||
Task<HttpClient> GetInstance(); | |||||
Task<HttpResponseMessage> HttpPost(string url, object model, bool hasLoading = true, bool multiPart = false); | |||||
Task<HttpResponseMessage> HttpGet(string url, bool hasLoading = true, bool goToLogin = true); | |||||
Task<HttpResponseMessage> HttpDelete(string url, bool hasLoading = true); | |||||
Task<HttpResponseMessage> HttpPut(string url, object model, bool hasLoading = true); | |||||
} | |||||
} |
@@ -0,0 +1,32 @@ | |||||
using Microsoft.JSInterop; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
namespace AbrBlazorTools | |||||
{ | |||||
public class LoadingSpinner : ILoadingSpinner | |||||
{ | |||||
private readonly IJSRuntime _jSRuntime; | |||||
public LoadingSpinner(IJSRuntime jSRuntime) | |||||
{ | |||||
_jSRuntime = jSRuntime; | |||||
} | |||||
public async Task HideLoading() | |||||
{ | |||||
await Task.Delay(1000); | |||||
await _jSRuntime.InvokeVoidAsync("methods.HideLoading", null); | |||||
} | |||||
public async Task ShowLoading() | |||||
{ | |||||
await _jSRuntime.InvokeVoidAsync("methods.ShowLoading", null); | |||||
} | |||||
} | |||||
public interface ILoadingSpinner | |||||
{ | |||||
Task ShowLoading(); | |||||
Task HideLoading(); | |||||
} | |||||
} |
@@ -0,0 +1,18 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
using CurrieTechnologies.Razor.SweetAlert2; | |||||
namespace BlazorPages.Tools | |||||
{ | |||||
public interface ISwalUtils | |||||
{ | |||||
public Task<SweetAlertResult> ShowToast(string message); | |||||
public Task<SweetAlertResult> ShowSwal(string title, string message, SweetAlertIcon icon); | |||||
} | |||||
} | |||||
@@ -0,0 +1,20 @@ | |||||
namespace BlazorPages.Tools | |||||
{ | |||||
public sealed class InfiniteScrollingItemsProviderRequest | |||||
{ | |||||
public InfiniteScrollingItemsProviderRequest(int startIndex, int page, CancellationToken cancellationToken) | |||||
{ | |||||
StartIndex = startIndex; | |||||
CancellationToken = cancellationToken; | |||||
Page = page; | |||||
} | |||||
public int StartIndex { get; } | |||||
public CancellationToken CancellationToken { get; } | |||||
public int Page { get; } | |||||
} | |||||
public delegate Task<IEnumerable<T>> ItemsProviderRequestDelegate<T>(InfiniteScrollingItemsProviderRequest request); | |||||
} |
@@ -0,0 +1,72 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Globalization; | |||||
using System.Linq; | |||||
using System.Reflection; | |||||
using System.Text.RegularExpressions; | |||||
using System.Threading.Tasks; | |||||
using Models.Identity; | |||||
namespace BlazorPages.Tools | |||||
{ | |||||
public static class Statics | |||||
{ | |||||
public const string TableClass = "table table-striped table-bordered border"; | |||||
public enum CultureType | |||||
{ | |||||
En, | |||||
Fa | |||||
} | |||||
public static CultureType Culture = CultureType.Fa; | |||||
public static string LoginTokenKey = "SignIn"; | |||||
public static string RefreshTokenKey = "RefreshToken"; | |||||
public static string Agent = ""; | |||||
public static string FcmToken = "RefreshToken"; | |||||
public static CheckLoginResponse CurrentLoginInfo { get; set; } | |||||
public static DateTime? LastCheck { set; get; } | |||||
public static bool IsFromMobileApp { get; internal set; } | |||||
public static string BaseAddress = "https://api.senso.farm/"; | |||||
//linux: | |||||
//public static string BaseAddress = "https://localhost:5001/"; | |||||
public static string ReplaceNewLineWithBr(this string text) | |||||
{ | |||||
var result = Regex.Replace(text, @"\r\n?|\n", "<br/>"); | |||||
result = result.Replace("<br/><br/><br/>", "<br/>").Replace("<br/><br/>", "<br/>"); | |||||
return result; | |||||
} | |||||
public static 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); | |||||
if (fieldInfo != null) | |||||
fieldInfo.SetValue(culture, cal); | |||||
FieldInfo info = formatInfo.GetType().GetField("calendar", BindingFlags.NonPublic | BindingFlags.Instance); | |||||
if (info != null) | |||||
info.SetValue(formatInfo, cal); | |||||
culture.NumberFormat.NumberDecimalSeparator = "/"; | |||||
culture.NumberFormat.DigitSubstitution = DigitShapes.NativeNational; | |||||
culture.NumberFormat.NumberNegativePattern = 0; | |||||
return culture; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,39 @@ | |||||
using CurrieTechnologies.Razor.SweetAlert2; | |||||
namespace AbrBlazorTools; | |||||
public static class SwalExtensions | |||||
{ | |||||
public static async Task<SweetAlertResult> ShowToast(this SweetAlertService service, string message) | |||||
{ | |||||
return await service.FireAsync( | |||||
new SweetAlertOptions | |||||
{ | |||||
Toast = true, | |||||
Text = message, | |||||
Timer = 5000 | |||||
}); | |||||
} | |||||
public static async Task<SweetAlertResult> ShowSwal(this SweetAlertService service, string title, string message, SweetAlertIcon icon) | |||||
{ | |||||
if (icon == SweetAlertIcon.Question) | |||||
return await service.FireAsync(new SweetAlertOptions | |||||
{ | |||||
ConfirmButtonText="Yes", | |||||
ShowConfirmButton = true, | |||||
ShowDenyButton = true, | |||||
ShowCloseButton = false, | |||||
Html = message, | |||||
Icon = icon, | |||||
Title = title, | |||||
}); | |||||
return await service.FireAsync(new SweetAlertOptions | |||||
{ | |||||
Html = message, | |||||
Icon = icon, | |||||
Title = title, | |||||
}); | |||||
} | |||||
} |
@@ -0,0 +1,59 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
using CurrieTechnologies.Razor.SweetAlert2; | |||||
namespace BlazorPages.Tools | |||||
{ | |||||
public class SwalUtils : ISwalUtils | |||||
{ | |||||
private readonly SweetAlertService _service; | |||||
public SwalUtils(SweetAlertService service) | |||||
{ | |||||
_service = service; | |||||
} | |||||
public async Task<SweetAlertResult> ShowToast(string message) | |||||
{ | |||||
return await _service.FireAsync( | |||||
new SweetAlertOptions | |||||
{ | |||||
Toast = true, | |||||
Text = message, | |||||
Timer = 5000, | |||||
ConfirmButtonText = "خُب" | |||||
}); | |||||
} | |||||
public async Task<SweetAlertResult> ShowSwal(string title, string message, SweetAlertIcon icon) | |||||
{ | |||||
if (icon == SweetAlertIcon.Question) | |||||
return await _service.FireAsync(new SweetAlertOptions | |||||
{ | |||||
ShowConfirmButton = true, | |||||
CancelButtonColor = "Red", | |||||
ShowCloseButton = false, | |||||
ShowCancelButton = true, | |||||
Html = message, | |||||
Icon = icon, | |||||
Title = title, | |||||
CancelButtonText = "انصراف", | |||||
ConfirmButtonText = "تائید" | |||||
}); | |||||
return await _service.FireAsync(new SweetAlertOptions | |||||
{ | |||||
ShowConfirmButton = false, | |||||
ShowCloseButton = false, | |||||
ShowCancelButton = true, | |||||
Html = message, | |||||
Icon = icon, | |||||
Title = title, | |||||
CancelButtonText = "خًب" | |||||
}); | |||||
} | |||||
} | |||||
} | |||||
@@ -0,0 +1,29 @@ | |||||
namespace BlazorPages.Tools | |||||
{ | |||||
public static class UriExtenssions | |||||
{ | |||||
public static string GetQueryStringValue(string uri, string key) | |||||
{ | |||||
try | |||||
{ | |||||
var parts = uri.Split("?", options: StringSplitOptions.RemoveEmptyEntries); | |||||
if (parts.Length != 2) | |||||
return ""; | |||||
var qparts = parts[1].Split("&"); | |||||
foreach (var kv in qparts) | |||||
{ | |||||
var k = kv.Split("=")[0]; | |||||
var v = kv.Split("=")[1]; | |||||
if (k.Equals(key, StringComparison.InvariantCultureIgnoreCase)) | |||||
return v; | |||||
} | |||||
return ""; | |||||
} | |||||
catch (Exception) | |||||
{ | |||||
return ""; | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,24 @@ | |||||
@using System.Net.Http | |||||
@using System.Net.Http.Json | |||||
@using BlazorPages.Tools | |||||
@using Microsoft.AspNetCore.Components.Forms | |||||
@using Microsoft.AspNetCore.Components.Routing | |||||
@using Microsoft.AspNetCore.Components.Web | |||||
@using Microsoft.AspNetCore.Components.Web.Virtualization | |||||
@using Microsoft.AspNetCore.Components.WebAssembly.Http | |||||
@using Microsoft.JSInterop | |||||
@using AbrBlazorTools | |||||
@using CurrieTechnologies.Razor.SweetAlert2 | |||||
@using ApiUtils | |||||
@using BlazorPages.Shared | |||||
@using BootStrapComponents | |||||
@using Newtonsoft.Json | |||||
@inject Blazored.LocalStorage.ILocalStorageService LocalStorage | |||||
@inject NavigationManager NavManager; | |||||
@inject IJSRuntime JSRuntime | |||||
@inject ISwalUtils SwalUtils | |||||
@inject IBrowserTools BrowserTools | |||||
@inject IUserUtils _userUtils | |||||
@inject SweetAlertService _sweetAlertService | |||||
@inject ILoadingSpinner LoadingSpinner | |||||
@@ -0,0 +1,47 @@ | |||||
| |||||
.valid.modified:not([type=checkbox]) { | |||||
outline: 1px solid #26b050; | |||||
} | |||||
.invalid { | |||||
outline: 1px solid red; | |||||
} | |||||
.validation-message { | |||||
color: red; | |||||
} | |||||
#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." | |||||
} | |||||
.text-justify { | |||||
text-align: justify; | |||||
line-height: 1.8em; | |||||
} |
@@ -0,0 +1,99 @@ | |||||
@font-face { | |||||
font-family: Vazir; | |||||
src: url('h/assets/fonts/Vazirmatn-Regular'); | |||||
src: url('/assets/fonts/Vazirmatn-Regular.eot?#iefix') format('embedded-opentype'), url('/assets/fonts/Vazirmatn-Regular.woff2') format('woff2'), url('/assets/fonts/Vazirmatn-Regular.woff') format('woff'), url('/assets/fonts/Vazirmatn-Regular.ttf') format('truetype'); | |||||
font-weight: normal; | |||||
font-display: swap; | |||||
} | |||||
@font-face { | |||||
font-family: Vazir; | |||||
src: url('/assets/fonts/Vazirmatn-Bold.eot'); | |||||
src: url('/assets/fonts/Vazirmatn-Bold.eot?#iefix') format('embedded-opentype'), url('/assets/fonts/Vazirmatn-Bold.woff2') format('woff2'), url('/assets/fonts/Vazirmatn-Bold.woff') format('woff'), url('/assets/fonts/Vazirmatn-Bold.ttf') format('truetype'); | |||||
font-weight: bold; | |||||
font-display: swap; | |||||
} | |||||
@font-face { | |||||
font-family: Vazir; | |||||
src: url('/assets/fonts/Vazirmatn-Thin.eot'); | |||||
src: url('/assets/fonts/Vazirmatn-Thin.eot?#iefix') format('embedded-opentype'), url('/assets/fonts/Vazirmatn-Thin.woff2') format('woff2'), url('/assets/fonts/Vazirmatn-Thin.woff') format('woff'), url('/assets/fonts/Vazirmatn-Thin.ttf') format('truetype'); | |||||
font-weight: 100; | |||||
font-display: swap; | |||||
} | |||||
@font-face { | |||||
font-family: Vazir; | |||||
src: url('/assets/fonts/Vazirmatn-Light.eot'); | |||||
src: url('/assets/fonts/Vazirmatn-Light.eot?#iefix') format('embedded-opentype'), url('/assets/fonts/Vazirmatn-Light.woff2') format('woff2'), url('/assets/fonts/Vazirmatn-Light.woff') format('woff'), url('/assets/fonts/Vazirmatn-Light.ttf') format('truetype'); | |||||
font-weight: 300; | |||||
font-display: swap; | |||||
} | |||||
@font-face { | |||||
font-family: Vazir; | |||||
src: url('/assets/fonts/Vazirmatn-Medium.eot'); | |||||
src: url('/assets/fonts/Vazirmatn-Medium.eot?#iefix') format('embedded-opentype'), url('/assets/fonts/Vazirmatn-Medium.woff2') format('woff2'), url('/assets/fonts/Vazirmatn-Medium.woff') format('woff'), url('/assets/fonts/Vazirmatn-Medium.ttf') format('truetype'); | |||||
font-weight: 500; | |||||
font-display: swap; | |||||
} | |||||
@font-face { | |||||
font-family: Vazir; | |||||
src: url('/assets/fonts/Vazirmatn-Black.eot'); | |||||
src: url('/assets/fonts/Vazirmatn-Black.eot?#iefix') format('embedded-opentype'), url('/assets/fonts/Vazirmatn-Black.woff2') format('woff2'), url('/assets/fonts/Vazirmatn-Black.woff') format('woff'), url('/assets/fonts/Vazirmatn-Black.ttf') format('truetype'); | |||||
font-weight: 900; | |||||
font-display: swap; | |||||
} | |||||
* { | |||||
font-family: Vazir !important; | |||||
*font-size: 2vh; | |||||
} | |||||
*::-webkit-scrollbar { | |||||
width: 8px; | |||||
} | |||||
*::-webkit-scrollbar-track { | |||||
border-radius: 8px; | |||||
background-color: #cacaca; | |||||
border: 1px solid #cacaca; | |||||
} | |||||
::-webkit-scrollbar-thumb { | |||||
border-radius: 6px; | |||||
border: 3px solid transparent; | |||||
background-clip: content-box; | |||||
background-color: #e7e7e7; | |||||
} | |||||
.hamburger { | |||||
width: 200px; | |||||
height: 120px; | |||||
position: relative; | |||||
margin: 0 20px; | |||||
} | |||||
.hamburger { | |||||
&::before, &::after | |||||
{ | |||||
content: ''; | |||||
position: absolute; | |||||
width: 100%; | |||||
height: 20px; | |||||
color: #333; | |||||
background-color: currentColor; | |||||
transition: all .45s ease-in-out; | |||||
&::before | |||||
{ | |||||
top: 0; | |||||
transform: rotate(0); | |||||
} | |||||
&::after { | |||||
bottom: 0; | |||||
box-shadow: 0 -50px currentColor; | |||||
} | |||||
} |
@@ -0,0 +1 @@ | |||||
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg height="32px" id="Layer_1" style="enable-background:new 0 0 32 32;" version="1.1" viewBox="0 0 32 32" width="32px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M4,10h24c1.104,0,2-0.896,2-2s-0.896-2-2-2H4C2.896,6,2,6.896,2,8S2.896,10,4,10z M28,14H4c-1.104,0-2,0.896-2,2 s0.896,2,2,2h24c1.104,0,2-0.896,2-2S29.104,14,28,14z M28,22H4c-1.104,0-2,0.896-2,2s0.896,2,2,2h24c1.104,0,2-0.896,2-2 S29.104,22,28,22z"/></svg> |