├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── License.txt
├── babel.config.js
├── buymecoffee.png
├── capacitor.config.json
├── cypress.json
├── design.png
├── ionic.config.json
├── ionic.starter.json
├── jest.config.js
├── package-lock.json
├── package.json
├── public
├── _redirects
├── assets
│ ├── icon
│ │ ├── favicon.png
│ │ └── favicon.svg
│ ├── img
│ │ ├── logo.png
│ │ ├── logo1.png
│ │ ├── logo2.png
│ │ ├── no-events.png
│ │ ├── screenshot1.png
│ │ └── screenshot2.png
│ └── shapes.svg
├── google60dcc85674ba26b1.html
├── index.html
├── manifest.json
└── robots.txt
├── readme.md
├── src
├── App.vue
├── components
│ ├── ConfList.vue
│ ├── EditEventModal.vue
│ ├── Fab.vue
│ ├── Header.vue
│ ├── LoginModal.vue
│ ├── NewEventModal.vue
│ ├── NoEvents.vue
│ ├── SearchFilters.vue
│ ├── SkeletonText.vue
│ ├── SpeakerList.vue
│ ├── Stats.vue
│ ├── Subscription.vue
│ ├── Tabs.vue
│ ├── UserSignUpModal.vue
│ └── VenueList.vue
├── firebase.js
├── main.js
├── router
│ └── index.js
├── shims-vue.d.ts
├── store
│ ├── index.js
│ └── modules
│ │ ├── auth.js
│ │ ├── events.js
│ │ ├── speakers.js
│ │ ├── userProfile.js
│ │ └── venues.js
├── theme
│ ├── media-queries.scss
│ └── variables.scss
└── views
│ ├── CreateEvent.vue
│ ├── CreateVenue.vue
│ ├── Dashboard.vue
│ ├── Home.vue
│ ├── Login.vue
│ ├── Profile.vue
│ ├── Register.vue
│ ├── Speakers.vue
│ └── Venues.vue
├── tests
├── e2e
│ ├── .eslintrc.js
│ ├── plugins
│ │ └── index.js
│ ├── specs
│ │ └── test.js
│ └── support
│ │ ├── commands.js
│ │ └── index.js
└── unit
│ └── example.spec.ts
└── tsconfig.json
/.eslintignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dlodeprojuicer/ionic-vue-mobile-template-05/5f50083e3cc7a4e529e8d1a6676a8ce6ea13715a/.eslintignore
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true
5 | },
6 | 'extends': [
7 | 'plugin:vue/vue3-essential',
8 | 'eslint:recommended',
9 | ],
10 | parserOptions: {
11 | ecmaVersion: 2020
12 | },
13 | rules: {
14 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
15 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
16 | 'vue/no-deprecated-slot-attribute': 'off',
17 | 'vue/custom-event-name-casing': 'off'
18 | },
19 | overrides: [
20 | {
21 | files: [
22 | '**/__tests__/*.{j,t}s?(x)',
23 | '**/tests/unit/**/*.spec.{j,t}s?(x)'
24 | ],
25 | env: {
26 | jest: true
27 | }
28 | }
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.DS_Store
3 | node_modules
4 | android
5 | ios
6 | dist
7 | .gradle
8 |
9 | # local env files
10 | .env.local
11 | .env.*.local
12 |
13 | # Log files
14 | npm-debug.log*
15 | yarn-debug.log*
16 | yarn-error.log*
17 |
18 | # Editor directories and files
19 | .idea
20 | .vscode
21 | *.suo
22 | *.ntvs*
23 | *.njsproj
24 | *.sln
25 | *.sw?
26 |
27 | api_key
28 | config.json
29 | build.json
30 |
31 |
32 | # Cordova
33 | /src-cordova/platforms
34 | /src-cordova/plugins
35 | /public/cordova.js
36 |
--------------------------------------------------------------------------------
/License.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 SIMO MAFUXWANA
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/buymecoffee.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dlodeprojuicer/ionic-vue-mobile-template-05/5f50083e3cc7a4e529e8d1a6676a8ce6ea13715a/buymecoffee.png
--------------------------------------------------------------------------------
/capacitor.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "appId": "io.ionic.starter",
3 | "appName": "my-app",
4 | "bundledWebRuntime": false,
5 | "npmClient": "npm",
6 | "webDir": "dist",
7 | "plugins": {
8 | "SplashScreen": {
9 | "launchShowDuration": 0
10 | }
11 | },
12 | "cordova": {}
13 | }
14 |
--------------------------------------------------------------------------------
/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "pluginsFile": "tests/e2e/plugins/index.js"
3 | }
4 |
--------------------------------------------------------------------------------
/design.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dlodeprojuicer/ionic-vue-mobile-template-05/5f50083e3cc7a4e529e8d1a6676a8ce6ea13715a/design.png
--------------------------------------------------------------------------------
/ionic.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "my-app",
3 | "integrations": {
4 | "capacitor": {}
5 | },
6 | "type": "vue"
7 | }
8 |
--------------------------------------------------------------------------------
/ionic.starter.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Tabs Starter",
3 | "baseref": "vue-starters",
4 | "tarignore": [
5 | "node_modules",
6 | "package-lock.json",
7 | "www"
8 | ],
9 | "scripts": {
10 | "test": "npm run build"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: '@vue/cli-plugin-unit-jest/presets/typescript-and-babel',
3 | transform: {
4 | '^.+\\.vue$': 'vue-jest'
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ionic-vue-mobile-template-01",
3 | "description": "Hybrid app template built with vue, ionic and capacitor",
4 | "author": "Simo Mafuxwana - @dlodeprojuicer",
5 | "version": "0.2.0",
6 | "private": true,
7 | "scripts": {
8 | "serve": "vue-cli-service serve",
9 | "build": "vue-cli-service build",
10 | "test:unit": "vue-cli-service test:unit",
11 | "test:e2e": "vue-cli-service test:e2e",
12 | "lint": "vue-cli-service lint"
13 | },
14 | "dependencies": {
15 | "@capacitor/core": "^2.4.1",
16 | "@capacitor/ios": "^2.4.1",
17 | "@ionic/vue": "^5.4.1",
18 | "@ionic/vue-router": "^5.4.1",
19 | "@johmun/vue-tags-input": "^2.1.0",
20 | "@mailchimp/mailchimp_marketing": "^3.0.22",
21 | "chart.js": "^2.9.3",
22 | "core-js": "^3.6.5",
23 | "firebase": "^7.22.0",
24 | "ionic-vue-form": "^1.2.2",
25 | "moment": "^2.29.0",
26 | "node-sass": "^4.14.1",
27 | "sass-loader": "^10.0.2",
28 | "vee-validate": "^4.0.0-beta.1",
29 | "vue": "3.0.0",
30 | "vue-chartjs": "^3.5.1",
31 | "vue-router": "^4.0.0-beta.9",
32 | "vuex": "^4.0.0-beta.4"
33 | },
34 | "devDependencies": {
35 | "@capacitor/cli": "^2.4.1",
36 | "@vue/cli-plugin-babel": "^4.5.4",
37 | "@vue/cli-plugin-eslint": "^4.5.4",
38 | "@vue/cli-plugin-router": "^4.5.4",
39 | "@vue/cli-plugin-unit-jest": "^4.5.4",
40 | "@vue/cli-service": "^4.5.4",
41 | "@vue/compiler-sfc": "^3.0.0-rc.10",
42 | "@vue/eslint-config-typescript": "^5.1.0",
43 | "@vue/test-utils": "^2.0.0-beta.3",
44 | "eslint": "^6.8.0",
45 | "eslint-plugin-vue": "^7.0.0-beta.3",
46 | "vue-jest": "^5.0.0-alpha.3"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/public/_redirects:
--------------------------------------------------------------------------------
1 | /* /index.html 200
2 | https://techconf-db.netlify.app https://techconf-db.com
--------------------------------------------------------------------------------
/public/assets/icon/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dlodeprojuicer/ionic-vue-mobile-template-05/5f50083e3cc7a4e529e8d1a6676a8ce6ea13715a/public/assets/icon/favicon.png
--------------------------------------------------------------------------------
/public/assets/icon/favicon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/assets/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dlodeprojuicer/ionic-vue-mobile-template-05/5f50083e3cc7a4e529e8d1a6676a8ce6ea13715a/public/assets/img/logo.png
--------------------------------------------------------------------------------
/public/assets/img/logo1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dlodeprojuicer/ionic-vue-mobile-template-05/5f50083e3cc7a4e529e8d1a6676a8ce6ea13715a/public/assets/img/logo1.png
--------------------------------------------------------------------------------
/public/assets/img/logo2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dlodeprojuicer/ionic-vue-mobile-template-05/5f50083e3cc7a4e529e8d1a6676a8ce6ea13715a/public/assets/img/logo2.png
--------------------------------------------------------------------------------
/public/assets/img/no-events.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dlodeprojuicer/ionic-vue-mobile-template-05/5f50083e3cc7a4e529e8d1a6676a8ce6ea13715a/public/assets/img/no-events.png
--------------------------------------------------------------------------------
/public/assets/img/screenshot1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dlodeprojuicer/ionic-vue-mobile-template-05/5f50083e3cc7a4e529e8d1a6676a8ce6ea13715a/public/assets/img/screenshot1.png
--------------------------------------------------------------------------------
/public/assets/img/screenshot2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dlodeprojuicer/ionic-vue-mobile-template-05/5f50083e3cc7a4e529e8d1a6676a8ce6ea13715a/public/assets/img/screenshot2.png
--------------------------------------------------------------------------------
/public/assets/shapes.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/public/google60dcc85674ba26b1.html:
--------------------------------------------------------------------------------
1 | google-site-verification: google60dcc85674ba26b1.html
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | TechConf-db | A concise list of tech conferences in South Africa
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
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 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "TechConf-db",
3 | "name": "TechConf-db",
4 | "icons": [
5 | {
6 | "src": "assets/icon/favicon.png",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "assets/icon/icon.png",
12 | "type": "image/png",
13 | "sizes": "512x512",
14 | "purpose": "maskable"
15 | }
16 | ],
17 | "start_url": ".",
18 | "display": "standalone",
19 | "theme_color": "#ffffff",
20 | "background_color": "#ffffff"
21 | }
22 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | ## Ionic Vue Mobile Template 05
2 | [](https://app.netlify.com/sites/ionic-vue-mobile-template-05/deploys)
3 |
4 | Hybrid mobile template built with Ionic Vue using capacitor for native builds.
5 |
6 | Template is based on [TechConf-db.com](https://techconf-db.com), a side project of mine. Unlike other templetes, this one is not meant to be a mobile app, atleast not yet, but it is responsive and can be used as a mobile app.
7 |
8 | [Demo](https://ionic-vue-mobile-template-05.netlify.app)
9 |
10 | ## Features
11 | - Vuex
12 | - Firebase
13 | - Mailchimp
14 | - Search Filter
15 | - Responsive
16 |
17 | ## Configs
18 | For this template, I created static data. Techconf-db is using a Firebase backend. Replace Firebase config in `src/firebase.js` or remove Firebase completely to use your prefer your a different backend.
19 |
20 | Also, make sure you replace Mailchimp config URL on line 123 of `src/components/Subscription.vue`
21 |
22 | ## Project setup
23 | ```
24 | npm install
25 | ```
26 |
27 | ### Run on the browser - development
28 | ```
29 | npm run serve
30 | ```
31 |
32 | ## Design
33 | 
34 |
35 | ## Native
36 |
37 | Using [Capacitor](https://capacitorjs.com/docs/getting-started) for native builds
38 |
39 | ## Prepare native builds
40 |
41 | ### iOS testing and distribution
42 | 1. Download the latest Xcode
43 | 2. `npm run build`
44 | 3. `npx cap add ios`
45 | 3. `npx cap copy`
46 | 4. `npx cap open ios` Xcode takes a few seconds to index the files; keep an eye at the top of Xcode's window for progress.
47 |
48 | [Not compulsory] For sanity check click on the play button in the top left. This will prepare and run the app in a simulator, if all goes well you should be able to run the app and click around. If not, create an issue 🤷 and I will have a look.
49 |
50 | ### Android testing and distribution
51 | 1. Download the latest Android Studio
52 | 2. `npm run build`
53 | 3. `npx cap add android`
54 | 3. `npx cap copy`
55 | 4. `npx cap open android` Android Studio takes a few seconds to index the files, keep an eye at the bottom of Android Studio for progress.
56 | 5. Testing - When indexing is complete, look for a green play button. Click the play button and it will launch the app in an emulator ([See here to setup Emulator](https://developer.android.com/studio/run/managing-avds)) or on the phone, if a phone is connected via USB.
57 |
58 | ## Official Docs
59 | - [Getting started](https://ionicframework.com/vue)
60 |
61 | ## Resources
62 | - [Newsletter](https://mailchi.mp/b9133e120ccf/sqan8ggx22) - Signup to my Ionic Vue newsletter to get templates and other Ionic Vue updates in your inbox!
63 | - [YouTube Channel](https://www.youtube.com/channel/UC5jZ6srZuLwt3O3ZtuM1Dsg) - Subscribe to my YouTube channel.
64 | - [Ionic Vue Tempalates](https://tinyurl.com/y2gl39dk) - Free Ionic Vue Templates.
65 | - [Ionic Vue VSCode Snippets](https://marketplace.visualstudio.com/items?itemName=dlodeprojuicer.ionicvuesnippets) - This extension adds ionic-vue snippets. Quickly add ionic-vue component code by simply typing iv. The iv prefix will show a range of snippets to choose from.
66 |
67 | ## Affiliates
68 | I want to keep doing these templates for free for as long as possible. I have joined a few affiliate programs to help take care of the costs.
69 | - [Pixeltrue](https://www.pixeltrue.com/?via=simo) - High-quality illustrations that will help you build breath-taking websites.
70 | - [Getrewardful](https://www.getrewardful.com/?via=simo) - Create your own affiliate program.
71 |
72 | Alternatively, you can buy me a coffee
73 |
74 | ## Credits
75 | - [manuelroviradesign](https://www.instagram.com/manuelroviradesign/) via [We Love Web Design](https://www.instagram.com/p/CC1GFMrBB6T/) - App design inspiration
76 | - [Tami Maiwashe](https://www.linkedin.com/in/tami-maiwashe-32824a19a/) - Documentation
77 | - [おかきょー](https://twitter.com/31415O_Kyo) - [Japanese doc translation](https://github.com/dlodeprojuicer/ionic-vue-mobile-template-01/blob/master/readme-ja.md)
78 |
79 | ## Contact
80 | - [@dlodeprojuicer](https://twitter.com/dlodeprojuicer) on Twitter
81 |
82 | ## Contact
83 | - [@dlodeprojuicer](https://twitter.com/dlodeprojuicer) on Twitter
84 | - [@IonicSA](https://twitter.com/ionicsa) - S.A ionic user group page
85 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
22 |
23 |
--------------------------------------------------------------------------------
/src/components/ConfList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ item.eventName }}
7 |
8 | {{ item.venue }}, {{ item.town }}
9 |
10 |
11 | {{ `${item.startFormatted} - ${item.endFormatted}` }}
12 |
13 |
14 | New dates TBA
15 |
16 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | Website
28 |
29 |
30 |
31 | Add to Calendar
32 |
33 |
36 |
37 |
38 |
39 |
40 |
41 |
196 |
197 |
216 |
--------------------------------------------------------------------------------
/src/components/EditEventModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
13 |
14 | Name
15 |
16 |
17 |
18 | Website
19 |
20 |
21 |
22 | Venue
23 |
24 |
25 |
26 | Street
27 |
28 |
29 |
30 | Area / Suburb
31 |
32 |
33 |
34 | Town
35 |
36 |
37 |
38 | Province
39 |
40 |
41 |
42 | Cancel
45 | Submit
48 |
49 |
50 |
51 |
52 |
53 |
116 |
117 |
--------------------------------------------------------------------------------
/src/components/Fab.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
18 |
19 |
20 |
21 |
122 |
123 |
--------------------------------------------------------------------------------
/src/components/Header.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Conferences
7 | Venues
8 | Speakers
9 | Profile
10 | Subscribe
11 | Logout
12 |
13 |
14 |
15 | {{ name }}
16 | {{ name2 }}
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | {{ name }}
28 |
29 |
30 |
31 |
32 |
33 |
34 | {{ name }}
35 |
36 |
37 | Conferences
38 |
39 |
40 | Venues
41 |
42 |
43 | Speakers
44 |
45 |
46 | Subscribe
47 |
48 |
49 | Profile
50 |
51 |
52 | Logout
53 |
54 |
55 |
56 |
57 |
58 |
59 |
141 |
142 |
179 |
--------------------------------------------------------------------------------
/src/components/LoginModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
13 |
14 | Email
15 |
16 |
17 |
18 | Password
19 |
20 |
21 |
22 |
23 | {{ endpointError.message }}
24 |
25 |
26 |
27 | Cancel
28 | Login
29 |
30 |
31 |
32 |
33 |
34 |
114 |
115 |
--------------------------------------------------------------------------------
/src/components/NewEventModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
12 |
13 | Name
14 |
15 |
16 |
17 | Website
18 |
19 |
20 |
24 |
25 |
26 |
27 | Venue
28 |
29 |
30 |
31 | Street
32 |
33 |
34 |
35 | Area / Suburb
36 |
37 |
38 |
39 | Town
40 |
41 |
42 |
43 | Province
44 |
45 |
46 | {{ item }}
47 |
48 |
49 |
50 |
51 | Start
52 |
60 |
61 |
62 | End
63 |
71 |
72 |
73 | Cancel
74 | Submit
75 |
76 |
77 |
78 |
79 |
192 |
193 |
--------------------------------------------------------------------------------
/src/components/NoEvents.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Add conference
5 |
6 |
7 |
8 |
44 |
45 |
--------------------------------------------------------------------------------
/src/components/SearchFilters.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | {{ item }}
15 |
16 |
17 |
18 |
19 | {{ item }}
25 |
26 |
27 |
28 |
29 |
30 |
31 |
116 |
--------------------------------------------------------------------------------
/src/components/SkeletonText.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
14 |
19 |
24 |
25 |
26 |
31 |
36 |
41 |
46 |
47 |
48 |
53 |
58 |
63 |
68 |
69 |
70 |
82 |
83 |
88 |
--------------------------------------------------------------------------------
/src/components/SpeakerList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
9 |
10 | {{ item.name }} {{ item.lastname }}
11 |
12 | {{ item.position }}
13 | Highlights
14 |
15 |
16 | {{ itm.name }} ({{ itm.year }})
17 |
18 |
19 | Contact: {{ item.contact }}
20 |
21 |
22 |
23 |
24 |
25 |
26 | {{ i.label }}
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
137 |
138 |
186 |
--------------------------------------------------------------------------------
/src/components/Stats.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ data }} events
4 | This month
5 |
6 |
7 |
8 |
44 |
45 |
--------------------------------------------------------------------------------
/src/components/Subscription.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
100 |
101 |
102 |
103 |
153 |
154 |
--------------------------------------------------------------------------------
/src/components/Tabs.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | made with + & by @dlodeprojuicer
6 |
7 |
8 |
9 |
10 |
11 |
27 |
28 |
40 |
--------------------------------------------------------------------------------
/src/components/UserSignUpModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
12 | {{ f.label }}
13 |
19 | {{ f.errMsg }}
20 |
21 |
22 | Province
23 |
24 |
25 | {{ item }}
26 |
27 |
28 |
29 |
30 |
31 | {{ endpointError.message }}
32 |
33 |
34 |
35 | Cancel
36 | Signup
39 |
40 |
41 |
42 |
43 |
193 |
194 |
--------------------------------------------------------------------------------
/src/components/VenueList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
11 |
12 | {{ item.venueName }}
13 | {{ item.area }}
14 |
15 |
16 |
17 | Website
18 |
19 |
20 |
21 |
22 |
23 | Email: {{ item.email }}
24 | Phone: {{ item.phone }}
25 |
26 |
27 |
28 | Size
29 |
30 | Area: {{ item.squareMeter }} m²
31 |
32 | Length: {{ item.length }} m
33 |
34 | Width: {{ item.width }} m
35 |
36 | Height: {{ item.height }} m
37 |
38 |
39 |
40 | Equipment
41 | {{ e }}
42 |
43 |
44 | Other
45 |
46 | Wheelchair Friendly: {{ item.wheelchairFriendly ? 'Yes' : 'No' }}
47 |
48 | WiFi: {{ item.wifi ? 'Yes' : 'No' }}
49 |
50 | Min. Capacity: {{ item.capacityMin }}
51 |
52 | Max. Capacity: {{ item.capacityMax }}
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | Edit
61 |
62 | Delete
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
167 |
168 |
228 |
--------------------------------------------------------------------------------
/src/firebase.js:
--------------------------------------------------------------------------------
1 | // Firebase App (the core Firebase SDK) is always required and
2 | // must be listed before other Firebase SDKs
3 | import * as firebase from "firebase/app";
4 |
5 | // Add the Firebase services that you want to use
6 | import "firebase/auth";
7 | import "firebase/storage";
8 | import "firebase/firestore";
9 | import "firebase/database";
10 | import "firebase/analytics";
11 |
12 |
13 | // replace this config with your own.
14 | const firebaseConfig = {
15 | apiKey: "AIzaSyAzJdnBy6MXtYlpGsmm70FJA-v9aiBPUdw",
16 | authDomain: "techconf-db-template.firebaseapp.com",
17 | databaseURL: "https://techconf-db-template.firebaseio.com",
18 | projectId: "techconf-db-template",
19 | storageBucket: "techconf-db-template.appspot.com",
20 | messagingSenderId: "862672279483",
21 | appId: "1:862672279483:web:f2e02196bafe0a217d969e",
22 | measurementId: "G-ZTFP3035QL"
23 | };
24 | // Initialize Firebase
25 | firebase.initializeApp(firebaseConfig);
26 | firebase.analytics();
27 |
28 | export default firebase;
29 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue';
2 | // import { createStore } from 'vuex';
3 | import App from './App.vue';
4 | import router from './router';
5 | import store from './store';
6 |
7 | import { IonicVue } from '@ionic/vue';
8 |
9 | /* Core CSS required for Ionic components to work properly */
10 | import '@ionic/vue/css/core.css';
11 |
12 | /* Basic CSS for apps built with Ionic */
13 | import '@ionic/vue/css/normalize.css';
14 | import '@ionic/vue/css/structure.css';
15 | import '@ionic/vue/css/typography.css';
16 |
17 | /* Optional CSS utils that can be commented out */
18 | import '@ionic/vue/css/padding.css';
19 | import '@ionic/vue/css/float-elements.css';
20 | import '@ionic/vue/css/text-alignment.css';
21 | import '@ionic/vue/css/text-transformation.css';
22 | import '@ionic/vue/css/flex-utils.css';
23 | import '@ionic/vue/css/display.css';
24 |
25 | /* Theme variables */
26 | import './theme/variables.scss';
27 |
28 | // import auth from "./store/modules/auth";
29 | // const store = createStore({
30 | // modules: {
31 | // auth,
32 | // // events,
33 | // // userProfile,
34 | // },
35 | // state: {
36 | // httpLoader: false,
37 | // },
38 | // getters: {
39 | // httpLoader({ httpLoader }) {
40 | // return httpLoader;
41 | // }
42 | // },
43 | // mutations: {
44 | // },
45 | // actions: {
46 | // }
47 | // });
48 |
49 |
50 | const app = createApp(App)
51 | .use(IonicVue)
52 | .use(router)
53 | .use(store)
54 |
55 | router.beforeEach((to, from, next) => {
56 | store.dispatch("loginStatus");
57 |
58 | if (to.meta.requiresAuth && !store.getters.loginToken) {
59 | next({ name:"conferences" });
60 | } else {
61 | next();
62 | }
63 |
64 | // firebase.auth().onAuthStateChanged(function(user) {
65 | // if (user) {
66 | // // User is signed in.
67 | // } else {
68 | // // No user is signed in.
69 | // }
70 | // });
71 | })
72 | router.isReady().then(() => {
73 | app.mount('#app');
74 | });
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import { createRouter, createWebHistory } from '@ionic/vue-router';
2 | import Tabs from '../components/Tabs.vue'
3 |
4 | const routes = [
5 | // {
6 | // path: '/',
7 | // component: () => import('@/views/Home.vue')
8 | // },
9 | // {
10 | // path: '/profile',
11 | // component: () => import('@/views/Profile.vue')
12 | // },
13 | // {
14 | // path: '/list',
15 | // component: () => import('@/views/List.vue')
16 | // },
17 |
18 | // add /privacy-policy route for google calendar api
19 | // add /terms-of-service route for google calendar api
20 | {
21 | path: '/',
22 | name: "conferences",
23 | component: Tabs,
24 | children: [
25 | {
26 | path: '/',
27 | name: "conferences",
28 | component: () => import('@/views/Home.vue'),
29 | meta: {
30 | requiresAuth: false,
31 | }
32 | },
33 | {
34 | path: '/venues',
35 | name: "venues",
36 | component: () => import('@/views/Venues.vue'),
37 | meta: {
38 | requiresAuth: false,
39 | }
40 | },
41 | {
42 | path: '/speakers',
43 | name: "speakers",
44 | component: () => import('@/views/Speakers.vue'),
45 | meta: {
46 | requiresAuth: false,
47 | }
48 | },
49 |
50 | // Maybe this must be its own top-level route?
51 | {
52 | path: '/login',
53 | component: () => import('@/views/Login.vue'),
54 | meta: {
55 | requiresAuth: false,
56 | }
57 | },
58 |
59 | // Maybe this must be its own top-level route?
60 | {
61 | path: '/register',
62 | component: () => import('@/views/Register.vue'),
63 | meta: {
64 | requiresAuth: false,
65 | }
66 | },
67 |
68 | // Maybe this must be its own top-level route?
69 | {
70 | path: '/create-venue',
71 | component: () => import('@/views/CreateVenue.vue'),
72 | meta: {
73 | requiresAuth: true,
74 | }
75 | },
76 |
77 | // Maybe this must be its own top-level route?
78 | {
79 | path: '/profile',
80 | component: () => import('@/views/Profile.vue'),
81 | meta: {
82 | requiresAuth: true,
83 | }
84 | },
85 |
86 | // Maybe this must be its own top-level route?
87 | {
88 | path: '/create-event',
89 | component: () => import('@/views/CreateEvent.vue'),
90 | meta: {
91 | requiresAuth: true,
92 | }
93 | }
94 | ]
95 | }
96 | ]
97 |
98 | const router = createRouter({
99 | history: createWebHistory(process.env.BASE_URL),
100 | routes
101 | })
102 |
103 | export default router
104 |
--------------------------------------------------------------------------------
/src/shims-vue.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.vue' {
2 | import { defineComponent } from 'vue'
3 | const component: ReturnType
4 | export default component
5 | }
6 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import { createStore } from 'vuex';
2 | import auth from "./modules/auth";
3 | import events from "./modules/events";
4 | import venues from "./modules/venues";
5 | import speakers from "./modules/speakers";
6 | // import userProfile from "./modules/userProfile";
7 | import firebase from "../firebase";
8 |
9 | const store = createStore({
10 | modules: {
11 | auth,
12 | events,
13 | venues,
14 | speakers,
15 | // userProfile
16 | },
17 | state: {
18 | httpLoader: false,
19 | searchString: null,
20 | userProfile: {},
21 | },
22 | getters: {
23 | httpLoader({ httpLoader }) {
24 | return httpLoader;
25 | },
26 | searchString({ searchString }) {
27 | return searchString;
28 | },
29 | userProfile({ userProfile }) {
30 | return userProfile || JSON.parse(localStorage.getItem("tcdbUserProfile"));
31 | },
32 | },
33 | mutations: {
34 | userProfile(state, data) {
35 | state.events = data;
36 | localStorage.setItem("tcdbUserProfile", JSON.stringify(data));
37 | },
38 | // updateSearch(state, data) {
39 | // console.log("M", data);
40 | // state[data.stateObject] = data;
41 | // }
42 | },
43 | actions: {
44 | createUser(context, request) {
45 | return new Promise((resolve, reject) => {
46 | firebase.firestore().collection("users")
47 | .doc(request.uid)
48 | .set({...request})
49 | .then(() => {
50 | resolve();
51 | })
52 | .catch(err => {
53 | reject(err);
54 | });
55 | })
56 | },
57 | getUserProfile(context, request) {
58 | return new Promise((resolve, reject) => {
59 | firebase.firestore().collection("users")
60 | .doc(request)
61 | .get()
62 | .then(doc => {
63 | context.commit("userProfile", doc.data());
64 | resolve(doc.data());
65 | }).catch(error => {
66 | reject(error);
67 | });
68 | })
69 | },
70 | getUsers() {
71 | return new Promise((resolve) => {
72 | firebase.firestore().collection("users")
73 | .get()
74 | .then(({ docs }) => {
75 | resolve(docs.map(a => a.data()));
76 | });
77 | })
78 | },
79 | updateUser(context, request) {
80 | request.updatedBy = context.getters.loginToken;
81 | return new Promise((resolve) => {
82 | firebase.firestore().collection("users")
83 | .doc(request.uid)
84 | .update(request)
85 | .then(() => {
86 | resolve();
87 | });
88 | })
89 | },
90 | refTracker(context, request) {
91 | return new Promise((resolve, reject) => {
92 | firebase.database().ref("refTracker")
93 | .once("value")
94 | .then((snapshot) => {
95 | let tracker = snapshot.val();
96 | context.dispatch("refTrackerUpdate", {[request]: ++tracker[request]})
97 | .then(res => {
98 | resolve("test", res);
99 | })
100 | .catch(err => {
101 | reject(err);
102 | });
103 | }).catch(error => {
104 | reject(error);
105 | });
106 | })
107 | },
108 | refTrackerUpdate(context, request) {
109 | return new Promise((resolve, reject) => {
110 | firebase.database().ref("refTracker").update(request)
111 | .then(data => {
112 | resolve(data);
113 | })
114 | .catch(err => {
115 | reject(err);
116 | })
117 | })
118 | }
119 | }
120 | });
121 |
122 | export default store;
123 |
--------------------------------------------------------------------------------
/src/store/modules/auth.js:
--------------------------------------------------------------------------------
1 | import firebase from "./../../firebase";
2 |
3 | const addToSpeakers = (speaker) => {
4 | let speakerObj = {
5 | name: speaker.name,
6 | lastname: speaker.lastname,
7 | position: speaker.role,
8 | contact: speaker.contactInfoConsent ? speaker.contact : "",
9 | image: "",
10 | social: [
11 | {
12 | link: speaker.twitter,
13 | label: "Twitter"
14 | },
15 | {
16 | link: speaker.linkedin,
17 | label: "LinkedIn"
18 | },
19 | {
20 | link: speaker.website,
21 | label: "Website"
22 | }
23 | ],
24 | highlights: [
25 | {
26 | name: speaker.highlight1name,
27 | year: speaker.highlight1year
28 | },
29 | {
30 | name: speaker.highlight2name,
31 | year: speaker.highlight2year
32 | },
33 | {
34 | name: speaker.highlight3name,
35 | year: speaker.highlight3year
36 | },
37 | ],
38 | };
39 | return speakerObj;
40 | };
41 |
42 | const state = {
43 | loginToken: false,
44 | }
45 |
46 | const getters = {
47 | loginToken({ loginToken }) {
48 | return loginToken || localStorage.getItem("tcdbLoginToken");
49 | },
50 | authError(state) {
51 | return state.authError;
52 | },
53 | }
54 |
55 | const mutations = {
56 | loginToken(state, token) {
57 | if(token) {
58 | state.loginToken = token;
59 | localStorage.setItem("tcdbLoginToken", token);
60 | } else {
61 | state.loginToken = null;
62 | localStorage.clear();
63 | }
64 | },
65 | authError(state, data) {
66 | state.authError = data;
67 | }
68 | }
69 |
70 | const actions = {
71 | login(context, request) {
72 | return new Promise((resolve, reject) => {
73 | firebase.auth().signInWithEmailAndPassword(request.email, request.password)
74 | .then(({ user }) => {
75 | context.dispatch("getUserProfile", user.uid).then(() => {
76 | context.commit("loginToken", user.uid);
77 | resolve(user.uid);
78 | });
79 | }).catch(function(error) {
80 | reject(error)
81 | });
82 | })
83 | },
84 | signUp(context, request) {
85 | return new Promise((resolve, reject) => {
86 | firebase.auth().createUserWithEmailAndPassword(request.email, request.password)
87 | .then(({ user }) => {
88 | context.commit("loginToken", user.uid);
89 | request.uid = user.uid;
90 | request.verified = true;
91 | delete request.password;
92 | context.dispatch("createUser", request).then(() => {
93 | if (request.isSpeaker) {
94 | let obj = {
95 | ...request.speaker,
96 | name: request.name,
97 | lastname: request.lastname,
98 | contact: request.email
99 | }
100 | context.dispatch("createSpeaker", addToSpeakers(obj)).then(() => {
101 | resolve(user.uid);
102 | }).catch(e => {
103 | reject(e);
104 | });
105 | } else {
106 | resolve(user.uid);
107 | }
108 | }).catch(err => {
109 | reject(err);
110 | });
111 | }).catch(error => {
112 | reject(error)
113 | });
114 | })
115 | },
116 | logout(context) {
117 | return new Promise((resolve, reject) => {
118 | firebase.auth().signOut()
119 | .then(() => {
120 | context.commit("loginToken", false);
121 | resolve();
122 | }).catch(error => {
123 | reject(error)
124 | });
125 | })
126 | },
127 | loginStatus(context) {
128 | return new Promise(() => {
129 | firebase.auth().onAuthStateChanged(user => {
130 | if (user) {
131 | // User is signed in.
132 | context.commit("loginToken", user.uid);
133 | } else {
134 | context.commit("loginToken", false);
135 | }
136 | });
137 | });
138 | }
139 | // subscribe(context, request) {
140 | // return new Promise((resolve, reject) => {
141 | // mailchimp.post(`lists/c72f027b89/members`, {
142 | // ...request,
143 | // status: "subscribed"
144 | // })
145 | // .then(() => resolve())
146 | // .catch(err => reject(err))
147 | // })
148 | // },
149 | // async mailchimpTest() {
150 | // const response = await mailchimp.ping.get();
151 | // console.log(response);
152 | // }
153 | }
154 |
155 | export default { state, getters, mutations, actions }
156 |
--------------------------------------------------------------------------------
/src/store/modules/events.js:
--------------------------------------------------------------------------------
1 | import firebase from "../../firebase";
2 | import moment from "moment";
3 |
4 | const state = {
5 | events: [
6 | {
7 | "id": "oeHnqwOO6hvJdJxhmUgO",
8 | "venue": "Virtual Conference",
9 | "start": "2020-11-10T09:30:40.637+02:00",
10 | "eventName": ".NET Conf",
11 | "createdBy": "JE3Bh37hpOch095fAEAcNbwrQWI3",
12 | "verified": true,
13 | "price": "",
14 | "contactPerson": "",
15 | "website": "https://www.dotnetconf.net/",
16 | "province": "",
17 | "end": "2020-11-12T09:30:40.640+02:00",
18 | "startFormatted": "10/11/2020",
19 | "endFormatted": "12/11/2020"
20 | },
21 | {
22 | "id": "bWxcaUQHMSdQs6UORnoB",
23 | "venue": "Pearson Institute of Higher Learning",
24 | "eventName": "0111 CTO Conf",
25 | "address": {},
26 | "province": "Gauteng",
27 | "town": "Johannesburg",
28 | "verified": true,
29 | "price": "",
30 | "createdBy": "JE3Bh37hpOch095fAEAcNbwrQWI3",
31 | "area": "Midrand",
32 | "website": "https://www.0111conf.co.za/johannesburg",
33 | "contactPerson": "",
34 | "startFormatted": null,
35 | "endFormatted": null
36 | },
37 | {
38 | "id": "cPspaROtoQZdzLrdAHx1",
39 | "eventName": "0111 CTO Conf",
40 | "website": "https://www.0111conf.co.za/cape-town",
41 | "venue": "University of Stellenbosch",
42 | "contactPerson": "",
43 | "createdBy": "JE3Bh37hpOch095fAEAcNbwrQWI3",
44 | "province": "Western Cape",
45 | "town": "Stellenbosch",
46 | "verified": true,
47 | "price": "",
48 | "startFormatted": null,
49 | "endFormatted": null
50 | }
51 | ],
52 | filteredEvents: [],
53 | updateEventSearchObject: {},
54 | monthEventCount: 0,
55 | }
56 |
57 | const getters = {
58 | events({ events = [] }) {
59 | return events || JSON.parse(localStorage.getItem("tcdbEvents"));
60 | },
61 | filteredEvents({ events = [], updateEventSearchObject }) {
62 | if (!updateEventSearchObject?.field || updateEventSearchObject?.field === "") {
63 | return events;
64 | } else {
65 | return events.filter(event => event[updateEventSearchObject.field].toLowerCase().includes(updateEventSearchObject.value.toLowerCase()));
66 | }
67 | },
68 | userEvents({ userEvents }) {
69 | return userEvents || JSON.parse(localStorage.getItem("tcdbUserEvents"));
70 | },
71 | monthEventCount({ events = [] }) {
72 | const date = new Date();
73 | const month = date.getMonth();
74 | const monthPlus = month + 1;
75 | return events.filter(event => {
76 | if(event.start) {
77 | return event.start.split("/")[1] == monthPlus
78 | }
79 | });
80 | },
81 | }
82 |
83 | const mutations = {
84 | events(state, data) {
85 | state.events = data;
86 | localStorage.setItem("tcdbEvents", JSON.stringify(data));
87 | },
88 | userEvents(state, data) {
89 | state.events = data;
90 | localStorage.setItem("tcdbUserEvents", JSON.stringify(data));
91 | },
92 | updateSearch(state, data) {
93 | state[data.stateObject] = data;
94 | }
95 | }
96 |
97 | const actions = {
98 | // Events
99 | getEvents(context) {
100 | return new Promise((resolve, reject) => {
101 | firebase.firestore().collection("events")
102 | .orderBy("eventName")
103 | .where("verified", "==", true)
104 | .get()
105 | .then(({ docs }) => {
106 | // const events = eventFormater(docs);
107 | const eventData = [];
108 | for (let x =0; docs.length > x; x++) {
109 | const docData = docs[x].data();
110 | eventData.push({
111 | id: docs[x].id,
112 | ...docData,
113 | startFormatted: docData.start ? moment(docData.start).format("DD/MM/YYYY") : null,
114 | endFormatted: docData.end ? moment(docData.end).format("DD/MM/YYYY") : null,
115 | });
116 | }
117 | context.commit("events", eventData);
118 | resolve(eventData);
119 | }).catch( error => {
120 | reject(error)
121 | });
122 | })
123 | },
124 | getUserEvents(context) {
125 | return new Promise((resolve) => {
126 | firebase.firestore().collection("events")
127 | .orderBy("eventName")
128 | .where("createdBy", "==", context.getters.loginToken)
129 | .get()
130 | .then(({ docs }) => {
131 | // const events = eventFormater(docs);
132 | const eventData = [];
133 | for (let x =0; docs.length > x; x++) {
134 | const docData = docs[x].data();
135 | eventData.push({
136 | id: docs[x].id,
137 | ...docData,
138 | startFormatted: docData.start ? moment(docData.start).format("DD/MM/YYYY") : null,
139 | endFormatted: docData.end ? moment(docData.end).format("DD/MM/YYYY") : null,
140 | });
141 | }
142 | context.commit("userEvents", eventData);
143 | resolve(eventData);
144 | });
145 | })
146 | },
147 | createEvent(context, request) {
148 | request.createdBy = context.getters.loginToken;
149 | return new Promise((resolve, reject) => {
150 | const createEventFn = r => {
151 | firebase.firestore().collection("events")
152 | .add(r)
153 | .then(() => {
154 | context.dispatch("getEvents").then(events => {
155 | context.commit("events", events);
156 | resolve(events)
157 | })
158 | .catch(error => {
159 | reject(error);
160 | });
161 | });
162 | }
163 |
164 | firebase.firestore().collection("users")
165 | .doc(context.getters.loginToken)
166 | .get()
167 | .then(user => {
168 | if (user.data().verified) {
169 | request.verified = true;
170 | createEventFn(request);
171 | } else {
172 | createEventFn(request)
173 | }
174 | });
175 | });
176 | },
177 | updateEvent(context, request) {
178 | request.updatedBy = context.getters.loginToken;
179 | return new Promise((resolve) => {
180 | firebase.firestore().collection("events")
181 | .doc(request.id)
182 | .update(request)
183 | .then(() => {
184 | context.dispatch("getEvents");
185 | resolve();
186 | });
187 | })
188 | },
189 | deleteEvent(context, request) {
190 | return new Promise(() => {
191 | firebase.firestore().collection("events")
192 | .doc(request)
193 | .delete();
194 | })
195 | },
196 | // Will be used once I figure out why gapi is undefined when used in this module
197 | // gcEvent(context,request) {
198 | // gapi.load("client:auth2", () => {
199 | // gapi.client.init({
200 | // apiKey: API_KEY,
201 | // clientId: CLIENT_ID,
202 | // discoveryDocs: DISCOVERY_DOCS,
203 | // scope: SCOPES
204 | // }).then(() => {
205 | // if (gapi.auth2.getAuthInstance().isSignedIn.get()) {
206 | // context.dispatch("gcCreateEvent", request);
207 | // } else {
208 | // gapi.auth2.getAuthInstance().signIn().then(() => {
209 | // context.dispatch("gcCreateEvent", request);
210 | // })
211 | // .catch(() => {
212 | // alert(`You need to signin to your Google account before you can add event to your calendar`);
213 | // });
214 | // }
215 | // })
216 | // .catch(err => {
217 | // alert(err.details);
218 | // })
219 | // })
220 | // },
221 | // gcCreateEvent(event) {
222 | // const gcEvent = {
223 | // "summary": event.eventName,
224 | // "location": event.venue,
225 | // "start": {
226 | // "dateTime": moment(event.start).utc().format("YYYY-MM-DDTHH:mm:ss.SSS[Z]"),
227 | // "timeZone": "Africa/Johannesburg"
228 | // },
229 | // "end": {
230 | // "dateTime": moment(event.end).utc().format("YYYY-MM-DDTHH:mm:ss.SSS[Z]"),
231 | // "timeZone": "Africa/Johannesburg"
232 | // },
233 | // "reminders": {
234 | // "useDefault": false,
235 | // "overrides": [
236 | // {"method": "email", "minutes": 24 * 60},
237 | // {"method": "popup", "minutes": 10}
238 | // ]
239 | // }
240 | // };
241 |
242 | // var request = gapi.client.calendar.events.insert({
243 | // 'calendarId': 'primary',
244 | // 'resource': gcEvent,
245 | // });
246 |
247 | // const rootWindow = window;
248 |
249 | // request.execute(gcEvent => {
250 | // rootWindow.open(gcEvent.htmlLink);
251 | // })
252 | // }
253 | }
254 |
255 | export default { state, getters, mutations, actions }
256 |
--------------------------------------------------------------------------------
/src/store/modules/speakers.js:
--------------------------------------------------------------------------------
1 | import firebase from "../../firebase";
2 |
3 | const state = {
4 | speakers: [
5 | {
6 | name: "Makazole",
7 | lastname: "Mapimpi",
8 | position: "S.A rugby wing",
9 | contact: "makazoli@fakeemail.com",
10 | image: "https://ionic-vue-mobile-template-01.netlify.app/assets/img/makazoli.png",
11 | social: [
12 | {
13 | link: "https://www.linkedin.com/in/simomafuxwana",
14 | label: "LinkedIn"
15 | },
16 | {
17 | link: "https://simomafuxwana.netlify.com",
18 | label: "Website"
19 | },
20 | {
21 | link: "https://twitter.com/dlodeprojuicer",
22 | label: "Twitter"
23 | }
24 | ],
25 | highlights: [
26 | {
27 | name: "Microsoft Ignite",
28 | year: "2020"
29 | },
30 | {
31 | name: "Microsoft Insiders Dev",
32 | year: "2019"
33 | },
34 | {
35 | name: "Global DevOps Bootcamp",
36 | year: "2019"
37 | },
38 | ],
39 | },
40 | {
41 | name: "Max",
42 | lastname: "Verstapan",
43 | position: "RedBull F1 Driver",
44 | contact: "supermax@fakeemail.com",
45 | image: "https://ionic-vue-mobile-template-01.netlify.app/assets/img/max.png",
46 | social: [
47 | {
48 | link: "https://www.linkedin.com/in/simomafuxwana",
49 | label: "LinkedIn"
50 | }
51 | ],
52 | highlights: [
53 | {
54 | name: "Microsoft Ignite",
55 | year: "2020"
56 | },
57 | {
58 | name: "Microsoft Insiders Dev",
59 | year: "2019"
60 | },
61 | {
62 | name: "Global DevOps Bootcamp",
63 | year: "2019"
64 | },
65 | ],
66 | }
67 | ],
68 | }
69 |
70 | const getters = {
71 | speakers({ speakers = [] }) {
72 | return speakers || JSON.parse(localStorage.getItem("tcdbSpeakers"));
73 | },
74 | }
75 |
76 | const mutations = {
77 | speakers(state, data) {
78 | state.speakers = data;
79 | localStorage.setItem("tcdbSpeakers", JSON.stringify(data));
80 | },
81 | }
82 |
83 | const actions = {
84 | createSpeaker(context, request) {
85 | request.createdBy = context.getters.loginToken;
86 | return new Promise((resolve, reject) => {
87 | const createVenueFn = r => {
88 | firebase.firestore().collection("speakers")
89 | .add(r)
90 | .then(() => {
91 | context.dispatch("getSpeakers").then(speakers => {
92 | context.commit("speakers", speakers);
93 | resolve(speakers)
94 | })
95 | .catch(error => {
96 | reject(error);
97 | });
98 | });
99 | }
100 |
101 | firebase.firestore().collection("users")
102 | .doc(context.getters.loginToken)
103 | .get()
104 | .then(user => {
105 | if (user.data().verified) {
106 | request.verified = true;
107 | createVenueFn(request);
108 | } else {
109 | createVenueFn(request)
110 | }
111 | }).catch(err => {
112 | reject(err);
113 | });
114 | });
115 | },
116 | getSpeakers(context) {
117 | return new Promise((resolve, reject) => {
118 | firebase.firestore().collection("speakers")
119 | .orderBy("name")
120 | .where("verified", "==", true)
121 | .get()
122 | .then(({ docs }) => {
123 | context.commit("speakers", docs.map(a => a.data()));
124 | resolve(docs.map(a => a.data()));
125 | }).catch( error => {
126 | reject(error)
127 | });
128 | })
129 | },
130 | }
131 |
132 | export default { state, getters, mutations, actions }
133 |
--------------------------------------------------------------------------------
/src/store/modules/userProfile.js:
--------------------------------------------------------------------------------
1 | import firebase from "../../firebase";
2 |
3 | const state = {
4 | userProfile: {},
5 | }
6 |
7 | const getters = {
8 | userProfile({ userProfile }) {
9 | return userProfile || JSON.parse(localStorage.getItem("tcdbUserProfile"));
10 | },
11 | }
12 |
13 | const mutations = {
14 | userProfile(state, data) {
15 | state.events = data;
16 | localStorage.setItem("tcdbUserProfile", JSON.stringify(data));
17 | },
18 | }
19 |
20 | const actions = {
21 | createUser(request) {
22 | return new Promise((resolve) => {
23 | firebase.firestore().collection("users")
24 | .doc(request.uid)
25 | .set({...request})
26 | .then(() => {
27 | resolve();
28 | });
29 | })
30 | },
31 | getUserProfile(context, request) {
32 | return new Promise((resolve, reject) => {
33 | firebase.firestore().collection("users")
34 | .doc(request)
35 | .get()
36 | .then(doc => {
37 | context.commit("userProfile", doc.data());
38 | resolve(doc.data());
39 | }).catch(error => {
40 | reject(error);
41 | });
42 | })
43 | },
44 | getUsers() {
45 | return new Promise((resolve) => {
46 | firebase.firestore().collection("users")
47 | .get()
48 | .then(({ docs }) => {
49 | resolve(docs.map(a => a.data()));
50 | });
51 | })
52 | },
53 | updateUser(context, request) {
54 | request.updatedBy = context.getters.loginToken;
55 | return new Promise((resolve) => {
56 | firebase.firestore().collection("users")
57 | .doc(request.uid)
58 | .update(request)
59 | .then(() => {
60 | resolve();
61 | });
62 | })
63 | }
64 | }
65 |
66 | export { state, getters, mutations, actions }
67 |
--------------------------------------------------------------------------------
/src/store/modules/venues.js:
--------------------------------------------------------------------------------
1 | import firebase from "../../firebase";
2 |
3 | const state = {
4 | venues: [
5 | {
6 | "area": "Cape Town",
7 | "phone": "021 412 9999",
8 | "venueName": "The Wistin",
9 | "width": 80.6,
10 | "length": 50.2,
11 | "height": 5.63,
12 | "equipment": [
13 | "Overhead Projector",
14 | "PA System"
15 | ],
16 | "aircon": true,
17 | "createdBy": "JE3Bh37hpOch095fAEAcNbwrQWI3",
18 | "email": "gallagher@gallagher.co.za",
19 | "squareMeter": 27000,
20 | "wheelchairFriendly": true,
21 | "website": "https://gallagher.co.za/hall2",
22 | "capacityMax": 2500,
23 | "capacityMin": 20,
24 | "wifi": true,
25 | "verified": true
26 | },
27 | {
28 | "area": "Midrand, Johannesburg",
29 | "phone": "011 266 3000",
30 | "venueName": "Hall 2 - Gallagher Converntion Center",
31 | "width": 107.9,
32 | "length": 61.5,
33 | "height": 9.83,
34 | "equipment": [
35 | "Overhead Projector",
36 | "PA System"
37 | ],
38 | "aircon": true,
39 | "createdBy": "JE3Bh37hpOch095fAEAcNbwrQWI3",
40 | "email": "gallagher@gallagher.co.za",
41 | "squareMeter": 27000,
42 | "wheelchairFriendly": true,
43 | "website": "https://gallagher.co.za/hall2",
44 | "capacityMax": 7000,
45 | "capacityMin": 20,
46 | "wifi": true,
47 | "verified": true
48 | }
49 | ],
50 | filteredVenues: [],
51 | updateVenueSearchObject: {}
52 | }
53 |
54 | const getters = {
55 | venues({ venues = [] }) {
56 | return venues || JSON.parse(localStorage.getItem("tcdbVenues"));
57 | },
58 | filteredVenues({ venues = [], updateVenueSearchObject }) {
59 | if (!updateVenueSearchObject.field || updateVenueSearchObject.field === "") {
60 | return venues;
61 | } else {
62 | return venues.filter(venue => venue[updateVenueSearchObject.field].toLowerCase().includes(updateVenueSearchObject.value.toLowerCase()));
63 | }
64 | },
65 | }
66 |
67 | const mutations = {
68 | venues(state, data) {
69 | state.venues = data;
70 | localStorage.setItem("tcdbVenues", JSON.stringify(data));
71 | },
72 | updateSearch(state, data) {
73 | state[data.stateObject] = data;
74 | }
75 | }
76 |
77 | const actions = {
78 | createVenue(context, request) {
79 | request.createdBy = context.getters.loginToken;
80 | return new Promise((resolve, reject) => {
81 | const createVenueFn = r => {
82 | firebase.firestore().collection("venues")
83 | .add(r)
84 | .then(() => {
85 | context.dispatch("getVenues").then(venues => {
86 | context.commit("venues", venues);
87 | resolve(venues)
88 | })
89 | .catch(error => {
90 | reject(error);
91 | });
92 | });
93 | }
94 |
95 | firebase.firestore().collection("users")
96 | .doc(context.getters.loginToken)
97 | .get()
98 | .then(user => {
99 | if (user.data().verified) {
100 | request.verified = true;
101 | createVenueFn(request);
102 | } else {
103 | createVenueFn(request)
104 | }
105 | });
106 | });
107 | },
108 | getVenues(context) {
109 | return new Promise((resolve, reject) => {
110 | firebase.firestore().collection("venues")
111 | .orderBy("venueName")
112 | .where("verified", "==", true)
113 | .get()
114 | .then(({ docs }) => {
115 | context.commit("venues", docs.map(a => a.data()));
116 | resolve(docs.map(a => a.data()));
117 | }).catch( error => {
118 | reject(error)
119 | });
120 | })
121 | },
122 | }
123 |
124 | export default { state, getters, mutations, actions }
125 |
--------------------------------------------------------------------------------
/src/theme/media-queries.scss:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | ##Device = Desktops
4 | ##Screen = 1281px to higher resolution desktops
5 | */
6 |
7 | @media (min-width: 1281px) {
8 | .mobile-nav {
9 | display: none;
10 | }
11 |
12 | .home-content {
13 | display: flex;
14 | }
15 |
16 | .desktop-only {
17 | display: inherit;
18 | }
19 |
20 | .mobile-only {
21 | display: none;
22 | }
23 |
24 | .lg-content-center {
25 | margin: auto;
26 | width: 60%;
27 | }
28 |
29 | ion-col.speaker-column {
30 | width: 33.33% !important;
31 | }
32 |
33 | .not-mobile {
34 | display: block;
35 | width: 50%;
36 | text-align: center;
37 | margin: 0 auto;
38 | z-index: 9999999999;
39 | background: #fff;
40 | color: #154d75;
41 | }
42 |
43 |
44 | .no-events-img {
45 | width: 25%;
46 | margin: 5% 38%;
47 | text-align: center;
48 | img {
49 | filter: grayscale(20%);
50 | opacity: 0.7;
51 | }
52 | ion-button {
53 | opacity: 0.9;
54 | }
55 | }
56 |
57 | ion-title.logo2 {
58 | display: none;
59 | }
60 |
61 | }
62 |
63 | /*
64 | ##Device = Laptops, Desktops
65 | ##Screen = B/w 1025px to 1280px
66 | */
67 |
68 | @media (min-width: 1025px) and (max-width: 1280px) {
69 | .mobile-nav {
70 | display: none;
71 | }
72 |
73 | .current-month {
74 | left: 89% !important;
75 | }
76 |
77 | .lg-content-center {
78 | width: 80%;
79 | margin: auto;
80 | }
81 |
82 | .mobile-only {
83 | display: none;
84 | }
85 |
86 | .desktop-only {
87 | display: inherit;
88 | }
89 |
90 | ion-col.speaker-column {
91 | width: 33.33% !important;
92 | }
93 |
94 | .not-mobile {
95 | display: block;
96 | width: 50%;
97 | margin: 5% 38%;
98 | text-align: center;
99 | margin: 0 auto;
100 | z-index: 9999999999;
101 | background: #fff;
102 | color: #154d75;
103 | }
104 |
105 |
106 | .no-events-img {
107 | width: 25%;
108 | margin: 8% 38%;
109 | text-align: center;
110 | img {
111 | filter: grayscale(20%);
112 | opacity: 0.7;
113 | }
114 | ion-button {
115 | opacity: 0.9;
116 | }
117 | }
118 |
119 | ion-title.logo2 {
120 | display: none;
121 | }
122 |
123 | }
124 |
125 | /*
126 | ##Device = Tablets, Ipads (portrait)
127 | ##Screen = B/w 801px to 1024px
128 | */
129 |
130 | @media (min-width: 768px) and (max-width: 1024px) and (orientation: portrait) {
131 | .lg-content-center {
132 | width: 60%;
133 | margin: auto;
134 | background: greenyellow;
135 | }
136 |
137 | .mobile-only {
138 | display: inherit;
139 | }
140 |
141 | .desktop-only {
142 | display: none;
143 | }
144 |
145 | ion-col.speaker-column {
146 | width: 33.33% !important;
147 | }
148 |
149 | .desktop-nav {
150 | display: none;
151 | }
152 |
153 | .mobile-nav {
154 | display: inherit;
155 | }
156 |
157 | .current-month {
158 | display: none;
159 | }
160 |
161 | .not-mobile {
162 | display: none;
163 | }
164 |
165 | .no-events-img {
166 | width: 35%;
167 | margin: 8% 30%;
168 | text-align: center;
169 | img {
170 | filter: grayscale(20%);
171 | opacity: 0.7;
172 | }
173 | ion-button {
174 | opacity: 0.9;
175 | }
176 | }
177 |
178 | ion-title.logo2 {
179 | display: none;
180 | }
181 | }
182 |
183 | /*
184 | ##Device = Tablets, Ipads (landscape)
185 | ##Screen = B/w 767px to 1024px
186 | */
187 |
188 | @media (min-width: 768px) and (max-width: 1024px) and (orientation: landscape) {
189 | .lg-content-center {
190 | width: 90%;
191 | margin: auto;
192 | }
193 |
194 | ion-col.speaker-column {
195 | width: 33.33% !important;
196 | }
197 |
198 | .mobile-only {
199 | display: none;
200 | }
201 |
202 | .desktop-only {
203 | display: inherit;
204 | }
205 |
206 | .mobile-nav {
207 | display: none;
208 | }
209 |
210 | .current-month {
211 | display: none;
212 | }
213 |
214 | .not-mobile {
215 | display: none;
216 | }
217 |
218 | .no-events-img {
219 | width: 35%;
220 | margin: 8% 30%;
221 | text-align: center;
222 | img {
223 | filter: grayscale(20%);
224 | opacity: 0.7;
225 | }
226 | ion-button {
227 | opacity: 0.9;
228 | }
229 | }
230 |
231 | ion-title.logo2 {
232 | display: none;
233 | }
234 | }
235 |
236 | /*
237 | ##Device = Low Resolution Tablets, Mobiles (Landscape)
238 | ##Screen = B/w 481px to 767px
239 | */
240 |
241 | @media (min-width: 481px) and (max-width: 767px) {
242 | .lg-content-center {
243 | width: 90%;
244 | margin: auto;
245 | }
246 |
247 | ion-col.speaker-column {
248 | width: 50% !important;
249 | }
250 |
251 | .mobile-only {
252 | display: inherit;
253 | }
254 |
255 | .desktop-only {
256 | display: none;
257 | }
258 |
259 | .mobile-nav {
260 | display: none;
261 | }
262 |
263 | .current-month {
264 | display: none;
265 | }
266 |
267 | .not-mobile {
268 | display: none;
269 | }
270 |
271 | .no-events-img {
272 | width: 45%;
273 | margin: 6% 30%;
274 | text-align: center;
275 | img {
276 | filter: grayscale(20%);
277 | opacity: 0.7;
278 | }
279 | ion-button {
280 | opacity: 0.9;
281 | }
282 | }
283 |
284 | ion-title.logo {
285 | display: none;
286 | }
287 |
288 |
289 | ion-title.logo2 {
290 | // display: inherit;
291 | font-weight: 900;
292 | }
293 | }
294 |
295 | /*
296 | ##Device = Most of the Smartphones Mobiles (Portrait)
297 | ##Screen = B/w 320px to 479px
298 | */
299 |
300 | @media (min-width: 320px) and (max-width: 480px) {
301 | // .lg-content-center {
302 | // background: rgb(5, 140, 178);
303 | // width: 0%;
304 | // }
305 |
306 | ion-col.speaker-column {
307 | width: 100% !important;
308 | }
309 |
310 | .desktop-only {
311 | display: none;
312 | }
313 |
314 | .mobile-only {
315 | display: inherit;
316 | }
317 |
318 | .desktop-nav {
319 | display: none;
320 | }
321 |
322 | .mobile-nav {
323 | display: inherit;
324 | }
325 |
326 | .current-month {
327 | display: none;
328 | }
329 |
330 | .not-mobile {
331 | display: none;
332 | }
333 |
334 | .no-events-img {
335 | width: 60%;
336 | margin: 20% 20%;
337 | text-align: center;
338 | img {
339 | filter: grayscale(20%);
340 | opacity: 0.7;
341 | }
342 | ion-button {
343 | opacity: 0.9;
344 | }
345 | h2 {
346 | font-size: 20px;
347 | }
348 | }
349 |
350 | ion-title.logo {
351 | display: none;
352 | }
353 |
354 | ion-title.logo2 {
355 | font-weight: 900;
356 | margin-left: 10px;
357 | }
358 |
359 | }
--------------------------------------------------------------------------------
/src/theme/variables.scss:
--------------------------------------------------------------------------------
1 | /* Ionic Variables and Theming. For more info, please see:
2 | http://ionicframework.com/docs/theming/ */
3 |
4 | @import "./media-queries.scss";
5 |
6 | $baseColor: #181819;
7 |
8 | // $colors: (success, #000);
9 |
10 | :root {
11 | --ion-color-primary: #355724;
12 | --ion-color-primary-tint: #19ce4f;
13 | --ion-color-primary-shade: #19ce4f;
14 |
15 | --ion-color-light: #ffffff;
16 | --ion-color-medium: #e6e5e5;
17 | --ion-color-medium-tint: #777777;
18 | --ion-color-medium-shade: #777777;
19 | --ion-color-medium-contrast:#777777;
20 | --ion-background-color: #e2e9ea;
21 | color: #000;
22 |
23 | }
24 |
25 | h1 , h2 , h3 , h4 , p , a , ul li , strong , label {
26 | color: #777777;
27 | }
28 |
29 | ion-toolbar.modal-header {
30 | --background: #144d75;
31 |
32 | ion-title {
33 | color: #fff;
34 | }
35 | }
36 |
37 | .form-buttons {
38 | margin: 50px auto;
39 | }
40 |
41 |
42 |
--------------------------------------------------------------------------------
/src/views/CreateEvent.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Add Conference
9 |
10 |
11 | Name
12 |
13 |
14 |
15 | Website
16 |
17 |
18 |
22 |
23 |
24 |
25 | Venue
26 |
27 |
28 |
29 | Street
30 |
31 |
32 |
33 | Area / Suburb
34 |
35 |
36 |
37 | Town
38 |
39 |
40 |
41 | Province
42 |
43 |
44 | {{ item }}
45 |
46 |
47 |
48 |
49 | Start
50 |
58 |
59 |
60 | End
61 |
69 |
70 |
71 | Cancel
72 | Submit
73 |
74 |
75 |
76 |
77 |
78 |
79 |
178 |
179 |
--------------------------------------------------------------------------------
/src/views/CreateVenue.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Add Venue
9 |
10 |
11 |
17 |
18 |
19 | Name
20 |
21 |
22 |
23 | Website
24 |
25 |
26 |
30 |
31 |
32 |
33 | Room m²
34 |
35 |
36 |
37 | Room Length
38 |
39 |
40 |
41 | Room Width
42 |
43 |
44 |
45 | Room Height
46 |
47 |
48 |
49 | Equipment
50 |
51 |
52 |
53 | Disability Friendly
54 |
58 |
59 |
60 |
61 | WiFi
62 |
66 |
67 |
68 |
69 | Air-Conditioning
70 |
74 |
75 |
76 |
77 | Area
78 |
79 |
80 |
81 | Phone
82 |
83 |
84 |
85 | Email
86 |
87 |
88 |
89 | Cancel
90 | Submit
91 |
92 |
93 |
94 |
95 |
96 |
97 |
204 |
205 |
--------------------------------------------------------------------------------
/src/views/Dashboard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
13 |
14 | {{ item.name }} {{ item.lastname }}
15 | {{ item.organisation }}
16 | {{ item.address.province }}
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
70 |
71 |
76 |
--------------------------------------------------------------------------------
/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | A concise list of tech conferences in ZA
6 |
7 |
8 |
9 |
10 |
11 |
No search results
12 |
13 |
14 |
15 |
16 |
17 |
18 |
70 |
71 |
--------------------------------------------------------------------------------
/src/views/Login.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Login
9 |
10 |
11 | Email
12 |
13 |
14 |
15 | Password
16 |
17 |
18 |
19 |
20 | {{ endpointError.message }}
21 |
22 |
23 |
28 |
29 |
30 |
31 |
32 |
33 |
121 |
122 |
--------------------------------------------------------------------------------
/src/views/Profile.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{ profile.name }} {{ profile.lastname }}
9 |
10 |
11 |
My Events
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
91 |
92 |
--------------------------------------------------------------------------------
/src/views/Register.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Register
9 |
10 |
11 | {{ f.label }}
12 |
18 | {{ f.errMsg }}
19 |
20 |
21 | Province
22 |
23 |
24 | {{ item }}
25 |
26 |
27 |
28 |
29 | Would you like to be added to the list of Speakers?
30 |
34 |
35 |
36 |
37 |
38 |
39 | {{ f.label }}
40 |
47 | {{ f.errMsg }}
48 |
49 |
50 |
51 | Is it okay if we display your contact details (email/number) on Speaker list?
52 |
56 |
57 |
58 |
59 |
60 |
61 | {{ endpointError.message }}
62 |
63 |
64 |
72 |
73 |
74 |
75 |
76 |
77 |
366 |
367 |
--------------------------------------------------------------------------------
/src/views/Speakers.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Speakers you can reach out to if you are planning a conference
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
66 |
67 |
--------------------------------------------------------------------------------
/src/views/Venues.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | A concise list of tech conference venues in ZA
6 |
7 |
8 |
9 |
10 |
11 |
No search results
12 |
13 | If you wish to add a venue please email simodms@gmail.com
14 |
15 |
16 |
17 |
18 |
19 |
20 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/tests/e2e/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | 'cypress'
4 | ],
5 | env: {
6 | mocha: true,
7 | 'cypress/globals': true
8 | },
9 | rules: {
10 | strict: 'off'
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/tests/e2e/plugins/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable arrow-body-style */
2 | // https://docs.cypress.io/guides/guides/plugins-guide.html
3 |
4 | // if you need a custom webpack configuration you can uncomment the following import
5 | // and then use the `file:preprocessor` event
6 | // as explained in the cypress docs
7 | // https://docs.cypress.io/api/plugins/preprocessors-api.html#Examples
8 |
9 | // /* eslint-disable import/no-extraneous-dependencies, global-require */
10 | // const webpack = require('@cypress/webpack-preprocessor')
11 |
12 | module.exports = (on, config) => {
13 | // on('file:preprocessor', webpack({
14 | // webpackOptions: require('@vue/cli-service/webpack.config'),
15 | // watchOptions: {}
16 | // }))
17 |
18 | return Object.assign({}, config, {
19 | fixturesFolder: 'tests/e2e/fixtures',
20 | integrationFolder: 'tests/e2e/specs',
21 | screenshotsFolder: 'tests/e2e/screenshots',
22 | videosFolder: 'tests/e2e/videos',
23 | supportFile: 'tests/e2e/support/index.js'
24 | })
25 | }
26 |
--------------------------------------------------------------------------------
/tests/e2e/specs/test.js:
--------------------------------------------------------------------------------
1 | // https://docs.cypress.io/api/introduction/api.html
2 |
3 | describe('My First Test', () => {
4 | it('Visits the app root url', () => {
5 | cy.visit('/')
6 | cy.contains('h1', 'Welcome to Your Vue.js + TypeScript App')
7 | })
8 | })
9 |
--------------------------------------------------------------------------------
/tests/e2e/support/commands.js:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example commands.js shows you how to
3 | // create various custom commands and overwrite
4 | // existing commands.
5 | //
6 | // For more comprehensive examples of custom
7 | // commands please read more here:
8 | // https://on.cypress.io/custom-commands
9 | // ***********************************************
10 | //
11 | //
12 | // -- This is a parent command --
13 | // Cypress.Commands.add("login", (email, password) => { ... })
14 | //
15 | //
16 | // -- This is a child command --
17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
18 | //
19 | //
20 | // -- This is a dual command --
21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
22 | //
23 | //
24 | // -- This is will overwrite an existing command --
25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
26 |
--------------------------------------------------------------------------------
/tests/e2e/support/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands'
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
21 |
--------------------------------------------------------------------------------
/tests/unit/example.spec.ts:
--------------------------------------------------------------------------------
1 | import { shallowMount } from '@vue/test-utils'
2 | import HelloWorld from '@/components/HelloWorld.vue'
3 |
4 | describe('HelloWorld.vue', () => {
5 | it('renders props.msg when passed', () => {
6 | const msg = 'new message'
7 | const wrapper = shallowMount(HelloWorld, {
8 | props: { msg }
9 | })
10 | expect(wrapper.text()).toMatch(msg)
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "strict": true,
6 | "jsx": "preserve",
7 | "importHelpers": true,
8 | "moduleResolution": "node",
9 | "skipLibCheck": true,
10 | "esModuleInterop": true,
11 | "allowSyntheticDefaultImports": true,
12 | "sourceMap": true,
13 | "baseUrl": ".",
14 | "types": [
15 | "webpack-env",
16 | "jest"
17 | ],
18 | "paths": {
19 | "@/*": [
20 | "src/*"
21 | ]
22 | },
23 | "lib": [
24 | "esnext",
25 | "dom",
26 | "dom.iterable",
27 | "scripthost"
28 | ]
29 | },
30 | "include": [
31 | "src/**/*.ts",
32 | "src/**/*.tsx",
33 | "src/**/*.vue",
34 | "tests/**/*.ts",
35 | "tests/**/*.tsx", "src/router/index.js", "src/shims-vue.d.js", "src/main.js"
36 | ],
37 | "exclude": [
38 | "node_modules"
39 | ]
40 | }
41 |
--------------------------------------------------------------------------------