Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
H
HIAST-Clinics
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
almohanad.hafez
HIAST-Clinics
Commits
92cd910a
Unverified
Commit
92cd910a
authored
Aug 22, 2024
by
Almouhannad Hafez
Committed by
GitHub
Aug 22, 2024
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #12 from Almouhannad/F_Add-authentication-service
F add authentication service
parents
9bec7b49
92c4497b
Changes
31
Hide whitespace changes
Inline
Side-by-side
Showing
31 changed files
with
631 additions
and
128 deletions
+631
-128
IJWTProvider.cs
Clinics.Backend/Application/Abstractions/JWT/IJWTProvider.cs
+2
-1
LoginCommandHandler.cs
...d/Application/Users/Commands/Login/LoginCommandHandler.cs
+11
-10
LoginResponse.cs
...Backend/Application/Users/Commands/Login/LoginResponse.cs
+3
-14
JWTProvider.cs
...nd/Persistence/Identity/Authentication/JWT/JWTProvider.cs
+12
-6
app-routing.module.ts
Clinics.Frontend/src/app/app-routing.module.ts
+27
-2
app.module.ts
Clinics.Frontend/src/app/app.module.ts
+14
-2
login-command.ts
...end/src/app/classes/Authentication/Login/login-command.ts
+2
-2
login-response.ts
...nd/src/app/classes/Authentication/Login/login-response.ts
+4
-0
login-result.ts
...tend/src/app/classes/Authentication/Login/login-result.ts
+9
-0
roles.ts
Clinics.Frontend/src/app/classes/Authentication/roles.ts
+6
-0
user-data.ts
Clinics.Frontend/src/app/classes/Authentication/user-data.ts
+13
-0
login-form.component.html
...nents/Authentication/login-form/login-form.component.html
+22
-21
login-form.component.ts
...ponents/Authentication/login-form/login-form.component.ts
+36
-4
forbidden.component.css
...c/app/components/errors/forbidden/forbidden.component.css
+0
-0
forbidden.component.html
.../app/components/errors/forbidden/forbidden.component.html
+4
-0
forbidden.component.ts
...rc/app/components/errors/forbidden/forbidden.component.ts
+10
-0
not-found.component.css
...c/app/components/errors/not-found/not-found.component.css
+0
-0
not-found.component.html
.../app/components/errors/not-found/not-found.component.html
+4
-0
not-found.component.ts
...rc/app/components/errors/not-found/not-found.component.ts
+10
-0
home.component.html
Clinics.Frontend/src/app/components/home/home.component.html
+5
-28
home.component.ts
Clinics.Frontend/src/app/components/home/home.component.ts
+1
-11
header.component.css
...d/src/app/components/template/header/header.component.css
+34
-16
header.component.html
.../src/app/components/template/header/header.component.html
+101
-5
header.component.ts
...nd/src/app/components/template/header/header.component.ts
+57
-1
layout.component.css
...d/src/app/components/template/layout/layout.component.css
+12
-0
layout.component.html
.../src/app/components/template/layout/layout.component.html
+6
-3
layout.component.ts
...nd/src/app/components/template/layout/layout.component.ts
+18
-2
authentication.service.ts
...src/app/services/authentication/authentication.service.ts
+76
-0
role-guard.ts
...tend/src/app/services/authentication/guards/role-guard.ts
+43
-0
authentication.interceptor.ts
.../authentication/interceptor/authentication.interceptor.ts
+32
-0
jwtHandler.ts
...cs.Frontend/src/app/services/authentication/jwtHandler.ts
+57
-0
No files found.
Clinics.Backend/Application/Abstractions/JWT/IJWTProvider.cs
View file @
92cd910a
using
Domain.Entities.Identity.Users
;
using
Domain.Entities.People.Shared
;
namespace
Application.Abstractions.JWT
;
public
interface
IJWTProvider
{
string
Generate
(
User
user
);
string
Generate
(
User
user
,
PersonalInfo
?
personalInfo
=
null
);
}
Clinics.Backend/Application/Users/Commands/Login/LoginCommandHandler.cs
View file @
92cd910a
...
...
@@ -33,37 +33,38 @@ public class LoginCommandHandler : CommandHandlerBase<LoginCommand, LoginRespons
return
Result
.
Failure
<
LoginResponse
>(
IdentityErrors
.
PasswordMismatch
);
#
endregion
#
region
2.
Generate
JWT
User
user
=
loginResult
.
Value
!;
string
token
=
_jwtProvider
.
Generate
(
user
);
#
endregion
#
region
3
.
Generate
Response
#
region
2
.
Generate
Response
#
region
3
.1
.
Admin
#
region
2
.1
.
Admin
if
(
user
.
Role
==
Roles
.
Admin
)
{
return
LoginResponse
.
GetResponse
(
user
,
token
);
var
token
=
_jwtProvider
.
Generate
(
user
);
return
LoginResponse
.
GetResponse
(
token
);
}
#
endregion
#
region
3
.2
.
Doctor
#
region
2
.2
.
Doctor
if
(
user
.
Role
==
Roles
.
Doctor
)
{
var
doctorUserResult
=
await
_userRepository
.
GetDoctorUserByUserNameFullAsync
(
user
.
UserName
);
if
(
doctorUserResult
.
IsFailure
)
return
Result
.
Failure
<
LoginResponse
>(
doctorUserResult
.
Error
);
return
LoginResponse
.
GetResponse
(
doctorUserResult
.
Value
.
User
,
token
,
doctorUserResult
.
Value
.
Doctor
.
PersonalInfo
);
var
token
=
_jwtProvider
.
Generate
(
user
,
doctorUserResult
.
Value
.
Doctor
.
PersonalInfo
);
return
LoginResponse
.
GetResponse
(
token
);
}
#
endregion
#
region
3
.3
.
Receptionist
user
#
region
2
.3
.
Receptionist
user
if
(
user
.
Role
==
Roles
.
Receptionist
)
{
var
receptionistUser
=
await
_userRepository
.
GetReceptionistUserByUserNameFullAsync
(
user
.
UserName
);
if
(
receptionistUser
.
IsFailure
)
return
Result
.
Failure
<
LoginResponse
>(
receptionistUser
.
Error
);
return
LoginResponse
.
GetResponse
(
receptionistUser
.
Value
.
User
,
token
,
receptionistUser
.
Value
.
PersonalInfo
);
var
token
=
_jwtProvider
.
Generate
(
user
,
receptionistUser
.
Value
.
PersonalInfo
);
return
LoginResponse
.
GetResponse
(
token
);
}
#
endregion
...
...
Clinics.Backend/Application/Users/Commands/Login/LoginResponse.cs
View file @
92cd910a
...
...
@@ -8,27 +8,16 @@ namespace Application.Users.Commands.Login;
public
class
LoginResponse
{
public
int
Id
{
get
;
set
;
}
public
string
UserName
{
get
;
set
;
}
=
null
!;
public
string
JWT
{
get
;
set
;
}
=
null
!;
public
string
FullName
{
get
;
set
;
}
=
null
!;
public
static
Result
<
LoginResponse
>
GetResponse
(
User
user
,
string
jwt
,
PersonalInfo
?
personalInfo
=
null
)
public
static
Result
<
LoginResponse
>
GetResponse
(
string
jwt
)
{
if
(
jwt
is
null
)
return
Result
.
Failure
<
LoginResponse
>(
IdentityErrors
.
NotFound
);
var
response
=
new
LoginResponse
{
Id
=
user
.
Id
,
UserName
=
user
.
UserName
,
JWT
=
jwt
};
if
(
personalInfo
is
null
)
{
if
(
user
.
Role
!=
Roles
.
Admin
)
return
Result
.
Failure
<
LoginResponse
>(
IdentityErrors
.
NotFound
);
response
.
FullName
=
Roles
.
AdminName
;
return
response
;
}
response
.
FullName
=
personalInfo
.
FullName
;
return
response
;
}
}
Clinics.Backend/Persistence/Identity/Authentication/JWT/JWTProvider.cs
View file @
92cd910a
using
Application.Abstractions.JWT
;
using
Domain.Entities.Identity.Users
;
using
Domain.Entities.People.Shared
;
using
Microsoft.Extensions.Options
;
using
Microsoft.IdentityModel.Tokens
;
using
System.IdentityModel.Tokens.Jwt
;
...
...
@@ -17,15 +18,20 @@ public sealed class JWTProvider : IJWTProvider
_options
=
options
.
Value
;
}
public
string
Generate
(
User
user
)
public
string
Generate
(
User
user
,
PersonalInfo
?
personalInfo
=
null
)
{
var
claims
=
new
Claim
[]
var
claims
=
new
List
<
Claim
>
{
new
(
ClaimTypes
.
Name
,
user
.
UserName
),
new
(
ClaimTypes
.
Role
,
user
.
Role
.
Name
)
new
(
"id"
,
user
.
Id
.
ToString
()
),
new
(
"userName"
,
user
.
UserName
),
new
(
"role"
,
user
.
Role
.
Name
)
};
if
(
personalInfo
is
not
null
)
{
claims
.
Add
(
new
Claim
(
"fullName"
,
personalInfo
.
FullName
));
}
var
signingCredentials
=
new
SigningCredentials
(
new
SymmetricSecurityKey
(
Encoding
.
UTF8
.
GetBytes
(
_options
.
SecretKey
)),
...
...
@@ -34,7 +40,7 @@ public sealed class JWTProvider : IJWTProvider
var
token
=
new
JwtSecurityToken
(
_options
.
Issuer
,
_options
.
Audience
,
claims
,
claims
.
ToArray
()
,
null
,
DateTime
.
UtcNow
.
AddDays
(
30
),
signingCredentials
);
...
...
Clinics.Frontend/src/app/app-routing.module.ts
View file @
92cd910a
import
{
NgModule
}
from
'@angular/core'
;
import
{
RouterModule
,
Routes
}
from
'@angular/router'
;
import
{
HomeComponent
}
from
'./components/home/home.component'
;
import
{
RoleGuard
}
from
'./services/authentication/guards/role-guard'
;
import
{
Roles
}
from
'./classes/Authentication/roles'
;
import
{
ForbiddenComponent
}
from
'./components/errors/forbidden/forbidden.component'
;
import
{
NotFoundComponent
}
from
'./components/errors/not-found/not-found.component'
;
const
routes
:
Routes
=
[
{
path
:
''
,
component
:
HomeComponent
}
];
{
path
:
''
,
redirectTo
:
'home'
,
pathMatch
:
'full'
,
},
{
path
:
'home'
,
component
:
HomeComponent
,
canActivate
:
[
RoleGuard
],
data
:
{
role
:
Roles
.
NotRegistered
}
},
{
path
:
'errors/forbidden'
,
component
:
ForbiddenComponent
,
canActivate
:
[
RoleGuard
],
data
:
{
role
:
Roles
.
NotRegistered
}
},
// Everything else
{
path
:
'**'
,
component
:
NotFoundComponent
}
];
@
NgModule
({
imports
:
[
RouterModule
.
forRoot
(
routes
)],
exports
:
[
RouterModule
]
...
...
Clinics.Frontend/src/app/app.module.ts
View file @
92cd910a
...
...
@@ -13,6 +13,12 @@ import { FooterComponent } from './components/template/footer/footer.component';
import
{
HomeComponent
}
from
'./components/home/home.component'
;
import
{
LoginFormComponent
}
from
'./components/Authentication/login-form/login-form.component'
;
import
{
FormsModule
}
from
'@angular/forms'
;
import
{
HTTP_INTERCEPTORS
,
HttpClientModule
}
from
'@angular/common/http'
;
import
{
AuthenticationService
}
from
'./services/authentication/authentication.service'
;
import
{
AuthenticationInterceptor
}
from
'./services/authentication/interceptor/authentication.interceptor'
;
import
{
ForbiddenComponent
}
from
'./components/errors/forbidden/forbidden.component'
;
import
{
NotFoundComponent
}
from
'./components/errors/not-found/not-found.component'
;
@
NgModule
({
imports
:
[
...
...
@@ -22,12 +28,16 @@ import { FormsModule } from '@angular/forms';
NgbModule
,
ToastrModule
.
forRoot
(),
FormsModule
,
HttpClientModule
,
],
// creators of services that this module contributes to the
// global collection of services; they become accessible in
// all parts of the app
providers
:
[],
providers
:
[
AuthenticationService
,
{
provide
:
HTTP_INTERCEPTORS
,
useClass
:
AuthenticationInterceptor
,
multi
:
true
},
],
// components and directives that belong to this module
// the subset of declarations that should be visible and usable in
...
...
@@ -38,7 +48,9 @@ import { FormsModule } from '@angular/forms';
HeaderComponent
,
FooterComponent
,
HomeComponent
,
LoginFormComponent
LoginFormComponent
,
ForbiddenComponent
,
NotFoundComponent
],
// identifies the root component that Angular should
...
...
Clinics.Frontend/src/app/classes/Authentication/login-command.ts
→
Clinics.Frontend/src/app/classes/Authentication/
Login/
login-command.ts
View file @
92cd910a
export
class
LoginCommand
{
export
class
LoginCommand
{
public
userName
!
:
string
;
public
password
!
:
string
;
}
Clinics.Frontend/src/app/classes/Authentication/Login/login-response.ts
0 → 100644
View file @
92cd910a
export
class
LoginResponse
{
public
jwt
!
:
string
;
}
Clinics.Frontend/src/app/classes/Authentication/Login/login-result.ts
0 → 100644
View file @
92cd910a
export
class
LoginResult
{
constructor
(
status
:
boolean
,
errorMessage
?:
string
)
{
this
.
status
=
status
;
this
.
errorMessage
=
errorMessage
;
}
public
status
:
Boolean
;
public
errorMessage
?:
string
;
}
\ No newline at end of file
Clinics.Frontend/src/app/classes/Authentication/roles.ts
0 → 100644
View file @
92cd910a
export
class
Roles
{
public
static
readonly
Admin
:
string
=
"admin"
;
public
static
readonly
Doctor
:
string
=
"doctor"
;
public
static
readonly
Receptionist
:
string
=
"receptionist"
;
public
static
readonly
NotRegistered
:
string
=
"notRegistered"
;
}
\ No newline at end of file
Clinics.Frontend/src/app/classes/Authentication/user-data.ts
0 → 100644
View file @
92cd910a
export
class
UserData
{
public
id
:
number
;
public
userName
!
:
string
;
public
role
!
:
string
;
public
fullName
?:
string
;
constructor
(
id
:
number
,
userName
:
string
,
role
:
string
,
fullName
?:
string
){
this
.
id
=
id
;
this
.
userName
=
userName
;
this
.
role
=
role
;
this
.
fullName
=
fullName
;
}
}
\ No newline at end of file
Clinics.Frontend/src/app/components/Authentication/login-form/login-form.component.html
View file @
92cd910a
<div
class=
"custom-child"
dir=
"rtl"
>
<div>
<div
class=
"text-center"
>
<div
class=
"text-center"
>
<h3
style=
"font-weight: 800;"
>
تسجيل الدخول
</h3>
</div>
<hr>
<div
*
ngIf=
"isFailure"
class=
"d-grid mb-2"
>
<button
type=
"button"
class=
"btn btn-danger"
style=
"cursor: auto;"
>
خطأ: {{errorMessage}}
</button>
</div>
<form
#
loginForm=
"ngForm"
(
ngSubmit
)="
onSubmit
()"
class=
"text-center"
autocomplete=
"off"
>
<!-- To avoid first field auto focus -->
...
...
@@ -15,36 +19,34 @@
</div>
<div
class=
"form-group mb-4"
>
<label
for=
"username"
class=
"col-form-label custom-label mb-2"
>
اسم المستخدم
<span
class=
"text-danger"
>
*
</span></label>
<input
type=
"text"
class=
"form-control text-center custom-input"
placeholder=
"ادخل اسم المستخدم"
dir=
"ltr"
[(
ngModel
)]="
formModel
.
userName
"
name=
"userName"
#
userName=
"ngModel"
required
maxlength=
"50"
>
<label
for=
"username"
class=
"col-form-label custom-label mb-2"
>
اسم المستخدم
<span
class=
"text-danger"
>
*
</span></label>
<input
type=
"text"
class=
"form-control text-center custom-input"
placeholder=
"ادخل اسم المستخدم"
dir=
"ltr"
[(
ngModel
)]="
formModel
.
userName
"
name=
"userName"
#
userName=
"ngModel"
required
maxlength=
"50"
>
<div
*
ngIf=
"(userName.touched || userName.dirty) && userName.errors"
class=
"mt-2"
>
<span
class=
"text-danger"
>
{{
userName.errors['required'] ? 'هذا الحقل مطلوب'
: ''
userName.errors['required'] ? 'هذا الحقل مطلوب'
: ''
}}
</span>
</div>
</div>
<div
class=
"form-group mb-4"
>
<label
for=
"password"
class=
"col-form-label custom-label mb-2"
>
كلمة المرور
<span
class=
"text-danger"
>
*
</span></label>
<input
type=
"password"
class=
"form-control text-center custom-input mb-2"
placeholder=
"ادخل كلمة المرور"
dir=
"ltr"
[(
ngModel
)]="
formModel
.
password
"
name=
"password"
#
password=
"ngModel"
required
maxlength=
"50"
>
<label
for=
"password"
class=
"col-form-label custom-label mb-2"
>
كلمة المرور
<span
class=
"text-danger"
>
*
</span></label>
<input
type=
"password"
class=
"form-control text-center custom-input mb-2"
placeholder=
"ادخل كلمة المرور"
dir=
"ltr"
[(
ngModel
)]="
formModel
.
password
"
name=
"password"
#
password=
"ngModel"
required
maxlength=
"50"
>
<div
*
ngIf=
"(password.touched || password.dirty) && password.errors"
class=
"mt-2"
>
<span
class=
"text-danger"
>
{{
password.errors['required'] ? 'هذا الحقل مطلوب'
: ''
password.errors['required'] ? 'هذا الحقل مطلوب'
: ''
}}
</span>
</div>
...
...
@@ -52,12 +54,11 @@
</div>
<div
class=
"d-grid gap-3"
>
<button
type=
"submit"
class=
"btn btn-outline-primary"
[
disabled
]="!
loginForm
.
dirty
||
loginForm
.
invalid
"
>
دخول
</button>
<button
type=
"submit"
class=
"btn btn-outline-primary"
[
disabled
]="!
loginForm
.
dirty
||
loginForm
.
invalid
"
>
دخول
</button>
<button
type=
"button"
class=
"btn btn-outline-secondary custom-cancel-button"
(
click
)="
parentModal
.
dismiss
()"
>
الغاء
</button>
(
click
)="
parentModal
.
dismiss
()"
>
الغاء
</button>
</div>
</form>
</div>
...
...
Clinics.Frontend/src/app/components/Authentication/login-form/login-form.component.ts
View file @
92cd910a
import
{
Component
,
In
put
,
ViewChild
}
from
'@angular/core'
;
import
{
Component
,
EventEmitter
,
Input
,
Out
put
,
ViewChild
}
from
'@angular/core'
;
import
{
NgForm
}
from
'@angular/forms'
;
import
{
LoginCommand
}
from
'../../../classes/Authentication/login-command'
;
import
{
LoginCommand
}
from
'../../../classes/Authentication/Login/login-command'
;
import
{
AuthenticationService
}
from
'../../../services/authentication/authentication.service'
;
import
{
UserData
}
from
'../../../classes/Authentication/user-data'
;
@
Component
({
selector
:
'app-login-form'
,
...
...
@@ -9,18 +11,48 @@ import { LoginCommand } from '../../../classes/Authentication/login-command';
})
export
class
LoginFormComponent
{
//#region CTOR DI
constructor
(
private
authenticationService
:
AuthenticationService
)
{}
//#endregion
//#region Inputs
@
Input
(
"parentModal"
)
parentModal
:
any
;
//#endregion
//#region Outputs
@
Output
(
"loggedIn"
)
loggedIn
:
EventEmitter
<
UserData
>
=
new
EventEmitter
();
//#endregion
//#region Variables
@
ViewChild
(
"loginForm"
)
loginForm
:
NgForm
;
formModel
:
LoginCommand
=
new
LoginCommand
();
isFailure
:
boolean
=
false
;
errorMessage
:
string
=
''
;
//#endregion
//#region On submit
onSubmit
():
void
{}
onSubmit
():
void
{
if
(
this
.
loginForm
.
valid
){
this
.
isFailure
=
false
;
this
.
errorMessage
=
''
;
this
.
authenticationService
.
login
(
this
.
formModel
)
.
subscribe
(
result
=>
{
if
(
result
.
status
===
false
){
this
.
isFailure
=
true
;
this
.
errorMessage
=
result
.
errorMessage
!
;
}
else
{
this
.
loggedIn
.
emit
(
this
.
authenticationService
.
getUserData
()
!
);
}
}
);
this
.
loginForm
.
form
.
markAsPristine
();
}
}
//#endregion
}
Clinics.Frontend/src/app/components/errors/forbidden/forbidden.component.css
0 → 100644
View file @
92cd910a
Clinics.Frontend/src/app/components/errors/forbidden/forbidden.component.html
0 → 100644
View file @
92cd910a
<div
class=
"cutom-child text-center"
style=
"padding: 6em;"
>
<i
class=
"bi bi-exclamation-triangle-fill text-danger"
style=
"font-size: 4em;"
></i>
<h1
class=
"text-danger text-center"
style=
"font-family: Cairo;"
>
خطأ: لا يمكنك الوصول إلى الصفحة التي طلبتها
</h1>
</div>
\ No newline at end of file
Clinics.Frontend/src/app/components/errors/forbidden/forbidden.component.ts
0 → 100644
View file @
92cd910a
import
{
Component
}
from
'@angular/core'
;
@
Component
({
selector
:
'app-forbidden'
,
templateUrl
:
'./forbidden.component.html'
,
styleUrl
:
'./forbidden.component.css'
})
export
class
ForbiddenComponent
{
}
Clinics.Frontend/src/app/components/errors/not-found/not-found.component.css
0 → 100644
View file @
92cd910a
Clinics.Frontend/src/app/components/errors/not-found/not-found.component.html
0 → 100644
View file @
92cd910a
<div
class=
"cutom-child text-center"
style=
"padding: 6em;"
>
<i
class=
"bi bi-exclamation-triangle-fill text-danger"
style=
"font-size: 4em;"
></i>
<h1
class=
"text-danger text-center"
style=
"font-family: Cairo;"
>
خطأ: الصفحة المطلوبة غير موجودة
</h1>
</div>
\ No newline at end of file
Clinics.Frontend/src/app/components/errors/not-found/not-found.component.ts
0 → 100644
View file @
92cd910a
import
{
Component
}
from
'@angular/core'
;
@
Component
({
selector
:
'app-not-found'
,
templateUrl
:
'./not-found.component.html'
,
styleUrl
:
'./not-found.component.css'
})
export
class
NotFoundComponent
{
}
Clinics.Frontend/src/app/components/home/home.component.html
View file @
92cd910a
...
...
@@ -15,21 +15,7 @@
<div
class=
"content row gy-4"
>
<div
class=
"col-lg-4 d-flex align-items-stretch"
>
<div
class=
"why-box"
>
<h3
class=
"text-center"
>
تسجيل الدخول
</h3>
<p
class=
"text-center text-light"
>
للاستفادة من خدمات هذا التطبيق، يحب عليك تسجيل الدخول أولاً
</p>
<div
class=
"text-center"
>
<button
(
click
)="
openLoginForm
(
loginModal
)"
class=
"btn more-btn"
>
<a>
تسجيل الدخول
<i
class=
"bi bi-chevron-right"
></i></a>
</button>
</div>
</div>
</div>
<div
class=
"col-lg-8 d-flex align-items-stretch"
>
<div
class=
"col-lg-11 d-flex align-items-stretch"
>
<div
class=
"d-flex flex-column justify-content-center"
>
<div
class=
"row gy-4"
>
...
...
@@ -37,7 +23,7 @@
<div
class=
"col-xl-4 d-flex align-items-stretch"
>
<div
class=
"icon-box"
>
<i
class=
"bi bi-clipboard-data"
></i>
<h4>
التاريخ المرضي
</h4>
<h4>
ا
ستعراض ا
لتاريخ المرضي
</h4>
<p
class=
"text-center"
style=
"font-weight: 500; color:var(--heading-color);"
>
يوفر
التطبيق امكانية استعراض التاريخ المرضي لموظفي المركز وأفراد عائلتهم
</p>
...
...
@@ -47,9 +33,9 @@
<div
class=
"col-xl-4 d-flex align-items-stretch"
>
<div
class=
"icon-box"
>
<i
class=
"bi bi-graph-up"
></i>
<h4>
ا
حصائيات
</h4>
<h4>
ا
ستخلاص الاحصائيات الطبيّة
</h4>
<p
class=
"text-center"
style=
"font-weight: 500; color:var(--heading-color);"
>
من
مزايا هذا التطبيق امكانية عرض احصائيات طبيّة
ل
موظفي المركز
مزايا هذا التطبيق امكانية عرض احصائيات طبيّة
عن
موظفي المركز
</p>
</div>
</div>
...
...
@@ -57,12 +43,11 @@
<div
class=
"col-xl-4 d-flex align-items-stretch"
>
<div
class=
"icon-box"
>
<i
class=
"bi bi-clock-history"
></i>
<h4>
دور
الانتظار
</h4>
<h4>
معالجة قائمة
الانتظار
</h4>
<p
style=
"font-weight: 500; color:var(--heading-color);"
>
يتيح هذا التطبيق إمكانية
تنظيم أدوار انتظار المرضى
</p>
</div>
</div>
</div>
</div>
</div>
...
...
@@ -199,12 +184,4 @@
</section>
<!-- #endregion -->
<!-- #region login pop-up -->
<ng-template
#
loginModal
let-modal
>
<div
class=
"modal-body"
>
<app-login-form
[
parentModal
]="
modal
"
></app-login-form>
</div>
</ng-template>
<!-- #endregion -->
</div>
\ No newline at end of file
Clinics.Frontend/src/app/components/home/home.component.ts
View file @
92cd910a
import
{
Component
}
from
'@angular/core'
;
import
{
NgbModal
}
from
'@ng-bootstrap/ng-bootstrap'
;
@
Component
({
selector
:
'app-home'
,
templateUrl
:
'./home.component.html'
,
styleUrl
:
'./home.component.css'
})
export
class
HomeComponent
{
export
class
HomeComponent
{
//#region CTOR DI
constructor
(
private
modalService
:
NgbModal
)
{}
//#endregion
//#region Login
openLoginForm
(
content
:
any
):
void
{
this
.
modalService
.
open
(
content
);
}
//#endregion
}
Clinics.Frontend/src/app/components/template/header/header.component.css
View file @
92cd910a
...
...
@@ -5,21 +5,46 @@
color
:
var
(
--heading-color
);
}
.custom-buttons
.btn-outline-primary
{
font-weight
:
65
0
;
.custom-buttons
.btn-outline-primary
{
font-weight
:
70
0
;
}
.custom-buttons
.btn-outline-secondary
{
color
:
var
(
--heading-color
);
font-weight
:
65
0
;
font-weight
:
70
0
;
border
:
none
;
}
.custom-buttons
.btn-outline-secondary
:hover
{
box-shadow
:
inset
0
0
0
2px
gray
;
box-shadow
:
inset
0
0
0
2px
gray
;
background-color
:
inherit
;
}
.navmenu
.dropdown
ul
li
a
{
cursor
:
pointer
;
font-weight
:
700
;
}
.navmenu
.dropdown
ul
li
a
{
display
:
block
;
text-align
:
center
;
}
.navmenu
.dropdown
ul
li
:not
(
:last-child
)
{
border-bottom
:
1px
solid
var
(
--heading-color
);
}
.custom-selected-dropdown
{
box-shadow
:
inset
0
0
0
2px
gray
;
}
.app-name-container
{
text-align
:
center
;
/* or any other alignment you prefer */
}
/* #endregion */
/* #region Header*/
...
...
@@ -219,7 +244,7 @@
.navmenu
.dropdown
ul
{
margin
:
0
;
padding
:
10px
0
;
background
:
var
(
--
nav-dropdown-
background-color
);
background
:
var
(
--background-color
);
display
:
block
;
position
:
absolute
;
visibility
:
hidden
;
...
...
@@ -253,22 +278,15 @@
color
:
var
(
--nav-dropdown-hover-color
);
}
.navmenu
.dropdown
:hover
>
ul
{
opacity
:
1
;
.navmenu
.dropdown
ul
{
opacity
:
0
;
top
:
100%
;
visibility
:
visible
;
}
.navmenu
.dropdown
.dropdown
ul
{
top
:
0
;
left
:
-90%
;
visibility
:
hidden
;
}
.navmenu
.dropdown
.dropdown
:hover
>
ul
{
.navmenu
.dropdown
ul
.show
{
opacity
:
1
;
top
:
0
;
left
:
-100%
;
top
:
100%
;
visibility
:
visible
;
}
}
...
...
Clinics.Frontend/src/app/components/template/header/header.component.html
View file @
92cd910a
<div
class=
"custom-child"
>
<div
class=
"header"
>
<div
class=
"branding d-flex align-items-center"
>
...
...
@@ -7,24 +8,119 @@
<img
src=
"assets/images/logo.png"
alt=
"Logo"
width=
"100em"
height=
"100em"
>
</div>
<h1
class=
"h3 m-auto custom-app-name"
>
المركز الطبي - برنامج الأطباء
</h1>
<div
class=
"container app-name-container m-auto"
>
<h1
class=
"h3 custom-app-name mb-4"
>
المركز الطبي - برنامج الأطباء
</h1>
<h3
dir=
"rtl"
*
ngIf=
"userData!==null"
class=
"h5 text-primary"
>
أهلاَ
<span
*
ngIf=
"userData.role===DOCTOR"
>
د.
</span>
{{userData.fullName}}!
</h3>
</div>
<!-- #region unregistered buttons -->
<nav
class=
"navmenu custom-buttons"
*
ngIf=
"userData === null"
>
<nav
class=
"navmenu custom-buttons"
>
<ul>
<li><a
href=
"#about"
><button
class=
"btn"
<li><a><button
class=
"btn"
[
class
]="{'
btn-outline-secondary
'
:
true
}"
(
click
)="
openLoginForm
(
loginModal
)"
>
تسجيل الدخول
</button></a></li>
<li><a
href=
"home#about"
><button
class=
"btn"
[
class
]="{'
btn-outline-primary
'
:
isSelected
('
About
'),
'
btn-outline-secondary
'
:
!
isSelected
('
About
')}"
(
click
)="
selectButton
('
About
')"
>
عن المستوصف
</button></a></li>
<li><a
href=
"#doctors"
><button
class=
"btn"
<li><a
href=
"home#doctors"
><button
class=
"btn"
[
class
]="{'
btn-outline-primary
'
:
isSelected
('
Doctors
'),
'
btn-outline-secondary
'
:
!
isSelected
('
Doctors
')}"
(
click
)="
selectButton
('
Doctors
')"
>
الفريق الطبي
</button></a></li>
<li><a
href=
"#"
><button
class=
"btn"
<li><a
href=
"home#"
><button
class=
"btn"
[
class
]="{'
btn-outline-primary
'
:
isSelected
('
Home
'),
'
btn-outline-secondary
'
:
!
isSelected
('
Home
')}"
(
click
)="
selectButton
('
Home
')"
>
الصفحة الرئيسية
</button></a></li>
</ul>
</nav>
<!-- #endregion -->
<!-- #region admin buttons -->
<nav
class=
"navmenu custom-buttons"
*
ngIf=
"userData?.role === ADMIN"
>
<ul>
<li
class=
"dropdown"
>
<button
[
ngClass
]="{'
btn
'
:
true
,
'
btn-outline-secondary
'
:
true
,
'
custom-selected-dropdown
'
:
showDropdown
}"
(
click
)="
showDropdown =
!showDropdown"
>
خيارات
<i
class=
"bi bi-chevron-down toggle-dropdown"
></i></button>
<ul
[
ngClass
]="{'
show
'
:
showDropdown
}"
>
<li
(
click
)="
onLogout
();"
><a>
تسجيل الخروج
</a></li>
</ul>
</li>
<li><a><button
class=
"btn"
[
class
]="{'
btn-outline-primary
'
:
isSelected
('
Admin-Receptionists
'),
'
btn-outline-secondary
'
:
!
isSelected
('
Admin-Receptionists
')}"
(
click
)="
selectButton
('
Admin-Receptionists
')"
>
موظفو الاستقبال
</button></a></li>
<li><a><button
class=
"btn"
[
class
]="{'
btn-outline-primary
'
:
isSelected
('
Admin-Dorctors
'),
'
btn-outline-secondary
'
:
!
isSelected
('
Admin-Dorctors
')}"
(
click
)="
selectButton
('
Admin-Dorctors
')"
>
الأطباء
</button></a></li>
<li><a
[
routerLink
]="'
home
'"
><button
class=
"btn"
[
class
]="{'
btn-outline-primary
'
:
isSelected
('
Home
'),
'
btn-outline-secondary
'
:
!
isSelected
('
Home
')}"
(
click
)="
selectButton
('
Home
')"
>
الصفحة الرئيسية
</button></a></li>
</ul>
</nav>
<!-- #endregion -->
<!-- #region doctor buttons -->
<nav
class=
"navmenu custom-buttons"
*
ngIf=
"userData?.role === DOCTOR"
>
<ul>
<li
class=
"dropdown"
>
<button
[
ngClass
]="{'
btn
'
:
true
,
'
btn-outline-secondary
'
:
true
,
'
custom-selected-dropdown
'
:
showDropdown
}"
(
click
)="
showDropdown =
!showDropdown"
>
خيارات
<i
class=
"bi bi-chevron-down toggle-dropdown"
></i></button>
<ul
[
ngClass
]="{'
show
'
:
showDropdown
}"
>
<li
(
click
)="
onLogout
();"
><a>
تسجيل الخروج
</a></li>
</ul>
</li>
<li><a
[
routerLink
]="'
home
'"
><button
class=
"btn"
[
class
]="{'
btn-outline-primary
'
:
isSelected
('
Home
'),
'
btn-outline-secondary
'
:
!
isSelected
('
Home
')}"
(
click
)="
selectButton
('
Home
')"
>
الصفحة الرئيسية
</button></a></li>
</ul>
</nav>
<!-- #endregion -->
<!-- #region receptionist buttons -->
<nav
class=
"navmenu custom-buttons"
*
ngIf=
"userData?.role === RECEPTIONIST"
>
<ul>
<li
class=
"dropdown"
>
<button
[
ngClass
]="{'
btn
'
:
true
,
'
btn-outline-secondary
'
:
true
,
'
custom-selected-dropdown
'
:
showDropdown
}"
(
click
)="
showDropdown =
!showDropdown"
>
خيارات
<i
class=
"bi bi-chevron-down toggle-dropdown"
></i></button>
<ul
[
ngClass
]="{'
show
'
:
showDropdown
}"
>
<li
(
click
)="
onLogout
();"
><a>
تسجيل الخروج
</a></li>
</ul>
</li>
<li><a
[
routerLink
]="'
home
'"
><button
class=
"btn"
[
class
]="{'
btn-outline-primary
'
:
isSelected
('
Home
'),
'
btn-outline-secondary
'
:
!
isSelected
('
Home
')}"
(
click
)="
selectButton
('
Home
')"
>
الصفحة الرئيسية
</button></a></li>
</ul>
</nav>
<!-- #endregion -->
</div>
</div>
</div>
<!-- #region login pop-up -->
<ng-template
#
loginModal
let-modal
>
<div
*
ngIf=
"userData===null"
class=
"modal-body"
>
<app-login-form
(
loggedIn
)="
onLogin
($
event
);
modal
.
dismiss
();"
[
parentModal
]="
modal
"
></app-login-form>
</div>
</ng-template>
<!-- #endregion -->
</div>
\ No newline at end of file
Clinics.Frontend/src/app/components/template/header/header.component.ts
View file @
92cd910a
import
{
Component
}
from
'@angular/core'
;
import
{
Component
,
EventEmitter
,
Input
,
Output
}
from
'@angular/core'
;
import
{
UserData
}
from
'../../../classes/Authentication/user-data'
;
import
{
AuthenticationService
}
from
'../../../services/authentication/authentication.service'
;
import
{
Router
}
from
'@angular/router'
;
import
{
Roles
}
from
'../../../classes/Authentication/roles'
;
import
{
NgbModal
}
from
'@ng-bootstrap/ng-bootstrap'
;
@
Component
({
selector
:
'app-header'
,
...
...
@@ -7,6 +12,57 @@ import { Component } from '@angular/core';
})
export
class
HeaderComponent
{
//#region CTOR DI
constructor
(
private
authenticationService
:
AuthenticationService
,
private
router
:
Router
,
private
modalService
:
NgbModal
)
{
}
//#endregion
//#region Inputs
@
Input
(
"userData"
)
userData
:
UserData
|
null
=
null
;
//#endregion
//#region Variables
//#region Roles
readonly
ADMIN
:
string
=
Roles
.
Admin
;
readonly
DOCTOR
:
string
=
Roles
.
Doctor
;
readonly
RECEPTIONIST
:
string
=
Roles
.
Receptionist
;
//#endregion
//#region Dropdown
showDropdown
:
boolean
=
false
;
//#endregion
//#endregion
//#region Login
//#region Login form pop-up
openLoginForm
(
content
:
any
):
void
{
this
.
modalService
.
open
(
content
);
}
//#endregion
//#region Login event
onLogin
(
userData
:
UserData
)
{
this
.
userData
=
userData
;
}
//#endregion
//#endregion
//#region On logout
onLogout
():
void
{
this
.
authenticationService
.
logout
();
this
.
userData
=
null
;
this
.
showDropdown
=
false
;
this
.
router
.
navigate
([
''
]);
}
//#endregion
//#region Selected button
private
selectedButton
:
string
=
'Home'
;
...
...
Clinics.Frontend/src/app/components/template/layout/layout.component.css
View file @
92cd910a
/* #region Custom */
.custom-parent
{
display
:
flex
;
flex-direction
:
column
;
min-height
:
100vh
;
}
app-footer
{
margin-top
:
auto
;
}
/* #endregion */
/* #region General */
::ng-deep
.custom-child
{
color
:
var
(
--default-color
);
...
...
Clinics.Frontend/src/app/components/template/layout/layout.component.html
View file @
92cd910a
<app-header
class=
"sticky-top"
></app-header>
<div
class=
"custom-parent"
>
<app-header
class=
"sticky-top"
[
userData
]="
userData
"
></app-header>
<router-outlet></router-outlet>
<router-outlet></router-outlet>
<app-footer></app-footer>
</div>
<app-footer></app-footer>
\ No newline at end of file
Clinics.Frontend/src/app/components/template/layout/layout.component.ts
View file @
92cd910a
import
{
Component
}
from
'@angular/core'
;
import
{
Component
,
OnInit
}
from
'@angular/core'
;
import
{
UserData
}
from
'../../../classes/Authentication/user-data'
;
import
{
AuthenticationService
}
from
'../../../services/authentication/authentication.service'
;
@
Component
({
selector
:
'app-layout'
,
templateUrl
:
'./layout.component.html'
,
styleUrl
:
'./layout.component.css'
})
export
class
LayoutComponent
{
export
class
LayoutComponent
implements
OnInit
{
//#region CTOR DI
constructor
(
private
authenticationService
:
AuthenticationService
){}
//#endregion
//#region On init
ngOnInit
():
void
{
this
.
userData
=
this
.
authenticationService
.
getUserData
();
}
//#endregion
//#region Variables
userData
:
UserData
|
null
=
null
;
//#endregion
}
Clinics.Frontend/src/app/services/authentication/authentication.service.ts
0 → 100644
View file @
92cd910a
import
{
Injectable
}
from
'@angular/core'
;
import
{
AppModule
}
from
'../../app.module'
;
import
{
HttpClient
,
HttpErrorResponse
,
HttpHeaders
,
HttpResponse
}
from
'@angular/common/http'
;
import
*
as
config
from
"../../../../config"
;
import
{
LoginCommand
}
from
'../../classes/Authentication/Login/login-command'
;
import
{
LoginResponse
}
from
'../../classes/Authentication/Login/login-response'
;
import
{
catchError
,
map
,
Observable
,
of
}
from
'rxjs'
;
import
{
LoginResult
}
from
'../../classes/Authentication/Login/login-result'
;
import
{
JWTHandler
}
from
'./jwtHandler'
;
import
{
UserData
}
from
'../../classes/Authentication/user-data'
;
import
{
Roles
}
from
'../../classes/Authentication/roles'
;
@
Injectable
({
providedIn
:
AppModule
})
export
class
AuthenticationService
{
//#region CTOR DI
constructor
(
private
http
:
HttpClient
)
{
}
//#endregion
//#region Constants
private
readonly
USERS_ENDPOINT
:
string
=
`
${
config
.
apiUrl
}
/Users`
//#endregion
//#region Login
private
postLogin
(
loginCommand
:
LoginCommand
):
Observable
<
LoginResponse
>
{
return
this
.
http
.
post
<
LoginResponse
>
(
this
.
USERS_ENDPOINT
,
loginCommand
);
}
login
(
loginCommand
:
LoginCommand
):
Observable
<
LoginResult
>
{
return
this
.
postLogin
(
loginCommand
).
pipe
(
map
((
loginResponse
:
LoginResponse
)
=>
{
JWTHandler
.
storeJWT
(
loginResponse
.
jwt
);
return
new
LoginResult
(
true
,
''
);
}),
catchError
((
error
:
HttpErrorResponse
)
=>
{
return
of
(
new
LoginResult
(
false
,
error
.
error
.
detail
));
})
);
}
//#endregion
//#region Logout
logout
():
void
{
JWTHandler
.
deleteJwtCookie
();
}
//#endregion
//#region Get user data
getUserData
():
UserData
|
null
{
let
jwt
=
JWTHandler
.
getJwtFromCookie
();
if
(
jwt
)
{
let
jwtJson
=
JWTHandler
.
decodeJWT
(
jwt
);
let
id
=
jwtJson
.
id
;
let
userName
=
jwtJson
.
userName
;
let
role
=
jwtJson
.
role
;
let
fullName
=
jwtJson
.
fullName
;
if
(
fullName
)
return
new
UserData
(
id
,
userName
,
role
,
fullName
);
if
(
role
==
Roles
.
Admin
)
return
new
UserData
(
id
,
userName
,
role
,
Roles
.
Admin
);
return
null
;
}
return
null
;
}
//#endregion
}
Clinics.Frontend/src/app/services/authentication/guards/role-guard.ts
0 → 100644
View file @
92cd910a
import
{
Injectable
}
from
'@angular/core'
;
import
{
CanActivate
,
ActivatedRouteSnapshot
,
RouterStateSnapshot
,
Router
}
from
'@angular/router'
;
import
{
AuthenticationService
}
from
'../authentication.service'
;
import
{
Roles
}
from
'../../../classes/Authentication/roles'
;
import
{
UserData
}
from
'../../../classes/Authentication/user-data'
;
@
Injectable
({
providedIn
:
'root'
})
export
class
RoleGuard
implements
CanActivate
{
constructor
(
private
authenticationService
:
AuthenticationService
,
private
router
:
Router
)
{
}
canActivate
(
route
:
ActivatedRouteSnapshot
,
state
:
RouterStateSnapshot
):
boolean
{
const
userData
:
UserData
|
null
=
this
.
authenticationService
.
getUserData
();
const
requiredRole
:
string
=
route
.
data
[
'role'
];
// No required role
if
(
!
requiredRole
)
return
true
;
// No required role
if
(
requiredRole
===
Roles
.
NotRegistered
)
return
true
;
// Reauired role, but not registered user
if
(
!
userData
)
{
this
.
router
.
navigate
([
"errors/forbidden"
]);
return
false
;
}
if
(
userData
.
role
!==
requiredRole
)
{
this
.
router
.
navigate
([
"errors/forbidden"
]);
return
false
;
}
return
true
;
}
}
\ No newline at end of file
Clinics.Frontend/src/app/services/authentication/interceptor/authentication.interceptor.ts
0 → 100644
View file @
92cd910a
import
{
Injectable
}
from
'@angular/core'
;
import
{
HttpInterceptor
,
HttpRequest
,
HttpHandler
,
HttpEvent
,
HttpHeaders
}
from
'@angular/common/http'
;
import
{
JWTHandler
}
from
'../jwtHandler'
;
import
{
Observable
}
from
'rxjs'
;
@
Injectable
()
export
class
AuthenticationInterceptor
implements
HttpInterceptor
{
//#region HTTP headers
private
readonly
HTTP_HEADERS
:
HttpHeaders
=
this
.
getHeaders
();
getHeaders
():
HttpHeaders
{
return
new
HttpHeaders
().
set
(
'Content-Type'
,
'application/json'
);
}
//#endregion
intercept
(
req
:
HttpRequest
<
any
>
,
next
:
HttpHandler
):
Observable
<
HttpEvent
<
any
>>
{
const
jwt
=
JWTHandler
.
getJwtFromCookie
();
// console.log(req);
req
=
req
.
clone
({
headers
:
this
.
HTTP_HEADERS
});
if
(
jwt
!==
null
)
{
req
=
req
.
clone
({
setHeaders
:
{
Authorization
:
`Bearer
${
jwt
}
`
}
});
}
// console.log(req);
return
next
.
handle
(
req
);
}
}
\ No newline at end of file
Clinics.Frontend/src/app/services/authentication/jwtHandler.ts
0 → 100644
View file @
92cd910a
export
class
JWTHandler
{
//#region JWT Decoder
public
static
decodeJWT
(
jwt
:
string
)
{
const
payload
=
jwt
.
split
(
'.'
)[
1
];
const
decodedPayload
=
atob
(
payload
);
const
textDecoder
=
new
TextDecoder
(
'utf-8'
);
const
claimsJson
=
JSON
.
parse
(
textDecoder
.
decode
(
new
Uint8Array
(
decodedPayload
.
split
(
''
).
map
(
c
=>
c
.
charCodeAt
(
0
))
)
)
);
return
claimsJson
;
}
//#endregion
//#region Sotre JWT in a cookie
public
static
storeJWT
(
jwt
:
string
):
void
{
const
claims
=
this
.
decodeJWT
(
jwt
);
const
expiresAt
=
claims
.
exp
*
1000
;
// convert seconds to milliseconds
const
cookieOptions
=
{
expires
:
new
Date
(
expiresAt
),
path
:
'/'
};
let
cookieString
=
`jwt=
${
jwt
}
;`
;
if
(
cookieOptions
.
expires
)
{
cookieString
+=
`expires=
${
cookieOptions
.
expires
.
toUTCString
()}
;`
;
}
if
(
cookieOptions
.
path
)
{
cookieString
+=
`path=
${
cookieOptions
.
path
}
;`
;
}
document
.
cookie
=
cookieString
;
}
//#endregion
//#region Get JWT from cookie
public
static
getJwtFromCookie
():
string
|
null
{
const
cookieString
=
document
.
cookie
;
const
cookiePairs
=
cookieString
.
split
(
';'
);
for
(
const
cookie
of
cookiePairs
)
{
const
[
key
,
value
]
=
cookie
.
trim
().
split
(
'='
);
if
(
key
===
'jwt'
)
{
return
value
}
}
return
null
;
}
//#endregion
//#region Delete JWT cookie
public
static
deleteJwtCookie
():
void
{
document
.
cookie
=
'jwt=; expires=Thu, 01 Jan 1970 00:00:00 GMT;'
;
}
//#endregion
}
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment