├── .env.example ├── .gitignore ├── .prettierrc ├── README.md ├── babel.config.js ├── package.json ├── public ├── favicon.ico ├── images │ └── sample.png └── index.html ├── serverless.yml ├── sls_s3_deploy_date.js ├── src ├── App.vue ├── assets │ ├── img │ │ ├── login.jpg │ │ ├── register_1.webp │ │ ├── register_2.jpg │ │ └── register_3.jpg │ ├── logo.png │ ├── slider │ │ ├── 1.jpg │ │ ├── 2.jpg │ │ ├── 3.jpg │ │ └── 4.jpg │ └── ss1.png ├── components │ ├── HelloWorld.vue │ ├── core │ │ ├── Footer.vue │ │ ├── Header.vue │ │ ├── LayoutMode.vue │ │ └── PageNotFound.vue │ ├── filters │ │ └── PriceFilter.vue │ ├── order │ │ ├── OrderDetail.vue │ │ └── OrderList.vue │ ├── pagination │ │ └── Pagination.vue │ ├── products │ │ ├── CategoryFilter.css │ │ ├── Product.vue │ │ ├── ProductDetail.vue │ │ ├── ProductList.vue │ │ ├── ProductSlider.vue │ │ └── ProductSliderDots.vue │ ├── shopping-cart │ │ ├── ShoppingCartContainer.vue │ │ └── ShoppingCartItem.vue │ └── slider │ │ └── Slider.vue ├── config │ └── index.js ├── data │ └── index.js ├── filters │ ├── brandFilter.js │ ├── orderByFilter.js │ ├── paginationFilter.js │ ├── priceFormatter.js │ └── shortenTitle.js ├── google-map.js ├── layouts │ ├── auth │ │ └── index.vue │ └── main │ │ └── index.vue ├── main.js ├── pages │ ├── Alert.vue │ ├── BillingPlan.vue │ ├── Dashboard.vue │ ├── Home.vue │ ├── OrderPage.vue │ ├── ProductDetailPage.vue │ ├── ProfilePage.vue │ ├── ShoppingCartPage.vue │ └── auth │ │ ├── 404.vue │ │ ├── ConfirmPage.vue │ │ ├── ImgDiv.vue │ │ ├── LoginPage.vue │ │ └── RegisterPage.vue ├── router.js ├── services │ ├── api │ │ └── index.js │ ├── auth │ │ └── index.js │ └── axios │ │ ├── fakeApi │ │ ├── auth │ │ │ └── index.js │ │ ├── index.js │ │ └── mock.js │ │ └── index.js ├── store │ ├── index.js │ ├── types.js │ └── user │ │ └── index.js ├── styles │ ├── borders.scss │ ├── buttons.scss │ ├── colors.scss │ ├── core.scss │ ├── global.scss │ ├── measurements.scss │ └── mixins.scss └── utilities │ └── cumulativeOffset.js └── yarn.lock /.env.example: -------------------------------------------------------------------------------- 1 | VUE_APP_API_URL=localhost:3000 2 | BASE_URL=localhost:8080 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | jspm_packages 4 | dist 5 | .serverless 6 | 7 | # local env files 8 | .env.local 9 | .env.*.local 10 | 11 | # Log files 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw* 24 | 25 | # ENV values 26 | .env -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "printWidth": 100, 5 | "tabWidth": 2, 6 | "semi": false 7 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tooltime-consumer 2 | 3 | ## To deploy to S3 as a Static Website, follow the below Steps. 4 | 5 | 1. in serverless.yml set provider.environment.VUE_APP_API_URL to the correct endpoint 6 | 2. In a future update, the script will obtain this value from another CloudFormation stack automatically 7 | 3. \$ npm run build 8 | 4. \$ sls deploy 9 | 10 | ## To remove the stack (and stop billing) 11 | 12 | 1. sls remove 13 | 14 | ## Project setup 15 | 16 | ``` 17 | yarn install 18 | 19 | yarn serve 20 | ``` 21 | 22 | 23 | - Test credentials 24 | ``` 25 | email: oliver@tt.com 26 | password: a 27 | 28 | email: william@tt.com 29 | password: a 30 | ``` 31 | 32 | ### Compiles and minifies for production 33 | 34 | ``` 35 | yarn run build 36 | ``` 37 | 38 | ### Run your tests 39 | 40 | ``` 41 | yarn run test 42 | ``` 43 | 44 | ### Lints and fixes files 45 | 46 | ``` 47 | yarn run lint 48 | ``` 49 | 50 | ### Customize configuration 51 | 52 | See [Configuration Reference](https://cli.vuejs.org/config/). 53 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ], 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tooltime-consumer", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "@fortawesome/fontawesome-free": "^5.15.3", 12 | "antd-img-crop": "^3.14.2", 13 | "axios": "^0.21.1", 14 | "axios-mock-adapter": "^1.19.0", 15 | "bootstrap-vue": "^2.21.2", 16 | "childprocess": "^2.0.2", 17 | "jsonwebtoken": "^8.5.1", 18 | "moment": "^2.29.1", 19 | "ping": "^0.4.1", 20 | "store": "^2.0.12", 21 | "stripe": "^8.160.0", 22 | "tcp-ping": "^0.1.1", 23 | "vue": "^2.6.10", 24 | "vue-google-autocomplete": "^1.1.1", 25 | "vue-horizontal-list": "^1.3.0", 26 | "vue-notification": "^1.3.20", 27 | "vue-router": "^3.5.1", 28 | "vue-swal": "^1.0.0", 29 | "vue2-google-maps": "^0.10.7", 30 | "vuex": "^2.3.1", 31 | "vuex-persistedstate": "^4.0.0-beta.3" 32 | }, 33 | "devDependencies": { 34 | "@vue/cli-plugin-babel": "^3.5.0", 35 | "@vue/cli-plugin-eslint": "^3.5.0", 36 | "@vue/cli-service": "^3.5.0", 37 | "babel-eslint": "^10.0.1", 38 | "core-js": "^2.6.12", 39 | "eslint": "^5.8.0", 40 | "eslint-plugin-vue": "^5.0.0", 41 | "node-sass": "^4.11.0", 42 | "sass-loader": "^7.1.0", 43 | "serverless-s3-sync": "^1.17.1", 44 | "vue-template-compiler": "^2.5.21" 45 | }, 46 | "eslintConfig": { 47 | "root": true, 48 | "env": { 49 | "node": true 50 | }, 51 | "extends": [ 52 | "plugin:vue/essential", 53 | "eslint:recommended" 54 | ], 55 | "rules": { 56 | "no-console": "off" 57 | }, 58 | "parserOptions": { 59 | "parser": "babel-eslint" 60 | } 61 | }, 62 | "postcss": { 63 | "plugins": { 64 | "autoprefixer": {} 65 | } 66 | }, 67 | "browserslist": [ 68 | "> 1%", 69 | "last 2 versions", 70 | "not ie <= 8" 71 | ] 72 | } 73 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smart-window/tooltime-frontend/6a33d4988f8db54d688e2f4a9e9103303450de61/public/favicon.ico -------------------------------------------------------------------------------- /public/images/sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smart-window/tooltime-frontend/6a33d4988f8db54d688e2f4a9e9103303450de61/public/images/sample.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ecomerce 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /serverless.yml: -------------------------------------------------------------------------------- 1 | # Welcome to serverless. Read the docs 2 | # https://serverless.com/framework/docs/ 3 | 4 | # Serverless.yml is the configuration the CLI 5 | # uses to deploy your code to your provider of choice 6 | 7 | # The `service` block is the name of the service 8 | service: tooltime-consumer 9 | frameworkVersion: '2' 10 | variablesResolutionMode: 20210219 11 | plugins: 12 | - serverless-s3-sync 13 | custom: 14 | s3DeployTime: ${file(./sls_s3_deploy_date.js)} 15 | s3Sync: 16 | - bucketName: tooltime-consumer # required 17 | #bucketPrefix: ${self:custom.s3DeployTime}/ # optional 18 | localDir: dist # required 19 | provider: 20 | name: aws 21 | runtime: nodejs12.x 22 | region: us-east-1 23 | environment: 24 | VUE_APP_API_URL: http://localhost:8000 25 | resources: 26 | Resources: 27 | S3Bucket: 28 | Type: AWS::S3::Bucket 29 | Properties: 30 | BucketName: tooltime-consumer 31 | AccessControl: PublicRead 32 | WebsiteConfiguration: 33 | IndexDocument: index.html 34 | ErrorDocument: index.html 35 | BucketPolicy: 36 | Type: AWS::S3::BucketPolicy 37 | Properties: 38 | PolicyDocument: 39 | Id: MyPolicy 40 | Version: '2012-10-17' 41 | Statement: 42 | - Sid: PublicReadForGetBucketObjects 43 | Effect: Allow 44 | Principal: '*' 45 | Action: 's3:GetObject' 46 | Resource: !Join 47 | - '' 48 | - - 'arn:aws:s3:::' 49 | - !Ref S3Bucket 50 | - /* 51 | Bucket: !Ref S3Bucket 52 | Outputs: 53 | WebsiteURL: 54 | Value: !GetAtt 55 | - S3Bucket 56 | - WebsiteURL 57 | Description: URL for website hosted on S3 58 | S3BucketSecureURL: 59 | Value: !Join 60 | - '' 61 | - - 'https://' 62 | - !GetAtt 63 | - S3Bucket 64 | - DomainName 65 | Description: Name of S3 bucket to hold website content 66 | -------------------------------------------------------------------------------- /sls_s3_deploy_date.js: -------------------------------------------------------------------------------- 1 | // This module provides the prefix for the s3 bucket. 2 | // Currently not used. 3 | // In a future update, it will be. 4 | module.exports = () => { 5 | const dt = new Date() 6 | return [ 7 | dt.getFullYear(), 8 | Number(dt.getMonth() + 1).toLocaleString('en-US', { minimumIntegerDigits: 2 }), 9 | Number(dt.getDate()).toLocaleString('en-US', { minimumIntegerDigits: 2 }), 10 | Number(dt.getHours()).toLocaleString('en-US', { minimumIntegerDigits: 2 }), 11 | Number(dt.getMinutes()).toLocaleString('en-US', { minimumIntegerDigits: 2 }), 12 | Number(dt.getSeconds()).toLocaleString('en-US', { minimumIntegerDigits: 2 }), 13 | ].join('') 14 | } 15 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 36 | 37 | 39 | -------------------------------------------------------------------------------- /src/assets/img/login.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smart-window/tooltime-frontend/6a33d4988f8db54d688e2f4a9e9103303450de61/src/assets/img/login.jpg -------------------------------------------------------------------------------- /src/assets/img/register_1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smart-window/tooltime-frontend/6a33d4988f8db54d688e2f4a9e9103303450de61/src/assets/img/register_1.webp -------------------------------------------------------------------------------- /src/assets/img/register_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smart-window/tooltime-frontend/6a33d4988f8db54d688e2f4a9e9103303450de61/src/assets/img/register_2.jpg -------------------------------------------------------------------------------- /src/assets/img/register_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smart-window/tooltime-frontend/6a33d4988f8db54d688e2f4a9e9103303450de61/src/assets/img/register_3.jpg -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smart-window/tooltime-frontend/6a33d4988f8db54d688e2f4a9e9103303450de61/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/slider/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smart-window/tooltime-frontend/6a33d4988f8db54d688e2f4a9e9103303450de61/src/assets/slider/1.jpg -------------------------------------------------------------------------------- /src/assets/slider/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smart-window/tooltime-frontend/6a33d4988f8db54d688e2f4a9e9103303450de61/src/assets/slider/2.jpg -------------------------------------------------------------------------------- /src/assets/slider/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smart-window/tooltime-frontend/6a33d4988f8db54d688e2f4a9e9103303450de61/src/assets/slider/3.jpg -------------------------------------------------------------------------------- /src/assets/slider/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smart-window/tooltime-frontend/6a33d4988f8db54d688e2f4a9e9103303450de61/src/assets/slider/4.jpg -------------------------------------------------------------------------------- /src/assets/ss1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smart-window/tooltime-frontend/6a33d4988f8db54d688e2f4a9e9103303450de61/src/assets/ss1.png -------------------------------------------------------------------------------- /src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 63 | 64 | 65 | 81 | -------------------------------------------------------------------------------- /src/components/core/Footer.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | 15 | 17 | -------------------------------------------------------------------------------- /src/components/core/Header.vue: -------------------------------------------------------------------------------- 1 | 117 | 118 | 149 | 150 | 169 | -------------------------------------------------------------------------------- /src/components/core/LayoutMode.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 18 | 19 | 50 | -------------------------------------------------------------------------------- /src/components/core/PageNotFound.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 13 | -------------------------------------------------------------------------------- /src/components/filters/PriceFilter.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 76 | 77 | 132 | -------------------------------------------------------------------------------- /src/components/order/OrderDetail.vue: -------------------------------------------------------------------------------- 1 | 160 | 161 | 355 | 356 | -------------------------------------------------------------------------------- /src/components/order/OrderList.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 65 | 84 | -------------------------------------------------------------------------------- /src/components/pagination/Pagination.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 36 | 37 | 39 | -------------------------------------------------------------------------------- /src/components/products/CategoryFilter.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable at-rule-empty-line-before,at-rule-name-space-after,at-rule-no-unknown */ 2 | /* stylelint-disable no-duplicate-selectors */ 3 | /* stylelint-disable */ 4 | /* stylelint-disable declaration-bang-space-before,no-duplicate-selectors,string-no-newline */ 5 | .list-group-item { 6 | border-left : 0; 7 | border-right : 0; 8 | } 9 | .ant-collapse { 10 | box-sizing: border-box; 11 | margin: 0; 12 | padding: 0; 13 | color: rgba(0, 0, 0, 0.65); 14 | font-size: 14px; 15 | font-variant: tabular-nums; 16 | line-height: 1.5; 17 | list-style: none; 18 | font-feature-settings: 'tnum'; 19 | background-color: #fafafa; 20 | border: 1px solid #d9d9d9; 21 | border-bottom: 0; 22 | border-radius: 4px; 23 | } 24 | .ant-collapse > .ant-collapse-item { 25 | border-bottom: 1px solid #d9d9d9; 26 | } 27 | .ant-collapse > .ant-collapse-item:last-child, 28 | .ant-collapse > .ant-collapse-item:last-child > .ant-collapse-header { 29 | border-radius: 0 0 4px 4px; 30 | } 31 | .ant-collapse > .ant-collapse-item > .ant-collapse-header { 32 | position: relative; 33 | padding: 12px 16px; 34 | padding-left: 40px; 35 | color: rgba(0, 0, 0, 0.85); 36 | line-height: 22px; 37 | cursor: pointer; 38 | transition: all 0.3s; 39 | } 40 | .ant-collapse > .ant-collapse-item > .ant-collapse-header .ant-collapse-arrow { 41 | color: inherit; 42 | font-style: normal; 43 | line-height: 0; 44 | text-align: center; 45 | text-transform: none; 46 | vertical-align: -0.125em; 47 | text-rendering: optimizeLegibility; 48 | -webkit-font-smoothing: antialiased; 49 | -moz-osx-font-smoothing: grayscale; 50 | position: absolute; 51 | top: 50%; 52 | left: 16px; 53 | display: inline-block; 54 | font-size: 12px; 55 | transform: translateY(-50%); 56 | } 57 | .ant-collapse > .ant-collapse-item > .ant-collapse-header .ant-collapse-arrow > * { 58 | line-height: 1; 59 | } 60 | .ant-collapse > .ant-collapse-item > .ant-collapse-header .ant-collapse-arrow svg { 61 | display: inline-block; 62 | } 63 | .ant-collapse > .ant-collapse-item > .ant-collapse-header .ant-collapse-arrow::before { 64 | display: none; 65 | } 66 | .ant-collapse > .ant-collapse-item > .ant-collapse-header .ant-collapse-arrow .ant-collapse > .ant-collapse-item > .ant-collapse-header .ant-collapse-arrow-icon { 67 | display: block; 68 | } 69 | .ant-collapse > .ant-collapse-item > .ant-collapse-header .ant-collapse-arrow svg { 70 | transition: transform 0.24s; 71 | } 72 | .ant-collapse > .ant-collapse-item > .ant-collapse-header .ant-collapse-extra { 73 | float: right; 74 | } 75 | .ant-collapse > .ant-collapse-item > .ant-collapse-header:focus { 76 | outline: none; 77 | } 78 | .ant-collapse > .ant-collapse-item.ant-collapse-no-arrow > .ant-collapse-header { 79 | padding-left: 12px; 80 | } 81 | .ant-collapse-icon-position-right > .ant-collapse-item > .ant-collapse-header { 82 | padding: 12px 16px; 83 | padding-right: 40px; 84 | } 85 | .ant-collapse-icon-position-right > .ant-collapse-item > .ant-collapse-header .ant-collapse-arrow { 86 | right: 16px; 87 | left: auto; 88 | } 89 | .ant-collapse-anim-active { 90 | transition: height 0.2s cubic-bezier(0.215, 0.61, 0.355, 1); 91 | } 92 | .ant-collapse-content { 93 | overflow: hidden; 94 | color: rgba(0, 0, 0, 0.65); 95 | background-color: #fff; 96 | border-top: 1px solid #d9d9d9; 97 | } 98 | .ant-collapse-content > .ant-collapse-content-box { 99 | padding: 0px; 100 | } 101 | .ant-collapse-content-inactive { 102 | display: none; 103 | } 104 | .ant-collapse-item:last-child > .ant-collapse-content { 105 | border-radius: 0 0 4px 4px; 106 | } 107 | .ant-collapse-borderless { 108 | background-color: #fafafa; 109 | border: 0; 110 | } 111 | .ant-collapse-borderless > .ant-collapse-item { 112 | border-bottom: 1px solid #d9d9d9; 113 | } 114 | .ant-collapse-borderless > .ant-collapse-item:last-child, 115 | .ant-collapse-borderless > .ant-collapse-item:last-child .ant-collapse-header { 116 | border-radius: 0; 117 | } 118 | .ant-collapse-borderless > .ant-collapse-item > .ant-collapse-content { 119 | background-color: transparent; 120 | border-top: 0; 121 | } 122 | .ant-collapse-borderless > .ant-collapse-item > .ant-collapse-content > .ant-collapse-content-box { 123 | padding-top: 4px; 124 | } 125 | .ant-collapse .ant-collapse-item-disabled > .ant-collapse-header, 126 | .ant-collapse .ant-collapse-item-disabled > .ant-collapse-header > .arrow { 127 | color: rgba(0, 0, 0, 0.25); 128 | cursor: not-allowed; 129 | } 130 | -------------------------------------------------------------------------------- /src/components/products/Product.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 58 | 59 | 120 | -------------------------------------------------------------------------------- /src/components/products/ProductDetail.vue: -------------------------------------------------------------------------------- 1 | 63 | 64 | 111 | 112 | 114 | -------------------------------------------------------------------------------- /src/components/products/ProductList.vue: -------------------------------------------------------------------------------- 1 | 104 | 105 | 228 | 229 | 280 | -------------------------------------------------------------------------------- /src/components/products/ProductSlider.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 41 | 42 | 69 | -------------------------------------------------------------------------------- /src/components/products/ProductSliderDots.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 32 | 33 | 67 | -------------------------------------------------------------------------------- /src/components/shopping-cart/ShoppingCartContainer.vue: -------------------------------------------------------------------------------- 1 | 108 | 109 | 188 | 189 | 191 | -------------------------------------------------------------------------------- /src/components/shopping-cart/ShoppingCartItem.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 86 | 87 | 170 | -------------------------------------------------------------------------------- /src/components/slider/Slider.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 62 | -------------------------------------------------------------------------------- /src/config/index.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | API_URL: process.env.VUE_APP_API_URL || 'http://localhost:3000', 3 | } 4 | export default config 5 | -------------------------------------------------------------------------------- /src/data/index.js: -------------------------------------------------------------------------------- 1 | export const sampleImages = ['/images/sample.png', '/images/sample.png', '/images/sample.png'] 2 | -------------------------------------------------------------------------------- /src/filters/brandFilter.js: -------------------------------------------------------------------------------- 1 | export const brandFilter = (arr, brand) => { 2 | if(!brand) return arr; 3 | 4 | return arr.filter(product => brand.includes(product.brand)); 5 | }; -------------------------------------------------------------------------------- /src/filters/orderByFilter.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | export const orderByFilter = (arr, type ) => { 4 | if(!type) return arr; 5 | if(type === 'asc') { 6 | return arr.slice().sort((el1, el2) => el1.price - el2.price); 7 | } else { 8 | return arr.slice().sort((el1, el2) => el2.price - el1.price); 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /src/filters/paginationFilter.js: -------------------------------------------------------------------------------- 1 | export const paginationPipe = (state,args) => { 2 | if (!args || !args.perPage || !args.currentPage) { 3 | return state; 4 | } 5 | const location = (args.perPage * (args.currentPage - 1)) || 0 ; 6 | 7 | return state.slice(location, location + args.perPage); 8 | }; 9 | 10 | -------------------------------------------------------------------------------- /src/filters/priceFormatter.js: -------------------------------------------------------------------------------- 1 | export const formatMoney = (price) => { 2 | return price.toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,'); 3 | }; -------------------------------------------------------------------------------- /src/filters/shortenTitle.js: -------------------------------------------------------------------------------- 1 | export const shortenTitle = (title) => { 2 | return title.split(' (')[0]; 3 | }; -------------------------------------------------------------------------------- /src/google-map.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import * as VueGoogleMaps from 'vue2-google-maps' 3 | import VueGoogleAutocomplete from 'vue-google-autocomplete' 4 | 5 | Vue.use(VueGoogleMaps, { 6 | load: { 7 | key: 'AIzaSyASyYRBZmULmrmw_P9kgr7_266OhFNinPA', 8 | libraries: 'places', // This is required if you use the Autocomplete plugin 9 | }, 10 | }) 11 | 12 | Vue.use(VueGoogleAutocomplete) 13 | -------------------------------------------------------------------------------- /src/layouts/auth/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 21 | 22 | -------------------------------------------------------------------------------- /src/layouts/main/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 55 | 60 | 61 | 101 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import './styles/global.scss' 4 | import '@fortawesome/fontawesome-free/css/all.css' 5 | Vue.config.devtools = true 6 | import store from './store' 7 | import VueSwal from 'vue-swal' 8 | import router from './router' 9 | 10 | import { BootstrapVue, BootstrapVueIcons } from 'bootstrap-vue' 11 | import Notifications from 'vue-notification' 12 | import './google-map' 13 | 14 | Vue.use(VueSwal) 15 | Vue.use(BootstrapVue) 16 | Vue.use(BootstrapVueIcons) 17 | Vue.use(Notifications) 18 | 19 | Vue.prototype.$notification = Notifications 20 | 21 | //filters 22 | import { brandFilter } from './filters/brandFilter' 23 | import { paginationPipe } from './filters/paginationFilter' 24 | import { shortenTitle } from './filters/shortenTitle' 25 | 26 | Vue.config.productionTip = false 27 | 28 | Vue.filter('brandFilter', brandFilter) 29 | Vue.filter('shortenTitle', shortenTitle) 30 | Vue.filter('pagination', paginationPipe) 31 | 32 | new Vue({ 33 | render: h => h(App), 34 | store, 35 | router, 36 | }).$mount('#app') 37 | -------------------------------------------------------------------------------- /src/pages/Alert.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 28 | 29 | 31 | -------------------------------------------------------------------------------- /src/pages/BillingPlan.vue: -------------------------------------------------------------------------------- 1 | 63 | 64 | 211 | 212 | 233 | -------------------------------------------------------------------------------- /src/pages/Dashboard.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 57 | 58 | 60 | -------------------------------------------------------------------------------- /src/pages/Home.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 31 | 32 | 34 | -------------------------------------------------------------------------------- /src/pages/OrderPage.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | -------------------------------------------------------------------------------- /src/pages/ProductDetailPage.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 41 | 42 | 44 | -------------------------------------------------------------------------------- /src/pages/ProfilePage.vue: -------------------------------------------------------------------------------- 1 | 88 | 89 | -------------------------------------------------------------------------------- /src/pages/ShoppingCartPage.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | 13 | 15 | -------------------------------------------------------------------------------- /src/pages/auth/404.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | -------------------------------------------------------------------------------- /src/pages/auth/ConfirmPage.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 27 | 28 | 30 | -------------------------------------------------------------------------------- /src/pages/auth/ImgDiv.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /src/pages/auth/LoginPage.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 85 | 86 | -------------------------------------------------------------------------------- /src/pages/auth/RegisterPage.vue: -------------------------------------------------------------------------------- 1 | 247 | 248 | 423 | 424 | 455 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import store from '@/store' 4 | import { CLEAR_BRAND_FILTER, CLEAR_ORDER_BY_PRICE } from './store/types' 5 | import Home from './pages/Home' 6 | import ProductDetailPage from './pages/ProductDetailPage' 7 | import ShoppingCartPage from './pages/ShoppingCartPage' 8 | import PageNotFound from './pages/auth/404' 9 | import LoginPage from './pages/auth/LoginPage' 10 | import RegisterPage from './pages/auth/RegisterPage' 11 | import ConfirmPage from './pages/auth/ConfirmPage' 12 | import MainLayout from './layouts/main' 13 | import AuthLayout from './layouts/auth' 14 | import ProfilePage from './pages/ProfilePage' 15 | import OrderPage from './pages/OrderPage' 16 | import Alert from './pages/Alert' 17 | import Dashboard from './pages/Dashboard' 18 | import BillingPlan from './pages/BillingPlan' 19 | var ping = require('ping') 20 | Vue.use(Router) 21 | 22 | const router = new Router({ 23 | base: process.env.BASE_URL, 24 | mode: 'history', 25 | scrollBehavior() { 26 | return { x: 0, y: 0 } 27 | }, 28 | routes: [ 29 | { 30 | path: '/alert', 31 | component: Alert, 32 | }, 33 | { 34 | path: '/', 35 | redirect: '/', 36 | component: MainLayout, 37 | meta: { 38 | authRequired: true, 39 | hidden: true, 40 | }, 41 | children: [ 42 | { 43 | path: '/', 44 | component: Dashboard, 45 | }, 46 | { 47 | path: '/billing-plan', 48 | component: BillingPlan, 49 | }, 50 | { 51 | path: '/products', 52 | component: Home, 53 | }, 54 | { 55 | path: '/product/:id', 56 | component: ProductDetailPage, 57 | }, 58 | { 59 | path: '/cart', 60 | component: ShoppingCartPage, 61 | }, 62 | { 63 | path: '/profile', 64 | component: ProfilePage, 65 | }, 66 | { 67 | path: '/order', 68 | component: OrderPage, 69 | }, 70 | { 71 | path: '/order/:id', 72 | component: OrderPage, 73 | }, 74 | ], 75 | }, 76 | 77 | { 78 | path: '/auth', 79 | redirect: 'auth/login', 80 | component: AuthLayout, 81 | children: [ 82 | { 83 | path: '/auth/404', 84 | meta: { 85 | title: 'Error 404', 86 | }, 87 | component: PageNotFound, 88 | }, 89 | { 90 | path: '/auth/login', 91 | meta: { 92 | title: 'Sign In', 93 | }, 94 | component: LoginPage, 95 | }, 96 | { 97 | path: '/auth/register', 98 | meta: { 99 | title: 'Sign Up', 100 | }, 101 | component: RegisterPage, 102 | }, 103 | { 104 | path: '/auth/confirm/:confirmationCode', 105 | meta: { 106 | title: 'Confirmation Code', 107 | }, 108 | component: ConfirmPage, 109 | }, 110 | ], 111 | }, 112 | 113 | // Redirect to 404 114 | { 115 | path: '*', 116 | redirect: 'auth/404', 117 | hidden: true, 118 | }, 119 | ], 120 | }) 121 | 122 | router.beforeEach((to, from, next) => { 123 | var cfg = { 124 | timeout: 10, 125 | // WARNING: -i 2 may not work in other platform like windows 126 | extra: ['-i', '2'], 127 | } 128 | 129 | var hosts = ['google.com', '8.8.4.4']; 130 | hosts.forEach(async (host) => { 131 | await ping.sys.probe(host, function (isAlive) { 132 | var msg = isAlive ? 'host ' + host + ' is alive' : 'host ' + host + ' is dead'; 133 | return msg 134 | }, cfg) 135 | }) 136 | 137 | if (to.matched.some(record => record.meta.authRequired)) { 138 | if (!store.state.user.authorized) { 139 | next({ 140 | path: '/auth/login', 141 | query: { redirect: to.fullPath }, 142 | }) 143 | } else { 144 | next() 145 | } 146 | } else { 147 | next() 148 | } 149 | }) 150 | 151 | router.afterEach(() => { 152 | store.commit(CLEAR_BRAND_FILTER) 153 | store.commit(CLEAR_ORDER_BY_PRICE) 154 | }) 155 | 156 | export default router 157 | -------------------------------------------------------------------------------- /src/services/api/index.js: -------------------------------------------------------------------------------- 1 | import axiosClient from '../axios' 2 | import router from '@/router' 3 | 4 | const successFunc = response => { 5 | return response.data 6 | } 7 | 8 | const failFunc = err => { 9 | if (err.message == "Network Error") { 10 | router.push('/alert') 11 | } else { 12 | throw new Error(err.message) 13 | } 14 | } 15 | 16 | export const getProducts = async () => { 17 | return axiosClient 18 | .get('/product') 19 | .then(successFunc) 20 | .catch(failFunc) 21 | } 22 | 23 | export const getProduct = async productId => { 24 | return axiosClient 25 | .get(`/product/${productId}`) 26 | .then(successFunc) 27 | .catch(failFunc) 28 | } 29 | 30 | export const getCategories = async () => { 31 | return axiosClient 32 | .get('/category') 33 | .then(successFunc) 34 | .catch(failFunc) 35 | } 36 | 37 | export const getServiceAreas = async () => { 38 | return axiosClient 39 | .get('/auth/servicearea') 40 | .then(successFunc) 41 | .catch(failFunc) 42 | } 43 | 44 | export const getLocations = async () => { 45 | return axiosClient 46 | .get('/location') 47 | .then(successFunc) 48 | .catch(failFunc) 49 | } 50 | 51 | export const getOrders = async () => { 52 | return axiosClient 53 | .get('/order') 54 | .then(successFunc) 55 | .catch(failFunc) 56 | } 57 | 58 | export const getOrder = async orderId => { 59 | return axiosClient 60 | .get(`/order/${orderId}`) 61 | .then(successFunc) 62 | .catch(failFunc) 63 | } 64 | 65 | export const createOrder = async request => { 66 | return axiosClient 67 | .post('/order', request) 68 | .then(successFunc) 69 | .catch(failFunc) 70 | } 71 | 72 | export const updateOrder = async (orderId, request) => { 73 | return axiosClient 74 | .patch(`/order/${orderId}`, request) 75 | .then(successFunc) 76 | .catch(failFunc) 77 | } 78 | 79 | export const removeOrder = async orderId => { 80 | return axiosClient 81 | .delete(`/order/${orderId}`) 82 | .then(successFunc) 83 | .catch(failFunc) 84 | } 85 | 86 | export const removeOrderItem = async orderItemId => { 87 | return axiosClient 88 | .delete(`/order-item/${orderItemId}`) 89 | .then(successFunc) 90 | .catch(failFunc) 91 | } 92 | 93 | export const updateProfile = async (customerId, updates) => { 94 | return axiosClient 95 | .patch(`/customer/${customerId}`, updates) 96 | .then(successFunc) 97 | .catch(failFunc) 98 | } 99 | 100 | 101 | export const checkoutSession = async (sessionId) => { 102 | return axiosClient.get('/stripe/checkout-session?sessionId=' + sessionId) 103 | .then(successFunc) 104 | .catch(failFunc) 105 | } 106 | 107 | export const getConfig = async () => { 108 | return axiosClient.get('/stripe/config') 109 | .then(successFunc) 110 | .catch(failFunc) 111 | } 112 | 113 | export const cancelSubscription = async (request) => { 114 | return axiosClient 115 | .post('/stripe/cancel-subscription', request) 116 | .then(successFunc) 117 | .catch(failFunc) 118 | } 119 | 120 | export const updateSubscription = async (request) => { 121 | return axiosClient 122 | .post('/stripe/update-subscription', request) 123 | .then(successFunc) 124 | .catch(failFunc) 125 | } 126 | 127 | export const resend_code = async (request) => { 128 | return axiosClient 129 | .post('/auth/resend_code', request) 130 | .then(successFunc) 131 | .catch(failFunc) 132 | } 133 | 134 | export const saveSubscription = async (request) => { 135 | return axiosClient 136 | .post('/auth/save-subscription', request) 137 | .then(successFunc) 138 | .catch(failFunc) 139 | } 140 | -------------------------------------------------------------------------------- /src/services/auth/index.js: -------------------------------------------------------------------------------- 1 | import apiClient from '@/services/axios' 2 | import router from '@/router' 3 | import store from 'store' 4 | 5 | const failFunc = err => { 6 | if (err.message == "Network Error") { 7 | router.push("/alert") 8 | } else { 9 | throw new Error(err.message) 10 | } 11 | } 12 | 13 | export async function login(email, password) { 14 | return apiClient 15 | .post('/auth/login', { 16 | email, 17 | password, 18 | }) 19 | .then(response => { 20 | if (response) { 21 | const { accessToken } = response.data 22 | if (accessToken) { 23 | store.set('accessToken', accessToken) 24 | } 25 | swal('You have successfully logged in!') 26 | return response.data 27 | } 28 | return false 29 | }) 30 | .catch(err => { 31 | if (err.message == "Network Error") { 32 | failFunc(err) 33 | } else { 34 | swal(err.response.data) 35 | } 36 | }) 37 | } 38 | 39 | export async function register(request) { 40 | return apiClient 41 | .post('/auth/register', request) 42 | .then(response => { 43 | return response.data 44 | }) 45 | .catch(failFunc) 46 | } 47 | 48 | export const verifyUser = (code) => { 49 | return apiClient 50 | .get('/auth/confirm/' + code).then((response) => { 51 | return response.data; 52 | }); 53 | }; 54 | 55 | export async function currentAccount() { 56 | return apiClient 57 | .get('/auth/account') 58 | .then(response => { 59 | if (response) { 60 | const { accessToken } = response.data 61 | if (accessToken) { 62 | store.set('accessToken', accessToken) 63 | } 64 | return response.data 65 | } 66 | return false 67 | }) 68 | .catch(failFunc) 69 | } 70 | 71 | export async function logout() { 72 | return apiClient 73 | .get('/auth/logout') 74 | .then(() => { 75 | store.remove('accessToken') 76 | return true 77 | }) 78 | .catch(failFunc) 79 | } 80 | -------------------------------------------------------------------------------- /src/services/axios/fakeApi/auth/index.js: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken' 2 | import mock from '../mock' 3 | 4 | const users = [ 5 | { 6 | id: 1, 7 | email: 'admin@admin.com', 8 | password: 'admin', 9 | name: 'Admin', 10 | avatar: '', 11 | role: 'admin', 12 | }, 13 | ] 14 | 15 | const jwtConfig = { 16 | secret: 'RM8EPpgXwovR9fp6ryDIoGHAB6iHsc0fb', 17 | expiresIn: 1 * 24 * 60 * 60 * 1000, 18 | } 19 | 20 | mock.onPost('/api/auth/login').reply(request => { 21 | const { email, password } = JSON.parse(request.data) 22 | const user = users.find(item => item.email === email && item.password === password) 23 | const error = user ? 'Something went wrong.' : 'Login failed, please try again' 24 | 25 | if (user) { 26 | const userData = Object.assign({}, user) 27 | delete userData.password 28 | userData.accessToken = jwt.sign({ id: userData.id }, jwtConfig.secret, { 29 | expiresIn: jwtConfig.expiresIn, 30 | }) // generate jwt token 31 | 32 | return [200, userData] 33 | } 34 | 35 | return [401, error] 36 | }) 37 | 38 | mock.onPost('/api/auth/register').reply(request => { 39 | const { email, password, name } = JSON.parse(request.data) 40 | const isAlreadyRegistered = users.find(user => user.email === email) 41 | 42 | if (!isAlreadyRegistered) { 43 | const user = { 44 | id: users.length + 1, 45 | email, 46 | password, 47 | name, 48 | avatar: '', 49 | role: 'admin', 50 | } 51 | users.push(user) 52 | 53 | const userData = Object.assign({}, user) 54 | delete userData.password 55 | userData.accessToken = jwt.sign({ id: userData.id }, jwtConfig.secret, { 56 | expiresIn: jwtConfig.expiresIn, 57 | }) 58 | 59 | return [200, userData] 60 | } 61 | 62 | return [401, 'This email is already in use.'] 63 | }) 64 | 65 | mock.onGet('/api/auth/account').reply(request => { 66 | const { AccessToken } = request.headers 67 | if (AccessToken) { 68 | const { id } = jwt.verify(AccessToken, jwtConfig.secret) 69 | const userData = Object.assign( 70 | {}, 71 | users.find(item => item.id === id), 72 | ) 73 | delete userData.password 74 | userData.accessToken = jwt.sign({ id: userData.id }, jwtConfig.secret, { 75 | expiresIn: jwtConfig.expiresIn, 76 | }) // refresh jwt token 77 | 78 | return [200, userData] 79 | } 80 | 81 | return [401] 82 | }) 83 | 84 | mock.onGet('/api/auth/logout').reply(() => { 85 | return [200] 86 | }) 87 | -------------------------------------------------------------------------------- /src/services/axios/fakeApi/index.js: -------------------------------------------------------------------------------- 1 | import mock from './mock' 2 | import './auth' 3 | 4 | mock.onAny().passThrough() 5 | -------------------------------------------------------------------------------- /src/services/axios/fakeApi/mock.js: -------------------------------------------------------------------------------- 1 | import MockAdapter from 'axios-mock-adapter' 2 | import apiClient from '../index' 3 | 4 | const mock = new MockAdapter(apiClient, { delayResponse: 500 }) 5 | 6 | export default mock 7 | -------------------------------------------------------------------------------- /src/services/axios/index.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import store from 'store' 3 | import config from '../../config' 4 | 5 | const apiClient = axios.create({ 6 | baseURL: `${config.API_URL}/`, 7 | }) 8 | 9 | apiClient.interceptors.request.use(request => { 10 | const accessToken = store.get('accessToken') 11 | if (accessToken) { 12 | request.headers.Authorization = `Bearer ${accessToken}` 13 | request.headers.AccessToken = accessToken 14 | } 15 | 16 | request.headers.common['Content-Type'] = 'application/json' 17 | request.headers.common['Access-Control-Allow-Origin'] = '*' 18 | return request 19 | }) 20 | 21 | export default apiClient 22 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import * as Types from './types' 4 | import { paginationPipe } from '../filters/paginationFilter' 5 | import user from './user' 6 | import * as api from '../services/api' 7 | import createPersistedState from 'vuex-persistedstate' 8 | 9 | Vue.use(Vuex) 10 | 11 | const store = new Vuex.Store({ 12 | plugins: [createPersistedState()], 13 | modules: { 14 | user, 15 | }, 16 | state: { 17 | products: [], 18 | categories: [], 19 | serviceAreas: [], 20 | locations: [], 21 | cart: [], 22 | orders: [], 23 | selectedOrder: {}, 24 | categoryFilter: [], 25 | sectionFilter: [], 26 | orderBy: '', 27 | perPage: 12, 28 | currentPage: 1, 29 | pagesToShow: 3, 30 | }, 31 | mutations: { 32 | [Types.ADD_PRODUCT_TO_CART](state, { product, quantity, startDate, endDate }) { 33 | if (product === undefined || product === null) return 34 | const cartItemIndex = state.cart.findIndex(item => item.id === product.id) 35 | 36 | if (cartItemIndex < 0) { 37 | state.cart.push({ ...product, quantity, startDate, endDate }) 38 | } else { 39 | state.cart[cartItemIndex].quantity += quantity 40 | } 41 | }, 42 | [Types.REMOVE_PRODUCT_FROM_CART](state, id) { 43 | const cartItemIndex = state.cart.findIndex(item => item.id === id) 44 | if (cartItemIndex > -1) state.cart.splice(cartItemIndex, 1) 45 | }, 46 | [Types.INCREMENT_CART_ITEM_QUANTITY](state, id) { 47 | const cartItemIndex = state.cart.findIndex(item => item.id === id) 48 | state.cart[cartItemIndex].quantity++ 49 | }, 50 | [Types.DECREMENT_CART_ITEM_QUANTITY](state, id) { 51 | const cartItemIndex = state.cart.findIndex(item => item.id === id) 52 | if (state.cart[cartItemIndex].quantity === 1) return void 0 53 | state.cart[cartItemIndex].quantity-- 54 | }, 55 | [Types.SET_CURRENT_PAGE](state, page) { 56 | state.currentPage = page 57 | }, 58 | [Types.PREV_PAGE](state) { 59 | state.currentPage-- 60 | }, 61 | [Types.NEXT_PAGE](state) { 62 | state.currentPage++ 63 | }, 64 | [Types.GO_PAGE](state, payload) { 65 | state.currentPage = payload 66 | }, 67 | [Types.ORDER_BY_ASC](state) { 68 | state.orderBy = 'asc' 69 | }, 70 | [Types.ORDER_BY_DESC](state) { 71 | state.orderBy = 'desc' 72 | }, 73 | [Types.CLEAR_ORDER_BY_PRICE](state) { 74 | state.orderBy = '' 75 | }, 76 | [Types.ADD_CATEGORY_TO_FILTER](state, categoryId) { 77 | const filters = state.categoryFilter 78 | if (filters.includes(categoryId)) return void 0 79 | 80 | filters.push(categoryId) 81 | state.categoryFilter = filters 82 | 83 | }, 84 | [Types.REMOVE_CATEGORY_FROM_FILTER](state, categoryId) { 85 | const filters = state.categoryFilter 86 | state.categoryFilter = filters.filter(filterId => filterId !== categoryId) 87 | }, 88 | [Types.ADD_SECTION_TO_FILTER](state, sectionId) { 89 | const filters = state.sectionFilter 90 | if (filters.includes(sectionId)) return void 0 91 | 92 | filters.push(sectionId) 93 | state.sectionFilter = filters 94 | }, 95 | [Types.REMOVE_SECTION_FROM_FILTER](state, sectionId) { 96 | const filters = state.sectionFilter 97 | state.sectionFilter = filters.filter(filterId => filterId !== sectionId) 98 | }, 99 | [Types.CLEAR_BRAND_FILTER](state) { 100 | state.categoryFilter = [] 101 | }, 102 | [Types.SET_CART](state, cartItems) { 103 | state.cart = cartItems 104 | }, 105 | [Types.SET_PRODUCTS](state, products) { 106 | state.products = products 107 | }, 108 | [Types.SET_CATEGORIES](state, categories) { 109 | state.categories = categories 110 | }, 111 | [Types.SET_SERVICE_AREAS](state, serviceAreas) { 112 | state.serviceAreas = serviceAreas 113 | }, 114 | [Types.SET_LOCATIONS](state, locations) { 115 | state.locations = locations 116 | }, 117 | [Types.SET_ORDERS](state, orders) { 118 | state.orders = orders 119 | }, 120 | [Types.SET_SELECTED_ORDER](state, order) { 121 | state.selectedOrder = order 122 | }, 123 | }, 124 | 125 | actions: { 126 | async LOAD_PRODUCTS({ commit }) { 127 | try { 128 | const products = await api.getProducts() 129 | commit(Types.SET_PRODUCTS, products) 130 | } catch (e) { 131 | commit(Types.SET_PRODUCTS, []) 132 | } 133 | }, 134 | async LOAD_CATEGORIES({ commit }) { 135 | try { 136 | const categories = await api.getCategories() 137 | commit(Types.SET_CATEGORIES, categories) 138 | } catch (e) { 139 | commit(Types.SET_CATEGORIES, []) 140 | } 141 | }, 142 | async LOAD_SERVICE_AREAS({ commit }) { 143 | try { 144 | const service_areas = await api.getServiceAreas() 145 | commit(Types.SET_SERVICE_AREAS, service_areas) 146 | } catch (e) { 147 | commit(Types.SET_SERVICE_AREAS, []) 148 | } 149 | }, 150 | async LOAD_LOCATIONS({ commit }) { 151 | try { 152 | const locations = await api.getLocations() 153 | commit(Types.SET_LOCATIONS, locations) 154 | } catch (e) { 155 | const locations = await api.getLocations() 156 | commit(Types.SET_LOCATIONS, locations) 157 | } 158 | }, 159 | async LOAD_ORDERS({ commit }) { 160 | try { 161 | const orders = await api.getOrders() 162 | commit(Types.SET_ORDERS, orders) 163 | } catch (e) { 164 | const orders = await api.getOrders() 165 | commit(Types.SET_ORDERS, orders) 166 | } 167 | }, 168 | async LOAD_SELECTED_ORDER({ commit }, orderId) { 169 | try { 170 | if (orderId === undefined) { 171 | return 172 | } 173 | const order = await api.getOrder(orderId) 174 | commit(Types.SET_SELECTED_ORDER, order) 175 | } catch (e) { 176 | commit(Types.SET_SELECTED_ORDER, {}) 177 | throw new Error(e.message) 178 | } 179 | }, 180 | async CREATE_ORDER({ dispatch, commit }, payload) { 181 | try { 182 | const res = await api.createOrder({ ...payload }) 183 | dispatch('LOAD_ORDERS') 184 | commit(Types.SET_CART, []) 185 | return res 186 | } catch (e) { 187 | throw new Error(e.message) 188 | } 189 | }, 190 | async UPDATE_ORDER({ dispatch }, payload) { 191 | try { 192 | await api.updateOrder(payload.orderId, payload.newOrder) 193 | dispatch('LOAD_ORDERS') 194 | } catch (e) { 195 | throw new Error(e.message) 196 | } 197 | }, 198 | async REMOVE_ORDER({ dispatch, commit }, orderId) { 199 | try { 200 | await api.removeOrder(orderId) 201 | dispatch('LOAD_ORDERS') 202 | commit(Types.SET_SELECTED_ORDER, {}) 203 | } catch (e) { 204 | throw new Error(e.message) 205 | } 206 | }, 207 | async REMOVE_ORDER_ITEM({ dispatch }, orderItemId) { 208 | try { 209 | await api.removeOrderItem(orderItemId) 210 | dispatch('LOAD_ORDERS') 211 | } catch (e) { 212 | throw new Error(e.message) 213 | } 214 | }, 215 | async UPDATE_PROFILE({ dispatch, state }, payload) { 216 | try { 217 | await api.updateProfile(state.user.id, payload) 218 | dispatch('user/LOAD_CURRENT_ACCOUNT') 219 | } catch (e) { 220 | throw new Error(e.message) 221 | } 222 | }, 223 | }, 224 | getters: { 225 | filterProducts(state) { 226 | const categoryFilter = state.categoryFilter 227 | const sectionFilter = state.sectionFilter 228 | if ((categoryFilter === undefined || categoryFilter.length === 0) && (sectionFilter === undefined || sectionFilter.length === 0)) { 229 | return state.products 230 | } else { 231 | const filteredProducts = state.products.filter(p => { 232 | if (categoryFilter.includes(p.categoryId) || sectionFilter.includes(p.sectionId)) { 233 | return p 234 | } 235 | }) 236 | return filteredProducts 237 | } 238 | }, 239 | 240 | paginate(state, getters) { 241 | return paginationPipe(getters.filterProducts, { 242 | perPage: state.perPage, 243 | currentPage: state.currentPage, 244 | }) 245 | }, 246 | brandsCount(state) { 247 | const counts = {} 248 | state.products.forEach(p => { 249 | counts[p.brand] = counts[p.brand] + 1 || 1 250 | }) 251 | 252 | return counts 253 | }, 254 | 255 | cartLength(state) { 256 | return state.cart.length 257 | }, 258 | 259 | totalCartItems(state) { 260 | return state.cart.reduce((count, curItem) => { 261 | return count + curItem.quantity 262 | }, 0) 263 | }, 264 | }, 265 | }) 266 | 267 | export default store 268 | -------------------------------------------------------------------------------- /src/store/types.js: -------------------------------------------------------------------------------- 1 | export const ADD_PRODUCT_TO_CART = 'ADD_PRODUCT_TO_CART' 2 | export const REMOVE_PRODUCT_FROM_CART = 'REMOVE_PRODUCT_FROM_CART' 3 | export const INCREMENT_CART_ITEM_QUANTITY = 'INCREMENT_CART_ITEM_QUANTITY' 4 | export const DECREMENT_CART_ITEM_QUANTITY = 'DECREMENT_CART_ITEM_QUANTITY' 5 | 6 | export const ADD_CATEGORY_TO_FILTER = 'ADD_CATEGORY_TO_FILTER' 7 | export const REMOVE_CATEGORY_FROM_FILTER = 'REMOVE_CATEGORY_FROM_FILTER' 8 | export const ADD_SECTION_TO_FILTER = 'ADD_SECTION_TO_FILTER' 9 | export const REMOVE_SECTION_FROM_FILTER = 'REMOVE_SECTION_FROM_FILTER' 10 | export const CLEAR_BRAND_FILTER = 'CLEAR_BRAND_FILTER' 11 | 12 | export const ORDER_BY_ASC = 'ORDER_BY_ASC' 13 | export const ORDER_BY_DESC = 'ORDER_BY_DESC' 14 | export const CLEAR_ORDER_BY_PRICE = 'CLEAR_ORDER_BY_PRICE' 15 | 16 | export const PREV_PAGE = 'PREV_PAGE' 17 | export const NEXT_PAGE = 'NEXT_PAGE' 18 | export const GO_PAGE = 'GO_PAGE' 19 | export const COUNT_ITEM = 'COUNT_ITEM' 20 | export const SET_CURRENT_PAGE = 'SET_CURRENT_PAGE' 21 | 22 | export const SET_PRODUCTS = 'SET_PRODUCTS' 23 | export const SET_CATEGORIES = 'SET_CATEGORIES' 24 | export const SET_SERVICE_AREAS = 'SET_SERVICE_AREAS' 25 | export const SET_LOCATIONS = 'SET_LOCATIONS' 26 | export const SET_ORDERS = 'SET_ORDERS' 27 | export const SET_CART = 'SET_CART' 28 | export const SET_SELECTED_ORDER = 'SET_SELECTED_ORDER' 29 | -------------------------------------------------------------------------------- /src/store/user/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import router from '@/router' 4 | import * as auth from '@/services/auth' 5 | 6 | Vue.use(Vuex) 7 | 8 | const INIT_STATE = { 9 | id: '', 10 | name: '', 11 | role: '', 12 | email: '', 13 | address: '', 14 | city: '', 15 | state: '', 16 | phone: '', 17 | stripeId: '', 18 | priceId: '', 19 | subscriptionId: '', 20 | status: '', 21 | authorized: false, 22 | loading: false, 23 | } 24 | 25 | export default { 26 | namespaced: true, 27 | state: INIT_STATE, 28 | 29 | mutations: { 30 | SET_STATE(state, payload) { 31 | Object.assign(state, { ...payload }) 32 | }, 33 | }, 34 | 35 | actions: { 36 | LOGIN({ commit, dispatch }, { payload }) { 37 | const { email, password } = payload 38 | commit('SET_STATE', { loading: true }) 39 | 40 | auth.login(email, password).then(success => { 41 | if (success) { 42 | dispatch('LOAD_CURRENT_ACCOUNT') 43 | commit('SET_STATE', { authorized: true }) 44 | } 45 | 46 | if (!success) { 47 | commit('SET_STATE', { loading: false }) 48 | } 49 | }) 50 | }, 51 | 52 | LOAD_CURRENT_ACCOUNT({ commit, dispatch }) { 53 | commit('SET_STATE', { loading: true }) 54 | 55 | auth 56 | .currentAccount() 57 | .then(response => { 58 | if (response) { 59 | commit('SET_STATE', { ...response, authorized: true }) 60 | dispatch('LOAD_PRODUCTS', {}, { root: true }) 61 | dispatch('LOAD_CATEGORIES', {}, { root: true }) 62 | dispatch('LOAD_SERVICE_AREAS', {}, { root: true }) 63 | dispatch('LOAD_LOCATIONS', {}, { root: true }) 64 | dispatch('LOAD_ORDERS', {}, { root: true }) 65 | } 66 | 67 | commit('SET_STATE', { loading: false }) 68 | }) 69 | .catch(() => { 70 | commit('SET_STATE', INIT_STATE) 71 | 72 | if (router.options.routes[2].path != '/auth') { 73 | router.push('/auth/login') 74 | } 75 | }) 76 | }, 77 | 78 | LOGOUT({ commit }) { 79 | auth.logout().then(() => { 80 | commit('SET_STATE', INIT_STATE) 81 | 82 | router.push('/auth/login') 83 | }) 84 | }, 85 | 86 | REGISTER({ commit }, request) { 87 | commit('SET_STATE', { loading: true }) 88 | auth 89 | .register(request) 90 | .then((response) => { 91 | response.status = null 92 | commit('SET_STATE', { tmp_email: response.email, loading: false }) 93 | }) 94 | .catch(err => { 95 | throw new Error(err.message) 96 | }) 97 | }, 98 | }, 99 | 100 | getters: { 101 | user: state => state, 102 | }, 103 | } 104 | -------------------------------------------------------------------------------- /src/styles/borders.scss: -------------------------------------------------------------------------------- 1 | @import 'mixins.scss'; 2 | 3 | .border { 4 | &-1 { 5 | border: 1px solid; 6 | } 7 | &-top { 8 | &-1 { 9 | border-top: 1px solid; 10 | } 11 | } 12 | &-bottom { 13 | &-1 { 14 | border-bottom: 1px solid; 15 | } 16 | } 17 | } 18 | 19 | .border-custom { 20 | border-color: #ced4da!important; 21 | } -------------------------------------------------------------------------------- /src/styles/buttons.scss: -------------------------------------------------------------------------------- 1 | @import 'mixins.scss'; 2 | 3 | .btn { 4 | &-primary { 5 | color: #fff; 6 | background-color: $primary; 7 | border-color: $primary; 8 | &:hover, 9 | &:active { 10 | background-color: $primary-light !important; 11 | border-color: $primary-light !important; 12 | } 13 | } 14 | 15 | &-secondary { 16 | color: $primary; 17 | background-color: white; 18 | border-color: $primary; 19 | &:hover, 20 | &:active { 21 | background-color: $primary !important; 22 | border-color: $primary !important; 23 | } 24 | &:focus { 25 | background-color: none !important; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/styles/colors.scss: -------------------------------------------------------------------------------- 1 | @import 'mixins.scss'; 2 | 3 | ///////////////////////////////////////////////////////////////////////////////////////// 4 | /* "COLORS" STYLES */ 5 | 6 | // Redefined with the /vendors/bootstrap/css/utilities.scss styles if CleanUI KIT bootstrap theme installed 7 | 8 | // font color 9 | .text { 10 | &-default { 11 | color: $default !important; 12 | } 13 | &-primary { 14 | color: $primary !important; 15 | } 16 | &-secondary { 17 | color: $secondary !important; 18 | } 19 | &-success { 20 | color: $success !important; 21 | } 22 | &-danger { 23 | color: $danger !important; 24 | } 25 | &-warning { 26 | color: $warning !important; 27 | } 28 | &-info { 29 | color: $info !important; 30 | } 31 | &-light { 32 | color: $light !important; 33 | } 34 | &-dark { 35 | color: $dark !important; 36 | } 37 | &-white { 38 | color: $white !important; 39 | } 40 | &-muted { 41 | color: $gray-5 !important; 42 | } 43 | &-blue { 44 | color: $blue !important; 45 | } 46 | &-blue-light { 47 | color: $blue-light !important; 48 | } 49 | &-red { 50 | color: $red !important; 51 | } 52 | &-yellow { 53 | color: $yellow !important; 54 | } 55 | &-pink { 56 | color: $pink !important; 57 | } 58 | &-orange { 59 | color: $orange !important; 60 | } 61 | &-gray-1 { 62 | color: $gray-1 !important; 63 | } 64 | &-gray-2 { 65 | color: $gray-2 !important; 66 | } 67 | &-gray-3 { 68 | color: $gray-3 !important; 69 | } 70 | &-gray-4 { 71 | color: $gray-4 !important; 72 | } 73 | &-gray-5 { 74 | color: $gray-5 !important; 75 | } 76 | &-gray-6 { 77 | color: $gray-6 !important; 78 | } 79 | } 80 | 81 | // background color 82 | .bg { 83 | &-default { 84 | background-color: $default !important; 85 | } 86 | &-primary { 87 | background-color: $primary !important; 88 | } 89 | &-primary-dark { 90 | background-color: $primary-dark !important; 91 | } 92 | &-secondary { 93 | background-color: $secondary !important; 94 | } 95 | &-success { 96 | background-color: $success !important; 97 | } 98 | &-danger { 99 | background-color: $danger !important; 100 | } 101 | &-warning { 102 | background-color: $warning !important; 103 | } 104 | &-info { 105 | background-color: $info !important; 106 | } 107 | &-light { 108 | background-color: $light !important; 109 | } 110 | &-dark { 111 | background-color: $dark !important; 112 | } 113 | &-white { 114 | background-color: $white !important; 115 | } 116 | &-blue { 117 | background-color: $blue !important; 118 | } 119 | &-blue-light { 120 | background-color: $blue-light !important; 121 | } 122 | &-red { 123 | background-color: $red !important; 124 | } 125 | &-yellow { 126 | background-color: $yellow !important; 127 | } 128 | &-orange { 129 | background-color: $orange !important; 130 | } 131 | &-gray-1 { 132 | background-color: $gray-1 !important; 133 | } 134 | &-gray-2 { 135 | background-color: $gray-2 !important; 136 | } 137 | &-gray-3 { 138 | background-color: $gray-3 !important; 139 | } 140 | &-gray-4 { 141 | background-color: $gray-4 !important; 142 | } 143 | &-gray-5 { 144 | background-color: $gray-5 !important; 145 | } 146 | &-gray-6 { 147 | background-color: $gray-6 !important; 148 | } 149 | } 150 | 151 | // border color 152 | .border { 153 | &-default { 154 | border-color: $default !important; 155 | } 156 | &-primary { 157 | border-color: $primary !important; 158 | } 159 | &-secondary { 160 | border-color: $primary !important; 161 | } 162 | &-success { 163 | border-color: $success !important; 164 | } 165 | &-danger { 166 | border-color: $danger !important; 167 | } 168 | &-warning { 169 | border-color: $warning !important; 170 | } 171 | &-info { 172 | border-color: $info !important; 173 | } 174 | &-light { 175 | border-color: $light !important; 176 | } 177 | &-dark { 178 | border-color: $dark !important; 179 | } 180 | &-white { 181 | border-color: $white !important; 182 | } 183 | &-blue { 184 | border-color: $blue !important; 185 | } 186 | &-blue-light { 187 | border-color: $blue-light !important; 188 | } 189 | &-red { 190 | border-color: $red !important; 191 | } 192 | &-yellow { 193 | border-color: $yellow !important; 194 | } 195 | &-orange { 196 | border-color: $orange !important; 197 | } 198 | &-gray-1 { 199 | border-color: $gray-1 !important; 200 | } 201 | &-gray-2 { 202 | border-color: $gray-2 !important; 203 | } 204 | &-gray-3 { 205 | border-color: $gray-3 !important; 206 | } 207 | &-gray-4 { 208 | border-color: $gray-4 !important; 209 | } 210 | &-gray-5 { 211 | border-color: $gray-5 !important; 212 | } 213 | &-gray-6 { 214 | border-color: $gray-6 !important; 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/styles/core.scss: -------------------------------------------------------------------------------- 1 | @import 'mixins.scss'; 2 | 3 | ///////////////////////////////////////////////////////////////////////////////////////// 4 | /* "CORE" STYLES */ 5 | 6 | :root { 7 | --kit-color-primary: #984c1a; 8 | --primary: #984c1a; 9 | --kit-font-family: 'Mukta', sans-serif; 10 | } 11 | 12 | html { 13 | font-size: 15px; 14 | @media (max-width: $sm-max-width) { 15 | font-size: 14px; 16 | } 17 | } 18 | 19 | body { 20 | font-size: 1rem; 21 | line-height: 1.5; 22 | font-family: $base-font-family; 23 | color: $text; 24 | overflow-x: hidden; 25 | position: relative; 26 | font-variant: normal; 27 | font-feature-settings: normal; 28 | } 29 | 30 | a { 31 | text-decoration: none; 32 | color: $primary; 33 | @include transition-color(); 34 | &:hover, 35 | &:active, 36 | &:focus { 37 | color: $primary-dark; 38 | text-decoration: none; 39 | } 40 | } 41 | 42 | a.nav-link { 43 | text-decoration: none; 44 | color: $primary !important; 45 | @include transition-color(); 46 | &:hover, 47 | &:active, 48 | &:focus { 49 | color: $primary-dark !important; 50 | text-decoration: none; 51 | } 52 | } 53 | 54 | input { 55 | outline: none !important; 56 | font-family: $base-font-family; 57 | color: $text; 58 | } 59 | 60 | button, 61 | input { 62 | box-shadow: none !important; 63 | outline: none !important; 64 | } 65 | 66 | input[type='text'], 67 | input[type='password'], 68 | input[type='email'], 69 | textarea { 70 | appearance: none !important; 71 | } 72 | 73 | h1, 74 | h2, 75 | h3, 76 | h4, 77 | h5, 78 | h6 { 79 | color: $black; 80 | } 81 | 82 | svg { 83 | vertical-align: initial; 84 | overflow: auto; 85 | } 86 | 87 | .badge-example { 88 | font-size: rem(14); 89 | text-transform: uppercase; 90 | margin-bottom: rem(15); 91 | background: $gray-2; 92 | color: $black; 93 | display: inline-block; 94 | padding: rem(3) rem(6); 95 | border-radius: 4px; 96 | } 97 | 98 | // dark theme 99 | [data-kit-theme='dark'] { 100 | body { 101 | background: $dark-gray-5; 102 | color: $dark-gray-1; 103 | } 104 | 105 | .badge-example { 106 | background: $dark-gray-4; 107 | } 108 | 109 | h1, 110 | h2, 111 | h3, 112 | h4, 113 | h5, 114 | h6 { 115 | color: $dark-gray-1; 116 | } 117 | 118 | h1 { 119 | font-size: rem(18); 120 | } 121 | h2 { 122 | font-size: rem(16); 123 | } 124 | h3 { 125 | font-size: rem(14); 126 | } 127 | h4 { 128 | font-size: rem(8) !important; 129 | } 130 | h5 { 131 | font-size: rem(10); 132 | } 133 | h6 { 134 | font-size: rem(8); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/styles/global.scss: -------------------------------------------------------------------------------- 1 | @import 'node_modules/bootstrap/scss/functions'; 2 | @import 'node_modules/bootstrap/scss/variables'; 3 | @import 'mixins.scss'; 4 | 5 | @import 'node_modules/bootstrap/scss/bootstrap'; 6 | @import 'node_modules/bootstrap-vue/src/index.scss'; 7 | @import 'mixins.scss'; 8 | @import 'core.scss'; 9 | @import 'measurements.scss'; 10 | @import 'colors.scss'; 11 | @import 'borders.scss'; 12 | @import 'buttons.scss'; 13 | 14 | html { 15 | height: 100%; 16 | } 17 | body { 18 | min-height: 100%; 19 | padding: 0; 20 | margin: 0; 21 | position: relative; 22 | } 23 | 24 | body::after { 25 | content: ''; 26 | display: block; 27 | height: 100px; 28 | } 29 | 30 | footer.main-footer { 31 | position: absolute; 32 | bottom: 0; 33 | width: 100%; 34 | height: 100px; 35 | } 36 | 37 | input::-webkit-calendar-picker-indicator { 38 | opacity: 0; 39 | } 40 | 41 | // ANY GLOBAL STYLES SHOULD BE DECLARED HERE. -------------------------------------------------------------------------------- /src/styles/measurements.scss: -------------------------------------------------------------------------------- 1 | @import 'mixins.scss'; 2 | 3 | ///////////////////////////////////////////////////////////////////////////////////////// 4 | /* "MEASUREMENTS" STYLES */ 5 | 6 | // font size 7 | .font-size { 8 | &-0 { 9 | font-size: rem(0); 10 | } 11 | &-10 { 12 | font-size: rem(10); 13 | } 14 | &-12 { 15 | font-size: rem(12); 16 | } 17 | &-14 { 18 | font-size: rem(14); 19 | } 20 | &-16 { 21 | font-size: rem(16); 22 | } 23 | &-18 { 24 | font-size: rem(18); 25 | } 26 | &-21 { 27 | font-size: rem(20); 28 | } 29 | &-24 { 30 | font-size: rem(24); 31 | } 32 | &-28 { 33 | font-size: rem(26); 34 | } 35 | &-30 { 36 | font-size: rem(30); 37 | } 38 | &-32 { 39 | font-size: rem(30); 40 | } 41 | &-36 { 42 | font-size: rem(36); 43 | } 44 | &-40 { 45 | font-size: rem(40); 46 | } 47 | &-48 { 48 | font-size: rem(48); 49 | } 50 | &-50 { 51 | font-size: rem(50); 52 | } 53 | &-60 { 54 | font-size: rem(60); 55 | } 56 | &-70 { 57 | font-size: rem(70); 58 | } 59 | &-80 { 60 | font-size: rem(80); 61 | } 62 | } 63 | 64 | // quick height 65 | .height { 66 | &-0 { 67 | height: rem(0); 68 | } 69 | &-10 { 70 | height: rem(10); 71 | } 72 | &-20 { 73 | height: rem(20); 74 | } 75 | &-40 { 76 | height: rem(40); 77 | } 78 | &-100 { 79 | height: rem(100); 80 | } 81 | &-150 { 82 | height: rem(150); 83 | } 84 | &-200 { 85 | height: rem(200); 86 | } 87 | &-250 { 88 | height: rem(250); 89 | } 90 | &-300 { 91 | height: rem(300); 92 | } 93 | &-400 { 94 | height: rem(400); 95 | } 96 | &-500 { 97 | height: rem(500); 98 | } 99 | &-600 { 100 | height: rem(600); 101 | } 102 | &-700 { 103 | height: rem(700); 104 | } 105 | &-0p { 106 | height: 0%; 107 | } 108 | &-10p { 109 | height: 10%; 110 | } 111 | &-20p { 112 | height: 20%; 113 | } 114 | &-25p { 115 | height: 25%; 116 | } 117 | &-33p { 118 | height: 33%; 119 | } 120 | &-50p { 121 | width: 50%; 122 | } 123 | &-66p { 124 | height: 66%; 125 | } 126 | &-75p { 127 | height: 75%; 128 | } 129 | &-80p { 130 | height: 80%; 131 | } 132 | &-90p { 133 | height: 80%; 134 | } 135 | &-100p { 136 | height: 100%; 137 | } 138 | } 139 | 140 | // quick width 141 | .width { 142 | &-10 { 143 | width: rem(10); 144 | } 145 | &-20 { 146 | width: rem(20); 147 | } 148 | &-40 { 149 | width: rem(40); 150 | } 151 | &-50 { 152 | width: rem(50); 153 | } 154 | &-100 { 155 | width: rem(100); 156 | } 157 | &-150 { 158 | width: rem(150); 159 | } 160 | &-200 { 161 | width: rem(200); 162 | } 163 | &-250 { 164 | width: rem(250); 165 | } 166 | &-300 { 167 | width: rem(300); 168 | } 169 | &-350 { 170 | width: rem(350); 171 | } 172 | &-400 { 173 | width: rem(400); 174 | } 175 | &-500 { 176 | width: rem(500); 177 | } 178 | &-600 { 179 | width: rem(600); 180 | } 181 | &-700 { 182 | width: rem(700); 183 | } 184 | &-0p { 185 | width: 0%; 186 | } 187 | &-10p { 188 | width: 10%; 189 | } 190 | &-20p { 191 | width: 20%; 192 | } 193 | &-25p { 194 | width: 25%; 195 | } 196 | &-33p { 197 | width: 33%; 198 | } 199 | &-50p { 200 | width: 50%; 201 | } 202 | &-66p { 203 | width: 66%; 204 | } 205 | &-75p { 206 | width: 75%; 207 | } 208 | &-80p { 209 | width: 80%; 210 | } 211 | &-90p { 212 | width: 80%; 213 | } 214 | &-100p { 215 | width: 100%; 216 | } 217 | } 218 | 219 | .line-height-1 { 220 | line-height: 1; 221 | } 222 | 223 | .border-5 { 224 | border-width: rem(5) !important; 225 | } 226 | -------------------------------------------------------------------------------- /src/styles/mixins.scss: -------------------------------------------------------------------------------- 1 | // Colors 2 | $white: #fff; 3 | $black: #141322; 4 | $brown: var(--kit-color-primary); 5 | $blue: #4b7cf3; 6 | $blue-light: #42baf9; 7 | $blue-dark: #2c60e4; 8 | $gray-1: #f2f4f8; 9 | $gray-2: #e4e9f0; 10 | $gray-3: #d9dee9; 11 | $gray-4: #c8c4db; 12 | $gray-5: #383838; 13 | $gray-6: #595c97; 14 | $yellow: #ff0; 15 | $orange: #faad15; 16 | $red: #f5222e; 17 | $pink: #fd3995; 18 | $purple: #652eff; 19 | $green: #41b883; 20 | 21 | $text: $gray-6; 22 | $border: $gray-2; 23 | 24 | // Accent colors 25 | $default: $gray-4; 26 | $primary: $brown; 27 | $primary-dark: #8e3d08; 28 | $primary-light: #d87433; 29 | $secondary: $gray-5; 30 | $success: $green; 31 | $info: $blue-light; 32 | $warning: $orange; 33 | $danger: $red; 34 | $light: $gray-1; 35 | $dark: $black; 36 | 37 | // dark theme 38 | $dark-gray-1: #aeaee0; 39 | $dark-gray-2: #7575a3; 40 | $dark-gray-3: #4f4f7a; 41 | $dark-gray-4: #232135; 42 | $dark-gray-5: #141322; 43 | $dark-gray-6: #0c0c1b; 44 | 45 | // Font Family 46 | $base-font-family: var(--kit-font-family); 47 | 48 | // Font Size 49 | $base-font-size: 15 !default; 50 | 51 | // Shadows 52 | $shadow: 0 0 40px -10px rgba($black, 0.2); 53 | $shadow-2: 0 4px 38px 0 rgba($black, 0.11), 0 0 21px 0 rgba($black, 0.05); 54 | $shadow-3: 0 0 100px -30px rgba(57, 55, 73, 0.3); 55 | $shadow-4: 0 4px 10px 0 rgba($black, 0.03), 0 0 10px 0 rgba($black, 0.02); 56 | $shadow-5: 0 0 40px -10px rgba($black, 0.4); 57 | $shadow-ant: 0 10px 35px -5px rgba(0, 0, 0, 0.15); 58 | 59 | // Convert value of rem() sass mixin function 60 | @function rem($px, $base: $base-font-size) { 61 | @return #{floor(($px/$base) * 100) / 100}rem; // to REMs 62 | // @return #{$px}px; // to PX's 63 | } 64 | 65 | // Transitions 66 | @mixin transition-bg() { 67 | transition: background 0.2s ease-in-out; 68 | } 69 | @mixin transition-color() { 70 | transition: color 0.2s ease-in-out; 71 | } 72 | @mixin transition-fast() { 73 | transition: all 0.05s ease-in-out; 74 | } 75 | @mixin transition-middle() { 76 | transition: all 0.1s ease-in-out; 77 | } 78 | @mixin transition-slow() { 79 | transition: all 0.2s ease-in-out; 80 | } 81 | 82 | // Responsive utils 83 | $xxl-min-width: 1600px; 84 | $xxl-max-width: 1599px; 85 | $xl-min-width: 1200px; 86 | $xl-max-width: 1199px; 87 | $lg-min-width: 992px; 88 | $lg-max-width: 991px; 89 | $md-min-width: 768px; 90 | $md-max-width: 767px; 91 | $sm-min-width: 576px; 92 | $sm-max-width: 575px; 93 | -------------------------------------------------------------------------------- /src/utilities/cumulativeOffset.js: -------------------------------------------------------------------------------- 1 | export const cumulativeOffset = (element) => { 2 | let top = 0; 3 | let left = 0; 4 | 5 | do { 6 | top += element.offsetTop || 0; 7 | left += element.offsetLeft || 0; 8 | element = element.offsetParent; 9 | 10 | } while (element); 11 | 12 | 13 | return { 14 | top, 15 | left 16 | }; 17 | }; 18 | --------------------------------------------------------------------------------