├── .meteor ├── cordova-plugins ├── release ├── platforms ├── .gitignore ├── .id ├── packages ├── .finished-upgraders └── versions ├── .eslintignore ├── packages ├── zensroom │ ├── tests │ │ ├── index.js │ │ └── models │ │ │ ├── bookings │ │ │ ├── schema.spec.js │ │ │ ├── collection.spec.js │ │ │ ├── fragments.spec.js │ │ │ ├── mutations.spec.js │ │ │ ├── parameters.spec.js │ │ │ ├── resolvers.spec.js │ │ │ └── permissions.spec.js │ │ │ ├── reviews │ │ │ ├── schema.spec.js │ │ │ ├── fragments.spec.js │ │ │ ├── mutations.spec.js │ │ │ ├── resolvers.spec.js │ │ │ ├── collection.spec.js │ │ │ ├── parameters.spec.js │ │ │ └── permissions.spec.js │ │ │ └── properties │ │ │ ├── schema.spec.js │ │ │ ├── collection.spec.js │ │ │ ├── fragments.spec.js │ │ │ ├── mutations.spec.js │ │ │ ├── resolvers.spec.js │ │ │ ├── parameters.spec.js │ │ │ └── permissions.spec.js │ ├── lib │ │ ├── stylesheets │ │ │ ├── _breakpoints.scss │ │ │ ├── _typography.scss │ │ │ ├── _home.scss │ │ │ ├── _footer.scss │ │ │ ├── _reviews.scss │ │ │ ├── _forms.scss │ │ │ ├── _bookings.scss │ │ │ ├── main.scss │ │ │ ├── _colors.scss │ │ │ ├── _global.scss │ │ │ ├── _variables.scss │ │ │ ├── _users.scss │ │ │ ├── _admin.scss │ │ │ ├── _spinner.scss │ │ │ ├── _header.scss │ │ │ ├── _accounts.scss │ │ │ └── _rooms.scss │ │ ├── client │ │ │ └── main.js │ │ ├── server │ │ │ ├── emails │ │ │ │ ├── common │ │ │ │ │ ├── test.handlebars │ │ │ │ │ └── wrapper.handlebars │ │ │ │ ├── rooms │ │ │ │ │ └── roomsNew.handlebars │ │ │ │ ├── bookings │ │ │ │ │ └── bookingsNew.handlebars │ │ │ │ └── templates.js │ │ │ ├── indexes.js │ │ │ ├── main.js │ │ │ ├── seed.js │ │ │ ├── bookings │ │ │ │ └── callbacks.js │ │ │ └── rooms │ │ │ │ └── callbacks.js │ │ ├── modules │ │ │ ├── icons.js │ │ │ ├── reviews │ │ │ │ ├── views.js │ │ │ │ ├── permissions.js │ │ │ │ ├── collection.js │ │ │ │ ├── schema.js │ │ │ │ └── check.js │ │ │ ├── rooms │ │ │ │ ├── views.js │ │ │ │ ├── permissions.js │ │ │ │ ├── fragments.js │ │ │ │ ├── collection.js │ │ │ │ ├── parameters.js │ │ │ │ └── schema.js │ │ │ ├── bookings │ │ │ │ ├── permissions.js │ │ │ │ ├── collection.js │ │ │ │ ├── fragments.js │ │ │ │ ├── resolvers.js │ │ │ │ ├── views.js │ │ │ │ └── schema.js │ │ │ ├── index.js │ │ │ ├── fragments.js │ │ │ ├── products.js │ │ │ ├── admin.js │ │ │ ├── data.js │ │ │ ├── emails.js │ │ │ ├── i18n.js │ │ │ ├── components.js │ │ │ ├── custom_fields.js │ │ │ └── routes.js │ │ ├── components │ │ │ ├── static │ │ │ │ ├── About.js │ │ │ │ ├── HowTo.js │ │ │ │ ├── Terms.js │ │ │ │ └── Privacy.js │ │ │ ├── common │ │ │ │ ├── Footer.js │ │ │ │ ├── FlashMessages.js │ │ │ │ ├── Home.js │ │ │ │ ├── Flash.js │ │ │ │ ├── Layout.js │ │ │ │ ├── AdminLayout.js │ │ │ │ └── Header.js │ │ │ ├── reviews │ │ │ │ ├── ReviewsItem.js │ │ │ │ ├── ReviewsList.js │ │ │ │ └── ReviewsNewForm.js │ │ │ ├── rooms │ │ │ │ ├── RoomsNewPage.js │ │ │ │ ├── RoomsReviews.js │ │ │ │ ├── RoomsItem.js │ │ │ │ ├── RoomsList.js │ │ │ │ ├── RoomsNewForm.js │ │ │ │ ├── RoomsMain.js │ │ │ │ ├── RoomsPhotos.js │ │ │ │ ├── RoomsPage.js │ │ │ │ ├── RoomsSearchResults.js │ │ │ │ ├── RoomsSearch.js │ │ │ │ ├── RoomsSearchFilters.js │ │ │ │ └── RoomsSearchForm.js │ │ │ ├── admin │ │ │ │ ├── AdminUsersRooms.js │ │ │ │ ├── AdminUsersReviews.js │ │ │ │ ├── AdminUsersBookings.js │ │ │ │ ├── ReviewsDashboard.js │ │ │ │ ├── RoomsDashboard.js │ │ │ │ └── BookingsDashboard.js │ │ │ ├── users │ │ │ │ ├── UsersSignUp.jsx │ │ │ │ ├── UsersLogIn.jsx │ │ │ │ ├── UsersAccountMenu.js │ │ │ │ ├── UsersProfile.js │ │ │ │ ├── UsersAccount.js │ │ │ │ └── UsersMenu.js │ │ │ └── bookings │ │ │ │ ├── BookingsPending.js │ │ │ │ ├── BookingsFuture.js │ │ │ │ ├── BookingsCurrent.js │ │ │ │ ├── BookingsPast.js │ │ │ │ ├── BookingsRoomUser.js │ │ │ │ ├── BookingsCompleted.js │ │ │ │ ├── BookingsPage.js │ │ │ │ └── BookingsNewForm.js │ │ └── containers │ │ │ └── withUnavailableDatesContainer.js │ └── package.js ├── vulcan-maps │ ├── lib │ │ ├── modules │ │ │ ├── headtags.js │ │ │ ├── index.js │ │ │ └── components.js │ │ ├── client │ │ │ └── main.js │ │ ├── server │ │ │ ├── main.js │ │ │ └── maps.js │ │ └── components │ │ │ └── Map.js │ └── package.js ├── .gitignore ├── _buffer │ ├── buffer.js │ └── package.js └── _boilerplate-generator │ ├── package.js │ ├── template-web.browser.js │ ├── template-web.cordova.js │ └── generator.js ├── Dockerfile ├── publish_packages.sh ├── .github └── CONTRIBUTING.md ├── jsdoc-conf.json ├── .editorconfig ├── .yo-rc.json ├── CONTRIBUTING.md ├── .gitignore ├── license.md ├── jsdoc.json ├── prestart_vulcan.sh ├── .eslintrc ├── sample_settings.json ├── package.json ├── README.md ├── .jshintrc └── History.md /.meteor/cordova-plugins: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | packages/_* 2 | -------------------------------------------------------------------------------- /packages/zensroom/tests/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.meteor/release: -------------------------------------------------------------------------------- 1 | METEOR@1.5.2.1 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM abernix/meteord:onbuild -------------------------------------------------------------------------------- /.meteor/platforms: -------------------------------------------------------------------------------- 1 | server 2 | browser 3 | -------------------------------------------------------------------------------- /packages/vulcan-maps/lib/modules/headtags.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/zensroom/lib/stylesheets/_breakpoints.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/zensroom/lib/stylesheets/_typography.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | dev_bundle 2 | local 3 | meteorite 4 | -------------------------------------------------------------------------------- /packages/.gitignore: -------------------------------------------------------------------------------- 1 | /bootstrap3-datepicker 2 | /npm-container -------------------------------------------------------------------------------- /packages/vulcan-maps/lib/modules/index.js: -------------------------------------------------------------------------------- 1 | import './components'; -------------------------------------------------------------------------------- /packages/vulcan-maps/lib/client/main.js: -------------------------------------------------------------------------------- 1 | import '../modules/index'; 2 | -------------------------------------------------------------------------------- /packages/vulcan-maps/lib/modules/components.js: -------------------------------------------------------------------------------- 1 | import '../components/Map'; -------------------------------------------------------------------------------- /packages/_buffer/buffer.js: -------------------------------------------------------------------------------- 1 | global.Buffer = global.Buffer || require("buffer").Buffer; -------------------------------------------------------------------------------- /packages/vulcan-maps/lib/server/main.js: -------------------------------------------------------------------------------- 1 | import '../modules/index'; 2 | export * from './maps'; -------------------------------------------------------------------------------- /packages/zensroom/lib/stylesheets/_home.scss: -------------------------------------------------------------------------------- 1 | .home-section{ 2 | margin-bottom: $spacing * 2; 3 | } -------------------------------------------------------------------------------- /publish_packages.sh: -------------------------------------------------------------------------------- 1 | for d in packages/* ; do 2 | echo "$d" 3 | cd $d 4 | meteor publish 5 | cd ../../ 6 | done -------------------------------------------------------------------------------- /packages/zensroom/lib/client/main.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | The client entry point for the package. 4 | 5 | */ 6 | 7 | import '../modules/index.js'; -------------------------------------------------------------------------------- /packages/zensroom/lib/server/emails/common/test.handlebars: -------------------------------------------------------------------------------- 1 | This is just a test

2 | 3 | Sent at {{date}}.

-------------------------------------------------------------------------------- /packages/zensroom/lib/stylesheets/_footer.scss: -------------------------------------------------------------------------------- 1 | .footer{ 2 | padding: $spacing; 3 | border-top: 1px solid #eee; 4 | margin-top: $spacing; 5 | text-align: center; 6 | } -------------------------------------------------------------------------------- /packages/zensroom/lib/stylesheets/_reviews.scss: -------------------------------------------------------------------------------- 1 | .reviews-item{ 2 | padding-bottom: $spacing; 3 | border-bottom: 1px solid $light-grey; 4 | margin-bottom: $spacing; 5 | } -------------------------------------------------------------------------------- /packages/zensroom/lib/stylesheets/_forms.scss: -------------------------------------------------------------------------------- 1 | .input-amenities, .input-spaces{ 2 | .col-sm-9{ 3 | display: flex; 4 | flex-wrap: wrap; 5 | } 6 | .checkbox{ 7 | width: 50%; 8 | } 9 | } -------------------------------------------------------------------------------- /packages/zensroom/lib/server/indexes.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | MongoDB indexes for geographic search 4 | 5 | */ 6 | 7 | import Rooms from '../modules/rooms/collection'; 8 | 9 | Rooms.rawCollection().createIndex({location: '2dsphere'}); 10 | -------------------------------------------------------------------------------- /packages/_buffer/package.js: -------------------------------------------------------------------------------- 1 | Package.describe({ 2 | name: "buffer" 3 | }); 4 | 5 | Package.onUse( function(api) { 6 | 7 | api.use([ 8 | 'ecmascript' 9 | ]); 10 | 11 | api.addFiles([ 12 | 'buffer.js' 13 | ], ['client']); 14 | 15 | }); 16 | -------------------------------------------------------------------------------- /packages/zensroom/lib/server/emails/rooms/roomsNew.handlebars: -------------------------------------------------------------------------------- 1 | 2 | {{RoomsSingle.user.displayName}} 3 | has added a new room: 4 | {{RoomsSingle.name}} 5 | 6 | 7 |

-------------------------------------------------------------------------------- /packages/zensroom/tests/models/bookings/schema.spec.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import schema from '../../../lib/models/bookings/schema'; 3 | 4 | describe('The schema for: bookings', () => { 5 | it('should have some tests', () => { 6 | assert(false); 7 | }); 8 | }); -------------------------------------------------------------------------------- /packages/zensroom/tests/models/reviews/schema.spec.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import schema from '../../../lib/models/reviews/schema'; 3 | 4 | describe('The schema for: reviews', () => { 5 | it('should have some tests', () => { 6 | assert(false); 7 | }); 8 | }); -------------------------------------------------------------------------------- /packages/zensroom/tests/models/properties/schema.spec.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import schema from '../../../lib/models/properties/schema'; 3 | 4 | describe('The schema for: properties', () => { 5 | it('should have some tests', () => { 6 | assert(false); 7 | }); 8 | }); -------------------------------------------------------------------------------- /packages/zensroom/tests/models/reviews/fragments.spec.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import fragments from '../../../lib/models/reviews/fragments'; 3 | 4 | describe('The fragments for: reviews', () => { 5 | it('should have some tests', () => { 6 | assert(false); 7 | }); 8 | }); -------------------------------------------------------------------------------- /packages/zensroom/tests/models/reviews/mutations.spec.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import mutations from '../../../lib/models/reviews/mutations'; 3 | 4 | describe('The mutations for: reviews', () => { 5 | it('should have some tests', () => { 6 | assert(false); 7 | }); 8 | }); -------------------------------------------------------------------------------- /packages/zensroom/tests/models/reviews/resolvers.spec.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import resolvers from '../../../lib/models/reviews/resolvers'; 3 | 4 | describe('The resolvers for: reviews', () => { 5 | it('should have some tests', () => { 6 | assert(false); 7 | }); 8 | }); -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Before starting on a new feature, please [check out the roadmap](https://trello.com/b/dwPR0LTz/vulcanjs-roadmap) and come check-in in the [Vulcan Slack channel](http://slack.telescopeapp.org/). 2 | 3 | Also, all PRs should be made to the `devel` branch, not `master`. 4 | -------------------------------------------------------------------------------- /packages/zensroom/tests/models/bookings/collection.spec.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import collection from '../../../lib/models/bookings/fragments'; 3 | 4 | describe('The collection for: bookings', () => { 5 | it('should have some tests', () => { 6 | assert(false); 7 | }); 8 | }); -------------------------------------------------------------------------------- /packages/zensroom/tests/models/bookings/fragments.spec.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import fragments from '../../../lib/models/bookings/fragments'; 3 | 4 | describe('The fragments for: bookings', () => { 5 | it('should have some tests', () => { 6 | assert(false); 7 | }); 8 | }); -------------------------------------------------------------------------------- /packages/zensroom/tests/models/bookings/mutations.spec.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import mutations from '../../../lib/models/bookings/mutations'; 3 | 4 | describe('The mutations for: bookings', () => { 5 | it('should have some tests', () => { 6 | assert(false); 7 | }); 8 | }); -------------------------------------------------------------------------------- /packages/zensroom/tests/models/bookings/parameters.spec.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import parameters from '../../../lib/models/bookings/parameters'; 3 | 4 | describe('The parameters for: bookings', () => { 5 | it('should have some tests', () => { 6 | assert(false); 7 | }); 8 | }); -------------------------------------------------------------------------------- /packages/zensroom/tests/models/bookings/resolvers.spec.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import resolvers from '../../../lib/models/bookings/resolvers'; 3 | 4 | describe('The resolvers for: bookings', () => { 5 | it('should have some tests', () => { 6 | assert(false); 7 | }); 8 | }); -------------------------------------------------------------------------------- /packages/zensroom/tests/models/reviews/collection.spec.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import collection from '../../../lib/models/reviews/fragments'; 3 | 4 | describe('The collection for: reviews', () => { 5 | it('should have some tests', () => { 6 | assert(false); 7 | }); 8 | }); -------------------------------------------------------------------------------- /packages/zensroom/tests/models/reviews/parameters.spec.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import parameters from '../../../lib/models/reviews/parameters'; 3 | 4 | describe('The parameters for: reviews', () => { 5 | it('should have some tests', () => { 6 | assert(false); 7 | }); 8 | }); -------------------------------------------------------------------------------- /jsdoc-conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["plugins/markdown"], 3 | "markdown": { 4 | "parser": "gfm" 5 | }, 6 | "source": { 7 | "exclude": [ 8 | ".git", 9 | ".meteor", 10 | "node_modules" 11 | ], 12 | "includePattern": ".+\\.js(x|doc)?$" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/zensroom/lib/server/main.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | The server entry point for the package. 4 | 5 | */ 6 | 7 | import '../modules/index.js'; 8 | import './indexes.js'; 9 | import './emails/templates.js'; 10 | 11 | import './bookings/callbacks.js'; 12 | 13 | import './rooms/callbacks.js'; 14 | -------------------------------------------------------------------------------- /packages/zensroom/tests/models/bookings/permissions.spec.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import permissions from '../../../lib/models/bookings/permissions'; 3 | 4 | describe('The permissions for: bookings', () => { 5 | it('should have some tests', () => { 6 | assert(false); 7 | }); 8 | }); -------------------------------------------------------------------------------- /packages/zensroom/tests/models/properties/collection.spec.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import collection from '../../../lib/models/properties/fragments'; 3 | 4 | describe('The collection for: properties', () => { 5 | it('should have some tests', () => { 6 | assert(false); 7 | }); 8 | }); -------------------------------------------------------------------------------- /packages/zensroom/tests/models/properties/fragments.spec.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import fragments from '../../../lib/models/properties/fragments'; 3 | 4 | describe('The fragments for: properties', () => { 5 | it('should have some tests', () => { 6 | assert(false); 7 | }); 8 | }); -------------------------------------------------------------------------------- /packages/zensroom/tests/models/properties/mutations.spec.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import mutations from '../../../lib/models/properties/mutations'; 3 | 4 | describe('The mutations for: properties', () => { 5 | it('should have some tests', () => { 6 | assert(false); 7 | }); 8 | }); -------------------------------------------------------------------------------- /packages/zensroom/tests/models/properties/resolvers.spec.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import resolvers from '../../../lib/models/properties/resolvers'; 3 | 4 | describe('The resolvers for: properties', () => { 5 | it('should have some tests', () => { 6 | assert(false); 7 | }); 8 | }); -------------------------------------------------------------------------------- /packages/zensroom/tests/models/reviews/permissions.spec.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import permissions from '../../../lib/models/reviews/permissions'; 3 | 4 | describe('The permissions for: reviews', () => { 5 | it('should have some tests', () => { 6 | assert(false); 7 | }); 8 | }); -------------------------------------------------------------------------------- /packages/zensroom/tests/models/properties/parameters.spec.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import parameters from '../../../lib/models/properties/parameters'; 3 | 4 | describe('The parameters for: properties', () => { 5 | it('should have some tests', () => { 6 | assert(false); 7 | }); 8 | }); -------------------------------------------------------------------------------- /packages/zensroom/tests/models/properties/permissions.spec.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import permissions from '../../../lib/models/properties/permissions'; 3 | 4 | describe('The permissions for: properties', () => { 5 | it('should have some tests', () => { 6 | assert(false); 7 | }); 8 | }); -------------------------------------------------------------------------------- /packages/zensroom/lib/server/seed.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Seed database (not currently used) 4 | 5 | */ 6 | 7 | // import Users from 'meteor/vulcan:users'; 8 | // import { newMutation } from 'meteor/vulcan:core'; 9 | 10 | // const seedData = []; 11 | 12 | // Meteor.startup(function() { 13 | 14 | // }); -------------------------------------------------------------------------------- /packages/zensroom/lib/stylesheets/_bookings.scss: -------------------------------------------------------------------------------- 1 | .rdtPicker td.rdtDisabled{ 2 | color: red; 3 | } 4 | 5 | .bookings-form-field{ 6 | margin-bottom: $spacing/2; 7 | } 8 | 9 | .bookings-form-submit{ 10 | margin-top: $spacing; 11 | } 12 | 13 | .bookings-checkout{ 14 | margin-bottom: $spacing; 15 | } -------------------------------------------------------------------------------- /packages/zensroom/lib/modules/icons.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Icons 4 | 5 | http://docs.vulcanjs.org/core-components.html#Icon 6 | 7 | */ 8 | 9 | import { Utils } from 'meteor/vulcan:core'; 10 | 11 | Utils.icons.marker = 'map-marker'; 12 | Utils.icons.previous = 'angle-left'; 13 | Utils.icons.next = 'angle-right'; 14 | -------------------------------------------------------------------------------- /packages/zensroom/lib/modules/reviews/views.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Reviews views 4 | 5 | http://docs.vulcanjs.org/terms-parameters.html#Using-Views 6 | 7 | */ 8 | 9 | import Reviews from './collection.js'; 10 | 11 | Reviews.addView('roomReviews', terms => ({ 12 | selector: { 13 | roomId: terms.roomId 14 | } 15 | })); -------------------------------------------------------------------------------- /packages/zensroom/lib/modules/rooms/views.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Rooms views 4 | 5 | http://docs.vulcanjs.org/terms-parameters.html#Using-Views 6 | 7 | */ 8 | 9 | import Rooms from './collection.js'; 10 | 11 | Rooms.addView('roomsSearch', terms => ({ 12 | options: { 13 | sort: {sticky: -1, baseScore: -1} 14 | } 15 | })); -------------------------------------------------------------------------------- /packages/zensroom/lib/modules/rooms/permissions.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Rooms permissions 4 | 5 | http://docs.vulcanjs.org/groups-permissions.html#Assigning-Actions 6 | 7 | */ 8 | 9 | import Users from 'meteor/vulcan:users'; 10 | 11 | Users.groups.members.can([ 12 | 'rooms.new', 13 | 'rooms.edit.own', 14 | 'rooms.remove.own', 15 | ]); -------------------------------------------------------------------------------- /packages/zensroom/lib/modules/reviews/permissions.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Reviews permissions 4 | 5 | http://docs.vulcanjs.org/groups-permissions.html#Assigning-Actions 6 | 7 | */ 8 | 9 | import Users from 'meteor/vulcan:users'; 10 | 11 | Users.groups.members.can([ 12 | 'reviews.new', 13 | 'reviews.edit.own', 14 | 'reviews.remove.own', 15 | ]); -------------------------------------------------------------------------------- /.meteor/.id: -------------------------------------------------------------------------------- 1 | # This file contains a token that is unique to your project. 2 | # Check it into your repository along with the rest of this directory. 3 | # It can be used for purposes such as: 4 | # - ensuring you don't accidentally deploy one app on top of another 5 | # - providing package authors with aggregated statistics 6 | 7 | 4ibiemui4bqn1j744h7 8 | -------------------------------------------------------------------------------- /packages/zensroom/lib/modules/bookings/permissions.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Bookings permissions 4 | 5 | http://docs.vulcanjs.org/groups-permissions.html#Assigning-Actions 6 | 7 | */ 8 | 9 | import Users from 'meteor/vulcan:users'; 10 | 11 | Users.groups.members.can([ 12 | 'bookings.new', 13 | 'bookings.edit.own', 14 | 'bookings.remove.own', 15 | ]); -------------------------------------------------------------------------------- /packages/zensroom/lib/components/static/About.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | About page. 4 | 5 | */ 6 | 7 | import React from 'react'; 8 | import { Components, registerComponent } from 'meteor/vulcan:core'; 9 | 10 | const About = () => 11 | 12 |
13 | 14 | About 15 | 16 |
17 | 18 | registerComponent('About', About); 19 | 20 | export default About; -------------------------------------------------------------------------------- /packages/zensroom/lib/components/static/HowTo.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | How To page. 4 | 5 | */ 6 | 7 | import React from 'react'; 8 | import { Components, registerComponent } from 'meteor/vulcan:core'; 9 | 10 | const HowTo = () => 11 | 12 |
13 | 14 | HowTo 15 | 16 |
17 | 18 | registerComponent('HowTo', HowTo); 19 | 20 | export default HowTo; -------------------------------------------------------------------------------- /packages/zensroom/lib/components/static/Terms.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Terms page. 4 | 5 | */ 6 | 7 | import React from 'react'; 8 | import { Components, registerComponent } from 'meteor/vulcan:core'; 9 | 10 | const Terms = () => 11 | 12 |
13 | 14 | Terms 15 | 16 |
17 | 18 | registerComponent('Terms', Terms); 19 | 20 | export default Terms; -------------------------------------------------------------------------------- /packages/zensroom/lib/components/static/Privacy.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Privacy page. 4 | 5 | */ 6 | 7 | import React from 'react'; 8 | import { Components, registerComponent } from 'meteor/vulcan:core'; 9 | 10 | const Privacy = () => 11 | 12 |
13 | 14 | Privacy 15 | 16 |
17 | 18 | registerComponent('Privacy', Privacy); 19 | 20 | export default Privacy; -------------------------------------------------------------------------------- /packages/zensroom/lib/components/common/Footer.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Footer 4 | 5 | */ 6 | 7 | import { registerComponent } from 'meteor/vulcan:core'; 8 | import React from 'react'; 9 | 10 | const Footer = props => 11 | 14 | 15 | 16 | Footer.displayName = 'Footer'; 17 | 18 | registerComponent('Footer', Footer); 19 | 20 | // export default Footer; -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [*.{js,html}] 6 | 7 | charset = utf-8 8 | end_of_line = lf 9 | indent_brace_style = 1TBS 10 | indent_size = 2 11 | indent_style = space 12 | insert_final_newline = true 13 | max_line_length = 80 14 | quote_type = auto 15 | spaces_around_operators = true 16 | trim_trailing_whitespace = true 17 | 18 | [*.md] 19 | 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /packages/zensroom/lib/modules/rooms/fragments.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Rooms fragments 4 | 5 | http://docs.vulcanjs.org/fragments.html 6 | 7 | */ 8 | 9 | import { registerFragment } from 'meteor/vulcan:core'; 10 | 11 | registerFragment(` 12 | fragment RoomsItemFragment on Room { 13 | _id 14 | createdAt 15 | name 16 | bedsNumber 17 | guestsNumber 18 | amenities 19 | photos 20 | } 21 | `); 22 | -------------------------------------------------------------------------------- /packages/_boilerplate-generator/package.js: -------------------------------------------------------------------------------- 1 | Package.describe({ 2 | name: "boilerplate-generator", 3 | summary: "Generates the boilerplate html from program's manifest", 4 | version: '1.2.0' 5 | }); 6 | 7 | Package.onUse(api => { 8 | api.use('ecmascript'); 9 | api.use([ 10 | 'underscore', 11 | ], 'server'); 12 | api.mainModule('generator.js', 'server'); 13 | api.export('Boilerplate', 'server'); 14 | }); 15 | -------------------------------------------------------------------------------- /packages/zensroom/lib/components/reviews/ReviewsItem.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Review list item 4 | 5 | */ 6 | 7 | import React from 'react'; 8 | import { registerComponent } from 'meteor/vulcan:core'; 9 | 10 | const ReviewsItem = ({ review }) => 11 | 12 |
13 | 14 | {review.comment} 15 | 16 |
17 | 18 | registerComponent('ReviewsItem', ReviewsItem); 19 | 20 | // export default ReviewsItem; -------------------------------------------------------------------------------- /packages/zensroom/lib/stylesheets/main.scss: -------------------------------------------------------------------------------- 1 | // @import "bootstrap"; 2 | 3 | @import "breakpoints"; 4 | @import "typography"; 5 | @import "colors"; 6 | @import "variables"; 7 | @import "spinner"; 8 | @import "global"; 9 | 10 | @import "accounts"; 11 | @import "admin"; 12 | @import "header"; 13 | @import "users"; 14 | @import "rooms"; 15 | @import "bookings"; 16 | @import "reviews"; 17 | @import "forms"; 18 | @import "home"; 19 | @import "footer"; 20 | -------------------------------------------------------------------------------- /packages/vulcan-maps/package.js: -------------------------------------------------------------------------------- 1 | Package.describe({ 2 | name: "vulcan:maps", 3 | summary: "Vulcan Google Maps package", 4 | version: '1.6.0', 5 | git: "https://github.com/VulcanJS/Vulcan.git" 6 | }); 7 | 8 | Package.onUse(function (api) { 9 | 10 | api.versionsFrom("METEOR@1.0"); 11 | 12 | api.use([ 13 | 'vulcan:core@1.6.0', 14 | ]); 15 | 16 | api.mainModule('lib/server/main.js', 'server'); 17 | api.mainModule('lib/client/main.js', 'client'); 18 | 19 | }); 20 | -------------------------------------------------------------------------------- /packages/zensroom/lib/server/emails/bookings/bookingsNew.handlebars: -------------------------------------------------------------------------------- 1 | 2 | {{BookingsSingle.user.displayName}} 3 | has booked room 4 | {{BookingsSingle.room.name}} 5 | : 6 | 7 |

8 | 9 | From {{BookingsSingle.startAtFormatted}} to {{BookingsSingle.endAtFormatted}} 10 |
11 | 12 |

-------------------------------------------------------------------------------- /packages/zensroom/lib/server/emails/templates.js: -------------------------------------------------------------------------------- 1 | import VulcanEmail from 'meteor/vulcan:email'; 2 | 3 | VulcanEmail.addTemplates({ 4 | test: Assets.getText("lib/server/emails/common/test.handlebars"), 5 | wrapper: Assets.getText("lib/server/emails/common/wrapper.handlebars"), 6 | roomsNew: Assets.getText("lib/server/emails/rooms/roomsNew.handlebars"), 7 | bookingsNew: Assets.getText("lib/server/emails/bookings/bookingsNew.handlebars"), 8 | }); -------------------------------------------------------------------------------- /packages/zensroom/lib/modules/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Modules entry point 4 | 5 | */ 6 | 7 | import './fragments'; 8 | import './icons'; 9 | import './custom_fields'; 10 | import './i18n'; 11 | import './components'; 12 | import './admin'; 13 | import './routes'; 14 | import './products'; 15 | import './emails'; 16 | 17 | import './rooms/collection'; 18 | 19 | import './reviews/collection'; 20 | import './reviews/views'; 21 | 22 | import './bookings/collection'; 23 | import './bookings/views'; 24 | 25 | 26 | -------------------------------------------------------------------------------- /packages/zensroom/lib/server/bookings/callbacks.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Server callbacks 4 | 5 | See: http://docs.vulcanjs.org/callbacks.html 6 | 7 | */ 8 | 9 | import { addCallback } from 'meteor/vulcan:core'; 10 | 11 | /* 12 | 13 | When a booking is paid, mark it as paid 14 | 15 | */ 16 | function setBookingPaidAt (modifier, post, charge) { 17 | modifier.$set.paidAt = new Date(); 18 | modifier.$set.status = 3; // mark as paid 19 | return modifier; 20 | } 21 | addCallback('bookings.charge.sync', setBookingPaidAt); 22 | -------------------------------------------------------------------------------- /.meteor/packages: -------------------------------------------------------------------------------- 1 | # see http://docs.vulcanjs.org/packages 2 | 3 | vulcan:core 4 | 5 | ############ Language Packages ############ 6 | 7 | vulcan:i18n-en-us 8 | 9 | ############ Accounts Packages ############ 10 | 11 | accounts-password@1.4.0 12 | # accounts-twitter 13 | # accounts-facebook 14 | 15 | ############ Your Packages ############ 16 | 17 | # example-movies 18 | # example-instagram 19 | # example-forum 20 | # example-customization 21 | # example-permissions 22 | # example-membership 23 | # example-interfaces 24 | 25 | vulcan:debug 26 | zensroom -------------------------------------------------------------------------------- /packages/zensroom/lib/stylesheets/_colors.scss: -------------------------------------------------------------------------------- 1 | $light-yellow: #FFFBDB; 2 | 3 | $lightest-grey: #f3f3f3; 4 | $lighter-grey: #eee; 5 | $light-grey: #ddd; 6 | 7 | $light-blue: #DAEDFF; 8 | $blue: #0275d8; 9 | $white: #fff; 10 | $medium-grey: #bbb; 11 | $dark-grey: #888; 12 | $black: #333; 13 | 14 | $red: #E04E4B; 15 | 16 | $header-bg: $light-blue; 17 | 18 | $active-color: $blue; 19 | 20 | $lightest-border: $lightest-grey; 21 | $lighter-border: $lighter-grey; 22 | $light-border: $light-grey; 23 | 24 | $light-text: $medium-grey; 25 | $medium-text: $dark-grey; 26 | -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "vulcanjs-cli": { 3 | "appName": "zensroom", 4 | "isVulcan": true, 5 | "packageManager": "yarn", 6 | "reactExtension": "jsx", 7 | "storyBook": { 8 | "isUsed": "pending", 9 | "setupStatus": "pending" 10 | }, 11 | "packages": { 12 | "zensroom": { 13 | "models": { 14 | "properties": {}, 15 | "reviews": {}, 16 | "bookings": {} 17 | }, 18 | "routes": { 19 | "home": { 20 | "routePath": "/" 21 | } 22 | } 23 | } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /packages/zensroom/lib/modules/rooms/collection.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Rooms collection 4 | 5 | http://docs.vulcanjs.org/schemas.html 6 | 7 | */ 8 | 9 | import { createCollection, getDefaultResolvers, getDefaultMutations } from 'meteor/vulcan:core'; 10 | import schema from './schema.js'; 11 | import './fragments.js'; 12 | import './permissions.js'; 13 | import './parameters.js'; 14 | 15 | const Rooms = createCollection({ 16 | collectionName: 'Rooms', 17 | typeName: 'Room', 18 | schema, 19 | resolvers: getDefaultResolvers('Rooms'), 20 | mutations: getDefaultMutations('Rooms'), 21 | }); 22 | 23 | export default Rooms; -------------------------------------------------------------------------------- /packages/zensroom/lib/modules/reviews/collection.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Reviews collection 4 | 5 | http://docs.vulcanjs.org/schemas.html 6 | 7 | */ 8 | 9 | import { createCollection, getDefaultResolvers, getDefaultMutations } from 'meteor/vulcan:core'; 10 | import schema from './schema.js'; 11 | import './permissions.js'; 12 | import { newCheck } from './check'; 13 | 14 | const Reviews = createCollection({ 15 | collectionName: 'Reviews', 16 | typeName: 'Review', 17 | schema, 18 | resolvers: getDefaultResolvers('Reviews'), 19 | mutations: getDefaultMutations('Reviews', { newCheck }), 20 | }); 21 | 22 | export default Reviews; -------------------------------------------------------------------------------- /packages/zensroom/lib/components/rooms/RoomsNewPage.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Page for inserting a new room 4 | 5 | */ 6 | 7 | import React from 'react'; 8 | import { Components, registerComponent } from 'meteor/vulcan:core'; 9 | import { FormattedMessage } from 'meteor/vulcan:i18n'; 10 | 11 | // import RoomsNewForm from './RoomsNewForm'; 12 | 13 | const RoomsNewPage = () => 14 | 15 |
16 |

17 | 18 |
19 | 20 | registerComponent('RoomsNewPage', RoomsNewPage); 21 | 22 | // export default RoomsNewPage; -------------------------------------------------------------------------------- /packages/zensroom/lib/components/admin/AdminUsersRooms.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Show a user's rooms in the admin users dashboard 4 | 5 | http://docs.vulcanjs.org/admin.html 6 | 7 | */ 8 | 9 | import React from 'react'; 10 | import { Link } from 'react-router'; 11 | import { registerComponent } from 'meteor/vulcan:core'; 12 | 13 | const AdminUsersRooms = ({ document: user }) => 14 | 19 | 20 | registerComponent('AdminUsersRooms', AdminUsersRooms); 21 | 22 | export default AdminUsersRooms; -------------------------------------------------------------------------------- /packages/zensroom/lib/stylesheets/_global.scss: -------------------------------------------------------------------------------- 1 | .main{ 2 | padding: $spacing; 3 | max-width: 900px; 4 | margin: 0 auto; 5 | } 6 | 7 | .admin-main{ 8 | padding: $spacing; 9 | width: 100%; 10 | } 11 | 12 | .page{ 13 | } 14 | 15 | .page-title{ 16 | margin-bottom: $spacing; 17 | padding-bottom: $spacing/2; 18 | border-bottom: 1px solid $lighter-grey; 19 | } 20 | 21 | .section-title{ 22 | font-size: 1.5rem; 23 | margin-bottom: $spacing; 24 | padding-bottom: $spacing/2; 25 | border-bottom: 1px solid $lighter-grey; 26 | } 27 | 28 | .dropdown-item{ 29 | &.active{ 30 | a{ 31 | color: white; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /packages/zensroom/lib/stylesheets/_variables.scss: -------------------------------------------------------------------------------- 1 | $spacing: 20px; 2 | 3 | $smaller-font: 0.8rem; 4 | $small-font: 0.9rem; 5 | $medium-font: 1rem; 6 | $large-font: 1.25rem; 7 | $larger-font: 1.35rem; 8 | 9 | $border: 1px solid $light-border; 10 | 11 | @mixin activeHover{ 12 | &:hover{ 13 | background: $active-color; 14 | color: $white; 15 | border-color: $active-color; 16 | text-decoration: none; 17 | } 18 | } 19 | 20 | @mixin flex-center{ 21 | display: flex; 22 | align-items: center; 23 | } 24 | 25 | @mixin border-radius{ 26 | border-radius: .25rem; 27 | } 28 | @mixin border{ 29 | border: $border; 30 | } 31 | 32 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | - **All PRs should be made to the `devel` branch, not `master`.** 2 | 3 | - Come check-in in the [Telescope Slack channel](http://slack.telescopeapp.org/). 👋 4 | 5 | - Completely new features should be shipped as external packages with their own repos (see [3rd party packages](http://nova-docs.telescopeapp.org/plugins.html)). Don't hesitate to come by the [Slack channel](http://slack.telescopeapp.org/) to speak about it. 6 | 7 | - We don't have test at the moment, and Travis integration is broken. If you know how to fix it, you are welcome (see [#1253](https://github.com/TelescopeJS/Telescope/issues/1253)! 8 | 9 | - Be nice 😉 10 | -------------------------------------------------------------------------------- /.meteor/.finished-upgraders: -------------------------------------------------------------------------------- 1 | # This file contains information which helps Meteor properly upgrade your 2 | # app when you run 'meteor update'. You should check it into version control 3 | # with your project. 4 | 5 | notices-for-0.9.0 6 | notices-for-0.9.1 7 | 0.9.4-platform-file 8 | notices-for-facebook-graph-api-2 9 | 1.2.0-standard-minifiers-package 10 | 1.2.0-meteor-platform-split 11 | 1.2.0-cordova-changes 12 | 1.2.0-breaking-changes 13 | 1.3.0-split-minifiers-package 14 | 1.3.5-remove-old-dev-bundle-link 15 | 1.4.0-remove-old-dev-bundle-link 16 | 1.4.1-add-shell-server-package 17 | 1.4.3-split-account-service-packages 18 | 1.5-add-dynamic-import-package 19 | -------------------------------------------------------------------------------- /packages/zensroom/lib/components/admin/AdminUsersReviews.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Show a user's reviews in the admin users dashboard 4 | 5 | http://docs.vulcanjs.org/admin.html 6 | 7 | */ 8 | 9 | import React from 'react'; 10 | import { Link } from 'react-router'; 11 | import { registerComponent, Utils } from 'meteor/vulcan:core'; 12 | 13 | const AdminUsersReviews = ({ document: user }) => 14 | 19 | 20 | registerComponent('AdminUsersReviews', AdminUsersReviews); 21 | 22 | export default AdminUsersReviews; -------------------------------------------------------------------------------- /packages/zensroom/lib/modules/fragments.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Fragments on the Users collection 4 | 5 | http://docs.vulcanjs.org/fragments.html 6 | 7 | */ 8 | 9 | import { extendFragment } from 'meteor/vulcan:core'; 10 | 11 | extendFragment('UsersAdmin', ` 12 | rooms(limit: 5){ 13 | ...RoomsItemFragment 14 | } 15 | bookings(limit: 5){ 16 | ...BookingsItemFragment 17 | } 18 | reviews(limit: 5){ 19 | ...ReviewsDefaultFragment 20 | } 21 | `); 22 | 23 | extendFragment('UsersCurrent', ` 24 | rooms(limit: 5){ 25 | ...RoomsItemFragment 26 | } 27 | bookings(limit: 5){ 28 | ...BookingsItemFragment 29 | } 30 | reviews(limit: 5){ 31 | ...ReviewsDefaultFragment 32 | } 33 | `); -------------------------------------------------------------------------------- /packages/zensroom/lib/modules/products.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Products 4 | 5 | http://docs.vulcanjs.org/payments.html 6 | 7 | */ 8 | 9 | import { addProduct } from 'meteor/vulcan:payments'; 10 | import moment from 'moment'; 11 | import Rooms from './rooms/collection'; 12 | 13 | addProduct('booking', booking => { 14 | 15 | const numberOfNights = moment(booking.endAt).diff(moment(booking.startAt), 'days'); 16 | const room = booking.room || Rooms.findOne({_id: booking.roomId}); 17 | const amount = room.pricePerNight * booking.numberOfGuests * numberOfNights * 100; 18 | 19 | return { 20 | name: 'Book this room', 21 | amount, 22 | currency: 'USD', 23 | description: `Book ${room.name}`, 24 | } 25 | }); -------------------------------------------------------------------------------- /packages/zensroom/lib/components/admin/AdminUsersBookings.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Show a user's bookings in the admin users dashboard 4 | 5 | http://docs.vulcanjs.org/admin.html 6 | 7 | */ 8 | 9 | import React from 'react'; 10 | import { Link } from 'react-router'; 11 | import { registerComponent } from 'meteor/vulcan:core'; 12 | 13 | const AdminUsersBookings = ({ document: user }) => 14 | 19 | 20 | registerComponent('AdminUsersBookings', AdminUsersBookings); 21 | 22 | export default AdminUsersBookings; -------------------------------------------------------------------------------- /packages/zensroom/lib/stylesheets/_users.scss: -------------------------------------------------------------------------------- 1 | .accounts-ui{ 2 | .field{ 3 | margin-bottom: $spacing/2; 4 | } 5 | } 6 | 7 | .users-menu{ 8 | .avatar{ 9 | margin-right: 5px; 10 | } 11 | } 12 | 13 | .avatar-initials{ 14 | background: $light-grey; 15 | color: $dark-grey; 16 | text-align: center; 17 | border-radius: 100%; 18 | text-decoration: none; 19 | height: 100%; 20 | display: flex; 21 | justify-content: center; 22 | align-items: center; 23 | span{ 24 | display: block; 25 | line-height: 1; 26 | } 27 | } 28 | 29 | .users-newsletter-settings { 30 | @include flex-center; 31 | margin-top: $spacing; 32 | } 33 | 34 | .log-in-message{ 35 | h3{ 36 | text-align: center; 37 | } 38 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .eslintcache 2 | npm-debug.log 3 | *.scssc 4 | .sass-cache/* 5 | .DS_Store 6 | *-ck.js 7 | providers_secret.js 8 | *.sublime-project 9 | 10 | *.sublime-workspace 11 | codekit-config.json 12 | 13 | config.rb 14 | 15 | deploy.sh 16 | 17 | .demeteorized 18 | 19 | dump/ 20 | dump/* 21 | 22 | settings.json 23 | settings.json.not-used 24 | .idea 25 | 26 | scratch 27 | 28 | .deploy 29 | .deploy/* 30 | ### Meteor template 31 | .meteor/local 32 | .meteor/meteorite 33 | mup.json 34 | 35 | packages_update.py 36 | 37 | versions 38 | 39 | get_file_list.sh 40 | packages_update.py 41 | publish_packages.sh 42 | 43 | node_modules 44 | 45 | bundle.tar.gz 46 | 47 | jsdoc-conf.json 48 | jsdoc.json 49 | packages/nova-router/.npm 50 | npm-debug.log 51 | -------------------------------------------------------------------------------- /packages/zensroom/lib/components/rooms/RoomsReviews.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Show a room's reviews 4 | 5 | */ 6 | 7 | import React from 'react'; 8 | import { Components, registerComponent } from 'meteor/vulcan:core'; 9 | import { FormattedMessage } from 'meteor/vulcan:i18n'; 10 | 11 | // import ReviewsList from '../reviews/ReviewsList'; 12 | // import ReviewsNewForm from '../reviews/ReviewsNewForm'; 13 | 14 | const RoomsReviews = ({ room, currentUser }) => 15 |
16 | 17 | 18 | 19 | {currentUser ? : null} 20 | 21 |
22 | 23 | registerComponent('RoomsReviews', RoomsReviews); 24 | 25 | // export default RoomsReviews; -------------------------------------------------------------------------------- /packages/zensroom/lib/modules/admin.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Extend the users admin dashboard 4 | 5 | http://docs.vulcanjs.org/admin.html 6 | 7 | */ 8 | 9 | import { extendFragment, addAdminColumn } from 'meteor/vulcan:core'; 10 | import AdminUsersRooms from '../components/admin/AdminUsersRooms'; 11 | import AdminUsersBookings from '../components/admin/AdminUsersBookings'; 12 | import AdminUsersReviews from '../components/admin/AdminUsersReviews'; 13 | 14 | addAdminColumn({ 15 | name: 'rooms', 16 | order: 50, 17 | component: AdminUsersRooms 18 | }); 19 | 20 | addAdminColumn({ 21 | name: 'bookings', 22 | order: 60, 23 | component: AdminUsersBookings 24 | }); 25 | 26 | addAdminColumn({ 27 | name: 'reviews', 28 | order: 70, 29 | component: AdminUsersReviews 30 | }); 31 | 32 | -------------------------------------------------------------------------------- /packages/vulcan-maps/lib/server/maps.js: -------------------------------------------------------------------------------- 1 | import googleMaps from '@google/maps'; 2 | import { getSetting } from 'meteor/vulcan:core'; 3 | import Promise from 'bluebird'; 4 | 5 | const googleMapsSetting = getSetting('googlemaps'); 6 | 7 | if (!googleMapsSetting) { 8 | throw new Error('Please fill in your Google Maps API Key or disable the Maps package.'); 9 | } 10 | 11 | const googleMapsClient = googleMaps.createClient({ 12 | key: googleMapsSetting.apiKey 13 | }); 14 | 15 | /* 16 | 17 | Note: Google Maps Node package doesn't support promises natively. 18 | See https://github.com/google/google-api-nodejs-client/issues/197#issuecomment-299569914 19 | 20 | */ 21 | export async function geocode(address) { 22 | const data = await Promise.fromCallback((cb) => googleMapsClient.geocode({ address }, cb)); 23 | return data.json.results[0]; 24 | } -------------------------------------------------------------------------------- /packages/zensroom/lib/components/common/FlashMessages.js: -------------------------------------------------------------------------------- 1 | // /* 2 | 3 | // Show all flash messages 4 | 5 | // */ 6 | 7 | // import { Components, registerComponent, withMessages } from 'meteor/vulcan:core'; 8 | // import React from 'react'; 9 | 10 | // // import Flash from './Flash'; 11 | 12 | // const FlashMessages = ({messages, clear, markAsSeen}) => { 13 | // return ( 14 | //
15 | // {messages 16 | // .filter(message => message.show) 17 | // .map(message => )} 18 | //
19 | // ); 20 | // } 21 | 22 | // FlashMessages.displayName = "FlashMessages"; 23 | 24 | // registerComponent('FlashMessages', FlashMessages, withMessages); 25 | 26 | // export default withMessages(FlashMessages); -------------------------------------------------------------------------------- /packages/zensroom/lib/components/users/UsersSignUp.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Users sign up form 4 | 5 | */ 6 | 7 | import { Components, registerComponent } from 'meteor/vulcan:core'; 8 | import React, { PropTypes, Component } from 'react'; 9 | import { FormattedMessage } from 'meteor/vulcan:i18n'; 10 | import { STATES } from 'meteor/vulcan:accounts'; 11 | import { Link } from 'react-router'; 12 | 13 | const UsersSignUp = ({state}) => 14 | 15 |
16 | 17 |

18 |
19 | 20 | UsersSignUp.displayName = 'UsersSignUp'; 21 | 22 | registerComponent('UsersSignUp', UsersSignUp); 23 | 24 | // export default UsersSignUp; 25 | -------------------------------------------------------------------------------- /packages/zensroom/lib/components/admin/ReviewsDashboard.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Show a list of all reviews 4 | 5 | http://docs.vulcanjs.org/core-components.html#Datatable 6 | 7 | */ 8 | 9 | import React from 'react'; 10 | import { Components, registerComponent } from 'meteor/vulcan:core'; 11 | import compose from 'recompose/compose'; 12 | import { FormattedMessage } from 'meteor/vulcan:i18n'; 13 | 14 | import Reviews from '../../modules/reviews/collection.js'; 15 | 16 | const ReviewsDashboard = () => 17 | 18 |
19 | 20 |

21 | 22 | 27 | 28 |
29 | 30 | registerComponent('ReviewsDashboard', ReviewsDashboard); 31 | 32 | // export default ReviewsDashboard; -------------------------------------------------------------------------------- /packages/zensroom/lib/components/users/UsersLogIn.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Users log in form 4 | 5 | */ 6 | 7 | import { Components, registerComponent } from 'meteor/vulcan:core'; 8 | import React, { PropTypes, Component } from 'react'; 9 | import { FormattedMessage } from 'meteor/vulcan:i18n'; 10 | import Dropdown from 'react-bootstrap/lib/Dropdown'; 11 | import { STATES } from 'meteor/vulcan:accounts'; 12 | import { Link } from 'react-router'; 13 | 14 | const UsersLogIn = ({state}) => 15 | 16 |
17 | 18 |

19 |
20 | 21 | UsersLogIn.displayName = 'UsersLogIn'; 22 | 23 | registerComponent('UsersLogIn', UsersLogIn); 24 | 25 | // export default UsersLogIn; 26 | -------------------------------------------------------------------------------- /packages/zensroom/lib/modules/bookings/collection.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Bookings collection 4 | 5 | http://docs.vulcanjs.org/schemas.html 6 | 7 | */ 8 | 9 | import { createCollection, getDefaultResolvers, getDefaultMutations } from 'meteor/vulcan:core'; 10 | import Users from 'meteor/vulcan:users'; 11 | import schema from './schema.js'; 12 | import './fragments.js'; 13 | import './permissions.js'; 14 | import './resolvers.js'; 15 | 16 | const Bookings = createCollection({ 17 | collectionName: 'Bookings', 18 | typeName: 'Booking', 19 | schema, 20 | resolvers: getDefaultResolvers('Bookings'), 21 | mutations: getDefaultMutations('Bookings'), 22 | }); 23 | 24 | // only admins or the owner can access a booking 25 | Bookings.checkAccess = (currentUser, booking) => { 26 | return Users.isAdmin(currentUser) || Users.owns(currentUser, booking); 27 | } 28 | 29 | export default Bookings; -------------------------------------------------------------------------------- /packages/zensroom/package.js: -------------------------------------------------------------------------------- 1 | Package.describe({ 2 | name: 'zensroom', 3 | }); 4 | 5 | Package.onUse((api) => { 6 | api.use([ 7 | 8 | 'fourseven:scss@4.5.0', 9 | 10 | // vulcan core 11 | 'vulcan:core', 12 | 13 | // vulcan packages 14 | 'vulcan:forms', 15 | 'vulcan:forms-upload', 16 | 'vulcan:accounts', 17 | 'vulcan:payments', 18 | 'vulcan:maps', 19 | 'vulcan:admin', 20 | 21 | ]); 22 | 23 | api.addFiles(['lib/stylesheets/main.scss'], ['client']); 24 | 25 | api.addAssets([ 26 | 'lib/server/emails/common/test.handlebars', 27 | 'lib/server/emails/common/wrapper.handlebars', 28 | 'lib/server/emails/rooms/roomsNew.handlebars', 29 | 'lib/server/emails/bookings/bookingsNew.handlebars', 30 | ], ['server']); 31 | 32 | api.mainModule('lib/server/main.js', 'server'); 33 | api.mainModule('lib/client/main.js', 'client'); 34 | }); -------------------------------------------------------------------------------- /packages/zensroom/lib/containers/withUnavailableDatesContainer.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Container for the UnavailableDates query 4 | 5 | http://docs.vulcanjs.org/data-loading.html#Higher-Order-Components 6 | 7 | */ 8 | 9 | import { graphql } from 'react-apollo'; 10 | import gql from 'graphql-tag'; 11 | 12 | const query = gql` 13 | query unavailableDates($roomId: String) { 14 | UnavailableDates(roomId:$roomId) 15 | } 16 | ` 17 | 18 | const withUnavailableDates = graphql(query, { 19 | 20 | alias: 'withUnavailableDates', 21 | 22 | options({room}) { 23 | return { 24 | variables: { 25 | roomId: room._id, 26 | }, 27 | }; 28 | }, 29 | 30 | props({ data }) { 31 | return { 32 | loading: data.networkStatus === 1, 33 | unavailableDates: data.UnavailableDates 34 | } 35 | } 36 | 37 | }); 38 | 39 | export default withUnavailableDates; 40 | 41 | 42 | -------------------------------------------------------------------------------- /packages/zensroom/lib/modules/bookings/fragments.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Fragments on the Bookings collection 4 | 5 | http://docs.vulcanjs.org/fragments.html 6 | 7 | */ 8 | 9 | import { registerFragment } from 'meteor/vulcan:core'; 10 | 11 | registerFragment(` 12 | fragment BookingsItemFragment on Booking { 13 | 14 | __typename 15 | _id 16 | createdAt 17 | startAt 18 | endAt 19 | paidAt 20 | startAtFormatted 21 | endAtFormatted 22 | paidAtFormatted 23 | startAtFormattedShort 24 | endAtFormattedShort 25 | paidAtFormattedShort 26 | numberOfGuests 27 | status 28 | amount 29 | pageUrl 30 | 31 | user { 32 | _id 33 | displayName 34 | emailHash 35 | slug 36 | pageUrl 37 | } 38 | 39 | room { 40 | _id 41 | pricePerNight 42 | name 43 | photos 44 | pageUrl 45 | } 46 | 47 | } 48 | `); 49 | -------------------------------------------------------------------------------- /packages/zensroom/lib/modules/reviews/schema.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Reviews schema 4 | 5 | http://docs.vulcanjs.org/schemas.html#Schemas 6 | 7 | */ 8 | 9 | const schema = { 10 | // default properties 11 | 12 | _id: { 13 | type: String, 14 | optional: true, 15 | viewableBy: ['guests'], 16 | }, 17 | createdAt: { 18 | type: Date, 19 | optional: true, 20 | viewableBy: ['guests'], 21 | onInsert: (document, currentUser) => { 22 | return new Date(); 23 | } 24 | }, 25 | userId: { 26 | type: String, 27 | optional: true, 28 | viewableBy: ['guests'], 29 | }, 30 | 31 | roomId: { 32 | type: String, 33 | viewableBy: ['guests'], 34 | insertableBy: ['members'], 35 | hidden: true, 36 | }, 37 | 38 | comment: { 39 | label: 'Comment', 40 | type: String, 41 | optional: false, 42 | viewableBy: ['guests'], 43 | insertableBy: ['members'], 44 | editableBy: ['members'], 45 | control: 'textarea' 46 | }, 47 | 48 | }; 49 | 50 | export default schema; -------------------------------------------------------------------------------- /packages/zensroom/lib/components/users/UsersAccountMenu.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | User menu (when not logged in) 4 | 5 | */ 6 | 7 | import { Components, registerComponent } from 'meteor/vulcan:core'; 8 | import React, { PropTypes, Component } from 'react'; 9 | import { FormattedMessage } from 'meteor/vulcan:i18n'; 10 | import Dropdown from 'react-bootstrap/lib/Dropdown'; 11 | import { STATES } from 'meteor/vulcan:accounts'; 12 | 13 | const UsersAccountMenu = ({state}) => 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | UsersAccountMenu.displayName = "UsersAccountMenu"; 26 | 27 | registerComponent('UsersAccountMenu', UsersAccountMenu); 28 | 29 | // export default UsersAccountMenu; 30 | -------------------------------------------------------------------------------- /packages/zensroom/lib/modules/bookings/resolvers.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Resolver to get the list of unavailable dates for a given room 4 | 5 | http://docs.vulcanjs.org/data-loading.html#Custom-Queries 6 | 7 | */ 8 | 9 | import { addGraphQLResolvers, addGraphQLQuery } from 'meteor/vulcan:core'; 10 | import moment from 'moment'; 11 | 12 | const unavailableDatesResolver = { 13 | Query: { 14 | UnavailableDates (root, { roomId }, { Rooms, Bookings, currentUser }) { 15 | const bookings = Bookings.find({ roomId, paidAt: {$exists: true} }).fetch(); 16 | let dates = []; 17 | bookings.forEach(booking => { 18 | const mStart = moment(booking.startAt); 19 | const mEnd = moment(booking.endAt); 20 | const duration = moment.duration(mEnd.diff(mStart)).as('days'); 21 | for(i = 0; i <= duration; i++) { 22 | dates.push(mStart.add(1, 'days').toDate()) 23 | } 24 | }); 25 | return dates; 26 | } 27 | } 28 | }; 29 | addGraphQLResolvers(unavailableDatesResolver); 30 | 31 | addGraphQLQuery(`UnavailableDates(roomId: String): [Date]`); 32 | -------------------------------------------------------------------------------- /packages/zensroom/lib/modules/bookings/views.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Bookings views 4 | 5 | http://docs.vulcanjs.org/terms-parameters.html#Using-Views 6 | 7 | */ 8 | 9 | import Bookings from './collection.js'; 10 | import moment from 'moment'; 11 | 12 | Bookings.addView('userBookings', terms => ({ 13 | selector: { 14 | userId: terms.userId, 15 | roomId: terms.roomId, 16 | paidAt: {$exists: true} 17 | } 18 | })); 19 | 20 | Bookings.addView('userPendingBookings', terms => ({ 21 | selector: { 22 | userId: terms.userId, 23 | status: 1 24 | } 25 | })); 26 | 27 | Bookings.addView('userBookingsPast', terms => ({ 28 | selector: { 29 | userId: terms.userId, 30 | endAt: {$lt: new Date()} 31 | } 32 | })); 33 | 34 | Bookings.addView('userBookingsCurrent', terms => ({ 35 | selector: { 36 | userId: terms.userId, 37 | startAt: {$lt: new Date()}, 38 | endAt: {$gt: new Date()} 39 | } 40 | })); 41 | 42 | Bookings.addView('userBookingsFuture', terms => ({ 43 | selector: { 44 | userId: terms.userId, 45 | startAt: {$gt: new Date()} 46 | } 47 | })); 48 | -------------------------------------------------------------------------------- /packages/zensroom/lib/modules/reviews/check.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Check if a user can perform the "new" mutation 4 | 5 | http://docs.vulcanjs.org/mutations.html#New-Mutation 6 | 7 | */ 8 | 9 | import moment from 'moment'; 10 | import Users from 'meteor/vulcan:users'; 11 | 12 | export const newCheck = (user, document) => { 13 | // if user is not logged in, disallow operation 14 | if (!user) return false; 15 | 16 | /* 17 | 18 | else, check if: 19 | 20 | - they can perform the "reviews.new" operation 21 | - they have booked the associated room in the past 22 | - they haven't already left a review 23 | 24 | */ 25 | 26 | const hasBookedRoom = _.some(user.bookings, booking => booking.roomId === document.roomId && moment().isBefore(moment(booking.endAt))); 27 | const hasLeftAReview = _.some(user.reviews, review => review.roomId === document.roomId); 28 | 29 | // console.log(user) 30 | // console.log(document) 31 | // console.log(hasBookedRoom) 32 | // console.log(hasLeftAReview) 33 | 34 | return Users.canDo(user, `reviews.new`) && hasBookedRoom && !hasLeftAReview; 35 | } -------------------------------------------------------------------------------- /packages/zensroom/lib/components/bookings/BookingsPending.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Components, registerComponent, withList, withCurrentUser, withDocument } from 'meteor/vulcan:core'; 3 | import { Link } from 'react-router'; 4 | import { Alert } from 'react-bootstrap'; 5 | import compose from 'recompose/compose'; 6 | 7 | import Bookings from '../../modules/bookings/collection.js'; 8 | 9 | const BookingsPending = ({loading, results}) => 10 |
11 | {results && results.length ? 12 | 13 | {results.map((booking) => ( 14 |
15 | Complete your booking of {booking.room.name}. 16 |
17 | ))} 18 |
: 19 | null 20 | } 21 |
; 22 | 23 | 24 | const options = { 25 | collection: Bookings, 26 | fragmentName: 'BookingsItemFragment' 27 | }; 28 | 29 | registerComponent('BookingsPending', BookingsPending, [withList, options]); 30 | 31 | // export default compose( 32 | // withList(options), 33 | // )(BookingsPending); 34 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Telescope Nova 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. 22 | -------------------------------------------------------------------------------- /packages/zensroom/lib/components/admin/RoomsDashboard.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Show a list of all rooms 4 | 5 | http://docs.vulcanjs.org/core-components.html#Datatable 6 | 7 | */ 8 | 9 | import React from 'react'; 10 | import { Components, registerComponent } from 'meteor/vulcan:core'; 11 | import compose from 'recompose/compose'; 12 | import { FormattedMessage } from 'meteor/vulcan:i18n'; 13 | 14 | import Rooms from '../../modules/rooms/collection.js'; 15 | 16 | const RoomPhotos = ({ document }) => 17 |
18 | {document.photos.map((photo) => )} 19 |
; 20 | 21 | const RoomsDashboard = () => 22 | 23 |
24 | 25 |

26 | 27 | 39 | 40 |
41 | 42 | registerComponent('RoomsDashboard', RoomsDashboard); 43 | 44 | // export default RoomsDashboard; 45 | -------------------------------------------------------------------------------- /packages/zensroom/lib/components/rooms/RoomsItem.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Room list item 4 | 5 | */ 6 | 7 | import React from 'react'; 8 | import { Components, registerComponent, getSetting } from 'meteor/vulcan:core'; 9 | import { Link } from 'react-router'; 10 | import { FormattedMessage } from 'meteor/vulcan:i18n'; 11 | 12 | const RoomsItem = ({room, currentUser}) => 13 | 14 |
15 | 16 |
17 | 18 | {room.photos && room.photos.length ? : null} 19 |
{getSetting('defaultCurrency', '$')}{room.pricePerNight}
20 | 21 |
22 | 23 |
24 | 25 |

{room.name}

26 |

{room.city}

27 | 28 |
29 | 30 |
31 | 32 | registerComponent('RoomsItem', RoomsItem); 33 | 34 | // export default RoomsItem; -------------------------------------------------------------------------------- /packages/zensroom/lib/stylesheets/_admin.scss: -------------------------------------------------------------------------------- 1 | .datatable{ 2 | .avatar{ 3 | display: inline-block; 4 | width: 20px; 5 | height: 20px; 6 | margin-right: 5px; 7 | vertical-align: middle; 8 | a, img{ 9 | border-radius: 100%; 10 | display: block; 11 | width: 100%; 12 | } 13 | } 14 | } 15 | 16 | .datatable-search{ 17 | margin-bottom: $spacing/2; 18 | max-width: 200px; 19 | } 20 | 21 | ////////////////////////////////////////////////////// 22 | // Bookings // 23 | ////////////////////////////////////////////////////// 24 | 25 | .bookings-dashboard-room{ 26 | img{ 27 | display: inline-block; 28 | max-width: 60px; 29 | margin-right: 10px; 30 | } 31 | span{ 32 | 33 | } 34 | } 35 | 36 | ////////////////////////////////////////////////////// 37 | // Photos // 38 | ////////////////////////////////////////////////////// 39 | 40 | .datatable-item-photos, .datatable-item-Photos{ 41 | min-width: 300px; 42 | img{ 43 | display: inline-block; 44 | max-width: 60px; 45 | margin-right: 10px; 46 | margin-bottom: 10px; 47 | } 48 | } -------------------------------------------------------------------------------- /packages/zensroom/lib/components/bookings/BookingsFuture.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Components, registerComponent, withCurrentUser } from 'meteor/vulcan:core'; 3 | import { FormattedMessage } from 'meteor/vulcan:i18n'; 4 | import compose from 'recompose/compose'; 5 | import { Link } from 'react-router'; 6 | 7 | import Bookings from '../../modules/bookings/collection.js'; 8 | 9 | const BookingsDate = ({ document }) => 10 |
11 | From {document.startAtFormattedShort} to {document.endAtFormattedShort} 12 |
13 | 14 | const BookingsFuture = ({ currentUser }) => 15 |
16 | 31 |
; 32 | 33 | 34 | registerComponent('BookingsFuture', BookingsFuture, withCurrentUser); 35 | 36 | // export default compose( 37 | // withCurrentUser, 38 | // )(BookingsFuture); 39 | -------------------------------------------------------------------------------- /packages/zensroom/lib/components/bookings/BookingsCurrent.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Components, registerComponent, withCurrentUser } from 'meteor/vulcan:core'; 3 | import { FormattedMessage } from 'meteor/vulcan:i18n'; 4 | import compose from 'recompose/compose'; 5 | import { Link } from 'react-router'; 6 | 7 | import Bookings from '../../modules/bookings/collection.js'; 8 | 9 | const BookingsDate = ({ document }) => 10 |
11 | From {document.startAtFormattedShort} to {document.endAtFormattedShort} 12 |
13 | 14 | const BookingsCurrent = ({ currentUser }) => 15 |
16 | 31 |
; 32 | 33 | 34 | registerComponent('BookingsCurrent', BookingsCurrent, withCurrentUser); 35 | 36 | // export default compose( 37 | // withCurrentUser, 38 | // )(BookingsCurrent); 39 | -------------------------------------------------------------------------------- /packages/zensroom/lib/stylesheets/_spinner.scss: -------------------------------------------------------------------------------- 1 | 2 | .spinner { 3 | height: 10px; 4 | @include flex-center; 5 | justify-content: center; 6 | div { 7 | width: 10px; 8 | height: 10px; 9 | background-color: rgba(0,0,0,0.35); 10 | 11 | border-radius: 100%; 12 | display: inline-block; 13 | -webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both; 14 | animation: sk-bouncedelay 1.4s infinite ease-in-out both; 15 | margin-right: $spacing/2; 16 | &:last-child{ 17 | margin-right: 0; 18 | } 19 | } 20 | &.white div{ 21 | background-color: rgba(255,255,255,0.55); 22 | } 23 | .bounce1 { 24 | -webkit-animation-delay: -0.32s; 25 | animation-delay: -0.32s; 26 | } 27 | 28 | .bounce2 { 29 | -webkit-animation-delay: -0.16s; 30 | animation-delay: -0.16s; 31 | } 32 | } 33 | 34 | @-webkit-keyframes sk-bouncedelay { 35 | 0%, 80%, 100% { -webkit-transform: scale(0) } 36 | 40% { -webkit-transform: scale(1.0) } 37 | } 38 | 39 | @keyframes sk-bouncedelay { 40 | 0%, 80%, 100% { 41 | -webkit-transform: scale(0); 42 | transform: scale(0); 43 | } 40% { 44 | -webkit-transform: scale(1.0); 45 | transform: scale(1.0); 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /packages/zensroom/lib/components/bookings/BookingsPast.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Components, registerComponent, withCurrentUser } from 'meteor/vulcan:core'; 3 | import { FormattedMessage } from 'meteor/vulcan:i18n'; 4 | import compose from 'recompose/compose'; 5 | import { Link } from 'react-router'; 6 | 7 | import Bookings from '../../modules/bookings/collection.js'; 8 | 9 | const BookingsDate = ({ document }) => 10 |
11 | From {document.startAtFormattedShort} to {document.endAtFormattedShort} 12 |
13 | 14 | const BookingsPast = ({currentUser}) => 15 |
16 | 31 |
; 32 | 33 | 34 | registerComponent('BookingsPast', BookingsPast, withCurrentUser); 35 | 36 | // export default compose( 37 | // withCurrentUser, 38 | // )(BookingsPast); 39 | -------------------------------------------------------------------------------- /packages/zensroom/lib/components/common/Home.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Home 4 | 5 | */ 6 | 7 | import React from 'react'; 8 | import { Components, registerComponent, withCurrentUser } from 'meteor/vulcan:core'; 9 | import { FormattedMessage } from 'meteor/vulcan:i18n'; 10 | import compose from 'recompose/compose'; 11 | 12 | 13 | // import RoomsList from '../rooms/RoomsList'; 14 | // import RoomsSearchForm from '../rooms/RoomsSearchForm'; 15 | 16 | const Home = ({currentUser}) => 17 | 18 |
19 | {currentUser ? : null} 20 | 21 | 22 |
23 |

24 | 25 |
26 | 27 |
28 |

29 | 30 |
31 | 32 |
33 | 34 | registerComponent('Home', Home, withCurrentUser); 35 | 36 | // export default compose( 37 | // withCurrentUser, 38 | // )(Home); 39 | -------------------------------------------------------------------------------- /packages/zensroom/lib/stylesheets/_header.scss: -------------------------------------------------------------------------------- 1 | .header{ 2 | @include flex-center; 3 | justify-content: space-between; 4 | padding: $spacing; 5 | border-bottom: 1px solid #eee; 6 | margin-bottom: $spacing; 7 | } 8 | 9 | .logo{ 10 | display: inline-block; 11 | margin-right: 5px; 12 | h1{ 13 | font-size: 2rem; 14 | } 15 | } 16 | 17 | .tagline{ 18 | display: inline-block; 19 | } 20 | 21 | .nav{ 22 | @include flex-center; 23 | } 24 | 25 | .nav-item{ 26 | margin-right: $spacing; 27 | &:last-child{ 28 | margin: 0; 29 | } 30 | } 31 | 32 | 33 | .users-account-menu, .users-menu{ 34 | .avatar{ 35 | width: 20px; 36 | height: 20px; 37 | img{ 38 | display: block; 39 | width: 100%; 40 | height: 100%; 41 | } 42 | } 43 | .dropdown-toggle{ 44 | @include flex-center; 45 | padding: 0; 46 | background: none; 47 | img{ 48 | margin-right: 5px; 49 | } 50 | } 51 | } 52 | 53 | .accounts-ui{ 54 | padding: $spacing; 55 | min-width: 240px; 56 | .buttons{ 57 | .btn{ 58 | display: block; 59 | width: 100%; 60 | text-align: center; 61 | margin-bottom: $spacing; 62 | &:last-child{ 63 | margin-bottom: 0; 64 | } 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /packages/vulcan-maps/lib/components/Map.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Components, registerComponent, getSetting } from 'meteor/vulcan:core'; 4 | import GoogleMap from 'google-map-react'; 5 | 6 | const Marker = () => ( 7 |
8 | ); 9 | 10 | class Map extends Component { 11 | 12 | static defaultProps = { 13 | zoom: 15 14 | }; 15 | 16 | constructor(props) { 17 | super(props); 18 | } 19 | 20 | render() { 21 | return ( 22 |
23 | 31 | {this.props.coordinates.map((coord, index) => )} 32 | 33 | 34 |
35 | ) 36 | } 37 | } 38 | 39 | Map.propTypes = { 40 | zoom: PropTypes.number, 41 | } 42 | 43 | registerComponent('Map', Map); 44 | -------------------------------------------------------------------------------- /jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | // Execute meteor-jsdoc in debug mode to track down errors. 3 | "debug": true, 4 | // node.js install path, default to: "`which node`" on Mac and Linux, "`where node`" on Windows 5 | "nodePath": "", 6 | // Project docs path 7 | "docsPath": "~/Dev/nova-docs", 8 | // Project docs Meteor server port, default to: 3333 9 | "meteorPort": 3333, 10 | // Copy the Meteor docs server before building the docs (required for the first build) 11 | // Setting this to false after the first build allows you to customize the docs templates 12 | // without seeing your changes overwritten the next time you build the docs. 13 | "initMeteor": true, 14 | // Update Meteor without overwriting your changes to the docs templates. 15 | "updateMeteor": true, 16 | // Add a preamble to your project's docs that will appear at the top of the docs. 17 | "preamble": true, 18 | // Link to the project repository (used to construct the file path in the docs). Optional. 19 | "projectRepo": "https://github.com/TelescopeJS/Telescope/tree/nova", 20 | // Values to be used in the `` for the docs. 21 | "docsConfig": { 22 | "title": "Nova Docs", 23 | "metas": { 24 | "description": "Documentation for Telescope Nova." 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/zensroom/lib/stylesheets/_accounts.scss: -------------------------------------------------------------------------------- 1 | .users-account-menu{ 2 | .icon{ 3 | display: none; 4 | } 5 | } 6 | 7 | .accounts-ui{ 8 | .buttons{ 9 | a{ 10 | display:block; 11 | text-align: center; 12 | margin-bottom: $spacing; 13 | } 14 | } 15 | .password-or-service{ 16 | text-align: center; 17 | margin-bottom: $spacing; 18 | } 19 | .social-buttons{ 20 | @include flex-center; 21 | justify-content: space-between; 22 | button{ 23 | margin-right: $spacing; 24 | width: 100%; 25 | &.btn-twitter{ 26 | background: #55acee; 27 | border-color: #55acee; 28 | } 29 | &.btn-facebook{ 30 | background: #3b5998; 31 | border-color: #3b5998; 32 | } 33 | &:last-child{ 34 | margin-right: 0 !important; 35 | } 36 | } 37 | } 38 | input.form-control{ 39 | font-size: 0.9rem; 40 | } 41 | .warning, .error{ 42 | color: #a94442; 43 | background: #f2dede; 44 | border: 1px solid #ebcccc; 45 | padding: $spacing/4 $spacing/2; 46 | border-radius: 3px; 47 | display: block; 48 | margin-top: $spacing/4; 49 | } 50 | } 51 | 52 | .password-reset-form{ 53 | text-align: center; 54 | } 55 | 56 | .change-password-link{ 57 | margin-bottom: $spacing; 58 | } 59 | -------------------------------------------------------------------------------- /packages/zensroom/lib/components/common/Flash.js: -------------------------------------------------------------------------------- 1 | // /* 2 | 3 | // Single Flash message. 4 | 5 | // */ 6 | 7 | // import { registerComponent } from 'meteor/vulcan:core'; 8 | // import React, { PureComponent } from 'react'; 9 | // import PropTypes from 'prop-types'; 10 | // import Alert from 'react-bootstrap/lib/Alert' 11 | 12 | // class Flash extends PureComponent { 13 | 14 | // constructor() { 15 | // super(); 16 | // this.dismissFlash = this.dismissFlash.bind(this); 17 | // } 18 | 19 | // componentDidMount() { 20 | // this.props.markAsSeen(this.props.message._id); 21 | // } 22 | 23 | // dismissFlash(e) { 24 | // e.preventDefault(); 25 | // this.props.clear(this.props.message._id); 26 | // } 27 | 28 | // render() { 29 | 30 | // let flashType = this.props.message.flashType; 31 | // flashType = flashType === "error" ? "danger" : flashType; // if flashType is "error", use "danger" instead 32 | 33 | // return ( 34 | // 35 | // {this.props.message.content} 36 | // 37 | // ) 38 | // } 39 | // } 40 | 41 | // Flash.propTypes = { 42 | // message: PropTypes.object.isRequired 43 | // } 44 | 45 | // registerComponent('Flash', Flash); 46 | 47 | // export default Flash; 48 | -------------------------------------------------------------------------------- /packages/zensroom/lib/components/common/Layout.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Layout 4 | 5 | */ 6 | 7 | import { Components, replaceComponent, withCurrentUser } from 'meteor/vulcan:core'; 8 | import React, { PropTypes, Component } from 'react'; 9 | import classNames from 'classnames'; 10 | import Helmet from 'react-helmet'; 11 | 12 | // import Header from './Header'; 13 | // import FlashMessages from './FlashMessages'; 14 | // import Footer from './Footer'; 15 | 16 | const Layout = ({currentUser, children, currentRoute}) => 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 | 31 | {React.cloneElement(children, { currentUser })} 32 | 33 |
34 | 35 | 36 | 37 |
38 | 39 | replaceComponent('Layout', Layout); 40 | 41 | // export default withCurrentUser(Layout); 42 | -------------------------------------------------------------------------------- /packages/zensroom/lib/components/common/AdminLayout.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Layout 4 | 5 | */ 6 | 7 | import { Components, registerComponent, withCurrentUser } from 'meteor/vulcan:core'; 8 | import React, { PropTypes, Component } from 'react'; 9 | import classNames from 'classnames'; 10 | import Helmet from 'react-helmet'; 11 | 12 | // import Header from './Header'; 13 | // import FlashMessages from './FlashMessages'; 14 | // import Footer from './Footer'; 15 | 16 | const AdminLayout = ({currentUser, children, currentRoute}) => 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 | 31 | {React.cloneElement(children, { currentUser })} 32 | 33 |
34 | 35 | 36 | 37 |
38 | 39 | registerComponent('AdminLayout', AdminLayout, withCurrentUser); 40 | 41 | // export default withCurrentUser(Layout); 42 | -------------------------------------------------------------------------------- /packages/zensroom/lib/components/reviews/ReviewsList.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | List of reviews, wrapped with withList 4 | 5 | http://docs.vulcanjs.org/data-loading.html#List-Resolver 6 | 7 | */ 8 | 9 | import React from 'react'; 10 | import { Components, registerComponent, withList, withCurrentUser, Loading } from 'meteor/vulcan:core'; 11 | import { FormattedMessage } from 'meteor/vulcan:i18n'; 12 | 13 | import Reviews from '../../modules/reviews/collection'; 14 | 15 | import ReviewsItem from './ReviewsItem'; 16 | 17 | const ReviewsList = ({results = [], currentUser, loading, loadMore, count, totalCount}) => 18 | 19 |
20 | 21 | {loading ? 22 | 23 | : 24 | 25 |
26 | 27 |

28 | 29 | {results.map(review => )} 30 | 31 | {totalCount > results.length ? 32 | {e.preventDefault(); loadMore();}}>Load More ({count}/{totalCount}) 33 | : null} 34 | 35 |
36 | } 37 | 38 |
39 | 40 | const options = { 41 | collection: Reviews 42 | }; 43 | 44 | registerComponent('ReviewsList', ReviewsList, [withList, options], withCurrentUser); 45 | 46 | // export default withList(options)(withCurrentUser(ReviewsList)); -------------------------------------------------------------------------------- /packages/zensroom/lib/components/rooms/RoomsList.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Room list, wrapped with withList 4 | 5 | http://docs.vulcanjs.org/data-loading.html#List-Resolver 6 | 7 | */ 8 | 9 | import React from 'react'; 10 | import { Components, registerComponent, withList, withCurrentUser } from 'meteor/vulcan:core'; 11 | import { FormattedMessage } from 'meteor/vulcan:i18n'; 12 | 13 | import Rooms from '../../modules/rooms/collection'; 14 | // import RoomsItem from './RoomsItem'; 15 | 16 | const RoomsList = ({results = [], currentUser, loading, loadMore, count, totalCount}) => 17 | 18 |
19 | 20 | {loading ? 21 | 22 | : 23 | 24 |
25 | 26 |
27 | {results.map(room => )} 28 |
29 | 30 | {totalCount > results.length ? 31 | {e.preventDefault(); loadMore();}}> ({count}/{totalCount}) 32 | : null} 33 | 34 |
35 | } 36 | 37 |
38 | 39 | const options = { 40 | collection: Rooms 41 | }; 42 | 43 | registerComponent('RoomsList', RoomsList, [withList, options], withCurrentUser); 44 | 45 | // export default withList(options)(withCurrentUser(RoomsList)); -------------------------------------------------------------------------------- /packages/zensroom/lib/modules/data.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Static data 4 | 5 | */ 6 | 7 | export const amenities = [ 8 | { value: 'essentials', label: 'Essentials (towels, bed sheets, etc.)' }, 9 | { value: 'wifi', label: 'Wifi' }, 10 | { value: 'pocket-wifi', label: 'Pocket Wifi' }, 11 | { value: 'shampoo', label: 'Shampoo' }, 12 | { value: 'closet-drawers', label: 'Closet/drawers' }, 13 | { value: 'tv', label: 'TV' }, 14 | { value: 'ac', label: 'Air Conditioning' }, 15 | { value: 'breakfast', label: 'Breakfast, coffee, tea' }, 16 | { value: 'desk', label: 'Desk/workspace' }, 17 | { value: 'fireplace', label: 'Fireplace' }, 18 | { value: 'iron', label: 'Iron' }, 19 | { value: 'hair-dryer', label: 'Hair dryer' }, 20 | { value: 'pets', label: 'Pets in the house' }, 21 | { value: 'private-entrance', label: 'Private entrance' }, 22 | { value: 'washing-machine', label: 'Washing Machine' }, 23 | { value: 'internet', label: 'Internet' }, 24 | ] 25 | 26 | export const spaces = [ 27 | { value: 'living', label: 'Private living room' }, 28 | { value: 'pool', label: 'Pool' }, 29 | { value: 'kitchen', label: 'Kitchen' }, 30 | { value: 'washer', label: 'Laundry - washer' }, 31 | { value: 'dryer', label: 'Laundry - dryer' }, 32 | { value: 'parking', label: 'Parking' }, 33 | { value: 'elevator', label: 'Elevator' }, 34 | { value: 'hot-tub', label: 'Hot tub' }, 35 | { value: 'gym', label: 'Gym' }, 36 | ] -------------------------------------------------------------------------------- /packages/zensroom/lib/components/reviews/ReviewsNewForm.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Form for inserting a new review 4 | 5 | http://docs.vulcanjs.org/forms.html 6 | 7 | */ 8 | 9 | import React from 'react'; 10 | import { Components, registerComponent, withCurrentUser, getFragment, withMessages } from 'meteor/vulcan:core'; 11 | import compose from 'recompose/compose'; 12 | import { intlShape, FormattedMessage } from 'meteor/vulcan:i18n'; 13 | 14 | import Reviews from '../../modules/reviews/collection.js'; 15 | 16 | const ReviewsNewForm = ({roomId, currentUser, router, flash}, {intl}) => 17 | 18 |
19 | {Reviews.options.mutations.new.check(currentUser, { roomId }) ? 20 | 21 |
22 |
23 | { 28 | flash(intl.formatMessage({id: 'reviews.created'}), 'success'); 29 | }} 30 | /> 31 |
: 32 | null 33 | } 34 |
35 | 36 | ReviewsNewForm.contextTypes = { 37 | intl: intlShape 38 | }; 39 | 40 | registerComponent('ReviewsNewForm', ReviewsNewForm, withMessages, withCurrentUser); 41 | 42 | // export default compose( 43 | // withMessages, 44 | // withCurrentUser 45 | // )(ReviewsNewForm); -------------------------------------------------------------------------------- /packages/zensroom/lib/components/common/Header.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Header 4 | 5 | */ 6 | 7 | import React from 'react'; 8 | import PropTypes from 'prop-types'; 9 | import { Components, registerComponent, withCurrentUser, getSetting } from 'meteor/vulcan:core'; 10 | import { Link } from 'react-router'; 11 | import Button from 'react-bootstrap/lib/Button'; 12 | import { FormattedMessage } from 'meteor/vulcan:i18n'; 13 | 14 | // import UsersMenu from '../users/UsersMenu'; 15 | // import UsersAccountMenu from '../users/UsersAccountMenu'; 16 | 17 | const Header = ({ currentUser }, context) => 18 |
19 | 20 |
21 |

{getSetting('title')}

22 |
{getSetting('tagline')}
23 |
24 | 25 |
26 | 27 | 28 | 29 | 30 |
31 | {currentUser ? : } 32 |
33 | 34 |
35 |
36 | 37 | Header.displayName = 'Header'; 38 | 39 | Header.propTypes = { 40 | currentUser: PropTypes.object, 41 | }; 42 | 43 | registerComponent('Header', Header, withCurrentUser); 44 | 45 | // export default withCurrentUser(Header); 46 | -------------------------------------------------------------------------------- /packages/zensroom/lib/components/rooms/RoomsNewForm.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Form for inserting a new room 4 | 5 | http://docs.vulcanjs.org/forms.html 6 | 7 | */ 8 | 9 | import React from 'react'; 10 | import { Components, registerComponent, withCurrentUser, getFragment, withMessages } from 'meteor/vulcan:core'; 11 | import { withRouter } from 'react-router'; 12 | import compose from 'recompose/compose'; 13 | import { intlShape, FormattedMessage } from 'meteor/vulcan:i18n'; 14 | 15 | import Rooms from '../../modules/rooms/collection.js'; 16 | 17 | const RoomsNewForm = ({currentUser, router, flash}, {intl}) => 18 | 19 |
20 | 21 | {Rooms.options.mutations.new.check(currentUser) ? 22 |
23 | { 27 | router.push({pathname: `/room/${room._id}`}); 28 | flash(intl.formatMessage({id: 'rooms.created'}), 'success'); 29 | }} 30 | /> 31 |
: 32 | null 33 | } 34 | 35 |
36 | 37 | RoomsNewForm.contextTypes = { 38 | intl: intlShape 39 | }; 40 | 41 | registerComponent('RoomsNewForm', RoomsNewForm, withRouter, withMessages, withCurrentUser); 42 | 43 | // export default compose( 44 | // withRouter, 45 | // withMessages, 46 | // withCurrentUser 47 | // )(RoomsNewForm); -------------------------------------------------------------------------------- /packages/zensroom/lib/components/bookings/BookingsRoomUser.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Show a user's bookings for a given room. Wrapped with withList. 4 | 5 | Example: 6 | 7 | 8 | 9 | http://docs.vulcanjs.org/data-loading.html#List-Resolver 10 | 11 | */ 12 | 13 | import React from 'react'; 14 | import { Components, registerComponent, withList, withCurrentUser } from 'meteor/vulcan:core'; 15 | import compose from 'recompose/compose'; 16 | import { FormattedMessage } from 'meteor/vulcan:i18n'; 17 | 18 | import Bookings from '../../modules/bookings/collection.js'; 19 | 20 | const BookingsRoomUser = ({loading, results }) => 21 | 22 |
23 | 24 |

25 | 26 | {loading ? : 27 | 28 |
29 | 30 | {results.length ?
:
} 31 | {results.map(booking => )} 32 | 33 |
34 | } 35 | 36 |
37 | 38 | const options = { 39 | collection: Bookings 40 | } 41 | 42 | registerComponent('BookingsRoomUser', BookingsRoomUser, [withList, options]); 43 | 44 | // export default compose( 45 | // withList(options), 46 | // )(BookingsRoomUser); -------------------------------------------------------------------------------- /prestart_vulcan.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if tput setaf 1 &> /dev/null; then 4 | purple=$(tput setaf 141) 5 | blue=$(tput setaf 153) 6 | bold=$(tput bold) 7 | reset=$(tput sgr0) 8 | else 9 | purple="" 10 | blue="" 11 | bold="" 12 | reset="" 13 | fi 14 | 15 | command -v meteor >/dev/null 2>&1 || { 16 | echo "Vulcan requires Meteor but it's not installed. Trying to Install..." >&2; 17 | 18 | if [ "$(uname)" == "Darwin" ]; then 19 | # Mac OS platform 20 | echo "🌋 ${bold}${purple}Good news you have a Mac and we will install it now! ${reset}"; 21 | curl https://install.meteor.com/ | bash; 22 | elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then 23 | # GNU/Linux platform 24 | echo "🌋 ${bold}${purple}Good news you are on GNU/Linux platform and we will install Meteor now! ${reset}"; 25 | curl https://install.meteor.com/ | bash; 26 | elif [ "$(expr substr $(uname -s) 1 10)" == "MINGW32_NT" ]; then 27 | # Windows NT platform 28 | echo "🌋 ${bold}${purple}Oh no! you are on a Windows platform and you will need to install Meteor Manually! ${reset}"; 29 | echo "📖 ${blue}Meteor for Windows is available at: ${purple}https://install.meteor.com/windows"; 30 | exit; 31 | fi 32 | 33 | } 34 | 35 | 36 | test -f settings.json || (echo "🛠 ${blue}Creating your own settings.json file...\n"; cp sample_settings.json settings.json;) 37 | 38 | echo "🌋 ${bold}${purple}Happy hacking with Vulcan!${reset}"; 39 | 40 | echo "📖 ${blue}The docs are available at: ${purple}http://docs.vulcanjs.org"; 41 | 42 | if tput setaf 1 &> /dev/null; then 43 | tput sgr0; 44 | fi 45 | -------------------------------------------------------------------------------- /packages/zensroom/lib/components/rooms/RoomsMain.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Main column of the RoomsPage component 4 | 5 | */ 6 | 7 | import React from 'react'; 8 | import { FormattedMessage } from 'meteor/vulcan:i18n'; 9 | import { Components, registerComponent } from 'meteor/vulcan:core'; 10 | 11 | import Rooms from '../../modules/rooms/collection'; 12 | 13 | // import RoomsReviews from './RoomsReviews'; 14 | 15 | const RoomsMain = ({ room, currentUser }) => 16 | 17 |
18 | 19 |
20 | 21 |

{room.name}

22 |

{room.city}

23 | 24 |
{room.description}
25 | 26 | {room.amenities ?
    {room.amenities.map(amenity =>
  • {amenity}
  • )}
: null} 27 | 28 | 29 | 30 |
31 | 32 |
33 | {room.location ? : null} 34 |
35 | 36 |
37 | 38 |
39 | 40 |
41 | 42 | registerComponent('RoomsMain', RoomsMain); 43 | 44 | // export default RoomsMain; -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "plugin:meteor/recommended", 5 | "plugin:react/recommended" 6 | ], 7 | "parser": "babel-eslint", 8 | "parserOptions": { 9 | "allowImportExportEverywhere": true, 10 | "ecmaVersion": 6, 11 | "sourceType": "module" 12 | }, 13 | "rules": { 14 | "babel/generator-star-spacing": 0, 15 | "babel/new-cap": [1, { 16 | "capIsNewExceptions": [ 17 | "Optional", 18 | "OneOf", 19 | "Maybe", 20 | "MailChimpAPI", 21 | "Juice", 22 | "Run", 23 | "AppComposer", 24 | "Query", 25 | ] 26 | }], 27 | "babel/array-bracket-spacing": 0, 28 | "babel/object-curly-spacing": 0, 29 | "babel/object-shorthand": 0, 30 | "babel/arrow-parens": 0, 31 | "babel/no-await-in-loop": 1, 32 | "comma-dangle": 0, 33 | "key-spacing": 0, 34 | "no-extra-boolean-cast": 0, 35 | "no-undef": 1, 36 | "no-unused-vars": [1, { 37 | "vars": "all", 38 | "args": "none", 39 | "varsIgnorePattern": "React|PropTypes|Component" 40 | }], 41 | "no-console": 1, 42 | "react/prop-types": 0, 43 | "meteor/audit-argument-checks": 0, 44 | "no-case-declarations": 0 45 | }, 46 | "env": { 47 | "browser": true, 48 | "commonjs": true, 49 | "es6": true, 50 | "meteor": true, 51 | "node": true 52 | }, 53 | "plugins": [ 54 | "babel", 55 | "meteor", 56 | "react" 57 | ], 58 | "settings": { 59 | "import/resolver": "meteor" 60 | }, 61 | "modules": true, 62 | "root": true, 63 | "globals": { 64 | "param": true, 65 | "returns": true 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /packages/zensroom/lib/components/bookings/BookingsCompleted.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Single booking page, wrapped with withDocument 4 | 5 | http://docs.vulcanjs.org/data-loading.html#Single-Resolver 6 | 7 | */ 8 | 9 | import React from 'react'; 10 | import { Components, registerComponent, withCurrentUser, withDocument } from 'meteor/vulcan:core'; 11 | import mapProps from 'recompose/mapProps'; 12 | import compose from 'recompose/compose'; 13 | import Button from 'react-bootstrap/lib/Button'; 14 | import gql from 'graphql-tag'; 15 | import { FormattedMessage } from 'meteor/vulcan:i18n'; 16 | 17 | import Bookings from '../../modules/bookings/collection'; 18 | 19 | const BookingsCompleted = ({document, loading, currentUser}) => 20 | 21 |
22 | {loading? 'Loading…' : 23 | 24 |
25 | 26 |

Booking for {document.room.name} completed

27 | 28 | 29 | 30 |
31 | 32 | } 33 | 34 |
35 | 36 | BookingsCompleted.displayName = 'BookingsPage'; 37 | 38 | const options = { 39 | collection: Bookings, 40 | fragmentName: 'BookingsItemFragment' 41 | }; 42 | 43 | const mapPropsFunction = props => ({...props, documentId: props.routeParams && props.routeParams.bookingId}); 44 | 45 | registerComponent('BookingsCompleted', BookingsCompleted, mapProps(mapPropsFunction), [withDocument, options], withCurrentUser); 46 | 47 | // export default compose( 48 | // mapProps(mapPropsFunction), 49 | // withDocument(options), 50 | // withCurrentUser 51 | // )(BookingsCompleted); 52 | -------------------------------------------------------------------------------- /packages/zensroom/lib/modules/emails.js: -------------------------------------------------------------------------------- 1 | import VulcanEmail from 'meteor/vulcan:email'; 2 | 3 | VulcanEmail.addEmails({ 4 | 5 | test: { 6 | template: "test", 7 | path: "/email/test", 8 | data() { 9 | return {date: new Date()}; 10 | }, 11 | subject() { 12 | return "This is a test"; 13 | }, 14 | }, 15 | 16 | roomsNew: { 17 | template: 'roomsNew', 18 | path: '/email/roomsNew', 19 | subject(data) { 20 | const room = _.isEmpty(data) ? {name: '[name]'} : data.RoomsSingle; 21 | return `A new room has been created: ${room.name}`; 22 | }, 23 | query: ` 24 | query OneRoom($documentId: String){ 25 | RoomsSingle(documentId: $documentId){ 26 | name 27 | description 28 | user{ 29 | _id 30 | displayName 31 | } 32 | } 33 | } 34 | `, 35 | testVariables: {} 36 | }, 37 | 38 | bookingsNew: { 39 | template: 'bookingsNew', 40 | path: '/email/bookingsNew', 41 | subject(data) { 42 | const booking = _.isEmpty(data) ? {room: {name: '[name]'}} : data.BookingsSingle; 43 | return `A booking has been created for room: ${booking.room.name}`; 44 | }, 45 | query: ` 46 | query OneBooking($documentId: String){ 47 | BookingsSingle(documentId: $documentId){ 48 | _id 49 | pageUrl 50 | startAtFormatted 51 | endAtFormatted 52 | numberOfGuests 53 | user{ 54 | _id 55 | displayName 56 | pageUrl 57 | } 58 | room{ 59 | _id 60 | name 61 | description 62 | pageUrl 63 | } 64 | } 65 | } 66 | `, 67 | testVariables: {} 68 | } 69 | 70 | }); 71 | -------------------------------------------------------------------------------- /packages/zensroom/lib/components/rooms/RoomsPhotos.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Display a room's photo 4 | 5 | */ 6 | 7 | import React, { PureComponent } from 'react'; 8 | import { Components, registerComponent, withCurrentUser, withDocument } from 'meteor/vulcan:core'; 9 | import mapProps from 'recompose/mapProps'; 10 | import compose from 'recompose/compose'; 11 | import { FormattedMessage } from 'meteor/vulcan:i18n'; 12 | 13 | import Rooms from '../../modules/rooms/collection'; 14 | 15 | class RoomsPhotos extends PureComponent { 16 | 17 | constructor() { 18 | super(); 19 | this.previous = this.previous.bind(this); 20 | this.next = this.next.bind(this); 21 | this.state = { 22 | selected: 0 23 | }; 24 | } 25 | 26 | previous(e) { 27 | e.preventDefault(); 28 | const totalPhotos = this.props.room.photos.length; 29 | this.setState({ 30 | selected: this.state.selected === 0 ? totalPhotos - 1 : this.state.selected - 1 31 | }); 32 | } 33 | 34 | next(e) { 35 | e.preventDefault(); 36 | const totalPhotos = this.props.room.photos.length; 37 | this.setState({ 38 | selected: this.state.selected === totalPhotos - 1 ? 0 : this.state.selected + 1 39 | }); 40 | } 41 | 42 | render() { 43 | 44 | return( 45 |
46 | 47 | 48 | 49 | 50 | 51 |
52 | ) 53 | } 54 | } 55 | 56 | RoomsPhotos.displayName = 'RoomsPhotos'; 57 | 58 | registerComponent('RoomsPhotos', RoomsPhotos); 59 | 60 | // export default RoomsPhotos; -------------------------------------------------------------------------------- /packages/zensroom/lib/modules/rooms/parameters.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Rooms parameters 4 | 5 | http://docs.vulcanjs.org/terms-parameters.html 6 | 7 | */ 8 | 9 | import { addCallback } from 'meteor/vulcan:core'; 10 | import moment from 'moment'; 11 | 12 | function addFromToParameters (parameters, terms, apolloClient, context) { 13 | 14 | if (terms.from || terms.to) { 15 | 16 | const mFrom = moment(terms.from, "YYYY-MM-DD").startOf('day'); 17 | const mTo = moment(terms.to, "YYYY-MM-DD").endOf('day'); 18 | 19 | /* 20 | Find all bookings during that period that: 21 | - End between "from" and "to" 22 | - Start between "from" and "to" 23 | */ 24 | const currentBookings = context.Bookings.find({$or: [ 25 | {$and: [ {startAt: {"$gt": mFrom.toDate()}}, {startAt: {"$lt": mTo.toDate()}} ]}, 26 | {$and: [ {endAt: {"$gt": mFrom.toDate()}}, {endAt: {"$lt": mTo.toDate()}} ]}, 27 | ]}).fetch(); 28 | const bookingsRoomIds = _.unique(_.pluck(currentBookings, 'roomId')); 29 | 30 | parameters.selector._id = {$nin: bookingsRoomIds}; 31 | } 32 | 33 | return parameters; 34 | } 35 | addCallback('rooms.parameters', addFromToParameters); 36 | 37 | function addFiltersParameter (parameters, terms, apolloClient) { 38 | 39 | if (terms.filters) { 40 | const filters = Array.isArray(terms.filters) ? terms.filters : [terms.filters]; 41 | parameters.selector.amenities = { $all: filters }; 42 | } 43 | 44 | return parameters; 45 | } 46 | addCallback('rooms.parameters', addFiltersParameter); 47 | 48 | function addSwNeParameters (parameters, terms, apolloClient) { 49 | 50 | if (terms.sw && terms.ne) { 51 | parameters.selector.location = { 52 | $geoWithin: { 53 | $box: [ 54 | [terms.sw.lng, terms.sw.lat], 55 | [terms.ne.lng, terms.ne.lat] 56 | ] 57 | } 58 | } 59 | } 60 | 61 | return parameters; 62 | } 63 | addCallback('rooms.parameters', addSwNeParameters); 64 | -------------------------------------------------------------------------------- /.meteor/versions: -------------------------------------------------------------------------------- 1 | accounts-base@1.3.4 2 | accounts-password@1.4.0 3 | allow-deny@1.0.9 4 | autoupdate@1.3.12 5 | babel-compiler@6.20.0 6 | babel-runtime@1.0.1 7 | base64@1.0.10 8 | binary-heap@1.0.10 9 | boilerplate-generator@1.2.0 10 | buffer@0.0.0 11 | caching-compiler@1.1.9 12 | callback-hook@1.0.10 13 | check@1.2.5 14 | ddp@1.3.1 15 | ddp-client@2.1.3 16 | ddp-common@1.2.9 17 | ddp-rate-limiter@1.0.7 18 | ddp-server@2.0.2 19 | diff-sequence@1.0.7 20 | dynamic-import@0.1.3 21 | ecmascript@0.8.3 22 | ecmascript-runtime@0.4.1 23 | ecmascript-runtime-client@0.4.3 24 | ecmascript-runtime-server@0.4.1 25 | ejson@1.0.14 26 | email@1.2.3 27 | fourseven:scss@4.5.4 28 | geojson-utils@1.0.10 29 | hot-code-push@1.0.4 30 | http@1.2.12 31 | id-map@1.0.9 32 | livedata@1.0.18 33 | localstorage@1.1.1 34 | logging@1.1.17 35 | meteor@1.7.2 36 | meteor-base@1.1.0 37 | meteorhacks:inject-initial@1.0.4 38 | meteorhacks:picker@1.0.3 39 | minifier-css@1.2.16 40 | minifier-js@2.1.4 41 | minimongo@1.3.2 42 | modules@0.10.0 43 | modules-runtime@0.8.0 44 | mongo@1.2.2 45 | mongo-dev-server@1.0.1 46 | mongo-id@1.0.6 47 | npm-bcrypt@0.9.3 48 | npm-mongo@2.2.30 49 | ordered-dict@1.0.9 50 | percolatestudio:synced-cron@1.1.0 51 | promise@0.9.0 52 | random@1.0.10 53 | rate-limit@1.0.8 54 | reactive-dict@1.1.9 55 | reactive-var@1.0.11 56 | reload@1.1.11 57 | retry@1.0.9 58 | routepolicy@1.0.12 59 | service-configuration@1.0.11 60 | session@1.1.7 61 | sha@1.0.9 62 | shell-server@0.2.4 63 | srp@1.0.10 64 | standard-minifier-css@1.3.5 65 | standard-minifier-js@2.1.2 66 | standard-minifiers@1.1.0 67 | tracker@1.1.3 68 | underscore@1.0.10 69 | url@1.1.0 70 | vulcan:accounts@1.8.0 71 | vulcan:admin@1.8.0 72 | vulcan:core@1.8.0 73 | vulcan:debug@1.8.0 74 | vulcan:email@1.8.0 75 | vulcan:forms@1.8.0 76 | vulcan:forms-upload@1.8.0 77 | vulcan:i18n@1.8.0 78 | vulcan:i18n-en-us@1.8.0 79 | vulcan:lib@1.8.0 80 | vulcan:maps@1.6.0 81 | vulcan:payments@1.8.0 82 | vulcan:routing@1.8.0 83 | vulcan:users@1.8.0 84 | webapp@1.3.19 85 | webapp-hashing@1.0.9 86 | zensroom@0.0.0 87 | -------------------------------------------------------------------------------- /packages/zensroom/lib/components/admin/BookingsDashboard.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Show a list of all bookings 4 | 5 | http://docs.vulcanjs.org/core-components.html#Datatable 6 | 7 | */ 8 | 9 | import React from 'react'; 10 | import { Components, registerComponent } from 'meteor/vulcan:core'; 11 | import compose from 'recompose/compose'; 12 | import { FormattedMessage } from 'meteor/vulcan:i18n'; 13 | import { Link } from 'react-router'; 14 | 15 | import Bookings from '../../modules/bookings/collection.js'; 16 | 17 | const BookingPrice = ({ document }) =>
{document.room.pricePerNight}
; 18 | 19 | const BookingUser = ({ document }) => 20 |
21 | 22 | 23 | {document.user.displayName} 24 | 25 |
; 26 | 27 | const BookingRoom = ({ document }) => 28 |
29 | 30 | 31 | {document.room.name} 32 | 33 |
; 34 | 35 | const BookingsDashboard = () => 36 | 37 |
38 | 39 |

40 | 41 | 64 | 65 |
66 | 67 | registerComponent('BookingsDashboard', BookingsDashboard); 68 | 69 | // export default BookingsDashboard; 70 | -------------------------------------------------------------------------------- /sample_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "public": { 3 | "title": "Your site title", 4 | "tagline":"Your site tagline", 5 | 6 | "logoUrl": "http://placekitten.com/250/80", 7 | "logoHeight": "80", 8 | "logoWidth": "250", 9 | "faviconUrl": "/favicon.ico", 10 | 11 | "requirePostsApproval":false, 12 | "requireViewInvite":false, 13 | "requirePostInvite":false, 14 | "enableNewsletter":true, 15 | "autoSubscribe":false, 16 | "emailNotifications":true, 17 | "postInterval": 20, 18 | "RSSLinksPointTo": "link", 19 | "commentInterval": 20, 20 | "maxPostsPerDay": 10, 21 | "startInvitesCount": 5, 22 | "postsPerPage": 10, 23 | 24 | "language": "en", 25 | "locale": "en", 26 | 27 | "twitterAccount": "foo", 28 | "facebookPage": "http://facebook.com/foo", 29 | 30 | "googleAnalyticsId":"123foo", 31 | "useSegment": false, 32 | "segmentWriteKey": "456bar" 33 | }, 34 | 35 | "defaultEmail": "hello@world.com", 36 | "mailUrl": "smtp://username%40yourdomain.mailgun.org:yourpassword123@smtp.mailgun.org:587/", 37 | "scoreUpdateInterval": "30", 38 | 39 | "embedlyKey":"123foo", 40 | 41 | "newsletterProvider": "mailchimp", 42 | "mailchimp": { 43 | "apiKey": "123foo", 44 | "listId": "123foo" 45 | }, 46 | "newsletterFrequency": [1,2,3,4,5,6,7], 47 | "newsletterTime": "14:20", 48 | "newsletterExcerptLength": 20, 49 | "postExcerptLength": 30, 50 | 51 | "categories": [ 52 | { 53 | "name": "Test Category 1", 54 | "description": "The first test category", 55 | "order": 4, 56 | "slug": "testcat1" 57 | }, 58 | { 59 | "name": "Test Category 2", 60 | "description": "The second test category", 61 | "order": 7, 62 | "slug": "testcat2" 63 | }, 64 | { 65 | "name": "Test Category 3", 66 | "description": "The third test category", 67 | "order": 10, 68 | "slug": "testcat3" 69 | } 70 | ], 71 | "oAuth": { 72 | "twitter": { 73 | "consumerKey": "foo", 74 | "secret": "bar" 75 | }, 76 | "facebook": { 77 | "appId": "foo", 78 | "secret": "bar" 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /packages/zensroom/lib/modules/i18n.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Internationalization Strings 4 | 5 | http://docs.vulcanjs.org/internationalization.html 6 | 7 | */ 8 | 9 | import { addStrings } from 'meteor/vulcan:core'; 10 | 11 | addStrings('en', { 12 | 'nav.how_to': 'How to Book', 13 | 'nav.about': 'About', 14 | 15 | 'bookings.bookings': 'Bookings', 16 | 'bookings.please_fill_in_all_fields': 'Please fill in all fields.', 17 | 'bookings.created': 'Booking created', 18 | 'bookings.book': 'Book This Room', 19 | 'bookings.paid_on': 'Paid on', 20 | 'bookings.complete_payment': 'Complete Payment', 21 | 'bookings.bookings_admin': 'Bookings', 22 | 'bookings.your_bookings': 'Your bookings for this room:', 23 | 'bookings.no_bookings': 'No bookings for this room yet.', 24 | 'bookings.load_more': 'Load More', 25 | 'bookings.from': 'From', 26 | 'bookings.to': 'To', 27 | 'bookings.number_of_guests': 'Number of guests', 28 | 'bookings.past': 'Past Bookings', 29 | 'bookings.current': 'Current Bookings', 30 | 'bookings.future': 'Future Bookings', 31 | 'bookings.dates': 'Dates', 32 | 33 | 'rooms.rooms': 'Rooms', 34 | 'rooms.rooms_admin': 'Rooms', 35 | 'rooms.new': 'New Room', 36 | 'rooms.create_new': 'Create New Room', 37 | 'rooms.created': 'New room created', 38 | 'rooms.featured': 'Featured Rooms', 39 | 'rooms.load_more': 'Load More', 40 | 'rooms.search_results': 'Search Results', 41 | 'rooms.location': 'Location', 42 | 'rooms.to': 'To', 43 | 'rooms.from': 'From', 44 | 'rooms.search': 'Search', 45 | 'rooms.per_night': '/night', 46 | 'rooms.filters': 'Filters', 47 | 'rooms.with_fireplace': 'With Fireplace', 48 | 49 | 'reviews.reviews': 'Reviews', 50 | 'reviews.reviews_admin': 'Reviews', 51 | 'reviews.created': 'Review created', 52 | 'reviews.load_more': 'Load More', 53 | 'reviews.leave_review': 'Leave a Review', 54 | 55 | 'users.rooms': 'Rooms', 56 | 'users.bookings': 'Bookings', 57 | 'users.reviews': 'Reviews', 58 | 59 | 'accounts.already_have_an_account': 'Already have an account?', 60 | 'accounts.log_in_here': 'Log in here', 61 | 'accounts.dont_have_an_account': 'Don\'t have an account?', 62 | 'accounts.sign_up_here': 'Sign up here', 63 | 64 | }); 65 | -------------------------------------------------------------------------------- /packages/_boilerplate-generator/template-web.browser.js: -------------------------------------------------------------------------------- 1 | // Template function for rendering the boilerplate html for browsers 2 | 3 | export default function({ 4 | meteorRuntimeConfig, 5 | rootUrlPathPrefix, 6 | inlineScriptsAllowed, 7 | css, 8 | js, 9 | additionalStaticJs, 10 | htmlAttributes, 11 | bundledJsCssUrlRewriteHook, 12 | head, 13 | body, 14 | dynamicHead, 15 | dynamicBody, 16 | }) { 17 | return [].concat( 18 | [ 19 | ' 20 | _.template(' <%= attrName %>="<%- attrValue %>"')({ 21 | attrName: key, 22 | attrValue: value 23 | }) 24 | ).join('') + '>', 25 | '' 26 | ], 27 | 28 | [ 29 | head, 30 | dynamicHead, 31 | ], 32 | 33 | _.map(css, ({url}) => 34 | _.template(' ')({ 35 | href: bundledJsCssUrlRewriteHook(url) 36 | }) 37 | ), 38 | 39 | [ 40 | '', 41 | '', 42 | body, 43 | dynamicBody, 44 | '', 45 | (inlineScriptsAllowed 46 | ? _.template(' ')({ 47 | conf: meteorRuntimeConfig 48 | }) 49 | : _.template(' ')({ 50 | src: rootUrlPathPrefix 51 | }) 52 | ) , 53 | '' 54 | ], 55 | 56 | _.map(js, ({url}) => 57 | _.template(' ')({ 58 | src: bundledJsCssUrlRewriteHook(url) 59 | }) 60 | ), 61 | 62 | _.map(additionalStaticJs, ({contents, pathname}) => ( 63 | (inlineScriptsAllowed 64 | ? _.template(' ')({ 65 | contents: contents 66 | }) 67 | : _.template(' ')({ 68 | src: rootUrlPathPrefix + pathname 69 | })) 70 | )), 71 | 72 | [ 73 | '', '', 74 | '', 75 | '' 76 | ], 77 | ).join('\n'); 78 | } 79 | 80 | -------------------------------------------------------------------------------- /packages/zensroom/lib/modules/components.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Components 4 | 5 | http://docs.vulcanjs.org/theming.html 6 | 7 | */ 8 | 9 | import '../components/admin/AdminUsersBookings'; 10 | import '../components/admin/AdminUsersReviews'; 11 | import '../components/admin/AdminUsersRooms'; 12 | import '../components/admin/BookingsDashboard'; 13 | import '../components/admin/RoomsDashboard'; 14 | import '../components/admin/ReviewsDashboard'; 15 | 16 | import '../components/bookings/BookingsNewForm'; 17 | import '../components/bookings/BookingsPage'; 18 | import '../components/bookings/BookingsRoomUser'; 19 | import '../components/bookings/BookingsPending'; 20 | import '../components/bookings/BookingsPast'; 21 | import '../components/bookings/BookingsCurrent'; 22 | import '../components/bookings/BookingsFuture'; 23 | import '../components/bookings/BookingsCompleted'; 24 | 25 | import '../components/common/Footer'; 26 | import '../components/common/Header'; 27 | import '../components/common/Home'; 28 | import '../components/common/Layout'; 29 | import '../components/common/AdminLayout'; 30 | 31 | import '../components/reviews/ReviewsItem'; 32 | import '../components/reviews/ReviewsList'; 33 | import '../components/reviews/ReviewsNewForm'; 34 | 35 | import '../components/rooms/RoomsItem'; 36 | import '../components/rooms/RoomsList'; 37 | import '../components/rooms/RoomsMain'; 38 | import '../components/rooms/RoomsNewForm'; 39 | import '../components/rooms/RoomsNewPage'; 40 | import '../components/rooms/RoomsPage'; 41 | import '../components/rooms/RoomsPhotos'; 42 | import '../components/rooms/RoomsReviews'; 43 | import '../components/rooms/RoomsSearch'; 44 | import '../components/rooms/RoomsSearchFilters'; 45 | import '../components/rooms/RoomsSearchForm'; 46 | import '../components/rooms/RoomsSearchResults'; 47 | 48 | import '../components/static/About'; 49 | import '../components/static/HowTo'; 50 | import '../components/static/Privacy'; 51 | import '../components/static/Terms'; 52 | 53 | import '../components/users/UsersAccount'; 54 | import '../components/users/UsersAccountMenu'; 55 | import '../components/users/UsersMenu'; 56 | import '../components/users/UsersProfile'; 57 | import '../components/users/UsersSignUp'; 58 | import '../components/users/UsersLogIn'; 59 | -------------------------------------------------------------------------------- /packages/zensroom/lib/components/users/UsersProfile.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | User profile page 4 | 5 | */ 6 | 7 | import { Components, registerComponent, withDocument, withCurrentUser } from 'meteor/vulcan:core'; 8 | import React from 'react'; 9 | import { FormattedMessage } from 'meteor/vulcan:i18n'; 10 | import Users from 'meteor/vulcan:users'; 11 | import { Link } from 'react-router'; 12 | import mapProps from 'recompose/mapProps'; 13 | import compose from 'recompose/compose'; 14 | 15 | const UsersProfile = (props) => { 16 | if (props.loading) { 17 | 18 | return
19 | 20 | } else if (!props.document) { 21 | 22 | console.log(`// missing user (_id/slug: ${props.documentId || props.slug})`); 23 | return
24 | 25 | } else { 26 | 27 | const user = props.document; 28 | 29 | const terms = {view: "userPosts", userId: user._id}; 30 | 31 | return ( 32 |
33 | 34 |

{Users.getDisplayName(user)}

35 |
    36 | 37 |
  • 38 |
    39 |
40 |
41 | ) 42 | } 43 | } 44 | 45 | UsersProfile.propTypes = { 46 | // document: PropTypes.object.isRequired, 47 | } 48 | 49 | UsersProfile.displayName = "UsersProfile"; 50 | 51 | const options = { 52 | collection: Users, 53 | queryName: 'usersSingleQuery', 54 | // fragmentName: 'UsersProfile', 55 | }; 56 | 57 | const mapPropsFunction = props => ({...props, userId: props.routeParams && props.routeParams.userId, slug: props.routeParams && props.routeParams.slug}); 58 | 59 | registerComponent('UsersProfile', UsersProfile, mapProps(mapPropsFunction), withCurrentUser, [withDocument, options]); 60 | 61 | // export default compose( 62 | // mapProps(mapPropsFunction), 63 | // withCurrentUser, 64 | // withDocument(options), 65 | // )(UsersProfile); -------------------------------------------------------------------------------- /packages/zensroom/lib/components/rooms/RoomsPage.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | A single room's page. Wrapped with withDocument. 4 | 5 | http://docs.vulcanjs.org/data-loading.html#Single-Resolver 6 | 7 | */ 8 | 9 | import React from 'react'; 10 | import { Components, registerComponent, withCurrentUser, withDocument } from 'meteor/vulcan:core'; 11 | import mapProps from 'recompose/mapProps'; 12 | import compose from 'recompose/compose'; 13 | import Button from 'react-bootstrap/lib/Button'; 14 | import { FormattedMessage } from 'meteor/vulcan:i18n'; 15 | 16 | import Rooms from '../../modules/rooms/collection'; 17 | 18 | // import RoomsPhotos from './RoomsPhotos'; 19 | // import RoomsMain from './RoomsMain'; 20 | 21 | // import BookingsNewForm from '../bookings/BookingsNewForm'; 22 | // import BookingsRoomUser from '../bookings/BookingsRoomUser'; 23 | 24 | const RoomsPage = ({document: room, documentId, loading, currentUser}) => 25 | 26 |
27 | 28 | {loading? 29 | 30 | : 31 | 32 |
33 | 34 | {room.photos && room.photos.length ? 35 |
}> 36 | 37 | 38 | : null} 39 | 40 |
41 | 42 | 43 | 44 |
45 | 46 | 47 | 48 | {/*currentUser ? 49 | 50 | : null*/} 51 | 52 |
53 | 54 |
55 | 56 |
57 | 58 | } 59 | 60 | 61 | 62 | 63 | RoomsPage.displayName = 'RoomsPage'; 64 | 65 | const options = { 66 | collection: Rooms 67 | }; 68 | 69 | const mapPropsFunction = props => ({...props, documentId: props.routeParams && props.routeParams.roomId}); 70 | 71 | registerComponent('RoomsPage', RoomsPage, mapProps(mapPropsFunction), [withDocument, options], withCurrentUser); 72 | 73 | // export default compose( 74 | // mapProps(mapPropsFunction), 75 | // withDocument(options), 76 | // withCurrentUser 77 | // )(RoomsPage); 78 | -------------------------------------------------------------------------------- /packages/zensroom/lib/components/users/UsersAccount.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Edit user account page 4 | 5 | */ 6 | 7 | import { Components, registerComponent, withCurrentUser, withMessages } from 'meteor/vulcan:core'; 8 | import React from 'react'; 9 | import PropTypes from 'prop-types'; 10 | import { FormattedMessage, intlShape } from 'meteor/vulcan:i18n'; 11 | import Users from 'meteor/vulcan:users'; 12 | import { STATES } from 'meteor/vulcan:accounts'; 13 | import mapProps from 'recompose/mapProps'; 14 | import compose from 'recompose/compose'; 15 | 16 | const UsersAccount = (props, context) => { 17 | return ( 18 | } 22 | > 23 |
24 |

25 | 26 |
27 | }> 28 | 29 | 30 |
31 | 32 | { 36 | props.flash(context.intl.formatMessage({ id: 'users.edit_success' }, {name: Users.getDisplayName(user)}), 'success') 37 | }} 38 | showRemove={true} 39 | /> 40 |
41 |
42 | ); 43 | }; 44 | 45 | 46 | UsersAccount.propTypes = { 47 | terms: PropTypes.object, // a user is defined by its unique _id or its unique slug 48 | }; 49 | 50 | UsersAccount.contextTypes = { 51 | intl: intlShape 52 | }; 53 | 54 | UsersAccount.displayName = 'UsersAccount'; 55 | 56 | const mapPropsFunction = props => ({...props, terms: props.routeParams && props.routeParams.slug ? {slug: props.routeParams.slug} : {documentId: props.currentUser._id}}); 57 | 58 | registerComponent('UsersAccount', UsersAccount, mapProps(mapPropsFunction), withMessages, withCurrentUser); 59 | 60 | // export default compose( 61 | // mapProps(mapPropsFunction), 62 | // withMessages, 63 | // withCurrentUser, 64 | // )(UsersAccount); -------------------------------------------------------------------------------- /packages/zensroom/lib/components/bookings/BookingsPage.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Single booking page, wrapped with withDocument 4 | 5 | http://docs.vulcanjs.org/data-loading.html#Single-Resolver 6 | 7 | */ 8 | 9 | import React from 'react'; 10 | import { Components, registerComponent, withCurrentUser, withDocument } from 'meteor/vulcan:core'; 11 | import mapProps from 'recompose/mapProps'; 12 | import compose from 'recompose/compose'; 13 | import Button from 'react-bootstrap/lib/Button'; 14 | import gql from 'graphql-tag'; 15 | import { FormattedMessage } from 'meteor/vulcan:i18n'; 16 | import { withRouter } from 'react-router'; 17 | import Bookings from '../../modules/bookings/collection'; 18 | 19 | const BookingsPage = ({document, loading, currentUser, router}) => 20 | 21 |
22 | {loading? 'Loading…' : 23 | 24 |
25 | 26 |

{document.room.name}

27 | 28 |
29 | {document.paidAt? 30 |

{document.paidAt}

: 31 | { 42 | router.push(`/booking/${document._id}/completed`) 43 | }} 44 | button={} 45 | /> 46 | } 47 |
48 | 49 | 50 | 51 |
52 | 53 | } 54 | 55 |
56 | 57 | BookingsPage.displayName = 'BookingsPage'; 58 | 59 | const options = { 60 | collection: Bookings, 61 | fragmentName: 'BookingsItemFragment' 62 | }; 63 | 64 | const mapPropsFunction = props => ({...props, documentId: props.routeParams && props.routeParams.bookingId}); 65 | 66 | registerComponent('BookingsPage', BookingsPage, mapProps(mapPropsFunction), [withDocument, options], withCurrentUser, withRouter); 67 | 68 | // export default compose( 69 | // mapProps(mapPropsFunction), 70 | // withDocument(options), 71 | // withCurrentUser, 72 | // withRouter, 73 | // )(BookingsPage); 74 | -------------------------------------------------------------------------------- /packages/zensroom/lib/modules/custom_fields.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Custom fields 4 | 5 | http://docs.vulcanjs.org/schemas.html#Custom-Fields 6 | 7 | */ 8 | 9 | import Users from 'meteor/vulcan:users'; 10 | 11 | Users.addField([ 12 | { 13 | fieldName: 'rooms', 14 | fieldSchema: { 15 | type: Array, 16 | optional: true, 17 | viewableBy: ['guests'], 18 | resolveAs: { 19 | fieldName: 'rooms', 20 | arguments: 'limit: Int = 5', 21 | type: '[Room]', 22 | resolver: (user, { limit }, { currentUser, Users, Rooms }) => { 23 | const rooms = Rooms.find({ userId: user._id }, { limit }).fetch(); 24 | 25 | // restrict documents fields 26 | // const viewableRooms = _.filter(rooms, room => Rooms.checkAccess(currentUser, room)); 27 | const restrictedRooms = Users.restrictViewableFields(currentUser, Rooms, rooms); 28 | 29 | return restrictedRooms; 30 | } 31 | } 32 | } 33 | }, 34 | { 35 | fieldName: 'bookings', 36 | fieldSchema: { 37 | type: Array, 38 | optional: true, 39 | viewableBy: ['guests'], 40 | resolveAs: { 41 | fieldName: 'bookings', 42 | arguments: 'limit: Int = 5', 43 | type: '[Booking]', 44 | resolver: (user, { limit }, { currentUser, Users, Bookings }) => { 45 | const bookings = Bookings.find({ userId: user._id }, { limit }).fetch(); 46 | 47 | // restrict documents fields 48 | // const viewableRooms = _.filter(rooms, room => Rooms.checkAccess(currentUser, room)); 49 | const restrictedBookings = Users.restrictViewableFields(currentUser, Bookings, bookings); 50 | 51 | return restrictedBookings; 52 | } 53 | } 54 | } 55 | }, 56 | { 57 | fieldName: 'reviews', 58 | fieldSchema: { 59 | type: Array, 60 | optional: true, 61 | viewableBy: ['guests'], 62 | resolveAs: { 63 | fieldName: 'reviews', 64 | arguments: 'limit: Int = 5', 65 | type: '[Review]', 66 | resolver: (user, { limit }, { currentUser, Users, Reviews }) => { 67 | const reviews = Reviews.find({ userId: user._id }, { limit }).fetch(); 68 | 69 | // restrict documents fields 70 | // const viewableRooms = _.filter(rooms, room => Rooms.checkAccess(currentUser, room)); 71 | const restrictedReviews = Users.restrictViewableFields(currentUser, Reviews, reviews); 72 | 73 | return restrictedReviews; 74 | } 75 | } 76 | } 77 | }, 78 | ]); -------------------------------------------------------------------------------- /packages/zensroom/lib/components/rooms/RoomsSearchResults.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Room search results. Wrapped with withList. 4 | 5 | http://docs.vulcanjs.org/data-loading.html#List-Resolver 6 | 7 | */ 8 | 9 | import React from 'react'; 10 | import { Components, registerComponent, withList } from 'meteor/vulcan:core'; 11 | import { FormattedMessage } from 'meteor/vulcan:i18n'; 12 | 13 | import Rooms from '../../modules/rooms/collection'; 14 | // import RoomsItem from './RoomsItem'; 15 | 16 | const defaultMapProperties = {lat: 35.6895, lng: 139.6917, type: 'country'}; // Tokyo, Japan 17 | 18 | const getCoords = room => room.location && {lng: room.location.coordinates[0], lat: room.location.coordinates[1]}; 19 | 20 | const getZoom = type => { 21 | switch(type) { 22 | case 'country': 23 | return 4; 24 | case 'locality': 25 | return 10; 26 | case 'street_address': 27 | return 15; 28 | } 29 | } 30 | 31 | const RoomsSearchResults = ({results = [], currentUser, loading, loadMore, count, totalCount, terms, onMapChange, mapProperties}) => 32 | 33 |
34 | 35 |

36 | 37 | {loading ? 38 | 39 | : 40 | 41 |
42 | 43 | 51 | 52 |
53 | {results.map(room => )} 54 | 55 | {totalCount > results.length ? 56 | {e.preventDefault(); loadMore();}}> ({count}/{totalCount}) 57 | : null } 58 |
59 | 60 |
61 | } 62 | 63 |
64 | 65 | 66 | // const mapPropsFunction = props => ({...props, terms: {...props.location.query}}); 67 | 68 | const options = { 69 | collection: Rooms, 70 | enableReducer: false 71 | } 72 | 73 | registerComponent('RoomsSearchResults', RoomsSearchResults, [withList, options]); 74 | 75 | // export default withList(options)(RoomsSearchResults); -------------------------------------------------------------------------------- /packages/zensroom/lib/server/rooms/callbacks.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Server callbacks 4 | 5 | See: http://docs.vulcanjs.org/callbacks.html 6 | 7 | */ 8 | 9 | import { addCallback } from 'meteor/vulcan:core'; 10 | import { geocode } from 'meteor/vulcan:maps'; 11 | import Users from 'meteor/vulcan:users'; 12 | import VulcanEmail from 'meteor/vulcan:email'; 13 | 14 | const geoFields = ['address', 'address2', 'state', 'city', 'zipCode', 'country']; 15 | 16 | const getAddressString = document => _.compact(geoFields.map(field => document[field])).join(' '); 17 | 18 | /* 19 | 20 | When a new room is created, geocode its address 21 | 22 | */ 23 | async function geocodeAddressOnNewRoom (document, currentUser) { 24 | if (_.some(geoFields, field => !!document[field])) { 25 | try { 26 | const geoData = await geocode(getAddressString(document)); 27 | document = { 28 | ...document, 29 | geoData, 30 | location: { 31 | type: 'Point', 32 | coordinates: [geoData.geometry.location.lng, geoData.geometry.location.lat] 33 | } 34 | } 35 | } catch (error) { 36 | console.log('//geoData error') 37 | console.log(error) 38 | } 39 | } 40 | return document; 41 | } 42 | addCallback('rooms.new.sync', geocodeAddressOnNewRoom); 43 | 44 | /* 45 | 46 | When a room is edited, geocode its address 47 | 48 | */ 49 | async function geocodeAddressOnEditRoom (modifier, document, currentUser) { 50 | if (_.some(geoFields, field => modifier.$set[field] && modifier.$set[field] !== document[field])) { 51 | try { 52 | const geoData = await geocode(getAddressString(modifier.$set)); 53 | modifier.$set = { 54 | ...modifier.$set, 55 | geoData, 56 | location: { 57 | type: 'Point', 58 | coordinates: [geoData.geometry.location.lng, geoData.geometry.location.lat] 59 | } 60 | } 61 | } catch (error) { 62 | console.log('//geoData error') 63 | console.log(error) 64 | } 65 | } 66 | return modifier; 67 | } 68 | addCallback('rooms.edit.sync', geocodeAddressOnEditRoom); 69 | 70 | /* 71 | 72 | When a new room is created, send a notification to all admins 73 | 74 | */ 75 | async function RoomsNewNotifications (room) { 76 | 77 | const adminUsers = Users.find({isAdmin : true, _id: {$ne: room.userId}}).fetch(); 78 | const emails = _.compact(_.pluck(adminUsers, 'email')); 79 | 80 | await VulcanEmail.buildAndSend({ 81 | to: emails, 82 | emailName: 'roomsNew', 83 | variables: { documentId: room._id} 84 | }); 85 | 86 | } 87 | addCallback('rooms.new.async', RoomsNewNotifications); 88 | -------------------------------------------------------------------------------- /packages/zensroom/lib/components/rooms/RoomsSearch.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Room search page. Contains form, filters, and results 4 | 5 | */ 6 | 7 | import React, { Component } from 'react'; 8 | import { Components, registerComponent } from 'meteor/vulcan:core'; 9 | import { withRouter } from 'react-router'; 10 | 11 | // import RoomsSearchForm from './RoomsSearchForm'; 12 | // import RoomsSearchResults from './RoomsSearchResults'; 13 | // import RoomsSearchFilters from './RoomsSearchFilters'; 14 | 15 | class RoomsSearch extends Component { 16 | 17 | constructor(props) { 18 | super(props); 19 | this.onMapChange = this.onMapChange.bind(this); 20 | 21 | // initialize state with lat/lng values provided by the URL query parameters 22 | this.state = { 23 | from: props.location.query.from, 24 | to: props.location.query.to, 25 | lat: props.location.query.lat, 26 | lng: props.location.query.lng, 27 | type: props.location.query.type, 28 | }; 29 | 30 | } 31 | 32 | // whenever URL change, also update component's state 33 | componentWillReceiveProps(nextProps) { 34 | this.state = { 35 | from: nextProps.location.query.from, 36 | to: nextProps.location.query.to, 37 | lat: nextProps.location.query.lat, 38 | lng: nextProps.location.query.lng, 39 | type: nextProps.location.query.type, 40 | }; 41 | } 42 | 43 | onMapChange(mapData) { 44 | // whenever map changes, update this component's state 45 | const { center, zoom, bounds, marginBounds, size } = mapData; 46 | this.setState({ 47 | lat: center.lat, 48 | lng: center.lng, 49 | sw: bounds.sw, 50 | ne: bounds.ne, 51 | }); 52 | } 53 | 54 | render() { 55 | // mapsProps object to center map on lat/lng and terms object to perform server query 56 | // Note: terms will not have sw/ne until onMapChange triggers for the first time. 57 | return ( 58 |
59 | 60 | 61 | 76 |
77 | ) 78 | } 79 | } 80 | 81 | registerComponent('RoomsSearch', RoomsSearch, withRouter); 82 | 83 | // export default withRouter(RoomsSearch); -------------------------------------------------------------------------------- /packages/zensroom/lib/components/rooms/RoomsSearchFilters.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Room search filters 4 | 5 | */ 6 | 7 | import React, { Component } from 'react'; 8 | import { Components, registerComponent } from 'meteor/vulcan:core'; 9 | import { withRouter } from 'react-router'; 10 | import { Checkbox } from 'formsy-react-components'; 11 | import { amenities, spaces } from '../../modules/data'; 12 | import { FormattedMessage } from 'meteor/vulcan:i18n'; 13 | 14 | const encodeObject = obj => _.map(obj, (value, key) => `${key}=${value}`).join('&'); 15 | const encodeArray = array => _.map(array, (value) => `filters=${value}`).join('&'); 16 | 17 | class RoomsSearchFilters extends Component { 18 | 19 | constructor(props) { 20 | super(props); 21 | this.toggleFilter = this.toggleFilter.bind(this); 22 | this.getURLFilters = this.getURLFilters.bind(this); 23 | } 24 | 25 | // get filters from URL 26 | // Note: handle cases where there's 0 filters, 1 filter, or 2+ filters 27 | getURLFilters() { 28 | const filters = this.props.location.query.filters; 29 | return filters ? Array.isArray(filters) ? filters : [filters] : []; 30 | } 31 | 32 | // toggle filter on/off 33 | toggleFilter(filterName) { 34 | 35 | let filters = this.getURLFilters(); 36 | 37 | if(_.contains(filters, filterName)) { 38 | // if URL contains filter, remove it 39 | filters = _.without(filters, filterName); 40 | } else { 41 | // if URL doesn't contain filter, add it 42 | filters.push(filterName); 43 | } 44 | 45 | // remove old filters 46 | const query = _.clone(this.props.location.query); 47 | delete query.filters; 48 | 49 | // build URL from current query string and new filters object 50 | let newUrl = `/search?`; 51 | newUrl += encodeObject(query); 52 | if (filters.length) { 53 | newUrl += `&${encodeArray(filters)}`; 54 | } 55 | 56 | // console.log(query) 57 | // console.log(filters) 58 | // console.log(newUrl) 59 | 60 | // replace URL 61 | this.props.router.replace(newUrl); 62 | } 63 | 64 | render() { 65 | const filters = this.getURLFilters(); 66 | 67 | return ( 68 |
69 |

70 |
    71 | {amenities.map(({label, value}, index) => 72 |
  • 73 | 76 |
  • 77 | )} 78 |
79 |
80 | ) 81 | } 82 | } 83 | 84 | registerComponent('RoomsSearchFilters', RoomsSearchFilters, withRouter); 85 | 86 | // export default withRouter(RoomsSearchFilters); -------------------------------------------------------------------------------- /packages/_boilerplate-generator/template-web.cordova.js: -------------------------------------------------------------------------------- 1 | // Template function for rendering the boilerplate html for cordova 2 | 3 | export default function({ 4 | meteorRuntimeConfig, 5 | rootUrlPathPrefix, 6 | inlineScriptsAllowed, 7 | css, 8 | js, 9 | additionalStaticJs, 10 | htmlAttributes, 11 | bundledJsCssUrlRewriteHook, 12 | head, 13 | body, 14 | dynamicHead, 15 | dynamicBody, 16 | }) { 17 | return [].concat( 18 | [ 19 | '', 20 | '', 21 | ' ', 22 | ' ', 23 | ' ', 24 | ' ', 25 | ' ', 26 | ], 27 | // We are explicitly not using bundledJsCssUrlRewriteHook: in cordova we serve assets up directly from disk, so rewriting the URL does not make sense 28 | _.map(css, ({url}) => 29 | _.template(' ')({ 30 | href: url 31 | }) 32 | ), 33 | [ 34 | ' ', 48 | '', 49 | ' ' 50 | ], 51 | _.map(js, ({url}) => 52 | _.template(' ')({ 53 | src: url 54 | }) 55 | ), 56 | 57 | _.map(additionalStaticJs, ({contents, pathname}) => ( 58 | (inlineScriptsAllowed 59 | ? _.template(' ')({ 60 | contents: contents 61 | }) 62 | : _.template(' ')({ 63 | src: rootUrlPathPrefix + pathname 64 | })) 65 | )), 66 | 67 | [ 68 | '', 69 | head, 70 | '', 71 | '', 72 | '', 73 | body, 74 | '', 75 | '' 76 | ], 77 | ).join('\n'); 78 | } 79 | 80 | -------------------------------------------------------------------------------- /packages/zensroom/lib/modules/routes.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Routes 4 | 5 | http://docs.vulcanjs.org/routing.html 6 | 7 | */ 8 | 9 | import { Components, addRoute, extendRoute } from 'meteor/vulcan:core'; 10 | 11 | // import Home from '../components/common/Home'; 12 | // import HowTo from '../components/static/HowTo'; 13 | // import About from '../components/static/About'; 14 | // import Privacy from '../components/static/Privacy'; 15 | // import Terms from '../components/static/Terms'; 16 | 17 | // import RoomsSearch from '../components/rooms/RoomsSearch'; 18 | // import RoomsPage from '../components/rooms/RoomsPage'; 19 | // import RoomsNewPage from '../components/rooms/RoomsNewPage'; 20 | 21 | // import BookingsPage from '../components/bookings/BookingsPage'; 22 | 23 | // import UsersProfile from '../components/users/UsersProfile'; 24 | // import UsersAccount from '../components/users/UsersAccount'; 25 | 26 | // import BookingsList from '../components/admin/BookingsList'; 27 | // import RoomsList from '../components/admin/RoomsList'; 28 | 29 | addRoute([ 30 | 31 | {name: 'home', path: '/', componentName: 'Home'}, 32 | {name: 'how-to', path: '/how-to', componentName: 'HowTo'}, 33 | {name: 'about', path: '/about', componentName: 'About'}, 34 | {name: 'privacy', path: '/privacy', componentName: 'Privacy'}, 35 | {name: 'terms', path: '/terms', componentName: 'Terms'}, 36 | 37 | {name: 'rooms.search', path: '/search', componentName: 'RoomsSearch'}, 38 | {name: 'rooms.new', path: '/room/new', componentName: 'RoomsNewPage'}, 39 | {name: 'rooms.page', path: '/room/:roomId(/:slug)', componentName: 'RoomsPage'}, 40 | 41 | {name: 'bookings.page', path: '/booking/:bookingId', componentName: 'BookingsPage'}, 42 | {name: 'bookings.past', path: '/account/bookings/past', componentName: 'BookingsPast'}, 43 | {name: 'bookings.current', path: '/account/bookings/current', componentName: 'BookingsCurrent'}, 44 | {name: 'bookings.future', path: '/account/bookings/future', componentName: 'BookingsFuture'}, 45 | {name: 'bookings.completed', path: '/booking/:bookingId/completed', componentName: 'BookingsCompleted'}, 46 | 47 | {name: 'users.single', path:'/users/:slug', componentName: 'UsersProfile'}, 48 | {name: 'users.account', path:'/account', componentName: 'UsersAccount'}, 49 | {name: 'users.edit', path:'/users/:slug/edit', componentName: 'UsersAccount'}, 50 | {name: 'users.signup', path:'/sign-up', componentName: 'UsersSignUp'}, 51 | {name: 'users.login', path:'/log-in', componentName: 'UsersLogIn'}, 52 | 53 | {name: 'bookings.dashboard', path:'/admin/bookings', componentName: 'BookingsDashboard', layoutName: 'AdminLayout'}, 54 | {name: 'rooms.dashboard', path:'/admin/rooms', componentName: 'RoomsDashboard', layoutName: 'AdminLayout'}, 55 | {name: 'reviews.dashboard', path:'/admin/reviews', componentName: 'ReviewsDashboard', layoutName: 'AdminLayout'}, 56 | 57 | ]); 58 | 59 | extendRoute('admin', { layoutName: 'AdminLayout' }); 60 | -------------------------------------------------------------------------------- /packages/_boilerplate-generator/generator.js: -------------------------------------------------------------------------------- 1 | import { readFile } from 'fs'; 2 | 3 | import WebBrowserTemplate from './template-web.browser'; 4 | import WebCordovaTemplate from './template-web.cordova'; 5 | 6 | // Copied from webapp_server 7 | const readUtf8FileSync = filename => Meteor.wrapAsync(readFile)(filename, 'utf8'); 8 | 9 | export class Boilerplate { 10 | constructor(arch, manifest, options = {}) { 11 | this.template = _getTemplate(arch); 12 | this.baseData = null; 13 | 14 | this._generateBoilerplateFromManifest( 15 | manifest, 16 | options 17 | ); 18 | } 19 | 20 | // The 'extraData' argument can be used to extend 'self.baseData'. Its 21 | // purpose is to allow you to specify data that you might not know at 22 | // the time that you construct the Boilerplate object. (e.g. it is used 23 | // by 'webapp' to specify data that is only known at request-time). 24 | toHTML(extraData) { 25 | if (!this.baseData || !this.template) { 26 | throw new Error('Boilerplate did not instantiate correctly.'); 27 | } 28 | 29 | return "\n" + 30 | this.template({ ...this.baseData, ...extraData }); 31 | } 32 | 33 | // XXX Exported to allow client-side only changes to rebuild the boilerplate 34 | // without requiring a full server restart. 35 | // Produces an HTML string with given manifest and boilerplateSource. 36 | // Optionally takes urlMapper in case urls from manifest need to be prefixed 37 | // or rewritten. 38 | // Optionally takes pathMapper for resolving relative file system paths. 39 | // Optionally allows to override fields of the data context. 40 | _generateBoilerplateFromManifest(manifest, { 41 | urlMapper = _.identity, 42 | pathMapper = _.identity, 43 | baseDataExtension, 44 | inline, 45 | } = {}) { 46 | 47 | const boilerplateBaseData = { 48 | css: [], 49 | js: [], 50 | head: '', 51 | body: '', 52 | meteorManifest: JSON.stringify(manifest), 53 | ...baseDataExtension, 54 | }; 55 | 56 | _.each(manifest, item => { 57 | const urlPath = urlMapper(item.url); 58 | const itemObj = { url: urlPath }; 59 | 60 | if (inline) { 61 | itemObj.scriptContent = readUtf8FileSync( 62 | pathMapper(item.path)); 63 | itemObj.inline = true; 64 | } 65 | 66 | if (item.type === 'css' && item.where === 'client') { 67 | boilerplateBaseData.css.push(itemObj); 68 | } 69 | 70 | if (item.type === 'js' && item.where === 'client' && 71 | // Dynamic JS modules should not be loaded eagerly in the 72 | // initial HTML of the app. 73 | !item.path.startsWith('dynamic/')) { 74 | boilerplateBaseData.js.push(itemObj); 75 | } 76 | 77 | if (item.type === 'head') { 78 | boilerplateBaseData.head = 79 | readUtf8FileSync(pathMapper(item.path)); 80 | } 81 | 82 | if (item.type === 'body') { 83 | boilerplateBaseData.body = 84 | readUtf8FileSync(pathMapper(item.path)); 85 | } 86 | }); 87 | 88 | this.baseData = boilerplateBaseData; 89 | } 90 | }; 91 | 92 | // Returns a template function that, when called, produces the boilerplate 93 | // html as a string. 94 | const _getTemplate = arch => { 95 | if (arch === 'web.browser') { 96 | return WebBrowserTemplate; 97 | } else if (arch === 'web.cordova') { 98 | return WebCordovaTemplate; 99 | } else { 100 | throw new Error('Unsupported arch: ' + arch); 101 | } 102 | }; 103 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Vulcan", 3 | "version": "1.7.0", 4 | "engines": { 5 | "npm": "^3.0" 6 | }, 7 | "scripts": { 8 | "prestart": "sh prestart_vulcan.sh", 9 | "start": "meteor --settings settings.json", 10 | "lint": "eslint --cache --ext .jsx,js packages" 11 | }, 12 | "dependencies": { 13 | "@google/maps": "^0.4.3", 14 | "analytics-node": "^2.1.1", 15 | "apollo-client": "^1.2.2", 16 | "apollo-errors": "^1.4.0", 17 | "babel-runtime": "^6.26.0", 18 | "bluebird": "^3.5.0", 19 | "body-parser": "^1.15.2", 20 | "classnames": "^2.2.3", 21 | "cookie-parser": "^1.4.3", 22 | "crypto-js": "^3.1.9-1", 23 | "dataloader": "^1.3.0", 24 | "deepmerge": "^1.2.0", 25 | "escape-string-regexp": "^1.0.5", 26 | "express": "^4.14.0", 27 | "flat": "^4.0.0", 28 | "formsy-react": "^0.19.5", 29 | "formsy-react-components": "^0.10.1", 30 | "google-map-react": "^0.24.0", 31 | "graphql": "^0.9.6", 32 | "graphql-anywhere": "^3.0.1", 33 | "graphql-date": "^1.0.2", 34 | "graphql-server-express": "^0.6.0", 35 | "graphql-tag": "^2.0.0", 36 | "graphql-tools": "^0.10.1", 37 | "graphql-type-json": "^0.1.4", 38 | "handlebars": "^4.0.5", 39 | "he": "^1.1.1", 40 | "history": "^3.0.0", 41 | "html-to-text": "^2.1.0", 42 | "immutability-helper": "^2.0.0", 43 | "import": "0.0.6", 44 | "intl": "^1.2.4", 45 | "intl-locales-supported": "^1.0.0", 46 | "isomorphic-fetch": "^2.2.1", 47 | "juice": "^1.11.0", 48 | "mailchimp": "^1.1.6", 49 | "marked": "^0.3.5", 50 | "metascraper": "^1.0.6", 51 | "meteor-node-stubs": "^0.2.3", 52 | "mingo": "^0.8.1", 53 | "moment": "^2.13.0", 54 | "optics-agent": "^1.0.5", 55 | "prop-types": "^15.5.10", 56 | "react": "^15.6.1", 57 | "react-addons-pure-render-mixin": "^15.4.1", 58 | "react-apollo": "^1.1.1", 59 | "react-bootstrap": "^0.30.7", 60 | "react-bootstrap-datetimepicker": "0.0.22", 61 | "react-cookie": "^0.4.6", 62 | "react-datetime": "^2.3.2", 63 | "react-dom": "^15.4.1", 64 | "react-dropzone": "^3.12.2", 65 | "react-helmet": "^5.1.3", 66 | "react-intl": "^2.1.3", 67 | "react-loadable": "^4.0.3", 68 | "react-places-autocomplete": "^5.0.0", 69 | "react-redux": "^5.0.1", 70 | "react-router": "^3.0.0", 71 | "react-router-bootstrap": "^0.23.1", 72 | "react-router-scroll": "^0.4.1", 73 | "react-stripe-checkout": "2.6.3", 74 | "recompose": "^0.21.2", 75 | "redux": "^3.6.0", 76 | "rss": "^1.2.1", 77 | "sanitize-html": "^1.11.4", 78 | "sendy-api": "^0.1.0", 79 | "simpl-schema": "^0.2.3", 80 | "speakingurl": "^9.0.0", 81 | "stripe": "^4.23.1", 82 | "styled-components": "^2.1.1", 83 | "tracker-component": "^1.3.14", 84 | "underscore": "^1.8.3", 85 | "url": "^0.11.0" 86 | }, 87 | "private": true, 88 | "devDependencies": { 89 | "autoprefixer": "^6.3.6", 90 | "babel-eslint": "^7.0.0", 91 | "eslint": "^3.10.1", 92 | "eslint-config-airbnb": "^13.0.0", 93 | "eslint-config-meteor": "0.0.9", 94 | "eslint-import-resolver-meteor": "^0.3.3", 95 | "eslint-plugin-babel": "^3.3.0", 96 | "eslint-plugin-import": "^2.2.0", 97 | "eslint-plugin-jsx-a11y": "^2.2.3", 98 | "eslint-plugin-meteor": "^4.0.1", 99 | "eslint-plugin-react": "^6.7.1" 100 | }, 101 | "postcss": { 102 | "plugins": { 103 | "autoprefixer": { 104 | "browsers": [ 105 | "last 2 versions" 106 | ] 107 | } 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /packages/zensroom/lib/components/users/UsersMenu.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | User menu (when logged in) 4 | 5 | */ 6 | 7 | import { Components, registerComponent, withCurrentUser } from 'meteor/vulcan:core'; 8 | import Users from 'meteor/vulcan:users'; 9 | import React from 'react'; 10 | import PropTypes from 'prop-types'; 11 | import { FormattedMessage } from 'meteor/vulcan:i18n'; 12 | import { Meteor } from 'meteor/meteor'; 13 | import Dropdown from 'react-bootstrap/lib/Dropdown'; 14 | import MenuItem from 'react-bootstrap/lib/MenuItem'; 15 | import { LinkContainer } from 'react-router-bootstrap'; 16 | import { withApollo } from 'react-apollo'; 17 | 18 | const UsersMenu = ({currentUser, client}) => 19 |
20 | 21 | 22 | 23 |
{Users.getDisplayName(currentUser)}
24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | {Users.canDo(currentUser, 'rooms.new') ? 43 | 44 | 45 | 46 | : null} 47 | 48 | {Users.canDo(currentUser, 'users.view.all') ? 49 | 50 | 51 | 52 | : null} 53 | 54 | {Users.canDo(currentUser, 'bookings.view.all') ? 55 | 56 | 57 | 58 | : null} 59 | 60 | {Users.canDo(currentUser, 'rooms.view.all') ? 61 | 62 | 63 | 64 | : null} 65 | 66 | {Users.canDo(currentUser, 'reviews.view.all') ? 67 | 68 | 69 | 70 | : null} 71 | 72 | Meteor.logout(() => client.resetStore())}> 73 | 74 |
75 |
76 | 77 | 78 | UsersMenu.propsTypes = { 79 | currentUser: PropTypes.object, 80 | client: PropTypes.object, 81 | }; 82 | 83 | registerComponent('UsersMenu', UsersMenu, withApollo, withCurrentUser); 84 | 85 | // export default withCurrentUser(withApollo(UsersMenu)); 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ZensRoom 2 | 3 | This is an open-source AirBnB-type app built using [VulcanJS](http://vulcanjs.org) and developed as prototype for [Zens](http://www.zens.tokyo/), a Tokyo-based company. 4 | 5 | ## Install 6 | 7 | 1. Clone this repo 8 | 2. Follow the [VulcanJS install instructions](http://docs.vulcanjs.org/#Install). 9 | 3. Run with `npm start` (or the equivalent `meteor --settings settings.json`). 10 | 11 | ## Local Development 12 | 13 | By default, the app will look for VulcanJS core packages (`vulcan:core`, `vulcan:email`, `vulcan:forms`, etc.) on [Atmosphere](https://atmospherejs.com/), Meteor's package server. 14 | 15 | For local development, it can be useful to have access to the VulcanJS codebase locally and be able to modify it if needed. You can do so by following these steps: 16 | 17 | 1. Clone the main [VulcanJS repo](https://github.com/VulcanJS/Vulcan) locally (for example, to `~/Vulcan`). 18 | 2. Inside the main repo, checkout the `devel` branch. 19 | 3. Go back to the ZensRoom directory and launch your app with: 20 | 21 | ``` 22 | METEOR_PACKAGE_DIRS="~/Vulcan/packages" meteor --port 3000 --settings settings.json 23 | ``` 24 | 25 | Note that if you'd like, you can create an alias for that command in your `.bash_profile` file: 26 | 27 | ``` 28 | alias runvulcan='METEOR_PACKAGE_DIRS="~/Vulcan/packages" meteor --port 3000 --settings settings.json' 29 | ``` 30 | 31 | ## Settings 32 | 33 | This project expects a few specific API keys, as defined in your project's `settings.json` (in addition to any other generic VulcanJS settings you might already have). Here's a sample file: 34 | 35 | ``` 36 | { 37 | "public": { 38 | "title": "Your Site Name", 39 | "tagline":"Your site tagline", 40 | 41 | "language": "en", 42 | "locale": "en", 43 | 44 | "cloudinary": { 45 | "cloudName": "123foo" 46 | }, 47 | 48 | "stripe": { 49 | "publishableKeyTest": "pk_test_123foo" 50 | }, 51 | 52 | "googlemaps": { 53 | "apiKey": "456foo" 54 | } 55 | }, 56 | 57 | "stripe": { 58 | "secretKeyTest": "sk_test_123foo" 59 | }, 60 | 61 | "defaultEmail": "hello@foo.com", 62 | "mailUrl": "smtp://username%40yourdomain.mailgun.org:yourpassword123@smtp.mailgun.org:587/", 63 | 64 | "oAuth": { 65 | "twitter": { 66 | "consumerKey": "foo", 67 | "secret": "bar" 68 | }, 69 | "facebook": { 70 | "appId": "foo", 71 | "secret": "bar" 72 | } 73 | } 74 | } 75 | ``` 76 | 77 | ## Dependencies 78 | 79 | The ZensRoom app depends on the following VulcanJS [packages](https://github.com/SachaG/Zensroom/blob/devel/packages/zensroom/package.js#L10-L19): 80 | 81 | - `vulcan:core`: VulcanJS core features. 82 | - `vulcan:forms`: [SmartForms](http://docs.vulcanjs.org/forms.html) component. 83 | - `vulcan:forms-upload`: Image upload form component (using [Cloudinary](http://cloudinary.com)). 84 | - `vulcan:accounts`: User accounts UI (log in/sign up/reset password/etc.). 85 | - `vulcan:payments`: [Payments package](http://docs.vulcanjs.org/payments.html) using Stripe. 86 | - `vulcan:admin`: [Admin](http://docs.vulcanjs.org/admin.html) dashboard. 87 | - `vulcan:maps`: Maps component. 88 | 89 | Packages in the repo not mentioned above (`vulcan:posts`, `vulcan:comments`, etc.) are not currently used by this project but might be in the future. 90 | 91 | See also `package.json` for a list of NPM dependencies. 92 | 93 | ## Architecture 94 | 95 | The code for the app is available in `/packages/zensroom`, split into the following directories: 96 | 97 | - `client`: contains the client entry point and any client-specific code. 98 | - `server`: contains the server entry point and any server-specific code. 99 | - `components`: contains all React components. 100 | - `containers`: contains all React containers. 101 | - `modules`: contains all other JavaScript modules. 102 | 103 | ## Collections (Models) 104 | 105 | The app uses the following collections: 106 | 107 | - `Rooms` 108 | - `Bookings` 109 | - `Reviews` 110 | - `Users` 111 | -------------------------------------------------------------------------------- /packages/zensroom/lib/components/rooms/RoomsSearchForm.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Room search form 4 | 5 | */ 6 | 7 | import React, { Component } from 'react'; 8 | import { Components, registerComponent, getSetting } from 'meteor/vulcan:core'; 9 | import { Form, Input } from 'formsy-react-components'; 10 | import { withRouter } from 'react-router' 11 | import DateTimePicker from 'react-datetime'; 12 | import Button from 'react-bootstrap/lib/Button'; 13 | import { FormattedMessage } from 'meteor/vulcan:i18n'; 14 | import moment from 'moment'; 15 | 16 | class RoomsSearchForm extends Component { 17 | constructor(props) { 18 | super(props); 19 | this.updateFromDate = this.updateFromDate.bind(this); 20 | this.updateToDate = this.updateToDate.bind(this); 21 | this.submitForm = this.submitForm.bind(this); 22 | 23 | const state = {}; 24 | if (props.location.query.from) { 25 | state.from = moment(props.location.query.from, 'YYYY-MM-DD'); 26 | } 27 | if (props.location.query.to) { 28 | state.to = moment(props.location.query.to, 'YYYY-MM-DD'); 29 | } 30 | if (props.location.query.location) { 31 | state.location = decodeURIComponent(props.location.query.location); 32 | } 33 | this.state = state; 34 | } 35 | 36 | updateFromDate(date) { 37 | this.setState({ from: date }); 38 | } 39 | 40 | updateToDate(date) { 41 | this.setState({ to: date }); 42 | } 43 | 44 | async submitForm({ location }) { 45 | 46 | let query = ''; 47 | 48 | if (this.state.from) { 49 | query += `from=${this.state.from.format('YYYY-MM-DD')}`; 50 | } 51 | 52 | if (this.state.to) { 53 | query += `&to=${this.state.to.format('YYYY-MM-DD')}`; 54 | } 55 | 56 | if (location) { 57 | const geocodeUrl = `https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(location)}&key=${getSetting('googlemaps').apiKey}` 58 | 59 | const response = await fetch(geocodeUrl); 60 | const geoData = await response.json(); 61 | console.log(geoData) 62 | const results = geoData.results[0]; 63 | query += `&location=${encodeURIComponent(location)}&lng=${results.geometry.location.lng}&lat=${results.geometry.location.lat}&type=${results.types[0]}` 64 | } 65 | 66 | this.props.router.push(`/search?${query}`); 67 | } 68 | 69 | render() { 70 | 71 | return ( 72 |
73 |
74 | 75 |
76 | 77 | this.updateFromDate(newDate)} 79 | format={"x"} 80 | value={this.state.from} 81 | timeFormat={false} 82 | /> 83 |
84 | 85 |
86 | 87 | this.updateToDate(newDate)} 89 | format={"x"} 90 | value={this.state.to} 91 | timeFormat={false} 92 | /> 93 |
94 | 95 |
96 | 97 | 98 |
99 | 100 |
101 | 102 |
103 | 104 |
105 |
106 | ); 107 | } 108 | 109 | } 110 | 111 | registerComponent('RoomsSearchForm', RoomsSearchForm, withRouter); 112 | 113 | // export default withRouter(RoomsSearchForm); -------------------------------------------------------------------------------- /packages/zensroom/lib/server/emails/common/wrapper.handlebars: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{siteName}} 8 | 90 | 91 | 92 | 93 |
94 | 95 | 96 | 97 | 98 | 131 | 132 |
99 | 100 | 101 | 102 | 103 | 112 | 113 | 114 | 120 | 121 | 122 | 126 | 127 |
104 | 105 | {{#if logoUrl}} 106 | 107 | {{else}} 108 | {{siteName}} 109 | {{/if}} 110 | 111 |
115 |
116 | 117 | {{{body}}} 118 | 119 |
128 | 129 | 130 |
133 | 134 |
135 |
136 | 137 | 138 | -------------------------------------------------------------------------------- /packages/zensroom/lib/stylesheets/_rooms.scss: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////// 2 | // Rooms Search Form // 3 | ////////////////////////////////////////////////////// 4 | 5 | .rooms-search-form{ 6 | padding: $spacing; 7 | margin-bottom: $spacing; 8 | .form-horizontal{ 9 | display: flex; 10 | } 11 | } 12 | 13 | .rooms-search-form-field{ 14 | flex: 1; 15 | margin-right: $spacing/2; 16 | display: flex; 17 | flex-direction: column; 18 | justify-content: flex-end; 19 | &:last-child{ 20 | margin-right: 0; 21 | } 22 | } 23 | 24 | .rooms-search-form-submit{ 25 | width: 100%; 26 | } 27 | 28 | ////////////////////////////////////////////////////// 29 | // Rooms Grid // 30 | ////////////////////////////////////////////////////// 31 | 32 | .rooms-grid{ 33 | display: flex; 34 | flex-wrap: wrap; 35 | .rooms-item{ 36 | width: calc((100% - #{$spacing * 2})/3); 37 | margin-right: $spacing; 38 | margin-bottom: $spacing; 39 | &:nth-child(3n){ 40 | margin-right: 0; 41 | } 42 | } 43 | } 44 | 45 | .rooms-grid-load-more{ 46 | border-radius: 3px; 47 | border: 1px solid $light-grey; 48 | padding: $spacing; 49 | display: block; 50 | text-align: center; 51 | } 52 | 53 | ////////////////////////////////////////////////////// 54 | // Rooms Item // 55 | ////////////////////////////////////////////////////// 56 | 57 | .rooms-item{ 58 | 59 | } 60 | 61 | .rooms-item-image{ 62 | position: relative; 63 | margin-bottom: $spacing/2; 64 | img{ 65 | display: block; 66 | width: 100%; 67 | } 68 | } 69 | 70 | .rooms-item-price{ 71 | position: absolute; 72 | top: 0; 73 | left: 0; 74 | bottom: 0; 75 | right: 0; 76 | display: flex; 77 | flex-direction: column; 78 | justify-content: flex-end; 79 | background: linear-gradient(rgba(0,0,0,0) 0%, rgba(0,0,0,0) 50%, rgba(0,0,0,0.6) 80%); 80 | div{ 81 | color: white; 82 | display: block; 83 | padding: 10px; 84 | } 85 | } 86 | 87 | .rooms-item-name{ 88 | font-size: 1rem; 89 | } 90 | 91 | .rooms-item-city{ 92 | font-size: 0.8rem; 93 | } 94 | 95 | ////////////////////////////////////////////////////// 96 | // Rooms Page // 97 | ////////////////////////////////////////////////////// 98 | 99 | .rooms-contents{ 100 | display: flex; 101 | } 102 | 103 | .rooms-main{ 104 | flex: 1; 105 | margin-right: $spacing*2; 106 | } 107 | 108 | .rooms-section{ 109 | margin-bottom: $spacing * 2; 110 | } 111 | 112 | .rooms-hero-image{ 113 | margin-bottom: $spacing*2; 114 | img{ 115 | display: block; 116 | width: 100%; 117 | cursor: pointer; 118 | } 119 | } 120 | 121 | .rooms-photos-modal{ 122 | display: flex; 123 | justify-content: center; 124 | align-items: center; 125 | max-width: 900px; 126 | .modal-content{ 127 | background: none; 128 | border: none; 129 | } 130 | .rooms-photos{ 131 | position: relative; 132 | } 133 | } 134 | 135 | .rooms-photos-previous, .rooms-photos-next{ 136 | position: absolute; 137 | top: 50%; 138 | margin-top: -20px; 139 | height: 40px; 140 | width: 40px; 141 | display: flex; 142 | justify-content: center; 143 | align-items: center; 144 | .icon{ 145 | display: block; 146 | color: white; 147 | font-size: 5rem; 148 | } 149 | &:hover, &:focus, &:visited{ 150 | color: white; 151 | text-decoration: none; 152 | } 153 | } 154 | .rooms-photos-previous{ 155 | left: -60px; 156 | } 157 | .rooms-photos-next{ 158 | right: -60px; 159 | } 160 | 161 | .rooms-name{ 162 | margin-bottom: $spacing/2; 163 | } 164 | .rooms-city{ 165 | margin-bottom: $spacing/2; 166 | font-size: 1rem; 167 | } 168 | .rooms-description{ 169 | margin-bottom: $spacing/2; 170 | } 171 | .rooms-amenities{ 172 | margin-bottom: $spacing/2; 173 | } 174 | 175 | .rooms-sidebar{ 176 | max-width: 30%; 177 | } 178 | 179 | .rooms-book{ 180 | margin-bottom: $spacing; 181 | } 182 | 183 | ////////////////////////////////////////////////////// 184 | // Rooms Search Results // 185 | ////////////////////////////////////////////////////// 186 | 187 | .rooms-search-results-map{ 188 | margin-bottom: $spacing; 189 | } 190 | 191 | ////////////////////////////////////////////////////// 192 | // Rooms Filters // 193 | ////////////////////////////////////////////////////// 194 | 195 | .rooms-search-filters{ 196 | margin-bottom: $spacing; 197 | } 198 | 199 | .rooms-search-filters-list{ 200 | list-style-type: none; 201 | padding: 0; 202 | margin: 0; 203 | display: flex; 204 | flex-wrap: wrap; 205 | } 206 | 207 | .rooms-search-filter{ 208 | width: calc((100% - #{$spacing * 2})/3); 209 | margin-right: $spacing; 210 | margin-bottom: $spacing/2; 211 | font-size: 0.9rem; 212 | &:nth-child(3n) { 213 | margin-right: 0; 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /packages/zensroom/lib/modules/bookings/schema.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | The Bookings schema 4 | 5 | http://docs.vulcanjs.org/schemas.html#Schemas 6 | 7 | */ 8 | 9 | import moment from 'moment'; 10 | import { Utils } from 'meteor/vulcan:core'; 11 | import Rooms from '../rooms/collection.js'; 12 | 13 | const schema = { 14 | // default properties 15 | 16 | _id: { 17 | type: String, 18 | optional: true, 19 | viewableBy: ['members'], 20 | }, 21 | createdAt: { 22 | type: Date, 23 | optional: true, 24 | viewableBy: ['members'], 25 | onInsert: (document, currentUser) => { 26 | return new Date(); 27 | } 28 | }, 29 | userId: { 30 | type: String, 31 | optional: true, 32 | viewableBy: ['members'], 33 | resolveAs: { 34 | fieldName: 'user', 35 | type: 'User', 36 | resolver: async (booking, args, { Users, currentUser }) => { 37 | const user = await Users.loader.load(booking.userId); 38 | return Users.restrictViewableFields(currentUser, Users, user); 39 | }, 40 | addOriginalField: true 41 | } 42 | }, 43 | 44 | roomId: { 45 | type: String, 46 | viewableBy: ['members'], 47 | insertableBy: ['members'], 48 | hidden: true, 49 | resolveAs: { 50 | fieldName: 'room', 51 | type: 'Room', 52 | resolver: async (booking, args, { Rooms, Users, currentUser }) => { 53 | const room = await Rooms.loader.load(booking.roomId); 54 | return Users.restrictViewableFields(currentUser, Rooms, room); 55 | }, 56 | addOriginalField: true 57 | }, 58 | }, 59 | 60 | startAt: { 61 | label: 'Check In Date', 62 | type: Date, 63 | viewableBy: ['members'], 64 | insertableBy: ['members'], 65 | editableBy: ['admins'], 66 | control: 'datetime', 67 | }, 68 | 69 | endAt: { 70 | label: 'Check Out Date', 71 | type: Date, 72 | viewableBy: ['members'], 73 | insertableBy: ['members'], 74 | editableBy: ['admins'], 75 | control: 'datetime', 76 | }, 77 | 78 | numberOfGuests: { 79 | label: 'Guests', 80 | type: Number, 81 | viewableBy: ['members'], 82 | insertableBy: ['members'], 83 | editableBy: ['admins'], 84 | }, 85 | 86 | amount: { 87 | label: 'Amount', 88 | type: Number, 89 | optional: true, 90 | viewableBy: ['members'], 91 | insertableBy: ['admins'], 92 | editableBy: ['admins'], 93 | onInsert: async document => { 94 | const room = await Rooms.queryOne(document.roomId, { fragmentName: 'RoomsItemFragment' }); 95 | const numberOfNights = moment(document.endAt).diff(moment(document.startAt), 'days'); 96 | const amount = room.pricePerNight * document.numberOfGuests * numberOfNights; 97 | return amount; 98 | }, 99 | }, 100 | 101 | paidAt: { 102 | type: Date, 103 | optional: true, 104 | viewableBy: ['members'], 105 | }, 106 | 107 | status: { 108 | type: Number, 109 | optional: true, 110 | viewableBy: ['members'], 111 | insertableBy: ['admins'], 112 | editableBy: ['admins'], 113 | control: 'select', 114 | form: { 115 | options: () => { 116 | return [ 117 | { 118 | value: 1, 119 | label: 'pending' 120 | }, 121 | { 122 | value: 2, 123 | label: 'approved' 124 | }, 125 | { 126 | value: 3, 127 | label: 'paid' 128 | }, 129 | { 130 | value: 4, 131 | label: 'rejected' 132 | }, 133 | ] 134 | }, 135 | }, 136 | onInsert: document => { 137 | return document.status || 1; 138 | }, 139 | }, 140 | 141 | // GraphQL-only fields 142 | 143 | pageUrl: { 144 | type: String, 145 | optional: true, 146 | resolveAs: { 147 | type: 'String', 148 | resolver: (booking, args, context) => { 149 | return `${Utils.getSiteUrl()}booking/${booking._id}`; 150 | }, 151 | } 152 | }, 153 | 154 | startAtFormatted: { 155 | label: 'Check In', 156 | type: String, 157 | optional: true, 158 | resolveAs: { 159 | type: 'String', 160 | resolver: (booking, args, context) => { 161 | return moment(booking.startAt).format('dddd, MMMM Do YYYY'); 162 | } 163 | } 164 | }, 165 | 166 | endAtFormatted: { 167 | label: 'Check Out', 168 | type: String, 169 | optional: true, 170 | resolveAs: { 171 | type: 'String', 172 | resolver: (booking, args, context) => { 173 | return moment(booking.endAt).format('dddd, MMMM Do YYYY'); 174 | } 175 | } 176 | }, 177 | 178 | paidAtFormatted: { 179 | label: 'Paid At', 180 | type: String, 181 | optional: true, 182 | resolveAs: { 183 | type: 'String', 184 | resolver: (booking, args, context) => { 185 | return booking.paidAt && moment(booking.paidAt).format('dddd, MMMM Do YYYY'); 186 | } 187 | } 188 | }, 189 | 190 | startAtFormattedShort: { 191 | label: 'Check In', 192 | type: String, 193 | optional: true, 194 | resolveAs: { 195 | type: 'String', 196 | resolver: (booking, args, context) => { 197 | return moment(booking.startAt).format('MM/DD/YY'); 198 | } 199 | } 200 | }, 201 | 202 | endAtFormattedShort: { 203 | label: 'Check Out', 204 | type: String, 205 | optional: true, 206 | resolveAs: { 207 | type: 'String', 208 | resolver: (booking, args, context) => { 209 | return moment(booking.endAt).format('MM/DD/YY'); 210 | } 211 | } 212 | }, 213 | 214 | paidAtFormattedShort: { 215 | label: 'Paid At', 216 | type: String, 217 | optional: true, 218 | resolveAs: { 219 | type: 'String', 220 | resolver: (booking, args, context) => { 221 | return booking.paidAt && moment(booking.paidAt).format('MM/DD/YY'); 222 | } 223 | } 224 | }, 225 | 226 | }; 227 | 228 | export default schema; -------------------------------------------------------------------------------- /packages/zensroom/lib/components/bookings/BookingsNewForm.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Form for inserting a new booking, wrapped with withNew HoC. 4 | 5 | http://docs.vulcanjs.org/mutations.html#Higher-Order-Components 6 | 7 | */ 8 | 9 | import React, { Component } from 'react'; 10 | import { Components, registerComponent, withCurrentUser, getFragment, getSetting, withMessages, withNew, addCallback } from 'meteor/vulcan:core'; 11 | import { withRouter } from 'react-router'; 12 | import compose from 'recompose/compose'; 13 | import DateTimePicker from 'react-datetime'; 14 | import Button from 'react-bootstrap/lib/Button'; 15 | import gql from 'graphql-tag'; 16 | import moment from 'moment'; 17 | import { intlShape, FormattedMessage } from 'meteor/vulcan:i18n'; 18 | import { Form, Input } from 'formsy-react-components'; 19 | 20 | import Bookings from '../../modules/bookings/collection'; 21 | import withUnavailableDates from '../../containers/withUnavailableDatesContainer'; 22 | 23 | class BookingsNewForm extends Component { 24 | 25 | constructor() { 26 | super(); 27 | 28 | this.success = this.success.bind(this); 29 | this.submitForm = this.submitForm.bind(this); 30 | this.updateFromDate = this.updateFromDate.bind(this); 31 | this.updateToDate = this.updateToDate.bind(this); 32 | this.updateGuests = this.updateGuests.bind(this); 33 | this.isAvailable = this.isAvailable.bind(this); 34 | this.createNewBooking = this.createNewBooking.bind(this); 35 | 36 | this.state = { 37 | from: null, 38 | to: null, 39 | numberOfGuests: 1, 40 | disabled: false 41 | } 42 | } 43 | 44 | updateFromDate(date) { 45 | this.setState({ from: date }); 46 | if (date > this.state.to) { 47 | this.setState({ 48 | to: date.clone().add('days', 1) 49 | }) 50 | } 51 | } 52 | 53 | updateToDate(date) { 54 | this.setState({ to: date }); 55 | } 56 | 57 | updateGuests(name, value) { 58 | this.setState({ 59 | numberOfGuests: parseInt(value || 1) 60 | }); 61 | } 62 | 63 | /* 64 | 65 | Helper to tell if a date is available 66 | 67 | */ 68 | isAvailable(mDate) { 69 | const unavailableDates = this.props.unavailableDates && this.props.unavailableDates.map(date => moment(new Date(date)).startOf('day').toString()); 70 | return !_.contains(unavailableDates, mDate.toString()) 71 | } 72 | 73 | /* 74 | 75 | Form submit handler 76 | 77 | */ 78 | submitForm(data) { 79 | 80 | // disable form to prevent multiple submissions 81 | this.setState({ disabled: true }); 82 | 83 | // if fields are missing, show message and abort submission 84 | if (!this.state.from || !this.state.to || !this.state.numberOfGuests) { 85 | alert(this.context.intl.formatMessage({id: 'bookings.please_fill_in_all_fields'})); 86 | this.setState({ disabled: false }); 87 | return; 88 | } 89 | 90 | // create alias for this.createNewBooking 91 | const createNewBooking = this.createNewBooking; 92 | 93 | // create callback function and set it to only run once 94 | function createNewBookingCallback() { 95 | createNewBooking(data); 96 | return {}; 97 | } 98 | createNewBookingCallback.runOnce = true; 99 | 100 | if (this.props.currentUser) { // user is logged in 101 | 102 | this.createNewBooking(data); 103 | 104 | } else { // user is not logged in 105 | 106 | // add postlogin callback, go to sign-up page, show message 107 | addCallback('users.postlogin', createNewBookingCallback); 108 | this.props.router.push('/sign-up'); 109 | this.props.flash(this.context.intl.formatMessage({id: 'users.please_sign_up_log_in'}), 'error'); 110 | 111 | } 112 | } 113 | 114 | /* 115 | 116 | Trigger new booking mutation and then call this.success() 117 | 118 | */ 119 | createNewBooking(data) { 120 | console.log('// createNewBooking') 121 | console.log(data) 122 | this.props.newMutation({document: { 123 | startAt: this.state.from.toDate(), 124 | endAt: this.state.to.toDate(), 125 | numberOfGuests: this.state.numberOfGuests, 126 | roomId: this.props.room._id 127 | }}).then(result => this.success(result.data.BookingsNew)); 128 | } 129 | 130 | /* 131 | 132 | Success callback 133 | 134 | */ 135 | success(booking) { 136 | this.props.router.push({pathname: `/booking/${booking._id}`}); 137 | this.props.flash(this.context.intl.formatMessage({id: 'bookings.created'}), 'success'); 138 | } 139 | 140 | /* 141 | 142 | Render 143 | 144 | */ 145 | render() { 146 | 147 | const numberOfNights = this.state.from && this.state.to ? this.state.to.diff(this.state.from, 'days') : 0; 148 | const totalPrice = this.props.room.pricePerNight * this.state.numberOfGuests * numberOfNights; 149 | 150 | return ( 151 |
152 | 153 |
154 | 155 |

Total Price: {getSetting('defaultCurrency', '$')}{totalPrice}

156 | 157 |
158 | 159 | this.updateFromDate(newDate)} 161 | format={"x"} 162 | isValidDate={(currentDate, selectedDate) => { 163 | const yesterday = moment().subtract( 1, 'day' ); 164 | return currentDate.isAfter(yesterday) && this.isAvailable(currentDate); 165 | }} 166 | timeFormat={false} 167 | value={this.state.from} 168 | /> 169 |
170 | 171 |
172 | 173 | this.updateToDate(newDate)} 175 | format={"x"} 176 | isValidDate={(currentDate, selectedDate) => { 177 | const yesterday = moment().subtract( 1, 'day' ); 178 | return currentDate.isAfter(yesterday) && currentDate.isAfter(moment(this.state.from)) && this.isAvailable(currentDate); 179 | }} 180 | timeFormat={false} 181 | value={this.state.to} 182 | /> 183 |
184 | 185 |
186 | 187 | 188 |
189 | 190 | 191 | 192 |
193 | 194 |
195 | 196 | ) 197 | } 198 | } 199 | 200 | BookingsNewForm.contextTypes = { 201 | intl: intlShape 202 | }; 203 | 204 | const options = { 205 | collection: Bookings, 206 | fragment: gql` 207 | fragment BookingFragment on Booking { 208 | __typename 209 | _id 210 | createdAt 211 | userId 212 | roomId 213 | startAt 214 | endAt 215 | paidAt 216 | } 217 | ` 218 | } 219 | 220 | registerComponent('BookingsNewForm', BookingsNewForm, [withNew, options], withRouter, withMessages, withCurrentUser, withUnavailableDates); 221 | 222 | // export default compose( 223 | // withNew(options), 224 | // withRouter, 225 | // withMessages, 226 | // withCurrentUser, 227 | // withUnavailableDates, 228 | // )(BookingsNewForm) 229 | -------------------------------------------------------------------------------- /packages/zensroom/lib/modules/rooms/schema.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Rooms schema 4 | 5 | http://docs.vulcanjs.org/schemas.html#Schemas 6 | 7 | */ 8 | 9 | import FormsUpload from 'meteor/vulcan:forms-upload'; 10 | import { amenities, spaces } from '../data'; 11 | import { Utils } from 'meteor/vulcan:core'; 12 | 13 | const formGroups = { 14 | infos: { 15 | name: 'infos', 16 | label: 'Infos', 17 | order: 10, 18 | }, 19 | photos: { 20 | name: 'photos', 21 | label: 'Photos', 22 | order: 20, 23 | startCollapsed: true 24 | }, 25 | amenities: { 26 | name: 'amenities', 27 | label: 'Amenities', 28 | order: 30, 29 | startCollapsed: true 30 | }, 31 | address: { 32 | name: 'address', 33 | label: 'Address', 34 | order: 40, 35 | startCollapsed: true 36 | }, 37 | price: { 38 | name: 'price', 39 | label: 'Price', 40 | order: 50, 41 | startCollapsed: true 42 | } 43 | }; 44 | 45 | const schema = { 46 | 47 | // default properties 48 | 49 | _id: { 50 | type: String, 51 | optional: true, 52 | viewableBy: ['guests'], 53 | }, 54 | 55 | createdAt: { 56 | type: Date, 57 | optional: true, 58 | viewableBy: ['guests'], 59 | onInsert: (document, currentUser) => { 60 | return new Date(); 61 | } 62 | }, 63 | 64 | userId: { 65 | type: String, 66 | optional: true, 67 | viewableBy: ['guests'], 68 | resolveAs: { 69 | fieldName: 'user', 70 | type: 'User', 71 | resolver: (movie, args, context) => { 72 | return context.Users.findOne({ _id: movie.userId }, { fields: context.Users.getViewableFields(context.currentUser, context.Users) }); 73 | }, 74 | addOriginalField: true 75 | } 76 | }, 77 | 78 | // custom properties 79 | 80 | roomType: { 81 | label: 'Room Type', 82 | type: String, 83 | optional: false, 84 | viewableBy: ['guests'], 85 | insertableBy: ['members'], 86 | editableBy: ['members'], 87 | control: 'select', 88 | form: { 89 | options: [ 90 | {label: 'Entire Place', value: 'place'}, 91 | {label: 'Private Room', value: 'private'}, 92 | {label: 'Shared Room', value: 'shared'}, 93 | ] 94 | }, 95 | group: formGroups.info 96 | }, 97 | 98 | propertyType: { 99 | label: 'Type', 100 | type: String, 101 | optional: false, 102 | viewableBy: ['guests'], 103 | insertableBy: ['members'], 104 | editableBy: ['members'], 105 | control: 'select', 106 | form: { 107 | options: [ 108 | {label: 'Apartment', value: 'apartment'}, 109 | {label: 'House', value: 'house'}, 110 | {label: 'Other', value: 'other'}, 111 | ] 112 | }, 113 | group: formGroups.info 114 | }, 115 | 116 | name: { 117 | label: 'Name', 118 | type: String, 119 | optional: false, 120 | viewableBy: ['guests'], 121 | insertableBy: ['members'], 122 | editableBy: ['members'], 123 | group: formGroups.info, 124 | searchable: true, 125 | limit: 90, 126 | max: 90 127 | }, 128 | 129 | description: { 130 | label: 'Description', 131 | type: String, 132 | optional: false, 133 | viewableBy: ['guests'], 134 | insertableBy: ['members'], 135 | editableBy: ['members'], 136 | control: 'textarea', 137 | group: formGroups.info, 138 | searchable: true, 139 | limit: 300, 140 | max: 300 141 | }, 142 | 143 | rules: { 144 | label: 'House Rules', 145 | type: String, 146 | optional: false, 147 | viewableBy: ['guests'], 148 | insertableBy: ['members'], 149 | editableBy: ['members'], 150 | control: 'textarea', 151 | group: formGroups.info 152 | }, 153 | 154 | bedsNumber: { 155 | label: 'Number of Beds', 156 | type: Number, 157 | optional: false, 158 | viewableBy: ['guests'], 159 | insertableBy: ['members'], 160 | editableBy: ['members'], 161 | group: formGroups.info, 162 | control: 'number' 163 | }, 164 | 165 | guestsNumber: { 166 | label: 'Number of Guests', 167 | type: Number, 168 | optional: false, 169 | viewableBy: ['guests'], 170 | insertableBy: ['members'], 171 | editableBy: ['members'], 172 | group: formGroups.info, 173 | control: 'number' 174 | }, 175 | 176 | photos: { 177 | label: 'Photos', 178 | type: Array, 179 | optional: false, 180 | viewableBy: ['guests'], 181 | insertableBy: ['members'], 182 | editableBy: ['members'], 183 | control: FormsUpload, // use the FormsUpload form component 184 | form: { 185 | options: { 186 | preset: 'zensroom' 187 | }, 188 | }, 189 | group: formGroups.photos 190 | }, 191 | 192 | 'photos.$': { 193 | type: Object, 194 | blackbox: true, 195 | optional: true, 196 | }, 197 | 198 | amenities: { 199 | label: 'Amenities', 200 | type: Array, 201 | optional: true, 202 | viewableBy: ['guests'], 203 | insertableBy: ['members'], 204 | editableBy: ['members'], 205 | control: 'checkboxgroup', 206 | form: { 207 | options: amenities 208 | }, 209 | group: formGroups.amenities 210 | }, 211 | 212 | 'amenities.$': { 213 | type: String, 214 | optional: true, 215 | }, 216 | 217 | spaces: { 218 | label: 'Spaces', 219 | type: Array, 220 | optional: true, 221 | viewableBy: ['guests'], 222 | insertableBy: ['members'], 223 | editableBy: ['members'], 224 | control: 'checkboxgroup', 225 | form: { 226 | options: spaces 227 | }, 228 | group: formGroups.amenities 229 | }, 230 | 231 | 'spaces.$': { 232 | type: String, 233 | optional: true, 234 | }, 235 | 236 | country: { 237 | type: String, 238 | optional: true, 239 | viewableBy: ['guests'], 240 | insertableBy: ['members'], 241 | editableBy: ['members'], 242 | group: formGroups.address 243 | }, 244 | 245 | zipCode: { 246 | type: String, 247 | optional: true, 248 | viewableBy: ['guests'], 249 | insertableBy: ['members'], 250 | editableBy: ['members'], 251 | group: formGroups.address 252 | }, 253 | 254 | state: { 255 | type: String, 256 | optional: true, 257 | viewableBy: ['guests'], 258 | insertableBy: ['members'], 259 | editableBy: ['members'], 260 | group: formGroups.address 261 | }, 262 | 263 | city: { 264 | type: String, 265 | optional: true, 266 | viewableBy: ['guests'], 267 | insertableBy: ['members'], 268 | editableBy: ['members'], 269 | group: formGroups.address 270 | }, 271 | 272 | address: { 273 | type: String, 274 | optional: true, 275 | viewableBy: ['guests'], 276 | insertableBy: ['members'], 277 | editableBy: ['members'], 278 | group: formGroups.address 279 | }, 280 | 281 | address2: { 282 | type: String, 283 | optional: true, 284 | viewableBy: ['guests'], 285 | insertableBy: ['members'], 286 | editableBy: ['members'], 287 | group: formGroups.address 288 | }, 289 | 290 | geoData: { 291 | type: Object, 292 | blackbox: true, 293 | optional: true, 294 | }, 295 | 296 | location: { 297 | type: Object, 298 | blackbox: true, 299 | optional: true, 300 | viewableBy: ['guests'] 301 | }, 302 | 303 | pricePerNight: { 304 | type: Number, 305 | optional: false, 306 | label: 'Price per night', 307 | viewableBy: ['guests'], 308 | insertableBy: ['members'], 309 | editableBy: ['members'], 310 | group: formGroups.price, 311 | control: 'number' 312 | }, 313 | 314 | // GraphQL-only fields 315 | 316 | pageUrl: { 317 | type: String, 318 | optional: true, 319 | resolveAs: { 320 | type: 'String', 321 | resolver: (room, args, context) => { 322 | return `${Utils.getSiteUrl()}/room/${room._id}`; 323 | }, 324 | } 325 | } 326 | 327 | }; 328 | 329 | export default schema; -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | //.jshintrc 2 | { 3 | // JSHint Meteor Configuration File 4 | // Match the Meteor Style Guide 5 | // 6 | // By @raix with contributions from @aldeed and @awatson1978 7 | // Source https://github.com/raix/Meteor-jshintrc 8 | // 9 | // See http://jshint.com/docs/ for more details 10 | 11 | "maxerr" : 50, // {int} Maximum error before stopping 12 | 13 | // Enforcing 14 | "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.) 15 | "camelcase" : true, // true: Identifiers must be in camelCase 16 | // "curly" : true, // true: Require {} for every new block or scope 17 | "eqeqeq" : true, // true: Require triple equals (===) for comparison 18 | "forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty() 19 | "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` 20 | "indent" : 2, // {int} Number of spaces to use for indentation 21 | "latedef" : false, // true: Require variables/functions to be defined before being used 22 | "newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()` 23 | "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` 24 | "noempty" : true, // true: Prohibit use of empty blocks 25 | "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment) 26 | "plusplus" : false, // true: Prohibit use of `++` & `--` 27 | "quotmark" : false, // Quotation mark consistency: 28 | // false : do nothing (default) 29 | // true : ensure whatever is used is consistent 30 | // "single" : require single quotes 31 | // "double" : require double quotes 32 | "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) 33 | "unused" : true, // true: Require all defined variables be used 34 | "strict" : false, // true: Requires all functions run in ES5 Strict Mode 35 | "trailing" : true, // true: Prohibit trailing whitespaces 36 | "maxparams" : false, // {int} Max number of formal params allowed per function 37 | "maxdepth" : false, // {int} Max depth of nested blocks (within functions) 38 | "maxstatements" : false, // {int} Max number statements per function 39 | "maxcomplexity" : false, // {int} Max cyclomatic complexity per function 40 | "maxlen" : false, // {int} Max number of characters per line 41 | 42 | // Relaxing 43 | "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) 44 | "boss" : false, // true: Tolerate assignments where comparisons would be expected 45 | "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. 46 | "eqnull" : false, // true: Tolerate use of `== null` 47 | "es5" : false, // true: Allow ES5 syntax (ex: getters and setters) 48 | "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`) 49 | "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) 50 | // (ex: `for each`, multiple try/catch, function expression…) 51 | "evil" : false, // true: Tolerate use of `eval` and `new Function()` 52 | "expr" : false, // true: Tolerate `ExpressionStatement` as Programs 53 | "funcscope" : false, // true: Tolerate defining variables inside control statements" 54 | "globalstrict" : true, // true: Allow global "use strict" (also enables 'strict') 55 | "iterator" : false, // true: Tolerate using the `__iterator__` property 56 | "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block 57 | "laxbreak" : false, // true: Tolerate possibly unsafe line breakings 58 | "laxcomma" : false, // true: Tolerate comma-first style coding 59 | "loopfunc" : false, // true: Tolerate functions being defined in loops 60 | "multistr" : false, // true: Tolerate multi-line strings 61 | "proto" : false, // true: Tolerate using the `__proto__` property 62 | "scripturl" : false, // true: Tolerate script-targeted URLs 63 | "smarttabs" : false, // true: Tolerate mixed tabs/spaces when used for alignment 64 | "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` 65 | "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation 66 | "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` 67 | "validthis" : false, // true: Tolerate using this in a non-constructor function 68 | 69 | // Environments 70 | "browser" : true, // Web Browser (window, document, etc) 71 | "couch" : false, // CouchDB 72 | "devel" : true, // Development/debugging (alert, confirm, etc) 73 | "dojo" : false, // Dojo Toolkit 74 | "jasmine" : true, // Jasmine testing framework 75 | "jquery" : false, // jQuery 76 | "mootools" : false, // MooTools 77 | "node" : false, // Node.js 78 | "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) 79 | "prototypejs" : false, // Prototype and Scriptaculous 80 | "rhino" : false, // Rhino 81 | "worker" : false, // Web Workers 82 | "wsh" : false, // Windows Scripting Host 83 | "yui" : false, // Yahoo User Interface 84 | //"meteor" : false, // Meteor.js 85 | 86 | // Legacy 87 | "nomen" : false, // true: Prohibit dangling `_` in variables 88 | "onevar" : false, // true: Allow only one `var` statement per function 89 | "passfail" : false, // true: Stop on first error 90 | "white" : false, // true: Check against strict whitespace and indentation rules 91 | 92 | // Custom globals, from http://docs.meteor.com, in the order they appear there 93 | "globals" : { 94 | "Meteor": false, 95 | "DDP": false, 96 | "Mongo": false, //Meteor.Collection renamed to Mongo.Collection 97 | "Session": false, 98 | "Accounts": false, 99 | "Template": false, 100 | "Blaze": false, //UI is being renamed Blaze 101 | "UI": false, 102 | "Match": false, 103 | "check": false, 104 | "Tracker": false, //Deps renamed to Tracker 105 | "Deps": false, 106 | "ReactiveVar": false, 107 | "EJSON": false, 108 | "HTTP": false, 109 | "Email": false, 110 | "Assets": false, 111 | "Handlebars": false, // https://github.com/meteor/meteor/wiki/Handlebars 112 | "Package": false, 113 | 114 | // Meteor internals 115 | "DDPServer": false, 116 | "global": false, 117 | "Log": false, 118 | "MongoInternals": false, 119 | "process": false, 120 | "WebApp": false, 121 | "WebAppInternals": false, 122 | 123 | // globals useful when creating Meteor packages 124 | "Npm": false, 125 | "Tinytest": false, 126 | 127 | // Meteor packages 128 | "$": false, 129 | "_": false, 130 | "__": false, 131 | "AccountsTemplates": false, 132 | "AutoForm": false, 133 | "Avatar": false, 134 | "Cookie": false, 135 | "FastRender": false, 136 | "Gravatar": false, 137 | "Herald": false, 138 | "Kadira": false, 139 | "moment": false, 140 | "Random": false, 141 | "RouteController": false, 142 | "Router": false, 143 | "SEO": false, 144 | "SimpleSchema": false, 145 | "SubsManager": false, 146 | "SyncedCron": false, 147 | "TAPi18n": false, 148 | "FlowRouter": false, 149 | 150 | // Telescope collections 151 | "Categories": true, 152 | "Comments": true, 153 | "Feeds": true, 154 | "Invites": true, 155 | "Migrations": true, 156 | "Posts": true, 157 | "Releases": true, 158 | "Searches": true, 159 | "Users": true, 160 | 161 | // Telescope objects 162 | "buildAndSendEmail": true, 163 | "buildEmailNotification": true, 164 | "buildEmailTemplate": true, 165 | "compareVersions": true, 166 | "coreSubscriptions": true, 167 | "daysPerPage": true, 168 | "deleteDummyContent": true, 169 | "Events": true, 170 | "fetchFeeds": true, 171 | "getCategoryUrl": true, 172 | "getEmailTemplate": true, 173 | "getPostCategories": true, 174 | "getTemplate": true, 175 | "getVotePower": true, 176 | "i18n": true, 177 | "InviteSchema": true, 178 | "logEvent": true, 179 | "marked": true, 180 | "Messages": true, 181 | "Pages": true, 182 | "sendEmail": true, 183 | "serveAPI": true, 184 | "Settings": true, 185 | "Telescope": true, 186 | "templates": true, 187 | "themeSettings": true 188 | }, 189 | "esnext": true 190 | } 191 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | ## v1.1.0 => vNEXT 2 | 3 | We now keep track of the version thanks to [`release`](https://github.com/zeit/release). 4 | 5 | To see the updates, check the [Nova's repo releases](https://github.com/TelescopeJS/Telescope/releases). 6 | 7 | ## v1.0.0 8 | - A lot of breaking changes: this is the Apollo/GraphQL version of Telescope Nova. [You can find the documentation here](http://nova-docs.telescopeapp.org/). 9 | 10 | ## v0.27.5 11 | 12 | - Nova is now powered by Meteor 1.4.2.3. 13 | - Newsletter settings + banner behavior fixed ([PR #1513](https://github.com/TelescopeJS/Telescope/pull/1513), thanks [@bengott](https://github.com/bengott)). **⚠️ If you are updating from a previous version of Nova/Legacy, [make sure to run migration script in your app on a server file!](https://github.com/bengott/Telescope/blob/e714aab27b323aee5ddbb97a5ece01a3cdc0e76f/packages/_nova-migrations/lib/server/migrations.js#L746-L782)** 14 | - Fix ESLint issues: revert comment in deep function, support for JSX files, clean up code ([PR #1511](https://github.com/TelescopeJS/Telescope/pull/1511), [PR #1512](https://github.com/TelescopeJS/Telescope/pull/1512), [PR #1515](https://github.com/TelescopeJS/Telescope/pull/1515), thanks [@comus](https://github.com/comus)). 15 | - Remove legacy packages not used anymore `telescope:migrations` & `telescope:invites`. 16 | - Packages with **_** are unmaintained & necessary Meteor packages for Nova to run. We have added small patches to them: `meteorhacks:inject-data` & `react-router:react-router-ssr`. 17 | 18 | ## v0.27.4 19 | 20 | - Nova is now powered by Meteor 1.4.2.1, which provides among other cool features super fast build time! Some NPM dependencies changed: **be sure to run `npm install` again!** 21 | - Fix typo in class name `posts-list-header-categories` ([PR #1487](https://github.com/TelescopeJS/Telescope/pull/1487), thanks [@seanjsong](https://github.com/seanjsong)). 22 | - Make `document` property available to all form components, but don't pass it down to standard input controls to avoid error. 23 | - Do not try to init legacy `Settings` collection client-side: this was an annoying warning that you may have got telling something about a forbidden insert. 24 | - Add reset password components and route ([PR #1491](https://github.com/TelescopeJS/Telescope/pull/1491), thanks [@malively](https://github.com/malively)). 25 | - Add internationalization messages for "no more posts", "no results" and "load more days" ([PR #1499](https://github.com/TelescopeJS/Telescope/pull/1499), thanks [@qge](https://github.com/qge)). 26 | - No more duplicate slugs if a user signs up with an external service (Facebook, Twitter, ..) and another user signs up with a username being the same as the other user (modification on `Telescope.utils.getUnsudedSlug` to handle edge case on `Users` collection). 27 | - Somebody can remove their account themselves again: `users.remove` fixed at the level of the permissions and related callbacks. 28 | - Server-side rendering / data-injection is fixed thanks to a `meteorhacks:inject-data` fork added locally in the packages (`nova-inject-data` folder, [see here for more info](https://github.com/TelescopeJS/Telescope/commit/f988686653b21896c3f5d321f30c34c1b5778628#diff-8f4ee0b18f5c4673b79684ee3c7d2430)) 29 | - Add simplified chinese (`zh-CN`) translation package to the README ([PR #1503](https://github.com/TelescopeJS/Telescope/pull/1503), thanks [@qge](https://github.com/qge)). 30 | - Only show comment reply button for logged in users ([PR #1504](https://github.com/TelescopeJS/Telescope/pull/1504), thanks [@qge](https://github.com/qge)). 31 | - Fix React `setState` race condition on `NovaForm` autofilled values ([PR #1507](https://github.com/TelescopeJS/Telescope/pull/1507), thanks [@sherryxiao1988](https://github.com/sherryxiao1988)). 32 | - Fix ESLint config: you can lint your project with `npm run lint`! It is based on `eslint:recommended` + `meteor` extends ([PR #1474](https://github.com/TelescopeJS/Telescope/pull/1474), thanks [@moimikey](https://github.com/moimikey)). 33 | 34 | ## v0.27.3 35 | 36 | - Explain with more details how to deploy with Meteor Up (PR [#1456](https://github.com/TelescopeJS/Telescope/pull/1456), thanks [@asmita005](https://github.com/asmita005)!). 37 | - Add slug to `newPendingPost` notifications, fixes [#1254](https://github.com/TelescopeJS/Telescope/issues/1254). 38 | - Ensure slug unicity on user's slug as done as category's slug (use of `Telescope.utils.getUnusedSlug`), fixes [#1213](https://github.com/TelescopeJS/Telescope/issues/1213). 39 | - Remove some dead code from Telescope Legacy. 40 | - Use of Comment's `getPageUrl` helper in `nova:rss`. 41 | - Prefer `Users` namespace to `Meteor.users` in active packages. 42 | - If you used the property `autoform` on your custom fields, it's now entitled `form`. This was an old reference to [AutoForm](https://github.com/aldeed/meteor-autoform) used by Telescope Legacy. We will give you a console warning if you still use it to advice you to change it. 43 | - Fix errors on `nova:forms`: callbacks from components wrapping a `NovaForm` (ex: `ModalTrigger`) are not fired anymore when it has already been unmounted. 44 | - Fix errors when logging out from the "profile check modal" (`UsersProfileCheck`). 45 | - Prevent errors when creating/editing a category with custom fields (load order of smart methods with extended schema). 46 | - The callback on `nova:subscribe` related to categories has been updated to prevent a user from receiving multiple emails if he/she is subscribed to multiple categories (PR [#1466](https://github.com/TelescopeJS/Telescope/pull/1466), thanks [@chptung](https://github.com/chptung)). 47 | - You can now submit a post/comments (or any `NovaForm` comp) with CMD + Enter / Ctrl + Enter shortcuts (PR [#1472](https://github.com/TelescopeJS/Telescope/pull/1472), thanks [@aszx87410](https://github.com/aszx87410)). 48 | - You can run Telescope Nova inside Docker without deploying (see [this awesome guide](http://spartatek.se/meteor_blog/docker/2016/01/12/running-telescope.html)), fixes [#1477](https://github.com/TelescopeJS/Telescope/issues/1477) 49 | - Add `flex-wrap: wrap;` to posts-categories class for better styling if a user creates a post with too many categories (PR [#1469](https://github.com/TelescopeJS/Telescope/pull/1469), thanks [@chptung](https://github.com/chptung)). 50 | 51 | **Changes that may break some parts of your app:** 52 | - Some callbacks have been renamed for consistency purposes: `postsParameters` becomes `posts.parameters`, `profileCompletedAsync` becomes `users.profileCompleted.async`, `profileCompletedChecks` becomes `users.profileCompleted.sync`, `onCreateUserAsync` becomes `users.new.async`, `onCreateUser` becomes `users.new.sync`, `UsersEdit` becomes `users.edit.sync`, `UsersEditAsync` becomes `users.edit.async`. 53 | - The use of `react-bootstrap@0.30.3` is now forced in `package.json`: the latest versions break the dropdown at the moment (see [#1463](https://github.com/TelescopeJS/Telescope/issues/1463)). You should re-run `npm install` if you update from a previous version. 54 | - The `currentUser` props has been removed, the current user is explicitly passed through the context as a matter of consistency across the app. If one of your custom components extending one of `nova:base-components` used `currentUser` as a props, you should update it to use it via the context and add the corresponding contextTypes. See commit [b04cb52](https://github.com/TelescopeJS/Telescope/commit/b04cb5247027fc431f7aa1704ef823ac8ce5fdd1). 55 | 56 | ## v0.27.2 57 | 58 | - Move `updateCurrentValue` function from `propTypes` to `contextTypes` in the datetime picker`DateTime` (`nova:forms`) ([#1449](https://github.com/TelescopeJS/Telescope/issues/1449)). 59 | - Check duplicate links on post's edit [(#247](https://github.com/TelescopeJS/Telescope/issues/247)). 60 | - Cloudinary images from `nova:cloudinary` are now served over HTTPS ([#1224](https://github.com/TelescopeJS/Telescope/issues/1224)). 61 | - Add year and name to licence ([#1117](https://github.com/TelescopeJS/Telescope/issues/1117)). 62 | - Clean Legacy's issues & PRs. Be ready for the [Hacktoberfest](https://hacktoberfest.digitalocean.com/)!! 🍻 63 | 64 | ## v0.27.1 65 | 66 | - Nova uses now React 15.3.x with associated Node modules, besides it prevents unknown prop warnings ([React docs](https://facebook.github.io/react/warnings/unknown-prop.html)). We still depends on `react-meteor-data` and mixins to load data (thanks @MHerszak for careful watch!), we may move soon to Apollo (contributions welcomed on `[apollo](https://github.com/TelescopeJS/Telescope/tree/apollo)` branch). 67 | - README updated. On deployment recommandations: you should go with Mup 1.0.3 ([repo](https://github.com/kadirahq/meteor-up)), MupX is not compatible with Meteor 1.4 ; on 3rd-party packages section, you can now upload images to a CDN ([package](https://github.com/xavcz/nova-forms-upload)). 68 | - The 404 Not Found route has been brought back to `nova:base-routes`, you can customize its shape by editing `Error404.jsx` (`nova:base-components`). 69 | - Added support for a custom CSS class for `SubscribeTo` component. 70 | - No more global variable in `nova:api` (the last one? \o/). 71 | - Fix a version problem with `fourseven:scss`, now running on 3.9.0. 72 | - Remove unnecessary NPM dependency on `load-script` (thanks [@MHerszak](https://github.com/mherszak)!). 73 | - You can now run Nova in Brazilian Portuguese by [adding this package](https://github.com/lukasag/nova-i18n-pt-br) (thanks [@lukasag](https://github.com/lukasag)!). 74 | - Added support for a `defaultValue` property in `nova:forms`. You can define it in your custom fields, it will be added if no value nor prefilled value is defined (thanks [@beeva-franciscocalle](https://github.com/beeva-franciscocalle)!). 75 | - Fixed edge bug when users don't have an `username`, use `displayName` instead (thanks [@jeffreywyman](https://github.com/jeffreywyman)!). 76 | 77 | ## v0.27.0 78 | 79 | - Remove Telescope global variable. 80 | - Update to Meteor 1.4. 81 | - A user can now subscribe to any collection with `nova:subscribe` package ([docs](https://github.com/TelescopeJS/Telescope/tree/master/packages/nova-subscribe)) and a reusable `SubscribeTo` component (thanks [@schabluk](https://github.com/schabluk)!). 82 | 83 | *The rest of the modifications are not yet documented, you can [browse the commits history from there](https://github.com/TelescopeJS/Telescope/commits/2b34713c0b6dbf094668f8a87d007443a1e2c580).* 84 | 85 | ## v0.26.5 86 | 87 | - Creation of a permissions (groups) API for the users. 88 | - The routes are now more easy to customize, [see docs](https://github.com/TelescopeJS/Telescope/tree/master#routes). 89 | 90 | *The rest of the modifications are not yet documented, you can [browse the commits history from there](https://github.com/TelescopeJS/Telescope/commits/cfc52b1158f3dd9cfc98ef5081f558112dc3c3cc).* 91 | 92 | ## v0.26.4 93 | 94 | - Collections are not globals anymore, you need to import them in order to use them. 95 | - NovaForm has been improved with a placeholder options for text fields and you can enhance any field with components placed before and after it. 96 | 97 | *The rest of the modifications are not yet documented, you can [browse the commits history from there](https://github.com/TelescopeJS/Telescope/commits/4f61940b07c48c6b3c7f13a47002c0199652a346).* 98 | 99 | ## v0.26.3 100 | 101 | - Switch from FlowRouter to React-Router v3. 102 | 103 | *The rest of the modifications are not yet documented, you can [browse the commits history from there](https://github.com/TelescopeJS/Telescope/commits/7b8624f709b6130fa8f93a141775491dc2455bbf).* 104 | 105 | ## v0.26.2 106 | 107 | - Made component names more consistent; Collection names (“Posts”, “Comments”, etc.) are **always plural** in component names. 108 | - Routes now live in their own package (`nova:base-routes`). 109 | - The search now searches in the `excerpt` field, not `body`, because `body` is not published to the client (and searches would give different results on client and server). 110 | - Removed option to manually set a post's author. 111 | - The Embedly thumbnail feature now includes a "clear thumbnail" link to remove it and an option to enter a URL manually. 112 | - There is now an autofill tags component you can optionally include and use with `meteor add nova:forms-tags` (see Embedly package custom fields for how to use custom components in forms). 113 | - You can now see a post's ID and stats in the post edit form if you're an admin. 114 | - Fixed bug (I hope?) where daily view would become messed up when client and server were on different timezones. 115 | - Now showing a user's posts on their profile page. 116 | - Added soft delete for comments (thanks [@justintime4tea](https://github.com/justintime4tea)!). 117 | - Fixed posts notifications bugs. 118 | - Got rid of a lot of Meteor packages in favor of NPM equivalents. 119 | 120 | ## v0.25.7 121 | 122 | First Nova version. 123 | --------------------------------------------------------------------------------