├── lib ├── assets │ └── .keep └── tasks │ └── .keep ├── public ├── favicon.ico ├── apple-touch-icon.png ├── apple-touch-icon-precomposed.png ├── robots.txt ├── 500.html ├── 422.html └── 404.html ├── Procfile ├── app ├── models │ ├── concerns │ │ └── .keep │ ├── guest.rb │ ├── host.rb │ ├── application_record.rb │ ├── event.rb │ └── user.rb ├── controllers │ ├── concerns │ │ └── .keep │ ├── application_controller.rb │ └── api_controller.rb ├── helpers │ └── application_helper.rb └── graph │ ├── meetup_event_planner_schema.rb │ ├── object_schema.rb │ ├── types │ ├── host_input_type.rb │ ├── guest_input_type.rb │ ├── host_type.rb │ ├── guest_type.rb │ ├── event_type_enum.rb │ ├── mutation_type.rb │ ├── user_signup_input_type.rb │ ├── user_type.rb │ ├── profile_input_type.rb │ ├── event_input_type.rb │ ├── auth_user_type.rb │ ├── event_type.rb │ └── query_type.rb │ ├── fields │ └── fetch_field.rb │ └── mutations │ ├── event_mutations.rb │ └── user_mutations.rb ├── vendor └── assets │ ├── javascripts │ └── .keep │ └── stylesheets │ └── .keep ├── client ├── Procfile ├── config │ ├── testing │ │ ├── __mocks__ │ │ │ └── .gitkeep │ │ └── preprocessor.js │ ├── generators │ │ ├── component │ │ │ ├── styles.scss.hbs │ │ │ ├── export.js.hbs │ │ │ ├── test.js.hbs │ │ │ ├── README.md.hbs │ │ │ ├── stateless.js.hbs │ │ │ ├── es6class.js.hbs │ │ │ └── index.js │ │ ├── container │ │ │ ├── styles.scss.hbs │ │ │ ├── export.js.hbs │ │ │ ├── constants.js.hbs │ │ │ ├── actions.js.hbs │ │ │ ├── reducer.test.js.hbs │ │ │ ├── reducer.js.hbs │ │ │ ├── README.md.hbs │ │ │ ├── actions.test.js.hbs │ │ │ ├── test.js.hbs │ │ │ └── index.js.hbs │ │ ├── page │ │ │ ├── export.js.hbs │ │ │ ├── index.module.scss.hbs │ │ │ ├── route.js.hbs │ │ │ ├── index.js.hbs │ │ │ ├── README.md.hbs │ │ │ └── index.js │ │ ├── utils │ │ │ ├── trimTemplateFile.js │ │ │ └── componentNameCheck.js │ │ └── index.js │ ├── templates │ │ └── _index.html │ └── webpack │ │ └── webpack.test.config.js ├── app │ └── src │ │ ├── containers │ │ ├── EventContainer │ │ │ ├── index.module.scss │ │ │ ├── README.md │ │ │ └── tests │ │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.js.snap │ │ │ │ └── index.test.js │ │ ├── LoginContainer │ │ │ ├── index.module.scss │ │ │ ├── README.md │ │ │ ├── constants.js │ │ │ ├── tests │ │ │ │ ├── reducer.test.js │ │ │ │ ├── index.test.js │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── index.test.js.snap │ │ │ │ └── actions.test.js │ │ │ ├── utils │ │ │ │ └── validation.js │ │ │ ├── actions.js │ │ │ └── reducer.js │ │ ├── SignupContainer │ │ │ ├── index.module.scss │ │ │ ├── README.md │ │ │ ├── constants.js │ │ │ ├── authUserDataFragment.js │ │ │ ├── tests │ │ │ │ ├── index.test.js │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── index.test.js.snap │ │ │ │ ├── actions.test.js │ │ │ │ └── reducer.test.js │ │ │ ├── utils │ │ │ │ └── validation.js │ │ │ ├── reducer.js │ │ │ └── actions.js │ │ ├── CreateEventContainer │ │ │ ├── index.module.scss │ │ │ ├── README.md │ │ │ ├── constants.js │ │ │ ├── tests │ │ │ │ ├── index.test.js │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── index.test.js.snap │ │ │ │ ├── actions.test.js │ │ │ │ └── reducer.test.js │ │ │ ├── reducer.js │ │ │ ├── utils │ │ │ │ └── validations.js │ │ │ └── actions.js │ │ ├── ProfileContainer │ │ │ ├── index.module.scss │ │ │ ├── README.md │ │ │ ├── authUserDataFragment.js │ │ │ ├── constants.js │ │ │ ├── tests │ │ │ │ ├── index.test.js │ │ │ │ └── __snapshots__ │ │ │ │ │ └── index.test.js.snap │ │ │ └── actions.js │ │ ├── EventsContainer │ │ │ ├── constants.js │ │ │ ├── actions.js │ │ │ ├── README.md │ │ │ ├── tests │ │ │ │ ├── actions.test.js │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── index.test.js.snap │ │ │ │ ├── reducer.test.js │ │ │ │ └── index.test.js │ │ │ └── reducer.js │ │ ├── LandingContainer │ │ │ ├── README.md │ │ │ ├── tests │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── index.test.js.snap │ │ │ │ └── index.test.js │ │ │ └── index.module.scss │ │ └── index.js │ │ ├── components │ │ ├── LoadingIndicator │ │ │ ├── index.module.scss │ │ │ ├── README.md │ │ │ ├── tests │ │ │ │ └── index.test.js │ │ │ └── index.js │ │ ├── Navbar │ │ │ ├── logo.png │ │ │ ├── README.md │ │ │ ├── index.module.scss │ │ │ ├── tests │ │ │ │ ├── index.test.js │ │ │ │ └── __snapshots__ │ │ │ │ │ └── index.test.js.snap │ │ │ └── index.js │ │ ├── App │ │ │ ├── constants.js │ │ │ ├── README.md │ │ │ ├── reducer.js │ │ │ ├── actions.js │ │ │ └── index.js │ │ ├── LoginForm │ │ │ ├── utils │ │ │ │ └── error.js │ │ │ ├── index.module.scss │ │ │ ├── tests │ │ │ │ ├── index.test.js │ │ │ │ └── mocks.js │ │ │ ├── README.md │ │ │ └── index.js │ │ ├── SignupForm │ │ │ ├── utils │ │ │ │ └── error.js │ │ │ ├── index.module.scss │ │ │ ├── README.md │ │ │ └── tests │ │ │ │ ├── index.test.js │ │ │ │ └── mocks.js │ │ ├── ToastMessage │ │ │ ├── tests │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── index.test.js.snap │ │ │ │ └── index.test.js │ │ │ ├── index.js │ │ │ └── README.md │ │ ├── SingleEvent │ │ │ ├── README.md │ │ │ ├── index.module.scss │ │ │ └── tests │ │ │ │ └── index.test.js │ │ ├── AppFooter │ │ │ ├── README.md │ │ │ ├── tests │ │ │ │ ├── index.test.js │ │ │ │ └── __snapshots__ │ │ │ │ │ └── index.test.js.snap │ │ │ ├── index.module.scss │ │ │ └── index.js │ │ ├── EventInfo │ │ │ ├── utils │ │ │ │ └── dates.js │ │ │ ├── index.module.scss │ │ │ ├── tests │ │ │ │ ├── index.test.js │ │ │ │ └── __snapshots__ │ │ │ │ │ └── index.test.js.snap │ │ │ ├── README.md │ │ │ └── index.js │ │ ├── AuthFormFooter │ │ │ ├── tests │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── index.test.js.snap │ │ │ │ └── index.test.js │ │ │ ├── README.md │ │ │ └── index.js │ │ ├── EventForm │ │ │ ├── utils │ │ │ │ └── error.js │ │ │ ├── index.module.scss │ │ │ ├── tests │ │ │ │ └── index.test.js │ │ │ └── README.md │ │ ├── ToolTip │ │ │ ├── tests │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── index.test.js.snap │ │ │ │ └── index.test.js │ │ │ ├── index.js │ │ │ └── README.md │ │ ├── EditableField │ │ │ ├── tests │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── index.test.js.snap │ │ │ │ └── index.test.js │ │ │ ├── index.module.scss │ │ │ ├── README.md │ │ │ └── index.js │ │ ├── UserProfile │ │ │ ├── README.md │ │ │ ├── tests │ │ │ │ ├── index.test.js │ │ │ │ └── mocks.js │ │ │ └── index.module.scss │ │ └── index.js │ │ ├── pages │ │ ├── LogoutPage │ │ │ ├── index.module.scss │ │ │ ├── README.md │ │ │ └── index.js │ │ ├── EventsPage │ │ │ ├── index.module.scss │ │ │ ├── README.md │ │ │ └── index.js │ │ ├── LandingPage │ │ │ ├── index.module.scss │ │ │ └── index.js │ │ ├── ProfilePage │ │ │ ├── index.module.scss │ │ │ ├── README.md │ │ │ └── index.js │ │ ├── NotFoundPage │ │ │ ├── index.module.scss │ │ │ └── index.js │ │ ├── EventPage │ │ │ ├── index.module.scss │ │ │ ├── README.md │ │ │ └── index.js │ │ ├── LoginPage │ │ │ ├── index.module.scss │ │ │ ├── README.md │ │ │ └── index.js │ │ ├── CreateEventPage │ │ │ ├── index.module.scss │ │ │ ├── README.md │ │ │ └── index.js │ │ ├── SignupPage │ │ │ ├── index.module.scss │ │ │ ├── README.md │ │ │ └── index.js │ │ └── index.js │ │ ├── index.js │ │ ├── apolloClient.js │ │ ├── reducers.js │ │ └── routes.js ├── .eslintignore ├── server.js ├── .gitattributes ├── .flowconfig ├── server │ ├── public │ │ ├── app │ │ │ └── src │ │ │ │ └── components │ │ │ │ └── Navbar │ │ │ │ └── logo.00e7c4cf372ade679404a6cf8f80704f.png │ │ └── index.html │ └── app.js ├── .eslintrc ├── index.html ├── .gitignore └── LICENSE ├── config ├── initializers │ ├── graphiql.rb │ ├── mime_types.rb │ ├── session_store.rb │ ├── application_controller_renderer.rb │ ├── filter_parameter_logging.rb │ ├── cookies_serializer.rb │ ├── backtrace_silencers.rb │ ├── wrap_parameters.rb │ ├── inflections.rb │ └── new_framework_defaults.rb ├── spring.rb ├── boot.rb ├── environment.rb ├── cable.yml ├── routes.rb ├── puma.rb ├── locales │ └── en.yml ├── application.rb └── environments │ ├── test.rb │ └── development.rb ├── .gitattributes ├── bin ├── bundle ├── web ├── rake ├── rails ├── spring ├── update └── setup ├── config.ru ├── db ├── migrate │ ├── 20161003004530_add_location_to_event.rb │ ├── 20161004010331_add_user_to_event.rb │ ├── 20161012003551_add_employer_to_user.rb │ ├── 20161003233357_add_auth_token_to_user.rb │ ├── 20161003005655_change_event_type_to_event_type.rb │ ├── 20161004003535_change_auth_token_on_user.rb │ ├── 20161002213314_add_host_to_event.rb │ ├── 20161002211618_add_attributes_to_user.rb │ ├── 20161002213058_create_hosts.rb │ ├── 20161004204156_add_avatar_to_user.rb │ ├── 20161003004858_create_guests.rb │ ├── 20161002211930_create_events.rb │ └── 20161002210703_devise_create_users.rb └── seeds.rb ├── Rakefile ├── spec ├── models │ ├── event_spec.rb │ ├── host_spec.rb │ ├── guest_spec.rb │ └── user_spec.rb ├── rails_helper.rb └── spec_helper.rb ├── .gitignore ├── LICENSE ├── Gemfile └── npm-debug.log /lib/assets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: bin/web 2 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/Procfile: -------------------------------------------------------------------------------- 1 | web: node server.js 2 | -------------------------------------------------------------------------------- /client/config/testing/__mocks__/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/app/src/containers/EventContainer/index.module.scss: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /client/.eslintignore: -------------------------------------------------------------------------------- 1 | app/build/ 2 | app/dist/ 3 | webpack.config.*.js 4 | -------------------------------------------------------------------------------- /app/models/guest.rb: -------------------------------------------------------------------------------- 1 | class Guest < ApplicationRecord 2 | belongs_to :event 3 | end 4 | -------------------------------------------------------------------------------- /app/models/host.rb: -------------------------------------------------------------------------------- 1 | class Host < ApplicationRecord 2 | has_many :events 3 | end 4 | -------------------------------------------------------------------------------- /client/app/src/containers/LoginContainer/index.module.scss: -------------------------------------------------------------------------------- 1 | .login { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /client/app/src/containers/SignupContainer/index.module.scss: -------------------------------------------------------------------------------- 1 | .signup { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /client/config/generators/component/styles.scss.hbs: -------------------------------------------------------------------------------- 1 | .{{ camelCase name }} { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /client/config/generators/container/styles.scss.hbs: -------------------------------------------------------------------------------- 1 | .{{ camelCase name }} { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /client/server.js: -------------------------------------------------------------------------------- 1 | require('babel-core/register'); 2 | var app = require('./server/app'); 3 | -------------------------------------------------------------------------------- /client/app/src/containers/CreateEventContainer/index.module.scss: -------------------------------------------------------------------------------- 1 | .createEvent { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /client/.gitattributes: -------------------------------------------------------------------------------- 1 | *.scss linguist-language=JavaScript 2 | *.html linguist-language=JavaScript 3 | -------------------------------------------------------------------------------- /client/app/src/containers/ProfileContainer/index.module.scss: -------------------------------------------------------------------------------- 1 | .profile { 2 | margin-top: 50px; 3 | } 4 | -------------------------------------------------------------------------------- /client/app/src/containers/EventsContainer/constants.js: -------------------------------------------------------------------------------- 1 | export const INCREMENT_CURRENT = 'INCREMENT_CURRENT'; 2 | -------------------------------------------------------------------------------- /client/app/src/components/LoadingIndicator/index.module.scss: -------------------------------------------------------------------------------- 1 | .loadingIndicator { 2 | margin-top: 20px; 3 | } 4 | -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | self.abstract_class = true 3 | end 4 | -------------------------------------------------------------------------------- /client/config/generators/component/export.js.hbs: -------------------------------------------------------------------------------- 1 | $1 2 | export {{ properCase name }} from './{{ properCase name }}'; 3 | -------------------------------------------------------------------------------- /config/initializers/graphiql.rb: -------------------------------------------------------------------------------- 1 | GraphiQL::Rails.config.headers['Authorization'] = -> (context) { 'bgPytR_GEtyre6C93num' } 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.scss linguist-language=JavaScript 2 | *.html linguist-language=JavaScript 3 | *.css linguist-language=JavaScript 4 | -------------------------------------------------------------------------------- /client/config/generators/page/export.js.hbs: -------------------------------------------------------------------------------- 1 | $1 2 | export {{ properCase name }}Page from './{{ properCase name }}Page/index'; 3 | -------------------------------------------------------------------------------- /client/app/src/pages/LogoutPage/index.module.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | height: 100vh; 3 | width: 100%; 4 | } 5 | .logout { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /client/config/generators/container/export.js.hbs: -------------------------------------------------------------------------------- 1 | $1 2 | export {{ properCase name }}Container from './{{ properCase name }}Container'; 3 | -------------------------------------------------------------------------------- /client/config/generators/container/constants.js.hbs: -------------------------------------------------------------------------------- 1 | export const {{ uppercase name }}_DEFAULT_ACTION = '{{ uppercase name }}_DEFAULT_ACTION'; 2 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | protect_from_forgery with: :exception 3 | end 4 | -------------------------------------------------------------------------------- /app/graph/meetup_event_planner_schema.rb: -------------------------------------------------------------------------------- 1 | MeetupEventPlannerSchema = GraphQL::Schema.new( 2 | query: QueryType, 3 | mutation: MutationType 4 | ) 5 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /client/app/src/components/Navbar/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RyanCCollins/meetup-event-planner/HEAD/client/app/src/components/Navbar/logo.png -------------------------------------------------------------------------------- /client/config/generators/page/index.module.scss.hbs: -------------------------------------------------------------------------------- 1 | .container { 2 | height: 100vh; 3 | width: 100%; 4 | } 5 | .{{ camelCase name }} { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /config/spring.rb: -------------------------------------------------------------------------------- 1 | %w( 2 | .ruby-version 3 | .rbenv-vars 4 | tmp/restart.txt 5 | tmp/caching-dev.txt 6 | ).each { |path| Spring.watch(path) } 7 | -------------------------------------------------------------------------------- /client/.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | .*/node_modules/fbjs/.* 3 | .*/node_modules/editions/.* 4 | 5 | [include] 6 | 7 | [libs] 8 | 9 | [options] 10 | -------------------------------------------------------------------------------- /client/app/src/components/App/constants.js: -------------------------------------------------------------------------------- 1 | export const AUTHENTICATE_USER = 'AUTHENTICATE_USER'; 2 | export const INVALIDATE_USER = 'INVALIDATE_USER'; 3 | -------------------------------------------------------------------------------- /client/config/generators/page/route.js.hbs: -------------------------------------------------------------------------------- 1 | 2 | $1 3 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /client/app/src/pages/EventsPage/index.module.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | min-height: calc(100vh - 74px); 3 | width: 100%; 4 | background-color: #ECEFF1; 5 | } 6 | -------------------------------------------------------------------------------- /client/app/src/pages/LandingPage/index.module.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | min-height: calc(100vh - 74px); 3 | width: 100%; 4 | background-color: #ECEFF1; 5 | } 6 | -------------------------------------------------------------------------------- /client/app/src/pages/ProfilePage/index.module.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | min-height: calc(100vh - 74px); 3 | width: 100%; 4 | background-color: #ECEFF1; 5 | } 6 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative 'config/environment' 4 | 5 | run Rails.application 6 | -------------------------------------------------------------------------------- /bin/web: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$RAILS_DEPLOYMENT" == "true" ]; then 4 | bundle exec puma -C config/puma.rb -p $PORT 5 | else 6 | node client/server.js 7 | fi 8 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative 'application' 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: async 6 | 7 | production: 8 | adapter: redis 9 | url: redis://localhost:6379/1 10 | -------------------------------------------------------------------------------- /app/graph/object_schema.rb: -------------------------------------------------------------------------------- 1 | ClassType = GraphQL::ObjectType.define do 2 | name 'Class' 3 | field :name, types.String 4 | field :ancestors, -> { types[ClassType] } 5 | end 6 | -------------------------------------------------------------------------------- /client/app/src/components/Navbar/README.md: -------------------------------------------------------------------------------- 1 | ## Navbar Component 2 | A simple Navbar component that is reusable 3 | 4 | ### Example 5 | 6 | ```js 7 | 8 | ``` 9 | -------------------------------------------------------------------------------- /db/migrate/20161003004530_add_location_to_event.rb: -------------------------------------------------------------------------------- 1 | class AddLocationToEvent < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :events, :location, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20161004010331_add_user_to_event.rb: -------------------------------------------------------------------------------- 1 | class AddUserToEvent < ActiveRecord::Migration[5.0] 2 | def change 3 | add_reference :events, :user, foreign_key: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20161012003551_add_employer_to_user.rb: -------------------------------------------------------------------------------- 1 | class AddEmployerToUser < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :users, :employer, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/graph/types/host_input_type.rb: -------------------------------------------------------------------------------- 1 | HostInputType = GraphQL::InputObjectType.define do 2 | name 'HostInput' 3 | description 'Input for hosts' 4 | input_field :name, !types.String 5 | end 6 | -------------------------------------------------------------------------------- /client/app/src/pages/NotFoundPage/index.module.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | height: 100vh; 3 | width: 100% 4 | }; 5 | 6 | .header { 7 | font-size: 32px; 8 | font: 'Open Sans'; 9 | } 10 | -------------------------------------------------------------------------------- /db/migrate/20161003233357_add_auth_token_to_user.rb: -------------------------------------------------------------------------------- 1 | class AddAuthTokenToUser < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :users, :auth_token, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/graph/types/guest_input_type.rb: -------------------------------------------------------------------------------- 1 | GuestInputType = GraphQL::InputObjectType.define do 2 | name 'GuestInput' 3 | description 'Input for guests' 4 | input_field :name, !types.String 5 | end 6 | -------------------------------------------------------------------------------- /client/app/src/components/LoginForm/utils/error.js: -------------------------------------------------------------------------------- 1 | const calculatedError = (input) => 2 | input.dirty || input.touched && input.error ? input.error : null; 3 | 4 | export default calculatedError; 5 | -------------------------------------------------------------------------------- /client/app/src/components/SignupForm/utils/error.js: -------------------------------------------------------------------------------- 1 | const calculatedError = (input) => 2 | input.dirty || input.touched && input.error ? input.error : null; 3 | 4 | export default calculatedError; 5 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.session_store :cookie_store, key: '_rails-graphql-boilerplate_session' 4 | -------------------------------------------------------------------------------- /db/migrate/20161003005655_change_event_type_to_event_type.rb: -------------------------------------------------------------------------------- 1 | class ChangeEventTypeToEventType < ActiveRecord::Migration[5.0] 2 | def change 3 | rename_column :events, :type, :event_type 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | devise_for :users 3 | mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/api" 4 | root to: redirect("/graphiql") 5 | resources :api 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20161004003535_change_auth_token_on_user.rb: -------------------------------------------------------------------------------- 1 | class ChangeAuthTokenOnUser < ActiveRecord::Migration[5.0] 2 | def change 3 | change_column :users, :auth_token, :string, default: "" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20161002213314_add_host_to_event.rb: -------------------------------------------------------------------------------- 1 | class AddHostToEvent < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :events, :host_id, :integer 4 | add_index :events, :host_id 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/graph/types/host_type.rb: -------------------------------------------------------------------------------- 1 | HostType = GraphQL::ObjectType.define do 2 | name 'Host' 3 | description 'A event entry' 4 | field :id, types.ID, 'The id of the host' 5 | field :name, !types.String, 'The name of the host' 6 | end 7 | -------------------------------------------------------------------------------- /client/app/src/pages/EventPage/index.module.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | min-height: calc(100vh - 74px); 3 | width: 100%; 4 | align-items: center; 5 | justify-content: center; 6 | display: flex; 7 | background-color: #ECEFF1; 8 | } 9 | -------------------------------------------------------------------------------- /client/app/src/pages/LoginPage/index.module.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | min-height: calc(100vh - 74px); 3 | width: 100%; 4 | align-items: center; 5 | justify-content: center; 6 | display: flex; 7 | background-color: #ECEFF1; 8 | } 9 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ApplicationController.renderer.defaults.merge!( 4 | # http_host: 'example.org', 5 | # https: false 6 | # ) 7 | -------------------------------------------------------------------------------- /db/migrate/20161002211618_add_attributes_to_user.rb: -------------------------------------------------------------------------------- 1 | class AddAttributesToUser < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :users, :name, :string, null: false 4 | add_column :users, :bio, :text 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20161002213058_create_hosts.rb: -------------------------------------------------------------------------------- 1 | class CreateHosts < ActiveRecord::Migration[5.0] 2 | def change 3 | create_table :hosts do |t| 4 | t.string :name 5 | 6 | t.timestamps 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /client/app/src/components/ToastMessage/tests/__snapshots__/index.test.js.snap: -------------------------------------------------------------------------------- 1 | exports[` should render with default props 1`] = ` 2 | 5 | Thanks! 6 | 7 | `; 8 | -------------------------------------------------------------------------------- /client/app/src/containers/EventsContainer/actions.js: -------------------------------------------------------------------------------- 1 | import * as types from './constants'; 2 | 3 | // eventsIncrementCurrent :: None -> {Action} 4 | export const eventsIncrementCurrent = () => ({ 5 | type: types.INCREMENT_CURRENT, 6 | }); 7 | -------------------------------------------------------------------------------- /client/app/src/pages/CreateEventPage/index.module.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | min-height: calc(100vh - 74px); 3 | width: 100%; 4 | align-items: center; 5 | justify-content: center; 6 | display: flex; 7 | background-color: #ECEFF1; 8 | } 9 | -------------------------------------------------------------------------------- /client/server/public/app/src/components/Navbar/logo.00e7c4cf372ade679404a6cf8f80704f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RyanCCollins/meetup-event-planner/HEAD/client/server/public/app/src/components/Navbar/logo.00e7c4cf372ade679404a6cf8f80704f.png -------------------------------------------------------------------------------- /app/graph/types/guest_type.rb: -------------------------------------------------------------------------------- 1 | GuestType = GraphQL::ObjectType.define do 2 | name 'Guest' 3 | description 'A single guest entry' 4 | field :id, types.ID, 'The ID of the guest' 5 | field :name, types.String, 'The name of the guest' 6 | end 7 | -------------------------------------------------------------------------------- /app/models/event.rb: -------------------------------------------------------------------------------- 1 | class Event < ApplicationRecord 2 | belongs_to :host 3 | has_many :guests 4 | belongs_to :user 5 | alias_attribute :created_by, :user 6 | enum event_type: [:birthday, :conference, :office, :wedding, :other, :coding] 7 | end 8 | -------------------------------------------------------------------------------- /client/app/src/containers/SignupContainer/README.md: -------------------------------------------------------------------------------- 1 | ## SignupContainer 2 | A container that handles signup for the site, connecting to redux and graphql. 3 | 4 | ### Other Information 5 | Coupled to the store and graphql, so not reusable without setup. 6 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative 'config/application' 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | require_relative '../config/boot' 8 | require 'rake' 9 | Rake.application.run 10 | -------------------------------------------------------------------------------- /client/app/src/components/SingleEvent/README.md: -------------------------------------------------------------------------------- 1 | ## SingleEvent Component 2 | A component for showing a single event 3 | 4 | ### Example 5 | 6 | ```js 7 | 8 | ``` 9 | 10 | ### Other Information 11 | See ./index.js for implementation details 12 | -------------------------------------------------------------------------------- /db/migrate/20161004204156_add_avatar_to_user.rb: -------------------------------------------------------------------------------- 1 | class AddAvatarToUser < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :users, :avatar, :string, default: 'https://github.com/RyanCCollins/cdn/blob/master/misc/no-user.png?raw=true' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/models/event_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Event, type: :model do 4 | it { should belong_to(:host) } 5 | it { should belong_to(:user) } 6 | it { should have_many(:guests) } 7 | it { should define_enum_for(:event_type) } 8 | end 9 | -------------------------------------------------------------------------------- /client/app/src/containers/EventContainer/README.md: -------------------------------------------------------------------------------- 1 | ## EventContainer 2 | A container for a single event 3 | 4 | ### Example Usage 5 | 6 | ```js 7 | 8 | ``` 9 | 10 | 11 | ### Other Information 12 | See ./index.js for implementation details 13 | -------------------------------------------------------------------------------- /client/app/src/pages/SignupPage/index.module.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | min-height: calc(100vh - 74px); 3 | width: 100%; 4 | align-items: center; 5 | justify-content: center; 6 | display: flex; 7 | background-color: #ECEFF1; 8 | padding: 40px 0; 9 | } 10 | -------------------------------------------------------------------------------- /client/app/src/components/AppFooter/README.md: -------------------------------------------------------------------------------- 1 | ## AppFooter Component 2 | A reusable footer component for the site. 3 | 4 | ### Example 5 | 6 | ```js 7 | 8 | ``` 9 | 10 | ### Other Information 11 | Presentational component that can be reused if needed. 12 | -------------------------------------------------------------------------------- /client/app/src/components/Navbar/index.module.scss: -------------------------------------------------------------------------------- 1 | .logo{ 2 | max-height: 45px; 3 | margin-left: 6%; 4 | } 5 | 6 | .rightNav { 7 | margin-right: 20px; 8 | } 9 | 10 | .menu { 11 | z-index: 100; 12 | } 13 | 14 | .menuItem { 15 | text-align: center; 16 | } 17 | -------------------------------------------------------------------------------- /client/app/src/containers/LoginContainer/README.md: -------------------------------------------------------------------------------- 1 | ## LoginContainer 2 | A container for handling site login 3 | 4 | ### Example Usage 5 | 6 | ```js 7 | 8 | ``` 9 | 10 | 11 | ### Other Information 12 | See ./index.js for implementation details 13 | -------------------------------------------------------------------------------- /client/config/generators/container/actions.js.hbs: -------------------------------------------------------------------------------- 1 | import * as types from './constants'; 2 | 3 | // {{ camelCase name }}defaultAction :: None -> {Action} 4 | export const {{ camelCase name }}DefaultAction = () => ({ 5 | type: types.{{ uppercase name }}_DEFAULT_ACTION, 6 | }); 7 | -------------------------------------------------------------------------------- /client/app/src/containers/LandingContainer/README.md: -------------------------------------------------------------------------------- 1 | ## LandingContainer 2 | A container for the landing page 3 | 4 | ### Example Usage 5 | 6 | ```js 7 | 8 | ``` 9 | 10 | 11 | ### Other Information 12 | See ./index.js for implementation details 13 | -------------------------------------------------------------------------------- /client/app/src/containers/ProfileContainer/README.md: -------------------------------------------------------------------------------- 1 | ## ProfileContainer 2 | A container that maps to the user profile 3 | 4 | ### Example Usage 5 | 6 | ```js 7 | 8 | ``` 9 | 10 | 11 | ### Other Information 12 | See ./index.js for implementation details 13 | -------------------------------------------------------------------------------- /db/migrate/20161003004858_create_guests.rb: -------------------------------------------------------------------------------- 1 | class CreateGuests < ActiveRecord::Migration[5.0] 2 | def change 3 | create_table :guests do |t| 4 | t.references :event, foreign_key: true 5 | t.string :name 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/graph/types/event_type_enum.rb: -------------------------------------------------------------------------------- 1 | EventTypeEnum = GraphQL::EnumType.define do 2 | name 'EventType' 3 | description 'The type of the event' 4 | value 'birthday' 5 | value 'conference' 6 | value 'office' 7 | value 'wedding' 8 | value 'coding' 9 | value 'other' 10 | end 11 | -------------------------------------------------------------------------------- /client/app/src/containers/EventsContainer/README.md: -------------------------------------------------------------------------------- 1 | ## EventsContainer 2 | A container that connects to a list of events 3 | 4 | ### Example Usage 5 | 6 | ```js 7 | 8 | ``` 9 | 10 | 11 | ### Other Information 12 | See ./index.js for implementation details 13 | -------------------------------------------------------------------------------- /client/app/src/components/EventInfo/utils/dates.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | 3 | const formatString = 'MMMM Do YYYY, h:mm:ss a'; 4 | const parseDates = (a, b) => 5 | `From: ${moment(a).format(formatString)} 6 | \n To: ${moment(b).format(formatString)}`; 7 | 8 | export default parseDates; 9 | -------------------------------------------------------------------------------- /client/app/src/containers/CreateEventContainer/README.md: -------------------------------------------------------------------------------- 1 | ## CreateEventContainer 2 | A container that allows creation of event 3 | 4 | ### Example Usage 5 | 6 | ```js 7 | 8 | ``` 9 | 10 | 11 | ### Other Information 12 | See ./index.js for implementation details 13 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Specify a serializer for the signed and encrypted cookie jars. 4 | # Valid options are :json, :marshal, and :hybrid. 5 | Rails.application.config.action_dispatch.cookies_serializer = :json 6 | -------------------------------------------------------------------------------- /client/app/src/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ // React must be in scope here 2 | import React from 'react'; 3 | /* eslint-enable */ 4 | import { render } from 'react-dom'; 5 | import RouterApp from './routes'; 6 | import '../styles/styles.scss'; 7 | 8 | render(, document.getElementById('app')); 9 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | APP_PATH = File.expand_path('../config/application', __dir__) 8 | require_relative '../config/boot' 9 | require 'rails/commands' 10 | -------------------------------------------------------------------------------- /client/app/src/components/LoginForm/index.module.scss: -------------------------------------------------------------------------------- 1 | .loginForm { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | background-color: #fff; 6 | padding: 24px; 7 | max-width: 100vw; 8 | box-sizing: border-box; 9 | } 10 | 11 | .formField { 12 | max-width: 430px; 13 | } 14 | -------------------------------------------------------------------------------- /client/app/src/pages/EventPage/README.md: -------------------------------------------------------------------------------- 1 | ## EventPage 2 | A top level page container that corresponds to a route by the same name. 3 | 4 | ### Route Parameters 5 | An paramaters that might be part of the route. 6 | 7 | ### Example Usage 8 | 9 | ```js 10 | 11 | ``` 12 | 13 | 14 | ### Other Information 15 | -------------------------------------------------------------------------------- /client/app/src/pages/EventsPage/README.md: -------------------------------------------------------------------------------- 1 | ## EventsPage 2 | A top level page container that corresponds to a route by the same name. 3 | 4 | ### Route Parameters 5 | An paramaters that might be part of the route. 6 | 7 | ### Example Usage 8 | 9 | ```js 10 | 11 | ``` 12 | 13 | 14 | ### Other Information 15 | -------------------------------------------------------------------------------- /client/app/src/pages/LoginPage/README.md: -------------------------------------------------------------------------------- 1 | ## LoginPage 2 | A top level page container that corresponds to a route by the same name. 3 | 4 | ### Route Parameters 5 | An paramaters that might be part of the route. 6 | 7 | ### Example Usage 8 | 9 | ```js 10 | 11 | ``` 12 | 13 | 14 | ### Other Information 15 | -------------------------------------------------------------------------------- /client/app/src/pages/LogoutPage/README.md: -------------------------------------------------------------------------------- 1 | ## LogoutPage 2 | A top level page container that corresponds to a route by the same name. 3 | 4 | ### Route Parameters 5 | An paramaters that might be part of the route. 6 | 7 | ### Example Usage 8 | 9 | ```js 10 | 11 | ``` 12 | 13 | 14 | ### Other Information 15 | -------------------------------------------------------------------------------- /client/app/src/pages/SignupPage/README.md: -------------------------------------------------------------------------------- 1 | ## SignupPage 2 | A top level page container that corresponds to a route by the same name. 3 | 4 | ### Route Parameters 5 | An paramaters that might be part of the route. 6 | 7 | ### Example Usage 8 | 9 | ```js 10 | 11 | ``` 12 | 13 | 14 | ### Other Information 15 | -------------------------------------------------------------------------------- /spec/models/host_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Host, type: :model do 4 | it 'is valid with valid attributes' do 5 | expect( 6 | Host.new(name: 'Malinda Gates') 7 | ).to be_valid 8 | end 9 | describe 'Associations' do 10 | it { should have_many(:events) } 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /client/app/src/pages/ProfilePage/README.md: -------------------------------------------------------------------------------- 1 | ## ProfilePage 2 | A top level page container that corresponds to a route by the same name. 3 | 4 | ### Route Parameters 5 | An paramaters that might be part of the route. 6 | 7 | ### Example Usage 8 | 9 | ```js 10 | 11 | ``` 12 | 13 | 14 | ### Other Information 15 | -------------------------------------------------------------------------------- /client/app/src/containers/LoginContainer/constants.js: -------------------------------------------------------------------------------- 1 | export const LOGIN_SHOW_ERROR = 'LOGIN_SHOW_ERROR'; 2 | export const LOGIN_SHOW_MESSAGE = 'LOGIN_SHOW_MESSAGE'; 3 | export const LOGIN_CLEAR_ERROR = 'LOGIN_CLEAR_ERROR'; 4 | export const LOGIN_CLEAR_MESSAGE = 'LOGIN_CLEAR_MESSAGE'; 5 | export const LOGIN_SET_LOADING = 'LOGIN_SET_LOADING'; 6 | -------------------------------------------------------------------------------- /client/app/src/pages/CreateEventPage/README.md: -------------------------------------------------------------------------------- 1 | ## CreateEventPage 2 | A top level page container that corresponds to a route by the same name. 3 | 4 | ### Route Parameters 5 | An paramaters that might be part of the route. 6 | 7 | ### Example Usage 8 | 9 | ```js 10 | 11 | ``` 12 | 13 | 14 | ### Other Information 15 | -------------------------------------------------------------------------------- /client/config/generators/utils/trimTemplateFile.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | const trimTemplateFile = (template) => { 4 | // Loads the template file and trims the whitespace and then returns the content as a string. 5 | return fs.readFileSync(template, 'utf8').replace(/\s*$/, ''); 6 | }; 7 | 8 | module.exports = trimTemplateFile; 9 | -------------------------------------------------------------------------------- /client/app/src/containers/LoginContainer/tests/reducer.test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | import loginReducer, { initialState } from '../reducer'; 3 | 4 | describe('loginReducer', () => { 5 | it('returns the initial state', () => { 6 | expect( 7 | loginReducer(undefined, {}) 8 | ).toEqual(initialState); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /db/migrate/20161002211930_create_events.rb: -------------------------------------------------------------------------------- 1 | class CreateEvents < ActiveRecord::Migration[5.0] 2 | def change 3 | create_table :events do |t| 4 | t.string :name 5 | t.integer :type 6 | t.datetime :start_date 7 | t.datetime :end_date 8 | t.text :message 9 | 10 | t.timestamps 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /client/app/src/pages/NotFoundPage/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import cssModules from 'react-css-modules'; 3 | import styles from './index.module.scss'; 4 | 5 | const NotFound = () => ( 6 |
7 |

Not Found

8 |
9 | ); 10 | 11 | export default cssModules(NotFound, styles); 12 | -------------------------------------------------------------------------------- /client/app/src/components/App/README.md: -------------------------------------------------------------------------------- 1 | ## App Component 2 | Top level Application component that sits above other components. 3 | 4 | ### Props 5 | 6 | | Prop | Type | Default | Possible Values 7 | | ------------- | -------- | ----------- | --------------------------------------------- 8 | | **children** | Element | | Any children react components 9 | -------------------------------------------------------------------------------- /client/config/testing/preprocessor.js: -------------------------------------------------------------------------------- 1 | const babelJest = require('babel-jest'); 2 | 3 | module.exports = { 4 | process(src, filename) { 5 | if (!filename.match(/\.jsx?$/)) { 6 | return ''; 7 | } 8 | return babelJest.process( 9 | src.replace(/import(.*)from.*\.((less)|(scss)|(svg)|(png)).*;/gi, 'let $1 = {};' 10 | ), filename); 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /client/config/generators/container/reducer.test.js.hbs: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | import {{ camelCase name }}Reducer, { initialState } from '../reducer'; 3 | 4 | describe('{{ camelCase name }}Reducer', () => { 5 | it('returns the initial state', () => { 6 | expect( 7 | {{ camelCase name }}Reducer(undefined, {}) 8 | ).toEqual(initialState); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /client/app/src/apolloClient.js: -------------------------------------------------------------------------------- 1 | import ApolloClient, { 2 | createNetworkInterface, 3 | addTypeName, 4 | } from 'apollo-client'; 5 | const productionUrl = 'https://meetup-event-planner-api.herokuapp.com/api'; 6 | 7 | const client = new ApolloClient({ 8 | networkInterface: createNetworkInterface(productionUrl), 9 | queryTransformer: addTypeName, 10 | }); 11 | 12 | export default client; 13 | -------------------------------------------------------------------------------- /client/app/src/pages/EventsPage/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import cssModules from 'react-css-modules'; 3 | import styles from './index.module.scss'; 4 | import { EventsContainer } from 'containers'; 5 | 6 | const EventsPage = () => ( 7 |
8 | 9 |
10 | ); 11 | 12 | export default cssModules(EventsPage, styles); 13 | -------------------------------------------------------------------------------- /client/app/src/pages/SignupPage/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import cssModules from 'react-css-modules'; 3 | import styles from './index.module.scss'; 4 | import { SignupContainer } from 'containers'; 5 | 6 | const SignupPage = () => ( 7 |
8 | 9 |
10 | ); 11 | 12 | export default cssModules(SignupPage, styles); 13 | -------------------------------------------------------------------------------- /client/app/src/containers/SignupContainer/constants.js: -------------------------------------------------------------------------------- 1 | export const SIGNUP_SHOW_ERROR = 'SIGNUP_SHOW_ERROR'; 2 | export const SIGNUP_SHOW_MESSAGE = 'SIGNUP_SHOW_MESSAGE'; 3 | export const SIGNUP_CLEAR_ERROR = 'SIGNUP_CLEAR_ERROR'; 4 | export const SIGNUP_CLEAR_MESSAGE = 'SIGNUP_CLEAR_MESSAGE'; 5 | export const SIGNUP_SET_LOADING = 'SIGNUP_SET_LOADING'; 6 | export const TOGGLE_SIGNUP_TIPS = 'TOGGLE_SIGNUP_TIPS'; 7 | -------------------------------------------------------------------------------- /client/app/src/pages/ProfilePage/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import cssModules from 'react-css-modules'; 3 | import styles from './index.module.scss'; 4 | import { ProfileContainer } from 'containers'; 5 | 6 | const ProfilePage = () => ( 7 |
8 | 9 |
10 | ); 11 | 12 | export default cssModules(ProfilePage, styles); 13 | -------------------------------------------------------------------------------- /app/graph/types/mutation_type.rb: -------------------------------------------------------------------------------- 1 | MutationType = GraphQL::ObjectType.define do 2 | name 'Mutation' 3 | 4 | field :CreateEvent, field: EventMutations::Create.field 5 | field :RSVP, field: EventMutations::RSVP.field 6 | field :SignUp, field: UserMutations::SignUp.field 7 | field :SignIn, field: UserMutations::SignIn.field 8 | field :UpdateProfile, field: UserMutations::UpdateProfile.field 9 | end 10 | -------------------------------------------------------------------------------- /client/app/src/components/EventInfo/index.module.scss: -------------------------------------------------------------------------------- 1 | .eventInfo { 2 | border-radius: 5px; 3 | border: 1px solid #e6e6e6; 4 | margin-bottom: 30px; 5 | padding: 30px !important; 6 | border-radius: 5px; 7 | @media screen and (min-width: 550px) { 8 | min-width: 580px; 9 | max-width: 100vw; 10 | } 11 | } 12 | 13 | .eventHeading { 14 | border-bottom: 1px solid rgba(0,0,0,.12); 15 | } 16 | -------------------------------------------------------------------------------- /client/config/generators/utils/componentNameCheck.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | const pageComponents = fs.readdirSync('app/src/components'); 4 | const pageContainers = fs.readdirSync('app/src/containers'); 5 | const components = pageComponents.concat(pageContainers); 6 | 7 | const componentNameCheck = (component) => 8 | components.indexOf(component) >= 0; 9 | 10 | module.exports = componentNameCheck; 11 | -------------------------------------------------------------------------------- /client/app/src/pages/CreateEventPage/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import cssModules from 'react-css-modules'; 3 | import styles from './index.module.scss'; 4 | import { CreateEventContainer } from 'containers'; 5 | 6 | const CreateEventPage = () => ( 7 |
8 | 9 |
10 | ); 11 | 12 | export default cssModules(CreateEventPage, styles); 13 | -------------------------------------------------------------------------------- /client/app/src/components/AuthFormFooter/tests/__snapshots__/index.test.js.snap: -------------------------------------------------------------------------------- 1 | exports[` should render with default props 1`] = ` 2 |
7 | 8 | Already a Member? 9 | 10 | 11 | 14 | Login 15 | 16 |
17 | `; 18 | -------------------------------------------------------------------------------- /client/app/src/components/EventForm/utils/error.js: -------------------------------------------------------------------------------- 1 | const calculatedError = (input) => 2 | input.touched && input.error ? input.error : null; 3 | 4 | export const atLeastOne = (guestsList, input) => 5 | input.touched && guestsList.length < 1 ? 'At least one guest required' : null; 6 | 7 | export const dateError = (input) => 8 | !input.valid && input.error ? input.error : null; 9 | 10 | export default calculatedError; 11 | -------------------------------------------------------------------------------- /client/app/src/components/Navbar/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import { shallow } from 'enzyme'; 2 | import React from 'react'; 3 | import { shallowToJson } from 'enzyme-to-json'; 4 | import Navbar from '../index'; 5 | 6 | describe('', () => { 7 | it('should render with default props', () => { 8 | const wrapper = shallow( 9 | 10 | ); 11 | expect(shallowToJson(wrapper)).toMatchSnapshot(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /client/app/src/containers/EventsContainer/tests/actions.test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | import * as actions from '../actions'; 3 | import * as types from '../constants'; 4 | 5 | describe('Events actions', () => { 6 | it('has a type of INCREMENT_CURRENT', () => { 7 | const expected = { 8 | type: types.INCREMENT_CURRENT, 9 | }; 10 | expect(actions.eventsIncrementCurrent()).toEqual(expected); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /app/graph/types/user_signup_input_type.rb: -------------------------------------------------------------------------------- 1 | UserSignupInputType = GraphQL::InputObjectType.define do 2 | name 'UserSignupInput' 3 | description 'The data the user enters on signup' 4 | input_field :email, !types.String 5 | input_field :name, !types.String 6 | input_field :password, !types.String 7 | input_field :password_confirmation, !types.String 8 | input_field :bio, types.String 9 | input_field :employer, types.String 10 | end 11 | -------------------------------------------------------------------------------- /client/app/src/components/SignupForm/index.module.scss: -------------------------------------------------------------------------------- 1 | .signupForm { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | background-color: #fff; 6 | padding: 24px; 7 | max-width: 100vw; 8 | box-sizing: border-box; 9 | } 10 | 11 | .formField { 12 | max-width: 430px; 13 | } 14 | 15 | .listItem { 16 | margin-top: 5px !important; 17 | margin-bottom: 5px !important; 18 | color: black !important; 19 | } 20 | -------------------------------------------------------------------------------- /client/app/src/containers/index.js: -------------------------------------------------------------------------------- 1 | /* Assemble all containers for export */ 2 | export ProfileContainer from './ProfileContainer'; 3 | export CreateEventContainer from './CreateEventContainer'; 4 | export EventContainer from './EventContainer'; 5 | export EventsContainer from './EventsContainer'; 6 | export SignupContainer from './SignupContainer'; 7 | export LoginContainer from './LoginContainer'; 8 | export LandingContainer from './LandingContainer'; 9 | -------------------------------------------------------------------------------- /client/app/src/components/AppFooter/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import AppFooter from '../index'; 2 | import { shallow } from 'enzyme'; 3 | import { shallowToJson } from 'enzyme-to-json'; 4 | import React from 'react'; 5 | 6 | describe('', () => { 7 | it('should render with default props', () => { 8 | const wrapper = shallow( 9 | 10 | ); 11 | expect(shallowToJson(wrapper)).toMatchSnapshot(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /app/graph/types/user_type.rb: -------------------------------------------------------------------------------- 1 | UserType = GraphQL::ObjectType.define do 2 | name 'User' 3 | description 'The user model type' 4 | field :id, types.ID, 'The id of this user' 5 | field :name, !types.String, 'The name of the user' 6 | field :bio, types.String, 'The bio of the user' 7 | field :events, types[EventType], 'The user events' 8 | field :avatar, types.String, 'The user avatar' 9 | field :employer, types.String, 'The user employer' 10 | end 11 | -------------------------------------------------------------------------------- /client/app/src/pages/LoginPage/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import cssModules from 'react-css-modules'; 3 | import styles from './index.module.scss'; 4 | import { LoginContainer } from 'containers'; 5 | 6 | // Pages map directly to Routes, i.e. one page equals on Route 7 | 8 | const LoginPage = () => ( 9 |
10 | 11 |
12 | ); 13 | 14 | export default cssModules(LoginPage, styles); 15 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /client/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-airbnb", 3 | "parser": "babel-eslint", 4 | "env": { 5 | "browser": true, 6 | "node": true, 7 | "mocha": true 8 | }, 9 | "rules": { 10 | "func-names": 0, 11 | "eol-last": 0, 12 | "react/jsx-no-bind": [ 2, { 13 | "ignoreRefs": false, 14 | "allowArrowFunctions": true, 15 | "allowBind": true 16 | }] 17 | }, 18 | "plugins": [ 19 | "react", 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /client/config/generators/container/reducer.js.hbs: -------------------------------------------------------------------------------- 1 | import * as types from './constants'; 2 | 3 | export const initialState = { 4 | // Initial State goes here! 5 | }; 6 | 7 | const {{ camelCase name }}Reducer = 8 | (state = initialState, action) => { 9 | switch (action.type) { 10 | case types.DEFAULT_ACTION: 11 | return state; 12 | default: 13 | return state; 14 | } 15 | }; 16 | 17 | export default {{ camelCase name }}Reducer; 18 | -------------------------------------------------------------------------------- /app/graph/types/profile_input_type.rb: -------------------------------------------------------------------------------- 1 | ProfileInputType = GraphQL::InputObjectType.define do 2 | name 'ProfileInput' 3 | description 'The user profile type' 4 | input_field :name, types.String, 'The name of the user' 5 | input_field :email, types.String, 'The email of the user' 6 | input_field :bio, types.String, 'The bio of the user' 7 | input_field :avatar, types.String, 'The user avatar' 8 | input_field :employer, types.String, 'The user employer' 9 | end 10 | -------------------------------------------------------------------------------- /client/config/generators/component/test.js.hbs: -------------------------------------------------------------------------------- 1 | import {{ properCase name }} from '../index'; 2 | import { shallow } from 'enzyme'; 3 | import { shallowToJson } from 'enzyme-to-json'; 4 | import React from 'react'; 5 | 6 | describe('<{{ properCase name }} />', () => { 7 | it('should render with default props', () => { 8 | const wrapper = shallow( 9 | <{{ properCase name }} /> 10 | ); 11 | expect(shallowToJson(wrapper)).toMatchSnapshot(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /client/config/generators/page/index.js.hbs: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import cssModules from 'react-css-modules'; 3 | import styles from './index.module.scss'; 4 | 5 | 6 | // Pages map directly to Routes, i.e. one page equals on Route 7 | 8 | const {{ properCase name }}Page = (props) => ( 9 |
10 | Hello from {{ properCase name }}Page ! 11 |
12 | ); 13 | 14 | export default cssModules({{ properCase name }}Page, styles); 15 | -------------------------------------------------------------------------------- /client/app/src/components/ToolTip/tests/__snapshots__/index.test.js.snap: -------------------------------------------------------------------------------- 1 | exports[` should render with default props 1`] = ` 2 |
3 | 8 |
9 |
    10 |
  • 11 | Hello 12 |
  • 13 |
  • 14 | Hello Again 15 |
  • 16 |
17 |
18 |
19 |
20 | `; 21 | -------------------------------------------------------------------------------- /client/app/src/components/AppFooter/index.module.scss: -------------------------------------------------------------------------------- 1 | .appFooter { 2 | background: #f5f5f5; 3 | padding: 50px; 4 | title { 5 | color: black !important; 6 | } 7 | } 8 | 9 | .flexOne { 10 | flex: 1; 11 | } 12 | 13 | .termsAnchor { 14 | margin-top: 40px; 15 | @media screen and (max-width: 680px) { 16 | display: flex; 17 | flex-direction: column; 18 | } 19 | } 20 | 21 | .seperator { 22 | @media screen and (max-width: 680px) { 23 | display: none; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/graph/types/event_input_type.rb: -------------------------------------------------------------------------------- 1 | EventInputType = GraphQL::InputObjectType.define do 2 | name 'EventInput' 3 | description 'Input type for an event' 4 | input_field :name, !types.String 5 | input_field :message, types.String 6 | input_field :start_date, !types.String 7 | input_field :end_date, !types.String 8 | input_field :type, !types.String 9 | input_field :host, HostInputType 10 | input_field :location, !types.String 11 | input_field :guests, types[GuestInputType] 12 | end 13 | -------------------------------------------------------------------------------- /client/app/src/components/ToastMessage/tests/index.test.js: -------------------------------------------------------------------------------- 1 | 2 | import ToastMessage from '../index'; 3 | import { shallow } from 'enzyme'; 4 | import { shallowToJson } from 'enzyme-to-json'; 5 | import React from 'react'; 6 | 7 | describe('', () => { 8 | it('should render with default props', () => { 9 | const wrapper = shallow( 10 | e} /> 11 | ); 12 | expect(shallowToJson(wrapper)).toMatchSnapshot(); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /client/app/src/components/AuthFormFooter/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import AuthFormFooter from '../index'; 2 | import { shallow } from 'enzyme'; 3 | import { shallowToJson } from 'enzyme-to-json'; 4 | import React from 'react'; 5 | 6 | describe('', () => { 7 | it('should render with default props', () => { 8 | const wrapper = shallow( 9 | 10 | ); 11 | expect(shallowToJson(wrapper)).toMatchSnapshot(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /client/config/generators/component/README.md.hbs: -------------------------------------------------------------------------------- 1 | ## {{ properCase name }} Component 2 | A component that ... 3 | 4 | ### Example 5 | 6 | ```js 7 | <{{ properCase name }} /> 8 | ``` 9 | 10 | {{#if wantPropTypes}} 11 | ### Props 12 | 13 | | Prop | Type | Default | Possible Values 14 | | ------------- | -------- | ----------- | --------------------------------------------- 15 | | **myProp** | String | | Any string value 16 | 17 | {{/if}} 18 | 19 | ### Other Information 20 | -------------------------------------------------------------------------------- /spec/models/guest_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Guest, type: :model do 4 | it 'should be valid with valid attributes' do 5 | event = Event.new 6 | expect( 7 | Guest.new(name: 'Bill Gates', event: event) 8 | ).to be_valid 9 | end 10 | it 'should fail without a valid event' do 11 | expect( 12 | Guest.new(name: 'Bill Gates') 13 | ).to_not be_valid 14 | end 15 | describe 'Associations' do 16 | it { should belong_to(:event) } 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/graph/fields/fetch_field.rb: -------------------------------------------------------------------------------- 1 | class FetchField < GraphQL::Field 2 | def initialize(model:, type:) 3 | self.type = type 4 | @model = model 5 | self.description = 'Find a #{model.name} by ID' 6 | self.arguments = { 7 | 'id' => GraphQL::Argument.define do 8 | name 'id' 9 | type !GraphQL::INT_TYPE 10 | description 'Id for record' 11 | end 12 | } 13 | end 14 | 15 | def resolve(object, arguments, ctx) 16 | @model.find(arguments['id']) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /client/app/src/pages/index.js: -------------------------------------------------------------------------------- 1 | /* Assemble all pages for export */ 2 | export LogoutPage from './LogoutPage/index'; 3 | export ProfilePage from './ProfilePage/index'; 4 | export CreateEventPage from './CreateEventPage/index'; 5 | export EventPage from './EventPage/index'; 6 | export EventsPage from './EventsPage/index'; 7 | export LoginPage from './LoginPage/index'; 8 | export SignupPage from './SignupPage/index'; 9 | export NotFoundPage from './NotFoundPage/index'; 10 | export LandingPage from './LandingPage/index'; 11 | -------------------------------------------------------------------------------- /client/app/src/pages/EventPage/index.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import cssModules from 'react-css-modules'; 3 | import styles from './index.module.scss'; 4 | import { EventContainer } from 'containers'; 5 | 6 | const EventPage = ({ 7 | params, 8 | }) => ( 9 |
10 | 11 |
12 | ); 13 | 14 | EventPage.propTypes = { 15 | params: PropTypes.object.isRequired, 16 | }; 17 | 18 | export default cssModules(EventPage, styles); 19 | -------------------------------------------------------------------------------- /client/config/generators/container/README.md.hbs: -------------------------------------------------------------------------------- 1 | ## {{ properCase name }}Container 2 | A container that does ... 3 | 4 | ### Example Usage 5 | 6 | ```js 7 | <{{ properCase name }}Container /> 8 | ``` 9 | 10 | {{#if wantPropTypes}} 11 | ### Props 12 | 13 | | Prop | Type | Default | Possible Values 14 | | ------------- | -------- | ----------- | --------------------------------------------- 15 | | **myProp** | String | | Any string value 16 | 17 | {{/if}} 18 | 19 | ### Other Information 20 | -------------------------------------------------------------------------------- /client/app/src/components/SingleEvent/index.module.scss: -------------------------------------------------------------------------------- 1 | .panel { 2 | background: #fff; 3 | box-shadow: 0 2px 4px 0 hsla(34,12%,61%,.52); 4 | width: 100%; 5 | margin-bottom: 80px; 6 | position: relative; 7 | min-width: 500px; 8 | max-width: 100vw; 9 | box-sizing: border-box; 10 | @media screen and (max-width: 500px) { 11 | min-width: 300px; 12 | width: 100%; 13 | } 14 | } 15 | 16 | .icon { 17 | margin-top: 20px; 18 | margin-bottom: 10px; 19 | } 20 | 21 | .paragraph { 22 | padding: 0 40px; 23 | } 24 | -------------------------------------------------------------------------------- /client/config/generators/container/actions.test.js.hbs: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | import * as actions from '../actions'; 3 | import * as types from '../constants'; 4 | 5 | describe('{{ properCase name }} actions', () => { 6 | describe('Default Action', () => { 7 | it('has a type of DEFAULT_ACTION', () => { 8 | const expected = { 9 | type: types.{{ uppercase name }}_DEFAULT_ACTION, 10 | }; 11 | expect(actions.{{ camelCase name }}DefaultAction()).toEqual(expected); 12 | }); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | workers Integer(ENV["WEB_CONCURRENCY"] || 3) 2 | threads_count = Integer(ENV["MAX_THREADS"] || 16) 3 | threads threads_count, threads_count 4 | 5 | preload_app! 6 | 7 | rackup DefaultRackup 8 | port ENV["PORT"] || 3000 9 | environment ENV["RACK_ENV"] || "development" 10 | 11 | on_worker_boot do 12 | # Worker specific setup for Rails 4.1+ 13 | # See: https://devcenter.heroku.com/articles/deploying-rails-applications-with-the-puma-web-server#on-worker-boot 14 | ActiveRecord::Base.establish_connection 15 | end 16 | -------------------------------------------------------------------------------- /client/app/src/containers/ProfileContainer/authUserDataFragment.js: -------------------------------------------------------------------------------- 1 | import { createFragment } from 'apollo-client'; 2 | import gql from 'graphql-tag'; 3 | 4 | export const authUserDataFragment = createFragment( 5 | gql` 6 | fragment authUserData on AuthUser { 7 | id 8 | bio 9 | email 10 | name 11 | avatar 12 | employer 13 | authToken: auth_token 14 | events { 15 | name 16 | id 17 | } 18 | } 19 | ` 20 | ); 21 | 22 | 23 | export default authUserDataFragment; 24 | -------------------------------------------------------------------------------- /client/app/src/containers/SignupContainer/authUserDataFragment.js: -------------------------------------------------------------------------------- 1 | import { createFragment } from 'apollo-client'; 2 | import gql from 'graphql-tag'; 3 | 4 | export const authUserDataFragment = createFragment( 5 | gql` 6 | fragment authUserData on AuthUser { 7 | id 8 | bio 9 | email 10 | name 11 | avatar 12 | employer 13 | authToken: auth_token 14 | events { 15 | name 16 | id 17 | } 18 | } 19 | ` 20 | ); 21 | 22 | 23 | export default authUserDataFragment; 24 | -------------------------------------------------------------------------------- /bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This file loads spring without using Bundler, in order to be fast. 4 | # It gets overwritten when you run the `spring binstub` command. 5 | 6 | unless defined?(Spring) 7 | require 'rubygems' 8 | require 'bundler' 9 | 10 | if (match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m)) 11 | Gem.paths = { 'GEM_PATH' => [Bundler.bundle_path.to_s, *Gem.path].uniq.join(Gem.path_separator) } 12 | gem 'spring', match[1] 13 | require 'spring/binstub' 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /client/app/src/components/EditableField/tests/__snapshots__/index.test.js.snap: -------------------------------------------------------------------------------- 1 | exports[` should render with default props 1`] = ` 2 |
10 | 18 |
19 | Hi 20 |
21 |
22 |
23 | `; 24 | -------------------------------------------------------------------------------- /client/app/src/components/EventForm/index.module.scss: -------------------------------------------------------------------------------- 1 | .addButton { 2 | position: absolute; 3 | z-index: 10; 4 | right: 45px; 5 | top: 68px; 6 | } 7 | 8 | .guestListField { 9 | padding: 0 24px !important; 10 | } 11 | 12 | .eventForm { 13 | @media screen and (max-width: 480px) { 14 | align-items: center; 15 | justify-content: center; 16 | display: flex; 17 | flex-direction: column; 18 | } 19 | } 20 | 21 | .locationInput { 22 | overflow: visible !important; 23 | } 24 | 25 | .formField { 26 | overflow: inherit !important; 27 | } 28 | -------------------------------------------------------------------------------- /client/app/src/components/SignupForm/README.md: -------------------------------------------------------------------------------- 1 | ## SignupForm Component 2 | A signup form component. 3 | 4 | ### Example 5 | 6 | ```js 7 | 11 | ``` 12 | 13 | ### Props 14 | 15 | | Prop | Type | Default | Possible Values 16 | | ------------- | -------- | ----------- | --------------------------------------------- 17 | | **onSubmit** | Func | | A callback function called on submission 18 | 19 | 20 | ### Other Information 21 | See index.js for the props and example 22 | -------------------------------------------------------------------------------- /client/app/src/components/LoginForm/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import LoginForm from '../index'; 2 | import { shallow } from 'enzyme'; 3 | import { shallowToJson } from 'enzyme-to-json'; 4 | import React from 'react'; 5 | import { fields } from './mocks'; 6 | 7 | describe('', () => { 8 | it('should render with default props', () => { 9 | const wrapper = shallow( 10 | e} 13 | invalid 14 | /> 15 | ); 16 | expect(shallowToJson(wrapper)).toMatchSnapshot(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /client/app/src/containers/EventsContainer/reducer.js: -------------------------------------------------------------------------------- 1 | import * as types from './constants'; 2 | import update from 'react-addons-update'; 3 | 4 | export const initialState = { 5 | current: 10, 6 | }; 7 | 8 | const eventsReducer = 9 | (state = initialState, action) => { 10 | switch (action.type) { 11 | case types.INCREMENT_CURRENT: 12 | return update(state, { 13 | current: { 14 | $set: state.current + 5, 15 | }, 16 | }); 17 | default: 18 | return state; 19 | } 20 | }; 21 | 22 | export default eventsReducer; 23 | -------------------------------------------------------------------------------- /client/app/src/components/EditableField/index.module.scss: -------------------------------------------------------------------------------- 1 | .formField { 2 | @media screen and (max-width: 700px) { 3 | max-width: 90vw; 4 | width: 400px; 5 | } 6 | @media screen and (max-width: 500px) { 7 | max-width: 90vw; 8 | width: 300px; 9 | textarea { 10 | max-width: 280px; 11 | } 12 | } 13 | @media screen and (max-width: 430px) { 14 | width: 270px; 15 | textarea { 16 | max-width: 250px; 17 | } 18 | } 19 | @media screen and (max-width: 400px) { 20 | width: 210px; 21 | textarea { 22 | max-width: 190px; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /client/app/src/components/ToolTip/index.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import Tip from 'grommet-udacity/components/Tip'; 3 | 4 | const ToolTip = ({ 5 | isShowing, 6 | children, 7 | onClose, 8 | }) => ( 9 |
10 | {isShowing && 11 | 12 | {children} 13 | 14 | } 15 |
16 | ); 17 | 18 | ToolTip.propTypes = { 19 | isShowing: PropTypes.bool.isRequired, 20 | children: PropTypes.node.isRequired, 21 | onClose: PropTypes.func.isRequired, 22 | }; 23 | 24 | export default ToolTip; 25 | -------------------------------------------------------------------------------- /client/app/src/containers/CreateEventContainer/constants.js: -------------------------------------------------------------------------------- 1 | export const CREATE_EVENT_ERROR = 'CREATE_EVENT_ERROR'; 2 | export const CREATE_EVENT_MESSAGE = 'CREATE_EVENT_MESSAGE'; 3 | export const CLEAR_CREATE_EVENT_ERROR = 'CLEAR_CREATE_EVENT_ERROR'; 4 | export const CLEAR_CREATE_EVENT_MESSAGE = 'CLEAR_CREATE_EVENT_MESSAGE'; 5 | export const CREATE_EVENT_ADD_GUESTS = 'CREATE_EVENT_ADD_GUESTS'; 6 | export const CREATE_EVENT_SET_START_DATE_FOCUS = 'CREATE_EVENT_SET_START_DATE_FOCUS'; 7 | export const CREATE_EVENT_SET_END_DATE_FOCUS = 'CREATE_EVENT_SET_END_DATE_FOCUS'; 8 | export const SET_EVENT_HOST = 'SET_EVENT_HOST'; 9 | -------------------------------------------------------------------------------- /client/app/src/containers/LandingContainer/tests/__snapshots__/index.test.js.snap: -------------------------------------------------------------------------------- 1 | exports[` should render with default props 1`] = ` 2 | 19 | `; 20 | -------------------------------------------------------------------------------- /client/app/src/containers/EventContainer/tests/__snapshots__/index.test.js.snap: -------------------------------------------------------------------------------- 1 | exports[` should render with default props 1`] = ` 2 | 19 | `; 20 | -------------------------------------------------------------------------------- /client/app/src/components/UserProfile/README.md: -------------------------------------------------------------------------------- 1 | ## UserProfile Component 2 | A component that represents the user's profile, allowing the user to see and interact with data. 3 | 4 | ### Example 5 | 6 | ```js 7 | 23 | ``` 24 | 25 | ### Props 26 | See index.js 27 | -------------------------------------------------------------------------------- /client/config/generators/page/README.md.hbs: -------------------------------------------------------------------------------- 1 | ## {{ properCase name }}Page 2 | A top level page container that corresponds to a route by the same name. 3 | 4 | ### Route Parameters 5 | An paramaters that might be part of the route. 6 | 7 | ### Example Usage 8 | 9 | ```js 10 | <{{ properCase name }}Page /> 11 | ``` 12 | 13 | {{#if wantPropTypes}} 14 | ### Props 15 | 16 | | Prop | Type | Default | Possible Values 17 | | ------------- | -------- | ----------- | --------------------------------------------- 18 | | **myProp** | String | | Any string value 19 | 20 | {{/if}} 21 | 22 | ### Other Information 23 | -------------------------------------------------------------------------------- /client/app/src/containers/EventsContainer/tests/__snapshots__/index.test.js.snap: -------------------------------------------------------------------------------- 1 | exports[` should render with default props 1`] = ` 2 | 19 | `; 20 | -------------------------------------------------------------------------------- /client/app/src/components/AuthFormFooter/README.md: -------------------------------------------------------------------------------- 1 | ## AuthFormFooter Component 2 | A component that shows at the bottom of the form, useful for linking to login / signup. 3 | 4 | ### Example 5 | 6 | ```js 7 | 11 | ``` 12 | 13 | ### Props 14 | 15 | | Prop | Type | Default | Possible Values 16 | | ------------- | -------- | ----------- | --------------------------------------------- 17 | | **link** | String | | Any string value 18 | | **text** | String | | Any string value 19 | 20 | 21 | ### Other Information 22 | Presentational Component 23 | -------------------------------------------------------------------------------- /client/server/app.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const isDeveloping = process.env.NODE_ENV !== 'production'; 3 | const port = isDeveloping ? 1337 : process.env.PORT; 4 | const path = require('path'); 5 | const express = require('express'); 6 | const app = express(); 7 | 8 | app.use(express.static(__dirname + '/public')); 9 | app.get('*', (req, res) => { 10 | res.sendFile(path.join(__dirname, 'public/index.html')); 11 | }); 12 | 13 | app.listen(port, '0.0.0.0', (err) => { 14 | if (err) { 15 | return console.warn(err); 16 | } 17 | return console.info(`==> 😎 Listening on port ${port}. Open http://0.0.0.0:${port}/ in your browser.`); 18 | }); 19 | /* eslint-enable */ -------------------------------------------------------------------------------- /client/app/src/components/AuthFormFooter/index.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import Footer from 'grommet-udacity/components/Footer'; 3 | import Anchor from 'grommet-udacity/components/Anchor'; 4 | 5 | const AuthFormFooter = ({ 6 | link, 7 | text, 8 | }) => ( 9 |
10 | {`${text}`}{' '} 11 | 12 | {`${link.charAt(1).toUpperCase()}${link.slice(2)}`} 13 | 14 |
15 | ); 16 | 17 | AuthFormFooter.propTypes = { 18 | link: PropTypes.string.isRequired, 19 | text: PropTypes.string.isRequired, 20 | }; 21 | 22 | export default AuthFormFooter; 23 | -------------------------------------------------------------------------------- /client/app/src/components/index.js: -------------------------------------------------------------------------------- 1 | /* Assemble all components for export */ 2 | export EditableField from './EditableField'; 3 | export ToolTip from './ToolTip'; 4 | export AppFooter from './AppFooter'; 5 | export LoginForm from './LoginForm'; 6 | export SingleEvent from './SingleEvent'; 7 | export UserProfile from './UserProfile'; 8 | export ToastMessage from './ToastMessage'; 9 | export EventForm from './EventForm'; 10 | export EventInfo from './EventInfo'; 11 | export LoadingIndicator from './LoadingIndicator'; 12 | export AuthFormFooter from './AuthFormFooter'; 13 | export SignupForm from './SignupForm'; 14 | export Navbar from './Navbar'; 15 | export App from './App'; 16 | -------------------------------------------------------------------------------- /client/app/src/pages/LandingPage/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import cssModules from 'react-css-modules'; 3 | import styles from './index.module.scss'; 4 | // Example to import a component using ES6 destructuring. 5 | /* eslint-disable*/ // Containers is an alias, so no file is found 6 | import { LandingContainer } from 'containers'; 7 | /* eslint-enable */ 8 | 9 | // Pages map directly to Routes, i.e. one page equals on Route 10 | // Handler that maps to a route in /utils/routes 11 | const LandingPage = () => ( 12 |
13 | 14 |
15 | ); 16 | 17 | export default cssModules(LandingPage, styles); 18 | -------------------------------------------------------------------------------- /client/server/public/index.html: -------------------------------------------------------------------------------- 1 | Meetup Event Planner
-------------------------------------------------------------------------------- /app/graph/types/auth_user_type.rb: -------------------------------------------------------------------------------- 1 | AuthUserType = GraphQL::ObjectType.define do 2 | name 'AuthUser' 3 | description 'The authenticated user model type' 4 | field :id, types.ID, 'The id of this user' 5 | field :name, !types.String, 'The name of the user' 6 | field :email, !types.String, 'The email of the user' 7 | field :bio, types.String, 'The bio of the user' 8 | field :created_at, types.String, 'The datetime string when the user was created' 9 | field :events, types[EventType], 'The user events' 10 | field :avatar, types.String, 'The user avatar' 11 | field :auth_token, types.String, 'The user auth token' 12 | field :employer, types.String, 'The user employer' 13 | end 14 | -------------------------------------------------------------------------------- /client/app/src/components/LoadingIndicator/README.md: -------------------------------------------------------------------------------- 1 | ## LoadingIndicator Component 2 | A component that acts as a loading indicator. 3 | 4 | ### Example 5 | 6 | ```js 7 | 8 | ``` 9 | 10 | ### Props 11 | 12 | | Prop | Type | Default | Possible Values 13 | | ------------- | -------- | ----------- | --------------------------------------------- 14 | | **isLoading** | Bool | True | Whether the loading indicator is currently loading or not 15 | | **message** | Bool | "Loading" | The message to display with the spinner. 16 | 17 | 18 | ### Other Information 19 | Will be centered in whatever box it is in. 20 | -------------------------------------------------------------------------------- /client/app/src/components/LoginForm/tests/mocks.js: -------------------------------------------------------------------------------- 1 | export const fields = { 2 | emailInput: { 3 | name: 'emailInput', 4 | value: 'demo@udacity.com', 5 | initialValue: '', 6 | valid: true, 7 | invalid: false, 8 | dirty: true, 9 | pristine: false, 10 | active: false, 11 | touched: true, 12 | visited: true, 13 | autofilled: false, 14 | }, 15 | passwordInput: { 16 | name: 'passwordInput', 17 | value: 'Password123!', 18 | initialValue: '', 19 | valid: true, 20 | invalid: false, 21 | dirty: true, 22 | pristine: false, 23 | active: false, 24 | touched: false, 25 | visited: false, 26 | autofilled: false, 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /client/app/src/components/ToastMessage/index.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import Toast from 'grommet-udacity/components/Toast'; 3 | 4 | const ToastMessage = ({ 5 | message, 6 | onClose, 7 | status, 8 | }) => ( 9 | 10 | {message} 11 | 12 | ); 13 | 14 | ToastMessage.propTypes = { 15 | message: PropTypes.string.isRequired, 16 | onClose: PropTypes.func.isRequired, 17 | status: PropTypes.oneOf([ 18 | 'critical', 19 | 'warning', 20 | 'ok', 21 | 'disabled', 22 | 'unknown', 23 | ]), 24 | }; 25 | 26 | ToastMessage.defaultProps = { 27 | status: 'ok', 28 | }; 29 | 30 | export default ToastMessage; 31 | -------------------------------------------------------------------------------- /client/config/templates/_index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Meetup Event Planner 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /client/app/src/containers/EventsContainer/tests/reducer.test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | import * as types from '../constants'; 3 | import eventsReducer, { initialState } from '../reducer'; 4 | 5 | describe('eventsReducer', () => { 6 | it('returns the initial state', () => { 7 | expect( 8 | eventsReducer(undefined, {}) 9 | ).toEqual(initialState); 10 | }); 11 | it('should handle reducer for INCREMENT_CURRENT', () => { 12 | const stateBefore = { 13 | current: 10, 14 | }; 15 | const stateAfter = { 16 | current: 15, 17 | }; 18 | expect( 19 | eventsReducer(stateBefore, { 20 | type: types.INCREMENT_CURRENT 21 | }) 22 | ).toEqual(stateAfter); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Meetup Event Planner 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /app/graph/types/event_type.rb: -------------------------------------------------------------------------------- 1 | EventType = GraphQL::ObjectType.define do 2 | name 'Event' 3 | description 'A event entry' 4 | field :id, types.ID, 'The id of this event' 5 | field :name, types.String, 'The title of this event' 6 | field :start_date, types.String, 'The start date / time' 7 | field :end_date, types.String, 'The end date / time' 8 | field :message, types.String, 'Optional message to the guests' 9 | field :event_type, EventTypeEnum, 'The type of event' 10 | field :host, HostType, 'The host of the event' 11 | field :guests, types[GuestType], 'The guests related to the event' 12 | field :location, types.String, 'The location of the event' 13 | field :created_by, UserType, 'The user who created the Event' 14 | end 15 | -------------------------------------------------------------------------------- /client/app/src/components/EditableField/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import EditableField from '../index'; 2 | import { shallow } from 'enzyme'; 3 | import { shallowToJson } from 'enzyme-to-json'; 4 | import React from 'react'; 5 | 6 | describe('', () => { 7 | it('should render with default props', () => { 8 | const wrapper = shallow( 9 | e} 12 | placeholder="Hello World" 13 | onEdit={e => e} 14 | value={"hello world"} 15 | name="bio" 16 | > 17 |
18 | Hi 19 |
20 |
21 | ); 22 | expect(shallowToJson(wrapper)).toMatchSnapshot(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /client/app/src/components/ToolTip/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import ToolTip from '../index'; 2 | import { shallow } from 'enzyme'; 3 | import { shallowToJson } from 'enzyme-to-json'; 4 | import React from 'react'; 5 | 6 | describe('', () => { 7 | it('should render with default props', () => { 8 | const wrapper = shallow( 9 | e} 12 | > 13 |
14 |
    15 |
  • 16 | Hello 17 |
  • 18 |
  • 19 | Hello Again 20 |
  • 21 |
22 |
23 |
24 | ); 25 | expect(shallowToJson(wrapper)).toMatchSnapshot(); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /client/app/src/components/App/reducer.js: -------------------------------------------------------------------------------- 1 | import update from 'react-addons-update'; 2 | import * as types from './constants'; 3 | 4 | export const initialState = { 5 | user: { 6 | authToken: null, 7 | }, 8 | }; 9 | 10 | const authReducer = (state = initialState, action) => { 11 | switch (action.type) { 12 | case types.AUTHENTICATE_USER: 13 | return update(state, { 14 | user: { 15 | $set: action.user, 16 | }, 17 | }); 18 | case types.INVALIDATE_USER: 19 | return update(state, { 20 | user: { 21 | $set: { 22 | authToken: null, 23 | }, 24 | }, 25 | }); 26 | default: 27 | return state; 28 | } 29 | }; 30 | 31 | export default authReducer; 32 | -------------------------------------------------------------------------------- /client/app/src/containers/EventsContainer/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import Events from '../index'; 2 | import { shallow } from 'enzyme'; 3 | import { shallowToJson } from 'enzyme-to-json'; 4 | import React from 'react'; 5 | import configureMockStore from 'redux-mock-store'; 6 | import thunk from 'redux-thunk'; 7 | import { initialState as eventsContainer } from '../reducer'; 8 | 9 | const middlewares = [thunk]; 10 | const mockStore = configureMockStore(middlewares); 11 | 12 | describe('', () => { 13 | it('should render with default props', () => { 14 | const store = mockStore({ eventsContainer }); 15 | const wrapper = shallow( 16 | 17 | ); 18 | expect(shallowToJson(wrapper)).toMatchSnapshot(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /client/app/src/containers/SignupContainer/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import Signup from '../index'; 2 | import { shallow } from 'enzyme'; 3 | import { shallowToJson } from 'enzyme-to-json'; 4 | import React from 'react'; 5 | import configureMockStore from 'redux-mock-store'; 6 | import thunk from 'redux-thunk'; 7 | import { initialState as signupContainer } from '../reducer'; 8 | 9 | const middlewares = [thunk]; 10 | const mockStore = configureMockStore(middlewares); 11 | 12 | describe('', () => { 13 | it('should render with default props', () => { 14 | const store = mockStore({ signupContainer }); 15 | const wrapper = shallow( 16 | 17 | ); 18 | expect(shallowToJson(wrapper)).toMatchSnapshot(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /client/config/generators/container/test.js.hbs: -------------------------------------------------------------------------------- 1 | import {{ properCase name }} from '../index'; 2 | import { shallow } from 'enzyme'; 3 | import { shallowToJson } from 'enzyme-to-json'; 4 | import React from 'react'; 5 | import configureMockStore from 'redux-mock-store'; 6 | import thunk from 'redux-thunk'; 7 | import { initialState } from '../reducer'; 8 | 9 | const middlewares = [thunk]; 10 | const mockStore = configureMockStore(middlewares); 11 | 12 | describe('<{{ properCase name }} />', () => { 13 | it('should render with default props', () => { 14 | const store = mockStore(initialState); 15 | const wrapper = shallow( 16 | <{{ properCase name }} store={store} /> 17 | ); 18 | expect(shallowToJson(wrapper)).toMatchSnapshot(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /client/app/src/components/ToastMessage/README.md: -------------------------------------------------------------------------------- 1 | ## ToastMessage Component 2 | A toast component that shows up at the top of the screen with an alert. 3 | message, 4 | onClose, 5 | status, 6 | ### Example 7 | 8 | ```js 9 | 10 | ``` 11 | 12 | ### Props 13 | 14 | | Prop | Type | Default | Possible Values 15 | | ------------- | -------- | ----------- | --------------------------------------------- 16 | | **message** | String | | Any string value 17 | | **onClose** | Func | | A callback when requesting to close 18 | | **status** | String / Enum | 'ok' | An enum value, one of: critical|warning|ok|disabled|unknown 19 | 20 | 21 | ### Other Information 22 | See: https://grommet.github.io/docs/toast 23 | -------------------------------------------------------------------------------- /client/app/src/containers/EventContainer/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import Event from '../index'; 2 | import { shallow } from 'enzyme'; 3 | import { shallowToJson } from 'enzyme-to-json'; 4 | import React from 'react'; 5 | 6 | import configureMockStore from 'redux-mock-store'; 7 | import thunk from 'redux-thunk'; 8 | import { initialState as authReducer } from '../../../components/App/reducer'; 9 | 10 | const middlewares = [thunk]; 11 | const mockStore = configureMockStore(middlewares); 12 | 13 | describe('', () => { 14 | it('should render with default props', () => { 15 | const store = mockStore({ authReducer }); 16 | const wrapper = shallow( 17 | 18 | ); 19 | expect(shallowToJson(wrapper)).toMatchSnapshot(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /client/app/src/containers/LandingContainer/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import Landing from '../index'; 2 | import { shallow } from 'enzyme'; 3 | import { shallowToJson } from 'enzyme-to-json'; 4 | import React from 'react'; 5 | import configureMockStore from 'redux-mock-store'; 6 | import thunk from 'redux-thunk'; 7 | import { initialState as authReducer } from '../../../components/App/reducer'; 8 | 9 | const middlewares = [thunk]; 10 | const mockStore = configureMockStore(middlewares); 11 | 12 | describe('', () => { 13 | it('should render with default props', () => { 14 | const store = mockStore({ authReducer }); 15 | const wrapper = shallow( 16 | 17 | ); 18 | expect(shallowToJson(wrapper)).toMatchSnapshot(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /client/app/src/containers/ProfileContainer/constants.js: -------------------------------------------------------------------------------- 1 | export const PROFILE_EDIT_BIO = 'PROFILE_EDIT_BIO'; 2 | export const PROFILE_SUBMISSION_INITIATION = 'PROFILE_SUBMISSION_INITIATION'; 3 | export const PROFILE_SUBMISSION_SUCCESS = 'PROFILE_SUBMISSION_SUCCESS'; 4 | export const PROFILE_SUBMISSION_FAILURE = 'PROFILE_SUBMISSION_FAILURE'; 5 | export const PROFILE_CLEAR_ERROR = 'PROFILE_CLEAR_ERROR'; 6 | export const PROFILE_CANCEL_EDITING = 'PROFILE_CANCEL_EDITING'; 7 | export const PROFILE_START_EDITING = 'PROFILE_START_EDITING'; 8 | export const PROFILE_EDIT_AVATAR = 'PROFILE_EDIT_AVATAR'; 9 | export const PROFILE_EDIT_EMAIL = 'PROFILE_EDIT_EMAIL'; 10 | export const PROFILE_EDIT_EMPLOYER = 'PROFILE_EDIT_EMPLOYER'; 11 | export const PROFILE_SET_DEFAULT_INPUTS = 'PROFILE_SET_DEFAULT_INPUTS'; 12 | -------------------------------------------------------------------------------- /client/config/generators/component/stateless.js.hbs: -------------------------------------------------------------------------------- 1 | {{#if wantPropTypes}} 2 | import React, { PropTypes } from 'react'; 3 | {{else}} 4 | import React from 'react'; 5 | {{/if}} 6 | 7 | {{#if wantSCSSModules}} 8 | import styles from './index.module.scss'; 9 | import cssModules from 'react-css-modules'; 10 | {{/if}} 11 | 12 | const {{ properCase name }} = (props) => ( 13 | {{#if wantSCSSModules}} 14 |
15 | {{else}} 16 |
17 | {{/if}} 18 |
19 | ); 20 | 21 | {{#if wantPropTypes}} 22 | {{ properCase name }}.propTypes = { 23 | 24 | }; 25 | {{/if}} 26 | 27 | {{#if wantSCSSModules}} 28 | export default cssModules({{ properCase name }}, styles); 29 | {{else}} 30 | export default {{ properCase name }}; 31 | {{/if}} 32 | -------------------------------------------------------------------------------- /client/app/src/components/SignupForm/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import SignupForm from '../index'; 2 | import { shallow } from 'enzyme'; 3 | import { shallowToJson } from 'enzyme-to-json'; 4 | import React from 'react'; 5 | import { fields } from './mocks'; 6 | 7 | describe('', () => { 8 | it('should render with default props', () => { 9 | const wrapper = shallow( 10 | e} 13 | isShowingPasswordTips={(e) => e} 14 | onPasswordFocus={(e) => e} 15 | onPasswordBlur={(e) => e} 16 | onInvalidateTip={(e) => e} 17 | invalid 18 | isShowingPasswordTips 19 | tipIsValid 20 | /> 21 | ); 22 | expect(shallowToJson(wrapper)).toMatchSnapshot(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /spec/models/user_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe User, type: :model do 4 | it 'is valid with valid attributes' do 5 | expect( 6 | User.new( 7 | name: 'GraphQL User', 8 | email: 'user@graphql.org', 9 | password: 'GraphQLUser123' 10 | ) 11 | ).to be_valid 12 | end 13 | it 'is not valid without a name' do 14 | expect( 15 | User.new( 16 | name: nil, 17 | email: 'user@graphql.org', 18 | password: 'GraphQLUser123' 19 | ) 20 | ).to_not be_valid 21 | end 22 | it 'is not valid without a valid email' do 23 | expect( 24 | User.new( 25 | name: 'GraphQL User', 26 | email: 'user@graphql', 27 | password: 'GraphQLUser123' 28 | ) 29 | ).to_not be_valid 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | npm-debug.log 4 | .DS_Store 5 | ======= 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # node-waf configuration 29 | .lock-wscript 30 | 31 | # Compiled binary addons (http://nodejs.org/api/addons.html) 32 | build/Release 33 | 34 | # Dependency directories 35 | node_modules 36 | jspm_packages 37 | 38 | # Optional npm cache directory 39 | .npm 40 | 41 | # Optional REPL history 42 | .node_repl_history 43 | -------------------------------------------------------------------------------- /client/app/src/containers/LoginContainer/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import Login from '../index'; 2 | import { shallow } from 'enzyme'; 3 | import { shallowToJson } from 'enzyme-to-json'; 4 | import React from 'react'; 5 | import configureMockStore from 'redux-mock-store'; 6 | import thunk from 'redux-thunk'; 7 | import { initialState as loginContainer } from '../reducer'; 8 | import { initialState as authReducer } from '../../../components/App/reducer'; 9 | 10 | const middlewares = [thunk]; 11 | const mockStore = configureMockStore(middlewares); 12 | 13 | describe('', () => { 14 | it('should render with default props', () => { 15 | const store = mockStore({ loginContainer, authReducer }); 16 | const wrapper = shallow( 17 | 18 | ); 19 | expect(shallowToJson(wrapper)).toMatchSnapshot(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /client/config/generators/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const componentGenerator = require('./component/index.js'); 3 | const containerGenerator = require('./container/index.js'); 4 | const pagesGenerator = require('./page/index.js'); 5 | 6 | module.exports = (plop) => { 7 | plop.setGenerator('component', componentGenerator); 8 | plop.setGenerator('container', containerGenerator); 9 | plop.setGenerator('page', pagesGenerator); 10 | plop.addHelper('uppercase', (text) => { 11 | return text.toUpperCase(); 12 | }); 13 | plop.addHelper('directory', (comp) => { 14 | try { 15 | fs.accessSync(`app/src/containers/${comp}`, fs.F_OK); 16 | return `containers/${comp}`; 17 | } catch (e) { 18 | return `components/${comp}`; 19 | } 20 | }); 21 | plop.addHelper('curly', (object, open) => (open ? '{' : '}')); 22 | }; 23 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more, please read the Rails Internationalization guide 20 | # available at http://guides.rubyonrails.org/i18n.html. 21 | 22 | en: 23 | hello: "Hello world" 24 | application_controller: 25 | unauthorized: "You are not authorized to access this part of the app." 26 | -------------------------------------------------------------------------------- /client/app/src/containers/ProfileContainer/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import Profile from '../index'; 2 | import { shallow } from 'enzyme'; 3 | import { shallowToJson } from 'enzyme-to-json'; 4 | import React from 'react'; 5 | import configureMockStore from 'redux-mock-store'; 6 | import thunk from 'redux-thunk'; 7 | import { initialState as profileContainer } from '../reducer'; 8 | import { initialState as authReducer } from '../../../components/App/reducer'; 9 | 10 | const middlewares = [thunk]; 11 | const mockStore = configureMockStore(middlewares); 12 | 13 | describe('', () => { 14 | it('should render with default props', () => { 15 | const store = mockStore({ profileContainer, authReducer }); 16 | const wrapper = shallow( 17 | 18 | ); 19 | expect(shallowToJson(wrapper)).toMatchSnapshot(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /client/app/src/components/LoginForm/README.md: -------------------------------------------------------------------------------- 1 | ## LoginForm Component 2 | A reusable login form component. 3 | 4 | ### Example 5 | 6 | ```js 7 | 12 | ``` 13 | 14 | ### Props 15 | 16 | | Prop | Type | Default | Possible Values 17 | | ------------- | -------- | ----------- | --------------------------------------------- 18 | | **invalid** | Boolean | | Redux form, invalid parameter 19 | | **passwordInput** | Object | | Redux form, password Input 20 | | **emailInput** | Object | | Redux form, email Input 21 | | **onSubmit** | Func | | Redux form, callback func for submission 22 | 23 | 24 | ### Other Information 25 | This component relies on redux form. See the LoginContainer for example usage. 26 | -------------------------------------------------------------------------------- /client/app/src/components/EventInfo/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import EventInfo from '../index'; 2 | import { shallow } from 'enzyme'; 3 | import { shallowToJson } from 'enzyme-to-json'; 4 | import React from 'react'; 5 | 6 | describe('', () => { 7 | it('should render with default props', () => { 8 | const event = { 9 | id: '4', 10 | name: 'Pool Party', 11 | endDate: '2014-12-12 00:00:00 UTC', 12 | startDate: '2016-11-02 00:00:00 UTC', 13 | type: 'birthday', 14 | location: '1204 North Carolina 55, Fuquay Varina, NC, United States', 15 | message: 'The best party', 16 | host: { name: 'Joaquin West' }, 17 | guests: [{ name: 'Bilbo Baggins' }], 18 | }; 19 | const wrapper = shallow( 20 | 21 | ); 22 | expect(shallowToJson(wrapper)).toMatchSnapshot(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /client/app/src/components/EventForm/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import EventForm from '../index'; 2 | import { shallow } from 'enzyme'; 3 | import { shallowToJson } from 'enzyme-to-json'; 4 | import React from 'react'; 5 | import { 6 | fields, 7 | mockFn, 8 | pastGuests, 9 | guestList, 10 | pastHosts, 11 | eventTypes, 12 | } from './mock'; 13 | 14 | describe('', () => { 15 | it('should render with default props', () => { 16 | const wrapper = shallow( 17 | 28 | ); 29 | expect(shallowToJson(wrapper)).toMatchSnapshot(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /client/app/src/containers/CreateEventContainer/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import CreateEvent from '../index'; 2 | import { shallow } from 'enzyme'; 3 | import { shallowToJson } from 'enzyme-to-json'; 4 | import React from 'react'; 5 | import configureMockStore from 'redux-mock-store'; 6 | import thunk from 'redux-thunk'; 7 | import { initialState as createEventContainer } from '../reducer'; 8 | import { initialState as authReducer } from '../../../components/App/reducer'; 9 | 10 | const middlewares = [thunk]; 11 | const mockStore = configureMockStore(middlewares); 12 | 13 | describe('', () => { 14 | it('should render with default props', () => { 15 | const store = mockStore({ createEventContainer, authReducer }); 16 | const wrapper = shallow( 17 | 18 | ); 19 | expect(shallowToJson(wrapper)).toMatchSnapshot(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | require 'fileutils' 4 | include FileUtils 5 | 6 | # path to your application root. 7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | chdir APP_ROOT do 14 | # This script is a way to update your development environment automatically. 15 | # Add necessary update steps to this file. 16 | 17 | puts '== Installing dependencies ==' 18 | system! 'gem install bundler --conservative' 19 | system('bundle check') || system!('bundle install') 20 | 21 | puts "\n== Updating database ==" 22 | system! 'bin/rails db:migrate' 23 | 24 | puts "\n== Removing old logs and tempfiles ==" 25 | system! 'bin/rails log:clear tmp:clear' 26 | 27 | puts "\n== Restarting application server ==" 28 | system! 'bin/rails restart' 29 | end 30 | -------------------------------------------------------------------------------- /client/app/src/containers/LoginContainer/utils/validation.js: -------------------------------------------------------------------------------- 1 | import * as validation from '../../../utils/validation'; 2 | import memoize from 'lru-memoize'; 3 | 4 | // Compose validation functions for all input fields 5 | const passwordInput = [ 6 | validation.containsLowercase, 7 | validation.containsUppercase, 8 | validation.minLength(8), 9 | validation.maxLength(20), 10 | validation.containsNumber, 11 | validation.valueRequired, 12 | validation.containsSpecialChar, 13 | ]; 14 | 15 | const emailInput = [ 16 | validation.isEmail, 17 | validation.valueRequired, 18 | validation.maxLength(50), 19 | validation.minLength(2), 20 | ]; 21 | 22 | // Create the validator 23 | const signupValidation = validation.createValidator({ 24 | passwordInput, 25 | emailInput, 26 | }); 27 | 28 | /* Memoize and export */ 29 | const validator = memoize(10)(signupValidation); 30 | export default validator; 31 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ApplicationRecord 2 | before_create :generate_auth_token! 3 | has_many :events 4 | 5 | # Include default devise modules. Others available are: 6 | # :confirmable, :lockable, :timeoutable and :omniauthable 7 | devise :database_authenticatable, :registerable, 8 | :recoverable, :rememberable, :trackable, :validatable 9 | before_save { self.email = email.downcase } 10 | 11 | # Active Record validations 12 | validates :auth_token, uniqueness: true 13 | validates :name, presence: true, length: { maximum: 50 } 14 | VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i 15 | validates :email, presence: true, length: { maximum: 255 }, 16 | format: { with: VALID_EMAIL_REGEX }, 17 | uniqueness: { case_sensitive: false } 18 | 19 | def generate_auth_token! 20 | self.auth_token = Devise.friendly_token 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /client/app/src/components/ToolTip/README.md: -------------------------------------------------------------------------------- 1 | ## ToolTip Component 2 | A reusable tooltip component. 3 | 4 | ### Example 5 | 6 | ```js 7 | e} 10 | > 11 |
    12 |
  • 1. Do this than that
  • 13 |
14 |
15 | ``` 16 | isShowing: PropTypes.bool.isRequired, 17 | children: PropTypes.node.isRequired, 18 | onClose: PropTypes.func.isRequired, 19 | 20 | ### Props 21 | 22 | | Prop | Type | Default | Possible Values 23 | | ------------- | -------- | ----------- | --------------------------------------------- 24 | | **isShowing** | Bool | | Is the tip showing? 25 | | **onClose** | Func | | The on close callback 26 | | **children** | Node | | React node to show in the tip. 27 | 28 | 29 | ### Other Information 30 | Relies on the grommet tip component. https://grommet.github.io/docs/tip 31 | -------------------------------------------------------------------------------- /client/app/src/components/UserProfile/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import UserProfile from '../index'; 2 | import { shallow } from 'enzyme'; 3 | import { shallowToJson } from 'enzyme-to-json'; 4 | import React from 'react'; 5 | import { user, mockFn } from './mocks'; 6 | 7 | describe('', () => { 8 | it('should render with default props', () => { 9 | const wrapper = shallow( 10 | 26 | ); 27 | expect(shallowToJson(wrapper)).toMatchSnapshot(); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /client/app/src/components/EventInfo/README.md: -------------------------------------------------------------------------------- 1 | ## EventInfo Component 2 | A component that takes an event and renders the event's info 3 | 4 | ### Example 5 | 6 | ```js 7 | 8 | ``` 9 | 10 | ### Props 11 | 12 | | Prop | Type | Default | Possible Values 13 | | ------------- | -------- | ----------- | --------------------------------------------- 14 | | **event** | Object | | An object, with the shape shown below 15 | 16 | ``` 17 | event: PropTypes.shape({ 18 | id: PropTypes.string.isRequired, 19 | name: PropTypes.string.isRequired, 20 | end: PropTypes.string.isRequired, 21 | start: PropTypes.string.isRequired, 22 | type: PropTypes.string.isRequired, 23 | location: PropTypes.string.isRequired, 24 | message: PropTypes.string.isRequired, 25 | host: PropTypes.shape({ 26 | name: PropTypes.string.isRequired, 27 | }), 28 | guests: PropTypes.array.isRequired, 29 | }), 30 | ``` 31 | -------------------------------------------------------------------------------- /client/config/generators/component/es6class.js.hbs: -------------------------------------------------------------------------------- 1 | {{#if wantPropTypes}} 2 | import React, { PropTypes, Component } from 'react'; 3 | {{else}} 4 | import React, { Component } from 'react'; 5 | {{/if}} 6 | {{#if wantSCSSModules}} 7 | import styles from './index.module.scss'; 8 | import cssModules from 'react-css-modules'; 9 | {{/if}} 10 | 11 | class {{ properCase name }} extends Component { // eslint-disable-line react/prefer-stateless-function 12 | render() { 13 | return ( 14 | {{#if wantSCSSModules}} 15 |
16 | {{else}} 17 |
18 | {{/if}} 19 |
20 | ); 21 | } 22 | } 23 | 24 | {{#if wantPropTypes}} 25 | {{ properCase name }}.propTypes = { 26 | 27 | }; 28 | {{/if}} 29 | 30 | {{#if wantSCSSModules}} 31 | export default cssModules({{ properCase name }}, styles); 32 | {{else}} 33 | export default {{ properCase name }}; 34 | {{/if}} 35 | -------------------------------------------------------------------------------- /client/app/src/components/App/actions.js: -------------------------------------------------------------------------------- 1 | import * as types from './constants'; 2 | 3 | export const authenticateUser = (user) => ({ 4 | type: types.AUTHENTICATE_USER, 5 | user, 6 | }); 7 | 8 | export const invalidateUser = () => ({ 9 | type: types.INVALIDATE_USER, 10 | }); 11 | 12 | export const logoutUser = () => (dispatch) => { 13 | localStorage.setItem('user', null); 14 | dispatch( 15 | invalidateUser() 16 | ); 17 | }; 18 | 19 | export const setPersistentUser = (user) => (dispatch) => { 20 | localStorage.setItem('user', JSON.stringify(user)); 21 | dispatch( 22 | authenticateUser(user) 23 | ); 24 | }; 25 | 26 | export const loadPersistedUser = () => (dispatch) => { 27 | const user = localStorage.getItem('user'); 28 | if (user) { 29 | const parsedUser = JSON.parse(user); 30 | dispatch( 31 | authenticateUser(parsedUser) 32 | ); 33 | } else { 34 | dispatch( 35 | invalidateUser() 36 | ); 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /client/app/src/components/AppFooter/tests/__snapshots__/index.test.js.snap: -------------------------------------------------------------------------------- 1 | exports[` should render with default props 1`] = ` 2 |
6 | 19 | 21 | 24 | Meetup Event Planner 25 | 26 | 27 | 29 | Made with ♥️ by 30 | 33 | Ryan Collins 34 | 35 | 36 | 37 |
38 | `; 39 | -------------------------------------------------------------------------------- /client/app/src/components/LoadingIndicator/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import LoadingIndicator from '../index'; 2 | import expect from 'expect'; 3 | import { shallow } from 'enzyme'; 4 | import React from 'react'; 5 | import Spinning from 'grommet/components/icons/Spinning'; 6 | import Box from 'grommet/components/Box'; 7 | import Heading from 'grommet/components/Heading'; 8 | 9 | describe('', () => { 10 | it('should render with an h2', () => { 11 | const component = shallow(); 12 | expect( 13 | component.find('h2') 14 | ).toExist(); 15 | }); 16 | it('should render with expected components', () => { 17 | const component = shallow(); 18 | expect( 19 | component.find() 20 | ).toExist(); 21 | expect( 22 | component.find() 23 | ).toExist(); 24 | expect( 25 | component.find() 26 | ).toExist(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /client/app/src/reducers.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { routerReducer } from 'react-router-redux'; 3 | import { reducer as formReducer } from 'redux-form'; 4 | import client from './apolloClient'; 5 | 6 | import authReducer from 'components/App/reducer'; 7 | import loginContainer from 'containers/LoginContainer/reducer'; 8 | import signupContainer from 'containers/SignupContainer/reducer'; 9 | import createEventContainer from 'containers/CreateEventContainer/reducer'; 10 | import eventsContainer from 'containers/EventsContainer/reducer'; 11 | import profileContainer from 'containers/ProfileContainer/reducer'; 12 | 13 | const rootReducer = combineReducers({ 14 | // Apply all of the reducers here. 15 | authReducer, 16 | loginContainer, 17 | signupContainer, 18 | createEventContainer, 19 | profileContainer, 20 | eventsContainer, 21 | routing: routerReducer, 22 | form: formReducer, 23 | apollo: client.reducer(), 24 | }); 25 | 26 | export default rootReducer; 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.rbc 2 | capybara-*.html 3 | .rspec 4 | /log 5 | /tmp 6 | /db/*.sqlite3 7 | /db/*.sqlite3-journal 8 | /public/system 9 | /coverage/ 10 | /spec/tmp 11 | **.orig 12 | rerun.txt 13 | pickle-email-*.html 14 | 15 | # TODO Comment out these rules if you are OK with secrets being uploaded to the repo 16 | config/initializers/secret_token.rb 17 | config/secrets.yml 18 | 19 | # dotenv 20 | # TODO Comment out this rule if environment variables can be committed 21 | .env 22 | 23 | ## Environment normalization: 24 | /.bundle 25 | /vendor/bundle 26 | 27 | # these should all be checked in to normalize the environment: 28 | # Gemfile.lock, .ruby-version, .ruby-gemset 29 | 30 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 31 | .rvmrc 32 | 33 | # if using bower-rails ignore default bower_components path bower.json files 34 | /vendor/assets/bower_components 35 | *.bowerrc 36 | bower.json 37 | 38 | # Ignore pow environment settings 39 | .powenv 40 | 41 | # Ignore Byebug command history file. 42 | .byebug_history 43 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative 'boot' 2 | 3 | require 'rails/all' 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(*Rails.groups) 8 | 9 | module RailsGraphqlBoilerplate 10 | class Application < Rails::Application 11 | config.api_only = false 12 | config.secret_key_base = ENV["SECRET_KEY_BASE"] 13 | config.autoload_paths << Rails.root.join('app/graph') 14 | config.autoload_paths << Rails.root.join('app/graph/types') 15 | config.autoload_paths << Rails.root.join('app/graph/fields') 16 | config.autoload_paths << Rails.root.join('app/graph/mutations') 17 | config.autoload_paths << Rails.root.join('app/graph/queries') 18 | config.generators do |g| 19 | g.test_framework :rspec 20 | end 21 | config.middleware.insert_before 0, "Rack::Cors" do 22 | allow do 23 | origins '*' 24 | resource '*', :headers => :any, :methods => [:get, :post, :options] 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /client/app/src/components/UserProfile/index.module.scss: -------------------------------------------------------------------------------- 1 | .avatar { 2 | width: 100px; 3 | height: 100px; 4 | border-radius: 50%; 5 | border: 2px solid transparent; 6 | overflow: hidden; 7 | cursor: pointer; 8 | transition: all .3s ease-in-out; 9 | border-radius: 50%; 10 | border: 1px solid #dbe2e8; 11 | margin: 0 auto; 12 | position: relative; 13 | } 14 | 15 | .input { 16 | width: 100%; 17 | } 18 | 19 | .panel { 20 | background: #fff; 21 | box-shadow: 0 2px 4px 0 rgba(167, 157, 144, 0.52); 22 | width: 100%; 23 | margin-bottom: 80px; 24 | position: relative; 25 | padding: 60px 0 !important; 26 | max-width: 900px; 27 | } 28 | 29 | .transformAvatar { 30 | display: inline-block; 31 | line-height: 1; 32 | overflow: hidden; 33 | pointer-events: none; 34 | position: absolute; 35 | top: -100px; 36 | left: 0; 37 | width: 100%; 38 | @media screen and (max-width: 768px) { 39 | top: -80px; 40 | } 41 | } 42 | 43 | .isButton { 44 | cursor: pointer; 45 | } 46 | 47 | .paragraph { 48 | padding: 0 20px; 49 | } 50 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | require 'fileutils' 4 | include FileUtils 5 | 6 | # path to your application root. 7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | chdir APP_ROOT do 14 | # This script is a starting point to setup your application. 15 | # Add necessary setup steps to this file. 16 | 17 | puts '== Installing dependencies ==' 18 | system! 'gem install bundler --conservative' 19 | system('bundle check') || system!('bundle install') 20 | 21 | # puts "\n== Copying sample files ==" 22 | # unless File.exist?('config/database.yml') 23 | # cp 'config/database.yml.sample', 'config/database.yml' 24 | # end 25 | 26 | puts "\n== Preparing database ==" 27 | system! 'bin/rails db:setup' 28 | 29 | puts "\n== Removing old logs and tempfiles ==" 30 | system! 'bin/rails log:clear tmp:clear' 31 | 32 | puts "\n== Restarting application server ==" 33 | system! 'bin/rails restart' 34 | end 35 | -------------------------------------------------------------------------------- /client/app/src/containers/LandingContainer/index.module.scss: -------------------------------------------------------------------------------- 1 | .landing { 2 | padding-top: 0 !important; 3 | } 4 | 5 | .buttonBox { 6 | height: 150px; 7 | margin-top: 30px; 8 | } 9 | 10 | .heroTitle { 11 | text-shadow: 2px 2px 0 #169dc1; 12 | } 13 | 14 | .paragraph { 15 | padding: 10px 40px; 16 | max-width: 576px; 17 | } 18 | 19 | .articleBox { 20 | background: #fff; 21 | box-shadow: 0 2px 4px 0 rgba(167, 157, 144, 0.52); 22 | width: 100%; 23 | margin-bottom: 80px; 24 | position: relative; 25 | padding: 60px 0 !important; 26 | max-width: 900px; 27 | box-sizing : border-box; 28 | } 29 | 30 | .article { 31 | max-width: 100vw; 32 | box-sizing: border-box; 33 | } 34 | 35 | .avatar { 36 | width: 200px; 37 | height: 200px; 38 | box-shadow: 0 0 10px rgba(0,0,0,.5); 39 | padding: 4px; 40 | line-height: 1.42857143; 41 | background-color: #fff; 42 | border: 1px solid #ddd; 43 | border-radius: 50%; 44 | } 45 | 46 | .headlineBox { 47 | max-width: 90vw; 48 | box-sizing: border-box; 49 | text-align: center; 50 | padding: 24px; 51 | } 52 | -------------------------------------------------------------------------------- /client/app/src/components/LoadingIndicator/index.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import styles from './index.module.scss'; 3 | import cssModules from 'react-css-modules'; 4 | import Spinning from 'grommet/components/icons/Spinning'; 5 | import Box from 'grommet/components/Box'; 6 | import Heading from 'grommet/components/Heading'; 7 | 8 | const LoadingIndicator = ({ 9 | isLoading, 10 | message, 11 | }) => ( 12 | 17 | {isLoading && 18 | 22 | 23 | {message} 24 | 25 | } 26 | 27 | ); 28 | 29 | 30 | LoadingIndicator.propTypes = { 31 | isLoading: PropTypes.bool.isRequired, 32 | message: PropTypes.string.isRequired, 33 | }; 34 | 35 | LoadingIndicator.defaultProps = { 36 | isLoading: true, 37 | message: 'Loading', 38 | }; 39 | 40 | export default cssModules(LoadingIndicator, styles); 41 | -------------------------------------------------------------------------------- /client/app/src/containers/CreateEventContainer/tests/__snapshots__/index.test.js.snap: -------------------------------------------------------------------------------- 1 | exports[` should render with default props 1`] = ` 2 | 32 | `; 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Ryan Collins 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 | -------------------------------------------------------------------------------- /client/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Ryan Collins 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 | -------------------------------------------------------------------------------- /client/app/src/components/EventForm/README.md: -------------------------------------------------------------------------------- 1 | ## EventForm Component 2 | A component that handles event creation. 3 | 4 | ### Example 5 | 6 | ```js 7 | 18 | ``` 19 | 20 | ### Props 21 | 22 | | Prop | Type | Default | Possible Values 23 | | ------------- | -------- | ----------- | --------------------------------------------- 24 | | **onSubmit** | Func | | Any function value 25 | | **pastGuests** | Array | | An array of past guests 26 | | **eventTypes** | Array | | An array of event types 27 | | **guestList** | Array | | The current guest list for the submission 28 | | **onRemoveGuest** | Func | | Any function value 29 | | **onAddGuest** | Func | | Any function value 30 | | **invalid** | Bool | | Boolean to determine if the form is valid or not 31 | -------------------------------------------------------------------------------- /client/app/src/containers/LoginContainer/actions.js: -------------------------------------------------------------------------------- 1 | import * as types from './constants'; 2 | 3 | // loginShowError :: String -> {Action} 4 | export const loginShowError = (error) => ({ 5 | type: types.LOGIN_SHOW_ERROR, 6 | error, 7 | }); 8 | 9 | // loginShowMessage :: String -> {Action} 10 | export const loginShowMessage = (message) => ({ 11 | type: types.LOGIN_SHOW_MESSAGE, 12 | message, 13 | }); 14 | 15 | // loginClearError :: None -> {Action} 16 | export const loginClearError = () => ({ 17 | type: types.LOGIN_CLEAR_ERROR, 18 | }); 19 | 20 | // loginClearMessage :: None -> {Action} 21 | export const loginClearMessage = () => ({ 22 | type: types.LOGIN_CLEAR_MESSAGE, 23 | }); 24 | 25 | export const clearLoginToast = (type) => 26 | (dispatch) => { 27 | switch (type) { 28 | case 'error': 29 | dispatch( 30 | loginClearError() 31 | ); 32 | break; 33 | case 'message': 34 | dispatch( 35 | loginClearMessage() 36 | ); 37 | break; 38 | default: 39 | break; 40 | } 41 | }; 42 | 43 | export const loginSetLoading = () => ({ 44 | type: types.LOGIN_SET_LOADING, 45 | }); 46 | -------------------------------------------------------------------------------- /client/app/src/pages/LogoutPage/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import cssModules from 'react-css-modules'; 3 | import styles from './index.module.scss'; 4 | import * as AppActions from 'components/App/actions'; 5 | import { connect } from 'react-redux'; 6 | import { bindActionCreators } from 'redux'; 7 | 8 | class LogoutPage extends Component { 9 | componentDidMount() { 10 | const { 11 | logoutUser, 12 | } = this.props.actions; 13 | logoutUser(); 14 | this.context.router.push('/'); 15 | } 16 | render() { 17 | return ( 18 |
19 | ); 20 | } 21 | } 22 | 23 | LogoutPage.propTypes = { 24 | actions: PropTypes.object.isRequired, 25 | }; 26 | 27 | LogoutPage.contextTypes = { 28 | router: PropTypes.func.isRequired, 29 | }; 30 | 31 | // mapDispatchToProps :: Dispatch -> {Action} 32 | const mapDispatchToProps = (dispatch) => ({ 33 | actions: bindActionCreators( 34 | AppActions, 35 | dispatch 36 | ), 37 | }); 38 | 39 | const Container = cssModules(LogoutPage, styles); 40 | 41 | export default connect( 42 | null, 43 | mapDispatchToProps, 44 | )(Container); 45 | -------------------------------------------------------------------------------- /client/app/src/containers/SignupContainer/utils/validation.js: -------------------------------------------------------------------------------- 1 | import * as validation from '../../../utils/validation'; 2 | import memoize from 'lru-memoize'; 3 | 4 | // Compose validation functions for all input fields 5 | const passwordInput = [ 6 | validation.containsLowercase, 7 | validation.containsUppercase, 8 | validation.minLength(8), 9 | validation.maxLength(20), 10 | validation.containsNumber, 11 | validation.valueRequired, 12 | validation.containsSpecialChar, 13 | ]; 14 | 15 | const nameInput = [ 16 | validation.containsTwoWords, 17 | validation.valueRequired, 18 | validation.maxLength(50), 19 | ]; 20 | 21 | const emailInput = [ 22 | validation.isEmail, 23 | validation.valueRequired, 24 | validation.maxLength(50), 25 | validation.minLength(2), 26 | ]; 27 | 28 | const passwordConfirmationInput = [ 29 | validation.valueRequired, 30 | ]; 31 | 32 | // Create the validator 33 | const signupValidation = validation.createValidator({ 34 | passwordInput, 35 | nameInput, 36 | emailInput, 37 | passwordConfirmationInput, 38 | }); 39 | 40 | /* Memoize and export */ 41 | const validator = memoize(10)(signupValidation); 42 | export default validator; 43 | -------------------------------------------------------------------------------- /client/app/src/containers/CreateEventContainer/tests/actions.test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | import * as actions from '../actions'; 3 | import * as types from '../constants'; 4 | 5 | describe('CreateEvent actions', () => { 6 | it('has a type of CREATE_EVENT_ERROR', () => { 7 | const error = 'Error'; 8 | const expected = { 9 | type: types.CREATE_EVENT_ERROR, 10 | error, 11 | }; 12 | expect(actions.createEventError(error)).toEqual(expected); 13 | }); 14 | it('has a type of CREATE_EVENT_MESSAGE', () => { 15 | const message = 'Message'; 16 | const expected = { 17 | type: types.CREATE_EVENT_MESSAGE, 18 | message, 19 | }; 20 | expect(actions.createEventMessage(message)).toEqual(expected); 21 | }); 22 | it('has a type of CLEAR_CREATE_EVENT_ERROR', () => { 23 | const expected = { 24 | type: types.CLEAR_CREATE_EVENT_ERROR, 25 | }; 26 | expect(actions.clearCreateEventError()).toEqual(expected); 27 | }); 28 | it('has a type of CLEAR_CREATE_EVENT_MESSAGE', () => { 29 | const expected = { 30 | type: types.CLEAR_CREATE_EVENT_MESSAGE, 31 | }; 32 | expect(actions.clearCreateEventMessage()).toEqual(expected); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | 4 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' 5 | gem 'rails', '~> 5.0.0', '>= 5.0.0.1' 6 | gem 'pg', '~> 0.18' 7 | gem 'puma', '~> 3.0' 8 | gem 'rack-cors', require: 'rack/cors' 9 | gem 'graphql' 10 | gem 'graphql-relay', '~>0.9' 11 | gem 'graphiql-rails' 12 | gem 'ffaker' 13 | 14 | group :development, :test do 15 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console 16 | gem 'byebug', platform: :mri 17 | end 18 | 19 | group :development do 20 | # Access an IRB console on exception pages or by using <%= console %> anywhere in the code. 21 | gem 'web-console' 22 | gem 'listen', '~> 3.0.5' 23 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring 24 | gem 'spring' 25 | gem 'spring-watcher-listen', '~> 2.0.0' 26 | end 27 | 28 | group :test do 29 | gem 'rspec-rails' 30 | gem 'rspec-graphql_matchers' 31 | gem 'shoulda-matchers' 32 | gem 'factory_girl_rails' 33 | end 34 | 35 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 36 | gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] 37 | gem 'devise' 38 | -------------------------------------------------------------------------------- /client/config/webpack/webpack.test.config.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | const ROOT_PATH = path.resolve(__dirname); 3 | 4 | module.exports = { 5 | output: { 6 | libraryTarget: 'commonjs2', 7 | }, 8 | resolve: { 9 | extensions: ['', '.js', '.jsx'], 10 | alias: { 11 | components: path.resolve(ROOT_PATH, 'app/src/components'), 12 | containers: path.resolve(ROOT_PATH, 'app/src/containers'), 13 | pages: path.resolve(ROOT_PATH, 'app/src/pages'), 14 | }, 15 | }, 16 | externals: { 17 | 'react/addons': true, 18 | 'react/lib/ExecutionEnvironment': true, 19 | 'react/lib/ReactContext': true, 20 | }, 21 | module: { 22 | loaders: [ 23 | { 24 | test: /\.scss$/, 25 | loaders: ['style', 'css', 'postcss', 'sass'], 26 | }, 27 | { 28 | test: /\.woff(2)?(\?v=[0-9].[0-9].[0-9])?$/, 29 | loader: 'url-loader?mimetype=application/font-woff', 30 | }, 31 | { 32 | test: /\.(ttf|eot|svg)(\?v=[0-9].[0-9].[0-9])?$/, 33 | loader: 'file-loader?name=[name].[ext]', 34 | }, 35 | { 36 | test: /\.(jpg|png)$/, 37 | loader: 'file?name=[path][name].[hash].[ext]', 38 | }, 39 | ], 40 | }, 41 | }; 42 | -------------------------------------------------------------------------------- /config/initializers/new_framework_defaults.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | # 3 | # This file contains migration options to ease your Rails 5.0 upgrade. 4 | # 5 | # Read the Rails 5.0 release notes for more info on each option. 6 | 7 | # Enable per-form CSRF tokens. Previous versions had false. 8 | Rails.application.config.action_controller.per_form_csrf_tokens = true 9 | 10 | # Enable origin-checking CSRF mitigation. Previous versions had false. 11 | Rails.application.config.action_controller.forgery_protection_origin_check = true 12 | 13 | # Make Ruby 2.4 preserve the timezone of the receiver when calling `to_time`. 14 | # Previous versions had false. 15 | ActiveSupport.to_time_preserves_timezone = true 16 | 17 | # Require `belongs_to` associations by default. Previous versions had false. 18 | Rails.application.config.active_record.belongs_to_required_by_default = true 19 | 20 | # Do not halt callback chains when a callback returns false. Previous versions had true. 21 | ActiveSupport.halt_callback_chains_on_return_false = false 22 | 23 | # Configure SSL options to enable HSTS with subdomains. Previous versions had false. 24 | Rails.application.config.ssl_options = { hsts: { subdomains: true } } 25 | -------------------------------------------------------------------------------- /client/app/src/containers/LoginContainer/tests/__snapshots__/index.test.js.snap: -------------------------------------------------------------------------------- 1 | exports[` should render with default props 1`] = ` 2 | 36 | `; 37 | -------------------------------------------------------------------------------- /client/app/src/components/AppFooter/index.js: -------------------------------------------------------------------------------- 1 | // Copyright ©️ 2016 - Ryan Collins 2 | // admin@ryancollins.io 3 | // http://www.ryancollins.io 4 | // Open sourced under the MIT license 5 | // See LICENSE.md file for details 6 | 7 | import React from 'react'; 8 | import styles from './index.module.scss'; 9 | import cssModules from 'react-css-modules'; 10 | import Footer from 'grommet-udacity/components/Footer'; 11 | import Box from 'grommet-udacity/components/Box'; 12 | import Heading from 'grommet-udacity/components/Heading'; 13 | import Anchor from 'grommet-udacity/components/Anchor'; 14 | 15 | const AppFooter = () => ( 16 |
17 | 24 | 25 | 26 | Meetup Event Planner 27 | 28 | 29 | 30 | Made with ♥️ by 31 | 32 | {' Ryan Collins'} 33 | 34 | 35 | 36 |
37 | ); 38 | 39 | export default cssModules(AppFooter, styles); 40 | -------------------------------------------------------------------------------- /client/app/src/containers/SignupContainer/tests/__snapshots__/index.test.js.snap: -------------------------------------------------------------------------------- 1 | exports[` should render with default props 1`] = ` 2 | 34 | `; 35 | -------------------------------------------------------------------------------- /spec/rails_helper.rb: -------------------------------------------------------------------------------- 1 | # This file is copied to spec/ when you run 'rails generate rspec:install' 2 | ENV['RAILS_ENV'] ||= 'test' 3 | require File.expand_path('../../config/environment', __FILE__) 4 | # Prevent database truncation if the environment is production 5 | abort("The Rails environment is running in production mode!") if Rails.env.production? 6 | require 'spec_helper' 7 | require 'rspec/rails' 8 | 9 | ActiveRecord::Migration.maintain_test_schema! 10 | 11 | RSpec.configure do |config| 12 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures 13 | config.fixture_path = "#{::Rails.root}/spec/fixtures" 14 | 15 | # If you're not using ActiveRecord, or you'd prefer not to run each of your 16 | # examples within a transaction, remove the following line or assign false 17 | # instead of true. 18 | config.use_transactional_fixtures = true 19 | 20 | config.infer_spec_type_from_file_location! 21 | 22 | # Filter lines from Rails gems in backtraces. 23 | config.filter_rails_from_backtrace! 24 | 25 | Shoulda::Matchers.configure do |configure| 26 | configure.integrate do |with| 27 | # Choose a test framework: 28 | with.test_framework :rspec 29 | # Or, choose the following (which implies all of the above): 30 | with.library :rails 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /client/app/src/containers/CreateEventContainer/reducer.js: -------------------------------------------------------------------------------- 1 | import * as types from './constants'; 2 | import update from 'react-addons-update'; 3 | 4 | export const initialState = { 5 | error: null, 6 | message: null, 7 | guestList: [], 8 | }; 9 | 10 | const createEventReducer = 11 | (state = initialState, action) => { 12 | switch (action.type) { 13 | case types.CREATE_EVENT_ERROR: 14 | return update(state, { 15 | error: { 16 | $set: action.error, 17 | }, 18 | }); 19 | case types.CREATE_EVENT_MESSAGE: 20 | return update(state, { 21 | message: { 22 | $set: action.message, 23 | }, 24 | }); 25 | case types.CLEAR_CREATE_EVENT_ERROR: 26 | return update(state, { 27 | error: { 28 | $set: null, 29 | }, 30 | }); 31 | case types.CLEAR_CREATE_EVENT_MESSAGE: 32 | return update(state, { 33 | message: { 34 | $set: null, 35 | }, 36 | }); 37 | case types.CREATE_EVENT_ADD_GUESTS: 38 | return update(state, { 39 | guestList: { 40 | $set: action.guests, 41 | }, 42 | }); 43 | default: 44 | return state; 45 | } 46 | }; 47 | 48 | export default createEventReducer; 49 | -------------------------------------------------------------------------------- /client/app/src/components/EditableField/README.md: -------------------------------------------------------------------------------- 1 | ## EditableField Component 2 | A reusable component that shows some child content and switches to an input field when clicked to edit. 3 | 4 | ### Example 5 | 6 | ```js 7 | 14 | 15 | {user.bio ? user.bio : 'Click to add a bio.'} 16 | 17 | 18 | ``` 19 | 20 | ### Props 21 | 22 | | Prop | Type | Default | Possible Values 23 | | ------------- | -------- | ----------- | --------------------------------------------- 24 | | **isEditing** | Bool | | Any bool value 25 | | **onEdit** | Func | | Any function value 26 | | **value** | String | | Any string value, represents the current input value 27 | | **name** | String | | A string that sets the label of the input, i.e. 'Bio' 28 | | **onClickToEdit** | Func | | Callback to call when the control is clicked to edit 29 | | **children** | Node | | The child component to show in non-editing state 30 | 31 | 32 | ### Other Information 33 | Reusable, see the UserProfile for example usage. 34 | -------------------------------------------------------------------------------- /client/app/src/routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Router, Route, IndexRoute } from 'react-router'; 3 | import { ApolloProvider } from 'react-apollo'; 4 | import client from './apolloClient'; 5 | import store, { history, userIsAuthenticated } from './store'; 6 | import App from 'components/App'; 7 | import * as Pages from 'pages'; 8 | 9 | const RouterApp = () => ( 10 | 11 | window.scrollTo(0, 0)} // eslint-disable-line 14 | > 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | ); 29 | 30 | export default RouterApp; 31 | -------------------------------------------------------------------------------- /npm-debug.log: -------------------------------------------------------------------------------- 1 | 0 info it worked if it ends with ok 2 | 1 verbose cli [ '/Users/myMac/.nvm/versions/node/v5.2.0/bin/node', 3 | 1 verbose cli '/Users/myMac/.nvm/versions/node/v5.2.0/bin/npm', 4 | 1 verbose cli 'run', 5 | 1 verbose cli 'test', 6 | 1 verbose cli '--', 7 | 1 verbose cli '-u' ] 8 | 2 info using npm@3.10.7 9 | 3 info using node@v5.2.0 10 | 4 verbose stack Error: ENOENT: no such file or directory, open '/Users/myMac/Developer/senior-web/meetup-event-planner/package.json' 11 | 4 verbose stack at Error (native) 12 | 5 verbose cwd /Users/myMac/Developer/senior-web/meetup-event-planner 13 | 6 error Darwin 15.5.0 14 | 7 error argv "/Users/myMac/.nvm/versions/node/v5.2.0/bin/node" "/Users/myMac/.nvm/versions/node/v5.2.0/bin/npm" "run" "test" "--" "-u" 15 | 8 error node v5.2.0 16 | 9 error npm v3.10.7 17 | 10 error path /Users/myMac/Developer/senior-web/meetup-event-planner/package.json 18 | 11 error code ENOENT 19 | 12 error errno -2 20 | 13 error syscall open 21 | 14 error enoent ENOENT: no such file or directory, open '/Users/myMac/Developer/senior-web/meetup-event-planner/package.json' 22 | 15 error enoent ENOENT: no such file or directory, open '/Users/myMac/Developer/senior-web/meetup-event-planner/package.json' 23 | 15 error enoent This is most likely not a problem with npm itself 24 | 15 error enoent and is related to npm not being able to find a file. 25 | 16 verbose exit [ -2, true ] 26 | -------------------------------------------------------------------------------- /app/graph/types/query_type.rb: -------------------------------------------------------------------------------- 1 | QueryType = GraphQL::ObjectType.define do 2 | name 'Query' 3 | description 'The query root of this schema.' 4 | 5 | # Get all events 6 | field :eventsCount, types.Int do 7 | resolve -> (obj, args, ctx) { 8 | Event.all.count 9 | } 10 | end 11 | field :events, types[EventType] do 12 | argument :first, types.Int 13 | resolve -> (obj, args, ctx) { 14 | events = Event.all.sort_by(&:start_date).reverse 15 | if args[:first] 16 | events = events.first(args[:first]) 17 | end 18 | events 19 | } 20 | end 21 | field :event, EventType do 22 | argument :id, types.ID 23 | resolve -> (obj, args, ctx) do 24 | event = Event.find_by_id(args[:id]) 25 | event 26 | end 27 | end 28 | field :hosts, types[HostType] do 29 | resolve -> (obj, args, ctx) do 30 | Host.all 31 | end 32 | end 33 | field :guests, types[GuestType] do 34 | resolve -> (obj, args, ctx) do 35 | Guest.all 36 | end 37 | end 38 | field :eventTypes, types[EventTypeEnum] do 39 | resolve -> (obj, args, ctx) do 40 | Event.defined_enums["event_type"].map { |k, _| k }.to_a 41 | end 42 | end 43 | field :authUser, AuthUserType do 44 | argument :auth_token, !types.String 45 | resolve -> (obj, args, ctx) do 46 | User.find_by(auth_token: args[:auth_token]) 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /client/app/src/containers/LoginContainer/reducer.js: -------------------------------------------------------------------------------- 1 | import * as types from './constants'; 2 | import update from 'react-addons-update'; 3 | 4 | export const initialState = { 5 | error: null, 6 | message: null, 7 | isLoading: false, 8 | }; 9 | 10 | const loginReducer = 11 | (state = initialState, action) => { 12 | switch (action.type) { 13 | case types.LOGIN_SHOW_ERROR: 14 | return update(state, { 15 | error: { 16 | $set: action.error, 17 | }, 18 | isLoading: { 19 | $set: false, 20 | }, 21 | }); 22 | case types.LOGIN_SHOW_MESSAGE: 23 | return update(state, { 24 | message: { 25 | $set: action.message, 26 | }, 27 | isLoading: { 28 | $set: false, 29 | }, 30 | }); 31 | case types.LOGIN_CLEAR_ERROR: 32 | return update(state, { 33 | error: { 34 | $set: null, 35 | }, 36 | }); 37 | case types.LOGIN_CLEAR_MESSAGE: 38 | return update(state, { 39 | message: { 40 | $set: null, 41 | }, 42 | }); 43 | case types.LOGIN_SET_LOADING: 44 | return update(state, { 45 | isLoading: { 46 | $set: true, 47 | }, 48 | }); 49 | default: 50 | return state; 51 | } 52 | }; 53 | 54 | export default loginReducer; 55 | -------------------------------------------------------------------------------- /client/app/src/components/SingleEvent/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import SingleEvent from '../index'; 2 | import { shallow } from 'enzyme'; 3 | import { shallowToJson } from 'enzyme-to-json'; 4 | import React from 'react'; 5 | 6 | describe('', () => { 7 | it('should render with default props', () => { 8 | const event = { 9 | id: 4, 10 | name: 'Pool Party', 11 | end: '2014-12-12 00:00:00 UTC', 12 | start: '2016-11-02 00:00:00 UTC', 13 | type: 'birthday', 14 | location: '1204 North Carolina 55, Fuquay Varina, NC, United States', 15 | message: 'The best party', 16 | host: { name: 'Joaquin West' }, 17 | guests: [{ name: 'Bilbo Baggins' }], 18 | }; 19 | const user = { 20 | id: '1', 21 | bio: 'Experienced Software Engineer specializing in implementing cutting-edge technologies in a multitude of domains, focusing on Front End Web Development and UI / UX.', 22 | email: 'ryan@udacity.com', 23 | name: 'Ryan Collins', 24 | avatar: 'https://github.com/RyanCCollins/cdn/blob/master/misc/ryanc.jpg?raw=true', 25 | employer: 'Udacity', 26 | authToken: 'hzqmg5KWxJK7--4iWQYq', 27 | events: [{ name: 'Alumni App Release Party', id: '1' }] 28 | }; 29 | const wrapper = shallow( 30 | e} user={user} event={event} /> 31 | ); 32 | expect(shallowToJson(wrapper)).toMatchSnapshot(); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /client/app/src/containers/CreateEventContainer/utils/validations.js: -------------------------------------------------------------------------------- 1 | import * as validation from '../../../utils/validation'; 2 | import memoize from 'lru-memoize'; 3 | 4 | const nameInput = [ 5 | validation.minLength(3), 6 | validation.maxLength(50), 7 | validation.valueRequired, 8 | ]; 9 | 10 | const startDateInput = [ 11 | validation.isValidDate, 12 | validation.valueRequired, 13 | validation.isInFuture, 14 | ]; 15 | 16 | const endDateInput = [ 17 | validation.isValidDate, 18 | validation.valueRequired, 19 | validation.noLaterThan('startDateInput'), 20 | ]; 21 | 22 | const typeInput = [ 23 | validation.valueRequired, 24 | validation.oneOf(['conference', 'office', 'birthday', 'wedding', 'other']), 25 | ]; 26 | 27 | const hostInput = [ 28 | validation.valueRequired, 29 | validation.minLength(3), 30 | validation.maxLength(50), 31 | ]; 32 | 33 | const locationInput = [ 34 | validation.minLength(10), 35 | validation.maxLength(100), 36 | validation.valueRequired, 37 | ]; 38 | 39 | const guestInput = [ 40 | validation.minLength(1), 41 | validation.maxLength(20), 42 | ]; 43 | 44 | // Create the validator 45 | const createEventValidation = validation.createValidator({ 46 | nameInput, 47 | startDateInput, 48 | endDateInput, 49 | typeInput, 50 | hostInput, 51 | locationInput, 52 | guestInput, 53 | }); 54 | 55 | /* Memoize and export */ 56 | const validator = memoize(10)(createEventValidation); 57 | export default validator; 58 | -------------------------------------------------------------------------------- /app/controllers/api_controller.rb: -------------------------------------------------------------------------------- 1 | class ApiController < ApplicationController 2 | skip_before_action :verify_authenticity_token 3 | #before_action :authenticate_user_from_token! 4 | def new 5 | end 6 | 7 | def create 8 | query_string = params[:query] 9 | query_variables = ensure_hash(params[:variables]) 10 | result = MeetupEventPlannerSchema.execute( 11 | query_string, 12 | variables: query_variables, 13 | context: { } 14 | ) 15 | render json: result 16 | end 17 | 18 | def authenticate_user_from_token! 19 | auth_token = request.headers['Authorization'] 20 | return authentication_error unless auth_token 21 | authenticate_with_auth_token auth_token 22 | end 23 | 24 | def authenticate_with_auth_token(auth_token) 25 | user = User.find_by(auth_token: auth_token) 26 | if user && Devise.secure_compare(user.auth_token, auth_token) 27 | sign_in user, store: false 28 | else 29 | authentication_error 30 | end 31 | end 32 | 33 | # Authentication Failure 34 | def authentication_error 35 | # User's token is either invalid or not in the right format 36 | render json: {error: t('application_controller.unauthorized')}, status: 401 37 | end 38 | 39 | private 40 | 41 | def ensure_hash(query_variables) 42 | if query_variables.blank? 43 | {} 44 | elsif query_variables.is_a?(String) 45 | JSON.parse(query_variables) 46 | else 47 | query_variables 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /client/app/src/containers/LoginContainer/tests/actions.test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | import * as actions from '../actions'; 3 | import * as types from '../constants'; 4 | 5 | describe('Login actions', () => { 6 | it('should have a type of LOGIN_SHOW_ERROR', () => { 7 | const error = 'Error'; 8 | const expected = { 9 | type: types.LOGIN_SHOW_ERROR, 10 | error, 11 | }; 12 | expect( 13 | actions.loginShowError(error) 14 | ).toEqual(expected); 15 | }); 16 | it('should have a type of LOGIN_SHOW_MESSAGE', () => { 17 | const message = 'Hello!'; 18 | const expected = { 19 | type: types.LOGIN_SHOW_MESSAGE, 20 | message, 21 | }; 22 | expect( 23 | actions.loginShowMessage(message) 24 | ).toEqual(expected); 25 | }); 26 | it('should have a type of LOGIN_CLEAR_ERROR', () => { 27 | const expected = { 28 | type: types.LOGIN_CLEAR_ERROR, 29 | }; 30 | expect( 31 | actions.loginClearError() 32 | ).toEqual(expected); 33 | }); 34 | it('should have a type of LOGIN_CLEAR_MESSAGE', () => { 35 | const expected = { 36 | type: types.LOGIN_CLEAR_MESSAGE, 37 | }; 38 | expect( 39 | actions.loginClearMessage() 40 | ).toEqual(expected); 41 | }); 42 | it('should have a type of LOGIN_SET_LOADING', () => { 43 | const expected = { 44 | type: types.LOGIN_SET_LOADING, 45 | }; 46 | expect( 47 | actions.loginSetLoading() 48 | ).toEqual(expected); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /client/config/generators/container/index.js.hbs: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | {{#if wantActionsAndReducer}} 3 | import { connect } from 'react-redux'; 4 | import { bindActionCreators } from 'redux'; 5 | import * as {{ properCase name }}ActionCreators from './actions'; 6 | {{/if}} 7 | {{#if wantSCSSModules}} 8 | import cssModules from 'react-css-modules'; 9 | import styles from './index.module.scss'; 10 | {{/if}} 11 | 12 | class {{ properCase name }} extends Component { // eslint-disable-line react/prefer-stateless-function 13 | render() { 14 | return ( 15 | {{#if wantSCSSModules}} 16 |
17 | {{else}} 18 |
19 | {{/if}} 20 |
21 | ); 22 | } 23 | } 24 | 25 | {{#if wantActionsAndReducer}} 26 | // mapStateToProps :: {State} -> {Props} 27 | const mapStateToProps = (state) => ({ 28 | // myProp: state.myProp, 29 | }); 30 | 31 | // mapDispatchToProps :: Dispatch -> {Action} 32 | const mapDispatchToProps = (dispatch) => ({ 33 | actions: bindActionCreators( 34 | {{ properCase name }}ActionCreators, 35 | dispatch 36 | ), 37 | }); 38 | {{/if}} 39 | 40 | {{#if wantSCSSModules}} 41 | const Container = cssModules({{ properCase name }}, styles); 42 | {{else}} 43 | const Container = {{ properCase name }}; 44 | {{/if}} 45 | 46 | {{#if wantActionsAndReducer}} 47 | export default connect( 48 | mapStateToProps, 49 | mapDispatchToProps 50 | )(Container); 51 | {{else}} 52 | export default Container; 53 | {{/if}} 54 | -------------------------------------------------------------------------------- /db/migrate/20161002210703_devise_create_users.rb: -------------------------------------------------------------------------------- 1 | class DeviseCreateUsers < ActiveRecord::Migration[5.0] 2 | def change 3 | create_table :users do |t| 4 | ## Database authenticatable 5 | t.string :email, null: false, default: "" 6 | t.string :encrypted_password, null: false, default: "" 7 | 8 | ## Recoverable 9 | t.string :reset_password_token 10 | t.datetime :reset_password_sent_at 11 | 12 | ## Rememberable 13 | t.datetime :remember_created_at 14 | 15 | ## Trackable 16 | t.integer :sign_in_count, default: 0, null: false 17 | t.datetime :current_sign_in_at 18 | t.datetime :last_sign_in_at 19 | t.inet :current_sign_in_ip 20 | t.inet :last_sign_in_ip 21 | 22 | ## Confirmable 23 | # t.string :confirmation_token 24 | # t.datetime :confirmed_at 25 | # t.datetime :confirmation_sent_at 26 | # t.string :unconfirmed_email # Only if using reconfirmable 27 | 28 | ## Lockable 29 | # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts 30 | # t.string :unlock_token # Only if unlock strategy is :email or :both 31 | # t.datetime :locked_at 32 | 33 | 34 | t.timestamps null: false 35 | end 36 | 37 | add_index :users, :email, unique: true 38 | add_index :users, :reset_password_token, unique: true 39 | # add_index :users, :confirmation_token, unique: true 40 | # add_index :users, :unlock_token, unique: true 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /client/app/src/containers/SignupContainer/reducer.js: -------------------------------------------------------------------------------- 1 | import * as types from './constants'; 2 | import update from 'react-addons-update'; 3 | 4 | export const initialState = { 5 | error: null, 6 | message: null, 7 | isLoading: false, 8 | isShowingTips: false, 9 | }; 10 | 11 | const signupReducer = 12 | (state = initialState, action) => { 13 | switch (action.type) { 14 | case types.SIGNUP_SHOW_ERROR: 15 | return update(state, { 16 | error: { 17 | $set: action.error, 18 | }, 19 | isLoading: { 20 | $set: false, 21 | }, 22 | }); 23 | case types.SIGNUP_SHOW_MESSAGE: 24 | return update(state, { 25 | message: { 26 | $set: action.message, 27 | }, 28 | isLoading: { 29 | $set: false, 30 | }, 31 | }); 32 | case types.SIGNUP_CLEAR_ERROR: 33 | return update(state, { 34 | error: { 35 | $set: null, 36 | }, 37 | }); 38 | case types.SIGNUP_CLEAR_MESSAGE: 39 | return update(state, { 40 | message: { 41 | $set: null, 42 | }, 43 | }); 44 | case types.SIGNUP_SET_LOADING: 45 | return update(state, { 46 | isLoading: { 47 | $set: true, 48 | }, 49 | }); 50 | case types.TOGGLE_SIGNUP_TIPS: 51 | return update(state, { 52 | isShowingTips: { 53 | $set: action.isShowing, 54 | }, 55 | }); 56 | default: 57 | return state; 58 | } 59 | }; 60 | 61 | export default signupReducer; 62 | -------------------------------------------------------------------------------- /client/app/src/containers/ProfileContainer/tests/__snapshots__/index.test.js.snap: -------------------------------------------------------------------------------- 1 | exports[` should render with default props 1`] = ` 2 | 45 | `; 46 | -------------------------------------------------------------------------------- /client/app/src/containers/SignupContainer/actions.js: -------------------------------------------------------------------------------- 1 | import * as types from './constants'; 2 | 3 | // signupShowError :: String -> {Action} 4 | export const signupShowError = (error) => ({ 5 | type: types.SIGNUP_SHOW_ERROR, 6 | error, 7 | }); 8 | 9 | // signupShowMessage :: String -> {Action} 10 | export const signupShowMessage = (message) => ({ 11 | type: types.SIGNUP_SHOW_MESSAGE, 12 | message, 13 | }); 14 | 15 | // clearSignupError :: None -> {Action} 16 | export const signupClearError = () => ({ 17 | type: types.SIGNUP_CLEAR_ERROR, 18 | }); 19 | 20 | // signupClearMessage :: None -> {Action} 21 | export const signupClearMessage = () => ({ 22 | type: types.SIGNUP_CLEAR_MESSAGE, 23 | }); 24 | 25 | export const clearSignupToast = (type) => 26 | (dispatch) => { 27 | switch (type) { 28 | case 'error': 29 | dispatch( 30 | signupClearError() 31 | ); 32 | break; 33 | case 'message': 34 | dispatch( 35 | signupClearMessage() 36 | ); 37 | break; 38 | default: 39 | break; 40 | } 41 | }; 42 | 43 | export const fieldsToData = (fields) => ({ 44 | userSignup: { 45 | name: fields.nameInput.value, 46 | email: fields.emailInput.value, 47 | password: fields.passwordInput.value, 48 | password_confirmation: fields.passwordConfirmationInput.value, 49 | bio: fields.bioInput.value || '', 50 | employer: fields.employerInput.value || '', 51 | }, 52 | }); 53 | 54 | export const signupSetLoading = () => ({ 55 | type: types.SIGNUP_SET_LOADING, 56 | }); 57 | 58 | export const toggleSignupTips = (isShowing) => ({ 59 | type: types.TOGGLE_SIGNUP_TIPS, 60 | isShowing, 61 | }); 62 | -------------------------------------------------------------------------------- /client/app/src/containers/SignupContainer/tests/actions.test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | import * as actions from '../actions'; 3 | import * as types from '../constants'; 4 | 5 | describe('Signup actions', () => { 6 | it('should handle SIGNUP_SHOW_ERROR', () => { 7 | const error = 'An error has occured'; 8 | const expected = { 9 | type: types.SIGNUP_SHOW_ERROR, 10 | error, 11 | }; 12 | expect(actions.signupShowError(error)).toEqual(expected); 13 | }); 14 | it('should handle SIGNUP_SHOW_MESSAGE', () => { 15 | const message = 'An error has not occured'; 16 | const expected = { 17 | type: types.SIGNUP_SHOW_MESSAGE, 18 | message, 19 | }; 20 | expect(actions.signupShowMessage(message)).toEqual(expected); 21 | }); 22 | it('should handle SIGNUP_CLEAR_ERROR', () => { 23 | const expected = { 24 | type: types.SIGNUP_CLEAR_ERROR, 25 | }; 26 | expect(actions.signupClearError()).toEqual(expected); 27 | }); 28 | it('should handle SIGNUP_CLEAR_MESSAGE', () => { 29 | const expected = { 30 | type: types.SIGNUP_CLEAR_MESSAGE, 31 | }; 32 | expect(actions.signupClearMessage()).toEqual(expected); 33 | }); 34 | it('should handle SIGNUP_SET_LOADING', () => { 35 | const expected = { 36 | type: types.SIGNUP_SET_LOADING, 37 | }; 38 | expect(actions.signupSetLoading()).toEqual(expected); 39 | }); 40 | it('should handle TOGGLE_SIGNUP_TIPS', () => { 41 | const isShowing = true; 42 | const expected = { 43 | type: types.TOGGLE_SIGNUP_TIPS, 44 | isShowing, 45 | }; 46 | expect( 47 | actions.toggleSignupTips(isShowing) 48 | ).toEqual(expected); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /client/app/src/components/EventInfo/tests/__snapshots__/index.test.js.snap: -------------------------------------------------------------------------------- 1 | exports[` should render with default props 1`] = ` 2 | 17 | 21 | Pool Party 22 | 23 | 26 | Birthday 27 | 28 | 31 | Hosted by 32 | Joaquin West 33 | 34 | 37 | From: November 2nd 2016, 12:00:00 am 38 | 39 | To: December 12th 2014, 12:00:00 am 40 | 41 | 44 | 1 people are attending 45 | 46 | 47 | The best party 48 | 49 |
53 |
67 |
68 | `; 69 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'graphql' 2 | require 'rspec/graphql_matchers' 3 | 4 | include RSpec::GraphqlMatchers::TypesHelper 5 | 6 | RSpec.configure do |config| 7 | # rspec-expectations config goes here. You can use an alternate 8 | # assertion/expectation library such as wrong or the stdlib/minitest 9 | # assertions if you prefer. 10 | config.expect_with :rspec do |expectations| 11 | # This option will default to `true` in RSpec 4. It makes the `description` 12 | # and `failure_message` of custom matchers include text for helper methods 13 | # defined using `chain`, e.g.: 14 | # be_bigger_than(2).and_smaller_than(4).description 15 | # # => "be bigger than 2 and smaller than 4" 16 | # ...rather than: 17 | # # => "be bigger than 2" 18 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 19 | end 20 | 21 | # rspec-mocks config goes here. You can use an alternate test double 22 | # library (such as bogus or mocha) by changing the `mock_with` option here. 23 | config.mock_with :rspec do |mocks| 24 | # Prevents you from mocking or stubbing a method that does not exist on 25 | # a real object. This is generally recommended, and will default to 26 | # `true` in RSpec 4. 27 | mocks.verify_partial_doubles = true 28 | end 29 | 30 | # This option will default to `:apply_to_host_groups` in RSpec 4 (and will 31 | # have no way to turn it off -- the option exists only for backwards 32 | # compatibility in RSpec 3). It causes shared context metadata to be 33 | # inherited by the metadata hash of host groups and examples, rather than 34 | # triggering implicit auto-inclusion in groups with matching metadata. 35 | config.shared_context_metadata_behavior = :apply_to_host_groups 36 | end 37 | -------------------------------------------------------------------------------- /client/app/src/containers/CreateEventContainer/actions.js: -------------------------------------------------------------------------------- 1 | import * as types from './constants'; 2 | 3 | // createEventError :: String -> {Action} 4 | export const createEventError = (error) => ({ 5 | type: types.CREATE_EVENT_ERROR, 6 | error, 7 | }); 8 | 9 | // createEventMessage :: String -> {Action} 10 | export const createEventMessage = (message) => ({ 11 | type: types.CREATE_EVENT_MESSAGE, 12 | message, 13 | }); 14 | 15 | // clearCreateEventError :: None -> {Action} 16 | export const clearCreateEventError = () => ({ 17 | type: types.CLEAR_CREATE_EVENT_ERROR, 18 | }); 19 | 20 | // clearCreateEventMessage :: None -> {Action} 21 | export const clearCreateEventMessage = () => ({ 22 | type: types.CLEAR_CREATE_EVENT_MESSAGE, 23 | }); 24 | 25 | export const clearCreateEventToast = (type) => 26 | (dispatch) => { 27 | switch (type) { 28 | case 'error': 29 | dispatch( 30 | clearCreateEventError() 31 | ); 32 | break; 33 | case 'message': 34 | dispatch( 35 | clearCreateEventMessage() 36 | ); 37 | break; 38 | default: 39 | break; 40 | } 41 | }; 42 | 43 | export const onSetGuests = (guests) => ({ 44 | type: types.CREATE_EVENT_ADD_GUESTS, 45 | guests, 46 | }); 47 | 48 | export const fieldsToData = (fields, guestList, user) => ({ 49 | authToken: user.authToken, 50 | event: { 51 | name: fields.nameInput.value, 52 | message: fields.messageInput.value, 53 | start_date: fields.startDateInput.value, 54 | end_date: fields.endDateInput.value, 55 | location: fields.locationInput.value, 56 | type: fields.typeInput.value, 57 | host: { 58 | name: fields.hostInput.value, 59 | }, 60 | guests: guestList.map((item) => ({ name: item.name })), 61 | }, 62 | }); 63 | -------------------------------------------------------------------------------- /client/app/src/components/Navbar/tests/__snapshots__/index.test.js.snap: -------------------------------------------------------------------------------- 1 | exports[` should render with default props 1`] = ` 2 |
4 |
16 | 18 | <Anchor 19 | href="/" 20 | tag="a"> 21 | <img 22 | alt="logo" 23 | className={undefined} 24 | src={Object {}} /> 25 | </Anchor> 26 | 27 | 40 | 44 | Events 45 | 46 | 58 | 62 | Login 63 | 64 | 68 | Signup 69 | 70 | 71 | 72 |
73 |
74 | `; 75 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /client/app/src/containers/ProfileContainer/actions.js: -------------------------------------------------------------------------------- 1 | import * as types from './constants'; 2 | 3 | // profileStartEditing :: None -> {Action} 4 | export const profileStartEditing = () => ({ 5 | type: types.PROFILE_START_EDITING, 6 | }); 7 | 8 | // profileEditBio :: String -> {Action} 9 | export const profileEditBio = (bio) => ({ 10 | type: types.PROFILE_EDIT_BIO, 11 | bio, 12 | }); 13 | 14 | // profileSubmissionInitiation :: None -> {Action} 15 | export const profileSubmissionInitiation = () => ({ 16 | type: types.PROFILE_SUBMISSION_INITIATION, 17 | }); 18 | 19 | // profileSubmissionSuccess :: None -> {Action} 20 | export const profileSubmissionSuccess = () => ({ 21 | type: types.PROFILE_SUBMISSION_SUCCESS, 22 | }); 23 | 24 | // profileSubmissionFailure :: JSON -> {Action} 25 | export const profileSubmissionFailure = (error) => ({ 26 | type: types.PROFILE_SUBMISSION_FAILURE, 27 | error, 28 | }); 29 | 30 | // profileClearError :: None -> {Action} 31 | export const profileClearError = () => ({ 32 | type: types.PROFILE_CLEAR_ERROR, 33 | }); 34 | 35 | // profileClearError :: None -> {Action} 36 | export const profileCancelEditing = () => ({ 37 | type: types.PROFILE_CANCEL_EDITING, 38 | }); 39 | 40 | // profileEditAvatar :: String -> {Action} 41 | export const profileEditAvatar = (avatar) => ({ 42 | type: types.PROFILE_EDIT_AVATAR, 43 | avatar, 44 | }); 45 | 46 | // profileEditEmail :: String -> {Action} 47 | export const profileEditEmail = (email) => ({ 48 | type: types.PROFILE_EDIT_EMAIL, 49 | email, 50 | }); 51 | 52 | // profileEditEmployer :: String -> {Action} 53 | export const profileEditEmployer = (employer) => ({ 54 | type: types.PROFILE_EDIT_EMPLOYER, 55 | employer, 56 | }); 57 | 58 | // setDefaultInputs :: Object -> {Action} 59 | export const setDefaultInputs = (inputs) => ({ 60 | type: types.PROFILE_SET_DEFAULT_INPUTS, 61 | inputs, 62 | }); 63 | -------------------------------------------------------------------------------- /client/app/src/components/App/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { bindActionCreators } from 'redux'; 3 | import { connect } from 'react-redux'; 4 | import * as actionCreators from './actions'; 5 | import { Navbar, AppFooter } from 'components'; 6 | 7 | class Main extends Component { 8 | componentDidMount() { 9 | const { 10 | loadPersistedUser, 11 | } = this.props.actions; 12 | loadPersistedUser(); 13 | } 14 | render() { 15 | const { 16 | user, 17 | } = this.props; 18 | return ( 19 |
20 | 23 | {React.cloneElement(this.props.children, this.props)} 24 | 25 |
26 | ); 27 | } 28 | } 29 | 30 | Main.propTypes = { 31 | children: React.children, 32 | actions: PropTypes.object.isRequired, 33 | user: PropTypes.object, 34 | }; 35 | 36 | // Map the global state to global props here. 37 | // See: https://egghead.io/lessons/javascript-redux-generating-containers-with-connect-from-react-redux-visibletodolist 38 | // mapStateToProps :: {State} -> {Action} 39 | const mapStateToProps = (state) => ({ 40 | user: state.authReducer.user, 41 | }); 42 | 43 | // Map the dispatch and bind the action creators. 44 | // See: http://redux.js.org/docs/api/bindActionCreators.html 45 | // mapDispatchToProps :: Dispatch Func -> {Actions} 46 | const mapDispatchToProps = (dispatch) => ({ 47 | actions: bindActionCreators( 48 | actionCreators, 49 | dispatch 50 | ), 51 | }); 52 | 53 | // Use connect both here and in your components. 54 | // See: https://egghead.io/lessons/javascript-redux-generating-containers-with-connect-from-react-redux-visibletodolist 55 | const App = connect( 56 | mapStateToProps, 57 | mapDispatchToProps 58 | )(Main); 59 | 60 | export default App; 61 | -------------------------------------------------------------------------------- /app/graph/mutations/event_mutations.rb: -------------------------------------------------------------------------------- 1 | module EventMutations 2 | Create = GraphQL::Relay::Mutation.define do 3 | name 'CreateEvent' 4 | input_field :event, EventInputType 5 | input_field :auth_token, !types.String 6 | 7 | return_field :event, EventType 8 | resolve -> (inputs, ctx) do 9 | event_inputs = inputs[:event] 10 | start_date = Date.strptime(event_inputs[:start_date], '%m/%d/%Y').to_datetime 11 | end_date = Date.strptime(event_inputs[:end_date], '%m/%d/%Y').to_datetime 12 | event = Event.new( 13 | name: event_inputs[:name], 14 | start_date: start_date, 15 | end_date: end_date, 16 | event_type: event_inputs[:type].downcase!, 17 | message: event_inputs[:message], 18 | location: event_inputs[:location] 19 | ) 20 | event_inputs[:guests].to_a.each do |guest| 21 | event.guests << Guest.new(name: guest.to_h["name"]) 22 | end 23 | host = Host.find_by(name: event[:host].to_h["name"]) 24 | event.user = User.find_by(auth_token: inputs[:auth_token]) 25 | event.host = if host 26 | host 27 | else 28 | Host.new(name: event_inputs[:host].to_h["name"]) 29 | end 30 | event.save! 31 | { 32 | event: event 33 | } 34 | end 35 | end 36 | RSVP = GraphQL::Relay::Mutation.define do 37 | input_field :event_id, !types.Int 38 | input_field :auth_token, !types.String 39 | 40 | return_field :event, EventType 41 | resolve -> (inputs, ctx) do 42 | event = Event.find_by(id: inputs[:event_id]) 43 | user = User.find_by(auth_token: inputs[:auth_token]) 44 | guest = Guest.find_by(name: user.name) || Guest.create(name: user.name) 45 | event.guests << guest 46 | event.save! 47 | { 48 | event: event 49 | } 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure public file server for tests with Cache-Control for performance. 16 | config.public_file_server.enabled = true 17 | config.public_file_server.headers = { 18 | 'Cache-Control' => 'public, max-age=3600' 19 | } 20 | 21 | # Show full error reports and disable caching. 22 | config.consider_all_requests_local = true 23 | config.action_controller.perform_caching = false 24 | 25 | # Raise exceptions instead of rendering exception templates. 26 | config.action_dispatch.show_exceptions = false 27 | 28 | # Disable request forgery protection in test environment. 29 | config.action_controller.allow_forgery_protection = false 30 | config.action_mailer.perform_caching = false 31 | 32 | # Tell Action Mailer not to deliver emails to the real world. 33 | # The :test delivery method accumulates sent emails in the 34 | # ActionMailer::Base.deliveries array. 35 | config.action_mailer.delivery_method = :test 36 | 37 | # Print deprecation notices to the stderr. 38 | config.active_support.deprecation = :stderr 39 | 40 | # Raises error for missing translations 41 | # config.action_view.raise_on_missing_translations = true 42 | end 43 | -------------------------------------------------------------------------------- /client/app/src/containers/CreateEventContainer/tests/reducer.test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | import * as types from '../constants'; 3 | import createEventReducer, { initialState } from '../reducer'; 4 | 5 | describe('createEventReducer', () => { 6 | it('returns the initial state', () => { 7 | expect( 8 | createEventReducer(undefined, {}) 9 | ).toEqual(initialState); 10 | }); 11 | it('should handle reducer for CREATE_EVENT_ERROR', () => { 12 | const error = 'Error'; 13 | const stateBefore = { 14 | error: null, 15 | }; 16 | const stateAfter = { 17 | error, 18 | }; 19 | expect( 20 | createEventReducer(stateBefore, { 21 | type: types.CREATE_EVENT_ERROR, 22 | error, 23 | }) 24 | ).toEqual(stateAfter); 25 | }); 26 | it('should handle reducer for CREATE_EVENT_MESSAGE', () => { 27 | const message = 'Hi there'; 28 | const stateBefore = { 29 | message: null, 30 | }; 31 | const stateAfter = { 32 | message, 33 | }; 34 | expect( 35 | createEventReducer(stateBefore, { 36 | type: types.CREATE_EVENT_MESSAGE, 37 | message, 38 | }) 39 | ).toEqual(stateAfter); 40 | }); 41 | it('should handle reducer for CLEAR_CREATE_EVENT_ERROR', () => { 42 | const error = 'Error'; 43 | const stateBefore = { 44 | error, 45 | }; 46 | const stateAfter = { 47 | error: null, 48 | }; 49 | expect( 50 | createEventReducer(stateBefore, { 51 | type: types.CLEAR_CREATE_EVENT_ERROR, 52 | }) 53 | ).toEqual(stateAfter); 54 | }); 55 | it('should handle reducer for CLEAR_CREATE_EVENT_MESSAGE', () => { 56 | const message = 'Hi there'; 57 | const stateBefore = { 58 | message, 59 | }; 60 | const stateAfter = { 61 | message: null, 62 | }; 63 | expect( 64 | createEventReducer(stateBefore, { 65 | type: types.CLEAR_CREATE_EVENT_MESSAGE, 66 | }) 67 | ).toEqual(stateAfter); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /client/app/src/components/SignupForm/tests/mocks.js: -------------------------------------------------------------------------------- 1 | export const fields = { 2 | nameInput: { 3 | name: 'nameInput', 4 | value: '', 5 | initialValue: '', 6 | valid: false, 7 | invalid: true, 8 | dirty: false, 9 | pristine: true, 10 | error: 'value Required', 11 | active: false, 12 | touched: true, 13 | visited: true, 14 | autofilled: false, 15 | }, 16 | emailInput: { 17 | name: 'emailInput', 18 | value: '', 19 | initialValue: '', 20 | valid: false, 21 | invalid: true, 22 | dirty: false, 23 | pristine: true, 24 | error: 'value Required', 25 | active: false, 26 | touched: false, 27 | visited: false, 28 | autofilled: false, 29 | }, 30 | passwordInput: { 31 | name: 'passwordInput', 32 | value: '', 33 | initialValue: '', 34 | valid: false, 35 | invalid: true, 36 | dirty: false, 37 | pristine: true, 38 | error: 'value Required', 39 | active: false, 40 | touched: false, 41 | visited: false, 42 | autofilled: false, 43 | }, 44 | passwordConfirmationInput: { 45 | name: 'passwordConfirmationInput', 46 | value: '', 47 | initialValue: '', 48 | valid: false, 49 | invalid: true, 50 | dirty: false, 51 | pristine: true, 52 | error: 'value Required', 53 | active: false, 54 | touched: false, 55 | visited: false, 56 | autofilled: false, 57 | }, 58 | bioInput: { 59 | name: 'bioInput', 60 | value: '', 61 | initialValue: '', 62 | valid: true, 63 | invalid: false, 64 | dirty: false, 65 | pristine: true, 66 | active: false, 67 | touched: false, 68 | visited: false, 69 | autofilled: false, 70 | }, 71 | employerInput: { 72 | name: 'employerInput', 73 | value: '', 74 | initialValue: '', 75 | valid: true, 76 | invalid: false, 77 | dirty: false, 78 | pristine: true, 79 | active: false, 80 | touched: false, 81 | visited: false, 82 | autofilled: false, 83 | }, 84 | }; 85 | -------------------------------------------------------------------------------- /client/app/src/components/EditableField/index.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import styles from './index.module.scss'; 3 | import cssModules from 'react-css-modules'; 4 | import Section from 'grommet-udacity/components/Section'; 5 | import Box from 'grommet-udacity/components/Box'; 6 | import FormField from 'grommet-udacity/components/FormField'; 7 | 8 | const EditableField = ({ 9 | isEditing, 10 | onEdit, 11 | value, 12 | name, 13 | altName, 14 | children, 15 | type, 16 | onClickToEdit, 17 | placeholder, 18 | autoFocus, 19 | }) => ( 20 |
21 | {isEditing ? 22 | 23 | 29 |