This commit is contained in:
Minhnn 2026-03-10 15:20:38 +07:00
commit 141e914d03
657 changed files with 35047 additions and 0 deletions

1
Phaha_Cms Submodule

@ -0,0 +1 @@
Subproject commit 995a7e075edb35f551c60218897b67f8f4f9d36e

View File

@ -0,0 +1,16 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# For the full list of supported browsers by the Angular framework, please see:
# https://angular.io/guide/browser-support
# You can see what browsers were selected by your queries by running:
# npx browserslist
last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major versions
last 2 iOS major versions
Firefox ESR

View File

@ -0,0 +1,16 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
[*.md]
max_line_length = off
trim_trailing_whitespace = false

48
Phaha_Package/.gitignore vendored Normal file
View File

@ -0,0 +1,48 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# Compiled output
/dist
/tmp
/out-tsc
/bazel-out
# Node
/node_modules
npm-debug.log
yarn-error.log
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
# System files
.DS_Store
Thumbs.db
package-lock.json
yarn.lock
src/environments/environment.ts
yarn.lock
src/environments/environment.ts
.vs/

4
Phaha_Package/.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,4 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
"recommendations": ["angular.ng-template"]
}

20
Phaha_Package/.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,20 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "ng serve",
"type": "pwa-chrome",
"request": "launch",
"preLaunchTask": "npm: start",
"url": "http://localhost:4200/"
},
{
"name": "ng test",
"type": "chrome",
"request": "launch",
"preLaunchTask": "npm: test",
"url": "http://localhost:9876/debug.html"
}
]
}

4
Phaha_Package/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,4 @@
{
"search.useGlobalIgnoreFiles": true,
"search.useParentIgnoreFiles": true
}

42
Phaha_Package/.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,42 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "start",
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": {
"regexp": "(.*?)"
},
"endsPattern": {
"regexp": "bundle generation complete"
}
}
}
},
{
"type": "npm",
"script": "test",
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": {
"regexp": "(.*?)"
},
"endsPattern": {
"regexp": "bundle generation complete"
}
}
}
}
]
}

92
Phaha_Package/README.md Normal file
View File

@ -0,0 +1,92 @@
# VNPost - POSTENP
## Getting started
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
## Add your files
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
```
cd existing_repo
git remote add origin https://gitlab.com/service30/vnpost-postenp.git
git branch -M main
git push -uf origin main
```
## Integrate with your tools
- [ ] [Set up project integrations](https://gitlab.com/service30/vnpost-postenp/-/settings/integrations)
## Collaborate with your team
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
## Test and Deploy
Use the built-in continuous integration in GitLab.
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
***
# Editing this README
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template.
## Suggestions for a good README
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
## Name
Choose a self-explaining name for your project.
## Description
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
## Badges
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
## Visuals
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
## Installation
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
## Usage
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
## Support
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
## Roadmap
If you have ideas for releases in the future, it is a good idea to list them in the README.
## Contributing
State if you are open to contributions and what your requirements are for accepting them.
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
## Authors and acknowledgment
Show your appreciation to those who have contributed to the project.
## License
For open source projects, say how it is licensed.
## Project status
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.

146
Phaha_Package/angular.json Normal file
View File

@ -0,0 +1,146 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"customer": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/customer",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
"src/assets",
"src/firebase-messaging-sw.js",
"src/manifest.json",
{
"glob": "**/*",
"input": "node_modules/ngx-extended-pdf-viewer/assets/",
"output": "/assets/"
}
],
"styles": [
"src/styles.scss",
"node_modules/bootstrap/dist/css/bootstrap.css",
"node_modules/ngx-toastr/toastr.css",
"node_modules/flatpickr/dist/flatpickr.min.css",
"./node_modules/quill/dist/quill.snow.css",
"./node_modules/quill/dist/quill.core.css"
],
"scripts": [
"node_modules/bootstrap/dist/js/bootstrap.js",
"./node_modules/quill/dist/quill.min.js"
]
},
"configurations": {
"production": {
"optimization": {
"scripts": true,
"styles": {
"minify": true,
"inlineCritical": false
},
"fonts": true
},
"sourceMap": false,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "4mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "150kb",
"maximumError": "150kb"
}
],
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"outputHashing": "all"
},
"development": {
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "customer:build:production"
},
"development": {
"browserTarget": "customer:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "customer:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
"src/assets",
"src/firebase-messaging-sw.js",
"src/manifest.json",
{
"glob": "**/*",
"input": "node_modules/ngx-extended-pdf-viewer/assets/",
"output": "/assets/"
}
],
"styles": [
"src/styles.scss"
],
"scripts": []
}
}
}
}
},
"cli": {
"analytics": "a9c3b5ff-118e-4409-a8fb-d930b33735ca"
}
}

View File

@ -0,0 +1,44 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
jasmine: {
// you can add configuration options for Jasmine here
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
// for example, you can disable the random execution with `random: false`
// or set a specific seed with `seed: 4321`
},
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
jasmineHtmlReporter: {
suppressAll: true // removes the duplicated traces
},
coverageReporter: {
dir: require('path').join(__dirname, './coverage/customer'),
subdir: '.',
reporters: [
{ type: 'html' },
{ type: 'text-summary' }
]
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};

View File

@ -0,0 +1,82 @@
{
"name": "customer",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"watch": "ng build --watch --configuration development",
"test": "ng test",
"build": "node --max_old_space_size=6144 ./node_modules/@angular/cli/bin/ng build --configuration production --build-optimizer",
"build-stats": "node --max_old_space_size=6144 ./node_modules/@angular/cli/bin/ng build --stats-json",
"build:prod": "node --max_old_space_size=6144 ./node_modules/@angular/cli/bin/ng build --configuration production",
"build:prod-stats": "node --max_old_space_size=6144 ./node_modules/@angular/cli/bin/ng build --configuration production --stats-json",
"bundle-analyzer": "webpack-bundle-analyzer dist/sml-vcu/stats.json"
},
"private": true,
"dependencies": {
"@angular/animations": "^14.0.5",
"@angular/cdk": "^14.2.1",
"@angular/common": "^14.0.0",
"@angular/compiler": "^14.0.0",
"@angular/core": "^14.0.0",
"@angular/fire": "^7.4.1",
"@angular/forms": "^14.0.0",
"@angular/localize": "^14.0.3",
"@angular/platform-browser": "^14.0.0",
"@angular/platform-browser-dynamic": "^14.0.0",
"@angular/router": "^14.0.0",
"@fortawesome/angular-fontawesome": "^0.11.1",
"@fortawesome/fontawesome-svg-core": "^6.2.0",
"@fortawesome/free-brands-svg-icons": "^6.2.0",
"@fortawesome/free-solid-svg-icons": "^6.2.0",
"@icon/unicons": "^3.0.6-alpha.0",
"@ng-bootstrap/ng-bootstrap": "^12.1.2",
"@ng-select/ng-select": "^9.0.2",
"@ngx-translate/core": "^14.0.0",
"@ngx-translate/http-loader": "^7.0.0",
"@popperjs/core": "^2.11.5",
"@types/file-saver": "^2.0.5",
"@types/popper.js": "^1.11.0",
"@types/quill": "1.3.10",
"angular-snapscroll": "^1.3.1",
"bootstrap": "^5.1.3",
"bootstrap-icons": "^1.8.3",
"file-saver": "^2.0.5",
"firebase": "9.8.4",
"flatpickr": "^4.6.13",
"hammerjs": "^2.0.8",
"js-sha256": "^0.9.0",
"moment": "^2.29.4",
"ng-block-ui": "^3.0.2",
"ng2-flatpickr": "^9.0.0",
"ng2-pdf-viewer": "^9.1.2",
"ngx-device-detector": "^4.0.1",
"ngx-extended-pdf-viewer": "^14.0.6",
"ngx-loading": "^13.0.1",
"ngx-plyr": "4.0.0",
"ngx-quill": "^18.0.0",
"ngx-sharebuttons": "^11.0.0",
"ngx-toastr": "^15.0.0",
"plyr": "3.6.4",
"quill": "^1.3.7",
"rxjs": "~7.5.0",
"sweetalert2": "^11.4.24",
"swiper": "^8.2.6",
"tslib": "^2.3.0",
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "^14.0.2",
"@angular/cli": "~14.0.2",
"@angular/compiler-cli": "^14.0.0",
"@types/hammerjs": "^2.0.41",
"@types/jasmine": "~4.0.0",
"jasmine-core": "~4.1.0",
"karma": "~6.3.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.0.0",
"karma-jasmine-html-reporter": "~1.7.0",
"typescript": "~4.7.2"
}
}

View File

@ -0,0 +1,16 @@
export class PageModel {
page: number = 1;
pageSize: number = 10;
keyWord: string = "";
sortColumn?: string;
sortDirection?: SortDirection;
totalRecords: number = 0;
firstItemOnPage: number = 0;
result: any = [];
editorialOfficeId: any = null;
pageNumber: number = 1;
totalItemCount: number = 0;
pageCount: number = 0;
}
export type SortDirection = 'asc' | 'desc' | '';

View File

@ -0,0 +1,116 @@
import { RouterModule, Routes } from '@angular/router';
import { PackageDetailComponent } from './package/package-detail/package-detail.component';
import { PackageHistoryComponent } from './package-history/package-history.component';
import { ViewBookComponent } from "./view-book/view-book.component";
import { LoginComponent } from './login/login.component';
import { PackagePaymentComponent } from './package/package-payment/package-payment.component';
import { PaymentSuccessComponent } from './package/package-payment/payment-success/payment-success.component';
import { ForgetPasswordComponent } from './forget-password/forget-password.component';
import { VerifyEmailComponent } from './register-user/verify-email/verify-email.component';
import { ListPackageComponent } from "./my-package/list-package/list-package.component";
import { AddUserComponent } from "./my-package/add-user/add-user.component";
import { PackageTrialComponent } from './package/package-trial/package-trial.component';
import { EditorialOfficeListComponent } from './editorial-office/editorial-office-list/editorial-office-list.component';
import { AuthorListComponent } from './home/author-list/author-list.component';
import { ErrorNotFoundComponent } from './layout/error-not-found/error-not-found.component';
import { PageNewsPrfeditorialPrfauthorComponent } from './page-news-prfeditorial-prfauthor/page-news-prfeditorial-prfauthor.component';
import { PagePrfEditorialOfficeComponent } from './page-news-prfeditorial-prfauthor/prf-editorial-office/page-prf-editorial-office/page-prf-editorial-office.component';
import { PagePrfAuthorComponent } from './page-news-prfeditorial-prfauthor/prf-author/page-prf-author/page-prf-author.component';
import { PageDetailPrfAuthorComponent } from './page-news-prfeditorial-prfauthor/prf-author/page-prf-author/page-detail-prf-author/page-detail-prf-author.component';
import { PageDetailPrfEditorialOfficeComponent } from './page-news-prfeditorial-prfauthor/prf-editorial-office/page-prf-editorial-office/page-detail-prf-editorial-office/page-detail-prf-editorial-office.component';
import { PageNewsCategoryComponent } from './page-news-prfeditorial-prfauthor/page-news/page-news-category/page-news-category.component';
import { PageNewsDetailComponent } from './page-news-prfeditorial-prfauthor/page-news/page-news-category/page-news-detail/page-news-detail.component';
import { BookDetailsComponent } from './book/book-details/book-details.component';
import { BookListByCategoryComponent } from './book/book-list-by-category/book-list-by-category.component';
import { PackageCodeComponent } from './my-package/package-code/package-code.component';
import { HonorPressComponent } from './honor-press/honor-press.component';
import { DetailHonorPressComponent } from './honor-press/detail-honor-press/detail-honor-press.component';
import { ResetPasswordComponent } from "./reset-password/reset-password.component";
import { ViewTestComponent } from './view-book/view-test/view-test.component';
import { AskonomyDetailComponent } from './package/package-askonomy/askonomy-detail/askonomy-detail.component';
import { AskonomyViewComponent } from './package/package-askonomy/askonomy-view/askonomy-view.component';
import { HomeV2Component } from './template-2/home-2/home-v2.component';
import { PackageNewsComponent } from './template-2/package-news/package-news.component';
import { PackageNewsDetailComponent } from './template-2/package-news/package-news-detail/package-news-detail.component';
import { VneconomyComponent } from './editorial-office/vneconomy/vneconomy-home/vneconomy.component';
import { VneconomyProfileComponent } from './editorial-office/vneconomy/vneconomy-profile/vneconomy-profile.component';
import { VneconomyDetailComponent } from './editorial-office/vneconomy/vneconomy-detail/vneconomy-detail.component';
import { VneconomyPremiumComponent } from './editorial-office/vneconomy/vneconomy-premium/vneconomy-premium.component';
import { VneconomyAskonomyComponent } from './editorial-office/vneconomy/vneconomy-askonomy/vneconomy-askonomy.component';
import { VneconomyPackagesComponent } from './editorial-office/vneconomy/vneconomy-packages/vneconomy-packages.component';
import { PackageUpgradeComponent } from './package/package-upgrade/package-upgrade.component';
import { VneconomyNewpaperComponent } from './editorial-office/vneconomy/vneconomy-newpaper/vneconomy-newpaper.component';
import { EditorialDetailComponent } from './modules/editorial/components/editorial-detail/editorial-detail.component';
import { NewspaperDetailComponent } from './modules/newspaper/components/newspaper-detail/newspaper-detail.component';
import { PackageSearchComponent } from './modules/packages/components/package-search/package-search.component';
import { AccountProfileComponent } from './modules/account/components/account-profile/account-profile.component';
import { PaymentInfoComponent } from './package/package-payment/payment-info/payment-info.component';
const routes: Routes = [
{ path: '', component: HomeV2Component },
{ path: 'chi-tiet-goi/:id', component: PackageDetailComponent },
// { path: 'chi-tiet-askonomy/:id', component: AskonomyDetailComponent },
{ path: 'tap-chi-kinh-te-viet-nam', component: VneconomyComponent, data: { showAskonomy: false } },
{ path: 'tap-chi-kinh-te-viet-nam/so-bao', component: VneconomyNewpaperComponent, data: { showAskonomy: false } },
{ path: 'chi-tiet-toa-soan/tap-chi-kinh-te-viet-nam', component: VneconomyComponent, data: { showAskonomy: false } },
{ path: 'tap-chi-kinh-te-viet-nam/profile', component: VneconomyProfileComponent, data: { showAskonomy: false } },
{ path: 'tap-chi-kinh-te-viet-nam/premium', component: VneconomyPremiumComponent, data: { showAskonomy: false } },
{ path: 'tap-chi-kinh-te-viet-nam/askonomy', component: VneconomyAskonomyComponent, data: { showAskonomy: false } },
{ path: 'tap-chi-kinh-te-viet-nam/packages', component: VneconomyPackagesComponent, data: { showAskonomy: false } },
{ path: 'tap-chi-kinh-te-viet-nam/detail/:id', component: VneconomyDetailComponent, data: { showAskonomy: false } },
{ path: 'askonomy', component: AskonomyViewComponent, data: { showHeader: false, showFooter: false, showAskonomy: false } },
{ path: 'user/package-history', component: PackageHistoryComponent },
{ path: 'auth/login', component: LoginComponent },
{ path: 'auth/login?return=', component: LoginComponent },
{ path: 'view/:id', component: ViewBookComponent, data: { showHeader: false, showFooter: false } },
{ path: 'view-trial/:id', component: ViewBookComponent, data: { showHeader: false, showFooter: false } },
{ path: 'view-test/:token', component: ViewTestComponent, data: { showHeader: false, showFooter: false } },
{ path: 'package-upgrade/:id/:packuserid', component: PackageUpgradeComponent },
{ path: 'package-pay/:id', component: PackagePaymentComponent },
{ path: 'package-pay/:id/:type', component: PackagePaymentComponent },
{ path: 'package-pay/:id/:type/:packuserid', component: PackagePaymentComponent },
{ path: 'package-pay/success/:id', component: PaymentSuccessComponent },
{ path: 'list-news/:id', component: ListPackageComponent },
{ path: 'search', component: PackageSearchComponent },
{ path: 'search/:key', component: PackageSearchComponent },
{ path: 'profile', component: AccountProfileComponent },
{ path: 'forget-password', component: ForgetPasswordComponent },
{ path: 'add-user/:id', component: AddUserComponent },
{ path: 'web/auth/verify-email', component: VerifyEmailComponent },
{ path: 'package-trial/:id', component: PackageTrialComponent },
{ path: 'editorial-office', component: EditorialOfficeListComponent },
{ path: 'tac-gia', component: AuthorListComponent },
{ path: 'news-profile', component: PageNewsPrfeditorialPrfauthorComponent },
{ path: 'news-by-category/:id', component: PageNewsCategoryComponent },
{ path: 'news/detail/:id', component: PageNewsDetailComponent },
{ path: 'profile-editorial-office', component: PagePrfEditorialOfficeComponent },
{ path: 'profile-editorial-office/detail/:id', component: PageDetailPrfEditorialOfficeComponent },
{ path: 'profile-author', component: PagePrfAuthorComponent },
{ path: 'profile-author/detail/:id', component: PageDetailPrfAuthorComponent },
{ path: 'chi-tiet-sach', component: BookDetailsComponent },
{ path: 'the-loai/chu-de-sach', component: BookListByCategoryComponent },
{ path: 'package-code', component: PackageCodeComponent },
{ path: 'honor-press', component: HonorPressComponent, data: { showHeader: false, showFooter: false } },
{ path: 'honor-press/detail/:id', component: DetailHonorPressComponent, data: { showHeader: false, showFooter: false } },
{ path: 'reset-password/:token', component: ResetPasswordComponent },
{ path: 'payment-info/:id', component: PaymentInfoComponent },
{ path: 'an-pham', component: PackageNewsComponent },
{ path: 'an-pham/:cateId', component: PackageNewsComponent },
{ path: 'chi-tiet/:id', component: NewspaperDetailComponent },
{ path: ':slug', component: EditorialDetailComponent },
{ path: '**', component: ErrorNotFoundComponent, data: { showHeader: false, showFooter: false } }
]
export const routing = RouterModule.forRoot(routes, {
onSameUrlNavigation: 'reload',
anchorScrolling: 'enabled',
scrollPositionRestoration: 'enabled',
});

View File

@ -0,0 +1,20 @@
<app-header *ngIf="showHeader"></app-header>
<div [ngClass]="{'main-mobile': isMobile && showHeader}">
<router-outlet></router-outlet>
</div>
<app-footer *ngIf="showFooter"></app-footer>
<div *ngIf="showAskonomy && currentUser && month" class="box-chat-askonomy">
<button class="askonomy_close" *ngIf="isShowAskonomy" (click)="isShowAskonomy = !isShowAskonomy;">
<i class="uil uil-times-circle"></i>
</button>
<div *ngIf="isShowAskonomy" class="box-chat-askonomy-support">
<span>Email: support.askonomy@vneconomy.vn</span>
<a target="_blank" href="https://docs.google.com/forms/d/e/1FAIpQLSeZYkMiTgjlycw89Jci5EjShrBb9z6SEKbbZ9KnDbCt-bdJ9Q/viewform">Phản hồi</a>
</div>
<div class="askonomy_iframe" *ngIf="isShowAskonomy" [innerHTML]="pathIframe">
</div>
<div class="askonomy_icon" *ngIf="!isShowAskonomy" (click)="isShowAskonomy = !isShowAskonomy">
<img src="/assets/images/icons/icon_askonomy.png">
</div>
</div>

View File

@ -0,0 +1,79 @@
.mt-140 {
margin-top: 140px;
}
.mt-60 {
margin-top: 60px;
}
.box-chat-askonomy {
position: fixed;
bottom: 10px;
left: 10px;
z-index: 100;
.askonomy_iframe {
background: #fff;
width: 360px;
iframe {
height: 500px;
width: 100%;
}
}
.askonomy_icon {
width: 60px;
height: 60px;
border-radius: 50%;
overflow: hidden;
box-shadow: 0 0 5px #d12129;
img {
width: 100%;
padding: 10px;
background: #d12129;
}
}
.askonomy_close {
position: absolute;
top: -10px;
right: -10px;
width: 30px;
height: 30px;
padding: 0;
border: none;
border-radius: 50%;
background: #fff;
i {
font-size: 20px;
}
}
.box-chat-askonomy-support {
padding: 5px 10px;
background: #fff;
font-weight: 600;
position: absolute;
bottom: 0;
left: 0;
width: 100%;
display: flex;
justify-content: space-evenly;
align-items: center;
a {
border: 1px solid #e09500;
padding: 2px 5px;
border-radius: 5px;
text-decoration: none;
color: #e09500;
box-shadow: 0 0 5px #e09500;
}
}
}

View File

@ -0,0 +1,104 @@
import { Component, OnInit } from '@angular/core';
import { DomSanitizer, Meta, MetaDefinition, SafeHtml, Title } from '@angular/platform-browser';
import { ActivatedRoute, Data, NavigationEnd, Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { DeviceDetectorService } from 'ngx-device-detector';
import { filter, map, mergeMap, tap } from 'rxjs';
import { AuthenticationService } from './auth/service/authentication.service';
import { environment } from 'src/environments/environment';
import { ToastrService } from 'ngx-toastr';
import { PackageService } from './package/package.service';
import Swal from 'sweetalert2';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
showHeader = false;
showFooter = false;
isShowAskonomy = false;
showAskonomy = false;
isMobile: boolean = false;
currentUser: any;
urlAskonomy = environment.urlAskonomy;
private i18nmyPackage: any;
month = false;
pathIframe: SafeHtml = '';
constructor(
private readonly _router: Router,
private readonly _activatedRoute: ActivatedRoute,
public readonly _translate: TranslateService,
private readonly _authenticationService: AuthenticationService,
private readonly _toastrService: ToastrService,
private readonly _sanitizer: DomSanitizer,
private readonly _packageService: PackageService
) {
if (window.location.pathname.startsWith('/en')) {
localStorage.setItem("lang", "en");
window.location.href = window.location.href.replace('/en', '');
} else if (!window.location.pathname.startsWith('/view') && window.location.pathname.startsWith('/vi')) {
localStorage.setItem("lang", "vi");
window.location.href = window.location.href.replace('/vi', '');
}
if (!localStorage.getItem("lang"))
localStorage.setItem("lang", "vi");
this.isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
this._translate.addLangs(['en', 'vi']);
const langcurrent = localStorage.getItem('lang');
this._translate.setDefaultLang(langcurrent ?? 'vi');
this._translate.use(langcurrent ?? 'vi');
this.currentUser = this._authenticationService.currentUserValue;
if (this.currentUser)
this.getMonth();
this.onGetI18n();
this.pathIframe = this._sanitizer.bypassSecurityTrustHtml(`<iframe style="width: 100%; height: 500px;" src="${this.urlAskonomy}" title="Trợ lý Thông tin Kinh tế" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>`);
}
getMonth() {
this._packageService.getMonth()
.subscribe(res => {
this.currentUser.month = res;
if (this.currentUser.month > 5) this.month = true;
localStorage.setItem('currentCustomer', JSON.stringify(this.currentUser));
})
}
showNotiVnaskonomy() {
Swal.fire({
title: this.i18nmyPackage.sweetAlert.askonomy.title,
html: this.i18nmyPackage.sweetAlert.askonomy.html,
icon: 'warning',
showCancelButton: true,
cancelButtonColor: '#fcaf17',
showConfirmButton: false,
cancelButtonText: this.i18nmyPackage.sweetAlert.askonomy.close,
customClass: {
confirmButton: 'btn btn-primary',
cancelButton: 'btn btn-danger ml-1'
}
});
}
onGetI18n() {
this._translate.get('myPackage').subscribe(data => {
this.i18nmyPackage = data;
});
}
ngOnInit() {
this._router.events.subscribe(event => {
if (event instanceof NavigationEnd) {
this.showHeader = this._activatedRoute.firstChild?.snapshot.data['showHeader'] !== false;
this.showFooter = this._activatedRoute.firstChild?.snapshot.data['showFooter'] !== false;
this.showAskonomy = this._activatedRoute.firstChild?.snapshot.data['showAskonomy'] !== false;
}
});
}
}

View File

@ -0,0 +1,144 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import '@angular/localize/init'
import { PackageModule } from "./package/package.module";
import { CommonModule } from '@angular/common';
import { routing } from './app-routing';
import { HomeModule } from './home/home.module';
import { HeaderModule } from './layout/header/header.module';
import { FooterModule } from './layout/footer/footer.module';
import { EditorialOfficeModule } from './editorial-office/editorial-office.module';
import { PackageHistoryComponent } from './package-history/package-history.component'
import { JwtInterceptor } from "./auth/helpers/jwt.interceptor";
import { LoginModule } from './login/login.module';
import { BlockUIModule } from 'ng-block-ui';
import { ToastrModule } from 'ngx-toastr';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ErrorInterceptor } from './auth/helpers/error.interceptor';
import { ViewBookModule } from "./view-book/view-book.module";
import { Ng2FlatpickrModule } from 'ng2-flatpickr';
import { MyPackageModule } from "./my-package/my-package.module";
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { NgSelectModule } from '@ng-select/ng-select';
import { VerifyEmailComponent } from './register-user/verify-email/verify-email.component';
import { NgxTranslateModule } from './translate/translate.module';
import { HTTP_INTERCEPTORS, HttpClientModule, HttpClient } from "@angular/common/http";
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { ErrorNotFoundComponent } from './layout/error-not-found/error-not-found.component';
import { HomeBookIntroComponent } from './home-book-intro/home-book-intro.component';
import { BookModule } from './book/book.module';
import { PageNewsPrfeditorialPrfauthorModule } from './page-news-prfeditorial-prfauthor/page-news-prfeditorial-prfauthor.module';
import { BookstoreComponent } from './bookstore/bookstore.component';
import { HeaderBookstoreComponent } from './bookstore/layout-bookstore/header-bookstore/header-bookstore.component';
import { FooterBookstoreComponent } from './bookstore/layout-bookstore/footer-bookstore/footer-bookstore.component';
import { BookstoreModule } from './bookstore/bookstore.module';
import { QuillModule } from 'ngx-quill';
import { ShareButtonsModule } from 'ngx-sharebuttons/buttons';
import { ShareIconsModule } from 'ngx-sharebuttons/icons';
import { HonorPressModule } from './honor-press/honor-press.module';
import { environment } from 'src/environments/environment';
import { initializeApp } from "firebase/app";
import { AngularFireModule } from "@angular/fire/compat";
import { AngularFireMessagingModule } from '@angular/fire/compat/messaging';
import { ResetPasswordComponent } from './reset-password/reset-password.component';
import { RegisterUserModule } from './register-user/register-user.module';
import { HomeV2Module } from './template-2/home-2/home-v2.module';
import { PackageNewsModule } from './template-2/package-news/package-news.module';
import { PackageNewsDetailModule } from './template-2/package-news/package-news-detail/package-news-detail.module';
import { PackageNewsOfOfficesModule } from './template-2/package-news/package-news-of-offices/package-news-of-offices.module';
import { VneconomyModule } from './editorial-office/vneconomy/vneconomy-home/vneconomy.module';
import { VneconomyProfileModule } from './editorial-office/vneconomy/vneconomy-profile/vneconomy-profile.module';
import { VneconomyDetailModule } from './editorial-office/vneconomy/vneconomy-detail/vneconomy-detail.module';
import { VneconomyPremiumModule } from './editorial-office/vneconomy/vneconomy-premium/vneconomy-premium.module';
import { VneconomyAskonomyModule } from './editorial-office/vneconomy/vneconomy-askonomy/vneconomy-askonomy.module';
import { VneconomyPackagesModule } from './editorial-office/vneconomy/vneconomy-packages/vneconomy-packages.module';
import { NewspaperModule } from './modules/newspaper/newspaper.module';
import { EditorialModule } from './modules/editorial/editorial.module';
import { AccountModule } from './modules/account/account.module';
export function httpTranslateLoader(http: HttpClient) {
return new TranslateHttpLoader(http, '../assets/i18n/', '.json');
}
// initializeApp(environment.firebase);
@NgModule({
declarations: [
AppComponent,
PackageHistoryComponent,
VerifyEmailComponent,
ErrorNotFoundComponent,
HomeBookIntroComponent,
BookstoreComponent,
HeaderBookstoreComponent,
FooterBookstoreComponent,
ResetPasswordComponent,
],
exports: [],
imports: [
routing,
BrowserModule,
NgbModule,
PackageModule,
CommonModule,
HonorPressModule,
HomeModule,
HeaderModule,
FooterModule,
EditorialOfficeModule,
QuillModule.forRoot(),
BlockUIModule.forRoot(),
ToastrModule.forRoot({ timeOut: 2000, maxOpened: 1 }),
BrowserAnimationsModule,
LoginModule,
ViewBookModule,
Ng2FlatpickrModule,
FormsModule,
ReactiveFormsModule,
MyPackageModule,
RegisterUserModule,
NgSelectModule,
NgxTranslateModule,
PageNewsPrfeditorialPrfauthorModule,
HttpClientModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: httpTranslateLoader,
deps: [HttpClient]
},
}),
BookModule,
BookstoreModule,
ShareButtonsModule.withConfig({
debug: true,
}),
ShareIconsModule,
HomeV2Module,
PackageNewsModule,
PackageNewsDetailModule,
PackageNewsOfOfficesModule,
VneconomyModule,
VneconomyProfileModule,
VneconomyDetailModule,
VneconomyPremiumModule,
VneconomyAskonomyModule,
VneconomyPackagesModule,
NewspaperModule,
EditorialModule,
AccountModule
// AngularFireModule.initializeApp(environment.firebase),
// AngularFireMessagingModule,
],
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true },
],
bootstrap: [AppComponent]
})
export class AppModule {
}

View File

@ -0,0 +1,39 @@
import {Injectable} from '@angular/core';
import {Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot} from '@angular/router';
import {AuthenticationService} from "../service/authentication.service";
@Injectable({providedIn: 'root'})
export class AuthGuard implements CanActivate {
/**
*
* @param _router Router
* @param _authenticationService AuthenticationService
*/
constructor(private _router: Router, private _authenticationService: AuthenticationService) {
}
// canActivate
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const currentUser = this._authenticationService.currentUserValue;
if (currentUser) {
// check if route is restricted by role
if (route.data['role'] && route.data['role'].indexOf(currentUser.role) === -1) {
// role not authorised so redirect to not-authorized page
this._router.navigate(['/login']);
return false;
}
// authorised so return true
return true;
}
// not logged in so redirect to login page with the return url
this._router.navigate(['/login'], {queryParams: {returnUrl: state.url}});
return false;
}
getArraysIntersection(a1: any[], a2: string | any[]) {
return a1.filter(function (n) {
return a2.indexOf(n) !== -1;
});
}
}

View File

@ -0,0 +1,113 @@
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { from, lastValueFrom, Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { AuthenticationService } from "../service/authentication.service";
import { BlockUI, NgBlockUI } from 'ng-block-ui';
import { ActiveToast, ToastrService } from 'ngx-toastr';
import { LoginService } from 'src/app/login/login.service';
import { TranslateService } from '@ngx-translate/core';
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
@BlockUI() blockUI: NgBlockUI | undefined;
toastrMessage: ActiveToast<any> | undefined;
private toastIsVisible: boolean | undefined;
private i18n: any;
private isLogout: boolean = false;
/**
* Constructor
* @param _router Router
* @param _authenticationService AuthenticationService
* @param _toastr ToastrService
*/
constructor(
private _router: Router,
private _toastr: ToastrService,
private _translate: TranslateService
) {
}
ngOnInit(): void {
}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return from(this.handle(request, next));
}
async handle(req: HttpRequest<any>, next: HttpHandler) {
this.onGetI18n();
return await lastValueFrom(next.handle(req).pipe(
catchError(err => {
let errorMessage = '';
switch (err.status) {
case 0:
errorMessage = this.i18n.expiredSignin;
localStorage.removeItem("currentCustomer");
break;
case 401:
errorMessage = this.i18n.expiredSignin;
localStorage.removeItem("currentCustomer");
this._router.navigate(['/']).then(() => { window.location.reload(); });
break;
case 404:
errorMessage = this.i18n.link;
this.blockUI?.stop();
break;
case 500:
if (err && err.error && err.error.message == "Tài khoản không hợp lệ") {
errorMessage = err.error.message;
localStorage.removeItem("currentCustomer");
window.location.reload();
} else if (err && err.error && err.error.message)
errorMessage = err.error.message;
else
errorMessage = "Lỗi máy chủ";
break;
case 405:
errorMessage = this.i18n.access;
this.blockUI?.stop();
break;
case 501:
errorMessage = this.i18n.execution;
this.blockUI?.stop();
break;
case 504:
errorMessage = this.i18n.server;
this.blockUI?.stop();
break;
case 408:
errorMessage = this.i18n.browser;
this.blockUI?.stop();
break;
case 400:
errorMessage = `${err.error.message}`;
this.blockUI?.stop();
break;
}
setTimeout(() => {
if (errorMessage && !this.toastIsVisible) {
this.toastrMessage = this._toastr
.error(errorMessage, this.i18n.noti, {
timeOut: 2000,
});
this.toastrMessage.onShown.subscribe(() => this.toastIsVisible = true);
this.toastrMessage.onHidden.subscribe(() => this.toastIsVisible = false);
}
}, 200);
// throwError
const error = errorMessage || err.message || err.error && err.error.message || err.statusText;
this.blockUI?.stop();
return throwError(error);
})
));
}
onGetI18n() {
this._translate.get('error-interceptor').subscribe(data => {
this.i18n = data;
})
}
}

View File

@ -0,0 +1,60 @@
import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor, HttpResponse } from '@angular/common/http';
import { Observable } from 'rxjs';
import { finalize } from 'rxjs/operators';
import { environment } from "../../../environments/environment";
import { AuthenticationService } from "../service/authentication.service";
import { extractHostname } from './utils';
import { BlockUI, NgBlockUI } from 'ng-block-ui';
import { tap } from 'rxjs/operators';
@Injectable()
export class JwtInterceptor implements HttpInterceptor {
@BlockUI() blockUI: NgBlockUI | undefined;
/**
*
* @param _authenticationService AuthenticationService
*/
constructor(private _authenticationService: AuthenticationService) {
}
/**
* Add auth header with jwt if user is logged in and request is to api url
* @param request HttpRequest
* @param next HttpHandler
*/
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
let requestUrl = request.url;
if(!requestUrl.startsWith('/package/status-paybill/'))
this.blockUI?.start('Đang xử lý...');
const currentUser = this._authenticationService.currentUserValue || JSON.parse(localStorage.getItem('currentCustomer') ?? '{}');
const isLoggedIn = currentUser && currentUser.jwtToken;
let langUrl = localStorage.getItem('lang');
const requestHostName = extractHostname(requestUrl);
if (requestHostName == '' || !requestHostName) {
requestUrl = `${environment.apiUrl}/${langUrl}${request.url}`;
}
const isApiUrl = requestUrl.startsWith(environment.apiUrl);
if (isLoggedIn && isApiUrl) {
request = request.clone({
url: requestUrl,
setHeaders: {
Authorization: `Bearer ${currentUser?.jwtToken}`
}
});
} else {
request = request.clone({
url: requestUrl
});
}
return next.handle(request).pipe(finalize(() => {
this.blockUI?.stop();
}));
}
}

View File

@ -0,0 +1,15 @@
export function extractHostname(url: string) {
let hostname;
// find & remove protocol (http, ftp, etc.) and get hostname
if (url.indexOf('//') > -1) {
hostname = url.split('/')[2];
} else {
hostname = url.split('/')[0];
}
// find & remove "?"
hostname = hostname.split('?')[0];
return hostname;
}

View File

@ -0,0 +1,25 @@
export class DetailNewsPaperModel {
id?: number;
content: string = '';
description: string = '';
editorialName: string = '';
image: string = '';
price: number = 0;
promotionPrice: number = 0;
title: string = '';
countCustomer: number = 0;
discountPercent: number = 0;
isTrial: boolean = false;
isBought: boolean = false;
dateCreate: string = '';
view: number = 0;
imageDataBase64: any = {};
}
export class PageModel {
page: number = 1;
pageSize: number = 6;
packageId: number = 0;
result: any = [];
totalRecord: number = 0;
}

View File

@ -0,0 +1,6 @@
export class PageModel {
page: number = 1;
pageSize: number = 10;
result: any = [];
totalRecord: number = 0;
}

View File

@ -0,0 +1,28 @@
export class User {
id?: number;
email?: string;
password?: string;
firstName?: string;
username?: string;
fullName?: string;
avatar?: string;
token?: string;
jwtToken?: string;
image?: string;
isAdmin?: boolean;
isActive?: boolean;
isEnable?: boolean;
role: any;
}
export class registerUser {
fullName: string = '';
email: string = '';
phone: string = '';
address: string = '';
gender: string = '';
birthDay: any;
confirmPassword: string = '';
password: string = '';
enterpriseSignDate: any;
}

View File

@ -0,0 +1,79 @@
import {Injectable} from '@angular/core';
import {HttpClient, HttpParams} from '@angular/common/http';
import {BehaviorSubject, Observable} from 'rxjs';
import {map} from 'rxjs/operators';
import {User} from "../models/user";
@Injectable({providedIn: 'root'})
export class AuthenticationService {
public currentUser: Observable<User>;
private currentUserSubject: BehaviorSubject<User>;
/**
* Constructor
* @param _http HttpClient
*/
constructor(
private _http: HttpClient,
) {
// @ts-ignore
this.currentUserSubject = new BehaviorSubject<User>(JSON.parse(localStorage.getItem('currentCustomer')));
this.currentUser = this.currentUserSubject.asObservable();
}
/**
* Get current user
*/
public get currentUserValue(): User {
return this.currentUserSubject.value;
}
/**
* Confirms if user is admin
*/
/**
* User login
*
* @param username username
* @param password password
* @returns user
*/
loginAccount(username: string, password: string) {
return this._http
.post<any>(`/api-user/login`, {username, password})
.pipe(
map(user => {
// login successful if there's a jwt token in the response
if (user) {
if (user.jwtToken) {
// store user details and jwt token in local storage to keep user logged in between page refreshes
localStorage.setItem('currentCustomer', JSON.stringify(user));
// notify
this.currentUserSubject.next(user);
} else {
sessionStorage.setItem('currentCustomer', JSON.stringify(user));
}
}
return user;
})
);
}
forgotPassword(email: string) {
return this._http
.post<any>(`/user/forgot-password`, {email})
.pipe();
}
/**
* User logout
*
*/
logout() {
localStorage.removeItem('currentCustomer');
// @ts-ignore
this.currentUserSubject.next(null);
}
}

View File

@ -0,0 +1 @@
<p>book-bestsellers-item works!</p>

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-book-bestsellers-item',
templateUrl: './book-bestsellers-item.component.html',
styleUrls: ['./book-bestsellers-item.component.scss']
})
export class BookBestsellersItemComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1,62 @@
<div class="bestsellers">
<div class="title-section text-uppercase">
<a href="#">Sách bán chạy</a>
</div>
<div class="book-swiper">
<swiper
[slidesPerView]="1" [spaceBetween]="30" [slidesPerGroup]="1"
[loop]="false" [loopFillGroupWithBlank]="true" [pagination]="false"
[navigation]="true" class="mySwiper"
[breakpoints]="
{
'576': {
slidesPerView: 1,
spaceBetween: 30
},
'992': {
slidesPerView: 2,
spaceBetween: 30
},
'1200': {
slidesPerView: 3,
spaceBetween: 30
}
}
"
>
<ng-template swiperSlide *ngFor="let item of fakeArray; let index = index">
<div class="book-item">
<div class="item-details">
<div class="item-img">
<img class="book-cover" routerLink="/chi-tiet-sach" [queryParams]="{ tab: '1'}"
src="../../../assets/images/book/book-1.jpg" alt="">
</div>
<div class="item-name text-uppercase">
<a routerLink="/chi-tiet-sach" [queryParams]="{ tab: '1'}">
bước chậm lại giữa thế gian
</a>
</div>
<div class="author-name text-capitalize">
<a href="/">
hae min
</a>
</div>
<div class="item-prices d-flex">
<div class="original-price">400,000đ</div>
<div class="sale-price">320,000đ</div>
</div>
</div>
<button type="button" class="btn btn-add">
<i class="bi bi-cart-plus"></i>
giỏ hàng
</button>
</div>
</ng-template>
</swiper>
</div>
<div class="see-more text-center">
<span>
<a routerLink="/the-loai/chu-de-sach">xem tất cả</a>
</span>
</div>
</div>

View File

@ -0,0 +1,195 @@
@import "~swiper/css";
@import "~swiper/css/pagination";
@import "~swiper/css/navigation";
app-book-bestsellers {
a {
text-decoration: none;
}
.bestsellers {
.title-section {
text-align: center;
margin-bottom: 32px;
a {
font-size: 20px;
color: #222;
font-weight: 600;
}
}
.swiper {
width: 100%;
height: 100%;
}
.swiper-slide {
text-align: center;
background: #fff;
border-radius: 10px;
box-shadow: inset rgba(10, 37, 64, 0.3) 0px -2px 6px 0px;
/* Center slide text vertically */
display: -webkit-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
-webkit-justify-content: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
-webkit-align-items: center;
align-items: center;
}
.swiper-slide img {
display: block;
height: 100%;
}
.book-swiper {
.swiper {
padding: 0 60px;
}
.swiper-button-prev, .swiper-button-next {
background-position: center;
background-color: #9a9a9a;
border-radius: 40px;
padding: 30px 20px;
&:hover {
background: #FCAF17;
}
}
.swiper-button-prev {
left: 0px;
}
.swiper-button-next {
right: 0px;
}
.swiper-button-next:after, .swiper-button-prev:after {
font-family: swiper-icons;
font-size: 24px;
text-transform: none !important;
letter-spacing: 0;
font-variant: initial;
line-height: 1;
color: #fff;
}
.swiper-button-next.swiper-button-disabled, .swiper-button-prev.swiper-button-disabled {
opacity: .35;
cursor: auto;
pointer-events: none;
}
.book-item {
padding: 30px 40px;
width: 100%;
.item-details {
display: flex;
flex-direction: column;
align-items: center;
}
.item-img {
height: 280px;
margin-bottom: 24px;
}
img.book-cover {
border-radius: 0 10px 10px 0;
box-shadow: 4px 0 4px #ccc;
cursor: pointer;
transition: transform 0.6s;
&:hover {
transform: scale(1.1);
}
}
.item-name {
a {
font-size: 15px;
font-weight: 500;
color: #222;
&:hover {
color: #E09500;
text-decoration: underline;
}
}
}
.author-name {
padding: 6px 0;
a {
color: #9a9a9a;
font-size: 14px;
font-weight: 500;
&:hover {
color: #E09500;
text-decoration: underline;
}
}
}
.item-prices {
.original-price, .sale-price {
font-size: 16px;
}
.original-price {
text-decoration: line-through;
margin-right: 10px;
color: #9a9a9a;
}
.sale-price {
font-weight: 500;
}
}
.btn-add {
width: 100%;
background-color: #ffe7b8;
color: #e09500;
margin-top: 24px;
padding: 8px 0;
text-transform: uppercase;
font-weight: 500;
border-radius: 24px;
i {
font-size: 18px;
margin-right: 9.75px;
}
&:hover {
border: 1px solid #e09500
}
}
}
}
.see-more {
margin-top: 48px;
a {
padding: 14px 24px;
background-color: #222;
border-radius: 6px;
color: #fff !important;
text-transform: uppercase;
font-weight: 600;
font-size: 12px;
&:hover {
background-color: #E09500;
}
}
}
}
}

View File

@ -0,0 +1,17 @@
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'app-book-bestsellers',
templateUrl: './book-bestsellers.component.html',
styleUrls: ['./book-bestsellers.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class BookBestsellersComponent implements OnInit {
fakeArray = new Array(10);
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SwiperModule } from 'swiper/angular';
import { BrowserModule } from '@angular/platform-browser';
@NgModule({
declarations: [],
imports: [
CommonModule,
SwiperModule,
BrowserModule
]
})
export class BookBestsellersModule { }

View File

@ -0,0 +1 @@
<p>book-cart works!</p>

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { BookCartComponent } from './book-cart.component';
describe('BookCartComponent', () => {
let component: BookCartComponent;
let fixture: ComponentFixture<BookCartComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ BookCartComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(BookCartComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-book-cart',
templateUrl: './book-cart.component.html',
styleUrls: ['./book-cart.component.scss']
})
export class BookCartComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1 @@
<p>book-checkout works!</p>

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { BookCheckoutComponent } from './book-checkout.component';
describe('BookCheckoutComponent', () => {
let component: BookCheckoutComponent;
let fixture: ComponentFixture<BookCheckoutComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ BookCheckoutComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(BookCheckoutComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-book-checkout',
templateUrl: './book-checkout.component.html',
styleUrls: ['./book-checkout.component.scss']
})
export class BookCheckoutComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1,225 @@
<block-ui>
<div class="container py-4">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a routerLink="/">{{'breadcrumb.home' | translate}}</a></li>
<li class="breadcrumb-item active" aria-current="page">Chi tiết sách</li>
</ol>
</nav>
<div class="book-details">
<div class="content-body">
<div class="book-title">
Bước chậm lại giữa thế gian vội
</div>
<div class="row">
<div class="col-lg-6 col-md-12">
<div class="book-cover">
<div class="book-cover-swiper">
<swiper
[slidesPerView]="1" [spaceBetween]="0" [slidesPerGroup]="1"
[loop]="false" [loopFillGroupWithBlank]="true" [pagination]="false"
[navigation]="true" class="mySwiper"
[breakpoints]="
{
'640': {
slidesPerView: 1,
spaceBetween: 0
},
'768': {
slidesPerView: 1,
spaceBetween: 0
},
'1024': {
slidesPerView: 1,
spaceBetween: 0
}
}
"
>
<ng-template swiperSlide *ngFor="let item of fakeArray; let index = index">
<div class="book-item">
<div class="item-details">
<div class="item-img">
<img class="book-cover" src="../../../assets/images/book/book-1.jpg" alt="">
</div>
</div>
</div>
</ng-template>
</swiper>
</div>
</div>
</div>
<div class="col-lg-6 col-md-12">
<div class="book-info">
<div class="row">
<div class="col-lg-4">
<div class="info-title">Giá tiền</div>
<div class="prices">
<div class="sale">98,000đ</div>
<div class="origin">126,000đ</div>
</div>
</div>
<div class="col-lg-5">
<div class="info-title">Tác giả</div>
<div class="author">
<a href="/">hae min</a>
</div>
</div>
<div class="col-lg-3">
<div class="info-title">Tình trạng</div>
<div class="status">Còn hàng</div>
</div>
</div>
<div class="quantity my-5">
<div class="info-title">Số lượng</div>
<div class="form-check-group">
<div class="form-check-inline">
<button class="btn btn-quantity" (click)="minus()"><i class="fa fa-minus"></i></button>
</div>
<div class="form-check-inline">
<input type="text" class="number-count form-control" [(ngModel)]="inputnumber">
</div>
<div class="form-check-inline">
<button class="btn btn-quantity" (click)="plus()"><i class="fa fa-plus"></i></button>
</div>
</div>
</div>
<div class="mb-5 row">
<div class="btn-buy-group col-lg-6 col-md-12">
<button class="btn btn-buy">Mua ngay</button>
</div>
<div class="btn-buy-group col-lg-6 col-md-12">
<button class="btn btn-add">Thêm vào giỏ hàng</button>
</div>
</div>
<hr>
</div>
</div>
</div>
</div>
</div>
<div class="book-details">
<div class="content-body">
<ul [(activeId)]="activeTab" ngbNav #nav="ngbNav" class="nav-pills" (navChange)="onChangeTab($event)">
<li [ngbNavItem]="1">
<a ngbNavLink>Chi tiết sách</a>
<ng-template ngbNavContent>
<div class="row">
<div class="col-md-4 col-sm-12">
<table class="table">
<tbody>
<tr>
<th class="table-title">Mã hàng</th>
<th>8935235217737</th>
</tr>
<tr>
<th class="table-title">Tên nhà cung cấp</th>
<th>
<a href="">
Nhã Nam
</a>
</th>
</tr>
<tr>
<th class="table-title">Tác giả</th>
<th>
<a href="">
Hae Min
</a>
</th>
</tr>
<tr>
<th class="table-title">Người dịch</th>
<th>Nguyễn Việt Tú Anh</th>
</tr>
<tr>
<th class="table-title">Nhà xuất bản</th>
<th>
<a href="">
NXB Hội Nhà Văn
</a>
</th>
</tr>
<tr>
<th class="table-title">Năm xuất bản</th>
<th>2018</th>
</tr>
<tr>
<th class="table-title">Ngôn ngữ</th>
<th>Tiếng Việt</th>
</tr>
<tr>
<th class="table-title">Kích thước</th>
<th>14 x 20.5 (cm)</th>
</tr>
<tr>
<th class="table-title">Số trang</th>
<th>254</th>
</tr>
</tbody>
</table>
</div>
<div class="col-md-8 col-sm-12">
<div class="book-description">
<div class="description-title">Nội dung</div>
<div class="description-content">
Chen vai thích cánh để có một chỗ bám trên xe buýt giờ đi làm,
nhích từng xentimét bánh xe trên đường lúc tan sở,
quay cuồng với thi cử và tiến độ công việc,
lu bù vướng mắc trong những mối quan hệ cả thân lẫn sơ…
bạn có luôn cảm thấy thế gian xung quanh mình đang xoay chuyển quá vội vàng?<br>
Nếu có thể, hãy tạm dừng một bước.<br>
Để tự hỏi, là do thế gian này vội vàng hay do chính tâm trí bạn đang quá bận rộn?
Để cầm cuốn sách nhỏ dung dị mà lắng đọng này lên, chậm rãi lật giở từng trang,
thong thả khám phá những điều mà chỉ khi bước chậm lại mới có thể thấu rõ: về các mối quan hệ,
về chính bản thân mình, về những trăn trở trước cuộc đời và nhân thế,
về bao điều lý trí rất hiểu nhưng trái tim chưa cách nào nghe theo.
</div>
</div>
</div>
</div>
</ng-template>
</li>
<li [ngbNavItem]="2">
<a ngbNavLink>Về tác giả</a>
<ng-template ngbNavContent>
<div class="row">
<div class="col-md-2 col-sm-12">
<div class="author-ava">
<img src="../../../assets/images/book/author-img.jpeg" alt="">
</div>
</div>
<div class="col-md-10 col-sm-12">
<div class="author-intro">
Nhà văn Hae Min là một tác giả viết sách, một diễn giả, đồng thời là chuyên gia huấn luyện cấp lãnh đạo.
Cô được mệnh danh là “nhà đấu tranh cho người hướng nội”.
Điều cô luôn hướng đến là giúp các tổ chức nhìn nhận đúng về người hướng nội và coi trọng họ,
và giúp những cá nhân hướng nội tự tin đảm nhiệm vị trí lãnh đạo và các vai trò có tầm ảnh hưởng.<br>
Cô trở nên am hiểu về người hướng nội trong thời gian làm giám đốc chương trình của
chính phủ liên bang và chuyên gia huấn luyện trong lĩnh vực phát triển sự nghiệp.
Cô cũng đảm nhiệm vai trò cố vấn học tập và phát triển trong các tổ chức hàng đầu như
GE, AT&T, NASA, Turner Broadcasting, và CDC. <br>
Quyển sách đầu tiên của cô về đề tài người hướng nội,
“The Introverted Leader: Building on Your Quiet Strength”
(tạm dịch “Người lãnh đạo hướng nội: Phát huy thế mạnh trầm tĩnh của bạn”),
thu hút độc giả ở khắp nơi và được dịch ra nhiều thứ tiếng.
</div>
</div>
</div>
</ng-template>
</li>
<li [ngbNavItem]="3">
<a ngbNavLink>Đánh giá & Bình luận</a>
<ng-template ngbNavContent>
Coming soon! 
</ng-template>
</li>
</ul>
<div [ngbNavOutlet]="nav"></div>
</div>
</div>
<app-book-related></app-book-related>
</div>
</block-ui>

View File

@ -0,0 +1,269 @@
@import "~swiper/css";
@import "~swiper/css/pagination";
@import "~swiper/css/navigation";
app-book-details {
a {
text-decoration: none;
color: #000;
}
.book-details {
background-color: #fff;
border-radius: 20px;
margin-bottom: 32px;
.content-body {
padding: 32px;
.book-title {
font-size: 24px;
font-weight: 600;
text-transform: uppercase;
margin-bottom: 32px;
text-align: center;
}
.book-cover {
background-color: #fff5e2;
border-radius: 20px;
.swiper {
width: 100%;
height: 100%;
border-radius: 100px 0;
}
.swiper-slide {
padding: 32px 0;
/* Center slide text vertically */
display: -webkit-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
-webkit-justify-content: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
-webkit-align-items: center;
align-items: center;
}
.swiper-slide img {
display: block;
height: 100%;
}
.book-cover-swiper {
.swiper {
padding: 0 !important;
}
.swiper-button-prev, .swiper-button-next {
background-position: center;
background-color: #9a9a9a;
border-radius: 40px;
padding: 30px 20px;
&:hover {
background: #FCAF17;
}
}
.swiper-button-prev {
left: 32px;
}
.swiper-button-next {
right: 32px;
}
.swiper-button-next:after, .swiper-button-prev:after {
font-family: swiper-icons;
font-size: 24px;
text-transform: none !important;
letter-spacing: 0;
font-variant: initial;
line-height: 1;
color: #fff;
}
.swiper-button-next.swiper-button-disabled, .swiper-button-prev.swiper-button-disabled {
opacity: .35;
cursor: auto;
pointer-events: none;
}
.book-item {
.item-details {
display: flex;
flex-direction: column;
align-items: center;
img.book-cover {
max-width: 100%;
border-radius: 0 10px 10px 0;
box-shadow: 4px 0 4px rgb(180, 180, 180);
}
}
}
}
}
.book-info {
padding-left: 12px;
.info-title {
font-size: 14px;
font-weight: 600;
color: #9a9a9a;
margin-bottom: 4px;
}
.prices {
.sale {
font-size: 32px;
font-weight: 600;
color: #E09500;
}
.origin {
font-size: 18px;
text-decoration: line-through;
color: #ccc;
font-weight: 500;
}
}
.author {
text-transform: capitalize;
a {
font-size: 18px;
font-weight: 600;
&:hover {
color: #E09500;
}
}
}
.status {
font-size: 18px;
font-weight: 600;
color: #28A745;
}
.quantity {
.form-check-group {
display: flex;
align-items: center;
}
.form-check-inline {
margin-right: 2px;
}
.number-count {
width: 52px;
border: 1px solid #9a9a9a;
border-radius: 6px;
}
.btn-quantity {
background-color: #9a9a9a;
border: 1px solid #9a9a9a;
color: #fff;
&:hover {
background-color: #FCAF17;
border: 1px solid #FCAF17;
color: #222;
}
}
}
.btn-buy-group {
.btn-buy, .btn-add {
width: 100%;
padding: 14px 24px;
font-size: 12;
font-weight: 600;
text-transform: uppercase;
border-radius: 6px;
}
.btn-buy {
background-color: #FCAF17;
border: 1px solid #FCAF17;
color: #fff;
&:hover {
background-color: #222;
border: 1px solid #222;
color: #fff;
}
}
.btn-add {
border: 1px solid #FCAF17;
&:hover {
background-color: #222;
border: 1px solid #222;
color: #fff;
}
}
}
}
.nav-pills .nav-link.active {
border-bottom: 2px solid #FCAF17;
background-color: #fff;
border-radius: 0;
color: #E09500;
}
.nav-pills {
border-bottom: 1px solid #ccc;
}
.nav-link {
color: #222;
padding: 0 24px 14px 24px;
font-weight: 500;
}
.tab-pane {
margin-top: 14px;
}
table {
// width: 40%;
thead, tbody, tfoot, tr, td, th {
border: none;
font-weight: normal;
color: #222;
}
.table-title {
font-weight: 600;
}
a {
color: #E09500;
&:hover {
text-decoration: underline;
}
}
}
.book-description {
.description-title {
font-weight: 600;
margin-bottom: 10px;
}
.description-content {
text-align: justify;
font-size: 14px;
overflow: hidden;
text-overflow: ellipsis;
-webkit-line-clamp: 12;
display: -webkit-box;
-webkit-box-orient: vertical;
}
}
.author-ava {
img {
max-width: 100%;
object-fit: cover;
border-radius: 10px;
}
}
.author-intro {
text-align: justify;
}
}
}
}

View File

@ -0,0 +1,43 @@
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
@Component({
selector: 'app-book-details',
templateUrl: './book-details.component.html',
styleUrls: ['./book-details.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class BookDetailsComponent implements OnInit {
fakeArray = new Array(10);
inputnumber = 0;
public activeTab: number = 1;
constructor(
private activatedRoute: ActivatedRoute,
private router: Router
) {
this.activatedRoute.queryParams.subscribe((params: any) => {
this.activeTab = parseInt(params['tab']);
});
}
plus() {
this.inputnumber = this.inputnumber + 1;
}
minus() {
if(this.inputnumber != 0) { this.inputnumber = this.inputnumber - 1 }
}
onChangeTab($event: any) {
this.activeTab = $event.nextId;
this.router.navigate(
['/chi-tiet-sach'],
{ queryParams: { tab: this.activeTab } }
);
}
ngOnInit(): void {
}
}

View File

@ -0,0 +1,12 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
@NgModule({
declarations: [],
imports: [
CommonModule,
]
})
export class BookDetailsModule { }

View File

@ -0,0 +1,3 @@
<block-ui>
</block-ui>

View File

@ -0,0 +1,5 @@
app-book-list-by-category {
}

View File

@ -0,0 +1,16 @@
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'app-book-list-by-category',
templateUrl: './book-list-by-category.component.html',
styleUrls: ['./book-list-by-category.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class BookListByCategoryComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1,10 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
@NgModule({
declarations: [],
imports: [
CommonModule
]
})
export class BookListByCategoryModule { }

View File

@ -0,0 +1 @@
<p>book-new-arrivals-item works!</p>

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-book-new-arrivals-item',
templateUrl: './book-new-arrivals-item.component.html',
styleUrls: ['./book-new-arrivals-item.component.scss']
})
export class BookNewArrivalsItemComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1,62 @@
<div class="new-arrivals">
<div class="title-section text-uppercase">
<a href="#">Sách mới</a>
</div>
<div class="book-swiper">
<swiper
[slidesPerView]="1" [spaceBetween]="30" [slidesPerGroup]="1"
[loop]="false" [loopFillGroupWithBlank]="true" [pagination]="false"
[navigation]="true" class="mySwiper"
[breakpoints]="
{
'640': {
slidesPerView: 1,
spaceBetween: 30
},
'768': {
slidesPerView: 2,
spaceBetween: 30
},
'1024': {
slidesPerView: 3,
spaceBetween: 30
}
}
"
>
<ng-template swiperSlide *ngFor="let item of fakeArray; let index = index">
<div class="book-item">
<div class="item-details">
<div class="item-img">
<img class="book-cover" src="../../../assets/images/book/book-2.jpeg" alt="">
</div>
<div class="item-name text-uppercase">
hai số phận
</div>
<div class="author-name text-capitalize">
jeffrey archer
</div>
<div class="item-prices d-flex">
<div class="sale-price">180,000đ</div>
</div>
</div>
<div class="group-button d-flex justify-content-evenly">
<button type="button" class="btn btn-discover" routerLink="/chi-tiet-sach" [queryParams]="{ tab: '1'}">
<i class="bi bi-eye"></i>
chi tiết
</button>
<button type="button" class="btn btn-add">
<i class="bi bi-cart-plus"></i>
giỏ hàng
</button>
</div>
</div>
</ng-template>
</swiper>
</div>
<div class="see-more text-center">
<span>
<a href="#">xem tất cả</a>
</span>
</div>
</div>

View File

@ -0,0 +1,197 @@
@import "~swiper/css";
@import "~swiper/css/pagination";
@import "~swiper/css/navigation";
app-book-new-arrivals {
a {
text-decoration: none;
}
.new-arrivals {
.title-section {
text-align: center;
margin-bottom: 32px;
a {
font-size: 20px;
color: #222;
font-weight: 600;
}
}
.swiper {
width: 100%;
height: 100%;
}
.swiper-slide {
text-align: center;
background: #fff;
border-radius: 10px;
box-shadow: inset rgba(10, 37, 64, 0.3) 0px -2px 6px 0px;
/* Center slide text vertically */
display: -webkit-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
-webkit-justify-content: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
-webkit-align-items: center;
align-items: center;
}
.swiper-slide img {
display: block;
height: 100%;
}
.book-swiper {
.swiper {
padding: 0 60px;
}
.swiper-button-prev, .swiper-button-next {
background-position: center;
background-color: #9a9a9a;
border-radius: 40px;
padding: 30px 20px;
&:hover {
background: #FCAF17;
}
}
.swiper-button-prev {
left: 0px;
}
.swiper-button-next {
right: 0px;
}
.swiper-button-next:after, .swiper-button-prev:after {
font-family: swiper-icons;
font-size: 24px;
text-transform: none !important;
letter-spacing: 0;
font-variant: initial;
line-height: 1;
color: #fff;
}
.swiper-button-next.swiper-button-disabled, .swiper-button-prev.swiper-button-disabled {
opacity: .35;
cursor: auto;
pointer-events: none;
}
.book-item {
padding: 30px 40px;
width: 100%;
.item-details {
display: flex;
flex-direction: column;
align-items: center;
}
.item-img {
height: 280px;
margin-bottom: 24px;
}
img.book-cover {
border-radius: 0 10px 10px 0;
box-shadow: 4px 0 4px #ccc;
cursor: pointer;
transition: transform 0.6s;
&:hover {
transform: scale(1.1);
}
}
.item-name {
a {
font-size: 15px;
font-weight: 500;
color: #222;
&:hover {
color: #E09500;
text-decoration: underline;
}
}
}
.author-name {
padding: 6px 0;
a {
color: #9a9a9a;
font-size: 14px;
font-weight: 500;
&:hover {
color: #E09500;
text-decoration: underline;
}
}
}
.item-prices {
.original-price, .sale-price {
font-size: 16px;
font-weight: 500;
}
.original-price {
text-decoration: line-through;
margin-right: 10px;
color: #9a9a9a;
}
.sale-price {
font-weight: 500;
}
}
.btn-add {
width: 100%;
background-color: #ffe7b8;
color: #e09500;
margin-top: 24px;
padding: 8px 0;
text-transform: uppercase;
font-weight: 500;
border-radius: 24px;
i {
font-size: 18px;
margin-right: 9.75px;
}
&:hover {
border: 1px solid #e09500
}
}
}
}
.see-more {
margin-top: 48px;
a {
padding: 14px 28px;
background-color: #222;
border-radius: 6px;
color: #fff !important;
text-transform: uppercase;
font-weight: 600;
font-size: 12px;
&:hover {
background-color: #E09500;
}
}
}
}
}

View File

@ -0,0 +1,17 @@
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'app-book-new-arrivals',
templateUrl: './book-new-arrivals.component.html',
styleUrls: ['./book-new-arrivals.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class BookNewArrivalsComponent implements OnInit {
fakeArray = new Array(10);
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SwiperModule } from 'swiper/angular';
import { BrowserModule } from '@angular/platform-browser';
@NgModule({
declarations: [],
imports: [
CommonModule,
SwiperModule,
BrowserModule
]
})
export class BookNewArrivalsModule { }

View File

@ -0,0 +1,63 @@
<div class="book-related">
<div class="title-section text-uppercase">
<a href="#">Sách liên quan</a>
</div>
<div class="book-swiper">
<swiper
[slidesPerView]="1" [spaceBetween]="30" [slidesPerGroup]="1"
[loop]="false" [loopFillGroupWithBlank]="true" [pagination]="false"
[navigation]="true" class="mySwiper"
[breakpoints]="
{
'640': {
slidesPerView: 1,
spaceBetween: 30
},
'768': {
slidesPerView: 1,
spaceBetween: 30
},
'1024': {
slidesPerView: 3,
spaceBetween: 30
}
}
"
>
<ng-template swiperSlide *ngFor="let item of fakeArray; let index = index">
<div class="book-item">
<div class="item-details">
<div class="item-img">
<img class="book-cover" src="../../../assets/images/book/book-1.jpg" alt="">
</div>
<div class="item-name text-uppercase">
bước chậm lại giữa thế gian
</div>
<div class="author-name text-capitalize">
hae min
</div>
<div class="item-prices d-flex">
<div class="original-price">400,000đ</div>
<div class="sale-price">320,000đ</div>
</div>
</div>
<div class="group-button d-flex justify-content-evenly">
<button type="button" class="btn btn-discover" routerLink="/chi-tiet-sach" [queryParams]="{ tab: '1'}">
<i class="bi bi-eye"></i>
chi tiết
</button>
<button type="button" class="btn btn-add">
<i class="bi bi-cart-plus"></i>
giỏ hàng
</button>
</div>
</div>
</ng-template>
</swiper>
</div>
<div class="see-more text-center">
<span>
<a href="#">xem tất cả</a>
</span>
</div>
</div>

View File

@ -0,0 +1,184 @@
@import "~swiper/css";
@import "~swiper/css/pagination";
@import "~swiper/css/navigation";
app-book-related {
a {
text-decoration: none;
}
.book-related {
.title-section {
margin: 120px 0 20px 0;
text-align: center;
a {
font-size: 20px;
color: #222;
font-weight: 600;
}
}
.swiper {
width: 100%;
height: 100%;
}
.swiper-slide {
text-align: center;
background: #fff;
border-radius: 10px;
box-shadow: inset rgba(10, 37, 64, 0.3) 0px -2px 6px 0px;
/* Center slide text vertically */
display: -webkit-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
-webkit-justify-content: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
-webkit-align-items: center;
align-items: center;
}
.swiper-slide img {
display: block;
height: 100%;
}
.book-swiper {
.swiper {
padding: 0 60px;
}
.swiper-button-prev, .swiper-button-next {
background-position: center;
background-color: #9a9a9a;
border-radius: 40px;
padding: 30px 20px;
&:hover {
background: #FCAF17;
}
}
.swiper-button-prev {
left: 0px;
}
.swiper-button-next {
right: 0px;
}
.swiper-button-next:after, .swiper-button-prev:after {
font-family: swiper-icons;
font-size: 24px;
text-transform: none !important;
letter-spacing: 0;
font-variant: initial;
line-height: 1;
color: #fff;
}
.swiper-button-next.swiper-button-disabled, .swiper-button-prev.swiper-button-disabled {
opacity: .35;
cursor: auto;
pointer-events: none;
}
.book-item {
padding: 30px 40px;
width: 100%;
.item-details {
display: flex;
flex-direction: column;
align-items: center;
}
.item-img {
height: 280px;
margin-bottom: 24px;
}
img.book-cover {
border-radius: 0 10px 10px 0;
box-shadow: 4px 0 4px #ccc;
}
.item-name {
font-size: 15px;
font-weight: 500;
}
.author-name {
color: #9a9a9a;
font-size: 14px;
padding: 6px 0;
cursor: pointer;
font-weight: 500;
}
.item-prices {
.original-price, .sale-price {
font-size: 16px;
}
.original-price {
text-decoration: line-through;
margin-right: 10px;
color: #9a9a9a;
}
.sale-price {
font-weight: 500;
}
}
.group-button {
margin-top: 24px;
.btn {
font-size: 12px;
background-color: #ffe7b8;
color: #e09500;
text-transform: uppercase;
font-weight: 500;
display: flex;
align-items: center;
border-radius: 24px;
padding: 6px 20px;
i {
font-size: 18px;
margin-right: 9.75px;
}
&:hover {
border: 1px solid #e09500;
}
}
}
}
}
.see-more {
margin: 34px 0 134px 0;
a {
padding: 14px 24px;
background-color: #222;
border-radius: 6px;
color: #fff;
text-transform: uppercase;
font-weight: 600;
font-size: 12px;
&:hover {
background-color: #E09500;
}
}
}
}
}

View File

@ -0,0 +1,17 @@
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'app-book-related',
templateUrl: './book-related.component.html',
styleUrls: ['./book-related.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class BookRelatedComponent implements OnInit {
fakeArray = new Array(10);
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SwiperModule } from 'swiper/angular';
import { BrowserModule } from '@angular/platform-browser';
@NgModule({
declarations: [],
imports: [
CommonModule,
SwiperModule,
BrowserModule
]
})
export class BookRelatedModule { }

View File

@ -0,0 +1 @@
<p>book-sale-item works!</p>

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-book-sale-item',
templateUrl: './book-sale-item.component.html',
styleUrls: ['./book-sale-item.component.scss']
})
export class BookSaleItemComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1 @@
<p>book-sale works!</p>

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-book-sale',
templateUrl: './book-sale.component.html',
styleUrls: ['./book-sale.component.scss']
})
export class BookSaleComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1,52 @@
<div class="book-subject">
<div class="title-section text-uppercase">
<a href="#">Thể loại</a>
</div>
<div class="subject-swiper">
<swiper
[slidesPerView]="1" [spaceBetween]="0" [slidesPerGroup]="1"
[loop]="false" [loopFillGroupWithBlank]="true" [pagination]="false"
[navigation]="false" class="mySwiper"
[breakpoints]="
{
'640': {
slidesPerView: 1,
spaceBetween: 0
},
'768': {
slidesPerView: 1,
spaceBetween: 0
},
'1024': {
slidesPerView: 3,
spaceBetween: 0
}
}
"
>
<ng-template swiperSlide *ngFor="let item of fakeArray; let index = index">
<div class="subject-item">
<div class="item-details">
<div class="item-img">
<img class="book-cover" src="../../../assets/images/book/subject-cover.png" alt="">
</div>
<div class="item-desc">
<div class="item-name">tiểu thuyết</div>
<div class="item-content">
Tiểu thuyết thường được trình bày theo dạng tự sự và trần thuật,
có thể tập trung vào một hoặc một vài cá nhân trong không gian và
thời gian nghệ thuật. Ngôn ngữ chính để trình bày loại hình văn học
này chính là ngôn ngữ văn xuôi với độ dài đáng kể.
</div>
</div>
<div class="see-more text-center">
<span>
<a href="#">khám phá</a>
</span>
</div>
</div>
</div>
</ng-template>
</swiper>
</div>
</div>

View File

@ -0,0 +1,110 @@
@import "~swiper/css";
@import "~swiper/css/pagination";
@import "~swiper/css/navigation";
app-book-subject {
a {
text-decoration: none;
}
.book-subject {
.title-section {
text-align: center;
margin-bottom: 32px;
a {
font-size: 20px;
color: #222;
font-weight: 600;
}
}
}
.swiper {
width: 100%;
height: 100%;
border-radius: 100px 0;
}
.swiper-slide {
text-align: center;
font-size: 18px;
background: #fff;
/* Center slide text vertically */
display: -webkit-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
-webkit-justify-content: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
-webkit-align-items: center;
align-items: center;
}
.swiper-slide img {
display: block;
height: 100%;
}
.subject-swiper {
.swiper {
padding: 0 !important;
}
.subject-item {
.item-details {
display: flex;
flex-direction: column;
align-items: center;
img.book-cover {
max-width: 100%;
}
.item-desc {
padding: 10px 40px;
.item-name {
text-transform: uppercase;
font-size: 15px;
font-weight: 500;
margin: 10px 0;
}
.item-content {
overflow: hidden;
text-overflow: ellipsis;
-webkit-line-clamp: 3;
display: -webkit-box;
-webkit-box-orient: vertical;
color: #9a9a9a;
font-size: 14px;
}
}
.see-more {
margin: 20px 0 40px 0;
a {
padding: 14px 28px;
background-color: #222;
border-radius: 30px;
color: #fff !important;
text-transform: uppercase;
font-weight: 600;
font-size: 12px;
&:hover {
background-color: #E09500;
}
}
}
}
}
}
}

View File

@ -0,0 +1,17 @@
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'app-book-subject',
templateUrl: './book-subject.component.html',
styleUrls: ['./book-subject.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class BookSubjectComponent implements OnInit {
fakeArray = new Array(12);
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SwiperModule } from 'swiper/angular';
@NgModule({
declarations: [],
imports: [
CommonModule,
SwiperModule
]
})
export class BookSubjectModule { }

View File

@ -0,0 +1,65 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { BookSaleComponent } from './book-sale/book-sale.component';
import { BookSaleItemComponent } from './book-sale-item/book-sale-item.component';
import { BookBestsellersComponent } from './book-bestsellers/book-bestsellers.component';
import { BookBestsellersItemComponent } from './book-bestsellers-item/book-bestsellers-item.component';
import { BookNewArrivalsComponent } from './book-new-arrivals/book-new-arrivals.component';
import { BookNewArrivalsItemComponent } from './book-new-arrivals-item/book-new-arrivals-item.component';
import { BookRelatedComponent } from './book-related/book-related.component';
import { BookDetailsComponent } from './book-details/book-details.component';
import { SwiperModule } from 'swiper/angular';
import { BookSubjectComponent } from './book-subject/book-subject.component';
import { RouterModule } from '@angular/router';
import { BlockUIModule } from 'ng-block-ui';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { HttpClient } from '@angular/common/http';
import { FormsModule } from '@angular/forms';
import { BookRelatedModule } from './book-related/book-related.module';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { BookListByCategoryComponent } from './book-list-by-category/book-list-by-category.component';
import { BookCartComponent } from './book-cart/book-cart.component';
import { BookCheckoutComponent } from './book-checkout/book-checkout.component';
import { BookListByCategoryModule } from './book-list-by-category/book-list-by-category.module';
@NgModule({
declarations: [
BookSaleComponent,
BookSaleItemComponent,
BookBestsellersComponent,
BookBestsellersItemComponent,
BookNewArrivalsComponent,
BookNewArrivalsItemComponent,
BookRelatedComponent,
BookDetailsComponent,
BookSubjectComponent,
BookListByCategoryComponent,
BookCartComponent,
BookCheckoutComponent
],
imports: [
CommonModule,
SwiperModule,
RouterModule,
BlockUIModule.forRoot(),
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: (http: HttpClient) => { return new TranslateHttpLoader(http, '../../../assets/i18n', '.json'); },
deps: [HttpClient]
}
}),
FormsModule,
NgbModule,
],
exports: [
BookBestsellersComponent,
BookSubjectComponent,
BookNewArrivalsComponent,
BookRelatedModule,
BookListByCategoryModule
]
})
export class BookModule { }

View File

@ -0,0 +1,9 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class BookService {
constructor() { }
}

View File

@ -0,0 +1,3 @@
<block-ui>
<app-header-bookstore></app-header-bookstore>
</block-ui>

View File

@ -0,0 +1,3 @@
app-bookstore {
background-color: #FCAF17;
}

View File

@ -0,0 +1,16 @@
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'app-bookstore',
templateUrl: './bookstore.component.html',
styleUrls: ['./bookstore.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class BookstoreComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1,12 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { BlockUIModule } from 'ng-block-ui';
@NgModule({
declarations: [],
imports: [
CommonModule,
BlockUIModule.forRoot()
]
})
export class BookstoreModule { }

View File

@ -0,0 +1,9 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class BookstoreService {
constructor() { }
}

View File

@ -0,0 +1 @@
<p>footer-bookstore works!</p>

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-footer-bookstore',
templateUrl: './footer-bookstore.component.html',
styleUrls: ['./footer-bookstore.component.scss']
})
export class FooterBookstoreComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1,91 @@
<header>
<div class="container">
<!-- Navbar -->
<nav class="navbar navbar-expand-lg navbar-light"><!-- Navbar brand -->
<a class="navbar-brand mt-2 mt-lg-0" href="#">
<img
src="../../../../assets/images/brand/Logo.png"
alt="Brand Logo" height="90" loading="lazy"
/>
</a>
<!-- Toggle button -->
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
data-bs-target="#navbarTogglerDemo02" aria-controls="navbarTogglerDemo02" aria-expanded="false"
aria-label="Toggle navigation"
>
<span class="navbar-toggler-icon"></span>
</button>
<!-- Collapsible wrapper -->
<div class="collapse navbar-collapse" id="navbarTogglerDemo02">
<!-- Left links -->
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="#">Trang chủ</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Danh mục
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#">Action</a></li>
<li><a class="dropdown-item" href="#">Another action</a></li>
</ul>
</li>
</ul>
</div>
<!-- Right elements -->
<div class="d-flex align-items-center">
<!-- Icon -->
<a class="text-reset me-3" href="#">
<i class="fas fa-shopping-cart"></i>
</a>
<!-- Notifications -->
<div class="dropdown">
<a class="text-reset me-3 dropdown-toggle hidden-arrow" href="#" id="navbarDropdownMenuLink"
role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-bell"></i>
<span class="badge rounded-pill badge-notification bg-danger">1</span>
</a>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdownMenuLink">
<li>
<a class="dropdown-item" href="#">Some news</a>
</li>
<li>
<a class="dropdown-item" href="#">Another news</a>
</li>
<li>
<a class="dropdown-item" href="#">Something else here</a>
</li>
</ul>
</div>
<button></button>
<!-- Avatar
<div class="dropdown">
<a class="dropdown-toggle d-flex align-items-center hidden-arrow" href="#"
id="navbarDropdownMenuAvatar" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<img src="https://mdbcdn.b-cdn.net/img/new/avatars/2.webp" class="rounded-circle" height="25"
alt="Black and White Portrait of a Man" loading="lazy" />
</a>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdownMenuAvatar">
<li>
<a class="dropdown-item" href="#">My profile</a>
</li>
<li>
<a class="dropdown-item" href="#">Settings</a>
</li>
<li>
<a class="dropdown-item" href="#">Logout</a>
</li>
</ul>
</div> -->
</div>
</nav>
</div>
</header>

View File

@ -0,0 +1,8 @@
app-header-bookstore {
header {
background-color: #fff;
}
.nav-link {
}
}

View File

@ -0,0 +1,16 @@
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'app-header-bookstore',
templateUrl: './header-bookstore.component.html',
styleUrls: ['./header-bookstore.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class HeaderBookstoreComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1,12 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
@NgModule({
declarations: [],
imports: [
CommonModule
]
})
export class HeaderBookstoreModule { }

View File

@ -0,0 +1,9 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class HeaderBookstoreService {
constructor() { }
}

View File

@ -0,0 +1,15 @@
<div class="office-item">
<div class="item-img">
<a routerLink="/{{item.slug}}">
<img class="img-fluid" [src]="baseUrl + item.image" (error)="errorHandler($event)">
</a>
</div>
<h3 class="item-title">
<a routerLink="/{{item.slug}}">{{item.name}}</a>
</h3>
<p class="item-total">{{'home.total-package' | translate}}: {{item.total}}</p>
<p class="item-total">{{'home.total-newpaper' | translate}}: {{item.totalNewspaper}}</p>
<div class="group-button mt-2">
<a routerLink="/{{item.slug}}" class="btn btn-detail">{{'cta.detail' | translate}}</a>
</div>
</div>

View File

@ -0,0 +1,68 @@
app-editorial-office-item {
margin-bottom: 16px;
.office-item {
box-shadow: 0 0 5px #efefef;
height: 100%;
padding: 8px;
border-radius: 5px;
display: flex;
flex-direction: column;
justify-content: space-between;
overflow: hidden;
&:hover {
opacity: 0.8;
box-shadow: 0 0 5px #ccc;
}
.item-img {
overflow: hidden;
border-radius: 5px;
min-height: 80px;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
margin: -5px;
img {
width: 100%;
object-fit: cover;
transition: transform 0.6s;
&:hover {
transform: scale(1.1);
}
}
}
.item-title {
text-align: center;
text-transform: uppercase;
font-weight: 600;
margin-bottom: 8px;
padding-top: 18px;
a {
text-decoration: none;
color: #666;
font-size: 14px;
display: block;
&:hover {
color: #fcaf17;
}
}
}
.item-total {
text-align: center;
margin-bottom: 0px;
}
.group-button {
text-align: center;
a {
background: #fcaf17;
color: #fff;
}
}
}
}

View File

@ -0,0 +1,23 @@
import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
import { environment } from 'src/environments/environment';
@Component({
selector: 'app-editorial-office-item',
templateUrl: './editorial-office-item.component.html',
styleUrls: ['./editorial-office-item.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class EditorialOfficeItemComponent implements OnInit {
@Input() item: any;
public baseUrl = environment.apiUrl.substring(0, environment.apiUrl.length - 4);
constructor() { }
errorHandler($event: any) {
$event.target.src = '/assets/images/background/default-image.png';
}
ngOnInit(): void {
}
}

View File

@ -0,0 +1,9 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class EditorialOfficeItemService {
constructor() { }
}

View File

@ -0,0 +1,32 @@
<block-ui>
<div class="container pt-4">
<div class="card">
<div class="container-fluid">
<div class="box-office">
<header class="office-header">
<span>Danh sách tòa soạn</span>
</header>
<div class="office-search">
<input (keyup.enter)="onGetOffice();" type="search" [(ngModel)]="dataModel.keyWord"
class="form-control" placeholder="{{'office-detail.placeholder-search' | translate}}" />
<button type="button" class="btn btn-primary" (click)="onGetOffice();">
<i class="fas fa-search"></i>
</button>
</div>
<div class="row">
<div class="col-md-3 col-sm-6 col-xs-12 mb-4"
*ngFor="let officeItem of dataModel.result; let index = index">
<app-editorial-office-item [item]="officeItem"></app-editorial-office-item>
</div>
</div>
<div class="cus-page">
<ngb-pagination [collectionSize]="dataModel.totalItemCount" [(page)]="dataModel.pageNumber"
[maxSize]="3" [rotate]="true" [pageSize]="dataModel.pageSize"
(pageChange)="onPageChange($event)">
</ngb-pagination>
</div>
</div>
</div>
</div>
</div>
</block-ui>

View File

@ -0,0 +1,77 @@
.card {
border: none;
.box-office {
padding: 16px 0;
}
.office-header {
text-transform: uppercase;
font-weight: 600;
margin-bottom: 20px;
padding-bottom: 5px;
border-bottom: 1px solid #ccc;
span {
font-size: 24px;
padding-bottom: 8px;
color: #666;
border-bottom: 3px solid #fcaf17;
}
}
.office-search {
display: flex;
margin-bottom: 20px;
input {
height: 40px;
border: none;
border-radius: 0;
background: #efefef;
font-size: 18px;
color: #666;
}
.form-control:focus {
box-shadow: none;
}
button {
width: 50px;
border-radius: 0;
background: #efefef;
border: none;
color: #fcaf17;
padding-top: 10px;
border-left: 1px solid #ccc;
&:hover {
background: #ccc;
}
}
}
}
::ng-deep .cus-page {
display: flex;
justify-content: flex-end;
li.page-item,
li.page-item.disabled {
a,
.page-link {
background-color: #e9ecef;
border-color: #e9ecef;
color: #222;
}
}
li.page-item {
width: 36px;
text-align: center;
line-height: 24px;
margin: 0 2px;
border-radius: 50%;
overflow: hidden;
font-weight: bold;
}
li.page-item.active .page-link {
background-color: #e09500;
border-color: #e09500;
color: #fff;
}
}

View File

@ -0,0 +1,35 @@
import { Component, OnInit } from '@angular/core';
import { PageModel } from 'src/app/Models/model';
import { EditorialOfficeListService } from './editorial-office-list.service';
@Component({
selector: 'app-editorial-office-list',
templateUrl: './editorial-office-list.component.html',
styleUrls: ['./editorial-office-list.component.scss']
})
export class EditorialOfficeListComponent implements OnInit {
dataModel: PageModel = new PageModel();
constructor(private _editorialOfficeListService: EditorialOfficeListService) {
this.dataModel.pageSize = 16;
}
onGetOffice() {
this._editorialOfficeListService.onGetListOffice(this.dataModel.keyWord, this.dataModel.pageNumber, this.dataModel.pageSize)
.subscribe(data => {
const keyWord = this.dataModel.keyWord;
this.dataModel = data;
this.dataModel.keyWord = keyWord;
});
}
onPageChange(numberPage: number): void {
this.dataModel.pageNumber = numberPage;
this.onGetOffice();
}
ngOnInit(): void {
this.onGetOffice();
}
}

View File

@ -0,0 +1,18 @@
import { HttpClient, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class EditorialOfficeListService {
constructor(private _httpClient: HttpClient) { }
onGetListOffice(keyWord: string, page: number, pageSize: number) {
let params = new HttpParams();
params = params.append('keyWord', keyWord);
params = params.append('page', page);
params = params.append('pageSize', pageSize);
return this._httpClient.get<any>(`/editorial-office/list-home`, { params });
}
}

View File

@ -0,0 +1,53 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SwiperModule } from "swiper/angular";
import { BrowserModule } from "@angular/platform-browser";
import { NgbModule, NgbNavModule } from "@ng-bootstrap/ng-bootstrap";
import { PackageModule } from "../package/package.module";
import { RouterModule } from '@angular/router';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { HttpClient } from '@angular/common/http';
import { EditorialOfficeItemComponent } from './editorial-office-item/editorial-office-item.component';
import { EditorialOfficeListComponent } from './editorial-office-list/editorial-office-list.component';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BlockUIModule } from 'ng-block-ui';
import { QuillModule } from 'ngx-quill';
import { PackageAskonomyModule } from '../package/package-askonomy/package-askonomy.module';
import { MyPackageModule } from '../my-package/my-package.module';
import { VneconomyNewpaperComponent } from './vneconomy/vneconomy-newpaper/vneconomy-newpaper.component';
import { VneconomyModule } from './vneconomy/vneconomy-home/vneconomy.module';
import { PackagesModule } from '../modules/packages/packages.module';
@NgModule({
declarations: [EditorialOfficeItemComponent, EditorialOfficeListComponent, VneconomyNewpaperComponent],
imports: [
CommonModule,
SwiperModule,
BrowserModule,
NgbModule,
NgbNavModule,
PackageModule,
PackagesModule,
ReactiveFormsModule,
RouterModule,
BlockUIModule.forRoot(),
FormsModule,
QuillModule.forRoot(),
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: (http: HttpClient) => { return new TranslateHttpLoader(http, '../../../assets/i18n', '.json'); },
deps: [HttpClient]
}
}),
PackageAskonomyModule,
MyPackageModule,
VneconomyModule
],
exports: [
EditorialOfficeItemComponent
]
})
export class EditorialOfficeModule {
}

View File

@ -0,0 +1,34 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from "@angular/common/http";
@Injectable({
providedIn: 'root'
})
export class EditorialOfficeService {
constructor(
private _httpClient: HttpClient,
) {
}
getDetailEditorialOffice(id: number) {
return this._httpClient.get<any>(`/editorial-office/detail/${id}`);
}
getDetailEditorialOfficeBySlug(slug: string) {
let params = new HttpParams();
params = params.set('slug', `${slug}`);
return this._httpClient.get<any>(`/editorial-office/detail`, { params });
}
getPackageOfEditorial(page: any, id: number) {
let params = new HttpParams();
if (page.pageNumber) {
params = params.append('page', `${page.pageNumber}`);
}
if (page.pageSize) {
params = params.append('pageSize', `${page.pageSize}`);
}
return this._httpClient.get<any>(`/package/in-editorial/${id}`, { params });
}
}

View File

@ -0,0 +1,64 @@
<section id="vne-tapchi" class="container mb-3">
<div class="vne-tapchi">
<a class="back-home-vne" href="/tap-chi-kinh-te-viet-nam"><i class="uil uil-home"></i> {{'VNECONOMY_HOME_PAGE' | translate}}</a>
<div class="row askonomy">
<div class="col-lg-8 col-12 col-info">
<!-- premium -->
<div class="row">
<div class="col-12 col-lg-5 text-center">
<img src="../../../../assets/templace/vneconomy/images/askonomyif.jpg" alt=""
class="img-fluid biabao">
</div>
<div class="col-12 col-lg-7 vne-boxinfo mt-3 mt-lg-0">
<h2 class="title text-center text-lg-start">Askonomy</h2>
<p class="sub-title">{{'VNECONOMY.ASKONOMY.SUMMARY1' | translate}}</p>
<p class="sub-title">{{'VNECONOMY.ASKONOMY.SUMMARY2' | translate}}</p>
<p class="sub-title">{{'VNECONOMY.ASKONOMY.FEATURES.TITLE' | translate}}</p>
<ul>
<li>{{'VNECONOMY.ASKONOMY.FEATURES.FEATURE1' | translate}}</li>
<li>{{'VNECONOMY.ASKONOMY.FEATURES.FEATURE2' | translate}}</li>
<li>{{'VNECONOMY.ASKONOMY.FEATURES.FEATURE3' | translate}}</li>
<li>{{'VNECONOMY.ASKONOMY.FEATURES.FEATURE4' | translate}}</li>
<li>{{'VNECONOMY.ASKONOMY.FEATURES.FEATURE5' | translate}}</li>
</ul>
<a href="/tap-chi-kinh-te-viet-nam/premium" style="text-decoration: none; color: #fcaf17; font-size: 14px; ">
{{'VNECONOMY.ASKONOMY.SIGN_UP_FOR_PREMIUM' | translate}}</a>
<div class="group-button text-center text-md-start">
<a name="register" id="register" class="btn btn-warning mt-2" href="/tap-chi-kinh-te-viet-nam/packages" role="button">
{{'VNECONOMY.ASKONOMY.EXPLORE_NOW' | translate}}
</a>
</div>
</div>
<!-- <div class="col-12 p-3 ms-2">
<p class="vne-cat">Catalog</p>
<ul class="vne-cats">
<li><a href="#">Business</a></li>
<li><a href="#">Finance</a></li>
</ul>
</div> -->
</div>
<!-- end premium -->
</div>
<div class="col-lg col-12 pe-md-0 mt-lg-0 mt-4 pt-3 pt-lg-0 col-pack vne-intro">
<div class="ratio ratio-16x9 video">
<iframe src="https://www.youtube.com/embed/NO8-yO7Zc90?rel=0"></iframe>
</div>
<div class="title">
<span>{{'TAP_CHI_KINH_TE_VIET_NAM' | translate}}</span>
<span>Vietnam Economic Times</span>
</div>
</div>
</div>
</div>
</section>
<app-vneconomy-package></app-vneconomy-package>
<app-vneconomy-register></app-vneconomy-register>
<app-vneconomy-news-hot></app-vneconomy-news-hot>
<app-vneconomy-comment></app-vneconomy-comment>
<app-vneconomy-contact></app-vneconomy-contact>

View File

@ -0,0 +1,76 @@
/* ------ askonomy --------- */
/* -------------------------- */
@media (min-width: 1400px) {
.vne-tapchi .askonomy .col-info {
max-width: 66%;
}
.vne-tapchi .askonomy .vne-boxinfo h2.title {
margin-top: 0px;
}
.vne-tapchi .askonomy .biabao {
height: 100%;
}
}
.vne-tapchi .askonomy h2.title {
margin-bottom: 5px;
}
.vne-tapchi .askonomy p.sub-title {
margin-bottom: 20px;
line-height: 1.6em;
text-align: justify;
}
.vne-tapchi .askonomy ul {
padding-left: 1.5rem;
}
.vne-tapchi .askonomy li {
font-size: 0.85em;
color: #777;
}
.vne-tapchi .askonomy li::marker {
color: var(--bs-warning);
font-size: 1.1rem;
}
@media (max-width: 992px) {
.vne-tapchi .askonomy p.sub-title {
font-size: 1em;
line-height: 1.3em;
}
.vne-tapchi .askonomy li {
font-size: 1em;
}
}
@media (min-width: 1400px) {
.vne-tapchi .askonomy .vne-intro .video {
margin-top: 50px;
}
}
.vne-tapchi {
background: #fff;
padding: 40px 40px 40px 40px;
}
.back-home-vne {
margin-bottom: 10px;
color: #fcaf17;
font-size: 18px;
position: sticky;
text-decoration: none;
display: flex;
align-items: center;
gap: 10px;
i{
font-size: 20px;
}
}

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-vneconomy-askonomy',
templateUrl: './vneconomy-askonomy.component.html',
styleUrls: ['./vneconomy-askonomy.component.scss']
})
export class VneconomyAskonomyComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1,46 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { VneconomyAskonomyComponent } from './vneconomy-askonomy.component';
import { SwiperModule } from 'swiper/angular';
import { BrowserModule } from '@angular/platform-browser';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { BlockUIModule } from 'ng-block-ui';
import { QuillModule } from 'ngx-quill';
import { Ng2FlatpickrModule } from 'ng2-flatpickr';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { HttpClient } from '@angular/common/http';
import { LoginModule } from 'src/app/login/login.module';
import { VneconomyModule } from '../vneconomy-home/vneconomy.module';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
@NgModule({
declarations: [
VneconomyAskonomyComponent
],
imports: [
CommonModule,
SwiperModule,
BrowserModule,
NgbModule,
ReactiveFormsModule,
RouterModule,
BlockUIModule.forRoot(),
FormsModule,
QuillModule.forRoot(),
Ng2FlatpickrModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: (http: HttpClient) => { return new TranslateHttpLoader(http, '../../../assets/i18n', '.json'); },
deps: [HttpClient]
}
}),
LoginModule,
VneconomyModule
]
})
export class VneconomyAskonomyModule { }

View File

@ -0,0 +1,9 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class VneconomyAskonomyService {
constructor() { }
}

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