Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
M
Meu-Template-Angular-CSR
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
Trần Anh Phú
Meu-Template-Angular-CSR
Commits
ecf1ce08
Commit
ecf1ce08
authored
Nov 05, 2024
by
vtduong0912
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
update: pagination
parent
aba3496f
Changes
15
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
249 additions
and
26 deletions
+249
-26
job.model.ts
src/app/+job/data-access/model/job.model.ts
+20
-0
job.service.ts
src/app/+job/data-access/service/job.service.ts
+25
-0
job.component.html
src/app/+job/feature/job.component.html
+30
-0
job.component.scss
src/app/+job/feature/job.component.scss
+0
-0
job.component.ts
src/app/+job/feature/job.component.ts
+64
-0
job.routes.ts
src/app/+job/job.routes.ts
+10
-0
admin.guard.ts
src/app/+login/data-access/guard/admin.guard.ts
+9
-4
auth.guard.ts
src/app/+login/data-access/guard/auth.guard.ts
+9
-4
login.guard.ts
src/app/+login/data-access/guard/login.guard.ts
+13
-0
auth.service.ts
src/app/+login/data-access/service/auth.service.ts
+9
-5
login.component.ts
src/app/+login/feature/login.component.ts
+6
-7
shell.routes.ts
src/app/+shell/feature/shell.routes.ts
+14
-2
header.component.html
...+shell/ui/components/header/feature/header.component.html
+22
-3
header.component.ts
...p/+shell/ui/components/header/feature/header.component.ts
+17
-1
response.type.ts
src/app/shared/data-access/interface/response.type.ts
+1
-0
No files found.
src/app/+job/data-access/model/job.model.ts
0 → 100644
View file @
ecf1ce08
export
namespace
Jobs
{
export
interface
Response
{
items
:
Job
[];
total
:
number
}
export
interface
Job
{
id
:
null
|
string
;
type
:
string
;
created_at
:
string
;
company
:
string
;
company_url
:
string
;
location
:
string
;
title
:
string
;
description
:
string
;
how_to_apply
?:
string
;
company_logo
?:
null
|
string
;
url
?:
string
;
}
}
\ No newline at end of file
src/app/+job/data-access/service/job.service.ts
0 → 100644
View file @
ecf1ce08
import
{
HttpClient
,
HttpParams
,
HttpRequest
}
from
"@angular/common/http"
;
import
{
Injectable
}
from
"@angular/core"
;
import
{
Jobs
}
from
"../model/job.model"
;
import
{
ResponseResult
,
Rows
}
from
"../../../shared/data-access/interface/response.type"
;
import
{
Observable
,
map
}
from
"rxjs"
;
@
Injectable
({
providedIn
:
'root'
})
export
class
JobService
{
constructor
(
private
_http
:
HttpClient
,
)
{
}
jobsGet
(
index
:
number
,
pageSize
:
number
)
{
return
this
.
_http
.
get
<
ResponseResult
<
Rows
<
Jobs
.
Job
>>>
(
'jobs'
,
{
params
:
{
page
:
index
,
take
:
pageSize
}
});
}
}
\ No newline at end of file
src/app/+job/feature/job.component.html
0 → 100644
View file @
ecf1ce08
<div
class=
"tw-max-w-3xl tw-mx-auto"
>
<div
class=
"tw-flex tw-flex-row tw-flex-wrap tw-mt-6"
>
<nz-card
*
ngFor=
"let job of jobList?.rows"
class=
"tw-w-full tw-my-1 hover:tw-scale-105 tw-transition"
[
nzTitle
]="
job
.
title
"
[
nzExtra
]="
companyLink
"
>
<ng-template
#
companyLink
>
<a
href=
"{{ job.company_url }}"
>
{{ job.company_url }}
</a>
</ng-template>
<div
class=
"tw-flex tw-flex-cols tw-flex-wrap tw-gap-3"
>
<div
class=
"tw-pointer-events-auto tw-flex"
>
<nz-avatar
class=
"tw-my-auto"
nzSrc=
"{{ job.company_logo }}"
></nz-avatar>
</div>
<div>
<p>
Created at: {{ job.created_at | date: 'dd/mm/YYYY'}}
</p>
<p>
Company name: {{ job.company }}
</p>
<p>
Location: {{ job.location }}
</p>
</div>
<div>
</div>
</div>
</nz-card>
<div
class=
"tw-w-full tw-grid tw-justify-items-center tw-my-3"
>
<nz-pagination
(
nzPageIndexChange
)="
onPageIndexChange
($
event
)"
[
nzPageIndex
]="
pageIndex
"
[
nzPageSize
]="
pageSize
"
[
nzTotal
]="
jobList
?.
itemCount
||
0
*
10
"
>
</nz-pagination>
</div>
</div>
</div>
src/app/+job/feature/job.component.scss
0 → 100644
View file @
ecf1ce08
src/app/+job/feature/job.component.ts
0 → 100644
View file @
ecf1ce08
import
{
ChangeDetectionStrategy
,
ChangeDetectorRef
,
Component
,
EventEmitter
,
OnInit
,
Output
}
from
'@angular/core'
;
import
{
CommonModule
,
DatePipe
,
JsonPipe
,
NgFor
}
from
'@angular/common'
;
import
{
JobService
}
from
'../data-access/service/job.service'
import
{
ResponseResult
,
Rows
}
from
'../../shared/data-access/interface/response.type'
;
import
{
Jobs
}
from
'../data-access/model/job.model'
;
import
{
catchError
,
map
,
of
,
tap
}
from
'rxjs'
;
import
{
NzCardModule
}
from
'ng-zorro-antd/card'
;
import
{
NzAvatarModule
}
from
'ng-zorro-antd/avatar'
;
import
{
NzPaginationModule
}
from
'ng-zorro-antd/pagination'
;
@
Component
({
selector
:
'meu-job-list-page'
,
standalone
:
true
,
templateUrl
:
'./job.component.html'
,
styleUrls
:
[
'./job.component.scss'
],
imports
:
[
CommonModule
,
NgFor
,
JsonPipe
,
NzCardModule
,
NzAvatarModule
,
NzPaginationModule
,
DatePipe
],
changeDetection
:
ChangeDetectionStrategy
.
OnPush
,
})
export
class
JobComponent
implements
OnInit
{
jobList
:
Rows
<
Jobs
.
Job
>
|
null
=
null
;
pageIndex
:
number
=
1
;
pageSize
:
number
=
5
;
@
Output
()
pageIndexChange
:
EventEmitter
<
number
>
=
new
EventEmitter
<
number
>
;
constructor
(
private
_service
:
JobService
,
private
_changeDetectorRef
:
ChangeDetectorRef
,
)
{
}
ngOnInit
():
void
{
this
.
getAllJobs
();
}
onPageIndexChange
(
index
:
number
)
{
this
.
getAllJobs
(
index
,
5
);
}
getAllJobs
(
index
:
number
=
1
,
pageSize
:
number
=
5
)
{
this
.
_service
.
jobsGet
(
index
,
pageSize
)
.
pipe
(
tap
((
response
:
ResponseResult
<
Rows
<
Jobs
.
Job
>>
)
=>
{
this
.
jobList
=
response
.
responseData
;
this
.
_changeDetectorRef
.
markForCheck
();
}),
catchError
((
error
:
ResponseResult
<
any
>
)
=>
{
return
of
(
null
);
})
)
.
subscribe
();
}
}
src/app/+job/job.routes.ts
0 → 100644
View file @
ecf1ce08
import
{
Route
}
from
'@angular/router'
;
const
JOB_ROUTES
:
Route
[]
=
[
{
path
:
''
,
loadComponent
:
()
=>
import
(
'./feature/job.component'
).
then
((
m
)
=>
m
.
JobComponent
),
},
];
export
default
JOB_ROUTES
;
src/app/+login/data-access/guard/admin.guard.ts
View file @
ecf1ce08
import
{
Injectable
}
from
'@angular/core'
;
import
{
Injectable
}
from
'@angular/core'
;
import
{
CanActivate
}
from
'@angular/router'
;
import
{
CanActivate
,
Router
}
from
'@angular/router'
;
import
{
AuthService
}
from
'../service/auth.service'
;
@
Injectable
({
@
Injectable
({
providedIn
:
'root'
,
providedIn
:
'root'
,
})
})
export
class
AdminGuard
implements
CanActivate
{
export
class
AdminGuard
implements
CanActivate
{
constructor
()
{}
constructor
(
private
_service
:
AuthService
,
private
_router
:
Router
)
{}
canActivate
():
boolean
{
canActivate
():
boolean
{
const
isLoggedIn
=
this
.
_service
.
isLoggedIn
();
const
isAdmin
=
this
.
_service
.
isAdmin
();
if
(
!
isLoggedIn
||
!
isAdmin
)
{
this
.
_router
.
navigate
([
'/'
]);
return
false
;
}
return
true
;
return
true
;
// return false if user role is not admin
// return false;
}
}
}
}
src/app/+login/data-access/guard/auth.guard.ts
View file @
ecf1ce08
import
{
Injectable
}
from
'@angular/core'
;
import
{
Injectable
}
from
'@angular/core'
;
import
{
CanActivate
}
from
'@angular/router'
;
import
{
CanActivate
,
Router
}
from
'@angular/router'
;
import
{
AuthService
}
from
'../service/auth.service'
;
@
Injectable
({
@
Injectable
({
providedIn
:
'root'
,
providedIn
:
'root'
,
})
})
export
class
AuthGuard
implements
CanActivate
{
export
class
AuthGuard
implements
CanActivate
{
constructor
()
{}
constructor
(
private
_service
:
AuthService
,
private
_router
:
Router
)
{}
canActivate
():
boolean
{
canActivate
():
boolean
{
const
isLoggedIn
=
this
.
_service
.
isLoggedIn
();
const
isAdmin
=
this
.
_service
.
isAdmin
();
if
(
!
isLoggedIn
||
!
isAdmin
)
{
this
.
_router
.
navigate
([
'/'
]);
return
false
;
}
return
true
;
return
true
;
// return false if logged in
// return false;
}
}
}
}
src/app/+login/data-access/guard/login.guard.ts
0 → 100644
View file @
ecf1ce08
import
{
Injectable
}
from
'@angular/core'
;
import
{
CanActivate
,
Router
}
from
'@angular/router'
;
import
{
AuthService
}
from
'../service/auth.service'
;
@
Injectable
({
providedIn
:
'root'
,
})
export
class
LoginGuard
implements
CanActivate
{
constructor
(
private
_service
:
AuthService
,
private
_router
:
Router
)
{}
canActivate
():
boolean
{
return
!
this
.
_service
.
isLoggedIn
();
}
}
src/app/+login/data-access/service/auth.service.ts
View file @
ecf1ce08
...
@@ -15,10 +15,14 @@ export class AuthService {
...
@@ -15,10 +15,14 @@ export class AuthService {
loginPost
(
request
:
Login
.
Request
)
{
loginPost
(
request
:
Login
.
Request
)
{
return
this
.
_http
return
this
.
_http
.
post
<
ResponseResult
<
Login
.
Response
>>
(
'login'
,
request
)
.
post
<
ResponseResult
<
Login
.
Response
>>
(
'login'
,
request
)
.
pipe
(
}
map
((
response
)
=>
{
console
.
log
(
response
);
isLoggedIn
()
{
return
response
;
return
!!
localStorage
.
getItem
(
"token"
)
}));
}
isAdmin
()
{
const
role
=
localStorage
.
getItem
(
"role"
);
return
role
===
'admin'
;
}
}
}
}
\ No newline at end of file
src/app/+login/feature/login.component.ts
View file @
ecf1ce08
...
@@ -11,7 +11,7 @@ import { NzAlertModule } from 'ng-zorro-antd/alert';
...
@@ -11,7 +11,7 @@ import { NzAlertModule } from 'ng-zorro-antd/alert';
import
{
FormBuilder
,
FormGroup
,
ReactiveFormsModule
,
Validators
}
from
'@angular/forms'
;
import
{
FormBuilder
,
FormGroup
,
ReactiveFormsModule
,
Validators
}
from
'@angular/forms'
;
import
{
AuthService
}
from
'../data-access/service/auth.service'
;
import
{
AuthService
}
from
'../data-access/service/auth.service'
;
import
{
validateUsername
}
from
'../../shared/utils/form-validator/username.validator'
;
import
{
validateUsername
}
from
'../../shared/utils/form-validator/username.validator'
;
import
{
catchError
,
of
,
tap
}
from
'rxjs'
;
import
{
catchError
,
map
,
of
,
tap
}
from
'rxjs'
;
import
{
ResponseResult
}
from
'../../shared/data-access/interface/response.type'
;
import
{
ResponseResult
}
from
'../../shared/data-access/interface/response.type'
;
import
{
Login
}
from
'../data-access/model/login.model'
;
import
{
Login
}
from
'../data-access/model/login.model'
;
import
{
Router
}
from
'@angular/router'
;
import
{
Router
}
from
'@angular/router'
;
...
@@ -38,7 +38,7 @@ import { Router } from '@angular/router';
...
@@ -38,7 +38,7 @@ import { Router } from '@angular/router';
export
class
LoginComponent
implements
OnInit
{
export
class
LoginComponent
implements
OnInit
{
passwordVisible
:
boolean
=
false
;
passwordVisible
:
boolean
=
false
;
errorMsg
:
string
=
""
;
errorMsg
:
string
=
""
;
signInFormGroup
!
:
FormGroup
;
signInFormGroup
:
FormGroup
;
// isEdit: boolean = false;
// isEdit: boolean = false;
constructor
(
constructor
(
private
_fb
:
FormBuilder
,
private
_fb
:
FormBuilder
,
...
@@ -62,7 +62,7 @@ export class LoginComponent implements OnInit {
...
@@ -62,7 +62,7 @@ export class LoginComponent implements OnInit {
Object
.
values
(
this
.
signInFormGroup
.
controls
).
forEach
(
control
=>
{
Object
.
values
(
this
.
signInFormGroup
.
controls
).
forEach
(
control
=>
{
if
(
control
.
invalid
)
{
if
(
control
.
invalid
)
{
control
.
markAsDirty
();
control
.
markAsDirty
();
control
.
updateValueAndValidity
({
onlySelf
:
true
});
control
.
updateValueAndValidity
({
onlySelf
:
true
});
}
}
});
});
return
;
return
;
...
@@ -82,13 +82,12 @@ export class LoginComponent implements OnInit {
...
@@ -82,13 +82,12 @@ export class LoginComponent implements OnInit {
}
}
onSuccess
(
response
:
ResponseResult
<
Login
.
Response
>
)
{
onSuccess
(
response
:
ResponseResult
<
Login
.
Response
>
)
{
localStorage
.
setItem
(
"token"
,
JSON
.
stringify
(
response
.
responseData
?.
token
)
??
""
);
localStorage
.
setItem
(
"token"
,
response
.
responseData
?.
token
??
""
);
localStorage
.
setItem
(
"role"
,
JSON
.
stringify
(
response
.
responseData
?.
role
)
??
""
);
localStorage
.
setItem
(
"role"
,
response
.
responseData
?.
role
??
""
);
this
.
_router
.
navigate
([
'/home'
]);
this
.
_router
.
navigate
([
'/home'
]);
}
}
onError
(
error
:
ResponseResult
<
any
>
)
{
onError
(
error
:
ResponseResult
<
any
>
)
{
this
.
errorMsg
=
"Invalid username or password!"
;
this
.
_changeDetectorRef
.
markForCheck
();
}
}
}
}
src/app/+shell/feature/shell.routes.ts
View file @
ecf1ce08
import
{
Routes
}
from
'@angular/router'
;
import
{
Routes
}
from
'@angular/router'
;
import
{
LayoutComponent
}
from
'../ui/layout.component'
;
import
{
LayoutComponent
}
from
'../ui/layout.component'
;
import
{
LoginComponent
}
from
'../../+login/feature/login.component'
;
import
{
AuthGuard
}
from
'../../+login/data-access/guard/auth.guard'
;
import
{
AuthGuard
}
from
'../../+login/data-access/guard/auth.guard'
;
import
{
AdminGuard
}
from
'../../+login/data-access/guard/admin.guard'
;
import
{
AdminGuard
}
from
'../../+login/data-access/guard/admin.guard'
;
import
{
JobComponent
}
from
'../../+job/feature/job.component'
;
import
{
LoginGuard
}
from
'../../+login/data-access/guard/login.guard'
;
export
const
shellRoutes
:
Routes
=
[
export
const
shellRoutes
:
Routes
=
[
{
{
...
@@ -37,9 +38,20 @@ export const shellRoutes: Routes = [
...
@@ -37,9 +38,20 @@ export const shellRoutes: Routes = [
children
:
[
children
:
[
{
{
path
:
''
,
path
:
''
,
canActivate
:
[
Auth
Guard
],
canActivate
:
[
Login
Guard
],
loadChildren
:
()
=>
import
(
'../../+login/login.routes'
),
loadChildren
:
()
=>
import
(
'../../+login/login.routes'
),
}
}
]
]
},
},
{
path
:
'jobs'
,
component
:
LayoutComponent
,
children
:
[
{
path
:
''
,
canActivate
:
[],
loadChildren
:
()
=>
import
(
'../../+job/job.routes'
)
}
]
}
];
];
src/app/+shell/ui/components/header/feature/header.component.html
View file @
ecf1ce08
<nz-header
class=
"tw-bg-white"
>
<nz-header
class=
"tw-bg-white
tw-flex tw-flex-wrap
"
>
<a
routerLink=
"/home"
routerLinkActive=
"active"
>
<a
routerLink=
"/home"
routerLinkActive=
"active"
>
<img
src=
"https://yt3.googleusercontent.com/y4J_Fs5ksRlxx6_LzT1VKxVqH_T8Vyn_RN_YYgLJhuMzBS5qxTgm7NlEcMkQd3hgCpfWtYcEUg=s900-c-k-c0x00ffffff-no-rj"
class=
"tw-border tw-rounded-full tw-w-10 tw-h-10 tw-mt-3 tw-float-left"
>
<img
src=
"https://yt3.googleusercontent.com/y4J_Fs5ksRlxx6_LzT1VKxVqH_T8Vyn_RN_YYgLJhuMzBS5qxTgm7NlEcMkQd3hgCpfWtYcEUg=s900-c-k-c0x00ffffff-no-rj"
class=
"tw-border tw-rounded-full tw-w-10 tw-h-10 tw-mt-3 tw-float-left"
>
</a>
</a>
<ul
nz-menu
nzMode=
"horizontal"
>
<ul
nz-menu
nzMode=
"horizontal"
>
<li
nz-menu-item
>
Home
</li>
<li
nz-menu-item
>
Home
</li>
<li
nz-menu-item
routerLink=
"/login"
routerLinkActive=
"active"
>
Login
</li>
<ng-container
*
ngIf=
"!isLoggedIn()"
>
<li
nz-menu-item
>
Register
</li>
<li
nz-menu-item
routerLink=
"/login"
routerLinkActive=
"active"
>
Login
</li>
<li
nz-menu-item
>
Register
</li>
</ng-container>
<ng-container
*
ngIf=
"isLoggedIn()"
>
<li
*
ngIf=
"isAdmin()"
nz-menu-item
routerLink=
"/admin"
routerLinkActive=
"active"
>
Admin
</li>
</ng-container>
</ul>
</ul>
<ng-container
*
ngIf=
"isLoggedIn()"
>
<div
class=
"tw-flex-grow"
>
<div
class=
"tw-grid tw-justify-items-end"
>
<img
nz-dropdown
nzTrigger=
"click"
[
nzDropdownMenu
]="
menu
"
src=
"https://avatars.githubusercontent.com/u/92918544?v=4"
class=
"tw-border tw-rounded-full tw-w-10 tw-h-10 tw-mt-3 tw-float-left"
>
<nz-dropdown-menu
#
menu=
"nzDropdownMenu"
>
<ul
nz-menu
>
<li
nz-menu-item
>
Profile
</li>
<li
(
click
)="
logOut
()"
nz-menu-item
routerLink=
"/logout"
routerLinkActive=
"active"
>
Logout
</li>
</ul>
</nz-dropdown-menu>
</div>
</div>
</ng-container>
</nz-header>
</nz-header>
src/app/+shell/ui/components/header/feature/header.component.ts
View file @
ecf1ce08
...
@@ -3,15 +3,31 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
...
@@ -3,15 +3,31 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import
{
NzLayoutModule
}
from
'ng-zorro-antd/layout'
;
import
{
NzLayoutModule
}
from
'ng-zorro-antd/layout'
;
import
{
NzCardModule
}
from
'ng-zorro-antd/card'
;
import
{
NzCardModule
}
from
'ng-zorro-antd/card'
;
import
{
NzMenuModule
}
from
'ng-zorro-antd/menu'
;
import
{
NzMenuModule
}
from
'ng-zorro-antd/menu'
;
import
{
NzDropDownModule
}
from
'ng-zorro-antd/dropdown'
import
{
RouterLink
,
RouterLinkActive
}
from
'@angular/router'
;
import
{
RouterLink
,
RouterLinkActive
}
from
'@angular/router'
;
import
{
AuthService
}
from
'../../../../../+login/data-access/service/auth.service'
;
@
Component
({
@
Component
({
selector
:
'meu-header'
,
selector
:
'meu-header'
,
standalone
:
true
,
standalone
:
true
,
imports
:
[
CommonModule
,
NzLayoutModule
,
NzCardModule
,
NzMenuModule
,
RouterLink
,
RouterLinkActive
],
imports
:
[
CommonModule
,
NzLayoutModule
,
NzCardModule
,
NzMenuModule
,
RouterLink
,
RouterLinkActive
,
NzDropDownModule
],
templateUrl
:
'./header.component.html'
,
templateUrl
:
'./header.component.html'
,
changeDetection
:
ChangeDetectionStrategy
.
OnPush
,
changeDetection
:
ChangeDetectionStrategy
.
OnPush
,
})
})
export
class
HeaderComponent
implements
OnInit
{
export
class
HeaderComponent
implements
OnInit
{
constructor
(
private
_service
:
AuthService
)
{}
ngOnInit
():
void
{}
ngOnInit
():
void
{}
isLoggedIn
():
boolean
{
return
this
.
_service
.
isLoggedIn
();
}
isAdmin
():
boolean
{
return
this
.
_service
.
isAdmin
();
}
logOut
()
{
localStorage
.
removeItem
(
"token"
);
localStorage
.
removeItem
(
"role"
);
}
}
}
src/app/shared/data-access/interface/response.type.ts
View file @
ecf1ce08
...
@@ -7,6 +7,7 @@ export interface ResponseResult<T> {
...
@@ -7,6 +7,7 @@ export interface ResponseResult<T> {
path
:
string
;
path
:
string
;
timestamp
:
number
;
timestamp
:
number
;
}
}
export
interface
Rows
<
T
>
{
export
interface
Rows
<
T
>
{
rows
:
T
[];
rows
:
T
[];
page
:
number
;
page
:
number
;
...
...
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