├── .eslintrc.js
├── .gitignore
├── .prettierrc.js
├── .vscode
└── settings.json
├── Developer
├── About.md
├── Info.md
├── KNOWLEDGE
│ ├── Knowledge_1.md
│ ├── Knowledge_2.md
│ └── Knowledge_3.md
├── PROGRESS
│ ├── Phase 1
│ │ └── Phase-1_1.md
│ └── Phase 2
│ │ └── Phase-2_1.md
├── ProjectStructure.md
└── SpecialFeatures.md
├── Dockerfile
├── README.md
├── ___test___
└── index.test.jsx
├── __mocks__
├── fileMock.js
└── styleMock.js
├── apollo-graphql
├── ApolloProvider.jsx
├── aws-graphql
│ ├── mutations.js
│ ├── queries.js
│ ├── schema.json
│ └── subscriptions.js
├── mutation
│ └── mutation.jsx
├── query
│ └── query.jsx
└── subscription
│ └── subscription.jsx
├── assets
├── images
│ ├── 404.png
│ ├── 404@2x.png
│ ├── addCircle.svg
│ ├── banner-bg.jpg
│ ├── banner
│ │ ├── 1.jpg
│ │ ├── 2.jpg
│ │ └── 3.jpg
│ ├── bucket.svg
│ ├── dashBorder.svg
│ ├── footer-bg.png
│ ├── footer-bg.svg
│ ├── heart.svg
│ ├── hotelMapMarker.png
│ ├── login-page-bg.jpg
│ ├── logo-alt.svg
│ ├── logo-with-text.svg
│ ├── post-image-1.jpg
│ ├── post-image-2.jpg
│ ├── post-thumb-1.jpg
│ ├── post-thumb-2.jpg
│ ├── profileCoverImage.jpg
│ ├── profileImage.jpg
│ ├── share.svg
│ ├── signin.jpg
│ └── single-post-bg.jpg
└── style
│ └── Global.style.jsx
├── babel.config.js
├── components
├── AddHotel
│ ├── AddHotel.style.jsx
│ ├── RenderAmenitiesForm.jsx
│ ├── RenderLocationInputForm.jsx
│ └── RenderUploadPhotosForm.jsx
├── ChangePassword
│ └── RenderChangePasswordForm.jsx
├── ContactForm
│ ├── ContactForm.jsx
│ └── ContactForm.style.jsx
├── Cover
│ ├── Cover.jsx
│ └── Cover.module.scss
├── Footer
│ ├── Footer.jsx
│ ├── Footer.style.jsx
│ └── footer-bg.svg
├── ForgetPassword
│ ├── RenderForgetPassswordForm.jsx
│ └── RenderForgetPasswordForm.style.jsx
├── GridCard
│ ├── GridCard.jsx
│ └── GridCard.style.jsx
├── IconCard
│ ├── IconCard.jsx
│ └── IconCard.style.jsx
├── ImageCard
│ ├── ImageCard.jsx
│ └── ImageCard.style.jsx
├── Loader
│ ├── Loader.jsx
│ └── Loader.style.jsx
├── Map
│ ├── ListingPageMap.jsx
│ ├── Map.jsx
│ ├── MapAutoComplete.jsx
│ ├── MapInfoWindow.jsx
│ ├── MapLocationBox.jsx
│ ├── MapSearchBox.jsx
│ ├── MapStyle.jsx
│ ├── SinglePageMap.jsx
│ └── hotelMapMarker.png
├── Navbar
│ ├── Navbar.jsx
│ └── Navbar.style.jsx
├── Notifications
│ └── Notifications.jsx
├── PriceCard
│ ├── PriceCard.jsx
│ └── PriceCard.style.jsx
├── ProductCard
│ └── ProductCard.jsx
├── Review
│ ├── RenderReviewForm.jsx
│ ├── RenderReviewForm.style.jsx
│ ├── ReviewCreateForm.jsx
│ └── ReviewFormComponent.jsx
├── SectionGrid
│ └── SectionGrid.jsx
├── SectionTitle
│ ├── SectionTitle.jsx
│ └── SectionTitle.style.jsx
├── Seo.jsx
├── SignIn
│ ├── RenderSignInForm.jsx
│ └── SignInForm.style.jsx
├── SignUp
│ ├── RenderSignUpForm.jsx
│ └── SignUpForm.style.jsx
├── SocialShare
│ └── SocialShare.jsx
├── Special
│ ├── Special.jsx
│ └── Special.module.scss
├── Stations
│ ├── Stations.jsx
│ └── Stations.module.scss
├── Statistics
│ ├── Statistic.jsx
│ └── Statistic.module.scss
├── StickyBooking
│ ├── StickyBooking.jsx
│ ├── StickyBooking.style.jsx
│ └── useWindowSize.jsx
├── UI
│ ├── Antd
│ │ ├── AntdInputWithFormik.jsx
│ │ ├── Avatar
│ │ │ └── Avatar.jsx
│ │ ├── Button
│ │ │ └── Button.jsx
│ │ ├── Card
│ │ │ └── Card.jsx
│ │ ├── Checkbox
│ │ │ └── Checkbox.jsx
│ │ ├── Divider
│ │ │ └── Divider.jsx
│ │ ├── Drawer
│ │ │ └── Drawer.jsx
│ │ ├── Dropdown
│ │ │ └── Dropdown.jsx
│ │ ├── Grid
│ │ │ ├── Col.jsx
│ │ │ └── Row.jsx
│ │ ├── Icon
│ │ │ └── Icon.jsx
│ │ ├── Input
│ │ │ └── Input.jsx
│ │ ├── InputNumber
│ │ │ └── InputNumber.jsx
│ │ ├── Layout
│ │ │ └── Layout.jsx
│ │ ├── Menu
│ │ │ └── Menu.jsx
│ │ ├── Modal
│ │ │ └── Modal.jsx
│ │ ├── Pagination
│ │ │ └── Pagination.jsx
│ │ ├── Popover
│ │ │ └── Popover.jsx
│ │ ├── Radio
│ │ │ └── Radio.jsx
│ │ ├── Slider
│ │ │ └── Slider.jsx
│ │ ├── Table
│ │ │ └── Table.jsx
│ │ ├── Tag
│ │ │ └── Tag.jsx
│ │ └── Upload
│ │ │ └── Upload.jsx
│ ├── Base.jsx
│ ├── Box
│ │ └── Box.jsx
│ ├── Card
│ │ ├── Card.jsx
│ │ └── Card.style.jsx
│ ├── CommentCard
│ │ ├── CommentCard.jsx
│ │ └── LikeDislike.jsx
│ ├── Container
│ │ ├── Container.jsx
│ │ └── Container.style.jsx
│ ├── ContentLoader
│ │ └── ContentLoader.jsx
│ ├── DatePicker
│ │ ├── ReactDates.jsx
│ │ ├── ReactDates.style.jsx
│ │ └── SingleDatePicker.jsx
│ ├── Favorite
│ │ ├── Favorite.jsx
│ │ ├── Favorite.style.jsx
│ │ └── useToggle.jsx
│ ├── GlideCarousel
│ │ ├── GlideCarousel.jsx
│ │ └── GlideCarousel.style.jsx
│ ├── Heading
│ │ └── Heading.jsx
│ ├── HtmlLabel
│ │ └── HtmlLabel.jsx
│ ├── Image
│ │ └── Image.jsx
│ ├── ImageUploader
│ │ ├── DragAndDropUploader.jsx
│ │ ├── ImageUploader.jsx
│ │ └── ImageUploader.style.jsx
│ ├── InputIncDec
│ │ ├── InputIncDec.jsx
│ │ └── InputIncDec.style.jsx
│ ├── Logo
│ │ ├── Logo.jsx
│ │ └── Logo.style.jsx
│ ├── Portal
│ │ └── Portal.jsx
│ ├── Rating
│ │ └── Rating.jsx
│ ├── ScrollBar
│ │ ├── ScrollBar.jsx
│ │ └── ScrollBar.style.jsx
│ ├── Steppers
│ │ └── FormStepper.jsx
│ ├── Text
│ │ └── Text.jsx
│ ├── Title
│ │ ├── Title.jsx
│ │ └── Title.style.jsx
│ ├── Toolbar
│ │ ├── Toolbar.jsx
│ │ └── Toolbar.style.jsx
│ └── ViewWithPopup
│ │ ├── ViewWithPopup.jsx
│ │ ├── ViewWithPopup.style.jsx
│ │ └── useOnClickOutside.jsx
└── User
│ └── RenderCreateOrUpdateForm.jsx
├── container
├── 404
│ ├── 404.jsx
│ └── 404.style.jsx
├── ForgetPassword
│ ├── ForgetPassword.style.jsx
│ └── ForgetPasswordForm.jsx
├── Home
│ ├── Location
│ │ ├── Location.jsx
│ │ └── Locations.style.jsx
│ └── Search
│ │ ├── Search.jsx
│ │ ├── Search.style.jsx
│ │ └── SearchForm.jsx
├── LanguageSwitcher
│ ├── LanguageSwitcher.jsx
│ └── config.jsx
├── Layout
│ ├── Footer
│ │ ├── Footer.jsx
│ │ └── FooterMenu.jsx
│ ├── Header
│ │ ├── AuthMenu.jsx
│ │ ├── AvatarImg.jsx
│ │ ├── Header.jsx
│ │ ├── Header.style.jsx
│ │ ├── MainMenu.jsx
│ │ ├── MobileMenu.jsx
│ │ ├── NavbarSearch.jsx
│ │ └── ProfileMenu.jsx
│ ├── Layout.jsx
│ ├── RightBar.jsx
│ ├── RightBar.style.jsx
│ └── bucket.svg
├── Listing
│ ├── Listing.style.jsx
│ ├── ListingMap.jsx
│ └── Search
│ │ ├── CategorySearch
│ │ ├── CategorySearch.jsx
│ │ └── CategorySearch.style.jsx
│ │ ├── MobileSearchView.jsx
│ │ ├── MobileSearchView.style.jsx
│ │ └── SearchParams.jsx
├── Payment
│ ├── BillingForm.jsx
│ ├── InputBox.jsx
│ ├── OffersTable.jsx
│ ├── OrderInfo.jsx
│ └── Payment.style.jsx
├── Pricing
│ ├── Pricing.style.jsx
│ └── PricingItems.jsx
├── SignIn
│ ├── SignIn.style.jsx
│ └── SignInForm.jsx
├── SignUp
│ ├── SignUp.style.jsx
│ └── SignUpForm.jsx
├── SinglePage
│ ├── Amenities
│ │ ├── Amenities.jsx
│ │ └── Amenities.style.jsx
│ ├── Description
│ │ ├── Description.jsx
│ │ └── Description.style.jsx
│ ├── ImageGallery
│ │ ├── ImageGallery.jsx
│ │ └── ImageGallery.style.jsx
│ ├── Location
│ │ ├── Location.jsx
│ │ └── Location.style.jsx
│ ├── Reservation
│ │ ├── BottomReservation.jsx
│ │ ├── OffersTable.jsx
│ │ ├── RenderReservationForm.jsx
│ │ ├── Reservation.jsx
│ │ └── Reservation.style.jsx
│ ├── Review
│ │ ├── Review.jsx
│ │ └── Review.style.jsx
│ ├── SinglePageView.style.jsx
│ └── TopBar
│ │ └── TopBar.jsx
├── User
│ ├── AccountDetails
│ │ ├── Chart
│ │ │ ├── Pie
│ │ │ │ ├── Pie.jsx
│ │ │ │ └── PieConfig.jsx
│ │ │ └── Radar
│ │ │ │ ├── Radar.jsx
│ │ │ │ └── RadarConfig.jsx
│ │ ├── Table
│ │ │ ├── CouponTable.jsx
│ │ │ └── TransactionTable.jsx
│ │ ├── UserBalance.jsx
│ │ ├── UserContact.jsx
│ │ ├── UserDetails.style.jsx
│ │ ├── UserDetailsPage.jsx
│ │ ├── UserFavoriteItemLists.jsx
│ │ └── UserItemLists.jsx
│ ├── AccountSettings
│ │ ├── ChangePasswordForm.jsx
│ │ ├── UserAccountSettings.style.jsx
│ │ ├── UserAccountSettingsPage.jsx
│ │ ├── UserCreateOrUpdateForm.jsx
│ │ └── UserPictureChangeForm.jsx
│ └── index.jsx
└── blankPage.jsx
├── context
├── AuthProvider.jsx
├── LanguageProvider.jsx
├── LayoutProvider.jsx
├── ReviewProvider.jsx
└── SearchProvider.jsx
├── i18n.js
├── jest.config.js
├── jest.setup.js
├── jsConfig.json
├── library
├── helpers
│ ├── activeLink.jsx
│ ├── get_api_data.jsx
│ ├── get_device_type.jsx
│ ├── gtag.jsx
│ ├── i18n.jsx
│ ├── redirect.jsx
│ ├── restriction.jsx
│ ├── rtl.jsx
│ ├── session.jsx
│ ├── url_handler.jsx
│ └── validators
│ │ └── fieldFormats.jsx
└── hooks
│ ├── useLocation.jsx
│ ├── useOnClickOutside.jsx
│ └── useWindowSize.jsx
├── next.config.js
├── package.json
├── pages
├── _app.jsx
├── _document.jsx
├── _error.jsx
├── account-settings.jsx
├── add-hotel.jsx
├── auth-processing.jsx
├── change-password.jsx
├── forget-password.jsx
├── index.jsx
├── invoices
│ └── [...txid].jsx
├── listing.jsx
├── login.jsx
├── payment.jsx
├── post
│ └── [...slug].jsx
├── pricing-plan.jsx
├── privacy.jsx
├── processing.jsx
├── profile.jsx
├── registration.jsx
└── search.jsx
├── public
├── favicon.ico
├── static
│ ├── data
│ │ ├── hotel-single.json
│ │ ├── hotel.json
│ │ ├── location.json
│ │ ├── top-hotel.json
│ │ └── user.json
│ ├── flag
│ │ ├── china.svg
│ │ ├── cn.svg
│ │ ├── france.svg
│ │ ├── italy.svg
│ │ ├── saudi-arabia.svg
│ │ ├── spain.svg
│ │ └── uk.svg
│ ├── images
│ │ ├── 404.png
│ │ ├── 404@2x.png
│ │ ├── addCircle.svg
│ │ ├── banner-bg.jpg
│ │ ├── bucket.svg
│ │ ├── dashBorder.svg
│ │ ├── favicon.png
│ │ ├── footer-bg.png
│ │ ├── footer-bg.svg
│ │ ├── heart.svg
│ │ ├── hotel-img.jpeg
│ │ ├── hotelMapMarker.png
│ │ ├── logo-alt.svg
│ │ ├── logo-with-text.svg
│ │ ├── map-loader.gif
│ │ ├── post-image-1.jpg
│ │ ├── post-image-2.jpg
│ │ ├── post-thumb-1.jpg
│ │ ├── post-thumb-2.jpg
│ │ ├── profileCoverImage.jpg
│ │ ├── profileImage.jpg
│ │ ├── share.svg
│ │ ├── signin.jpg
│ │ └── single-post-bg.jpg
│ └── locales
│ │ ├── en
│ │ └── common.json
│ │ └── vi
│ │ └── common.json
└── zeit.svg
├── settings
├── config.js
├── constants.js
└── setup.js
├── style.css
├── themes
└── default.theme.js
├── translations
├── config.js
├── conversion
│ ├── index.js
│ └── raw
│ │ ├── arab.js
│ │ ├── chinese.js
│ │ ├── eng.js
│ │ ├── fr.js
│ │ ├── ital.js
│ │ └── span.js
├── entries
│ ├── ar_SA.js
│ ├── en-US.js
│ ├── es_ES.js
│ ├── fr_FR.js
│ ├── it_IT.js
│ └── zh-Hans-CN.js
├── index.js
└── locales
│ ├── ar_SA.json
│ ├── en_US.json
│ ├── es_ES.json
│ ├── fr_FR.json
│ ├── it_IT.json
│ └── zh-Hans.json
└── yarn.lock
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "env": {
3 | "browser": true,
4 | "es6": true
5 | },
6 | "extends": [
7 | "plugin:react/recommended",
8 | "airbnb"
9 | ],
10 | "globals": {
11 | "Atomics": "readonly",
12 | "SharedArrayBuffer": "readonly",
13 | "React": "writable"
14 | },
15 | //config cho Absolute import
16 | "settings": {
17 | "import/resolver": {
18 | "node": {
19 | "paths": ["./"]
20 | }
21 | },
22 | },
23 | "parser": 'babel-eslint',
24 | "parserOptions": {
25 | "ecmaFeatures": {
26 | "jsx": true
27 | },
28 | "ecmaVersion": 2020,
29 | "sourceType": "module",
30 | "allowImportExportEverywhere": true
31 | },
32 | "plugins": [
33 | "react"
34 | ],
35 | "rules": {
36 | "jsx-a11y/anchor-is-valid": "off",
37 | "react/react-in-jsx-scope": "off",
38 | "react/prop-types": 0,
39 | "react/jsx-props-no-spreading": "off",
40 | "react/require-default-props": 0,
41 | "linebreak-style": 0,
42 | "import/order": "off",
43 | "import/prefer-default-export": "off",
44 | "import/no-extraneous-dependencies": ["error", { "devDependencies": true, "optionalDependencies": false, "peerDependencies": false }],
45 | "react/default-props-match-prop-types":"off",
46 | "react/forbid-prop-types":"off",
47 | "no-plusplus":"off"
48 | }
49 | };
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 |
21 | # debug
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
26 | # local env files
27 | .env.local
28 | .env.development.local
29 | .env.test.local
30 | .env.production.local
31 | mock.txt
32 |
33 | #amplify
34 | amplify/\#current-cloud-backend
35 | amplify/.config/local-*
36 | amplify/mock-data
37 | amplify/backend/amplify-meta.json
38 | amplify/backend/awscloudformation
39 | build/
40 | dist/
41 | node_modules/
42 | aws-exports.js
43 | awsconfiguration.json
44 | amplifyconfiguration.json
45 | amplify-build-config.json
46 | amplify-gradle-config.json
47 | amplifytools.xcconfig
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | useTabs: false,
3 | printWidth: 80,
4 | singleQuote: true,
5 | trailingComma: 'es5',
6 | jsxBracketSameLine: true,
7 | noSemi: false
8 | };
9 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "prettier.packageManager": "yarn",
3 | "eslint.packageManager": "yarn",
4 | "eslint.run": "onSave",
5 | "eslint.format.enable": true
6 | }
--------------------------------------------------------------------------------
/Developer/About.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/Developer/About.md
--------------------------------------------------------------------------------
/Developer/Info.md:
--------------------------------------------------------------------------------
1 | ## [Từ tác giả](https://github.com/php1301/DoAnReactJS/edit/master/PROGRESS/Info.md)
2 | UI của dự án solo làm từ đầu đến cuối và nhiều assignments vào giai đoạn làm dự án...Khá là mệt nhưng cũng rất tâm huyết cho nó vì đây là 1 dự án đánh dấu bước chuyển mình của mình
3 | ***
4 | Everything will be fulfilled
5 |
--------------------------------------------------------------------------------
/Developer/PROGRESS/Phase 1/Phase-1_1.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/Developer/PROGRESS/Phase 1/Phase-1_1.md
--------------------------------------------------------------------------------
/Developer/PROGRESS/Phase 2/Phase-2_1.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/Developer/PROGRESS/Phase 2/Phase-2_1.md
--------------------------------------------------------------------------------
/Developer/SpecialFeatures.md:
--------------------------------------------------------------------------------
1 | ## [Tính năng nổi bật](https://github.com/php1301/vexere-ui/blob/master/Developer/SpecialFeatures.md)
2 | + ReactJS with SSR using NEXTJS
3 | + Responsive bắt mắt
4 | + Styled-Components
5 | + Thiết kế Medium-Styles
6 | + Validation
7 | + Có demo test bằng Jest/Enzyme và react/tesing
8 | + Context API
9 | + Sử dụng API nhà làm - tự thiết kế backend
10 | + Tích hợp Social Login + SSO dễ dàng đăng nhập
11 | + Update thường xuyên
12 | + SEO with SSR
13 | + Strict với Folder Structure, Linter
14 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | #base image
2 | FROM node
3 |
4 | # set working directory
5 | RUN mkdir /usr/src/app
6 | #copy all files from current directory to docker
7 | COPY . /usr/src/app
8 |
9 | WORKDIR /usr/src/app
10 |
11 | # add `/usr/src/app/node_modules/.bin` to $PATH
12 | ENV PATH /usr/src/app/node_modules/.bin:$PATH
13 |
14 | # install and cache app dependencies
15 | RUN yarn
16 |
17 | # start app
18 | CMD ["npm", "start"]
--------------------------------------------------------------------------------
/___test___/index.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { mount } from 'enzyme';
3 | import Index from 'pages/index';
4 | // instance của index page
5 | describe('index page', () => {
6 | // it('mô tả sự kiện test')... expect(logic)
7 | it('should have App component', () => {
8 | const subject = mount();
9 |
10 | expect(subject.find('App')).toHaveLength(1);
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/__mocks__/fileMock.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/__mocks__/fileMock.js
--------------------------------------------------------------------------------
/__mocks__/styleMock.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/__mocks__/styleMock.js
--------------------------------------------------------------------------------
/apollo-graphql/aws-graphql/mutations.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | // this is an auto generated file. This will be overwritten
3 |
4 | export const createTodo = /* GraphQL */ `
5 | mutation CreateTodo(
6 | $input: CreateTodoInput!
7 | $condition: ModelTodoConditionInput
8 | ) {
9 | createTodo(input: $input, condition: $condition) {
10 | id
11 | name
12 | description
13 | createdAt
14 | updatedAt
15 | }
16 | }
17 | `;
18 | export const updateTodo = /* GraphQL */ `
19 | mutation UpdateTodo(
20 | $input: UpdateTodoInput!
21 | $condition: ModelTodoConditionInput
22 | ) {
23 | updateTodo(input: $input, condition: $condition) {
24 | id
25 | name
26 | description
27 | createdAt
28 | updatedAt
29 | }
30 | }
31 | `;
32 | export const deleteTodo = /* GraphQL */ `
33 | mutation DeleteTodo(
34 | $input: DeleteTodoInput!
35 | $condition: ModelTodoConditionInput
36 | ) {
37 | deleteTodo(input: $input, condition: $condition) {
38 | id
39 | name
40 | description
41 | createdAt
42 | updatedAt
43 | }
44 | }
45 | `;
46 |
--------------------------------------------------------------------------------
/apollo-graphql/aws-graphql/queries.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | // this is an auto generated file. This will be overwritten
3 |
4 | export const getTodo = /* GraphQL */ `
5 | query GetTodo($id: ID!) {
6 | getTodo(id: $id) {
7 | id
8 | name
9 | description
10 | createdAt
11 | updatedAt
12 | }
13 | }
14 | `;
15 | export const listTodos = /* GraphQL */ `
16 | query ListTodos(
17 | $filter: ModelTodoFilterInput
18 | $limit: Int
19 | $nextToken: String
20 | ) {
21 | listTodos(filter: $filter, limit: $limit, nextToken: $nextToken) {
22 | items {
23 | id
24 | name
25 | description
26 | createdAt
27 | updatedAt
28 | }
29 | nextToken
30 | }
31 | }
32 | `;
33 |
--------------------------------------------------------------------------------
/apollo-graphql/aws-graphql/subscriptions.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | // this is an auto generated file. This will be overwritten
3 |
4 | export const onCreateTodo = /* GraphQL */ `
5 | subscription OnCreateTodo {
6 | onCreateTodo {
7 | id
8 | name
9 | description
10 | createdAt
11 | updatedAt
12 | }
13 | }
14 | `;
15 | export const onUpdateTodo = /* GraphQL */ `
16 | subscription OnUpdateTodo {
17 | onUpdateTodo {
18 | id
19 | name
20 | description
21 | createdAt
22 | updatedAt
23 | }
24 | }
25 | `;
26 | export const onDeleteTodo = /* GraphQL */ `
27 | subscription OnDeleteTodo {
28 | onDeleteTodo {
29 | id
30 | name
31 | description
32 | createdAt
33 | updatedAt
34 | }
35 | }
36 | `;
37 |
--------------------------------------------------------------------------------
/apollo-graphql/subscription/subscription.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-extraneous-dependencies */
2 | import gql from 'graphql-tag';
3 |
4 | export const UNREAD_NOTIFICATION = gql`
5 | subscription UnreadNotification($channelId: String){
6 | unreadNotification(channelId: $channelId){
7 | unreadNotification
8 | }
9 | }
10 | `;
11 | export const NOTIFICATION_BELL = gql`
12 | subscription NotificationBell($channelId: String){
13 | notificationBell(channelId: $channelId) {
14 | peopleReviewedQuantity
15 | reviewAuthorName
16 | query
17 | reviewTitle
18 | read
19 | reviewText
20 | reviewedHotelName
21 | reviewAuthorProfilePic
22 | }
23 | }
24 | `;
25 | export const REALTIME_REVIEWS = gql`
26 | subscription RealtimeReviews($hotelId: String){
27 | realtimeReviews(hotelId: $hotelId){
28 | reviewID
29 | reviewTitle
30 | reviewText
31 | sortOfTrip
32 | reviewAuthorId{
33 | id
34 | }
35 | reviewAuthorFirstName
36 | reviewAuthorLastName
37 | reviewAuthorEmail
38 | reviewOverall
39 | reviewAuthorPic
40 | reviewedHotelId
41 | reviewTips
42 | reviewPics{
43 | url
44 | }
45 | reviewDate
46 | reviewOptional{
47 | option
48 | optionField
49 | }
50 | reviewFields{
51 | rating
52 | ratingFieldName
53 | }
54 | }
55 | }
56 | `;
57 | export const REALTIME_LIKE_DISLIKE = gql`
58 | subscription RealtimeLikeDislike($reviewID: String){
59 | realtimeLikeDislike(reviewID: $reviewID){
60 | reviewID
61 | peopleLiked{
62 | id
63 | }
64 | peopleDisliked{
65 | id
66 | }
67 | }
68 | }
69 | `;
70 | export const REALTIME_NOTIFICATION_TRANSACTION = gql`
71 | subscription RealtimeNotificationTransaction($userId: String){
72 | realtimeNotificationTransaction(userId: $userId){
73 | TXID
74 | transactionPrice
75 | }
76 | }
77 | `;
78 |
--------------------------------------------------------------------------------
/assets/images/404.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/assets/images/404.png
--------------------------------------------------------------------------------
/assets/images/404@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/assets/images/404@2x.png
--------------------------------------------------------------------------------
/assets/images/addCircle.svg:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/assets/images/banner-bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/assets/images/banner-bg.jpg
--------------------------------------------------------------------------------
/assets/images/banner/1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/assets/images/banner/1.jpg
--------------------------------------------------------------------------------
/assets/images/banner/2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/assets/images/banner/2.jpg
--------------------------------------------------------------------------------
/assets/images/banner/3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/assets/images/banner/3.jpg
--------------------------------------------------------------------------------
/assets/images/dashBorder.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/assets/images/footer-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/assets/images/footer-bg.png
--------------------------------------------------------------------------------
/assets/images/heart.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/assets/images/hotelMapMarker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/assets/images/hotelMapMarker.png
--------------------------------------------------------------------------------
/assets/images/login-page-bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/assets/images/login-page-bg.jpg
--------------------------------------------------------------------------------
/assets/images/logo-alt.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/assets/images/logo-with-text.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/assets/images/post-image-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/assets/images/post-image-1.jpg
--------------------------------------------------------------------------------
/assets/images/post-image-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/assets/images/post-image-2.jpg
--------------------------------------------------------------------------------
/assets/images/post-thumb-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/assets/images/post-thumb-1.jpg
--------------------------------------------------------------------------------
/assets/images/post-thumb-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/assets/images/post-thumb-2.jpg
--------------------------------------------------------------------------------
/assets/images/profileCoverImage.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/assets/images/profileCoverImage.jpg
--------------------------------------------------------------------------------
/assets/images/profileImage.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/assets/images/profileImage.jpg
--------------------------------------------------------------------------------
/assets/images/share.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/assets/images/signin.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/assets/images/signin.jpg
--------------------------------------------------------------------------------
/assets/images/single-post-bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/assets/images/single-post-bg.jpg
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function (api) {
2 | api.cache(true);
3 |
4 | const presets = ['next/babel'];
5 | const plugins = [
6 | ['styled-components', { ssr: true }],
7 | ['import', { libraryName: 'antd', style: 'css' }],
8 | ];
9 |
10 | return {
11 | presets,
12 | plugins,
13 | };
14 | };
15 |
--------------------------------------------------------------------------------
/components/AddHotel/RenderLocationInputForm.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Map from '../Map/Map';
3 | import MapWithSearchBox from '../Map/MapSearchBox';
4 | import MapLocationBox, { MapDataHelper } from '../Map/MapLocationBox';
5 |
6 | // field: { name, value, onChange, onBlur }
7 | // form: các values, setXXXX, handleXXXX, dirty - thuộc tính formik, isValid, status, etc,
8 | export const FormMapComponent = ({ field, form, ...props }) => {
9 | let tempFormData = [];
10 | const [formLocationState, setFormLocationState] = useState([]);
11 | return (
12 |
25 | );
26 | };
27 |
--------------------------------------------------------------------------------
/components/AddHotel/RenderUploadPhotosForm.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 | import React from 'react';
3 | import DragAndDropUploader from 'components/UI/ImageUploader/DragAndDropUploader';
4 |
5 | export const PhotoUploadComponent = ({
6 | field, // { name, value, onChange, onBlur }
7 | form, // các values, setXXXX, handleXXXX, dirty-formik, isValid, status, etc,
8 | ...props
9 | }) => {
10 | const onChange = (value) => {
11 | form.setFieldValue(field.name, value);
12 | };
13 | return (
14 |
19 | );
20 | };
21 |
--------------------------------------------------------------------------------
/components/ChangePassword/RenderChangePasswordForm.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Field, Form } from 'formik';
3 | import { AntInput } from 'components/UI/Antd/AntdInputWithFormik';
4 | import Row from 'components/UI/Antd/Grid/Row';
5 | import Col from 'components/UI/Antd/Grid/Col';
6 | import Button from 'components/UI/Antd/Button/Button';
7 |
8 | const RenderChangePasswordForm = (props) => {
9 | const { values, submitCount, handleSubmit } = props;
10 | return (
11 |
56 | );
57 | };
58 |
59 | export default RenderChangePasswordForm;
60 |
--------------------------------------------------------------------------------
/components/Cover/Cover.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Layout } from 'antd';
3 | import cover from './Cover.module.scss';
4 |
5 | const { Content } = Layout;
6 |
7 | export default function Cover() {
8 | return (
9 |
10 |
11 |

12 |
13 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/components/Cover/Cover.module.scss:
--------------------------------------------------------------------------------
1 | // .siteLayoutContent {
2 | // width: 100%;
3 | // height: calc(100vh - 60px);
4 | // position: relative;
5 | // display: flex;
6 | // -webkit-box-align: center;
7 | // align-items: center;
8 | // -webkit-box-pack: center;
9 | // justify-content: center;
10 | // z-index: 1;
11 | // transition: all 200ms ease 0s;
12 | // padding: 24px;
13 | // min-height: 280px;
14 |
15 | // .coverImage {
16 | // position: absolute;
17 | // width: 100%;
18 | // height: 100%;
19 | // object-fit: cover;
20 | // top: 0px;
21 | // left: 0px;
22 | // bottom: 0px;
23 | // right: 0px;
24 | // }
25 | // }
26 |
--------------------------------------------------------------------------------
/components/Footer/Footer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import FooterWrapper, {
4 | MenuWrapper,
5 | CopyrightArea,
6 | SecondaryFooter,
7 | } from './Footer.style';
8 |
9 | const Footer = ({
10 | logo, menu, bgSrc, copyright, className, path,
11 | }) => (
12 | <>
13 |
18 | {!!path && }
19 | >
20 | );
21 |
22 | Footer.propTypes = {
23 | className: PropTypes.string,
24 | logo: PropTypes.element,
25 | menu: PropTypes.element,
26 | bgSrc: PropTypes.string,
27 | copyright: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
28 | };
29 |
30 | export default Footer;
31 |
--------------------------------------------------------------------------------
/components/ForgetPassword/RenderForgetPassswordForm.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Field, Form } from 'formik';
3 | import { MdLockOpen } from 'react-icons/md';
4 | import { AntInput } from 'components/UI/Antd/AntdInputWithFormik';
5 | import Button from 'components/UI/Antd/Button/Button';
6 |
7 | import FormWrapper from './RenderForgetPasswordForm.style';
8 |
9 | const RenderForgetPassWordForm = (props) => {
10 | const { values, submitCount, handleSubmit } = props;
11 | return (
12 |
13 |
35 |
36 | );
37 | };
38 |
39 | export default RenderForgetPassWordForm;
40 |
--------------------------------------------------------------------------------
/components/ForgetPassword/RenderForgetPasswordForm.style.jsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { themeGet } from '@styled-system/theme-get';
3 |
4 | const FormWrapper = styled.div`
5 | .field-container {
6 | .ant-form-item {
7 | .ant-form-item-control-wrapper {
8 | .ant-form-item-control-input {
9 | &:focus {
10 | box-shadow: none;
11 | }
12 | }
13 | .ant-form-item-explain {
14 | margin: 5px 0 10px;
15 | }
16 | }
17 | }
18 | }
19 | }
20 | `;
21 |
22 | export const FieldWrapper = styled.div`
23 | display: flex;
24 | justify-content: space-between;
25 | align-items: center;
26 | margin-top: 41px;
27 | margin-bottom: 41px;
28 |
29 | > a {
30 | color: ${themeGet('primary.0', '#008489')};
31 | font-size: 15px;
32 | font-weight: 700;
33 | line-height: 1;
34 | &:hover,
35 | &:focus {
36 | outline: none;
37 | text-decoration: none;
38 | }
39 | }
40 | `;
41 |
42 | export const SwitchWrapper = styled.div`
43 | display: flex;
44 | align-items: center;
45 |
46 | .ant-switch {
47 | min-width: 36px;
48 | height: 21px;
49 | margin-right: 10px;
50 | &.ant-switch-checked {
51 | background-color: ${themeGet('primary.0', '#008489')};
52 | }
53 | &::after {
54 | width: 17px;
55 | height: 17px;
56 | }
57 | &:focus {
58 | box-shadow: none;
59 | }
60 | .ant-click-animating-node {
61 | display: none;
62 | }
63 | }
64 | `;
65 |
66 | export const Label = styled.span`
67 | font-size: 15px;
68 | font-weight: 700;
69 | line-height: 1;
70 | color: ${themeGet('text.0', '#2C2C2C')};
71 | `;
72 |
73 | export default FormWrapper;
74 |
--------------------------------------------------------------------------------
/components/GridCard/GridCard.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import GridCardWrapper, {
4 | ImageWrapper,
5 | FavoriteIcon,
6 | ContentWrapper,
7 | LocationArea,
8 | TitleArea,
9 | PriceArea,
10 | RatingArea,
11 | MetaWrapper,
12 | ButtonGroup,
13 | } from './GridCard.style';
14 |
15 | const GridCard = ({
16 | className,
17 | favorite,
18 | location,
19 | title,
20 | price,
21 | rating,
22 | editBtn,
23 | viewDetailsBtn,
24 | children,
25 | }) => {
26 | const classes = viewDetailsBtn || editBtn ? `has_btn ${className}` : className;
27 | return (
28 |
29 | {children}
30 |
31 | {location && {location}}
32 | {title && {title}}
33 |
34 | {price && {price}}
35 | {rating && {rating}}
36 | {viewDetailsBtn || editBtn ? (
37 |
38 | {viewDetailsBtn}
39 | {editBtn}
40 |
41 | ) : null}
42 |
43 |
44 |
45 | {favorite && {favorite}}
46 |
47 | );
48 | };
49 |
50 | GridCard.propTypes = {
51 | className: PropTypes.string,
52 | title: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
53 | price: PropTypes.string,
54 | rating: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
55 | location: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
56 | editBtn: PropTypes.element,
57 | viewDetailsBtn: PropTypes.element,
58 | };
59 |
60 | export default GridCard;
61 |
--------------------------------------------------------------------------------
/components/IconCard/IconCard.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import IconCardWrapper, { IconWrapper, TitleArea } from './IconCard.style';
4 |
5 | const IconCard = ({
6 | icon, title, align, className,
7 | }) => {
8 | // Add all classs to an array
9 | const addAllClasses = ['icon_card'];
10 |
11 | // className prop checking
12 | if (className) {
13 | addAllClasses.push(className);
14 | }
15 |
16 | return (
17 |
18 | {icon && {icon}}
19 | {title && {title}}
20 |
21 | );
22 | };
23 |
24 | IconCard.propTypes = {
25 | className: PropTypes.string,
26 | title: PropTypes.string,
27 | icon: PropTypes.element,
28 | };
29 |
30 | export default IconCard;
31 |
--------------------------------------------------------------------------------
/components/IconCard/IconCard.style.jsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { themeGet } from '@styled-system/theme-get';
3 |
4 | const IconCardWrapper = styled.div`
5 | border-radius: 4px;
6 | padding: 38px 36px 30px;
7 | background-color: ${themeGet('color.2', '#F7F7F7')};
8 | margin-bottom: 20px;
9 | text-align: ${(props) => (props.align ? props.align : 'center')};
10 |
11 | @media only screen and (max-width: 480px) {
12 | padding: 28px 20px 25px;
13 | }
14 | `;
15 |
16 | export const IconWrapper = styled.div`
17 | svg {
18 | width: 80px;
19 | height: auto;
20 | fill: ${themeGet('primary.0', '#008489')};
21 |
22 | @media only screen and (max-width: 767px) {
23 | width: 50px;
24 | }
25 | }
26 | `;
27 |
28 | export const TitleArea = styled.div`
29 | color: ${themeGet('text.0', '#2C2C2C')};
30 | font-size: 15px;
31 | font-weight: 400;
32 | margin-top: 10px;
33 | `;
34 |
35 | export default IconCardWrapper;
36 |
--------------------------------------------------------------------------------
/components/ImageCard/ImageCard.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import Link from 'next/link';
4 | import ImageCardWrapper, {
5 | ContentWrapper,
6 | Title,
7 | Meta,
8 | } from './ImageCard.style';
9 |
10 | const ImageCardNext = ({
11 | className, imageSrc, title, link, meta,
12 | }) => {
13 | const addAllClasses = ['image_card'];
14 | const pathLink = title.replace(/\s/g, '-');
15 |
16 | if (className) {
17 | addAllClasses.push(className);
18 | }
19 |
20 | return (
21 |
22 |
28 |
29 |
35 |
36 | {title && {title}}
37 |
38 | {meta && {meta}}
39 |
40 |
41 |
42 |
43 | );
44 | };
45 |
46 | ImageCardNext.propTypes = {
47 | className: PropTypes.string,
48 | imageSrc: PropTypes.string,
49 | title: PropTypes.string,
50 | link: PropTypes.string,
51 | meta: PropTypes.string,
52 | };
53 |
54 | ImageCardNext.defaultProps = {
55 | link: '#',
56 | };
57 |
58 | export default ImageCardNext;
59 |
--------------------------------------------------------------------------------
/components/ImageCard/ImageCard.style.jsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { themeGet } from '@styled-system/theme-get';
3 |
4 | const ImageCardWrapper = styled.div`
5 | > a {
6 | display: block;
7 | height: 370px;
8 | position: relative;
9 | border-radius: 6px;
10 | overflow: hidden;
11 |
12 | > img {
13 | width: 100%;
14 | height: 100%;
15 | object-fit: cover;
16 | position: absolute;
17 | left: 0;
18 | top: 0;
19 | }
20 | }
21 | `;
22 |
23 | export const ContentWrapper = styled.div`
24 | position: absolute;
25 | width: 100%;
26 | height: 100%;
27 | top: 0;
28 | left: 0;
29 | display: flex;
30 | flex-direction: column;
31 | justify-content: center;
32 | align-items: center;
33 | background-color: rgba(0, 0, 0, 0.4);
34 | z-index: 2;
35 | `;
36 |
37 | export const Title = styled.h1`
38 | color: ${themeGet('color.1', '#ffffff')};
39 | font-size: 25px;
40 | line-height: 30px;
41 | font-weight: 700;
42 | text-transform: capitalize;
43 | text-align: center;
44 | text-shadow: 0 2px 2px rgba(0, 0, 0, 0.25);
45 | @media only screen and (max-width: 480px) {
46 | font-size: 20px;
47 | line-height: 28px;
48 | }
49 | `;
50 |
51 | export const Meta = styled.div`
52 | color: ${themeGet('color.1', '#ffffff')};
53 | font-size: 17px;
54 | line-height: 21px;
55 | font-weight: 400;
56 | text-transform: capitalize;
57 | text-shadow: 0 2px 2px rgba(0, 0, 0, 0.25);
58 | @media only screen and (max-width: 480px) {
59 | font-size: 15px;
60 | }
61 | `;
62 |
63 | export default ImageCardWrapper;
64 |
--------------------------------------------------------------------------------
/components/Loader/Loader.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import LoaderWrapper from './Loader.style';
4 |
5 | const Loader = ({ fill, className }) => {
6 | // default className
7 | const addAllClasses = ['loader'];
8 |
9 | // add new class using className prop
10 | if (className) {
11 | addAllClasses.push(className);
12 | }
13 |
14 | return (
15 |
16 |
45 |
46 | );
47 | };
48 |
49 | Loader.propTypes = {
50 | /** ClassName of the Loader component. */
51 | className: PropTypes.string,
52 |
53 | /** Add Loader color using fill prop. */
54 | fill: PropTypes.string,
55 | };
56 |
57 | Loader.defaultProps = {
58 | fill: '#008489',
59 | };
60 |
61 | export default Loader;
62 |
--------------------------------------------------------------------------------
/components/Loader/Loader.style.jsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const LoaderWrapper = styled.div`
4 | width: 100%;
5 | display: flex;
6 | align-items: center;
7 | justify-content: center;
8 | svg {
9 | width: 52px;
10 | height: 100px;
11 | display: inline-block;
12 | }
13 | `;
14 |
15 | export default LoaderWrapper;
16 |
--------------------------------------------------------------------------------
/components/Map/ListingPageMap.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Marker } from 'react-google-maps';
3 | import HotelInfoWindow from './MapInfoWindow';
4 | import MakerImage from './hotelMapMarker.png';
5 | // Render cụm marker được listed từ props data truyền vô - render khi toggle isOpen
6 | const HotelMapMarkerCluster = (props) => {
7 | const locationArray = [];
8 | const {
9 | location, infoWindowToggle, isOpen, markerIndex,
10 | } = props;
11 | // Truyền props location từ việc click vô (trả data map tương ứng)
12 | if (location && location.length !== 0) {
13 | for (let i = 0; i < location.length; i++) {
14 | const { id } = location[i];
15 | const lat = parseFloat(location[i].location[0].lat);
16 | const lng = parseFloat(location[i].location[0].lng);
17 | const { title } = location[i];
18 | const thumbUrl = location[i].image.thumb_url;
19 | const { formattedAddress } = location[i].location[0];
20 | const { price } = location[i];
21 | const { rating } = location[i];
22 | const { ratingCount } = location[i];
23 |
24 | locationArray.push({
25 | id,
26 | lat,
27 | lng,
28 | title,
29 | thumbUrl,
30 | formattedAddress,
31 | price,
32 | rating,
33 | ratingCount,
34 | });
35 | }
36 | }
37 | return locationArray.map((singlePostLocation) => (
38 | {
43 | infoWindowToggle(singlePostLocation.id);
44 | }}
45 | >
46 | {isOpen && markerIndex === singlePostLocation.id ? (
47 | {
50 | infoWindowToggle(singlePostLocation.id);
51 | }}
52 | />
53 | ) : (
54 | ''
55 | )}
56 |
57 | ));
58 | };
59 |
60 | export default HotelMapMarkerCluster;
61 |
--------------------------------------------------------------------------------
/components/Map/MapInfoWindow.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { InfoWindow } from 'react-google-maps';
3 | import Rating from 'components/UI/Rating/Rating';
4 | import GridCard from '../GridCard/GridCard';
5 |
6 | const HotelInfoWindow = (props) => {
7 | const { postData } = props;
8 |
9 | const id = postData && postData.id;
10 | const title = postData && postData.title;
11 | const thumbUrl = postData && postData.thumbUrl;
12 | const formattedAddress = postData && postData.formattedAddress;
13 | const price = postData && postData.price;
14 | const rating = postData && postData.rating;
15 | const ratingCount = postData && postData.ratingCount;
16 |
17 | return (
18 |
19 |
26 | }
27 | >
28 |
29 |
30 |
31 | );
32 | };
33 |
34 | export default HotelInfoWindow;
35 |
--------------------------------------------------------------------------------
/components/Map/SinglePageMap.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Marker } from 'react-google-maps';
3 | import HotelInfoWindow from './MapInfoWindow';
4 | import MakerImage from './hotelMapMarker.png';
5 | // Render map cho trang details của từng hotel - truyền data tương tự theo query
6 | const SingleMapDisplay = (props) => {
7 | const locationArray = [];
8 | const TXIDLocation = [];
9 | const {
10 | location,
11 | infoWindowToggle,
12 | isOpen,
13 | markerIndex,
14 | rating,
15 | ratingCount,
16 | image,
17 | price,
18 | title,
19 | } = props;
20 | if (location && location.type && location.type === 'txid') {
21 | TXIDLocation.push({
22 | lat: location && location.lat,
23 | lng: location && location.lng,
24 | formattedAddress: location && location.formattedAddress,
25 | });
26 | }
27 | locationArray.push({
28 | lat: location && location.lat,
29 | lng: location && location.lng,
30 | id: location && location.id,
31 | title,
32 | thumbUrl: image && image.thumb_url,
33 | formattedAddress: location && location.formattedAddress,
34 | price,
35 | rating,
36 | ratingCount,
37 | });
38 |
39 | return locationArray.map((singlePostLocation) => (
40 | {
45 | if (location && location.type && location.type !== 'txid') infoWindowToggle(singlePostLocation.id);
46 | }}
47 | >
48 | {isOpen && markerIndex === singlePostLocation.id && location && location.type && location.type !== 'txid' ? (
49 | {
52 | infoWindowToggle(singlePostLocation.id);
53 | }}
54 | />
55 | ) : (
56 | ''
57 | )}
58 |
59 | ));
60 | };
61 |
62 | const HotelMapMarkerSingle = (props) => ;
63 |
64 | export default HotelMapMarkerSingle;
65 |
--------------------------------------------------------------------------------
/components/Map/hotelMapMarker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/components/Map/hotelMapMarker.png
--------------------------------------------------------------------------------
/components/Navbar/Navbar.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import NavbarWrapper, {
4 | LogoArea,
5 | MenuArea,
6 | AvatarWrapper,
7 | AuthWrapper,
8 | MenuWrapper,
9 | } from './Navbar.style';
10 |
11 | const Navbar = ({
12 | className,
13 | logo,
14 | avatar,
15 | navMenu,
16 | authMenu,
17 | profileMenu,
18 | isLogin,
19 | headerType,
20 | searchComponent,
21 | location,
22 | searchVisibility,
23 | }) => {
24 | // Add all classs to an array
25 | const addAllClasses = ['navbar'];
26 |
27 | // className prop checking
28 | if (className) {
29 | addAllClasses.push(className);
30 | }
31 |
32 | // headerType prop checking
33 | if (headerType) {
34 | addAllClasses.push(`is_${headerType}`);
35 | }
36 |
37 | return (
38 |
39 | {logo || searchVisibility ? (
40 |
41 | {logo}
42 | {!searchVisibility && location.pathname === '/'
43 | ? null
44 | : searchComponent}
45 |
46 | ) : null}
47 |
48 | {navMenu && {navMenu}}
49 | {isLogin && avatar ? (
50 | {profileMenu}
51 | ) : (
52 | authMenu && (
53 | {authMenu}
54 | )
55 | )}
56 |
57 |
58 | );
59 | };
60 |
61 | Navbar.propTypes = {
62 | className: PropTypes.string,
63 | navMenu: PropTypes.element,
64 | avatar: PropTypes.element,
65 | authMenu: PropTypes.element,
66 | isLogin: PropTypes.bool,
67 | headerType: PropTypes.oneOf(['transparent', 'default']),
68 | };
69 |
70 | export default Navbar;
71 |
--------------------------------------------------------------------------------
/components/Review/ReviewFormComponent.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Radio from 'components/UI/Antd/Radio/Radio';
3 | import DragAndDropUploader from 'components/UI/ImageUploader/DragAndDropUploader';
4 | import Checkbox from 'components/UI/Antd/Checkbox/Checkbox';
5 | import Text from 'components/UI/Text/Text';
6 | import { v4 as uuidv4 } from 'uuid';
7 |
8 | export const RadioGroupComp = ({ field, form, ...props }) => {
9 | const { dataOptions } = props;
10 |
11 | const onChangeValue = (checkedValue) => {
12 | form.setFieldValue(field.name, checkedValue.target.value);
13 | };
14 |
15 | return (
16 | <>
17 |
18 | {dataOptions
19 | && dataOptions.map((single) => (
20 |
21 | {single.label}
22 |
23 | ))}
24 |
25 | {form.touched[field.name] && form.errors[field.name] && (
26 |
27 | )}
28 | >
29 | );
30 | };
31 |
32 | // eslint-disable-next-line no-unused-vars
33 | export const PhotoUploadComponent = ({ field, form, ...props }) => {
34 | const onChange = (value) => {
35 | form.setFieldValue(field.name, value);
36 | };
37 |
38 | return (
39 | <>
40 |
45 | {form.touched[field.name] && form.errors[field.name] && (
46 |
47 | )}
48 | >
49 | );
50 | };
51 |
52 | export const CheckBoxComp = ({ field, form, ...props }) => {
53 | const [checkedValue, setCheckedValue] = useState(false);
54 | const { checkBoxContent } = props;
55 |
56 | const onChange = () => {
57 | setCheckedValue(!checkedValue);
58 | form.setFieldValue(field.name, checkedValue);
59 | };
60 |
61 | return (
62 | <>
63 |
64 | {checkBoxContent || ''}
65 |
66 | {form.touched[field.name] && form.errors[field.name] && (
67 |
68 | )}
69 | >
70 | );
71 | };
72 |
--------------------------------------------------------------------------------
/components/SectionTitle/SectionTitle.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import SectionTitleExtended from '../UI/Title/Title';
3 |
4 | import SectionTitleWrapper from './SectionTitle.style';
5 |
6 | const SectionTitle = ({ ...props }) => (
7 |
8 |
9 |
10 | );
11 |
12 | export default SectionTitle;
13 |
--------------------------------------------------------------------------------
/components/SectionTitle/SectionTitle.style.jsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { themeGet } from '@styled-system/theme-get';
3 |
4 | const SectionTitleWrapper = styled.div`
5 | h2,
6 | a {
7 | margin: 0;
8 | }
9 |
10 | h2 {
11 | color: ${themeGet('text.0', '#2C2C2C')};
12 | font-size: 25px;
13 | line-height: 30px;
14 | font-weight: 700;
15 |
16 | @media only screen and (max-width: 480px) {
17 | font-size: 17px;
18 | line-height: 21px;
19 | }
20 | }
21 |
22 | a {
23 | color: ${themeGet('text.0', '#2C2C2C')};
24 | font-size: 17px;
25 | line-height: 21px;
26 | font-weight: 400;
27 |
28 | @media only screen and (max-width: 480px) {
29 | font-size: 15px;
30 | line-height: 20px;
31 | }
32 |
33 | &:hover {
34 | color: ${themeGet('primary.0', '#008489')};
35 | }
36 | }
37 | `;
38 |
39 | export default SectionTitleWrapper;
40 |
--------------------------------------------------------------------------------
/components/Seo.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Head from 'next/head';
3 |
4 |
5 | export const SEO = () => ({
6 | title,
7 | description,
8 | canonical,
9 | css,
10 | js,
11 | image,
12 | }) => (
13 |
14 | {title}
15 |
16 |
20 |
21 |
22 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | {css && }
35 | {image ? (
36 |
37 | ) : (
38 |
42 | )}
43 | {image && }
44 | {canonical && }
45 | {js && }
46 |
47 | );
48 |
--------------------------------------------------------------------------------
/components/SignIn/RenderSignInForm.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Field, Form } from 'formik';
3 | import Link from 'next/link';
4 | import { MdLockOpen } from 'react-icons/md';
5 | import { AntInput, AntSwitch } from 'components/UI/Antd/AntdInputWithFormik';
6 | import Button from 'components/UI/Antd/Button/Button';
7 |
8 | import FormWrapper, {
9 | FieldWrapper,
10 | SwitchWrapper,
11 | Label,
12 | } from './SignInForm.style';
13 |
14 | const RenderBasicInfoForm = (props) => {
15 | const {
16 | values, submitCount, handleSubmit, forgetPasswordLink,
17 | } = props;
18 | return (
19 |
20 |
67 |
68 | );
69 | };
70 |
71 | export default RenderBasicInfoForm;
72 |
--------------------------------------------------------------------------------
/components/SignIn/SignInForm.style.jsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { themeGet } from '@styled-system/theme-get';
3 |
4 | const FormWrapper = styled.div`
5 | .field-container {
6 | .ant-form-item {
7 | .ant-form-item-control-wrapper {
8 | .ant-form-item-control {
9 | .ant-form-item-control-input {
10 | &:focus {
11 | box-shadow: none;
12 | }
13 | }
14 | .ant-form-item-explain {
15 | margin: 5px 0 10px;
16 | }
17 | }
18 | }
19 | }
20 | }
21 | `;
22 |
23 | export const FieldWrapper = styled.div`
24 | display: flex;
25 | justify-content: space-between;
26 | align-items: center;
27 | margin-top: 41px;
28 | margin-bottom: 41px;
29 |
30 | > a {
31 | color: ${themeGet('primary.0', '#008489')};
32 | font-size: 15px;
33 | font-weight: 700;
34 | line-height: 1;
35 | &:hover,
36 | &:focus {
37 | outline: none;
38 | color: #008489d1;
39 | text-decoration: none;
40 | }
41 | }
42 | `;
43 |
44 | export const SwitchWrapper = styled.div`
45 | display: flex;
46 | align-items: center;
47 |
48 | .field-container {
49 | .ant-form-item {
50 | margin-bottom: 0;
51 | margin-right: 10px;
52 | }
53 | }
54 |
55 | .ant-switch {
56 | min-width: 36px;
57 | height: 21px;
58 | &.ant-switch-checked {
59 | background-color: ${themeGet('primary.0', '#008489')};
60 | }
61 | &::after {
62 | width: 17px;
63 | height: 17px;
64 | }
65 | &:focus {
66 | box-shadow: none;
67 | }
68 | .ant-click-animating-node {
69 | display: none;
70 | }
71 | }
72 | `;
73 |
74 | export const Label = styled.span`
75 | font-size: 15px;
76 | font-weight: 700;
77 | color: ${themeGet('text.0', '#2C2C2C')};
78 | line-height: 1;
79 | `;
80 |
81 | export default FormWrapper;
82 |
--------------------------------------------------------------------------------
/components/SocialShare/SocialShare.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | FacebookShareButton,
4 | TwitterShareButton,
5 | PinterestShareButton,
6 | LinkedinShareButton,
7 | } from 'react-share';
8 | import {
9 | FaTwitter,
10 | FaFacebookF,
11 | FaPinterest,
12 | FaLinkedinIn,
13 | } from 'react-icons/fa';
14 | import Popover from 'components/UI/Antd/Popover/Popover';
15 |
16 | export const FaceBookShare = (props) => {
17 | const { title, shareURL } = props;
18 | return (
19 |
20 |
21 |
22 |
23 | {' '}
24 | Facebook
25 |
26 |
27 |
28 | );
29 | };
30 |
31 | export const TwitterShare = (props) => {
32 | const { shareURL, title, author } = props;
33 | return (
34 |
35 |
36 |
37 |
38 | {' '}
39 | Twitter
40 |
41 |
42 |
43 | );
44 | };
45 |
46 | export const LinkedInShare = (props) => {
47 | const { shareURL, title } = props;
48 | return (
49 |
50 |
51 |
57 |
58 | {' '}
59 | LinkedIn
60 |
61 |
62 |
63 | );
64 | };
65 |
66 | export const PinterestShare = (props) => {
67 | const { shareURL, title, media } = props;
68 | const mediaForPinterest = media ? media[0].url : [];
69 | return (
70 |
71 |
72 |
77 |
78 | {' '}
79 | Pinterest
80 |
81 |
82 |
83 | );
84 | };
85 |
--------------------------------------------------------------------------------
/components/Special/Special.jsx:
--------------------------------------------------------------------------------
1 | // import { Card, Col, Row } from 'antd';
2 | // import React from 'react';
3 | // import special from './Special.module.scss';
4 |
5 | // export default function Special() {
6 | // return (
7 | //
8 | //
Ưu Đãi nổi bật
9 | //
10 | //
11 | // {/* title={Title} */}
12 | //
13 | //
14 | //
15 | //
16 | //
17 | //
18 | //
19 | //
20 | //
21 | //
22 | //
23 | //
24 | //
25 | //
26 | //
27 | //
28 |
29 | // );
30 | // }
31 |
--------------------------------------------------------------------------------
/components/Special/Special.module.scss:
--------------------------------------------------------------------------------
1 | .siteCardWrapper {
2 | margin-top: 30px;
3 | .specialCard {
4 | width: 100%;
5 | height: 100%;
6 | object-fit: cover;
7 | object-position: center center;
8 | border-radius: 4px;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/components/Stations/Stations.jsx:
--------------------------------------------------------------------------------
1 | // import { Row, Col, Divider } from 'antd';
2 | // import station from './Stations.module.scss';
3 |
4 |
5 | // export default function Stations() {
6 | // return (
7 | // <>
8 | //
9 | // Bến xe khách
10 | //
16 | //
17 | //
18 | //
Bến Xe Miền Đông
19 | //
20 | //
21 | //
22 | //
23 | //
Bến Xe Gia Lâm
24 | //
25 | //
26 | //
27 | //
28 | //
Bến Xe Nước Ngầm
29 | //
30 | //
31 | //
32 | //
33 | //
Bến Xe Mỹ Đình
34 | //
35 | //
36 | //
37 | // >
38 | // );
39 | // }
40 |
--------------------------------------------------------------------------------
/components/Stations/Stations.module.scss:
--------------------------------------------------------------------------------
1 | .stationGroup {
2 | height: 161px;
3 | display: block;
4 | background-image: url(https://storage.googleapis.com/fe-production/images/bx-mien-dong.jpg);
5 | background-size: cover;
6 | width: 236px;
7 | position: relative;
8 | margin-right: 16px;
9 | background-position: center center;
10 | background-repeat: no-repeat;
11 | border-radius: 3px;
12 | &:nth-child(2) {
13 | background-image: url(https://storage.googleapis.com/fe-production/images/bx-gia-lam.jpg);
14 | }
15 | &:nth-child(3) {
16 | background-image: url(https://storage.googleapis.com/fe-production/images/bx-nuoc-ngam.jpg);
17 | }
18 | &:nth-child(4) {
19 | background-image: url(https://storage.googleapis.com/fe-production/images/bx-my-dinh.jpg);
20 | }
21 | .stationh1 {
22 | font-weight: 500;
23 | font-size: 18px;
24 | bottom: 10px;
25 | position: absolute;
26 | z-index: 1;
27 | bottom: 5px;
28 | text-align: center;
29 | width: 100%;
30 | color: white;
31 | }
32 | &::after {
33 | content: '';
34 | position: absolute;
35 | top: 81px;
36 | left: 0px;
37 | right: 0px;
38 | bottom: 0px;
39 | height: 80px;
40 | opacity: 0.6;
41 | background: linear-gradient(rgba(0, 0, 0, 0) 0%, rgb(0, 0, 0) 100%) 0% 0%
42 | no-repeat padding-box padding-box transparent;
43 | border-radius: 3px;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/components/Statistics/Statistic.jsx:
--------------------------------------------------------------------------------
1 | import { Row, Col, Divider } from 'antd';
2 | import statistic from './Statistic.module.scss';
3 |
4 |
5 | export default function Statistic() {
6 | return (
7 | <>
8 |
9 | Hệ thống vé xe khách và vé xe limousine lớn nhất Việt Nam
10 |
14 |
15 |
16 |
17 |
18 |
5000+
19 | Tuyến đường
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
2000+
28 | Nhà xe
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
5000+
37 | Đại lý bán vé
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
400+
46 | Bến xe
47 |
48 |
49 |
50 |
51 | >
52 | );
53 | }
54 |
--------------------------------------------------------------------------------
/components/Statistics/Statistic.module.scss:
--------------------------------------------------------------------------------
1 | // .statisticGroup {
2 | // .statisticBox {
3 | // padding: 8px 0;
4 | // display: flex;
5 | // -webkit-box-pack: center;
6 | // justify-content: center;
7 | // -webkit-box-align: center;
8 | // align-items: center;
9 | // width: 228px;
10 | // height: 111px;
11 | // opacity: 1;
12 | // margin-right: 16px;
13 | // border-width: 1px;
14 | // border-style: outset;
15 | // border-color: rgba(0, 0, 0, 0.06);
16 | // border-image: initial;
17 | // border-radius: 4px;
18 | // .statisticIcon {
19 | // display: flex;
20 | // width: 50px;
21 | // height: 45px;
22 | // margin-right: 10px;
23 | // margin-left: 10px;
24 | // background-image: url(https://storage.googleapis.com/fe-production/svgIcon/static-icon-1.svg);
25 | // background-size: auto;
26 | // background-repeat: no-repeat;
27 | // background-position: center center;
28 | // }
29 | // .statisticDetail {
30 | // width: 50%;
31 | // height: 60%;
32 | // text-align: center;
33 | // font-size: 20px;
34 | // .statisticValue {
35 | // &:nth-child(1) {
36 | // font-size: 26px;
37 | // font-weight: bold;
38 | // padding-top: 1px;
39 | // }
40 | // font-size: 16px;
41 | // margin: 0px;
42 | // }
43 | // }
44 | // }
45 | // &:nth-child(2) {
46 | // .statisticIcon {
47 | // background-image: url(https://storage.googleapis.com/fe-production/svgIcon/static-icon-2.svg);
48 | // }
49 | // }
50 | // &:nth-child(3) {
51 | // .statisticIcon {
52 | // background-image: url(https://storage.googleapis.com/fe-production/svgIcon/static-icon-3.svg);
53 | // }
54 | // }
55 | // &:nth-child(4) {
56 | // .statisticIcon {
57 | // background-image: url(https://storage.googleapis.com/fe-production/svgIcon/static-icon-4.svg);
58 | // }
59 | // }
60 | // }
61 |
--------------------------------------------------------------------------------
/components/StickyBooking/useWindowSize.jsx:
--------------------------------------------------------------------------------
1 | // Tham khảo từ https://usehooks.com/useWindowSize/
2 |
3 | import { useState, useEffect } from 'react';
4 |
5 | function getSize() {
6 | return {
7 | innerHeight: window.innerHeight,
8 | innerWidth: window.innerWidth,
9 | outerHeight: window.outerHeight,
10 | outerWidth: window.outerWidth,
11 | };
12 | }
13 |
14 | const useWindowSize = () => {
15 | const [windowSize, setWindowSize] = useState(getSize());
16 |
17 | function handleResize() {
18 | setWindowSize(getSize());
19 | }
20 |
21 | useEffect(() => {
22 | window.addEventListener('resize', handleResize);
23 | return () => {
24 | window.removeEventListener('resize', handleResize);
25 | };
26 | }, []);
27 |
28 | return windowSize;
29 | };
30 |
31 | export default useWindowSize;
32 |
--------------------------------------------------------------------------------
/components/UI/Antd/Avatar/Avatar.jsx:
--------------------------------------------------------------------------------
1 | import { Avatar } from 'antd';
2 |
3 | export default Avatar;
4 |
--------------------------------------------------------------------------------
/components/UI/Antd/Button/Button.jsx:
--------------------------------------------------------------------------------
1 | import { Button } from 'antd';
2 |
3 | export default Button;
4 |
--------------------------------------------------------------------------------
/components/UI/Antd/Card/Card.jsx:
--------------------------------------------------------------------------------
1 | import { Card } from 'antd';
2 |
3 | export default Card;
4 |
--------------------------------------------------------------------------------
/components/UI/Antd/Checkbox/Checkbox.jsx:
--------------------------------------------------------------------------------
1 | import { Checkbox } from 'antd';
2 |
3 | export default Checkbox;
4 |
--------------------------------------------------------------------------------
/components/UI/Antd/Divider/Divider.jsx:
--------------------------------------------------------------------------------
1 | import { Divider } from 'antd';
2 |
3 | export default Divider;
4 |
--------------------------------------------------------------------------------
/components/UI/Antd/Drawer/Drawer.jsx:
--------------------------------------------------------------------------------
1 | import { Drawer } from 'antd';
2 |
3 | export default Drawer;
4 | // Drawer chứa các actions
5 |
--------------------------------------------------------------------------------
/components/UI/Antd/Dropdown/Dropdown.jsx:
--------------------------------------------------------------------------------
1 | import { Dropdown } from 'antd';
2 |
3 | export default Dropdown;
4 |
--------------------------------------------------------------------------------
/components/UI/Antd/Grid/Col.jsx:
--------------------------------------------------------------------------------
1 | import { Col } from 'antd';
2 |
3 | export default Col;
4 |
--------------------------------------------------------------------------------
/components/UI/Antd/Grid/Row.jsx:
--------------------------------------------------------------------------------
1 | import { Row } from 'antd';
2 |
3 | export default Row;
4 |
--------------------------------------------------------------------------------
/components/UI/Antd/Icon/Icon.jsx:
--------------------------------------------------------------------------------
1 | import { Icon } from 'antd';
2 |
3 | export default Icon;
4 |
--------------------------------------------------------------------------------
/components/UI/Antd/Input/Input.jsx:
--------------------------------------------------------------------------------
1 | import { Input } from 'antd';
2 |
3 | export default Input;
4 |
--------------------------------------------------------------------------------
/components/UI/Antd/InputNumber/InputNumber.jsx:
--------------------------------------------------------------------------------
1 | import { InputNumber } from 'antd';
2 |
3 | export default InputNumber;
4 |
--------------------------------------------------------------------------------
/components/UI/Antd/Layout/Layout.jsx:
--------------------------------------------------------------------------------
1 | import { Layout } from 'antd';
2 |
3 | export default Layout;
4 |
--------------------------------------------------------------------------------
/components/UI/Antd/Menu/Menu.jsx:
--------------------------------------------------------------------------------
1 | import { Menu } from 'antd';
2 |
3 | export default Menu;
4 |
--------------------------------------------------------------------------------
/components/UI/Antd/Modal/Modal.jsx:
--------------------------------------------------------------------------------
1 | import { Modal } from 'antd';
2 |
3 | export default Modal;
4 |
--------------------------------------------------------------------------------
/components/UI/Antd/Pagination/Pagination.jsx:
--------------------------------------------------------------------------------
1 | import { Pagination } from 'antd';
2 |
3 | export default Pagination;
4 |
--------------------------------------------------------------------------------
/components/UI/Antd/Popover/Popover.jsx:
--------------------------------------------------------------------------------
1 | import { Popover } from 'antd';
2 |
3 | export default Popover;
4 |
--------------------------------------------------------------------------------
/components/UI/Antd/Radio/Radio.jsx:
--------------------------------------------------------------------------------
1 | import { Radio } from 'antd';
2 |
3 | export default Radio;
4 |
--------------------------------------------------------------------------------
/components/UI/Antd/Slider/Slider.jsx:
--------------------------------------------------------------------------------
1 | import { Slider } from 'antd';
2 |
3 | export default Slider;
4 |
--------------------------------------------------------------------------------
/components/UI/Antd/Table/Table.jsx:
--------------------------------------------------------------------------------
1 | import { Table } from 'antd';
2 |
3 | export default Table;
4 |
--------------------------------------------------------------------------------
/components/UI/Antd/Tag/Tag.jsx:
--------------------------------------------------------------------------------
1 | import { Tag } from 'antd';
2 |
3 | export default Tag;
4 |
--------------------------------------------------------------------------------
/components/UI/Antd/Upload/Upload.jsx:
--------------------------------------------------------------------------------
1 | import { Upload } from 'antd';
2 |
3 | export default Upload;
4 |
--------------------------------------------------------------------------------
/components/UI/Base.jsx:
--------------------------------------------------------------------------------
1 | /** Tất cá components phải kế thừa từ mục base - default */
2 | import {
3 | space,
4 | borders,
5 | borderColor,
6 | width,
7 | minWidth,
8 | maxWidth,
9 | height,
10 | minHeight,
11 | maxHeight,
12 | display,
13 | fontSize,
14 | flex,
15 | order,
16 | alignSelf,
17 | color,
18 | compose,
19 | } from 'styled-system';
20 |
21 | export const themed = (key) => (props) => props.theme[key];
22 |
23 | export const base = compose(
24 | () => ({ boxSizing: 'border-box' }),
25 | space,
26 | width,
27 | minWidth,
28 | maxWidth,
29 | height,
30 | minHeight,
31 | maxHeight,
32 | fontSize,
33 | color,
34 | flex,
35 | order,
36 | alignSelf,
37 | borders,
38 | borderColor,
39 | display,
40 | );
41 |
42 | base.propTypes = {
43 | ...display.propTypes,
44 | ...space.propTypes,
45 | ...borders.propTypes,
46 | ...borderColor.propTypes,
47 | ...width.propTypes,
48 | ...height.propTypes,
49 | ...fontSize.propTypes,
50 | ...color.propTypes,
51 | ...flex.propTypes,
52 | ...order.propTypes,
53 | ...alignSelf.propTypes,
54 | };
55 |
--------------------------------------------------------------------------------
/components/UI/Card/Card.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | Wrapper, Header, Content, Footer,
4 | } from './Card.style';
5 |
6 | export default function Card({
7 | title,
8 | className,
9 | header,
10 | content,
11 | children,
12 | footer,
13 | }) {
14 | return (
15 |
16 |
17 | {content || children}
18 | {footer && }
19 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/components/UI/Card/Card.style.jsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Wrapper = styled.div`
4 | padding: 1rem;
5 | border-radius: 3px;
6 | box-shadow: 0 2px 20px rgba(0, 0, 0, 0.16);
7 | `;
8 | export const Header = styled.header`
9 | display: flex;
10 | justify-content: space-between;
11 | align-items: center;
12 | `;
13 |
14 | export const Content = styled.div``;
15 | export const Footer = styled.footer`
16 | display: flex;
17 | justify-content: space-between;
18 | align-items: center;
19 | `;
20 |
--------------------------------------------------------------------------------
/components/UI/Container/Container.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import ContainerWrapper from './Container.style';
4 |
5 | const Container = ({
6 | children, className, fullWidth, noGutter, fluid,
7 | }) => (
8 |
14 | {children}
15 |
16 | );
17 |
18 | Container.propTypes = {
19 | className: PropTypes.string,
20 | fullWidth: PropTypes.bool,
21 | noGutter: PropTypes.bool,
22 | };
23 |
24 | export default Container;
25 |
--------------------------------------------------------------------------------
/components/UI/Container/Container.style.jsx:
--------------------------------------------------------------------------------
1 | import styled, { css } from 'styled-components';
2 |
3 | const ContainerWrapper = styled.div`
4 | margin-left: auto;
5 | margin-right: auto;
6 | ${(props) => props.fullWidth
7 | && css`
8 | width: 100%;
9 | max-width: none !important;
10 | `};
11 | ${(props) => (props.noGutter
12 | && css`
13 | padding-left: 0;
14 | padding-right: 0;
15 | `)
16 | || css`
17 | padding-left: 30px;
18 | padding-right: 30px;
19 |
20 | @media only screen and (max-width: 480px) {
21 | padding-left: 25px;
22 | padding-right: 25px;
23 | }
24 | `};
25 |
26 | ${(props) => props.fluid
27 | && css`
28 | width: 100% !important;
29 | max-width: 100% !important;
30 | @media only screen and (min-width: 1441px) {
31 | padding-left: 75px;
32 | padding-right: 75px;
33 | }
34 | `}
35 | @media (min-width: 768px) {
36 | max-width: 750px;
37 | width: 100%;
38 | }
39 | @media (min-width: 992px) {
40 | max-width: 970px;
41 | width: 100%;
42 | }
43 | @media (min-width: 1200px) {
44 | max-width: 1170px;
45 | width: 100%;
46 | }
47 | `;
48 |
49 | export default ContainerWrapper;
50 |
--------------------------------------------------------------------------------
/components/UI/Favorite/Favorite.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useMutation } from 'react-apollo';
3 | import PropTypes from 'prop-types';
4 | import useToggle from './useToggle';
5 | import FavoriteWrapper from './Favorite.style';
6 | import { LIKE, DISLIKE } from 'apollo-graphql/mutation/mutation';
7 |
8 | const Favorite = ({
9 | className, content, onClick, id, heart,
10 | }) => {
11 | // use toggle hooks
12 | const [toggleValue, toggleHandler] = useToggle(heart === 1);
13 | const [likeHotel] = useMutation(LIKE);
14 | const [dislikeHotel] = useMutation(DISLIKE);
15 | const addAllClass = ['favorite'];
16 |
17 | if (className) {
18 | addAllClass.push(className);
19 | }
20 | const handleClick = (event) => {
21 | toggleHandler();
22 | if (toggleValue) {
23 | dislikeHotel({
24 | variables: {
25 | id,
26 | },
27 | });
28 | } else {
29 | likeHotel({
30 | variables: {
31 | id,
32 | },
33 |
34 | });
35 | }
36 | onClick(!toggleValue);
37 | };
38 |
39 | return (
40 |
44 |
50 | {content}
51 |
52 | );
53 | };
54 |
55 | Favorite.propTypes = {
56 | className: PropTypes.string,
57 | content: PropTypes.string,
58 | /**
59 | * Callback triggered khi mà value bị thay đổi
60 | *
61 | * @param {object} event Nguồn event source của callback
62 | * Access value mới bằng cách `event.target.value`.
63 | */
64 | onClick: PropTypes.func,
65 | };
66 |
67 | Favorite.defaultProps = {
68 | onClick: () => {},
69 | };
70 |
71 | export default Favorite;
72 |
--------------------------------------------------------------------------------
/components/UI/Favorite/Favorite.style.jsx:
--------------------------------------------------------------------------------
1 | import styled, { css, keyframes } from 'styled-components';
2 | import { themeGet } from '@styled-system/theme-get';
3 |
4 | const addAnimation = keyframes`
5 | 0% {
6 | transform: scale(1) rotate(0deg);
7 | }
8 | 50% {
9 | transform: scale(1.34) rotateY(90deg);
10 | }
11 | `;
12 |
13 | const removeAnimation = keyframes`
14 | 0% {
15 | transform: scale(1.34) rotateY(90deg);
16 | }
17 | 50% {
18 | transform: scale(1) rotateY(0);
19 | }
20 | `;
21 |
22 | const AddAnimation = css`
23 | animation: ${addAnimation} 0.4s cubic-bezier(0.38, 0.7, 0.6, 0.29);
24 | `;
25 |
26 | const RemoveAnimation = css`
27 | animation: ${removeAnimation} 0.55s cubic-bezier(0.38, 0.7, 0.6, 0.29);
28 | `;
29 |
30 | const FavoriteWrapper = styled.button`
31 | display: inline-block;
32 | cursor: pointer;
33 | border: 0;
34 | padding: 10px;
35 | background-color: transparent;
36 | &:hover,
37 | &:focus {
38 | border: 0;
39 | box-shadow: none;
40 | outline: none;
41 | }
42 |
43 | svg {
44 | width: 22px;
45 | height: 20px;
46 | ${RemoveAnimation}
47 | path {
48 | fill: ${themeGet('color.5', 'rgba(0, 0, 0, 0.25)')};
49 | stroke: ${themeGet('color.1', '#ffffff')};
50 | stroke-width: 2px;
51 | transition: fill 1s ease;
52 | }
53 | }
54 |
55 | &.active {
56 | svg {
57 | ${AddAnimation};
58 | path {
59 | fill: ${themeGet('color.4', '#FC5C63')};
60 | }
61 | }
62 | }
63 | `;
64 |
65 | export default FavoriteWrapper;
66 |
--------------------------------------------------------------------------------
/components/UI/Favorite/useToggle.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useCallback } from 'react';
2 | // kỉ thuật làm toggle bằng cách truyền value từ server
3 | const useToggle = (initialValue) => {
4 | const [value, setValue] = useState(initialValue);
5 | // Thay vi sử dụng prevState thì sử dụng useCallBack
6 | const toggler = useCallback(() => setValue((prevValue) => !prevValue), []);
7 | return [value, toggler];
8 | };
9 | export default useToggle;
10 |
--------------------------------------------------------------------------------
/components/UI/GlideCarousel/GlideCarousel.style.jsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | // Glide wrapper style
4 | const GlideWrapper = styled.div`
5 | .glide__slides {
6 | margin-bottom: 0;
7 | }
8 | .glide__controls {
9 | .glide__prev--area,
10 | .glide__next--area {
11 | cursor: pointer;
12 | }
13 | }
14 | `;
15 |
16 | // Glide slide wrapper style
17 | const GlideSlideWrapper = styled.li`
18 | position: relative;
19 | `;
20 |
21 | // Button wrapper style
22 | const ButtonWrapper = styled.div`
23 | display: inline-block;
24 | `;
25 |
26 | // ButtonControlWrapper style
27 | const ButtonControlWrapper = styled.div``;
28 |
29 | // BulletControlWrapper style
30 | const BulletControlWrapper = styled.div``;
31 |
32 | // BulletButton style
33 | const BulletButton = styled.button`
34 | cursor: pointer;
35 | width: 10px;
36 | height: 10px;
37 | margin: 4px;
38 | border: 0;
39 | padding: 0;
40 | outline: none;
41 | border-radius: 50%;
42 | background-color: #d6d6d6;
43 |
44 | &:hover,
45 | &.glide__bullet--active {
46 | background-color: #869791;
47 | }
48 | `;
49 |
50 | // default button style
51 | const DefaultBtn = styled.button`
52 | cursor: pointer;
53 | margin: 10px 3px;
54 | `;
55 |
56 | export {
57 | GlideSlideWrapper,
58 | ButtonControlWrapper,
59 | ButtonWrapper,
60 | BulletControlWrapper,
61 | BulletButton,
62 | DefaultBtn,
63 | };
64 | export default GlideWrapper;
65 |
--------------------------------------------------------------------------------
/components/UI/Heading/Heading.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/default-props-match-prop-types */
2 | /* eslint-disable react/jsx-props-no-spreading */
3 | import React from 'react';
4 | import PropTypes from 'prop-types';
5 | import styled from 'styled-components';
6 | import {
7 | fontFamily,
8 | fontWeight,
9 | textAlign,
10 | lineHeight,
11 | letterSpacing,
12 | } from 'styled-system';
13 | import { base, themed } from '../Base';
14 |
15 | const HeadingWrapper = styled('p')(
16 | base,
17 | fontFamily,
18 | fontWeight,
19 | textAlign,
20 | lineHeight,
21 | letterSpacing,
22 | themed('Heading'),
23 | );
24 |
25 | const Heading = ({ content, ...props }) => (
26 | {content}
27 | );
28 |
29 | Heading.propTypes = {
30 | content: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
31 | as: PropTypes.oneOf(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']),
32 | fontFamily: PropTypes.oneOfType([
33 | PropTypes.string,
34 | PropTypes.number,
35 | PropTypes.arrayOf(
36 | PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
37 | ),
38 | ]),
39 | fontWeight: PropTypes.oneOfType([
40 | PropTypes.string,
41 | PropTypes.number,
42 | PropTypes.arrayOf(
43 | PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
44 | ),
45 | ]),
46 | textAlign: PropTypes.oneOfType([
47 | PropTypes.string,
48 | PropTypes.number,
49 | PropTypes.arrayOf(
50 | PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
51 | ),
52 | ]),
53 | lineHeight: PropTypes.oneOfType([
54 | PropTypes.string,
55 | PropTypes.number,
56 | PropTypes.arrayOf(
57 | PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
58 | ),
59 | ]),
60 | letterSpacing: PropTypes.oneOfType([
61 | PropTypes.string,
62 | PropTypes.number,
63 | PropTypes.arrayOf(
64 | PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
65 | ),
66 | ]),
67 | ...base.propTypes,
68 | };
69 |
70 | Heading.defaultProps = {
71 | as: 'h2',
72 | mt: 0,
73 | mb: 0,
74 | fontWeight: 700,
75 | };
76 |
77 | export default Heading;
78 |
--------------------------------------------------------------------------------
/components/UI/HtmlLabel/HtmlLabel.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 | import {
5 | fontFamily,
6 | fontWeight,
7 | textAlign,
8 | lineHeight,
9 | letterSpacing,
10 | } from 'styled-system';
11 | import { base, themed } from '../Base';
12 |
13 | const LabelWrapper = styled('label')(
14 | base,
15 | fontFamily,
16 | fontWeight,
17 | textAlign,
18 | lineHeight,
19 | letterSpacing,
20 | themed('Heading'),
21 | );
22 |
23 | const HtmlLabel = ({ htmlFor, content, ...props }) => (
24 |
25 | {content}
26 |
27 | );
28 |
29 | HtmlLabel.propTypes = {
30 | htmlFor: PropTypes.string,
31 | content: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
32 | fontFamily: PropTypes.oneOfType([
33 | PropTypes.string,
34 | PropTypes.number,
35 | PropTypes.arrayOf(
36 | PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
37 | ),
38 | ]),
39 | fontWeight: PropTypes.oneOfType([
40 | PropTypes.string,
41 | PropTypes.number,
42 | PropTypes.arrayOf(
43 | PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
44 | ),
45 | ]),
46 | textAlign: PropTypes.oneOfType([
47 | PropTypes.string,
48 | PropTypes.number,
49 | PropTypes.arrayOf(
50 | PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
51 | ),
52 | ]),
53 | lineHeight: PropTypes.oneOfType([
54 | PropTypes.string,
55 | PropTypes.number,
56 | PropTypes.arrayOf(
57 | PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
58 | ),
59 | ]),
60 | letterSpacing: PropTypes.oneOfType([
61 | PropTypes.string,
62 | PropTypes.number,
63 | PropTypes.arrayOf(
64 | PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
65 | ),
66 | ]),
67 | ...base.propTypes,
68 | };
69 |
70 | export default HtmlLabel;
71 |
--------------------------------------------------------------------------------
/components/UI/Image/Image.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 | import { base, themed } from '../Base';
5 |
6 | const ImageWrapper = styled('img')(
7 | {
8 | display: 'block',
9 | maxWidth: '100%',
10 | height: 'auto',
11 | },
12 | base,
13 | themed('Image'),
14 | );
15 |
16 | const Image = ({ src, alt, ...props }) => (
17 |
18 | );
19 |
20 | export default Image;
21 |
22 | Image.propTypes = {
23 | src: PropTypes.string.isRequired,
24 | alt: PropTypes.string.isRequired,
25 | };
26 |
27 | Image.defaultProps = {
28 | m: 0,
29 | };
30 |
--------------------------------------------------------------------------------
/components/UI/ImageUploader/ImageUploader.style.jsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const ImageUpload = styled.div`
4 | display: flex;
5 | align-items: center;
6 | .image-drag-area {
7 | width: 125px;
8 | height: 125px;
9 | border: 1px dashed #e6e6e6;
10 | border-radius: 3px;
11 | font-size: 38px;
12 | color: #e6e6e6;
13 | display: flex;
14 | align-items: center;
15 | justify-content: center;
16 | }
17 | .ant-upload-text {
18 | font-size: 15px;
19 | font-weight: 700;
20 | color: #fff;
21 | background-color: #008489;
22 | border-radius: 3px;
23 | padding: 8px 15px;
24 | margin-left: 30px;
25 | }
26 | `;
27 |
--------------------------------------------------------------------------------
/components/UI/InputIncDec/InputIncDec.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { PlusOutlined, MinusOutlined } from '@ant-design/icons';
3 | import Input from '../Antd/Input/Input';
4 | import InputIncDecWrapper from './InputIncDec.style';
5 |
6 | const InputIncDec = ({
7 | className, increment, decrement, ...props
8 | }) => {
9 | const addAllClasses = ['quantity'];
10 | if (className) {
11 | addAllClasses.push(className);
12 | }
13 |
14 | return (
15 |
16 |
19 |
20 |
23 |
24 | );
25 | };
26 |
27 | export default InputIncDec;
28 |
--------------------------------------------------------------------------------
/components/UI/InputIncDec/InputIncDec.style.jsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const InputIncDecWrapper = styled.div`
4 | width: 104px;
5 | height: 18px;
6 | display: flex;
7 | align-items: center;
8 | position: relative;
9 | /* overflow: hidden; */
10 |
11 | input[type='number'] {
12 | width: calc(100% - 54px);
13 | position: absolute;
14 | left: 27px;
15 | top: 0;
16 | height: 100%;
17 | padding: 0;
18 | border: 0;
19 | text-align: center;
20 | background-color: transparent;
21 |
22 | &::-webkit-inner-spin-button,
23 | &::-webkit-outer-spin-button {
24 | -webkit-appearance: none;
25 | margin: 0;
26 | }
27 |
28 | &:focus {
29 | outline: none;
30 | box-shadow: none;
31 | }
32 | }
33 |
34 | button {
35 | border: 0;
36 | width: 27px;
37 | height: 27px;
38 | border-radius: 50%;
39 | display: inline-flex;
40 | align-items: center;
41 | position: absolute;
42 | top: 0;
43 | padding: 0;
44 | background-color: transparent;
45 | cursor: pointer;
46 | justify-content: center;
47 |
48 | &.decBtn {
49 | left: 0;
50 | }
51 | &.incBtn {
52 | right: 0;
53 | }
54 |
55 | &:hover,
56 | &:focus {
57 | outline: 0;
58 | }
59 | }
60 | `;
61 |
62 | export default InputIncDecWrapper;
63 |
--------------------------------------------------------------------------------
/components/UI/Logo/Logo.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable jsx-a11y/anchor-is-valid */
2 | import React from 'react';
3 | import PropTypes from 'prop-types';
4 | import Link from 'next/link';
5 | import LogoArea from './Logo.style';
6 |
7 | const LogoNext = ({
8 | className, withLink, linkTo, title, src,
9 | }) => (
10 |
11 | {withLink ? (
12 |
13 |
14 | {src &&
}
15 | {title && {title}
}
16 |
17 |
18 | ) : (
19 | <>
20 | {src &&
}
21 | {title && {title}
}
22 | >
23 | )}
24 |
25 | );
26 |
27 | LogoNext.propTypes = {
28 | className: PropTypes.string,
29 | withLink: PropTypes.bool,
30 | src: PropTypes.string,
31 | title: PropTypes.string,
32 | linkTo: PropTypes.string,
33 | };
34 |
35 | export default LogoNext;
36 |
--------------------------------------------------------------------------------
/components/UI/Logo/Logo.style.jsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { themeGet } from '@styled-system/theme-get';
3 |
4 | const LogoArea = styled.div`
5 | display: flex;
6 | align-items: center;
7 |
8 | a {
9 | display: flex;
10 | align-items: center;
11 | }
12 |
13 | img {
14 | width: 20px;
15 | }
16 |
17 | h3 {
18 | color: ${themeGet('primary.0', '#008489')};
19 | text-transform: capitalize;
20 | font-size: 20px;
21 | font-weight: 700;
22 | margin: 0 0 0 10px;
23 | }
24 | `;
25 |
26 | export default LogoArea;
27 |
--------------------------------------------------------------------------------
/components/UI/Portal/Portal.jsx:
--------------------------------------------------------------------------------
1 | // Comp portal được chèn vào DOM sau
2 | // Modal được mounted, có nghĩa là children
3 | // sẽ được gắn trên một node DOM tách rời. Nếu children
4 | // yêu cầu phải được gắn vào cây DOM
5 | // ngay lập tức khi được mounted, ví dụ để đo Dom node
6 | // Hoặc sử dụng 'autoFocus' trong lóp cha, thêm
7 | // state cho Modal và chỉ render children khi Modal
8 | // được DOM.
9 | //* *Xử lý riêng style modal, outside click*/
10 | import { useState, useEffect } from 'react';
11 | import ReactDOM from 'react-dom';
12 |
13 | export default function Portal({ children, rendererId }) {
14 | const [containerEl] = useState(document.createElement('div'));
15 | useEffect(() => {
16 | const portalRoot = document.getElementById(rendererId) || document.body;
17 | portalRoot.appendChild(containerEl);
18 | return () => portalRoot.removeChild(containerEl);
19 | });
20 | return ReactDOM.createPortal(children, containerEl);
21 | }
22 |
--------------------------------------------------------------------------------
/components/UI/Rating/Rating.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-plusplus */
2 | import React from 'react';
3 | import PropTypes from 'prop-types';
4 | import { IoIosStar, IoIosStarOutline } from 'react-icons/io';
5 |
6 | const Rating = (props) => {
7 | const {
8 | rating, ratingCount, type, ratingFieldName,
9 | } = props;
10 | // Render đúng số sao
11 | let i;
12 | const ratingView = [];
13 | // if (rating && rating !== 0) {
14 | const floorValue = Math.floor(rating);
15 | for (i = 0; i < 5; i++) {
16 | if (i < floorValue) {
17 | ratingView.push();
18 | } else {
19 | ratingView.push();
20 | }
21 | }
22 | // }
23 | let listingCondition;
24 | if (rating && rating === 5) {
25 | listingCondition = 'Awesome';
26 | } else if (rating >= 4 && rating < 5) {
27 | listingCondition = 'Good';
28 | } else if (rating >= 3 && rating < 4) {
29 | listingCondition = 'Average';
30 | } else if (rating >= 2 && rating < 3) {
31 | listingCondition = 'Bad';
32 | } else if (rating >= 1) {
33 | listingCondition = 'Terrible';
34 | } else {
35 | listingCondition = 'No Rating Yet';
36 | }
37 |
38 | let showRatingCount;
39 | if (ratingCount) {
40 | showRatingCount = `(${ratingCount})`;
41 | } else {
42 | showRatingCount = '';
43 | }
44 |
45 | return (
46 | <>
47 | {/* type bulk truyền từ ProductCard */}
48 | {type && type === 'bulk' ? (
49 | <>
50 | {ratingView}
51 |
52 | {` ${listingCondition}`}
53 | {' '}
54 | {`${showRatingCount}`}
55 |
56 | >
57 | ) : (
58 | <>
59 | {ratingFieldName}
60 | {' '}
61 | {ratingView}
62 | >
63 | )}
64 | >
65 | );
66 | };
67 |
68 | Rating.propTypes = {
69 | type: PropTypes.string.isRequired,
70 | ratingCount: PropTypes.number,
71 | rating: PropTypes.number,
72 | ratingFieldName: PropTypes.string,
73 | };
74 |
75 | export default Rating;
76 |
--------------------------------------------------------------------------------
/components/UI/ScrollBar/ScrollBar.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-scroll';
3 |
4 | import { Wrapper, InnerWrapper } from './ScrollBar.style';
5 |
6 | export default function ScrollBar({
7 | menu = [], other, height, className,
8 | }) {
9 | // Add all classs to an array
10 | const addAllClasses = ['scrollbar'];
11 |
12 | // className prop checking
13 | if (className) {
14 | addAllClasses.push(className);
15 | }
16 | return (
17 |
18 |
19 |
20 | {menu.map((menuItem) => (
21 |
34 | {menuItem.name}
35 |
36 | ))}
37 |
38 | {other && other}
39 |
40 |
41 | );
42 | }
43 |
--------------------------------------------------------------------------------
/components/UI/ScrollBar/ScrollBar.style.jsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Wrapper = styled.div`
4 | min-height: 82px;
5 | background-color: #ffffff;
6 | overflow-y: hidden;
7 |
8 | @media only screen and (max-width: 1200px) {
9 | min-height: 60px;
10 | }
11 |
12 | .linkItem {
13 | padding: 1rem;
14 | }
15 |
16 | .scrollbar_right {
17 | flex-shrink: 0;
18 | }
19 | `;
20 |
21 | export const InnerWrapper = styled.div`
22 | display: flex;
23 | justify-content: space-between;
24 | align-items: center;
25 | overflow-x: auto;
26 | min-height: 110px;
27 | padding-bottom: 30px;
28 | margin-bottom: -30px;
29 |
30 | @media only screen and (max-width: 1200px) {
31 | min-height: 80px;
32 | padding-bottom: 20px;
33 | margin-bottom: -20px;
34 | }
35 | `;
36 |
--------------------------------------------------------------------------------
/components/UI/Text/Text.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/default-props-match-prop-types */
2 | /* eslint-disable react/jsx-props-no-spreading */
3 | import React from 'react';
4 | import PropTypes from 'prop-types';
5 | import styled from 'styled-components';
6 | import {
7 | fontFamily,
8 | fontWeight,
9 | textAlign,
10 | lineHeight,
11 | letterSpacing,
12 | } from 'styled-system';
13 | import { base, themed } from '../Base';
14 |
15 | const TextWrapper = styled('p')(
16 | base,
17 | fontFamily,
18 | fontWeight,
19 | textAlign,
20 | lineHeight,
21 | letterSpacing,
22 | themed('Text'),
23 | );
24 |
25 | const Text = ({ content, ...props }) => (
26 | {content}
27 | );
28 |
29 | Text.propTypes = {
30 | content: PropTypes.string,
31 | as: PropTypes.string,
32 | fontFamily: PropTypes.oneOfType([
33 | PropTypes.string,
34 | PropTypes.number,
35 | PropTypes.arrayOf(
36 | PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
37 | ),
38 | ]),
39 | fontWeight: PropTypes.oneOfType([
40 | PropTypes.string,
41 | PropTypes.number,
42 | PropTypes.arrayOf(
43 | PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
44 | ),
45 | ]),
46 | textAlign: PropTypes.oneOfType([
47 | PropTypes.string,
48 | PropTypes.number,
49 | PropTypes.arrayOf(
50 | PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
51 | ),
52 | ]),
53 | lineHeight: PropTypes.oneOfType([
54 | PropTypes.string,
55 | PropTypes.number,
56 | PropTypes.arrayOf(
57 | PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
58 | ),
59 | ]),
60 | letterSpacing: PropTypes.oneOfType([
61 | PropTypes.string,
62 | PropTypes.number,
63 | PropTypes.arrayOf(
64 | PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
65 | ),
66 | ]),
67 | ...base.propTypes,
68 | };
69 |
70 | Text.defaultProps = {
71 | as: 'p',
72 | m: 0,
73 | };
74 |
75 | export default Text;
76 |
--------------------------------------------------------------------------------
/components/UI/Title/Title.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import SectionTitleWrapper, { TitleWrapper, LinkWrapper } from './Title.style';
4 |
5 | const SectionTitle = ({
6 | className, title, link, ...props
7 | }) => {
8 | // Add all classs to an array
9 | const addAllClasses = ['section_title'];
10 |
11 | // className prop checking
12 | if (className) {
13 | addAllClasses.push(className);
14 | }
15 |
16 | return (
17 |
18 | {title && {title}}
19 | {link && {link}}
20 |
21 | );
22 | };
23 |
24 | SectionTitle.propTypes = {
25 | /** ClassName SectionTitle. */
26 | className: PropTypes.string,
27 | /** Cho heading component */
28 | title: PropTypes.element,
29 | /** Cho TextLink component */
30 | link: PropTypes.element,
31 | };
32 |
33 | export default SectionTitle;
34 |
--------------------------------------------------------------------------------
/components/UI/Title/Title.style.jsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { base } from '../Base';
3 |
4 | const SectionTitleWrapper = styled.div`
5 | display: flex;
6 | justify-content: space-between;
7 | align-items: baseline;
8 | margin: 30px 0;
9 |
10 | @media only screen and (max-width: 480px) {
11 | margin: 15px 0 20px;
12 | }
13 |
14 | ${base}
15 | `;
16 |
17 | export const TitleWrapper = styled.div``;
18 |
19 | export const LinkWrapper = styled.div``;
20 |
21 | export default SectionTitleWrapper;
22 |
--------------------------------------------------------------------------------
/components/UI/Toolbar/Toolbar.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ToolbarWrapper, LeftSide, RightSide } from './Toolbar.style';
3 |
4 | export default function Toolbar({
5 | left, right, className, children,
6 | }) {
7 | // Add all classs to an array
8 | const addAllClasses = ['toolbar'];
9 |
10 | // className prop checking
11 | if (className) {
12 | addAllClasses.push(className);
13 | }
14 |
15 | return (
16 |
17 | {left && {left}}
18 | {right && {right}}
19 | {children && children}
20 |
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/components/UI/Toolbar/Toolbar.style.jsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const ToolbarWrapper = styled.div`
4 | display: flex;
5 | justify-content: space-between;
6 | align-items: center;
7 | min-height: 70px;
8 | background: ${(props) => (props.bg ? props.bg : '#fff')};
9 | `;
10 | export const LeftSide = styled.div``;
11 | export const RightSide = styled.div``;
12 |
--------------------------------------------------------------------------------
/components/UI/ViewWithPopup/ViewWithPopup.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable jsx-a11y/click-events-have-key-events */
2 | /* eslint-disable jsx-a11y/no-static-element-interactions */
3 | import React, { useState, useRef } from 'react';
4 | import { useOnClickOutside } from './useOnClickOutside';
5 | import Portal from '../Portal/Portal';
6 | import { Wrapper, Container } from './ViewWithPopup.style';
7 |
8 | export default function ViewWithPopup({
9 | view,
10 | popup,
11 | noView = false,
12 | style,
13 | className,
14 | }) {
15 | const [showPopup, setShowPopup] = useState(false);
16 | const ref = useRef();
17 | // dùng ref bắt thuộc tính DOM node, ở đây là click ra ngoài
18 | // bắt ref của clickOutSide
19 | useOnClickOutside(ref, () => setShowPopup(false));
20 | // kĩ thuật gắn tên class
21 | const addAllClasses = ['view_with__popup'];
22 |
23 | // className prop checking
24 | if (className) {
25 | addAllClasses.push(className);
26 | }
27 | return (
28 |
29 | {/* có view và noView = false như set ở prop */}
30 | {view && noView && (
31 | setShowPopup(!showPopup)}>
32 | {view}
33 |
34 | )}
35 | setShowPopup(true)}
39 | style={style}
40 | >
41 | {/* check null */}
42 | {view && !noView && view}
43 | {showPopup && (
44 |
47 | )}
48 |
49 |
50 | );
51 | }
52 |
--------------------------------------------------------------------------------
/components/UI/ViewWithPopup/ViewWithPopup.style.jsx:
--------------------------------------------------------------------------------
1 | import styled, { css } from 'styled-components';
2 |
3 | export const Container = styled.div`
4 | border-radius: ${(props) => (props.radius ? props.radius : 3)}px;
5 | ${(props) => props.showPopup
6 | && css`
7 | box-shadow: 0 2px 20px rgba(0, 0, 0, 0.16);
8 | position: absolute;
9 | width: 100%;
10 | z-index: 999;
11 | min-width: 300px;
12 | padding: 30px;
13 | background-color: #ffffff;
14 | `}
15 |
16 | .ant-checkbox-group {
17 | display: flex;
18 | flex-direction: column;
19 | .ant-checkbox-group-item {
20 | margin: 9px 0;
21 | &:first-child {
22 | margin-top: 0;
23 | }
24 | &:last-child {
25 | margin-bottom: 0;
26 | }
27 | }
28 | }
29 | .ant-radio-group {
30 | display: flex;
31 | flex-direction: column;
32 | .ant-checkbox-group-item {
33 | margin: 9px 0;
34 | &:first-child {
35 | margin-top: 0;
36 | }
37 | &:last-child {
38 | margin-bottom: 0;
39 | }
40 | }
41 | }
42 |
43 | .date_picker {
44 | margin-bottom: 0;
45 |
46 | .DateRangePicker {
47 | .DateRangePickerInput {
48 | border-radius: 3px;
49 | }
50 | }
51 | }
52 | `;
53 |
54 | export const Wrapper = styled.div`
55 | position: relative;
56 | width: 100%;
57 | `;
58 |
--------------------------------------------------------------------------------
/components/UI/ViewWithPopup/useOnClickOutside.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | // Xử lý click ngoài modal thì out
3 | // hooks useOnClickOutside
4 | // eslint-disable-next-line import/prefer-default-export
5 | export function useOnClickOutside(ref, handler) {
6 | useEffect(() => {
7 | const listener = (event) => {
8 | // Không làm gì nếu click ref hoặc phần tử thấp hơn
9 | if (!ref.current || ref.current.contains(event.target)) {
10 | return;
11 | }
12 | handler(event);
13 | };
14 | document.addEventListener('mousedown', listener);
15 | document.addEventListener('touchstart', listener);
16 | return () => {
17 | document.removeEventListener('mousedown', listener);
18 | document.removeEventListener('touchstart', listener);
19 | };
20 | }, [ref, handler]);
21 | }
22 |
--------------------------------------------------------------------------------
/container/404/404.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Link from 'next/link';
3 | import Image from 'components/UI/Image/Image';
4 | import Heading from 'components/UI/Heading/Heading';
5 | import NotFoundWrapper, { ContentWrapper } from './404.style';
6 | import Image404 from 'assets/images/404@2x.png';
7 |
8 | const ErrorPage = (props) => {
9 | const { errorCode } = props;
10 | let content = 'An unexpected error has occurred';
11 | if (errorCode === 400) {
12 | content = `${errorCode} : Bad Request`;
13 | } else if (errorCode === 404) {
14 | content = `${errorCode} : This page could not be found`;
15 | } else if (errorCode === 405) {
16 | content = `${errorCode} : Method Not Allowed`;
17 | } else if (errorCode === 500) {
18 | content = `${errorCode} : Internal Server Error`;
19 | }
20 |
21 | return (
22 |
23 |
24 |
25 |
26 |
27 | Go Back to Home
28 |
29 |
30 |
31 | );
32 | };
33 |
34 | export default ErrorPage;
35 |
--------------------------------------------------------------------------------
/container/404/404.style.jsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { themeGet } from '@styled-system/theme-get';
3 |
4 | const NotFoundWrapper = styled.div`
5 | display: flex;
6 | align-items: center;
7 | justify-content: center;
8 | max-width: 890px;
9 | padding: 0 25px;
10 | margin: 60px auto;
11 |
12 | img {
13 | max-width: 560px;
14 | @media only screen and (min-width: 1901px) {
15 | max-width: 100%;
16 | }
17 | @media only screen and (max-width: 667px) {
18 | max-width: 100%;
19 | }
20 | }
21 | `;
22 |
23 | export const ContentWrapper = styled.div`
24 | text-align: center;
25 |
26 | h2 {
27 | font-size: 18px;
28 | font-weight: 700px;
29 | margin: 56px auto 30px;
30 | color: ${themeGet('primary.0', '#008489')};
31 | }
32 |
33 | a {
34 | height: 37px;
35 | padding: 0 14px;
36 | font-size: 15px;
37 | box-shadow: none;
38 | font-weight: 700;
39 | text-shadow: none;
40 | display: inline-flex;
41 | align-items: center;
42 | border-radius: 3px;
43 | color: ${themeGet('color.1', '#ffffff')};
44 | border-color: ${themeGet('primary.0', '#008489')};
45 | background-color: ${themeGet('primary.0', '#008489')};
46 | &:focus,
47 | &:hover {
48 | outline: 0;
49 | color: ${themeGet('color.1', '#ffffff')};
50 | border-color: ${themeGet('primary.1', '#399C9F')};
51 | background-color: ${themeGet('primary.1', '#399C9F')};
52 | }
53 | }
54 | `;
55 |
56 | export default NotFoundWrapper;
57 |
--------------------------------------------------------------------------------
/container/ForgetPassword/ForgetPasswordForm.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useMutation } from 'react-apollo';
3 | import { toast } from 'react-toastify';
4 | import { Formik } from 'formik';
5 | import * as Yup from 'yup';
6 | import RenderForgetPasswordForm from 'components/ForgetPassword/RenderForgetPassswordForm';
7 | import { FORGET_PASSWORD } from 'apollo-graphql/mutation/mutation';
8 |
9 | export default function ForgetPassWordForm() {
10 | const [forgetPassword] = useMutation(FORGET_PASSWORD, {
11 | onCompleted: () => {
12 | toast.success('Email for resetting password sent!', {
13 | position: 'top-left',
14 | autoClose: 5000,
15 | hideProgressBar: true,
16 | closeOnClick: true,
17 | pauseOnHover: true,
18 | draggable: true,
19 | progress: undefined,
20 | });
21 | },
22 | });
23 | const initialValues = {
24 | email: '',
25 | };
26 |
27 | const getPasswordFormValidationSchema = () => Yup.object().shape({
28 | email: Yup.string()
29 | .email('Invalid Email')
30 | .required('Must enter Email'),
31 | });
32 |
33 | const handleSubmit = async (formProps) => {
34 | const { email } = formProps;
35 | try {
36 | await forgetPassword({
37 | variables: {
38 | email,
39 | },
40 | });
41 | } catch (e) {
42 | toast.error(e.message.slice(15), {
43 | position: 'top-left',
44 | autoClose: 5000,
45 | hideProgressBar: true,
46 | closeOnClick: true,
47 | pauseOnHover: true,
48 | draggable: true,
49 | progress: undefined,
50 | });
51 | }
52 | };
53 |
54 | return (
55 |
60 | {RenderForgetPasswordForm}
61 |
62 | );
63 | }
64 |
--------------------------------------------------------------------------------
/container/Home/Location/Locations.style.jsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { themeGet } from '@styled-system/theme-get';
3 |
4 | const LocationWrapper = styled.div`
5 | padding: 52px 0;
6 | @media only screen and (max-width: 480px) {
7 | padding: 30px 0;
8 | }
9 | `;
10 |
11 | export const CarouselSection = styled.div`
12 | .react-multi-carousel-list {
13 | .react-multi-carousel-item {
14 | padding: 0 10px;
15 | transition: transform 0.3s ease;
16 |
17 | .image_card {
18 | > a {
19 | @media only screen and (max-width: 1600px) {
20 | height: 310px;
21 | }
22 | @media only screen and (max-width: 991px) {
23 | height: 280px;
24 | }
25 | }
26 | }
27 | }
28 |
29 | .react-multiple-carousel__arrow {
30 | opacity: 0;
31 | visibility: hidden;
32 | width: 36px;
33 | height: 36px;
34 | border-radius: 50%;
35 | display: flex;
36 | align-items: center;
37 | justify-content: center;
38 | background-color: ${themeGet('color.1', '#ffffff')};
39 | box-shadow: 0 3px 6px ${themeGet('boxShadow.1', 'rgba(0, 0, 0, 0.16)')};
40 | transition: background-color 0.2s ease;
41 |
42 | &::before {
43 | color: ${themeGet('primary.0', '#008489')};
44 | }
45 |
46 | &:hover {
47 | background-color: ${themeGet('primary.0', '#008489')};
48 | &::before {
49 | color: ${themeGet('color.1', '#ffffff')};
50 | }
51 | }
52 | }
53 |
54 | &:hover {
55 | .react-multiple-carousel__arrow {
56 | opacity: 1;
57 | visibility: visible;
58 | }
59 | }
60 |
61 | .react-multiple-carousel__arrow--left {
62 | left: 30px;
63 | }
64 |
65 | .react-multiple-carousel__arrow--right {
66 | right: 30px;
67 | }
68 | }
69 | `;
70 |
71 | export default LocationWrapper;
72 |
--------------------------------------------------------------------------------
/container/LanguageSwitcher/LanguageSwitcher.jsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react';
2 | // import { useRouter } from 'next/router';
3 | import IntlMessages from 'library/helpers/i18n';
4 | import config from './config';
5 | import { LanguageContext } from 'context/LanguageProvider';
6 |
7 |
8 | export default function LanguageSwitcher({ language, dispatch }) {
9 | const switchLanguage = () => {
10 | dispatch({
11 | type: 'SWITCH',
12 | });
13 | // router.reload();
14 | };
15 | return (
16 |
17 |
18 |
19 |
20 |
21 | {config.options.map((option) => {
22 | const { languageId, icon, locale } = option;
23 | const customClass = locale === language.locale.substring(0, 2)
24 | ? 'selectedTheme languageSwitch'
25 | : 'languageSwitch';
26 |
27 | return (
28 |
39 | );
40 | })}
41 |
42 |
43 | );
44 | }
45 |
--------------------------------------------------------------------------------
/container/LanguageSwitcher/config.jsx:
--------------------------------------------------------------------------------
1 | import englishLang from 'public/static/flag/uk.svg';
2 | import chineseLang from 'public/static/flag/china.svg';
3 | import spanishLang from 'public/static/flag/spain.svg';
4 | import frenchLang from 'public/static/flag/france.svg';
5 | import italianLang from 'public/static/flag/italy.svg';
6 |
7 |
8 | const language = 'english';
9 |
10 | const config = {
11 | defaultLanguage: language,
12 | options: [
13 | {
14 | languageId: 'english',
15 | locale: 'en',
16 | text: 'English',
17 | icon: englishLang,
18 | },
19 | {
20 | languageId: 'chinese',
21 | locale: 'zh',
22 | text: 'Chinese',
23 | icon: chineseLang,
24 | },
25 | {
26 | languageId: 'spanish',
27 | locale: 'es',
28 | text: 'Spanish',
29 | icon: spanishLang,
30 | },
31 | {
32 | languageId: 'french',
33 | locale: 'fr',
34 | text: 'French',
35 | icon: frenchLang,
36 | },
37 | {
38 | languageId: 'italian',
39 | locale: 'it',
40 | text: 'Italian',
41 | icon: italianLang,
42 | },
43 | ],
44 | };
45 |
46 | export function getCurrentLanguage(lang) {
47 | let selecetedLanguage = config.options[0];
48 | config.options.forEach((languageRender) => {
49 | if (languageRender.languageId === lang) {
50 | selecetedLanguage = languageRender;
51 | }
52 | });
53 | return selecetedLanguage;
54 | }
55 | export default config;
56 |
--------------------------------------------------------------------------------
/container/Layout/Footer/Footer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Link from 'next/link';
3 | import Logo from '../../../components/UI/Logo/Logo';
4 | import Footers from '../../../components/Footer/Footer';
5 | import LogoImage from '../../../assets/images/logo-alt.svg';
6 | import FooterMenu from './FooterMenu';
7 |
8 | const url = '//github.com/php1301';
9 |
10 | const Footer = ({ path }) => (
11 | }
14 | menu={}
15 | copyright={(
16 | <>
17 |
18 |
19 | PhucPham
20 |
21 | {' '}
22 | Inspired
23 | {' '}
24 | {new Date().getFullYear()}
25 | {' '}
26 | AirBNB - TripFinder.
27 |
28 | >
29 | )}
30 | />
31 | );
32 | // {' '} space style ESlint
33 | export default Footer;
34 |
--------------------------------------------------------------------------------
/container/Layout/Footer/FooterMenu.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Link from 'next/link';
3 | import Menu from 'components/UI/Antd/Menu/Menu';
4 |
5 | import {
6 | HOME_PAGE,
7 | LISTING_POSTS_PAGE,
8 | PRIVACY_PAGE,
9 | PRICING_PLAN_PAGE,
10 | USER_PROFILE_PAGE,
11 | } from 'settings/constants';
12 |
13 | const FooterMenu = () => (
14 |
41 | );
42 |
43 | export default FooterMenu;
44 |
--------------------------------------------------------------------------------
/container/Layout/Header/AuthMenu.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Menu from 'components/UI/Antd/Menu/Menu';
3 | import ActiveLink from 'library/helpers/activeLink';
4 |
5 | import { LOGIN_PAGE, REGISTRATION_PAGE } from 'settings/constants';
6 |
7 | const AuthMenu = ({ className }) => (
8 |
16 | );
17 |
18 | export default AuthMenu;
19 |
--------------------------------------------------------------------------------
/container/Layout/Header/AvatarImg.jsx:
--------------------------------------------------------------------------------
1 | import { useQuery } from 'react-apollo';
2 | import Logo from 'components/UI/Logo/Logo';
3 | import { GET_USER_INFO } from 'apollo-graphql/query/query';
4 |
5 | const AvatarImg = ({ user }) => {
6 | const {
7 | loading,
8 | data,
9 | } = useQuery(GET_USER_INFO, {
10 | variables: {
11 | id: user.id,
12 | },
13 | });
14 | if (loading) return '';
15 | const AvatarImgUrl = data && data.getUserInfo.profile_pic_main; // fix thành avatar ở backend gửi
16 | return ();
17 | };
18 |
19 | export default AvatarImg;
20 |
--------------------------------------------------------------------------------
/container/Layout/Header/MainMenu.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/prop-types */
2 | import React from 'react';
3 | import { withRouter } from 'next/router';
4 | import Menu from 'components/UI/Antd/Menu/Menu';
5 | import Notifications from 'components/Notifications/Notifications';
6 | import ActiveLink from 'library/helpers/activeLink';
7 |
8 | import {
9 | HOME_PAGE,
10 | LISTING_POSTS_PAGE,
11 | USER_PROFILE_PAGE,
12 | PRICING_PLAN_PAGE,
13 | SEARCH_TXID_PAGE,
14 | } from 'settings/constants'; // routing
15 |
16 | const MainMenu = ({
17 | className, router, id, isLoggedIn,
18 | }) => (
19 |
68 | );
69 |
70 | export default withRouter(MainMenu);
71 |
--------------------------------------------------------------------------------
/container/Layout/Header/MobileMenu.jsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react';
2 | import Menu from 'components/UI/Antd/Menu/Menu';
3 | import ActiveLink from 'library/helpers/activeLink';
4 | import { AuthContext } from 'context/AuthProvider';
5 | import {
6 | HOME_PAGE,
7 | LISTING_POSTS_PAGE,
8 | PRICING_PLAN_PAGE,
9 | USER_ACCOUNT_SETTINGS_PAGE,
10 | SEARCH_TXID_PAGE,
11 | } from 'settings/constants';
12 |
13 | // có thể coi className như props
14 | const MobileMenu = ({ className, sidebarHandler }) => {
15 | // auth context
16 | const { loggedIn, logOut } = useContext(AuthContext);
17 |
18 | return (
19 |
52 | );
53 | };
54 |
55 | export default MobileMenu;
56 |
--------------------------------------------------------------------------------
/container/Layout/Layout.jsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react';
2 | import { IntlProvider } from 'react-intl';
3 | import { ToastContainer } from 'react-toastify';
4 | import { withRouter } from 'next/router';
5 |
6 | import LayoutWrapper from 'components/UI/Antd/Layout/Layout';
7 |
8 | import Header from './Header/Header';
9 | import Footer from './Footer/Footer';
10 | import RightBar from './RightBar';
11 |
12 | import {
13 | LISTING_POSTS_PAGE,
14 | LOGIN_PAGE,
15 | REGISTRATION_PAGE,
16 | SINGLE_POST_PAGE,
17 | CHANGE_PASSWORD_PAGE,
18 | FORGET_PASSWORD_PAGE,
19 |
20 | } from 'settings/constants';
21 | import { LayoutProvider } from 'context/LayoutProvider';
22 | import { LanguageContext } from 'context/LanguageProvider';
23 |
24 | const { Content } = LayoutWrapper;
25 |
26 | const Layout = ({
27 | children, router, user, isLoggedIn,
28 | }) => {
29 | const { state, dispatch } = useContext(LanguageContext);
30 | return (
31 |
32 |
33 | {router.pathname === LOGIN_PAGE
34 | || router.pathname === CHANGE_PASSWORD_PAGE
35 | || router.pathname === FORGET_PASSWORD_PAGE
36 | || router.pathname === REGISTRATION_PAGE ? (
37 | {children}
38 | ) : (
39 | <>
40 |
41 |
46 |
47 |
48 | {children}
49 | {router.pathname === LISTING_POSTS_PAGE ? (
50 |
51 | ) : (
52 |
53 | )}
54 |
55 | >
56 | )}
57 |
58 |
59 | );
60 | };
61 |
62 | export default withRouter(Layout);
63 |
--------------------------------------------------------------------------------
/container/Layout/RightBar.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Link from 'next/link';
3 | import LanguageSwitcher from '../LanguageSwitcher/LanguageSwitcher';
4 | import bucketSVG from './bucket.svg';
5 | import IntlMessages from 'library/helpers/i18n';
6 | import ThemeSwitcherStyle from './RightBar.style';
7 |
8 | export default function ThemeSwitcher({ language, dispatch }) {
9 | const url = '//luudanthanchet.gitbook.io/palace-tripfinder/';
10 | const [isActivated, setIsActivated] = useState(false);
11 | const [showNote, setShowNote] = useState(false);
12 | const toggle = () => {
13 | setIsActivated(!isActivated);
14 | };
15 | return (
16 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
46 |
55 |
56 | );
57 | }
58 |
--------------------------------------------------------------------------------
/container/Listing/ListingMap.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import isEmpty from 'lodash/isEmpty';
3 | import Map, { MapDataProcessing } from 'components/Map/Map';
4 | import Loader from 'components/Loader/Loader';
5 | import { FixedMap } from './Listing.style';
6 |
7 | const ListingMap = (props) => {
8 | const { mapData, loading } = props;
9 | return (
10 |
11 | {isEmpty(mapData) || loading ? (
12 |
13 | ) : (
14 |
17 | )}
18 |
19 | );
20 | };
21 |
22 | export default ListingMap;
23 |
--------------------------------------------------------------------------------
/container/Listing/Search/SearchParams.jsx:
--------------------------------------------------------------------------------
1 |
2 | // default data cho CategorySearch
3 | export const priceInit = {
4 | 0: '$0',
5 | 1000: '$1000',
6 | };
7 |
8 | export const calendarItem = {
9 | separator: '-',
10 | format: 'MM-DD-YYYY',
11 | locale: 'en',
12 | };
13 |
14 | export const getAmenities = {
15 | id: 1,
16 | name: 'Amenities',
17 | identifier: 'amenities',
18 | options: [
19 | { label: 'Free Wi-Fi', value: 'free-wifi' },
20 | { label: 'Free Parking', value: 'free-parking' },
21 | { label: 'Breakfast included', value: 'breakfast', disabled: true },
22 | { label: 'Pool', value: 'pool' },
23 | { label: 'Air Conditioning', value: 'air-condition' },
24 | { label: 'Hot Shower', value: 'hot-shower', disabled: true },
25 | { label: 'Cable TV', value: 'cable-tv', disabled: true },
26 | ],
27 | };
28 |
29 | export const getPropertyType = {
30 | id: 2,
31 | name: 'Property Type',
32 | identifier: 'propertyType',
33 | options: [
34 | { label: 'Villa', value: 'Villa' },
35 | { label: 'Hotel', value: 'Hotel' },
36 | { label: 'Resort', value: 'Resort' },
37 | { label: 'Cottage', value: 'Cottage' },
38 | { label: 'Duplex', value: 'Duplex' },
39 | { label: 'Landscape', value: 'Landscape' },
40 | ],
41 | };
42 |
43 | export const sortListing = {
44 | id: 6,
45 | name: 'Sort',
46 | identifier: 'sortListing',
47 | options: [
48 | { label: 'Rating ASC', value: 'rating_ASC' },
49 | { label: 'Rating DESC', value: 'rating_DESC' },
50 | { label: 'Created ASC', value: 'createdAt_ASC' },
51 | { label: 'Created DESC', value: 'createdAt_DESC' },
52 | { label: 'Reviews MOST', value: 'ratingCount_DESC' },
53 | { label: 'Reviews LEAST', value: 'ratingCount_ASC' },
54 | { label: 'Price ASC', value: 'price_ASC' },
55 | { label: 'Price DESC', value: 'price_DESC' },
56 | ],
57 | };
58 |
--------------------------------------------------------------------------------
/container/Payment/InputBox.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable jsx-a11y/label-has-associated-control */
2 | import React from 'react';
3 | import Input from 'components/UI/Antd/Input/Input';
4 | import { InputBoxWrapper } from 'container/Payment/Payment.style';
5 |
6 | const InputBox = ({
7 | label, placeholder, important, defaultValue,
8 | }) => (
9 |
10 |
14 |
15 |
16 | );
17 |
18 | export default InputBox;
19 |
--------------------------------------------------------------------------------
/container/Pricing/Pricing.style.jsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { themeGet } from '@styled-system/theme-get';
3 |
4 | const PricingWrapper = styled.div`
5 | max-width: 1140px;
6 | margin: 0 auto;
7 | padding-bottom: 30px;
8 |
9 | @media only screen and (max-width: 1200px) {
10 | padding-left: 30px;
11 | padding-right: 30px;
12 | }
13 | @media only screen and (max-width: 480px) {
14 | padding: 0 25px;
15 | }
16 | `;
17 |
18 | export const PricingHeader = styled.div`
19 | text-align: center;
20 | padding: 60px 0;
21 | @media only screen and (max-width: 480px) {
22 | padding: 40px 0;
23 | }
24 | `;
25 |
26 | export const Title = styled.h2`
27 | color: ${themeGet('text.0', '#2C2C2C')};
28 | font-size: 25px;
29 | font-weight: 700;
30 | margin-bottom: 10px;
31 | `;
32 |
33 | export const Description = styled.p`
34 | color: ${themeGet('text.0', '#2C2C2C')};
35 | font-size: 15px;
36 | line-height: 18px;
37 | margin-bottom: 30px;
38 | `;
39 |
40 | export const PricingTableArea = styled.div`
41 | display: flex;
42 | flex-wrap: wrap;
43 | margin: 0 -15px;
44 |
45 | .price_card {
46 | width: calc(100% / 3 - 30px);
47 | margin: 0 15px;
48 |
49 | @media only screen and (max-width: 767px) {
50 | width: 100%;
51 | margin-bottom: 30px;
52 | }
53 | }
54 | `;
55 |
56 | export const ButtonGroup = styled.div`
57 | width: 244px;
58 | min-height: 47px;
59 | padding: 5px;
60 | border-radius: 3px;
61 | background-color: ${themeGet('color.2', '#F7F7F7')};
62 | margin: 0 auto;
63 | display: flex;
64 | align-items: center;
65 |
66 | button {
67 | color: ${themeGet('text.0', '#2C2C2C')};
68 | font-size: 15px;
69 | min-width: 117px;
70 | min-height: 37px;
71 | display: flex;
72 | align-items: center;
73 | justify-content: center;
74 | border: none;
75 | background: none;
76 | cursor: pointer;
77 |
78 | &.active {
79 | font-weight: 700;
80 | border-radius: 3px;
81 | box-shadow: 0 0 1px rgba(0, 0, 0, 0.16);
82 | background-color: ${themeGet('color.1', '#ffffff')};
83 | }
84 |
85 | &:hover,
86 | &:focus {
87 | text-decoration: none;
88 | outline: none;
89 | }
90 | }
91 | `;
92 |
93 | export const Button = styled.button``;
94 |
95 | export default PricingWrapper;
96 |
--------------------------------------------------------------------------------
/container/Pricing/PricingItems.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PriceCard from 'components/PriceCard/PriceCard';
3 |
4 | export default function PricingItems({ user, plans }) {
5 | return plans.map((plan) => (
6 |
13 | ));
14 | }
15 |
--------------------------------------------------------------------------------
/container/SignIn/SignInForm.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-expressions */
2 | import React, { useContext } from 'react';
3 | import { Formik } from 'formik';
4 | import Router from 'next/router';
5 | import RenderSignInForm from 'components/SignIn/RenderSignInForm';
6 | import * as Yup from 'yup';
7 | import { AuthContext } from 'context/AuthProvider';
8 | import { FORGET_PASSWORD_PAGE } from 'settings/constants';
9 |
10 | const SignInForm = ({ prev }) => {
11 | const initialValues = {
12 | email: '',
13 | password: '',
14 | rememberMe: false,
15 | };
16 |
17 | const getLoginValidationSchema = () => Yup.object().shape({
18 | email: Yup.string()
19 | .email('Invalid Email')
20 | .required('Email is required'),
21 | password: Yup.string()
22 | .min(6, 'Password is too short')
23 | .max(20, 'Password is too long')
24 | .required('Password is required!'),
25 | });
26 |
27 | const { signIn, loggedIn } = useContext(AuthContext);
28 | if (loggedIn) {
29 | prev ? Router.push(prev) : Router.push('/');
30 | }
31 | const handleSubmit = (formProps) => {
32 | signIn(formProps);
33 | };
34 | return (
35 | // tất cả các props của formik
36 |
41 | {(props) => (
42 |
46 | )}
47 |
48 | );
49 | };
50 | export default SignInForm;
51 |
--------------------------------------------------------------------------------
/container/SignUp/SignUpForm.jsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react';
2 | import { Formik } from 'formik';
3 | import Router from 'next/router';
4 | import * as Yup from 'yup';
5 | import RenderSignUpForm from 'components/SignUp/RenderSignUpForm';
6 | import { AuthContext } from '../../context/AuthProvider';
7 |
8 | const initialValues = {
9 | email: '',
10 | username: '',
11 | password: '',
12 | confirmPassword: '',
13 | termsAndConditions: false,
14 | rememberMe: false,
15 | };
16 |
17 | const getRegisterValidationSchema = () => Yup.object().shape({
18 | username: Yup.string()
19 | .min(2, 'Too Short!')
20 | .max(50, 'Too Long!')
21 | .required('Username is Required!'),
22 | email: Yup.string()
23 | .email('Invalid email')
24 | .required('Email is Required!'),
25 | password: Yup.string()
26 | .min(6, 'Password has to be longer than 6 characters!')
27 | .max(20, 'Too Long!')
28 | .required('Password is required!'),
29 | confirmPassword: Yup.string()
30 | .oneOf([Yup.ref('password'), null], 'Passwords are not the same!')
31 | .required('Password confirmation is required'),
32 | termsAndConditions: Yup.bool()
33 | .test(
34 | 'consent',
35 | 'You have to agree with our Terms and Conditions!',
36 | (value) => value === true,
37 | )
38 | .required('You have to agree with our Terms and Conditions!'),
39 | });
40 |
41 | export default () => {
42 | const { signUp, loggedIn } = useContext(AuthContext);
43 | if (loggedIn) Router.push('/');
44 | const handleSubmit = (formProps) => {
45 | signUp(formProps);
46 | };
47 | return (
48 |
53 | {RenderSignUpForm}
54 |
55 | );
56 | };
57 |
--------------------------------------------------------------------------------
/container/SinglePage/Amenities/Amenities.style.jsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const AmenitiesWrapper = styled.div`
4 | padding: 29px 0;
5 |
6 | .amenities_title {
7 | margin-bottom: 30px;
8 | }
9 |
10 | a {
11 | &:hover {
12 | color: #31b8bd;
13 | }
14 | }
15 | `;
16 |
17 | export const AmenitiesArea = styled.div`
18 | display: flex;
19 | flex-wrap: wrap;
20 | width: 100%;
21 | justify-content: space-between;
22 | margin-bottom: -15px;
23 |
24 | > div {
25 | width: calc(100% / 4 - 10px);
26 |
27 | @media only screen and (max-width: 767px) {
28 | width: calc(100% / 3 - 10px);
29 | margin-bottom: 20px;
30 | }
31 |
32 | @media only screen and (max-width: 580px) {
33 | width: calc(100% / 2 - 10px);
34 | margin-bottom: 20px;
35 | }
36 | }
37 | `;
38 |
39 | export default AmenitiesWrapper;
40 |
--------------------------------------------------------------------------------
/container/SinglePage/Description/Description.style.jsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { themeGet } from '@styled-system/theme-get';
3 |
4 | const DescriptionWrapper = styled.div`
5 | padding: 0 0 29px;
6 |
7 | button {
8 | font-size: 15px;
9 | font-weight: 700;
10 | border: 0;
11 | padding: 0;
12 | height: auto;
13 | line-height: 1;
14 | box-shadow: none;
15 | text-shadow: none;
16 | color: ${themeGet('primary.0', '#008489')};
17 |
18 | &:hover,
19 | &:focus {
20 | color: ${themeGet('primary.1', '#399C9F')};
21 | }
22 |
23 | &::after {
24 | content: none;
25 | }
26 | }
27 | `;
28 |
29 | export default DescriptionWrapper;
30 |
--------------------------------------------------------------------------------
/container/SinglePage/ImageGallery/ImageGallery.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ImageGallery from 'react-image-gallery';
3 | import ImageGalleryWrapper from './ImageGallery.style';
4 |
5 | // Mock
6 |
7 | // import PostImage1 from 'assets/images/post-image-1.jpg';
8 | // import PostImage2 from 'assets/images/post-image-2.jpg';
9 | // import PostThumb1 from 'assets/images/post-thumb-1.jpg';
10 | // import PostThumb2 from 'assets/images/post-thumb-2.jpg';
11 |
12 | // const images = [
13 | // {
14 | // original: PostImage1,
15 | // thumbnail: PostThumb1,
16 | // },
17 | // {
18 | // original: PostImage2,
19 | // thumbnail: PostThumb2,
20 | // },
21 | // ];
22 |
23 | const PostImageGallery = ({ gallery }) => {
24 | const temp = [];
25 | gallery.map((i) => {
26 | temp.push({
27 | original: i.url,
28 | });
29 | return temp;
30 | });
31 | // Ảnh gallery từ mock data sẽ có quality thấp
32 | return (
33 |
34 |
42 |
43 | );
44 | };
45 |
46 | export default PostImageGallery;
47 |
--------------------------------------------------------------------------------
/container/SinglePage/Location/Location.style.jsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const LocationWrapper = styled.div`
4 | padding: 29px 0;
5 | .location_meta {
6 | margin-bottom: 29px;
7 | }
8 | a {
9 | &:hover {
10 | color: #31b8bd;
11 | }
12 | }
13 | `;
14 |
15 | export default LocationWrapper;
16 |
--------------------------------------------------------------------------------
/container/User/AccountDetails/Chart/Pie/Pie.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable camelcase */
2 | import React, { useState, useMemo } from 'react';
3 | import { useQuery } from 'react-apollo';
4 | import { Pie } from 'react-chartjs-2';
5 | import { GET_USER_POSTS } from 'apollo-graphql/query/query';
6 | import { processPieData } from './PieConfig';
7 | import Loader from 'components/Loader/Loader';
8 | import Button from 'components/UI/Antd/Button/Button';
9 |
10 | const PieChart = ({ user }) => {
11 | const [fetch, setFetch] = useState(false);
12 | const {
13 | loading, error, data,
14 | } = useQuery(GET_USER_POSTS, {
15 | variables: {
16 | uid: user.id,
17 | },
18 | fetchPolicy: 'cache-and-network',
19 | });
20 |
21 |
22 | const listed_posts = useMemo(() => {
23 | if (loading || error) return ;
24 | // console.log('changed');
25 | // return data && data.userPosts.listed_posts
26 | // ? data.userPosts.listed_posts : [];
27 | return processPieData(data.userPosts.listed_posts);
28 | }, [loading, error, data, fetch]);
29 |
30 | if (loading) return ;
31 | if (error) return `Error${error} occurred`;
32 |
33 |
34 | return (
35 | <>
36 |
39 |
40 | >
41 | );
42 | };
43 | export default PieChart;
44 |
--------------------------------------------------------------------------------
/container/User/AccountDetails/Chart/Pie/PieConfig.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-return-assign */
2 |
3 | const processPieData = (data) => {
4 | let one = 0;
5 | let two = 0;
6 | let three = 0;
7 | let four = 0;
8 | let five = 0;
9 | data.map((i) => {
10 | let overall = i.rating || Math.floor(Math.random() * 5) + 1; // 1 to 5
11 | overall /= ((i.reviews && i.reviews.length > 0) || 1);
12 | // console.log(overall);
13 | if (overall > 0 && overall <= 1) {
14 | one++;
15 | }
16 | if (overall > 1 && overall <= 2) {
17 | two++;
18 | }
19 | if (overall > 2 && overall <= 3) {
20 | three++;
21 | }
22 | if (overall > 3 && overall <= 4) {
23 | four++;
24 | }
25 | if (overall > 4) {
26 | five++;
27 | }
28 | });
29 | return {
30 | labels: ['1🌟', '2🌟', '3🌟', '4🌟', '5🌟'],
31 | datasets: [
32 | {
33 | data: [one, two, three, four, five],
34 | backgroundColor: ['#FF6384', '#48A6F2', '#ffbf00', '#ccffff', '#ff6600'],
35 | hoverBackgroundColor: ['#FF6384', '#48A6F2', '#ffbf00', '#ccffff', '#ff6600'],
36 | },
37 | ],
38 | };
39 | };
40 |
41 | export { processPieData };
42 |
--------------------------------------------------------------------------------
/container/User/AccountDetails/Chart/Radar/Radar.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable camelcase */
2 | import React, { useState, useMemo } from 'react';
3 | import { useQuery } from 'react-apollo';
4 | import { Radar } from 'react-chartjs-2';
5 | import { GET_USER_POSTS } from 'apollo-graphql/query/query';
6 | import { processRadarData } from './RadarConfig';
7 | import Loader from 'components/Loader/Loader';
8 | import Button from 'components/UI/Antd/Button/Button';
9 |
10 | const RadarChart = ({ user }) => {
11 | const [fetch, setFetch] = useState(false);
12 | const {
13 | loading, error, data,
14 | } = useQuery(GET_USER_POSTS, {
15 | variables: {
16 | uid: user.id,
17 | },
18 | fetchPolicy: 'cache-and-network',
19 | });
20 | const listed_posts = useMemo(() => {
21 | if (loading || error) return ;
22 | // return data && data.userPosts.listed_posts
23 | // ? data.userPosts.listed_posts : [];
24 | return processRadarData(data.userPosts.listed_posts);
25 | }, [loading, error, data, fetch]);
26 |
27 | if (loading) return ;
28 | if (error) return `Error${error} occurred`;
29 |
30 | return (
31 | <>
32 |
35 |
36 | >
37 | );
38 | };
39 | export default RadarChart;
40 |
--------------------------------------------------------------------------------
/container/User/AccountDetails/Chart/Radar/RadarConfig.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-return-assign */
2 | const processRadarData = (data) => {
3 | let hotel = 0;
4 | let villa = 0;
5 | let duplex = 0;
6 | let cottage = 0;
7 | let landscape = 0;
8 | let resort = 0;
9 | data.map((i) => {
10 | if (i.propertyType === 'Hotel') hotel += 1;
11 | if (i.propertyType === 'Villa') villa += 1;
12 | if (i.propertyType === 'Resort') duplex += 1;
13 | if (i.propertyType === 'Duplex') cottage += 1;
14 | if (i.propertyType === 'Cottage') landscape += 1;
15 | if (i.propertyType === 'Landscape') resort += 1;
16 | });
17 | return {
18 | labels: [
19 | 'Hotel',
20 | 'Villa',
21 | 'Duplex',
22 | 'Cottage',
23 | 'Landscape',
24 | 'Resort',
25 | ],
26 | datasets: [
27 | {
28 | label: 'Hotel, Villa, Duplex',
29 | backgroundColor: 'rgba(72,166,242,0.2)',
30 | borderColor: 'rgba(72,166,242,1)',
31 | pointBackgroundColor: 'rgba(72,166,242,1)',
32 | pointBorderColor: '#fff',
33 | pointHoverBackgroundColor: '#fff',
34 | pointHoverBorderColor: 'rgba(72,166,242,1)',
35 | data: [
36 | hotel + Math.floor(Math.random() * 10) + 1,
37 | villa + Math.floor(Math.random() * 10) + 1,
38 | duplex + Math.floor(Math.random() * 10) + 1,
39 | ],
40 | },
41 | {
42 | label: 'Cottage, landscape, resort',
43 | backgroundColor: 'rgba(255,99,132,0.2)',
44 | borderColor: 'rgba(255,99,132,1)',
45 | pointBackgroundColor: 'rgba(255,99,132,1)',
46 | pointBorderColor: '#fff',
47 | pointHoverBackgroundColor: '#fff',
48 | pointHoverBorderColor: 'rgba(255,99,132,1)',
49 | data: [
50 | cottage + Math.floor(Math.random() * 10) + 1,
51 | landscape + Math.floor(Math.random() * 10) + 1,
52 | resort + Math.floor(Math.random() * 10) + 1,
53 | ],
54 | },
55 | ],
56 | };
57 | };
58 | export { processRadarData };
59 |
--------------------------------------------------------------------------------
/container/User/AccountDetails/UserFavoriteItemLists.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-extraneous-dependencies */
2 | /* eslint-disable camelcase */
3 | import React from 'react';
4 | import { useQuery } from '@apollo/react-hooks';
5 | import SectionGrid from 'components/SectionGrid/SectionGrid';
6 | import { PostPlaceholder } from 'components/UI/ContentLoader/ContentLoader';
7 | import { SINGLE_POST_PAGE } from 'settings/constants';
8 | import { GET_USER_POSTS } from 'apollo-graphql/query/query';
9 | import Loader from 'components/Loader/Loader';
10 |
11 | const FAVOURITE_POST_LIMIT = 6;
12 | export default function AgentFavItemLists({
13 | // processedData,
14 | loadMoreData,
15 | loadingPost,
16 | deviceType,
17 | user,
18 | // userInfo,
19 | }) {
20 | // const favourite_post = processedData[0] && processedData[0].favourite_post
21 | // ? processedData[0].favourite_post
22 | // : [];
23 | const { loading, error, data } = useQuery(GET_USER_POSTS, {
24 | variables: {
25 | uid: user.id,
26 | },
27 | fetchPolicy: 'cache-and-network',
28 | });
29 | if (loading) return ;
30 |
31 | // Query 1 lần có được từ props userInfo
32 | // const favourite_post = userInfo && userInfo.favourite_post ? userInfo.favourite_post : [];
33 |
34 | // Query từng tab - live update
35 | const favourite_post = data && data.userPosts.favourite_post ? data.userPosts.favourite_post : [];
36 | return (
37 | }
49 | />
50 | );
51 | }
52 |
--------------------------------------------------------------------------------
/container/User/AccountDetails/UserItemLists.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable camelcase */
2 | // import { useState } from 'react';
3 | import { useQuery } from 'react-apollo';
4 | import SectionGrid from 'components/SectionGrid/SectionGrid';
5 | import { PostPlaceholder } from 'components/UI/ContentLoader/ContentLoader';
6 | import { SINGLE_POST_PAGE } from 'settings/constants';
7 | import { GET_USER_POSTS } from 'apollo-graphql/query/query';
8 | import Loader from 'components/Loader/Loader';
9 |
10 | const LISTED_POST_LIMIT = 6;
11 | export default function AgentItemLists({
12 | // processedData,
13 | loadMoreData,
14 | loadingPost,
15 | deviceType,
16 | user,
17 | // userInfo
18 | }) {
19 | // Mock data thì uncomment dòng dưới
20 | // const listed_posts = processedData[0] && processedData[0].listed_posts
21 | // ? processedData[0].listed_posts
22 | // : [];
23 | // Đây là query life refresh, giữa các tab
24 | // Có thể sử dụng prop userInfo để tận dụng query 1 lần xài cho tất cả tabs
25 | const {
26 | loading, error, data,
27 | } = useQuery(GET_USER_POSTS, {
28 | variables: {
29 | uid: user.id,
30 | },
31 | fetchPolicy: 'cache-and-network',
32 | });
33 | if (loading) return ;
34 | if (error) return `Error${error} occurred`;
35 | const listed_posts = data && data.userPosts.listed_posts ? data.userPosts.listed_posts : [];
36 | const favourite_post = data && data.userPosts.favourite_post ? data.userPosts.favourite_post : [];
37 | return (
38 | }
50 | />
51 | );
52 | }
53 |
--------------------------------------------------------------------------------
/container/User/index.jsx:
--------------------------------------------------------------------------------
1 | export {
2 | default as UserAccountSettingsPage,
3 | } from './AccountSettings/UserAccountSettingsPage';
4 |
5 | export {
6 | default as UserDetailsPage,
7 | } from './AccountDetails/UserDetailsPage';
8 |
--------------------------------------------------------------------------------
/container/blankPage.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | // destructuring ngay lúc truyền vào
3 | // //const BlankPage = (props) => {
4 | // const { children } = props;
5 | // return <>{children}>;
6 | // };
7 | // //
8 | const BlankPage = ({ children }) => (
9 | <>
10 | {children}
11 | >
12 | );
13 | export default BlankPage;
14 |
--------------------------------------------------------------------------------
/context/LanguageProvider.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-param-reassign */
2 | /* eslint-disable no-return-assign */
3 | import React, { createContext, useReducer, useEffect } from 'react';
4 | import AppLocale from 'translations/index';
5 |
6 | export const LanguageContext = createContext();
7 |
8 | const reducer = (state, action) => {
9 | switch (action.type) {
10 | case 'SWITCH':
11 | // console.log('state changed');
12 | return state = AppLocale[`${localStorage.getItem('lang') || 'en'}`];
13 | default:
14 | return state;
15 | }
16 | };
17 |
18 | export const LanguageProvider = ({ children, language }) => {
19 | // console.log('Before reduce');
20 | // console.log(language);
21 | const [state, dispatch] = useReducer(reducer, language);
22 | // Initial State bị thay đổi của useReducer
23 | // Để update được state trả về của useReducer để useContext
24 | // Ta phải tự update state
25 | // Tự update khi state thay đổi bằng useEffect
26 | // Truyền dependency là state -> return lại state là giá trị trả về
27 | // của hàm reducer ở trên khi state của useReducer bị thay đổi
28 | // Update lại state của useReducer và truyền vào provider
29 | const init = () => {
30 | dispatch({
31 | type: 'SWITCH',
32 | });
33 | };
34 | // console.log('Before effect');
35 | // console.log(state);
36 | useEffect(() => {
37 | init();
38 | }, [state]);
39 | // console.log('After reduce');
40 | // console.log(state);
41 | return (
42 |
43 | {children}
44 |
45 | );
46 | };
47 |
--------------------------------------------------------------------------------
/context/LayoutProvider.jsx:
--------------------------------------------------------------------------------
1 | import React, { createContext, useContext, useReducer } from 'react';
2 |
3 | export const LayoutContext = createContext();
4 |
5 | const initialState = {
6 | searchVisibility: false,
7 | };
8 | const reducer = (state, action) => {
9 | switch (action.type) {
10 | case 'SHOW_TOP_SEARCHBAR':
11 | return {
12 | ...state,
13 | searchVisibility: true,
14 | };
15 | case 'HIDE_TOP_SEARCHBAR':
16 | return {
17 | ...state,
18 | searchVisibility: false,
19 | };
20 | default:
21 | return state;
22 | }
23 | };
24 |
25 | export const LayoutProvider = ({ children }) => {
26 | const [state, dispatch] = useReducer(reducer, initialState);
27 | return (
28 |
29 | {children}
30 |
31 | );
32 | };
33 |
34 | export const useStateValue = () => useContext(LayoutContext);
35 |
--------------------------------------------------------------------------------
/context/ReviewProvider.jsx:
--------------------------------------------------------------------------------
1 | import React, { createContext, useReducer } from 'react';
2 |
3 | export const ReviewContext = createContext();
4 |
5 | const reducer = (state, action) => {
6 | switch (action.type) {
7 | case 'ADD_COMMENT':
8 | state.unshift(action.payload);
9 | return state;
10 | default:
11 | return state;
12 | }
13 | };
14 |
15 | export const ReviewProvider = ({ children, reviews }) => {
16 | const [stateReviews, dispatch] = useReducer(reducer, reviews);
17 | return (
18 |
19 | {children}
20 |
21 | );
22 | };
23 |
--------------------------------------------------------------------------------
/context/SearchProvider.jsx:
--------------------------------------------------------------------------------
1 | import React, { createContext, useReducer } from 'react';
2 |
3 | export const SearchContext = createContext();
4 |
5 | const reducer = (state, action) => {
6 | switch (action.type) {
7 | case 'UPDATE':
8 | return { ...state, ...action.payload };
9 | default:
10 | return state;
11 | }
12 | };
13 |
14 | export const SearchProvider = ({ children, query }) => {
15 | const [state, dispatch] = useReducer(reducer, query);
16 | return (
17 |
18 | {children}
19 |
20 | );
21 | };
22 |
--------------------------------------------------------------------------------
/i18n.js:
--------------------------------------------------------------------------------
1 | // /* eslint-disable import/no-extraneous-dependencies */
2 | // // import i18n from 'i18next';
3 | // // import { initReactI18next } from 'react-i18next';
4 |
5 | // import Backend from 'i18next-http-backend';
6 | // import LanguageDetector from 'i18next-browser-languagedetector';
7 | // // // not like to use this?
8 | // // // have a look at the Quick start guide
9 | // // // for passing in lng and translations on init
10 |
11 | // // i18n
12 | // // .use(Backend)
13 | // // .use(LanguageDetector)
14 | // // .use(initReactI18next)
15 | // // .init({
16 | // // fallbackLng: 'en',
17 | // // debug: true,
18 | // // interpolation: {
19 | // // escapeValue: false,
20 | // // },
21 | // // });
22 |
23 |
24 | // // export default i18n;
25 | // const NextI18Next = require('next-i18next').default;
26 | // const { localeSubpaths } = require('next/config').default().publicRuntimeConfig;
27 | // const path = require('path');
28 |
29 | // module.exports = new NextI18Next({
30 | // defaultLanguage: 'en',
31 | // otherLanguages: ['fr', 'vi'],
32 | // localeSubpaths,
33 | // localePath: path.resolve('./public/static/locales'),
34 | // use: [Backend, LanguageDetector],
35 | // });
36 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | setupFilesAfterEnv: ['/jest.setup.js'],
3 | setupFiles: ['/settings/setup.js'],
4 | testPathIgnorePatterns: ['/.next/', '/node_modules/'],
5 | moduleNameMapper: {
6 | // media assets parse vào mock fileMock
7 | '\\.(jpg|ico|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '/__mocks__/fileMock.js',
8 | // styling parse vào mock styleMock
9 | '\\.(css|scss)$': '/__mocks__/styleMock.js',
10 | // Config cho absolute path sử dụng regex để jest đọc file
11 | '^components/(.*)$': '/components/$1',
12 | '^library/(.*)$': '/library/$1',
13 | '^settings/(.*)$': '/settings/$1',
14 | '^assets/(.*)$': '/assets/$1',
15 | '^container/(.*)': '/container/$1',
16 | 'tests/(.*)': '/__tests__/$1',
17 | },
18 | };
19 |
--------------------------------------------------------------------------------
/jest.setup.js:
--------------------------------------------------------------------------------
1 | import '@testing-library/jest-dom/extend-expect';
2 | import '@testing-library/react/dont-cleanup-after-each';
3 |
--------------------------------------------------------------------------------
/jsConfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "./",
4 | "paths": {
5 | "components/*": ["components/*"],
6 | "tests/*": ["__tests__/*"]
7 | }
8 | },
9 | "exclude": ["node_modules"]
10 | }
11 |
--------------------------------------------------------------------------------
/library/helpers/activeLink.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/prop-types */
2 | import { withRouter } from 'next/router';
3 | // xử lý router của nextjs - component link cho navbar
4 | const ActiveLink = ({
5 | className, children, router, href,
6 | }) => {
7 | const handleClick = (e) => {
8 | e.preventDefault();
9 | router.push(href); // push theo href được truyền vào từ Layout->Header->MainMenu
10 | }; // xử lý sự kiện click
11 | return (
12 |
13 | {children}
14 |
15 | );
16 | };
17 |
18 | export default withRouter(ActiveLink);
19 |
--------------------------------------------------------------------------------
/library/helpers/get_api_data.jsx:
--------------------------------------------------------------------------------
1 | import fetch from 'isomorphic-unfetch';
2 | import shuffle from 'lodash/shuffle';
3 | // fetch của Next-js recommend
4 |
5 | const FetchAPIData = (url) => fetch(url)
6 | .then((r) => r.json())
7 | .then((data) => data);
8 |
9 | export const ProcessAPIData = (apiData) => {
10 | const fetchData = {};
11 | if (apiData) {
12 | apiData.forEach((item, key) => {
13 | fetchData.data = item.data ? [...item.data] : [];
14 | fetchData.name = item.name ?? '';
15 | });
16 | }
17 | // mảng object fetched data
18 | const data = fetchData ?? [];
19 | return data;
20 | };
21 |
22 | export const SearchedData = (processedData) => {
23 | const randNumber = Math.floor(Math.random() * 50 + 1);
24 | const data = shuffle(processedData.slice(0, randNumber));
25 | return data;
26 | };
27 |
28 | export const SearchStateKeyCheck = (state) => {
29 | Object.keys(state).forEach((key) => {
30 | if (
31 | state[key] !== null
32 | && state[key] !== ''
33 | && state[key] !== []
34 | && state[key] !== 0
35 | && state[key] !== 100
36 | ) { return true; }
37 | return false;
38 | });
39 | };
40 |
41 | // Ki thuat paginator - spread data hien giờ + slice số lượng data thêm vô
42 | // bắt đầu từ: độ dài hiện giờ của posts
43 | // kết thúc từ độ dài hiện giờ của posts + số lượng data responsive từ limit
44 | export const Paginator = (posts, processedData, limit) => [...posts, ...processedData
45 | .slice(posts.length, posts.length + limit)];
46 |
47 |
48 | const GetAPIData = async (apiUrl) => {
49 | const promises = apiUrl.map(async (repo) => {
50 | const apiPath = `${process.env.SERVER_API}/static/data`; // đọc từ biến env
51 | const api = `${apiPath}/${repo.endpoint}.json`;
52 | // console.log(api, 'api api');
53 | const response = await FetchAPIData(api);
54 | return {
55 | name: repo.name,
56 | data: response,
57 | };
58 | });
59 | const receviedData = await Promise.all(promises);
60 | return receviedData;
61 | };
62 | export default GetAPIData;
63 |
--------------------------------------------------------------------------------
/library/helpers/get_device_type.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/prefer-default-export */
2 | import MobileDetect from 'mobile-detect';
3 |
4 | export function getDeviceType(req) {
5 | let userAgent;
6 | let deviceType;
7 | if (req) {
8 | userAgent = req.headers['user-agent'];
9 | } else {
10 | userAgent = navigator.userAgent;
11 | }
12 | const md = new MobileDetect(userAgent);
13 | if (md.tablet()) {
14 | deviceType = 'tablet';
15 | } else if (md.mobile()) {
16 | deviceType = 'mobile';
17 | } else {
18 | deviceType = 'desktop';
19 | }
20 | return deviceType;
21 | }
22 |
--------------------------------------------------------------------------------
/library/helpers/gtag.jsx:
--------------------------------------------------------------------------------
1 | export const GA_TRACKING_ID = process.env.G_ID; // GA_Tracking ID
2 | // https://developers.google.com/analytics/devguides/collection/gtagjs/pages
3 | export const pageview = (url) => {
4 | window.gtag('config', GA_TRACKING_ID, {
5 | page_path: url,
6 | });
7 | };
8 |
9 | // https://developers.google.com/analytics/devguides/collection/gtagjs/events
10 | export const event = ({
11 | action, category, label, value,
12 | }) => {
13 | window.gtag('event', action, {
14 | event_category: category,
15 | event_label: label,
16 | value,
17 | });
18 | };
19 |
--------------------------------------------------------------------------------
/library/helpers/i18n.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { injectIntl, FormattedMessage } from 'react-intl';
3 |
4 | const InjectMassage = (props) => ;
5 | export default injectIntl(InjectMassage, {
6 | withRef: false,
7 | });
8 |
--------------------------------------------------------------------------------
/library/helpers/redirect.jsx:
--------------------------------------------------------------------------------
1 | import Router from 'next/router';
2 |
3 | /**
4 | * Redirect đến bất kì url
5 | */
6 | export default (ctx = {}, target) => {
7 | if (ctx.res) {
8 | // Nếu đang SSR, trả về HTTP 303 và response Location
9 | // Dùng để redirect.
10 | ctx.res.writeHead(303, { Location: target });
11 | ctx.res.end();
12 | } else {
13 | // Sử dụng Replace của Router (next) để replace cái
14 | // Location của cái mới, xóa khỏi history (history.push)
15 | Router.replace(target);
16 | }
17 | };
18 |
--------------------------------------------------------------------------------
/library/helpers/restriction.jsx:
--------------------------------------------------------------------------------
1 | import nextCookie from 'next-cookies';
2 | import { getCookie, TOKEN_COOKIE, USER_COOKIE } from './session';
3 | import redirect from './redirect';
4 |
5 | export const isAuthenticated = (ctx) => {
6 | const token = getCookie(TOKEN_COOKIE, ctx);
7 | const isLoggedIn = !!token;
8 | // token ? true: false
9 | if (isLoggedIn) redirect(ctx, '/');
10 | return { isLoggedIn };
11 | };
12 |
13 | export const secretPage = (ctx) => {
14 | // console.log(ctx)
15 | // ctx từ getInitialProps
16 | const token = getCookie(TOKEN_COOKIE, ctx);
17 | const isLoggedIn = !!token;
18 | if (!isLoggedIn) {
19 | // Có thể sử dụng ctx.pathname để lấy prevUrl
20 | // Global Redirect trang trước
21 | redirect(ctx, '/login');
22 | }
23 | return { isLoggedIn };
24 | };
25 | export const getIsLoggedIn = (ctx) => {
26 | // console.log(ctx)
27 | // ctx từ getInitialProps
28 | const token = getCookie(TOKEN_COOKIE, ctx);
29 | const isLoggedIn = !!token;
30 | return isLoggedIn;
31 | };
32 |
33 | // parse data
34 |
35 | export const withPaymentSecret = (ctx) => {
36 | // getCookie có 2 phase, client side và server side
37 | // Lúc bấm button booking sẽ trigger client side và get đúng secret
38 | // Nếu user f5 hoặc load trang từ đâu đó
39 | // hoặc bất cứ method nào ngoài client sẽ trigger server side
40 | // Vô tình trả sai payload và getIntialProps redirect qua /error
41 | const secret = getCookie('secret', ctx);
42 | return secret;
43 | };
44 | export const withChangePasswordSecret = (ctx) => {
45 | const cookieServer = nextCookie(ctx);
46 | const secret = cookieServer && cookieServer['reset-password'];
47 | return secret;
48 | };
49 | export const withData = (ctx) => {
50 | const token = getCookie(TOKEN_COOKIE, ctx);
51 | const isLoggedIn = !!token;
52 | const isUser = getCookie(USER_COOKIE, ctx);
53 | const userCookie = isUser ? JSON.parse(isUser) : {};
54 | const user = userCookie || {};
55 | return { user, isLoggedIn };
56 | };
57 |
--------------------------------------------------------------------------------
/library/helpers/rtl.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | let direction = 'ltr';
4 | if (typeof window !== 'undefined') {
5 | direction = document.getElementsByTagName('html')[0].getAttribute('dir');
6 | }
7 | const withDirection = Component => props => {
8 | return ;
9 | };
10 |
11 | export default withDirection;
12 | export { direction };
13 |
--------------------------------------------------------------------------------
/library/helpers/session.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-prototype-builtins */
2 | import cookie from 'js-cookie';
3 | import nextCookie from 'next-cookies';
4 |
5 | export const FIREBASE_COOKIE = '__session'; // firebase chỉ accept __session cookie
6 | export const TOKEN_COOKIE = 'token';
7 | export const USER_COOKIE = 'user';
8 |
9 | // Cho client khi CSR
10 | // Ở đây khi làm BE đã parse vô luôn browser
11 | export const getCookieFromBrowser = (key) => cookie.get(key);
12 |
13 | // Khi đang SSR - genrate = nextCookie, gắn vô key 'id_token' trong tab cookie
14 | // Dành cho monorepo hoặc không parse cookie
15 | // Xử lý refresh page lúc process.browser chưa nhận
16 | const getCookieFromServer = (ctx, key) => {
17 | const specifiKey = key === 'token' ? 'token' : 'user';
18 | const cookieServer = nextCookie(ctx);
19 | const token = cookieServer && cookieServer[specifiKey] ? cookieServer[specifiKey] : false;
20 | if (!token) return null;
21 | return JSON.stringify(token);
22 | };
23 |
24 | // Get Cookie và set context phù hợp cho CSR (chỉ cần key token để auth)
25 | // SSR(Trả về cả context và key cho meaningful data lần đầu)
26 | export const getCookie = (key, context = {}) => (process.browser ? getCookieFromBrowser(key)
27 | : getCookieFromServer(context, key));
28 |
29 | // Xử lý các option của cookies
30 | // 7 = 7 days
31 | export const setCookie = (key, token) => {
32 | cookie.set(key, token, { expires: 7 });
33 | };
34 |
35 | // Xóa cookie - signed out/change password
36 | export const removeCookie = (key) => {
37 | cookie.remove(key);
38 | };
39 |
--------------------------------------------------------------------------------
/library/helpers/validators/fieldFormats.jsx:
--------------------------------------------------------------------------------
1 | export const dateFormat = 'MM-DD-YYYY';
2 | export const timeFormat = 'HH:mm';
3 |
--------------------------------------------------------------------------------
/library/hooks/useLocation.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable prefer-rest-params */
2 | /* eslint-disable func-names */
3 |
4 | // Tham khảo từ https://hooks-guide.netlify.app/react-use/useLocation
5 |
6 |
7 | import { useEffect, useState } from 'react';
8 |
9 | // utils
10 | const isClient = typeof window === 'object';
11 | const on = (obj, ...args) => obj.addEventListener(...args);
12 | const off = (obj, ...args) => obj.removeEventListener(...args);
13 |
14 | const patchHistoryMethod = (method) => {
15 | const original = window.history[method];
16 | window.history[method] = function (state) {
17 | const result = original.apply(this, arguments);
18 | const event = new Event(method.toLowerCase());
19 | event.state = state;
20 | window.dispatchEvent(event);
21 | return result;
22 | };
23 | };
24 | if (isClient) {
25 | patchHistoryMethod('pushState');
26 | patchHistoryMethod('replaceState');
27 | }
28 | export const useLocation = () => {
29 | const buildState = (trigger) => {
30 | const { state, length } = window.history;
31 | const {
32 | hash,
33 | host,
34 | hostname,
35 | href,
36 | origin,
37 | pathname,
38 | port,
39 | protocol,
40 | search,
41 | } = window.location;
42 | return {
43 | trigger,
44 | state,
45 | length,
46 | hash,
47 | host,
48 | hostname,
49 | href,
50 | origin,
51 | pathname,
52 | port,
53 | protocol,
54 | search,
55 | };
56 | };
57 | const [state, setState] = useState(
58 | isClient
59 | ? buildState('load')
60 | : {
61 | trigger: 'load',
62 | length: 1,
63 | },
64 | );
65 | useEffect(() => {
66 | const onChange = (trigger) => setState(buildState(trigger));
67 | const onPopstate = () => onChange('popstate');
68 | const onPushstate = () => onChange('pushstate');
69 | const onReplacestate = () => onChange('replacestate');
70 | on(window, 'popstate', onPopstate);
71 | on(window, 'pushstate', onPushstate);
72 | on(window, 'replacestate', onReplacestate);
73 | return () => {
74 | off(window, 'popstate', onPopstate);
75 | off(window, 'pushstate', onPushstate);
76 | off(window, 'replacestate', onReplacestate);
77 | };
78 | }, []);
79 | return state;
80 | };
81 |
--------------------------------------------------------------------------------
/library/hooks/useOnClickOutside.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 |
3 | function useOnClickOutside(ref, handler) {
4 | useEffect(() => {
5 | const listener = (event) => {
6 | if (!ref.current || ref.current.contains(event.target)) {
7 | return;
8 | }
9 | handler(event);
10 | };
11 |
12 | document.addEventListener('mousedown', listener);
13 | document.addEventListener('touchstart', listener);
14 |
15 | return () => {
16 | document.removeEventListener('mousedown', listener);
17 | document.removeEventListener('touchstart', listener);
18 | };
19 | }, [ref, handler]);
20 | }
21 |
22 | export default useOnClickOutside;
23 |
--------------------------------------------------------------------------------
/library/hooks/useWindowSize.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import throttle from 'lodash/throttle';
3 |
4 | const events = new Set();
5 | const onResize = () => events.forEach((fn) => fn());
6 |
7 | const useWindowSize = (options = {}) => {
8 | const { throttleMs = 100 } = options;
9 | const [size, setSize] = React.useState({
10 | width: process.browser && window.innerWidth,
11 | height: process.browser && window.innerHeight,
12 | });
13 |
14 | const handle = throttle(() => {
15 | setSize({
16 | width: process.browser && window.innerWidth,
17 | height: process.browser && window.innerHeight,
18 | });
19 | }, throttleMs);
20 |
21 | React.useEffect(() => {
22 | if (events.size === 0) {
23 | window.addEventListener('resize', onResize, true);
24 | }
25 |
26 | events.add(handle);
27 |
28 | return () => {
29 | events.delete(handle);
30 |
31 | if (events.size === 0) {
32 | window.removeEventListener('resize', onResize, true);
33 | }
34 | };
35 | }, [handle]);
36 |
37 | return size;
38 | };
39 |
40 | export default useWindowSize;
41 |
--------------------------------------------------------------------------------
/pages/_error.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-nested-ternary */
2 | import React from 'react';
3 | import ErrorPage from 'container/404/404';
4 |
5 | class Error extends React.Component {
6 | static getInitialProps({ res, err }) {
7 | const statusCode = res ? res.statusCode : err ? err.statusCode : null;
8 |
9 | return { statusCode };
10 | }
11 |
12 | render() {
13 | const { statusCode } = this.props;
14 | return ;
15 | }
16 | }
17 | export default Error;
18 |
--------------------------------------------------------------------------------
/pages/account-settings.jsx:
--------------------------------------------------------------------------------
1 | import Router from 'next/router';
2 | import Head from 'next/head';
3 | import { UserAccountSettingsPage } from 'container/User/index';
4 | import GetAPIData, { ProcessAPIData } from 'library/helpers/get_api_data';
5 | import { secretPage } from 'library/helpers/restriction';
6 | import { getDeviceType } from 'library/helpers/get_device_type';
7 |
8 | const AccountSettings = (props) => {
9 | if (typeof window !== 'undefined') { if (props.query.u) Router.replace('/account-settings'); }
10 | return (
11 | <>
12 |
13 |
14 | Account Settings
15 |
16 |
17 |
20 | >
21 | );
22 | };
23 |
24 | AccountSettings.getInitialProps = async (context) => {
25 | const { req, query } = context;
26 | const isLoggedIn = secretPage(context);
27 | const apiUrl = [
28 | {
29 | endpoint: 'user',
30 | name: 'userProfile',
31 | },
32 | ];
33 | const pageData = await GetAPIData(apiUrl);
34 | const processedData = ProcessAPIData(pageData);
35 | const deviceType = getDeviceType(req);
36 | return {
37 | processedData, deviceType, isLoggedIn, query,
38 | };
39 | };
40 | // export async function getServerSideProps() {
41 | // const apiUrl = [
42 | // {
43 | // endpoint: 'user',
44 | // name: 'userProfile',
45 | // },
46 | // ];
47 | // const pageData = await GetAPIData(apiUrl);
48 | // const processedData = ProcessAPIData(pageData);
49 | // return {
50 | // props: { processedData },
51 | // };
52 | // }
53 |
54 | export default AccountSettings;
55 |
--------------------------------------------------------------------------------
/pages/auth-processing.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-expressions */
2 | import { useEffect, useContext } from 'react';
3 | import Router from 'next/router';
4 | import Head from 'next/head';
5 | import Loader from 'components/Loader/Loader';
6 | import { USER_COOKIE } from 'library/helpers/session';
7 | import redirect from 'library/helpers/redirect';
8 | import { AuthContext } from 'context/AuthProvider';
9 |
10 | const AuthProcessing = ({ query }) => {
11 | const {
12 | addItem, setUser, loggedIn,
13 | } = useContext(AuthContext);
14 | if (loggedIn) {
15 | Router.push('/account-settings');
16 | }
17 | useEffect(async () => {
18 | const githubPayload = await fetch('https://api.hotel-prisma.ml/github/callback', {
19 | method: 'POST',
20 | credentials: 'include', // lưu token
21 | headers: {
22 | 'Content-Type': 'application/json',
23 | },
24 | body: JSON.stringify(query),
25 | });
26 | if (githubPayload.status === 200) {
27 | const userPayload = await githubPayload.json();
28 | setUser(userPayload.userSendToClient);
29 | addItem(USER_COOKIE, userPayload.userSendToClient);
30 | // setLoggedIn(true);
31 | // Router.reload();
32 | Router.push('/account-settings?u=1');
33 | } else {
34 | redirect({}, '/error');
35 | }
36 | }, []);
37 | return (
38 | <>
39 |
40 |
41 | Processsing...
42 |
43 |
44 |
45 | >
46 | );
47 | };
48 |
49 | export async function getServerSideProps({ query, ...context }) {
50 | !query.code && redirect(context, '/error');
51 | return { props: { query } };
52 | }
53 |
54 | export default AuthProcessing;
55 |
--------------------------------------------------------------------------------
/pages/forget-password.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ToastContainer } from 'react-toastify';
3 | import Head from 'next/head';
4 | import Logo from 'components/UI/Logo/Logo';
5 | import ForgetPassWordForm from 'container/ForgetPassword/ForgetPasswordForm';
6 | import { getIsLoggedIn } from 'library/helpers/restriction';
7 | import redirect from 'library/helpers/redirect';
8 | import ForgetPassWordWrapper, {
9 | Title,
10 | TitleInfo,
11 | ForgetPassWordFormWrapper,
12 | ForgetPassWordBannerWrapper,
13 | } from 'container/ForgetPassword/ForgetPassword.style';
14 | // demo image
15 | import signInImage from 'assets/images/login-page-bg.jpg';
16 | import palace from 'assets/images/logo-alt.svg';
17 |
18 | const ForgetPassWord = () => {
19 | return (
20 |
21 |
22 | Forget Password Page
23 |
24 |
25 |
26 |
27 | Welcome Back
28 | Enter your email to recover your account
29 |
30 |
31 |
32 |
40 |
41 |
42 | );
43 | }
44 | export async function getServerSideProps(context) {
45 | const isLoggedIn = getIsLoggedIn(context);
46 | if (isLoggedIn === true) redirect(context, '/profile');
47 | return {
48 | props: {},
49 | };
50 | }
51 |
52 | export default ForgetPassWord;
--------------------------------------------------------------------------------
/pages/processing.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-expressions */
2 | import { useEffect } from 'react';
3 | import { useMutation } from 'react-apollo';
4 | import Router from 'next/router';
5 | import Head from 'next/head';
6 | import Loader from 'components/Loader/Loader';
7 | import Cookies from 'js-cookie';
8 | import { UPDATE_STRIPE_ID } from 'apollo-graphql/mutation/mutation';
9 | import { toast } from 'react-toastify';
10 | import redirect from 'library/helpers/redirect';
11 |
12 | const Processing = ({ user, query }) => {
13 | const [updateStripeId] = useMutation(UPDATE_STRIPE_ID, {
14 | onCompleted: () => {
15 | toast.success('Validation successfully you will now be redirected to profile', {
16 | position: 'top-right',
17 | autoClose: 5000,
18 | hideProgressBar: false,
19 | closeOnClick: true,
20 | pauseOnHover: true,
21 | draggable: true,
22 | progress: undefined,
23 | });
24 | const newCookie = { ...user, role: query.plan };
25 | Cookies.set('user', newCookie, { expires: 7 });
26 | Router.replace('/profile?u=1');
27 | },
28 | });
29 | useEffect(async () => {
30 | await updateStripeId({
31 | variables: {
32 | stripeId: query.accountId,
33 | type: query.plan,
34 | },
35 | });
36 | }, []);
37 | return (
38 | <>
39 |
40 |
41 | Processing...
42 |
43 |
44 |
45 | >
46 | );
47 | };
48 |
49 |
50 | export async function getServerSideProps({ query, ...context }) {
51 | (!query.accountId || !query.plan) && redirect(context, '/error');
52 | return { props: { query } };
53 | }
54 |
55 | export default Processing;
56 |
--------------------------------------------------------------------------------
/pages/profile.jsx:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Router from 'next/router';
3 | import { UserDetailsPage } from 'container/User/index';
4 | // import GetAPIData, { ProcessAPIData } from 'library/helpers/get_api_data';
5 | import { secretPage } from 'library/helpers/restriction';
6 | import { getDeviceType } from 'library/helpers/get_device_type';
7 |
8 | const Profile = ({ processedData, ...props }) => {
9 | if (typeof window !== 'undefined') { if (props.query.u) Router.replace('/profile'); }
10 | return (
11 | <>
12 |
13 | Profile | Profile Page
14 |
15 |
19 | >
20 | );
21 | };
22 |
23 | Profile.getInitialProps = async (context) => {
24 | const { query, req } = context;
25 | const isLoggedIn = secretPage(context);
26 | // const apiUrl = [
27 | // {
28 | // endpoint: 'user',
29 | // name: 'listingUser',
30 | // },
31 | // ];
32 | // const { data, loading, error } = useQuery(GET_PROFILE_OF_CURRENT_USER,{})
33 |
34 | // const pageData = await GetAPIData(apiUrl); // getApiData(data)
35 | // const processedData = ProcessAPIData(pageData);
36 | const deviceType = getDeviceType(req);
37 |
38 | return {
39 | query, isLoggedIn, deviceType,
40 | };
41 | };
42 |
43 | export default Profile;
44 |
--------------------------------------------------------------------------------
/pages/search.jsx:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Router from 'next/router';
3 | import Input from 'components/UI/Antd/Input/Input';
4 | import { SearchOutlined } from '@ant-design/icons';
5 | import Heading from 'components/UI/Heading/Heading';
6 |
7 | const { Search } = Input;
8 | const handleSearch = (e) => {
9 | Router.push('/invoices/[...txid]', `/invoices/${e}`);
10 | };
11 | const suffix = (
12 |
18 | );
19 |
20 | const SearchTXID = ({ query }) => (
21 |
22 |
23 |
Search TXID
24 |
25 |
26 | { handleSearch(e); }}
33 | onPressEnter={(e) => { handleSearch(e.target.value); }}
34 | />
35 |
36 | );
37 |
38 | SearchTXID.getInitialProps = async ({ query }) => ({ query });
39 | export default SearchTXID;
40 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/public/favicon.ico
--------------------------------------------------------------------------------
/public/static/flag/cn.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/public/static/flag/france.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
22 |
--------------------------------------------------------------------------------
/public/static/flag/italy.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
22 |
--------------------------------------------------------------------------------
/public/static/flag/spain.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
23 |
--------------------------------------------------------------------------------
/public/static/images/404.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/public/static/images/404.png
--------------------------------------------------------------------------------
/public/static/images/404@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/public/static/images/404@2x.png
--------------------------------------------------------------------------------
/public/static/images/addCircle.svg:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/public/static/images/banner-bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/public/static/images/banner-bg.jpg
--------------------------------------------------------------------------------
/public/static/images/dashBorder.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/public/static/images/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/public/static/images/favicon.png
--------------------------------------------------------------------------------
/public/static/images/footer-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/public/static/images/footer-bg.png
--------------------------------------------------------------------------------
/public/static/images/heart.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/public/static/images/hotel-img.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/public/static/images/hotel-img.jpeg
--------------------------------------------------------------------------------
/public/static/images/hotelMapMarker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/public/static/images/hotelMapMarker.png
--------------------------------------------------------------------------------
/public/static/images/logo-alt.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/public/static/images/logo-with-text.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/public/static/images/map-loader.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/public/static/images/map-loader.gif
--------------------------------------------------------------------------------
/public/static/images/post-image-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/public/static/images/post-image-1.jpg
--------------------------------------------------------------------------------
/public/static/images/post-image-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/public/static/images/post-image-2.jpg
--------------------------------------------------------------------------------
/public/static/images/post-thumb-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/public/static/images/post-thumb-1.jpg
--------------------------------------------------------------------------------
/public/static/images/post-thumb-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/public/static/images/post-thumb-2.jpg
--------------------------------------------------------------------------------
/public/static/images/profileCoverImage.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/public/static/images/profileCoverImage.jpg
--------------------------------------------------------------------------------
/public/static/images/profileImage.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/public/static/images/profileImage.jpg
--------------------------------------------------------------------------------
/public/static/images/share.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/public/static/images/signin.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/public/static/images/signin.jpg
--------------------------------------------------------------------------------
/public/static/images/single-post-bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/public/static/images/single-post-bg.jpg
--------------------------------------------------------------------------------
/public/static/locales/en/common.json:
--------------------------------------------------------------------------------
1 | {
2 | "common": {
3 | "title": "Các khách sạn sang trọng",
4 | "change-locale": "Đổi {{locale}}"
5 | },
6 |
7 | "error-with-status": "A {{statusCode}} error occurred on server",
8 | "error-without-status": "An error occurred on the server"
9 | }
10 |
--------------------------------------------------------------------------------
/public/static/locales/vi/common.json:
--------------------------------------------------------------------------------
1 | {
2 | "key": "value of key",
3 | "look": {
4 | "deep": "value of look deep"
5 | }
6 | }
--------------------------------------------------------------------------------
/public/zeit.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/settings/config.js:
--------------------------------------------------------------------------------
1 | export const HOME_PAGE_SECTIONS_ITEM_LIMIT_FOR_MOBILE_DEVICE = 4;
2 | export const HOME_PAGE_SECTIONS_ITEM_LIMIT_FOR_TABLET_DEVICE = 4;
3 | export const HOME_PAGE_SECTIONS_ITEM_LIMIT_FOR_DESKTOP_DEVICE = 5;
4 | export const HOME_PAGE_SECTIONS_COLUMNS_RESPONSIVE_WIDTH = [
5 | 1 / 1,
6 | 1 / 2,
7 | 1 / 3,
8 | 1 / 4,
9 | 1 / 5,
10 | ];
11 | export const LISTING_PAGE_POST_LIMIT = 10;
12 |
13 | export const LISTING_PAGE_COLUMN_WIDTH_WITHOUT_MAP = [
14 | 1 / 1,
15 | 1 / 2,
16 | 1 / 3,
17 | 1 / 4,
18 | 1 / 5,
19 | ];
20 | export const LISTING_PAGE_COLUMN_WIDTH_WITH_MAP = [
21 | 1 / 1,
22 | 1 / 2,
23 | 1 / 2,
24 | 1 / 2,
25 | 1 / 3,
26 | ];
27 |
--------------------------------------------------------------------------------
/settings/constants.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/prefer-default-export */
2 | // **************** ROUTE CHO NEXTJS **************************
3 |
4 | // **************** ROUTE CONSTANT START **************************
5 | // General Page Section
6 | export const HOME_PAGE = '/';
7 |
8 | // Listing Single Page Section
9 | export const LISTING_POSTS_PAGE = '/listing';
10 | export const SINGLE_POST_PAGE = '/post';
11 |
12 | // User Profile Page Section
13 | export const USER_PROFILE_PAGE = '/profile';
14 | export const USER_ACCOUNT_SETTINGS_PAGE = '/account-settings';
15 | export const ADD_HOTEL_PAGE = '/add-hotel';
16 |
17 | // Other Pages
18 | export const PRICING_PLAN_PAGE = '/pricing-plan';
19 | export const PRIVACY_PAGE = '/privacy';
20 | export const SEARCH_TXID_PAGE = '/search';
21 |
22 | // export const AUTHENTICATION_PAGE = '/user';
23 | export const LOGIN_PAGE = '/login';
24 | export const REGISTRATION_PAGE = '/registration';
25 | export const CHANGE_PASSWORD_PAGE = '/change-password';
26 | export const FORGET_PASSWORD_PAGE = '/forget-password';
27 |
28 | // **************** ROUTE CONSTANT END **************************
29 |
--------------------------------------------------------------------------------
/settings/setup.js:
--------------------------------------------------------------------------------
1 | const enzyme = require('enzyme');
2 | const Adapter = require('enzyme-adapter-react-16');
3 |
4 | enzyme.configure({ adapter: new Adapter() });
5 |
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/style.css
--------------------------------------------------------------------------------
/themes/default.theme.js:
--------------------------------------------------------------------------------
1 | const defaultTheme = {
2 | breakpoints: ['481px', '768px', '992px', '1201px', '1441px'],
3 | primary: [
4 | '#008489', // 0: Default
5 | '#399C9F', // 1: Chart
6 | ],
7 | color: [
8 | '#000000', // 0: Black
9 | '#ffffff', // 1: White
10 | '#F7F7F7', // 2: Dropdown Hover
11 | '#E9E8E8', // 3: Bullet bg
12 | '#FC5C63', // 4: Active Favorite
13 | 'rgba(0, 0, 0, 0.25)', // 5: Inactive Favorite
14 | '#273343', // 6: Agent Social Icon
15 | '#3b5998', // 7: Facebook
16 | '#55ADEE', // 8: Twitter
17 | '#FFF900', // 9: Snapchat
18 | '#dd4b39', // 10: Google Plus
19 | '#F61C0D', // 11: Youtube
20 | '#e4405f', // 12: Instagram
21 | '#E2E2E2', // 13: Range Color
22 | '#00ACEE', // 14: Github Color
23 | '#FFCB2B', // 15: Firebase Color
24 | '#484848', // 16: Link button
25 | ],
26 | warning: [
27 | '#F29100', // 0: Warning
28 | ],
29 | success: [
30 | '#00BB5D', // 0: Success
31 | ],
32 | error: [
33 | '#F9503D', // 0: Error
34 | 'rgba(249, 80, 61, 0.08)', // 0: Error Light Bg
35 | ],
36 | text: [
37 | '#2C2C2C', // 0: Heading
38 | '#909090', // 1: Meta Text
39 | '#777777', // 2: Text Info
40 | ],
41 | border: [
42 | '#EBEBEB', // 0: Light border
43 | '#E8E8E8', // 1: Default Border
44 | 'rgba(57, 151, 247, 0.35)', // 2: Active Light Border
45 | '#E6E6E6', // 3: Pricing Border
46 | ],
47 | boxShadow: [
48 | 'rgba(26, 68, 116, 0.16)', // 0: Card hover
49 | 'rgba(0, 0, 0, 0.16)', // 1: Carousel Button shadow
50 | ],
51 | fonts: {
52 | primary: 'Lato, sans-serif',
53 | },
54 | };
55 |
56 | export default defaultTheme;
57 |
--------------------------------------------------------------------------------
/translations/config.js:
--------------------------------------------------------------------------------
1 | // import englishLang from '../../image/flag/uk.svg';
2 | // import chineseLang from '../../image/flag/china.svg';
3 | // import spanishLang from '../../image/flag/spain.svg';
4 | // import frenchLang from '../../image/flag/france.svg';
5 | // import italianLang from '../../image/flag/italy.svg';
6 |
7 | const config = {
8 | defaultLanguage: 'english',
9 | options: [
10 | {
11 | languageId: 'english',
12 | locale: 'en',
13 | text: 'English',
14 | // icon: englishLang,
15 | },
16 | {
17 | languageId: 'chinese',
18 | locale: 'zh',
19 | text: 'Chinese',
20 | // icon: chineseLang,
21 | },
22 | {
23 | languageId: 'spanish',
24 | locale: 'es',
25 | text: 'Spanish',
26 | // icon: spanishLang,
27 | },
28 | {
29 | languageId: 'french',
30 | locale: 'fr',
31 | text: 'French',
32 | // icon: frenchLang,
33 | },
34 | {
35 | languageId: 'italian',
36 | locale: 'it',
37 | text: 'Italian',
38 | // icon: italianLang,
39 | },
40 | ],
41 | };
42 |
43 | export function getCurrentLanguage(lang) {
44 | let selecetedLanguage = config.options[0];
45 | config.options.forEach((language) => {
46 | if (language.languageId === lang) {
47 | selecetedLanguage = language;
48 | }
49 | });
50 | return selecetedLanguage;
51 | }
52 | export default config;
53 |
--------------------------------------------------------------------------------
/translations/conversion/index.js:
--------------------------------------------------------------------------------
1 | import ch from './raw/chinese';
2 | import fr from './raw/fr';
3 | import ital from './raw/ital';
4 | import span from './raw/span';
5 | import arab from './raw/arab';
6 | import english from './raw/eng';
7 |
8 | export function getKeys(object) {
9 | const keys = [];
10 | const variables = [];
11 | let text = '';
12 | Object.keys(object).forEach((key) => {
13 | keys.push(key);
14 | variables.push(object[key]);
15 | text += `${object[key]}\n`;
16 | });
17 | // getValues(keys);
18 | return {
19 | keys,
20 | variables,
21 | };
22 | }
23 | export function getValues(enMessages) {
24 | const { keys, variables } = getKeys(enMessages);
25 | const langs = [english, ch, fr, ital, span, arab];
26 | const langsNm = ['eng', 'ch', 'fr', 'ital', 'span', 'arab'];
27 | langs.forEach((lang, ii) => {
28 | const translatedDAta = lang.split('\n');
29 | const obj = {};
30 | keys.forEach((key, index) => {
31 | obj[key] = translatedDAta[index + 1];
32 | });
33 | console.log(
34 | langsNm[ii],
35 | translatedDAta.length,
36 | keys.length,
37 | '\n',
38 | JSON.stringify(obj, null, 2),
39 | );
40 | });
41 | }
42 |
--------------------------------------------------------------------------------
/translations/entries/ar_SA.js:
--------------------------------------------------------------------------------
1 | import antdSA from 'antd/lib/locale-provider/en_US';
2 | import saMessages from '../locales/ar_SA.json';
3 |
4 | const saLang = {
5 | messages: {
6 | ...saMessages,
7 | },
8 | antd: antdSA,
9 | locale: 'ar',
10 | };
11 | export default saLang;
12 |
--------------------------------------------------------------------------------
/translations/entries/en-US.js:
--------------------------------------------------------------------------------
1 | import antdEn from 'antd/lib/locale-provider/en_US';
2 | // import appLocaleData from 'react-intl/locale-data/en';
3 | import enMessages from '../locales/en_US.json';
4 | // import { getKeys, getValues } from '../conversion';
5 | // getValues(enMessages);
6 |
7 | const EnLang = {
8 | messages: {
9 | ...enMessages,
10 | },
11 | antd: antdEn,
12 | locale: 'en-US',
13 | // data: appLocaleData,
14 | };
15 | export default EnLang;
16 |
--------------------------------------------------------------------------------
/translations/entries/es_ES.js:
--------------------------------------------------------------------------------
1 | import antdSA from 'antd/lib/locale-provider/ca_ES';
2 | // import appLocaleData from 'react-intl/locale-data/es';
3 | import saMessages from '../locales/es_ES.json';
4 |
5 | const saLang = {
6 | messages: {
7 | ...saMessages,
8 | },
9 | antd: antdSA,
10 | locale: 'es',
11 | // data: appLocaleData
12 | };
13 | export default saLang;
14 |
--------------------------------------------------------------------------------
/translations/entries/fr_FR.js:
--------------------------------------------------------------------------------
1 | import antdSA from 'antd/lib/locale-provider/fr_FR';
2 | // import appLocaleData from 'react-intl/locale-data/fr';
3 | import saMessages from '../locales/fr_FR.json';
4 |
5 | const saLang = {
6 | messages: {
7 | ...saMessages,
8 | },
9 | antd: antdSA,
10 | locale: 'fr-FR',
11 | // data: appLocaleData
12 | };
13 | export default saLang;
14 |
--------------------------------------------------------------------------------
/translations/entries/it_IT.js:
--------------------------------------------------------------------------------
1 | import antdSA from 'antd/lib/locale-provider/it_IT';
2 | // import appLocaleData from 'react-intl/locale-data/it';
3 | import saMessages from '../locales/it_IT.json';
4 |
5 | const saLang = {
6 | messages: {
7 | ...saMessages,
8 | },
9 | antd: antdSA,
10 | locale: 'it-IT',
11 | // data: appLocaleData
12 | };
13 | export default saLang;
14 |
--------------------------------------------------------------------------------
/translations/entries/zh-Hans-CN.js:
--------------------------------------------------------------------------------
1 | // import appLocaleData from 'react-intl/locale-data/zh';
2 | import zhMessages from '../locales/zh-Hans.json';
3 |
4 | const ZhLan = {
5 | messages: {
6 | ...zhMessages,
7 | },
8 | antd: null,
9 | locale: 'zh-Hans-CN',
10 | // data: appLocaleData
11 | };
12 | export default ZhLan;
13 |
--------------------------------------------------------------------------------
/translations/index.js:
--------------------------------------------------------------------------------
1 | import Enlang from './entries/en-US';
2 | import Zhlang from './entries/zh-Hans-CN';
3 | import Salang from './entries/ar_SA';
4 | import Itlang from './entries/it_IT';
5 | import Eslang from './entries/es_ES';
6 | import Frlang from './entries/fr_FR';
7 |
8 | const AppLocale = {
9 | en: Enlang,
10 | zh: Zhlang,
11 | sa: Salang,
12 | it: Itlang,
13 | es: Eslang,
14 | fr: Frlang,
15 | };
16 | // addLocaleData(AppLocale.en.data);
17 | // addLocaleData(AppLocale.zh.data);
18 | // addLocaleData(AppLocale.sa.data);
19 | // addLocaleData(AppLocale.it.data);
20 | // addLocaleData(AppLocale.es.data);
21 | // addLocaleData(AppLocale.fr.data);
22 |
23 | export default AppLocale;
24 |
--------------------------------------------------------------------------------
/translations/locales/ar_SA.json:
--------------------------------------------------------------------------------
1 | {
2 | "languageSwitcher.label": "غير اللغة",
3 | "uiElements.badge.badge": "شارة",
4 | "uiElements.badge.basicExample": "مثال أساسي",
5 | "uiElements.badge.basicExampleSubTitle":
6 | "أبسط الاستخدام. سيتم إخفاء شارة عندما العد هو 0، ولكن يمكننا استخدام شوزيرو لإظهار ذلك.",
7 | "checkout.billingform.firstname": "الاسم الاول",
8 | "checkout.billingform.lastname": "الكنية",
9 | "checkout.billingform.company": "اسم الشركة",
10 | "checkout.billingform.email": "عنوان البريد الإلكتروني",
11 | "checkout.billingform.mobile": "رقم المحمول",
12 | "checkout.billingform.country": "بلد",
13 | "checkout.billingform.city": "مدينة",
14 | "checkout.billingform.address": "عنوان",
15 | "checkout.billingform.addressoptional": "شقة، جناح، وحدة الخ (اختياري",
16 | "checkout.billingform.checkbox": "انشئ حساب؟",
17 | "themeSwitcher.note": "شراء الآن(Show Note)",
18 | "themeSwitcher.settings": "إعدادات"
19 | }
20 |
--------------------------------------------------------------------------------
/translations/locales/en_US.json:
--------------------------------------------------------------------------------
1 | {
2 | "languageSwitcher.label": "Change Language",
3 | "uiElements.badge.badge": "Badge",
4 | "uiElements.badge.basicExample": "Basic Example",
5 | "uiElements.badge.basicExampleSubTitle": "Simplest Usage. Badge will be hidden when count is 0 but we can use showZero to show it.",
6 | "checkout.billingform.firstname": "First Name",
7 | "checkout.billingform.lastname": "Last Name",
8 | "checkout.billingform.company": "Company Name",
9 | "checkout.billingform.email": "Email Address",
10 | "checkout.billingform.mobile": "Mobile No",
11 | "themeSwitcher.note": "Show note",
12 | "themeSwitcher.settings": "Settings",
13 | "sidebar.selectbox": "Select"
14 | }
15 |
--------------------------------------------------------------------------------
/translations/locales/es_ES.json:
--------------------------------------------------------------------------------
1 | {
2 | "languageSwitcher.label": "Cambiar idioma",
3 | "uiElements.badge.badge": "Distintivo",
4 | "uiElements.badge.basicExample": "Ejemplo Básico",
5 | "uiElements.badge.basicExampleSubTitle": "Uso más simple. La insignia se ocultará cuando count sea 0 pero podemos usar showZero para mostrarlo.",
6 | "checkout.billingform.firstname": "Nombre de pila",
7 | "checkout.billingform.lastname": "Apellido",
8 | "checkout.billingform.company": "nombre de empresa",
9 | "checkout.billingform.email": "Dirección de correo electrónico",
10 | "checkout.billingform.mobile": "No móviles",
11 | "themeSwitcher.note": "Compra ahora(Show note)",
12 | "themeSwitcher.settings": "AJUSTES"
13 | }
14 |
--------------------------------------------------------------------------------
/translations/locales/fr_FR.json:
--------------------------------------------------------------------------------
1 | {
2 | "languageSwitcher.label": "Cambiar idioma",
3 | "uiElements.badge.badge": "Distintivo",
4 | "uiElements.badge.basicExample": "Ejemplo Básico",
5 | "uiElements.badge.basicExampleSubTitle": "Uso más simple. La insignia se ocultará cuando count sea 0 pero podemos usar showZero para mostrarlo.",
6 | "checkout.billingform.firstname": "Nombre de pila",
7 | "checkout.billingform.lastname": "Apellido",
8 | "checkout.billingform.company": "nombre de empresa",
9 | "checkout.billingform.email": "Dirección de correo electrónico",
10 | "checkout.billingform.mobile": "No móviles",
11 | "themeSwitcher.note": "ACHETER MAINTENANT(Show note)",
12 | "themeSwitcher.settings": "AJUSTES"
13 | }
14 |
--------------------------------------------------------------------------------
/translations/locales/it_IT.json:
--------------------------------------------------------------------------------
1 | {
2 | "languageSwitcher.label": "Switcher di temi",
3 | "uiElements.badge.badge": "Esempio di base",
4 | "uiElements.badge.basicExample": "Uso più semplice. Il distintivo sarà nascosto quando il conteggio è 0 ma possiamo usare showZero per mostrarlo.",
5 | "uiElements.badge.basicExampleSubTitle": "Numero di overflow",
6 | "checkout.billingform.firstname": "Cognome",
7 | "checkout.billingform.lastname": "Nome della ditta",
8 | "checkout.billingform.company": "Indirizzo email",
9 | "checkout.billingform.email": "Mobile no",
10 | "checkout.billingform.mobile": "Nazione",
11 | "themeSwitcher.note": "ACQUISTA ADESSO(Show note)",
12 | "themeSwitcher.settings": "Selezionare"
13 | }
14 |
--------------------------------------------------------------------------------
/translations/locales/zh-Hans.json:
--------------------------------------------------------------------------------
1 | {
2 | "languageSwitcher.label": "改变语言",
3 | "uiElements.badge.badge": "徽章",
4 | "uiElements.badge.basicExample": "基本例子",
5 | "uiElements.badge.basicExampleSubTitle": "最简单的用法当count为0时,徽章将被隐藏,但是我们可以使用showZero来显示。",
6 | "checkout.billingform.firstname": "名字",
7 | "checkout.billingform.lastname": "姓",
8 | "checkout.billingform.company": "公司名",
9 | "checkout.billingform.email": "电子邮件地址",
10 | "checkout.billingform.mobile": "手机号码",
11 | "themeSwitcher.note": "现在买(Show note)",
12 | "themeSwitcher.settings": "设置"
13 | }
14 |
--------------------------------------------------------------------------------