diff --git a/admin/Gemfile b/admin/Gemfile
index be68523a5..bddf8b3c5 100644
--- a/admin/Gemfile
+++ b/admin/Gemfile
@@ -64,7 +64,7 @@ gem 'resque-lonely_job', '~> 1.0.0'
gem 'eventmachine', '1.2.3'
gem 'amqp', '0.9.8'
#gem 'logging-rails', :require => 'logging/rails'
-gem 'pg_migrate'
+gem 'pg_migrate', '0.1.14'
gem 'ruby-protocol-buffers', '1.2.2'
gem 'sendgrid', '1.2.0'
gem 'geokit-rails'
@@ -84,7 +84,7 @@ gem 'stripe'
gem 'zip-codes'
gem 'email_validator'
gem 'best_in_place' #, github: 'bernat/best_in_place'
-
+gem 'auto_strip_attributes', '2.6.0'
#group :libv8 do
@@ -126,9 +126,9 @@ end
group :test do
gem 'simplecov', '~> 0.7.1'
gem 'simplecov-rcov'
- gem 'capybara-webkit'
- gem 'capybara-screenshot', '0.3.22' # 1.0.0 broke compat with rspec. maybe we need newer rspec
- gem 'poltergeist'
+# gem 'capybara-webkit'
+# gem 'capybara-screenshot', '0.3.22' # 1.0.0 broke compat with rspec. maybe we need newer rspec
+# gem 'poltergeist'
end
gem 'pry'
diff --git a/admin/Gemfile.lock b/admin/Gemfile.lock
index a946d9be3..4d567f4de 100644
--- a/admin/Gemfile.lock
+++ b/admin/Gemfile.lock
@@ -87,6 +87,8 @@ GEM
arel (6.0.4)
arr-pm (0.0.10)
cabin (> 0)
+ auto_strip_attributes (2.6.0)
+ activerecord (>= 4.0)
aws-sdk (1.67.0)
aws-sdk-v1 (= 1.67.0)
aws-sdk-v1 (1.67.0)
@@ -114,13 +116,6 @@ GEM
rack (>= 1.0.0)
rack-test (>= 0.5.4)
xpath (~> 2.0)
- capybara-screenshot (0.3.22)
- capybara (>= 1.0, < 3)
- colored
- launchy
- capybara-webkit (1.14.0)
- capybara (>= 2.3.0, < 2.14.0)
- json
carrierwave (0.11.2)
activemodel (>= 3.2.0)
activesupport (>= 3.2.0)
@@ -134,7 +129,6 @@ GEM
childprocess (0.8.0)
ffi (~> 1.0, >= 1.0.11)
clamp (1.0.1)
- cliver (0.3.2)
cocoon (1.2.11)
coderay (1.1.2)
coffee-rails (4.2.2)
@@ -144,7 +138,6 @@ GEM
coffee-script-source
execjs
coffee-script-source (1.12.2)
- colored (1.2)
concurrent-ruby (1.0.5)
country-select (1.1.1)
crass (1.0.3)
@@ -403,10 +396,6 @@ GEM
insist
mustache (= 0.99.8)
stud
- poltergeist (1.17.0)
- capybara (~> 2.1)
- cliver (~> 0.3.1)
- websocket-driver (>= 0.2.0)
polyamorous (1.3.3)
activerecord (>= 3.0)
postgres-copy (0.6.0)
@@ -610,9 +599,6 @@ GEM
rack (>= 1.0.0)
warden (1.2.7)
rack (>= 1.0)
- websocket-driver (0.7.0)
- websocket-extensions (>= 0.1.0)
- websocket-extensions (0.1.3)
will_paginate (3.1.6)
xdan-datetimepicker-rails (2.5.4)
jquery-rails
@@ -629,6 +615,7 @@ DEPENDENCIES
activeadmin
activeadmin_addons
amqp (= 0.9.8)
+ auto_strip_attributes (= 2.6.0)
aws-sdk (~> 1)
bcrypt-ruby (= 3.0.1)
best_in_place
@@ -636,8 +623,6 @@ DEPENDENCIES
bootstrap-will_paginate (= 0.0.6)
bugsnag
capybara
- capybara-screenshot (= 0.3.22)
- capybara-webkit
carrierwave (= 0.11.2)
carrierwave_direct
cocoon
@@ -669,8 +654,7 @@ DEPENDENCIES
launchy
mime-types (= 1.25)
net-ssh
- pg_migrate
- poltergeist
+ pg_migrate (= 0.1.14)
postgres-copy (= 0.6.0)
postgres_ext
protected_attributes
@@ -708,4 +692,4 @@ DEPENDENCIES
zip-codes
BUNDLED WITH
- 1.13.7
+ 1.17.1
diff --git a/admin/app/admin/event_brite_order.rb b/admin/app/admin/event_brite_order.rb
new file mode 100644
index 000000000..ad45eedbb
--- /dev/null
+++ b/admin/app/admin/event_brite_order.rb
@@ -0,0 +1,10 @@
+ActiveAdmin.register JamRuby::EventBriteOrder, :as => 'EventBriteOrder' do
+ menu :parent => 'Misc'
+
+ config.sort_order = 'created_at DESC'
+
+ filter :live_stream
+ filter :email
+ filter :order_id
+
+end
diff --git a/admin/app/admin/event_brite_order_uploads.rb b/admin/app/admin/event_brite_order_uploads.rb
new file mode 100644
index 000000000..984a2cdf2
--- /dev/null
+++ b/admin/app/admin/event_brite_order_uploads.rb
@@ -0,0 +1,51 @@
+ActiveAdmin.register_page "EventBriteOrderUploads" do
+
+ menu :label => 'Event Brite Order Upload', :parent => 'Misc'
+
+ page_action :upload_eventbriteorders, :method => :post do
+ EventBriteOrder.transaction do
+
+ puts params
+
+
+ live_stream = LiveStream.find_by_id!(params[:jam_ruby_event_brite_order][:live_stream_id])
+
+ file = params[:jam_ruby_event_brite_order][:csv]
+
+ upload = EventBriteOrderUpload.new
+ upload.upload_file_name = file.original_filename
+ upload.save!
+
+ array_of_arrays = CSV.read(file.tempfile.path, headers:true)
+ array_of_arrays.each do |row|
+ order_id = row['Order ID']
+
+ event_brite_order = EventBriteOrder.find_by_order_id(order_id)
+ if event_brite_order.nil?
+ event_brite_order = EventBriteOrder.new
+ end
+ event_brite_order.event_brite_order_upload = upload
+ event_brite_order.live_stream = live_stream
+ event_brite_order.event_name = row['Event Name']
+ event_brite_order.order_id = order_id
+ event_brite_order.ticket_count = row['Tickets']
+ event_brite_order.ticket_type = row['Type']
+ event_brite_order.first_name = row['First Name']
+ event_brite_order.last_name = row['Last Name']
+ event_brite_order.email = row['Email Address']
+ event_brite_order.save!
+ end
+
+ redirect_to admin_eventbriteorderuploads_path, :notice => "Created #{array_of_arrays.length} event brite orders!"
+ end
+ end
+ content do
+ active_admin_form_for EventBriteOrder.new, :url => admin_eventbriteorderuploads_upload_eventbriteorders_path, :builder => ActiveAdmin::FormBuilder do |f|
+ f.inputs "Upload Event Brite Orders" do
+ f.input :csv, as: :file, required: true, :label => "An event brite order CSV exactly as exported from Eventbrite"
+ f.input :live_stream, required:true, as: :select, :collection => LiveStream.upcoming
+ end
+ f.actions
+ end
+ end
+end
diff --git a/admin/app/admin/live_stream.rb b/admin/app/admin/live_stream.rb
new file mode 100644
index 000000000..a1bdc2617
--- /dev/null
+++ b/admin/app/admin/live_stream.rb
@@ -0,0 +1,21 @@
+ActiveAdmin.register JamRuby::LiveStream, :as => 'LiveStream' do
+ menu :parent => 'Misc'
+
+ config.sort_order = 'created_at DESC'
+
+ filter :listed
+ filter :event_id
+
+ before_build do |record|
+ if !record.event_type.nil?
+ puts "escape"
+ else
+ record.slug = 'please-do-this-sort-of-thing'
+ record.starts_at = 10.days.from_now.midnight
+ record.ends_at = 11.days.from_now.midnight
+ record.img_width = 200
+ record.event_type = 'eventbrite'
+ end
+ end
+
+end
diff --git a/admin/app/assets/stylesheets/custom.css.scss b/admin/app/assets/stylesheets/custom.css.scss
index 19e45fee7..ec1f940e8 100644
--- a/admin/app/assets/stylesheets/custom.css.scss
+++ b/admin/app/assets/stylesheets/custom.css.scss
@@ -29,4 +29,12 @@
background-color:white;
border:1px solid gray;
padding:5px;
-}
\ No newline at end of file
+}
+.datetime_select li.fragment {
+
+
+ label {
+ display: block;
+ width:auto;
+ }
+}
diff --git a/db/manifest b/db/manifest
index b8b9f7843..266c37788 100755
--- a/db/manifest
+++ b/db/manifest
@@ -390,4 +390,5 @@ onboarding_emails.sql
limit_counter_reminders.sql
amazon_v2.sql
store_backend_details_rate_session.sql
-invited_user_receiver.sql
\ No newline at end of file
+invited_user_receiver.sql
+live_streams.sql
\ No newline at end of file
diff --git a/db/up/live_streams.sql b/db/up/live_streams.sql
new file mode 100644
index 000000000..c5c3679f2
--- /dev/null
+++ b/db/up/live_streams.sql
@@ -0,0 +1,47 @@
+CREATE TABLE live_streams (
+ id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
+ slug VARCHAR(512) NOT NULL UNIQUE,
+ title TEXT,
+ description TEXT,
+ social_description TEXT,
+ listed BOOLEAN NOT NULL DEFAULT FALSE,
+ starts_at TIMESTAMP,
+ ends_at TIMESTAMP,
+ img_url VARCHAR(1024),
+ img_width INTEGER,
+ img_height INTEGER,
+ youtube_code VARCHAR(1024),
+ eventbriteid VARCHAR(1024),
+ event_type VARCHAR(100),
+ event_brite_registration_url VARCHAR(1024),
+ allow_in BOOLEAN NOT NULL DEFAULT FALSE,
+ white_label_player BOOLEAN NOT NULL DEFAULT TRUE,
+ user_id VARCHAR(64) REFERENCES users(id) ON DELETE SET NULL,
+ band_id VARCHAR(64) REFERENCES bands(id) ON DELETE SET NULL,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE TABLE event_brite_orders (
+ id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
+ live_stream_id VARCHAR(64) REFERENCES live_streams(id) ON DELETE CASCADE,
+ event_brite_order_upload_id VARCHAR(64) REFERENCES event_brite_order_uploads(id) ON DELETE CASCADE,
+ event_name VARCHAR(100) NOT NULL,
+ order_id VARCHAR(100) NOT NULL UNIQUE,
+ ticket_count INTEGER,
+ ticket_type VARCHAR(100),
+ first_name VARCHAR(100),
+ last_name VARCHAR(100),
+ email VARCHAR(200),
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ times_claimed INTEGER NOT NULL DEFAULT 0
+);
+
+
+CREATE TABLE event_brite_order_uploads (
+ id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
+ upload_file_name VARCHAR(500) NOT NULL,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
\ No newline at end of file
diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb
index cc2662810..8a6531664 100755
--- a/ruby/lib/jam_ruby.rb
+++ b/ruby/lib/jam_ruby.rb
@@ -241,6 +241,9 @@ require "jam_ruby/models/jam_track_file"
require "jam_ruby/models/jam_track_mixdown"
require "jam_ruby/models/jam_track_mixdown_package"
require "jam_ruby/models/genre_jam_track"
+require "jam_ruby/models/live_stream"
+require "jam_ruby/models/event_brite_order"
+require "jam_ruby/models/event_brite_order_upload"
require "jam_ruby/app/mailers/async_mailer"
require "jam_ruby/app/mailers/batch_mailer"
require "jam_ruby/app/mailers/progress_mailer"
diff --git a/ruby/lib/jam_ruby/models/event_brite_order.rb b/ruby/lib/jam_ruby/models/event_brite_order.rb
new file mode 100644
index 000000000..1861c2f1c
--- /dev/null
+++ b/ruby/lib/jam_ruby/models/event_brite_order.rb
@@ -0,0 +1,22 @@
+class JamRuby::EventBriteOrder < ActiveRecord::Base
+
+
+ belongs_to :live_stream, class_name: 'JamRuby::LiveStream'
+ belongs_to :event_brite_order_upload, class_name: 'JamRuby::EventBriteOrderUpload'
+
+ validates :event_name, presence: true
+ validates :order_id, presence: true
+ validates :ticket_count, presence: true
+ validates :ticket_type, presence: true
+
+ before_validation :sanitize
+
+ def sanitize
+ self.first_name.strip! if self.first_name
+ self.last_name.strip! if self.last_name
+ self.email.strip! if self.email
+ self.ticket_type.strip! if self.ticket_type
+ self.order_id.strip! if self.order_id
+ self.event_name.strip! if self.event_name
+ end
+end
diff --git a/ruby/lib/jam_ruby/models/event_brite_order_upload.rb b/ruby/lib/jam_ruby/models/event_brite_order_upload.rb
new file mode 100644
index 000000000..c2d3a599d
--- /dev/null
+++ b/ruby/lib/jam_ruby/models/event_brite_order_upload.rb
@@ -0,0 +1,5 @@
+class JamRuby::EventBriteOrderUpload < ActiveRecord::Base
+ has_many :event_brite_orders, class_name: 'JamRuby::EventBriteOrder'
+
+ validates :upload_file_name, presence: true
+end
diff --git a/ruby/lib/jam_ruby/models/live_stream.rb b/ruby/lib/jam_ruby/models/live_stream.rb
new file mode 100644
index 000000000..9e0b3cf74
--- /dev/null
+++ b/ruby/lib/jam_ruby/models/live_stream.rb
@@ -0,0 +1,37 @@
+class JamRuby::LiveStream < ActiveRecord::Base
+
+ attr_accessible :user_id, :band_id, :starts_at, :ends_at, :img_url, :slug, :title, :description, :listed, :allow_in, :white_label_player, :youtube_code, :eventbriteid, :event_type, :social_description, :event_brite_registration_url, as: :admin
+
+ #belongs_to :user, class_name: 'JamRuby::User'
+ #belongs_to :band, class_name: 'JamRuby::Band'
+
+ #validate :one_of_user_band
+ validates :slug, uniqueness: true, presence: true
+
+ before_validation :sanitize_active_admin
+
+ def ready_display
+ self.starts_at && self.ends_at && (self.user_id || self.band_id)
+ end
+
+ def self.upcoming
+ LiveStream.where(listed: true).where("starts_at > ?", 2.days.ago).order('starts_at DESC')
+ end
+
+ def sanitize_active_admin
+ self.img_url = nil if self.img_url == ''
+ self.user_id = nil if self.user_id == ''
+ self.band_id = nil if self.band_id == ''
+ self.social_description = nil if self.social_description == ''
+ end
+
+ def one_of_user_band
+ if band && user
+ errors.add(:user, 'specify band, or user. not both')
+ end
+ end
+
+ def admin_name
+ "#{title} EB:#{eventbriteid} #{starts_at}"
+ end
+end
diff --git a/web/app/assets/javascripts/events/authorized_event_storage.js b/web/app/assets/javascripts/events/authorized_event_storage.js
new file mode 100644
index 000000000..c6a2d098a
--- /dev/null
+++ b/web/app/assets/javascripts/events/authorized_event_storage.js
@@ -0,0 +1,81 @@
+(function (context) {
+
+ /**
+ * Javascript wrappers for the REST API
+ */
+
+ "use strict";
+
+ var AuthorizedEventStorage = {}
+ context.JK = context.JK || {};
+ context.JK.AuthorizedEventStorage = AuthorizedEventStorage
+
+ var key = 'authorized_events'
+
+ AuthorizedEventStorage.storeAuthorizedEvent = (authorizedEvent) => {
+ console.log("storing authorized event", authorizedEvent)
+ var events = fetchFromStorage()
+ if(!events) {
+ events = {}
+ }
+ if(!events.list) {
+ events.list = {}
+ }
+
+ if(!authorizedEvent.event_id) {
+ console.error("no event_id on new event", authorizedEvent)
+ alert("Unable to store order (no event_id)! Please contact support@jamkazam.com.")
+ return
+ }
+ if(!authorizedEvent.order_id) {
+ console.error("no order_id on new event", authorizedEvent)
+ alert("Unable to store order (no order_id)! Please contact support@jamkazam.com.")
+ return
+ }
+
+ events.list[authorizedEvent.event_id] = authorizedEvent
+
+ try {
+ window.localStorage.setItem(key, JSON.stringify(events) )
+ }
+ catch(e) {
+ console.error("can't update localStorage")
+ alert("Could not update local storage ith order.\nPlease try a different browser. Please do not use incognito mode.")
+ }
+
+ }
+
+ AuthorizedEventStorage.locateEvent = (eventId) => {
+ var events = fetchFromStorage()
+ if(events && events.list) {
+ return events.list[eventId]
+ }
+ else {
+ console.log("no events found in storage")
+ return null
+ }
+ }
+
+ AuthorizedEventStorage.listAuthorizedEvents = () => {
+ var events = fetchFromStorage()
+ if(events) {
+ return events.list
+ }
+ else {
+ return {}
+ }
+ }
+
+ function fetchFromStorage() {
+ var events = window.localStorage.getItem(key)
+ if(events){
+ try {
+ return JSON.parse(events)
+ }
+ catch(e){
+ console.log("events not parseable", events)
+ return null;
+ }
+ }
+ }
+})(window);
diff --git a/web/app/assets/javascripts/events/events.js b/web/app/assets/javascripts/events/events.js
new file mode 100644
index 000000000..5879e313d
--- /dev/null
+++ b/web/app/assets/javascripts/events/events.js
@@ -0,0 +1,9 @@
+//= require ./jam_rest
+//= require ./authorized_event_storage
+//= require reflux
+//= require react
+//= require react_ujs
+//= require react-init
+//= require react-input-autosize
+//= require react-select
+//= require ./react-components
\ No newline at end of file
diff --git a/web/app/assets/javascripts/events/jam_rest.js b/web/app/assets/javascripts/events/jam_rest.js
new file mode 100644
index 000000000..beb114692
--- /dev/null
+++ b/web/app/assets/javascripts/events/jam_rest.js
@@ -0,0 +1,43 @@
+(function (context) {
+
+ /**
+ * Javascript wrappers for the REST API
+ */
+
+ "use strict";
+
+ var Rest2 = {}
+ context.JK = context.JK || {};
+ context.JK.Rest2 = Rest2
+
+ Rest2.listLiveStreams = () => {
+ return fetch('/api/live_streams', {
+ method: 'get',
+ credentials: 'same-origin', // include, *same-origin, omit
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ })
+ }
+ Rest2.getLiveStream = (id) => {
+ return fetch('/api/live_streams/' + id, {
+ method: 'get',
+ credentials: 'same-origin', // include, *same-origin, omit
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ })
+ }
+
+ Rest2.authorizeLiveStream = (data) => {
+ return fetch('/api/live_streams/claim', {
+ method: 'post',
+ credentials: 'same-origin', // include, *same-origin, omit
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ cache: 'no-cache',
+ body: JSON.stringify(data)
+ })
+ }
+})(window);
diff --git a/web/app/assets/javascripts/events/react-components.js b/web/app/assets/javascripts/events/react-components.js
new file mode 100644
index 000000000..7a16dee63
--- /dev/null
+++ b/web/app/assets/javascripts/events/react-components.js
@@ -0,0 +1,3 @@
+//= require_directory ./react-components/actions
+//= require ./react-components/stores/EventStore
+//= require_directory ./react-components
\ No newline at end of file
diff --git a/web/app/assets/javascripts/events/react-components/EventList.js.jsx b/web/app/assets/javascripts/events/react-components/EventList.js.jsx
new file mode 100644
index 000000000..465cc9fa6
--- /dev/null
+++ b/web/app/assets/javascripts/events/react-components/EventList.js.jsx
@@ -0,0 +1,115 @@
+context = window
+EventActions = context.EventActions
+
+context.EventList = React.createClass({
+
+ mixins: [Reflux.listenTo(EventStore, "onEventsChanged")],
+
+ events: null,
+
+ getInitialState: function () {
+ return {events: null}
+ },
+
+ componentDidMount: function () {
+ },
+
+ isModeYours: function() {
+ return this.props.mode == 'yours'
+ },
+
+ render: function () {
+ return this.renderAll();
+ },
+
+
+ renderEvents() {
+ if(!this.state.events || this.state.events.length == 0) {
+ return null;
+ }
+
+ var items = []
+
+
+ var isModeYours = this.isModeYours();
+ for(var i = 0; i < this.state.events.length; i++) {
+ var event = this.state.events[i]
+
+ if(isModeYours && (!!event.authorization || event.allow_in) || !isModeYours && (!event.authorization && !event.allow_in)) {
+
+ var dynamic = null
+ var thumb = null
+ if(event.img_url) {
+ thumb =
+ dynamic = 'event'
+ }
+ else {
+ dynamic = 'event no-image'
+ }
+
+ var title = event.title ? event.title : 'Missing event title'
+ title = {title}
+ var when = event.starts_at ? new Date(event.starts_at).toLocaleString() : 'Unknown start time'
+ var item =
+ items.push(item)
+ }
+ }
+ return items
+ },
+
+ renderAll: function() {
+ var events = this.renderEvents()
+ var headerText = this.isModeYours() ? 'Your Registered Events' : 'All Upcoming Events'
+
+ if(!events || events.length == 0) {
+ return null;
+ }
+
+ if(events) {
+ var response =
+
+ {headerText}
+
+
+
+ {events}
+
+
+ return response
+ }
+ else {
+ return
+
+ {headerText}
+
+
+
+ Loading ...
+
+
;
+ }
+ },
+
+ onEventsChanged: function (allEvents) {
+ var scopedEvents = []
+ if(this.isModeYours()) {
+ allEvents.events.entries.forEach(function(event) {
+ if (event.authorization || event.allow_in) {
+ scopedEvents.push(event)
+ }
+ })
+ }
+ else {
+ scopedEvents = allEvents.events.entries
+ }
+
+
+ this.setState({events: scopedEvents})
+ }
+})
\ No newline at end of file
diff --git a/web/app/assets/javascripts/events/react-components/EventPage.js.jsx b/web/app/assets/javascripts/events/react-components/EventPage.js.jsx
new file mode 100644
index 000000000..0417246d6
--- /dev/null
+++ b/web/app/assets/javascripts/events/react-components/EventPage.js.jsx
@@ -0,0 +1,220 @@
+context = window
+EventActions = context.EventActions
+
+context.EventPage = React.createClass({
+
+ mixins: [Reflux.listenTo(EventStore, "onEventsChanged")],
+
+
+ getInitialState: function () {
+ return {submitting: false, error: null, event: null}
+ },
+
+ parseSlug: function() {
+ var path = window.location.pathname
+
+ console.log("slug path: ", path)
+
+ var pathPart = path.substring('/events/'.length)
+ console.log("slug part", pathPart)
+
+ var query = pathPart.indexOf('?')
+ if(query > -1) {
+ var slug = pathPart.substring(0, query)
+ }
+ else {
+ var slug = pathPart
+ }
+
+
+ console.log("slug", slug)
+ return slug;
+
+ },
+
+ componentDidMount: function () {
+ EventActions.single(this.parseSlug())
+ // new Plyr('#video');
+ },
+
+ componentDidUpdate: function() {
+
+ },
+
+ authorizeDone: function(response) {
+ this.setState({submitting:false})
+ EventActions.addAuthorization(response)
+ },
+
+ authorizeFailed: function(e) {
+ if(e instanceof SyntaxError) {
+ this.setState({error: 'Server error. Please try again or contact support@jamkazam.com.'})
+ }
+ else if(e instanceof Error) {
+ this.setState({error: 'Please enter a valid Eventbrite Order ID'})
+ }
+ else {
+ console.log("heheh", e)
+ }
+ this.setState({submitting:false})
+ },
+
+ handleSubmit: function(event) {
+ var value = document.getElementById("order-input").value
+ if(value) {
+ context.JK.Rest2.authorizeLiveStream({order: value}).then((response) => {
+ if (!response.ok) {
+ throw Error(response.statusText);
+ }
+ return response.json()
+ }).then((response) => this.authorizeDone(response)).catch((jqXHR) => this.authorizeFailed(jqXHR))
+ this.setState({submitting: true, error:null})
+ }
+ event.preventDefault();
+ },
+
+ isErrored() {
+ return !!this.state.error
+ },
+
+ isReady() {
+ return !!this.state.event
+ },
+
+ isAuthorized() {
+ return this.state.event.authorization || this.state.event.allow_in
+ },
+
+ isNotAuthorized() {
+ return false
+ },
+
+ videoCode() {
+ return this.state.event.youtube_code
+ },
+
+ title() {
+ return this.state.event.title ? this.state.event.title : 'Unknown title'
+ },
+
+ description() {
+ return this.state.event.description ? this.state.event.description : ''
+ },
+
+ startsAt() {
+ return this.state.event.starts_at? "Starts at " + new Date(this.state.event.starts_at).toLocaleString() : 'Unknown start time'
+ },
+
+ header() {
+
+ if(this.isErrored()) {
+ return
+ }
+ else if(this.isReady()) {
+ var title = this.title()
+ var description = this.description()
+ var startsAt = this.startsAt()
+
+ return
+
{title}
+
{description}
+
{startsAt}
+
+ }
+ else {
+ return
+ }
+ },
+
+ body: function() {
+ var video = null
+ if(this.isErrored()) {
+ video = null
+ }
+ else if(this.isReady()) {
+ if(this.isAuthorized()) {
+ //video =
+
+ var videoCode = this.videoCode()
+ if(videoCode) {
+ var src = "https://www.youtube.com/embed/" + this.videoCode() + "?modestbranding=true&autoplay=0&rel=0"
+ video =
+
+
+ }
+ else {
+ video =
+ }
+
+ }
+ else {
+ var notRegistered = You are not registered for this event!
+ var onceDone = Enter your EventBrite order code at the JamKazam Event Registration page when done registering.
+
+ var eventBriteUrl = this.state.event.event_brite_registration_url
+ if(eventBriteUrl) {
+ var meat =
+ {notRegistered}
+
+ Please register at EventBrite to see this video
+ {onceDone}
+
+
+ }
+ else {
+ var meat =
+ {notRegistered}
+
+ Please find your event at EventBrite and register for this event.
+ {onceDone}
+
+
+ }
+ video =
+ {meat}
+
+ }
+ }
+ else {
+ video =
+ }
+
+ return video
+ },
+
+ render: function () {
+
+ var header = this.header()
+
+ var body = this.body()
+
+ var response =
+
+
+ {header}
+
+
+ {body}
+
+
+ return response
+ },
+
+ onEventsChanged: function (allEvents) {
+ var event = null;
+ if(allEvents && allEvents.events && allEvents.events.entries && allEvents.events.entries.length > 0){
+ event = allEvents.events.entries[0]
+ }
+
+ console.log("event change", event, allEvents)
+ this.setState({event: event, error:allEvents.events_error})
+ }
+})
\ No newline at end of file
diff --git a/web/app/assets/javascripts/events/react-components/EventsPage.js.jsx b/web/app/assets/javascripts/events/react-components/EventsPage.js.jsx
new file mode 100644
index 000000000..67b673499
--- /dev/null
+++ b/web/app/assets/javascripts/events/react-components/EventsPage.js.jsx
@@ -0,0 +1,87 @@
+context = window
+EventActions = context.EventActions
+
+context.EventsPage = React.createClass({
+
+ getInitialState: function () {
+ return {submitting: false, error: null}
+ },
+
+ componentDidMount: function () {
+ EventActions.refresh()
+ },
+
+ authorizeDone: function(response) {
+ this.setState({submitting:false})
+ EventActions.addAuthorization(response)
+ },
+
+ authorizeFailed: function(e) {
+ if(e instanceof SyntaxError) {
+ this.setState({error: 'Server error. Please try again or contact support@jamkazam.com.'})
+ }
+ else if(e instanceof Error) {
+ this.setState({error: 'Please enter a valid Eventbrite Order ID'})
+ }
+ else {
+ console.log("heheh", e)
+ }
+ this.setState({submitting:false})
+ },
+
+ handleSubmit: function(event) {
+ var value = document.getElementById("order-input").value
+ if(value) {
+ context.JK.Rest2.authorizeLiveStream({order: value}).then((response) => {
+ if (!response.ok) {
+ throw Error(response.statusText);
+ }
+ return response.json()
+ }).then((response) => this.authorizeDone(response)).catch((jqXHR) => this.authorizeFailed(jqXHR))
+ this.setState({submitting: true, error:null})
+ }
+ event.preventDefault();
+ },
+ render: function () {
+
+ var ctaButtonClasses = "cta-button"
+ if(this.state.submitting) {
+ ctaButtonClasses = ctaButtonClasses + " submitting"
+ }
+ var errorClasses = "error"
+ if(this.state.error) {
+ errorClasses = errorClasses + " active"
+ }
+ var response =
+
+
+
+
Enter your order number from below:
+
+
+
+
+ {this.state.error}
+
+
+
+
+
+ return response
+ },
+
+})
\ No newline at end of file
diff --git a/web/app/assets/javascripts/events/react-components/actions/EventActions.js.coffee b/web/app/assets/javascripts/events/react-components/actions/EventActions.js.coffee
new file mode 100644
index 000000000..69025d513
--- /dev/null
+++ b/web/app/assets/javascripts/events/react-components/actions/EventActions.js.coffee
@@ -0,0 +1,6 @@
+
+@EventActions = Reflux.createActions({
+ refresh: {},
+ single: {}
+ addAuthorization: {}
+})
\ No newline at end of file
diff --git a/web/app/assets/javascripts/events/react-components/stores/EventStore.js.coffee b/web/app/assets/javascripts/events/react-components/stores/EventStore.js.coffee
new file mode 100644
index 000000000..1f49243ae
--- /dev/null
+++ b/web/app/assets/javascripts/events/react-components/stores/EventStore.js.coffee
@@ -0,0 +1,70 @@
+
+context = window
+
+console.log(@EventActions)
+console.log(window.EventActions)
+
+@EventStore = Reflux.createStore(
+ {
+ listenables: window.EventActions
+
+ events: null
+ eventsError: null
+ eventStorage: null
+ AuthorizedEventsStorage: null
+
+ init: ->
+ @AuthorizedEventStorage = context.JK.AuthorizedEventStorage
+
+ changed: () ->
+ state = @getState()
+ @trigger(state)
+
+ addAuthorization: (authorization) ->
+ @AuthorizedEventStorage.storeAuthorizedEvent(authorization)
+ @collateEvents()
+
+ onRefresh: () ->
+ context.JK.Rest2.listLiveStreams().then((response) => response.json()).then((response) => @refreshDone(response)).catch((jqXHR) => @refreshFailed(jqXHR))
+
+ onSingle: (slug) ->
+ context.JK.Rest2.getLiveStream(slug).then((response) => response.json()).then((response) =>
+ console.log("single", response)
+ if(response.errors)
+ @refreshFailed(response)
+ else
+ matchedFormat = {entries:[ response ]}
+ @refreshDone(matchedFormat))
+
+ .catch((jqXHR) => @refreshFailed(jqXHR))
+
+ refreshDone: (events) ->
+
+ @eventsError = false
+ @events = events
+ @collateEvents()
+
+ refreshFailed: (events) ->
+ console.log("events error", events)
+ @eventsError = true
+ @changed()
+
+ collateEvents: () ->
+ authorizedEvents = @AuthorizedEventStorage.listAuthorizedEvents()
+
+ for event in @events.entries
+
+ authorization = authorizedEvents[event.id]
+ if authorization
+ console.log("event is authorized", event)
+ event.authorization = authorization
+ else
+ event.authorization = null
+
+ @changed()
+
+ getState:() ->
+ {events: @events, events_error: @eventsError}
+
+ }
+)
diff --git a/web/app/assets/javascripts/spikes/responsive.js b/web/app/assets/javascripts/spikes/responsive.js
new file mode 100644
index 000000000..9aea01813
--- /dev/null
+++ b/web/app/assets/javascripts/spikes/responsive.js
@@ -0,0 +1,125 @@
+var defaultURL = 'http://127.0.0.1:3000/signup'; //<---- CHANGE TO YOUR WEBSITE URL
+
+//show loading graphic
+function showLoader(id) {
+ $('#' + id + ' img').fadeIn('slow');
+}
+
+//hdie loading graphic
+function hideLoader(id) {
+ $('#' + id + ' img').fadeOut('slow');
+}
+
+//function to check load state of each frame
+function allLoaded(){
+ var results = [];
+ $('iframe').each(function(){
+ if(!$(this).data('loaded')){results.push(false)}
+ });
+ var result = (results.length > 0) ? false : true;
+ return result;
+};
+
+function loadPage($frame, url) {
+ if ( url.substr(0,7) !== 'http://' && url.substr(0,8) !== 'https://' && url.substr(0, 7) !== 'file://' ) {
+ url = 'http://'+url;
+ }
+ $('iframe').not($frame).each(function(){showLoader($(this).parent().attr('id'));})
+ $('iframe').not($frame).data('loaded', false);
+ $('iframe').not($frame).attr('src', url);
+}
+
+$('.frame').each(function(){showLoader($(this).attr('id'))});
+
+
+//when document loads
+$(document).ready(function(){
+
+ loadPage('', defaultURL);
+
+ //query string
+ var qsArray = window.location.href.split('?');
+ var qs = qsArray[qsArray.length-1];
+
+ if(qs != '' && qsArray.length > 1){
+ $('#url input[type=text]').val(qs);
+ loadPage('', qs);
+ }
+
+ //set slidable div width
+ $('#frames #inner').css('width', function(){
+ var width = 0;
+ $('.frame').each(function(){width += $(this).outerWidth() + 20});
+ return width;
+ });
+
+ //add event handlers for options radio buttons
+ $('input[type=radio]').change(function(){
+ $frames = $('#frames');
+ $inputs = $('input[type=radio]:checked').val();
+
+ if($inputs == '1'){
+ $frames.addClass('widthOnly');
+ } else {
+ $frames.removeClass('widthOnly');
+ }
+ });
+
+ //add event handlers for scrollbars checkbox
+ $('input[type=checkbox]').change(function(){
+ var scrollBarWidth = 15;
+ $frames = $('#frames');
+ $inputs = $('#scrollbar:checked');
+
+ if( $inputs.length == 0 ){
+ scrollBarWidth = -15;
+ }
+
+ $frames.find('iframe').each(function(i,el) {
+ $(el).attr('width', parseInt($(el).attr('width')) + scrollBarWidth);
+ });
+ });
+
+ //when the url textbox is used
+ $('form').submit(function(){
+ loadPage('' , $('#url input[type=text]').val());
+ return false;
+ });
+
+ //when frame loads
+ $('iframe').load(function(){
+
+ var $this = $(this);
+ var url = '';
+ var error = false;
+
+ try{
+ url = $this.contents().get(0).location.href;
+ } catch(e) {
+ error = true;
+ if($('#url input[type=text]').val() != ''){
+ url = $('#url input[type=text]').val();
+ } else {
+ url = defaultURL;
+ }
+ }
+
+ //load other pages with the same URL
+ if(allLoaded()){
+ if(error){
+ alert('Browsers prevent navigation from inside iframes across domains.\nPlease use the textbox at the top for external sites.');
+ loadPage('', defaultURL);
+ }else{
+ loadPage($this, url);
+ }
+ }
+
+ //when frame loads, hide loader graphic
+ else{
+ error = false;
+ hideLoader($(this).parent().attr('id'));
+ $(this).data('loaded',true);
+ }
+ });
+
+});
\ No newline at end of file
diff --git a/web/app/assets/javascripts/wizard/gear/gear_wizard.js b/web/app/assets/javascripts/wizard/gear/gear_wizard.js
index 5066986d0..ebf6202dd 100644
--- a/web/app/assets/javascripts/wizard/gear/gear_wizard.js
+++ b/web/app/assets/javascripts/wizard/gear/gear_wizard.js
@@ -232,8 +232,9 @@
}
if(networkTest.length) {
- stepNetworkTest.initialize($wizardSteps.filter($('.network-test')), self);
- STEPS[dynamicStepCount++] = stepNetworkTest
+ // skip network test always
+ //stepNetworkTest.initialize($wizardSteps.filter($('.network-test')), self);
+ //STEPS[dynamicStepCount++] = stepNetworkTest
}
STEPS[dynamicStepCount++]=stepSuccess
diff --git a/web/app/assets/stylesheets/events/constants.scss b/web/app/assets/stylesheets/events/constants.scss
new file mode 100644
index 000000000..b0befe2fd
--- /dev/null
+++ b/web/app/assets/stylesheets/events/constants.scss
@@ -0,0 +1,21 @@
+
+$base-font-family: 'arial', sans-serif;
+
+$large-font-size: 1.5rem;
+$standard-font-size: 1rem;
+$smaller-font-size: .8rem;
+$small-font-size: .7rem;
+$input-font-size: 1.0rem;
+$submit-button-width:9rem;
+$checkbox-label-width:14rem;
+$create-account-width:10rem;
+$start-promo-btn-width:13rem;
+$jamkazam-background:#323232;
+
+$copy-color-on-dark: #b9b9b9;
+$copy-color-on-white: #575757;
+$cta-color: #e03d04;
+$chunkyBorderWidth: 6px;
+
+
+
diff --git a/web/app/assets/stylesheets/events/events.scss b/web/app/assets/stylesheets/events/events.scss
new file mode 100644
index 000000000..05719c098
--- /dev/null
+++ b/web/app/assets/stylesheets/events/events.scss
@@ -0,0 +1,26 @@
+/**
+
+*= require dialogs/banner
+*= require_directory ./react-components
+*/
+
+@import "events/constants";
+
+html,
+body {
+ min-height: 100%;
+ overflow:auto;
+ background-color:$jamkazam-background;
+ color:white;
+}
+
+body {
+ min-height: 100%;
+ margin: 0;
+ padding: 0;
+
+ font-size: 16px;
+ line-height: 1.2;
+ font-family: Raleway, Arial, Helvetica, sans-serif;
+
+}
diff --git a/web/app/assets/stylesheets/events/react-components/EventList.scss b/web/app/assets/stylesheets/events/react-components/EventList.scss
new file mode 100644
index 000000000..09cde5513
--- /dev/null
+++ b/web/app/assets/stylesheets/events/react-components/EventList.scss
@@ -0,0 +1,81 @@
+@import "events/constants";
+@import "client/common.scss";
+
+.EventList {
+
+ .event-header {
+ background-color: $cta-color;
+ color: white;
+ font-size: 24px;
+ text-align: center;
+ padding: 20px 0;
+ border-width: $chunkyBorderWidth 0 $chunkyBorderWidth;
+ border-style: solid;
+ border-color: $copy-color-on-dark;
+ }
+
+ .event-section {
+ padding: 10px;
+ //s border-width: 0 0 $chunkyBorderWidth;
+ //border-style: solid;
+ //border-color: $copy-color-on-dark;
+
+ &.loading {
+ text-align:center;
+ }
+ }
+
+ .event {
+ display:grid;
+ grid-template-columns: 10rem 20rem;
+ align-content:center;
+ height:8rem;
+ border-style: solid;
+ border-width: 2px 0 2px 0;
+ border-color: $copy-color-on-dark;
+
+ &:first-child {
+ border-width:0 0 1px 0;
+ }
+ &:last-child {
+ border-width:1px 0 0 0;
+ }
+ &:only-child {
+ border-width:0;
+ }
+
+ &.no-image {
+ grid-template-columns: 30rem;
+ }
+ }
+
+ .title-link {
+ color:white;
+ }
+ img.thumb {
+ width:100%;
+ max-height:400px;
+ }
+ div.thumb {
+ display: grid;
+ justify-content: center;
+ align-content: center;
+ text-transform: uppercase;
+ padding-right:1rem;
+ }
+
+ .detail {
+ display:grid;
+ justify-content: center;
+ padding-left:1rem;
+ align-content:center;
+ }
+
+ .title {
+ font-size:1.5rem;
+ }
+
+ .when {
+ font-size:1rem;
+ }
+}
\ No newline at end of file
diff --git a/web/app/assets/stylesheets/events/react-components/EventPage.scss b/web/app/assets/stylesheets/events/react-components/EventPage.scss
new file mode 100644
index 000000000..cb5abbda7
--- /dev/null
+++ b/web/app/assets/stylesheets/events/react-components/EventPage.scss
@@ -0,0 +1,114 @@
+@import "events/constants";
+@import "client/common.scss";
+
+
+[data-react-class="EventPage"] {
+ width:100%;
+
+ .EventPage {
+ width:100%;
+ }
+
+ #header {
+ display:grid;
+ justify-content: center;
+ width:100%;
+ background-color:$jamkazam-background;
+ padding:1.5rem 0;
+ }
+
+ .title {
+ font-size:2rem;
+ }
+
+ .description {
+ color:gray;
+ font-size:1rem;
+ margin-top:.2rem;
+ }
+
+ .starts-at {
+ color:gray;
+ font-size:1rem;
+ margin-top:.2rem;
+ }
+ #root {
+ min-height: 100%;
+
+ align-items: center;
+ //justify-content: center;
+ min-width: 50%;
+ display: flex;
+ flex-direction: column;
+ }
+
+ #player, #no-player {
+ position:relative;
+ padding-bottom:56.25%;
+ padding-top:30px;
+ height:0;
+ overflow:hidden;
+ top:-5rem;
+ border-width: $chunkyBorderWidth;
+ border-style: solid;
+ border-color: $copy-color-on-dark;
+ a {
+ color:#fc0;
+ }
+ }
+
+ #no-player {
+ display:grid;
+ align-content: center;
+ }
+
+ .no-register {
+ text-decoration: underline;
+ font-weight:bold;
+ margin-top:2rem;
+ display:block;
+ }
+
+ .no-code {
+ background-color: black;
+ padding-bottom:0 !important;
+ height:auto !important;
+ min-height:200px !important;
+ padding-top:0 !important;
+ text-align:center;
+ font-size:1rem;
+ ul,ol {
+ text-align:left;
+ }
+ }
+ #top-container {
+ background-color:black;
+ display: grid;
+ justify-content:center;
+ padding:2rem 0 7rem 0;
+ text-align:center;
+ }
+
+ div.root {
+ flex-grow: 1;
+ overflow: auto;
+ }
+
+ html {
+ font-size: 100%;
+ }
+
+ .listing {
+ width:50%;
+ margin:auto;
+ //display:grid;
+ //justify-content: center;
+ }
+
+
+ @media (min-width:1px) and (max-width: 1199px) {
+ html {
+ font-size: 100%;
+ }
+ }
+}
\ No newline at end of file
diff --git a/web/app/assets/stylesheets/events/react-components/EventsPage.scss b/web/app/assets/stylesheets/events/react-components/EventsPage.scss
new file mode 100644
index 000000000..2c50fdcda
--- /dev/null
+++ b/web/app/assets/stylesheets/events/react-components/EventsPage.scss
@@ -0,0 +1,169 @@
+@import "events/constants";
+@import "client/common.scss";
+
+
+[data-react-class="EventsPage"] {
+ width:100%;
+
+ .EventsPage {
+ width:100%;
+ }
+
+ #header {
+ display:grid;
+ justify-content: center;
+ width:100%;
+ background-color:$jamkazam-background;
+ padding:1.5rem 0;
+ }
+
+ #root {
+ min-height: 100%;
+
+ align-items: center;
+ //justify-content: center;
+ min-width: 50%;
+ display: flex;
+ flex-direction: column;
+ }
+
+
+
+ .order-input {
+ font-size:1rem;
+ padding:5px;
+ }
+
+ .cta-button {
+ cursor:pointer;
+ font-size: 1rem;
+ color: white;
+ background-color: $cta-color;
+ text-align: center;
+ padding: 5px;
+ margin-left:5px;
+ border-radius: 8px;
+ border: 1px outset buttonface;
+ font-family: Raleway, Arial, Helvetica, sans-serif;
+
+ &.submitting {
+ background-color: gray;
+ }
+ }
+ .error {
+ margin-top: 1rem;
+ #position: absolute;
+ display:none;
+ color:red;
+ font-weight:bold;
+ &.active {
+ display:inline;
+ }
+ }
+ .eventbrite-logo {
+ height:2rem;
+ vertical-align: middle;
+ }
+ #top-container {
+ background-color:black;
+ display: grid;
+ justify-content:center;
+ padding:5rem 0 7rem 0;
+ }
+
+ div.root {
+ flex-grow: 1;
+ overflow: auto;
+ }
+
+ .logo-holder {
+ max-width: 300px;
+ min-width:0;
+ img {
+ width: 100%;
+ }
+ }
+
+
+ .submit-row {
+ text-align:center;
+ }
+ p {
+ margin:.5rem 0;
+ }
+ p:first-child {
+ margin-top:0;
+ }
+ p.last-child {
+ margin-bottom:0;
+ }
+
+ .header {
+ font-size: $large-font-size;
+ margin-top: 1rem;
+ width: 100%;
+ max-width: 700px;
+ text-align: center;
+ }
+
+ html {
+ font-size: 100%;
+ }
+
+ .listings {
+ display:grid;
+ justify-content: center;
+ width:100%;
+ }
+
+ .preview-and-action-box {
+ background-color: black;
+ position:relative;
+ top: -2rem;
+ min-width: 400px;
+ @include border_box_sizing;
+ border-width: 0 $chunkyBorderWidth $chunkyBorderWidth;
+ border-style: solid;
+ border-color: $copy-color-on-dark;
+ z-index: 1;
+ }
+
+ @media (min-width:1px) and (max-width: 1199px) {
+ html {
+ font-size: 100%;
+ }
+ .code-input {
+ max-width:$submit-button-width;
+ }
+ .code-input input {
+ font-size: $standard-font-size;
+ }
+ .code-instructions {
+ width:$submit-button-width;
+ }
+
+ .text-input input {
+ width:$create-account-width;
+ }
+ .select-input select{
+ width:$create-account-width;
+ }
+
+ .instructions.create-account {
+ margin-top:1rem;
+ text-align:justify;
+ }
+ .success-msg {
+ margin-top: 1rem;
+ }
+ .logo-holder.create-account {
+ margin-top:2rem;
+ }
+ .code-form.create-account {
+ margin-top:1rem;
+ }
+ .powered-by.create-account {
+ margin-top:0.5rem;
+ }
+ }
+}
\ No newline at end of file
diff --git a/web/app/controllers/api_live_streams_controller.rb b/web/app/controllers/api_live_streams_controller.rb
new file mode 100644
index 000000000..2993171fd
--- /dev/null
+++ b/web/app/controllers/api_live_streams_controller.rb
@@ -0,0 +1,47 @@
+
+class ApiLiveStreamsController < ApiController
+
+
+ respond_to :json
+
+ def log
+ @log || Logging.logger[ApiLiveStreamsController]
+ end
+
+
+ def index
+ @live_streams = LiveStream.upcoming
+
+ render "api_live_streams/index", :layout => nil
+ end
+
+ def claim
+ order = params[:order]
+ if order.nil?
+ render :json => {}, :status => 404, layout: nil
+ return
+ end
+
+ order.strip!
+
+ if order.start_with? "#"
+ order = order[1..-1]
+ end
+
+ event_brite_order = EventBriteOrder.find_by_order_id(order)
+
+ if event_brite_order.nil?
+ render :json => {}, :status => 404, layout: nil
+ else
+ EventBriteOrder.where(id: event_brite_order.id).update_all(times_claimed: event_brite_order.times_claimed + 1)
+ render :json => {event_id: event_brite_order.live_stream.id, order_id: event_brite_order.order_id, event_type: 'eventbrite'}, :status => :ok, layout: nil
+ end
+ end
+
+ def show
+ slug = params[:slug]
+ @live_stream = LiveStream.find_by_slug!(slug)
+ render "api_live_streams/show", :layout => nil
+ end
+
+end
diff --git a/web/app/controllers/events_controller.rb b/web/app/controllers/events_controller.rb
index 71b5288fb..f5866f775 100644
--- a/web/app/controllers/events_controller.rb
+++ b/web/app/controllers/events_controller.rb
@@ -2,7 +2,20 @@ class EventsController < ApplicationController
respond_to :html
+
+ def list
+ render 'events', layout: 'events'
+ end
+
+
def show
+ @event = LiveStream.find_by_slug(params[:slug])
+
+ render 'event', :layout => 'events'
+ end
+
+ # 2014 lulz
+ def show_old
@event = Event.find_by_slug!(params[:slug])
render 'event', :layout => "web"
end
diff --git a/web/app/controllers/spikes_controller.rb b/web/app/controllers/spikes_controller.rb
index 069a2d757..ba4580614 100644
--- a/web/app/controllers/spikes_controller.rb
+++ b/web/app/controllers/spikes_controller.rb
@@ -31,6 +31,10 @@ class SpikesController < ApplicationController
render :layout => false
end
+ def responsive
+ render :layout => false
+ end
+
def subscription
#Notification.send_reload(MessageFactory::ALL_NATIVE_CLIENTS)
diff --git a/web/app/views/api_live_streams/index.rabl b/web/app/views/api_live_streams/index.rabl
new file mode 100644
index 000000000..2d451824c
--- /dev/null
+++ b/web/app/views/api_live_streams/index.rabl
@@ -0,0 +1,4 @@
+
+node :entries do |page|
+ partial "api_live_streams/show", object: @live_streams
+end
\ No newline at end of file
diff --git a/web/app/views/api_live_streams/show.rabl b/web/app/views/api_live_streams/show.rabl
new file mode 100644
index 000000000..7c5ef608a
--- /dev/null
+++ b/web/app/views/api_live_streams/show.rabl
@@ -0,0 +1,3 @@
+object @live_stream
+
+attributes :id, :slug, :title, :description, :starts_at, :allow_in, :ends_at, :img_url, :img_width, :img_height, :youtube_code, :eventbriteid, :event_type, :event_brite_registration_url, :white_label_player
\ No newline at end of file
diff --git a/web/app/views/events/event.slim b/web/app/views/events/event.slim
new file mode 100644
index 000000000..7ddad31ba
--- /dev/null
+++ b/web/app/views/events/event.slim
@@ -0,0 +1,6 @@
+- provide(:page_name, 'JamKazam Event')
+- provide(:description, @description)
+- provide(:title, @title)
+
+#root
+ = react_component 'EventPage'
diff --git a/web/app/views/events/event.html.haml b/web/app/views/events/event_old.html.haml
similarity index 100%
rename from web/app/views/events/event.html.haml
rename to web/app/views/events/event_old.html.haml
diff --git a/web/app/views/events/events.slim b/web/app/views/events/events.slim
new file mode 100644
index 000000000..e499e9061
--- /dev/null
+++ b/web/app/views/events/events.slim
@@ -0,0 +1,6 @@
+- provide(:page_name, 'Upcoming JamKazam Events')
+- provide(:description, @description)
+- provide(:title, @title)
+
+#root
+ = react_component 'EventsPage'
diff --git a/web/app/views/layouts/events.html.erb b/web/app/views/layouts/events.html.erb
new file mode 100644
index 000000000..1f7d46b6b
--- /dev/null
+++ b/web/app/views/layouts/events.html.erb
@@ -0,0 +1,36 @@
+
+
+
+ <%= full_title(yield(:title)) %>
+
+
+
+
+ <%= stylesheet_link_tag "events/events", media: "all" %>
+ <%= include_gon(:init => true) %>
+ <%= csrf_meta_tags %>
+
+
+ <% if content_for?(:social_meta) %>
+ <%= yield(:social_meta) %>
+ <% else %>
+ <%= render "layouts/social_meta" %>
+ <% end %>
+ <%= render "shared/ad_sense" %>
+
+
+
+
+ <%= javascript_include_tag "events/events" %>
+
+
+<%= render "clients/help" %>
+<%= render 'dialogs/banner' %>
+<%= render "shared/ga" %>
+
+
\ No newline at end of file
diff --git a/web/app/views/spikes/responsive.html.erb b/web/app/views/spikes/responsive.html.erb
new file mode 100644
index 000000000..0a5e88171
--- /dev/null
+++ b/web/app/views/spikes/responsive.html.erb
@@ -0,0 +1,70 @@
+
+
+
+
+
+ Responsive Design Testing
+
+
+
+
+
+
+
+
240 x 320 (small phone)
+
+
+
+
320 x 480 (iPhone)
+
+
+
+
480 x 640 (small tablet)
+
+
+
+
768 x 1024 (iPad - Portrait)
+
+
+
+
1024 x 768 (iPad - Landscape)
+
+
+
+
+A tool by Matt Kersley - Fork it on Github
+Note: The content width may not be pixel perfect - I have added 15px to the iframes to cater for the scrollbars
+
+
+<%= javascript_include_tag "spikes/responsive.js" %>
+
+
+
\ No newline at end of file
diff --git a/web/config/application.rb b/web/config/application.rb
index eae92ffc2..171b42b88 100644
--- a/web/config/application.rb
+++ b/web/config/application.rb
@@ -469,5 +469,5 @@ if defined?(Bundler)
config.rating_dialog_min_time = 60
config.rating_dialog_min_num = 1
- end
+ end
end
diff --git a/web/config/routes.rb b/web/config/routes.rb
index efceaf231..bdba4420c 100644
--- a/web/config/routes.rb
+++ b/web/config/routes.rb
@@ -131,6 +131,7 @@ Rails.application.routes.draw do
get '/gmail_contacts', to: 'gmail#gmail_contacts'
get '/events/:slug', to: 'events#show', :as => 'event'
+ get '/events', to: 'events#list', as: 'events'
get '/endorse/:id/:service', to: 'users#endorse', :as => 'endorse'
@@ -143,6 +144,7 @@ Rails.application.routes.draw do
require 'resque-retry/server'
mount Resque::Server.new, :at => "/resque" if Rails.env == "development"
+ get '/responsive', to: 'spikes#responsive'
# route to spike controller (proof-of-concepts)
get '/listen_in', to: 'spikes#listen_in'
get '/facebook_invite', to: 'spikes#facebook_invite'
@@ -229,6 +231,11 @@ Rails.application.routes.draw do
post '/auths/login' => 'api_auths#login'
+ # live streams
+ match '/live_streams' => 'api_live_streams#index', :via => :get
+ match '/live_streams/claim' => 'api_live_streams#claim', :via => :post
+ match '/live_streams/:slug' => 'api_live_streams#show', :via => :get
+
# music sessions
match '/sessions/:id/participants/legacy' => 'api_music_sessions#participant_create_legacy', :via => :post # can be removed when new Create Session comes in
match '/sessions/:id/participants' => 'api_music_sessions#participant_create', :via => :post