├── .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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 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 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/images/footer-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/php1301/DoAnFullstack-ui/313b2ecba2afa73bea35d3b6dec894ea1984fb6f/assets/images/footer-bg.png -------------------------------------------------------------------------------- /assets/images/heart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/images/logo-with-text.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TripFinder. 5 | 6 | 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 | 2 | 3 | 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 | 13 | { 16 | tempFormData = MapDataHelper(value); 17 | form.setFieldValue(field.name, tempFormData); 18 | setFormLocationState(value); 19 | }} 20 | {...field} 21 | {...props} 22 | /> 23 | 24 | 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 |
12 | 13 | 14 | 24 | 25 | 26 | 36 | 37 | 38 | 48 | 49 | 50 |
51 | 54 |
55 |
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 | cover 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 | 14 | {logo && logo} 15 | {menu && {menu}} 16 | {copyright && {copyright}} 17 | 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 |
14 | 24 | 34 | 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 | {title} 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 | 17 | 18 | 25 | 26 | 27 | 34 | 35 | 36 | 43 | 44 | 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 | {title} 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 &&