├── .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 |
2 |
3 |
4 |
5 |
6 |
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 |
2 |
3 |
{{ msg }}
4 |
5 | For a guide and recipes on how to configure / customize this project,
6 | check out the
7 | vue-cli documentation.
8 |
9 |
Installed CLI Plugins
10 |
28 |
Essential Links
29 |
36 |
Ecosystem
37 |
52 |
53 |
54 |
55 |
63 |
64 |
65 |
81 |
--------------------------------------------------------------------------------
/src/components/core/Footer.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
14 |
15 |
17 |
--------------------------------------------------------------------------------
/src/components/core/Header.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Please verify your email address. To resent confirmation link click
7 | here
8 |
9 |
10 |
11 |
12 | TOOLTIME
13 |
14 |
15 | Products
16 |
17 |
18 |
19 |
20 | Location
21 |
22 |
23 |
24 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | Location Details
38 |
39 |
40 | {{ user.Servicearea.Location.name }}
41 |
42 |
43 |
44 | {{ user.Servicearea.Location.description }}
45 |
46 |
47 |
48 | {{ user.Servicearea.Location.zip }}, {{ user.Servicearea.Location.city }},
49 | {{ user.Servicearea.Location.state }}
50 |
51 |
52 |
53 | {{ user.Servicearea.Location.phone }}
54 |
55 |
56 |
57 | {{ user.Servicearea.Location.hours }}
58 |
59 |
60 |
61 |
62 |
63 |
64 |
70 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | Close
89 |
90 |
91 |
92 |
93 |
94 | Billing Plan
95 |
96 |
97 | Cart ({{ totalCartItems }})
98 |
99 |
100 |
101 | Orders({{ orders.length }})
102 |
103 |
104 |
105 |
106 | {{ user.name }}
107 |
108 | Sign Out
109 | Profile
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
149 |
150 |
169 |
--------------------------------------------------------------------------------
/src/components/core/LayoutMode.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
18 |
19 |
50 |
--------------------------------------------------------------------------------
/src/components/core/PageNotFound.vue:
--------------------------------------------------------------------------------
1 |
2 | Page not found
3 |
4 |
5 |
10 |
11 |
13 |
--------------------------------------------------------------------------------
/src/components/filters/PriceFilter.vue:
--------------------------------------------------------------------------------
1 |
2 |
35 |
36 |
37 |
76 |
77 |
132 |
--------------------------------------------------------------------------------
/src/components/order/OrderDetail.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Select Order to see the details
5 |
6 |
7 |
8 | {{ orderTitle }}
9 |
10 | {{ order.email }}
11 |
12 |
13 | {{ order.phone }}
14 |
15 |
16 | {{ order.address }},{{
17 | order.city
18 | }},
19 | {{ order.state }}
20 |
21 |
22 |
23 | {{ pickDate(order.pickupDate) }}
24 |
25 |
26 | Agency
27 |
28 |
33 | {{ location.name }}
34 |
35 |
36 |
37 |
38 | Notes
39 |
40 |
41 |
42 |
43 | {{ row.index + 1 }}
44 |
45 |
46 |
47 |
48 |
49 | {{ row.value }}
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
83 | {{ location.name }}
84 |
85 |
86 |
87 |
88 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 | {{ row.index + 1 }}
101 |
102 |
103 |
104 |
105 |
106 | {{ row.value }}
107 |
108 |
109 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
158 |
159 |
160 |
161 |
355 |
356 |
--------------------------------------------------------------------------------
/src/components/order/OrderList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Order List
5 |
6 |
7 |
22 |
23 |
24 |
25 |
26 |
27 |
65 |
84 |
--------------------------------------------------------------------------------
/src/components/pagination/Pagination.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
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 |
2 |
3 |
4 |
![]()
5 |
6 |
7 |
8 | {{ product.name }}
9 |
10 |
11 |
12 |
17 | VIEW PRODUCT
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
58 |
59 |
120 |
--------------------------------------------------------------------------------
/src/components/products/ProductDetail.vue:
--------------------------------------------------------------------------------
1 |
2 |
62 |
63 |
64 |
111 |
112 |
114 |
--------------------------------------------------------------------------------
/src/components/products/ProductList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | Newest
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | {{ topLabel }} {{ searchProducts.length }} Items
93 |
94 |
95 |
96 |
101 |
102 |
103 |
104 |
105 |
228 |
229 |
280 |
--------------------------------------------------------------------------------
/src/components/products/ProductSlider.vue:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
18 |
41 |
42 |
69 |
--------------------------------------------------------------------------------
/src/components/products/ProductSliderDots.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
12 |
13 |
32 |
33 |
67 |
--------------------------------------------------------------------------------
/src/components/shopping-cart/ShoppingCartContainer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Shopping cart
7 |
8 |
9 |
10 |
11 |
16 | There is no item in cart
17 |
18 |
19 |
20 |
27 |
28 |
29 | Total Items: {{ totalCartItems }}
30 |
31 |
32 |
33 |
34 | Reservation Details
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | {{ user.Servicearea.Location.name }}, {{ user.Servicearea.Location.zip }},
63 | {{ user.Servicearea.Location.city }}, {{ user.Servicearea.Location.state }},
64 | {{ user.Servicearea.Location.phone }}
65 |
66 |
67 |
68 |
73 |
79 |
80 |
85 |
91 |
92 |
93 |
94 |
95 |
96 | OK
97 |
98 | Cancel
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
188 |
189 |
191 |
--------------------------------------------------------------------------------
/src/components/shopping-cart/ShoppingCartItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
![prewiew]()
10 |
11 |
12 |
13 | {{ product.name | shortenTitle }}
14 |
15 |
16 | {{ product.description }}
17 |
18 |
19 |
22 |
23 |
24 | x
25 |
26 |
27 |
44 |
45 |
48 |
49 |
50 |
51 |
52 |
53 |
86 |
87 |
170 |
--------------------------------------------------------------------------------
/src/components/slider/Slider.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
21 |
22 |
--------------------------------------------------------------------------------
/src/layouts/main/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
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 |
2 |
3 |
4 |
5 |
6 |
7 | Site is down temporarily for maintenance.
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
28 |
29 |
31 |
--------------------------------------------------------------------------------
/src/pages/BillingPlan.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
23 |
24 | {{ plan.nickname }}
25 |
26 |
27 | $ /{{
28 | plan.recurring.interval
29 | }}
30 |
31 |
32 | {{ plan.product.description }}
33 |
34 |
35 | Subscribe
36 |
37 |
38 |
39 |
40 |
41 | Cancel
43 |
44 |
45 |
46 | Subscribe
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
211 |
212 |
233 |
--------------------------------------------------------------------------------
/src/pages/Dashboard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Featured Products
8 |
17 |
18 | More Products
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
57 |
58 |
60 |
--------------------------------------------------------------------------------
/src/pages/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
31 |
32 |
34 |
--------------------------------------------------------------------------------
/src/pages/OrderPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | No Data
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/pages/ProductDetailPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
41 |
42 |
44 |
--------------------------------------------------------------------------------
/src/pages/ProfilePage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Edit Profile
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | Edit
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 | Submit
77 |
78 |
79 | Cancel
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/src/pages/ShoppingCartPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
13 |
15 |
--------------------------------------------------------------------------------
/src/pages/auth/404.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | PAGE NOT FOUND
5 | GO TO HOME
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/pages/auth/ConfirmPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
Account Confirmed!
7 |
8 | Please Login
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
27 |
28 |
30 |
--------------------------------------------------------------------------------
/src/pages/auth/ImgDiv.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
13 |
--------------------------------------------------------------------------------
/src/pages/auth/LoginPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |

7 |
8 |
9 |
10 |
40 |
41 |
42 | Don't have account ?
43 | Sign Up
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
85 |
86 |
--------------------------------------------------------------------------------
/src/pages/auth/RegisterPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
21 |
22 |
23 |
24 | service area code
25 | email and password
26 | personal detail
27 | please select your plan
28 |
29 |
30 |
36 |
42 |
47 |
48 |
49 |
50 |
60 | {{ getLocation }}
61 |
62 |
63 |
64 |
65 |
72 |
73 |
74 |
81 |
82 |
87 |
94 |
95 |
96 |
97 |
98 |
105 |
106 |
107 |
114 |
115 |
116 |
123 |
124 |
125 |
132 |
133 |
134 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
162 |
163 | {{ plan.nickname }}
164 |
165 |
166 | $ /{{
167 | plan.recurring.interval
168 | }}
169 |
170 |
171 | {{ plan.product.description }}
172 |
173 |
174 | Subscribe
177 |
178 |
179 |
180 |
181 |
182 | Cancel
184 |
189 |
190 |
191 | Subscribe
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 | Do you already have account ?
210 | Sign In
211 |
212 |
213 |
214 |
215 |
216 | Back
217 |
218 |
219 | Next
220 |
221 |
222 |
223 | Back
224 |
225 | Next
226 |
227 |
228 |
229 | Back
230 | Submit
231 |
232 |
233 | Back
234 |
235 | Next
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
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 |
--------------------------------------------------------------------------------