├── .babelrc ├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── README.md ├── build ├── css-loaders.js ├── dev-client.js ├── dev-server.js ├── karma.conf.js ├── webpack.base.conf.js ├── webpack.dev.conf.js └── webpack.prod.conf.js ├── firebase-rules.json ├── flux.json ├── index.html ├── package.json ├── src ├── assets │ └── logo.png ├── components │ ├── About.vue │ ├── App.vue │ ├── Hello.vue │ ├── Me.vue │ ├── Search.vue │ ├── booking │ │ └── ConfirmDecline.vue │ ├── layout │ │ └── LayoutDefault.vue │ ├── me │ │ ├── MeBookings.vue │ │ ├── MeSpaces.vue │ │ └── MeSpacesEdit.vue │ ├── partials │ │ ├── BookingWidget.vue │ │ ├── Footer.vue │ │ ├── GithubRibbon.vue │ │ ├── Header.vue │ │ └── LocationInput.vue │ └── space │ │ ├── SpaceCreate.vue │ │ ├── SpaceForm.vue │ │ ├── SpaceList.vue │ │ └── SpaceListItem.vue ├── main.js └── services │ ├── Api.js │ ├── Auth.js │ ├── Firebase.js │ ├── Router.js │ ├── Routes.js │ └── Settings.js ├── static └── .gitkeep ├── test └── unit │ ├── Hello.spec.js │ └── index.js ├── version └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-2"], 3 | "plugins": ["transform-runtime"], 4 | "comments": false 5 | } 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style 4 | extends: 'standard', 5 | // required to lint *.vue files 6 | plugins: [ 7 | 'html' 8 | ], 9 | // add your custom rules here 10 | 'rules': { 11 | // allow paren-less arrow functions 12 | 'arrow-parens': 0, 13 | // allow debugger during development 14 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .env 3 | .netlify 4 | node_modules/ 5 | dist/ 6 | npm-debug.log 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Timekit Marketplace Demo 2 | 3 | A quick showcase on how to integrate [Timekit](http://timekit.io) with a booking centered web app. 4 | 5 | You can play around with a hosted version here: 6 | [https://marketplace-demo.timekit.io](https://marketplace-demo.timekit.io) 7 | 8 | ## How it works 9 | 10 | As a vendor, you are able to create spaces available for bookings and add simple availability rules to them. Visitors can then book these spaces and the creator of them can confirm/decline bookings. 11 | 12 | The booking flow uses the [Booking.js](http://github.com/timekit-io/booking-js) widget. 13 | 14 | It uses Firebase for auth and DB, and integrates with Timekit through the [JavaScript SDK](http://github.com/timekit-io/js-sdk) 15 | 16 | Feel free to contribute and comment. 17 | 18 | ## Setup your own copy 19 | 1. Clone repo 20 | 2. Create a free account and app on Firebase.io 21 | - Copy the contents of `/firebase-rules.json` into your Firebase app rules 22 | - Create a new user in "Auth" that you want to login with 23 | - In "Database", create a new entity with the following structure: 24 | ``` 25 | users: { 26 | your-firebase-user-uid: { 27 | timekit: { 28 | api_token: "your-timekit-api-token", 29 | email: "your-timekit-email" 30 | } 31 | } 32 | } 33 | ``` 34 | 4. Edit `/src/services/Settings.js` and to match your settings 35 | - `timekit-app` should be set to your app slug registered on Timekit 36 | - `timekit-api-url` should be the API base URL (default will do in most cases) 37 | - `firebase-url` can be found in your Firebase dashboard 38 | 5. Run `npm install` and `npm run dev` to compile and start the dev server 39 | 6. Open http://localhost:8080 in your browser 40 | 7. Login with a valid Timekit user 41 | 42 | ## Notes 43 | There’s a few known bugs/features like: 44 | - Not validating forms 45 | - Not listing spaces by distance 46 | - Not being able to save connecting existing Timekit user to app 47 | 48 | ## Technical details 49 | The web app is build using [Vue.js](http://vuejs.org) and [Webpack](http://webpack.github.io), through [vue-cli](https://github.com/vuejs/vue-cli), and it uses [Firebase](https://www.firebase.com) for data storage. 50 | -------------------------------------------------------------------------------- /build/css-loaders.js: -------------------------------------------------------------------------------- 1 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 2 | 3 | module.exports = function (options) { 4 | // generate loader string to be used with extract text plugin 5 | function generateLoaders (loaders) { 6 | var sourceLoader = loaders.map(function (loader) { 7 | var extraParamChar 8 | if (/\?/.test(loader)) { 9 | loader = loader.replace(/\?/, '-loader?') 10 | extraParamChar = '&' 11 | } else { 12 | loader = loader + '-loader' 13 | extraParamChar = '?' 14 | } 15 | return loader + (options.sourceMap ? extraParamChar + 'sourceMap' : '') 16 | }).join('!') 17 | 18 | if (options.extract) { 19 | return ExtractTextPlugin.extract('vue-style-loader', sourceLoader) 20 | } else { 21 | return ['vue-style-loader', sourceLoader].join('!') 22 | } 23 | } 24 | 25 | // http://vuejs.github.io/vue-loader/configurations/extract-css.html 26 | return [ 27 | { 28 | key: 'css', 29 | value: generateLoaders(['css']) 30 | }, 31 | { 32 | key: 'less', 33 | value: generateLoaders(['css', 'less']) 34 | }, 35 | { 36 | key: 'sass', 37 | value: generateLoaders(['css', 'sass?indentedSyntax']) 38 | }, 39 | { 40 | key: 'scss', 41 | value: generateLoaders(['css', 'sass']) 42 | }, 43 | { 44 | key: 'stylus', 45 | value: generateLoaders(['css', 'stylus']) 46 | }, 47 | { 48 | key: 'styl', 49 | value: generateLoaders(['css', 'stylus']) 50 | } 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /build/dev-client.js: -------------------------------------------------------------------------------- 1 | var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') 2 | 3 | hotClient.subscribe(function (event) { 4 | if (event.action === 'reload') { 5 | window.location.reload() 6 | } 7 | }) 8 | -------------------------------------------------------------------------------- /build/dev-server.js: -------------------------------------------------------------------------------- 1 | var express = require('express') 2 | var webpack = require('webpack') 3 | var config = require('./webpack.dev.conf') 4 | 5 | var app = express() 6 | var compiler = webpack(config) 7 | 8 | var devMiddleware = require('webpack-dev-middleware')(compiler, { 9 | publicPath: config.output.publicPath, 10 | stats: { 11 | colors: true, 12 | chunks: false 13 | } 14 | }) 15 | 16 | var hotMiddleware = require('webpack-hot-middleware')(compiler) 17 | // force page reload when html-webpack-plugin template changes 18 | compiler.plugin('compilation', function (compilation) { 19 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 20 | hotMiddleware.publish({ action: 'reload' }) 21 | cb() 22 | }) 23 | }) 24 | 25 | // handle fallback for HTML5 history API 26 | app.use(require('connect-history-api-fallback')()) 27 | // serve webpack bundle output 28 | app.use(devMiddleware) 29 | // enable hot-reload and state-preserving 30 | // compilation error display 31 | app.use(hotMiddleware) 32 | // serve pure static assets 33 | app.use('/static', express.static('./static')) 34 | 35 | app.listen(8080, function (err) { 36 | if (err) { 37 | console.log(err) 38 | return 39 | } 40 | console.log('Listening at http://localhost:8080') 41 | }) 42 | -------------------------------------------------------------------------------- /build/karma.conf.js: -------------------------------------------------------------------------------- 1 | var webpackConf = require('./webpack.base.conf') 2 | delete webpackConf.entry 3 | webpackConf.devtool = 'inline-source-map' 4 | 5 | module.exports = function (config) { 6 | config.set({ 7 | browsers: ['PhantomJS'], 8 | frameworks: ['jasmine'], 9 | reporters: ['spec'], 10 | files: ['../test/unit/index.js'], 11 | preprocessors: { 12 | '../test/unit/index.js': ['webpack', 'sourcemap'] 13 | }, 14 | webpack: webpackConf, 15 | webpackMiddleware: { 16 | noInfo: true 17 | } 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | 3 | module.exports = { 4 | entry: { 5 | app: './src/main.js' 6 | }, 7 | output: { 8 | path: path.resolve(__dirname, '../dist/static'), 9 | publicPath: '/static/', 10 | filename: '[name].js' 11 | }, 12 | resolve: { 13 | extensions: ['', '.js', '.vue'], 14 | fallback: [path.join(__dirname, '../node_modules')], 15 | alias: { 16 | 'src': path.resolve(__dirname, '../src') 17 | } 18 | }, 19 | resolveLoader: { 20 | fallback: [path.join(__dirname, '../node_modules')] 21 | }, 22 | module: { 23 | preLoaders: [ 24 | { 25 | test: /\.vue$/, 26 | loader: 'eslint', 27 | exclude: /node_modules/ 28 | }, 29 | { 30 | test: /\.js$/, 31 | loader: 'eslint', 32 | exclude: /node_modules/ 33 | } 34 | ], 35 | loaders: [ 36 | { 37 | test: /\.vue$/, 38 | loader: 'vue' 39 | }, 40 | { 41 | test: /\.js$/, 42 | loader: 'babel', 43 | exclude: /node_modules/ 44 | }, 45 | { 46 | test: /\.json$/, 47 | loader: 'json' 48 | }, 49 | { 50 | test: /\.html$/, 51 | loader: 'vue-html' 52 | }, 53 | { 54 | test: /\.(png|jpg|gif|svg)$/, 55 | loader: 'url', 56 | query: { 57 | limit: 10000, 58 | name: '[name].[ext]?[hash:7]' 59 | } 60 | } 61 | ] 62 | }, 63 | eslint: { 64 | formatter: require('eslint-friendly-formatter') 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack') 2 | var config = require('./webpack.base.conf') 3 | var cssLoaders = require('./css-loaders') 4 | var HtmlWebpackPlugin = require('html-webpack-plugin') 5 | 6 | // eval-source-map is faster for development 7 | config.devtool = '#eval-source-map' 8 | 9 | config.vue = config.vue || {} 10 | config.vue.loaders = config.vue.loaders || {} 11 | cssLoaders({ 12 | sourceMap: false, 13 | extract: false 14 | }).forEach(function (loader) { 15 | config.vue.loaders[loader.key] = loader.value 16 | }) 17 | 18 | // add hot-reload related code to entry chunks 19 | var polyfill = 'eventsource-polyfill' 20 | var devClient = './build/dev-client' 21 | Object.keys(config.entry).forEach(function (name, i) { 22 | var extras = i === 0 ? [polyfill, devClient] : [devClient] 23 | config.entry[name] = extras.concat(config.entry[name]) 24 | }) 25 | 26 | // necessary for the html plugin to work properly 27 | // when serving the html from in-memory 28 | config.output.publicPath = '/' 29 | 30 | config.plugins = (config.plugins || []).concat([ 31 | new webpack.DefinePlugin({ 32 | 'process.env': { 33 | NODE_ENV: '"development"' 34 | } 35 | }), 36 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage 37 | new webpack.optimize.OccurenceOrderPlugin(), 38 | new webpack.HotModuleReplacementPlugin(), 39 | new webpack.NoErrorsPlugin(), 40 | // https://github.com/ampedandwired/html-webpack-plugin 41 | new HtmlWebpackPlugin({ 42 | filename: 'index.html', 43 | template: 'index.html', 44 | inject: true 45 | }) 46 | ]) 47 | 48 | module.exports = config 49 | -------------------------------------------------------------------------------- /build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack') 2 | var config = require('./webpack.base.conf') 3 | var cssLoaders = require('./css-loaders') 4 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 5 | var HtmlWebpackPlugin = require('html-webpack-plugin') 6 | 7 | // naming output files with hashes for better caching. 8 | // dist/index.html will be auto-generated with correct URLs. 9 | config.output.filename = '[name].[chunkhash].js' 10 | config.output.chunkFilename = '[id].[chunkhash].js' 11 | 12 | // whether to generate source map for production files. 13 | // disabling this can speed up the build. 14 | var SOURCE_MAP = true 15 | 16 | config.devtool = SOURCE_MAP ? '#source-map' : false 17 | 18 | config.vue = config.vue || {} 19 | config.vue.loaders = config.vue.loaders || {} 20 | cssLoaders({ 21 | sourceMap: SOURCE_MAP, 22 | extract: true 23 | }).forEach(function (loader) { 24 | config.vue.loaders[loader.key] = loader.value 25 | }) 26 | 27 | config.plugins = (config.plugins || []).concat([ 28 | // http://vuejs.github.io/vue-loader/workflow/production.html 29 | new webpack.DefinePlugin({ 30 | 'process.env': { 31 | NODE_ENV: '"production"' 32 | } 33 | }), 34 | new webpack.optimize.UglifyJsPlugin({ 35 | compress: { 36 | warnings: false 37 | } 38 | }), 39 | new webpack.optimize.OccurenceOrderPlugin(), 40 | // extract css into its own file 41 | new ExtractTextPlugin('[name].[contenthash].css'), 42 | // generate dist index.html with correct asset hash for caching. 43 | // you can customize output by editing /index.html 44 | // see https://github.com/ampedandwired/html-webpack-plugin 45 | new HtmlWebpackPlugin({ 46 | filename: '../index.html', 47 | template: 'index.html', 48 | inject: true, 49 | minify: { 50 | removeComments: true, 51 | collapseWhitespace: true, 52 | removeAttributeQuotes: true 53 | // more options: 54 | // https://github.com/kangax/html-minifier#options-quick-reference 55 | } 56 | }) 57 | ]) 58 | 59 | module.exports = config 60 | -------------------------------------------------------------------------------- /firebase-rules.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | ".read": true, 4 | ".write": false, 5 | 6 | "users": { 7 | ".read": false, 8 | ".write": false, 9 | 10 | "$user_id": { 11 | ".write": "auth.uid === $user_id", 12 | "timekit": { 13 | ".write": false 14 | } 15 | } 16 | }, 17 | 18 | "spaces": { 19 | ".read": true, 20 | ".write": "auth != null", 21 | ".indexOn": ["owner"], 22 | "$space": { 23 | "owner": { 24 | ".validate": "newData.val() == auth.uid" 25 | } 26 | } 27 | }, 28 | 29 | "_geofire": { 30 | ".read": true, 31 | ".write": "auth != null", 32 | 33 | "$type": { 34 | ".indexOn": ["g"] 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /flux.json: -------------------------------------------------------------------------------- 1 | { 2 | "release": { 3 | "check-git-master": true, 4 | "bump-version-file": true, 5 | "bump-package-version": true, 6 | "release-on-github": true 7 | }, 8 | "deploy": { 9 | "prod": { 10 | "check-git-master": true, 11 | "install-npm-deps": true, 12 | "build-dist-bundle": "yarn run build", 13 | "netlify-site-deploy": "c55eeee0-1763-49b5-9c62-dba8d205209e" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Timekit - Marketplace Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "timekit-marketplace-demo", 3 | "description": "Timekit Marketplace Demo", 4 | "author": "Henning Horn ", 5 | "version": "1.1.0", 6 | "private": true, 7 | "scripts": { 8 | "dev": "node build/dev-server.js", 9 | "build": "rimraf dist && mkdirp dist && ncp static dist/static && cross-env NODE_ENV=production webpack --progress --hide-modules --config build/webpack.prod.conf.js --bail", 10 | "release:major": "../flux/flux timekit:release major marketplace-demo", 11 | "release:minor": "../flux/flux timekit:release minor marketplace-demo", 12 | "release:patch": "../flux/flux timekit:release patch marketplace-demo", 13 | "deploy:prod": "../flux/flux timekit:deploy prod marketplace-demo", 14 | "test": "karma start build/karma.conf.js --single-run" 15 | }, 16 | "dependencies": { 17 | "firebase": "^2.4.1", 18 | "geofire": "^4.0.0", 19 | "google-maps": "^3.2.1", 20 | "moment": "^2.12.0", 21 | "putainde-localstorage": "^4.0.0", 22 | "timekit-sdk": "^1.4.0", 23 | "vue": "^1.0.16", 24 | "vue-router": "vuejs/vue-router" 25 | }, 26 | "devDependencies": { 27 | "babel-core": "^6.0.0", 28 | "babel-loader": "^6.0.0", 29 | "babel-plugin-transform-runtime": "^6.0.0", 30 | "babel-preset-es2015": "^6.0.0", 31 | "babel-preset-stage-2": "^6.0.0", 32 | "babel-runtime": "^5.8.38", 33 | "connect-history-api-fallback": "^1.1.0", 34 | "cross-env": "^1.0.7", 35 | "css-loader": "^0.23.0", 36 | "eslint": "^2.0.0", 37 | "eslint-config-standard": "^5.1.0", 38 | "eslint-friendly-formatter": "^1.2.2", 39 | "eslint-loader": "^1.3.0", 40 | "eslint-plugin-html": "^1.3.0", 41 | "eslint-plugin-promise": "^1.0.8", 42 | "eslint-plugin-standard": "^1.3.2", 43 | "eventsource-polyfill": "^0.9.6", 44 | "express": "^4.13.3", 45 | "extract-text-webpack-plugin": "^1.0.1", 46 | "file-loader": "^0.8.4", 47 | "function-bind": "^1.0.2", 48 | "html-webpack-plugin": "^2.8.1", 49 | "inject-loader": "^2.0.1", 50 | "jasmine-core": "^2.4.1", 51 | "json-loader": "^0.5.4", 52 | "karma": "^0.13.15", 53 | "karma-jasmine": "^0.3.6", 54 | "karma-phantomjs-launcher": "^1.0.0", 55 | "karma-sourcemap-loader": "^0.3.7", 56 | "karma-spec-reporter": "0.0.24", 57 | "karma-webpack": "^1.7.0", 58 | "mkdirp": "^0.5.1", 59 | "ncp": "^2.0.0", 60 | "phantomjs-prebuilt": "^2.1.3", 61 | "rimraf": "^2.5.0", 62 | "url-loader": "^0.5.7", 63 | "vue-hot-reload-api": "^1.2.0", 64 | "vue-html-loader": "^1.0.0", 65 | "vue-loader": "^8.1.3", 66 | "vue-style-loader": "^1.0.0", 67 | "webpack": "^1.12.2", 68 | "webpack-dev-middleware": "^1.4.0", 69 | "webpack-hot-middleware": "^2.6.0" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timekit-io/marketplace-demo/8c77b3e6bfa33794b83c2a848386424513d72aed/src/assets/logo.png -------------------------------------------------------------------------------- /src/components/About.vue: -------------------------------------------------------------------------------- 1 | 25 | -------------------------------------------------------------------------------- /src/components/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 26 | -------------------------------------------------------------------------------- /src/components/Hello.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 20 | -------------------------------------------------------------------------------- /src/components/Me.vue: -------------------------------------------------------------------------------- 1 | 28 | -------------------------------------------------------------------------------- /src/components/Search.vue: -------------------------------------------------------------------------------- 1 | 132 | 133 | 219 | -------------------------------------------------------------------------------- /src/components/booking/ConfirmDecline.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 62 | -------------------------------------------------------------------------------- /src/components/layout/LayoutDefault.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 20 | -------------------------------------------------------------------------------- /src/components/me/MeBookings.vue: -------------------------------------------------------------------------------- 1 | 47 | 48 | 78 | -------------------------------------------------------------------------------- /src/components/me/MeSpaces.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 90 | -------------------------------------------------------------------------------- /src/components/me/MeSpacesEdit.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 89 | -------------------------------------------------------------------------------- /src/components/partials/BookingWidget.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 53 | -------------------------------------------------------------------------------- /src/components/partials/Footer.vue: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /src/components/partials/GithubRibbon.vue: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /src/components/partials/Header.vue: -------------------------------------------------------------------------------- 1 | 76 | 77 | 146 | -------------------------------------------------------------------------------- /src/components/partials/LocationInput.vue: -------------------------------------------------------------------------------- 1 | 4 | 39 | -------------------------------------------------------------------------------- /src/components/space/SpaceCreate.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 138 | -------------------------------------------------------------------------------- /src/components/space/SpaceForm.vue: -------------------------------------------------------------------------------- 1 | 123 | 124 | 178 | -------------------------------------------------------------------------------- /src/components/space/SpaceList.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 27 | -------------------------------------------------------------------------------- /src/components/space/SpaceListItem.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 79 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from './services/Router' 3 | 4 | if (process.env.NODE_ENV === 'development') { 5 | Vue.config.debug = true 6 | } 7 | 8 | /* eslint-disable no-new */ 9 | Router(Vue) 10 | 11 | /* 12 | new Vue({ 13 | el: 'body', 14 | components: { App } 15 | }) 16 | */ 17 | -------------------------------------------------------------------------------- /src/services/Api.js: -------------------------------------------------------------------------------- 1 | import Timekit from 'timekit-sdk' 2 | import Settings from './Settings' 3 | 4 | let config = { 5 | app: Settings.get('timekit-app'), 6 | apiBaseUrl: Settings.get('timekit-api-url') 7 | } 8 | 9 | Timekit.configure(config) 10 | 11 | if (Settings.get('timekit-email') && Settings.get('timekit-api-token')) { 12 | Timekit.setUser(Settings.get('timekit-email'), Settings.get('timekit-api-token')) 13 | } 14 | 15 | export default Timekit 16 | -------------------------------------------------------------------------------- /src/services/Auth.js: -------------------------------------------------------------------------------- 1 | import Firebase from './Firebase' 2 | 3 | let Auth = (function () { 4 | var user = Firebase.getAuth() 5 | 6 | Firebase.onAuth(function (authData) { 7 | user = authData 8 | }) 9 | 10 | var Auth = {} 11 | 12 | Auth.isAuthenticated = function () { 13 | return user !== null 14 | } 15 | 16 | Auth.loginPassword = function (email, password) { 17 | return Firebase.authWithPassword({ 18 | email: email, 19 | password: password 20 | }) 21 | } 22 | 23 | Auth.logout = function () { 24 | Firebase.unauth() 25 | } 26 | 27 | Auth.getUser = function () { 28 | return user 29 | } 30 | 31 | Auth.onAuthChange = function (fn) { 32 | Firebase.onAuth(fn) 33 | } 34 | 35 | return Auth 36 | })() 37 | 38 | export default Auth 39 | -------------------------------------------------------------------------------- /src/services/Firebase.js: -------------------------------------------------------------------------------- 1 | import Firebase from 'firebase' 2 | import Settings from './Settings' 3 | 4 | let FB = new Firebase(Settings.get('firebase-url')) 5 | 6 | export default FB 7 | -------------------------------------------------------------------------------- /src/services/Router.js: -------------------------------------------------------------------------------- 1 | import VueRouter from 'vue-router' 2 | import Routes from './Routes' 3 | import App from '../components/App' 4 | 5 | export default function (Vue) { 6 | Vue.use(VueRouter) 7 | let router = new VueRouter({ 8 | linkActiveClass: 'is-active' 9 | }) 10 | 11 | Routes(router) 12 | 13 | router.start(App, '#app') 14 | } 15 | -------------------------------------------------------------------------------- /src/services/Routes.js: -------------------------------------------------------------------------------- 1 | import Auth from '../services/Auth' 2 | import About from '../components/About' 3 | import Search from '../components/Search' 4 | import SpaceCreate from '../components/space/SpaceCreate' 5 | import Me from '../components/Me' 6 | import MeBookings from '../components/me/MeBookings' 7 | import MeSpaces from '../components/me/MeSpaces' 8 | import MeSpacesEdit from '../components/me/MeSpacesEdit' 9 | 10 | export default function (router) { 11 | router.map({ 12 | '/about': { 13 | name: 'about', 14 | component: About, 15 | layout: 'default' 16 | }, 17 | 18 | '/search': { 19 | name: 'search', 20 | component: Search, 21 | layout: 'default' 22 | }, 23 | 24 | '/create_space': { 25 | name: 'create_space', 26 | component: SpaceCreate, 27 | layout: 'default', 28 | auth: true 29 | }, 30 | 31 | '/me': { 32 | name: 'me', 33 | component: Me, 34 | layout: 'default', 35 | auth: true, 36 | subRoutes: { 37 | '/bookings': { 38 | name: 'me_bookings', 39 | component: MeBookings 40 | }, 41 | '/spaces': { 42 | name: 'me_spaces', 43 | component: MeSpaces 44 | }, 45 | '/spaces/:id': { 46 | name: 'me_spaces_edit', 47 | component: MeSpacesEdit 48 | } 49 | } 50 | } 51 | }) 52 | 53 | router.redirect({ 54 | '*': 'search' 55 | }) 56 | 57 | router.beforeEach(function (transition) { 58 | if (transition.to.auth && !Auth.isAuthenticated()) { 59 | transition.redirect('/') 60 | } else { 61 | transition.next() 62 | } 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /src/services/Settings.js: -------------------------------------------------------------------------------- 1 | import Storage from 'putainde-localstorage' 2 | 3 | let store = Storage.create({ 4 | namespace: 'marketplace' 5 | }) 6 | 7 | store.set('timekit-app', 'marketplace-demo') 8 | store.set('firebase-url', 'https://timekit-rmt.firebaseio.com/') 9 | store.set('google-maps-key', 'AIzaSyACwmNz_uSpQQTao9KStvrYdDJV7qgRfXA') 10 | store.set('default-user-email', 'marketplace-demo@timekit.io') 11 | store.set('default-user-password', 'marketplace-demo-password') 12 | 13 | if (process.env.NODE_ENV === 'development') { 14 | store.set('timekit-api-url', 'http://api-localhost.timekit.io/') 15 | } else { 16 | store.set('timekit-api-url', 'https://api.timekit.io/') 17 | } 18 | 19 | function get (key) { 20 | return store.get(key) 21 | } 22 | 23 | function set (key, value) { 24 | store.set(key, value) 25 | } 26 | 27 | function remove (key) { 28 | store.remove(key) 29 | } 30 | 31 | export default { 32 | get, 33 | set, 34 | remove 35 | } 36 | -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timekit-io/marketplace-demo/8c77b3e6bfa33794b83c2a848386424513d72aed/static/.gitkeep -------------------------------------------------------------------------------- /test/unit/Hello.spec.js: -------------------------------------------------------------------------------- 1 | /* global describe, it, expect */ 2 | 3 | import Vue from 'vue' 4 | import Hello from 'src/components/Hello' 5 | 6 | describe('Hello.vue', () => { 7 | it('should render correct contents', () => { 8 | const vm = new Vue({ 9 | template: '
', 10 | components: { Hello } 11 | }).$mount() 12 | expect(vm.$el.querySelector('.hello h1').textContent).toBe('Hello World!') 13 | }) 14 | }) 15 | 16 | // also see example testing a component with mocks at 17 | // https://github.com/vuejs/vue-loader-example/blob/master/test/unit/a.spec.js#L24-L49 18 | -------------------------------------------------------------------------------- /test/unit/index.js: -------------------------------------------------------------------------------- 1 | // Polyfill fn.bind() for PhantomJS 2 | /* eslint-disable no-extend-native */ 3 | Function.prototype.bind = require('function-bind') 4 | 5 | // require all test files (files that ends with .spec.js) 6 | var testsContext = require.context('.', true, /\.spec$/) 7 | testsContext.keys().forEach(testsContext) 8 | -------------------------------------------------------------------------------- /version: -------------------------------------------------------------------------------- 1 | v1.1.0 --------------------------------------------------------------------------------