Selaa lähdekoodia

Add project files.

master
ehsan vaseghi 2 viikkoa sitten
commit
dbf38531fa
100 muutettua tiedostoa jossa 5642 lisäystä ja 0 poistoa
  1. +25
    -0
      .dockerignore
  2. +63
    -0
      .gitattributes
  3. +364
    -0
      .gitignore
  4. +376
    -0
      .idea/.idea.DadYar/.idea/.gitignore
  5. +12
    -0
      Api/.config/dotnet-tools.json
  6. +0
    -0
     
  7. +62
    -0
      Api/Api.csproj
  8. +28
    -0
      Api/AutoMapperConfig.cs
  9. +96
    -0
      Api/Controllers/AuthController.cs
  10. +175
    -0
      Api/Controllers/BaseController.cs
  11. +55
    -0
      Api/Controllers/PlantsController.cs
  12. BIN
     
  13. +97
    -0
      Api/Program.cs
  14. +38
    -0
      Api/Properties/launchSettings.json
  15. +23
    -0
      Api/Start/AddRequiredHeaderParameter.cs
  16. +51
    -0
      Api/Start/AuthRegister.cs
  17. +32
    -0
      Api/Start/DI.cs
  18. +47
    -0
      Api/Start/MvcRegistrar.cs
  19. +62
    -0
      Api/Start/SwaggerRegistrar.cs
  20. BIN
     
  21. +9
    -0
      Api/appsettings.Development.json
  22. +17
    -0
      Api/appsettings.json
  23. BIN
     
  24. +62
    -0
      BlazorPages/ApiUtils/UserUtils.cs
  25. +12
    -0
      BlazorPages/App.razor
  26. +36
    -0
      BlazorPages/BlazorPages.csproj
  27. +37
    -0
      BlazorPages/BootStrapComponents/BaseBootStrapComponent.cs
  28. +15
    -0
      BlazorPages/BootStrapComponents/BtButton.razor
  29. +109
    -0
      BlazorPages/BootStrapComponents/Generate.cs
  30. +8
    -0
      BlazorPages/BootStrapComponents/Jumbotron.razor
  31. +42
    -0
      BlazorPages/Dependency.cs
  32. +8
    -0
      BlazorPages/Pages/Components/BaseComponent.cs
  33. +37
    -0
      BlazorPages/Pages/Components/FloatingLabelInput.razor
  34. +56
    -0
      BlazorPages/Pages/Index.razor
  35. +128
    -0
      BlazorPages/Pages/Login/Login.razor
  36. +33
    -0
      BlazorPages/Shared/DialogBox.razor
  37. +15
    -0
      BlazorPages/Shared/DialogFormComponents/InputTextDialog.razor
  38. +1
    -0
      BlazorPages/Shared/Icons/CashIcon.razor
  39. +56
    -0
      BlazorPages/Shared/LayoutWithoutMenu.razor
  40. +197
    -0
      BlazorPages/Shared/MainLayout.razor
  41. +81
    -0
      BlazorPages/Shared/MainLayout.razor.css
  42. +70
    -0
      BlazorPages/Shared/NavMenu.razor
  43. +65
    -0
      BlazorPages/Shared/NavMenu.razor.css
  44. +25
    -0
      BlazorPages/Shared/ShowDialogButton.razor
  45. +7
    -0
      BlazorPages/Shared/TopBar.razor
  46. +65
    -0
      BlazorPages/Tools/BrowserTools.cs
  47. +178
    -0
      BlazorPages/Tools/DateTimeExtension.cs
  48. +234
    -0
      BlazorPages/Tools/HttpClientWithLoginToken.cs
  49. +32
    -0
      BlazorPages/Tools/ILoadingSpiner.cs
  50. +18
    -0
      BlazorPages/Tools/ISwalUtils.cs
  51. +20
    -0
      BlazorPages/Tools/InfiniteScrollingItemsProviderRequest.cs
  52. +72
    -0
      BlazorPages/Tools/Statics.cs
  53. +39
    -0
      BlazorPages/Tools/SwalExtensions.cs
  54. +59
    -0
      BlazorPages/Tools/SwalUtils.cs
  55. +29
    -0
      BlazorPages/Tools/UriExtenssions.cs
  56. +24
    -0
      BlazorPages/_Imports.razor
  57. BIN
     
  58. +47
    -0
      BlazorPages/wwwroot/assets/css/app.css
  59. +99
    -0
      BlazorPages/wwwroot/assets/css/base.css
  60. +1993
    -0
      BlazorPages/wwwroot/assets/css/login.css
  61. BIN
     
  62. BIN
     
  63. BIN
     
  64. BIN
     
  65. BIN
     
  66. BIN
     
  67. BIN
     
  68. BIN
     
  69. BIN
     
  70. BIN
     
  71. BIN
     
  72. BIN
     
  73. BIN
     
  74. BIN
     
  75. BIN
     
  76. BIN
     
  77. BIN
     
  78. BIN
     
  79. BIN
     
  80. +1
    -0
      BlazorPages/wwwroot/assets/images/Hamburger_icon.svg
  81. BIN
     
  82. BIN
     
  83. BIN
     
  84. BIN
     
  85. BIN
     
  86. BIN
     
  87. BIN
     
  88. BIN
     
  89. BIN
     
  90. BIN
     
  91. BIN
     
  92. BIN
     
  93. BIN
     
  94. BIN
     
  95. BIN
     
  96. BIN
     
  97. BIN
     
  98. BIN
     
  99. BIN
     
  100. BIN
     

+ 25
- 0
.dockerignore Näytä tiedosto

@@ -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

+ 63
- 0
.gitattributes Näytä tiedosto

@@ -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

+ 364
- 0
.gitignore Näytä tiedosto

@@ -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

+ 376
- 0
.idea/.idea.DadYar/.idea/.gitignore Näytä tiedosto

@@ -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

+ 12
- 0
Api/.config/dotnet-tools.json Näytä tiedosto

@@ -0,0 +1,12 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "5.0.10",
"commands": [
"dotnet-ef"
]
}
}
}


+ 62
- 0
Api/Api.csproj Näytä tiedosto

@@ -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>

+ 28
- 0
Api/AutoMapperConfig.cs Näytä tiedosto

@@ -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>();



}
}
}

+ 96
- 0
Api/Controllers/AuthController.cs Näytä tiedosto

@@ -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();
}
}



}
}

+ 175
- 0
Api/Controllers/BaseController.cs Näytä tiedosto

@@ -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; }
}
}

+ 55
- 0
Api/Controllers/PlantsController.cs Näytä tiedosto

@@ -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();
}

}
}


+ 97
- 0
Api/Program.cs Näytä tiedosto

@@ -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();

+ 38
- 0
Api/Properties/launchSettings.json Näytä tiedosto

@@ -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
}
}
}

+ 23
- 0
Api/Start/AddRequiredHeaderParameter.cs Näytä tiedosto

@@ -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,
});
}
}
}

+ 51
- 0
Api/Start/AuthRegister.cs Näytä tiedosto

@@ -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();

}
}
}

+ 32
- 0
Api/Start/DI.cs Näytä tiedosto

@@ -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);

}
}
}

+ 47
- 0
Api/Start/MvcRegistrar.cs Näytä tiedosto

@@ -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";

}
}
}

+ 62
- 0
Api/Start/SwaggerRegistrar.cs Näytä tiedosto

@@ -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();
});
}
}
}


+ 9
- 0
Api/appsettings.Development.json Näytä tiedosto

@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

+ 17
- 0
Api/appsettings.json Näytä tiedosto

@@ -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"
}
}


+ 62
- 0
BlazorPages/ApiUtils/UserUtils.cs Näytä tiedosto

@@ -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);


}

+ 12
- 0
BlazorPages/App.razor Näytä tiedosto

@@ -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>

+ 36
- 0
BlazorPages/BlazorPages.csproj Näytä tiedosto

@@ -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>

+ 37
- 0
BlazorPages/BootStrapComponents/BaseBootStrapComponent.cs Näytä tiedosto

@@ -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,
}
}

+ 15
- 0
BlazorPages/BootStrapComponents/BtButton.razor Näytä tiedosto

@@ -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();
}
}

+ 109
- 0
BlazorPages/BootStrapComponents/Generate.cs Näytä tiedosto

@@ -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
}

}

+ 8
- 0
BlazorPages/BootStrapComponents/Jumbotron.razor Näytä tiedosto

@@ -0,0 +1,8 @@
@namespace BootStrapComponents
@inherits BaseBootStrapComponent
<div class="jumbotron @(string.Join(" ",AutoClass())) @AdditionalClass">
@ChildContent
</div>
@code {

}

+ 42
- 0
BlazorPages/Dependency.cs Näytä tiedosto

@@ -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());



}
}
}

+ 8
- 0
BlazorPages/Pages/Components/BaseComponent.cs Näytä tiedosto

@@ -0,0 +1,8 @@
using Microsoft.AspNetCore.Components;

namespace Components
{
public class BaseComponent:ComponentBase
{
}
}

+ 37
- 0
BlazorPages/Pages/Components/FloatingLabelInput.razor Näytä tiedosto

@@ -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();

}

+ 56
- 0
BlazorPages/Pages/Index.razor Näytä tiedosto

@@ -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();
}

}

+ 128
- 0
BlazorPages/Pages/Login/Login.razor Näytä tiedosto

@@ -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");

}
}

}

+ 33
- 0
BlazorPages/Shared/DialogBox.razor Näytä tiedosto

@@ -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();
}
}

+ 15
- 0
BlazorPages/Shared/DialogFormComponents/InputTextDialog.razor Näytä tiedosto

@@ -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; }
}

+ 1
- 0
BlazorPages/Shared/Icons/CashIcon.razor Näytä tiedosto

@@ -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>

+ 56
- 0
BlazorPages/Shared/LayoutWithoutMenu.razor Näytä tiedosto

@@ -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");
}

}

+ 197
- 0
BlazorPages/Shared/MainLayout.razor Näytä tiedosto

@@ -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");
}

}

+ 81
- 0
BlazorPages/Shared/MainLayout.razor.css Näytä tiedosto

@@ -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;
}
}

+ 70
- 0
BlazorPages/Shared/NavMenu.razor Näytä tiedosto

@@ -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");

}
}

+ 65
- 0
BlazorPages/Shared/NavMenu.razor.css Näytä tiedosto

@@ -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;
}
}


+ 25
- 0
BlazorPages/Shared/ShowDialogButton.razor Näytä tiedosto

@@ -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();
}
}

+ 7
- 0
BlazorPages/Shared/TopBar.razor Näytä tiedosto

@@ -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>

+ 65
- 0
BlazorPages/Tools/BrowserTools.cs Näytä tiedosto

@@ -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();
}
}

+ 178
- 0
BlazorPages/Tools/DateTimeExtension.cs Näytä tiedosto

@@ -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,

}
}

+ 234
- 0
BlazorPages/Tools/HttpClientWithLoginToken.cs Näytä tiedosto

@@ -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);
}
}

+ 32
- 0
BlazorPages/Tools/ILoadingSpiner.cs Näytä tiedosto

@@ -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();
}
}

+ 18
- 0
BlazorPages/Tools/ISwalUtils.cs Näytä tiedosto

@@ -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);
}

}


+ 20
- 0
BlazorPages/Tools/InfiniteScrollingItemsProviderRequest.cs Näytä tiedosto

@@ -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);

}

+ 72
- 0
BlazorPages/Tools/Statics.cs Näytä tiedosto

@@ -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;
}
}
}

+ 39
- 0
BlazorPages/Tools/SwalExtensions.cs Näytä tiedosto

@@ -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,
});
}
}

+ 59
- 0
BlazorPages/Tools/SwalUtils.cs Näytä tiedosto

@@ -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 = "خًب"
});
}
}

}


+ 29
- 0
BlazorPages/Tools/UriExtenssions.cs Näytä tiedosto

@@ -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 "";
}
}
}
}

+ 24
- 0
BlazorPages/_Imports.razor Näytä tiedosto

@@ -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



+ 47
- 0
BlazorPages/wwwroot/assets/css/app.css Näytä tiedosto

@@ -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;
}

+ 99
- 0
BlazorPages/wwwroot/assets/css/base.css Näytä tiedosto

@@ -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;
}
}

+ 1993
- 0
BlazorPages/wwwroot/assets/css/login.css
File diff suppressed because it is too large
Näytä tiedosto





















+ 1
- 0
BlazorPages/wwwroot/assets/images/Hamburger_icon.svg Näytä tiedosto

@@ -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>





















Some files were not shown because too many files changed in this diff

Ladataan…
Peruuta
Tallenna