diff --git a/app/assets/images/common/button-background-brown-active-border.png b/app/assets/images/common/button-background-brown-active-border.png new file mode 100644 index 000000000..8a666e47b Binary files /dev/null and b/app/assets/images/common/button-background-brown-active-border.png differ diff --git a/app/assets/images/common/button-background-brown-active.png b/app/assets/images/common/button-background-brown-active.png new file mode 100644 index 000000000..eeff76f4f Binary files /dev/null and b/app/assets/images/common/button-background-brown-active.png differ diff --git a/app/assets/images/common/button-background-brown-disabled-border.png b/app/assets/images/common/button-background-brown-disabled-border.png new file mode 100644 index 000000000..62f12be18 Binary files /dev/null and b/app/assets/images/common/button-background-brown-disabled-border.png differ diff --git a/app/assets/images/common/button-background-brown-disabled.png b/app/assets/images/common/button-background-brown-disabled.png new file mode 100644 index 000000000..a0e743222 Binary files /dev/null and b/app/assets/images/common/button-background-brown-disabled.png differ diff --git a/app/assets/images/common/button-background-brown-pressed-border.png b/app/assets/images/common/button-background-brown-pressed-border.png new file mode 100644 index 000000000..ca2134e18 Binary files /dev/null and b/app/assets/images/common/button-background-brown-pressed-border.png differ diff --git a/app/assets/images/common/button-background-brown-pressed.png b/app/assets/images/common/button-background-brown-pressed.png new file mode 100644 index 000000000..21e6bd380 Binary files /dev/null and b/app/assets/images/common/button-background-brown-pressed.png differ diff --git a/app/assets/images/common/button-background-fb-active-border.png b/app/assets/images/common/button-background-fb-active-border.png new file mode 100644 index 000000000..140858b50 Binary files /dev/null and b/app/assets/images/common/button-background-fb-active-border.png differ diff --git a/app/assets/images/common/button-background-fb-active.png b/app/assets/images/common/button-background-fb-active.png new file mode 100644 index 000000000..7e054911c Binary files /dev/null and b/app/assets/images/common/button-background-fb-active.png differ diff --git a/app/assets/images/common/button-background-fb-disabled-border.png b/app/assets/images/common/button-background-fb-disabled-border.png new file mode 100644 index 000000000..b3a0fedcf Binary files /dev/null and b/app/assets/images/common/button-background-fb-disabled-border.png differ diff --git a/app/assets/images/common/button-background-fb-disabled.png b/app/assets/images/common/button-background-fb-disabled.png new file mode 100644 index 000000000..adb402464 Binary files /dev/null and b/app/assets/images/common/button-background-fb-disabled.png differ diff --git a/app/assets/images/common/button-background-fb-pressed-border.png b/app/assets/images/common/button-background-fb-pressed-border.png new file mode 100644 index 000000000..2cb8f9059 Binary files /dev/null and b/app/assets/images/common/button-background-fb-pressed-border.png differ diff --git a/app/assets/images/common/button-background-fb-pressed.png b/app/assets/images/common/button-background-fb-pressed.png new file mode 100644 index 000000000..cc7ab716a Binary files /dev/null and b/app/assets/images/common/button-background-fb-pressed.png differ diff --git a/app/assets/images/common/button-background-gplus-active-border.png b/app/assets/images/common/button-background-gplus-active-border.png new file mode 100644 index 000000000..30ed218dd Binary files /dev/null and b/app/assets/images/common/button-background-gplus-active-border.png differ diff --git a/app/assets/images/common/button-background-gplus-active.png b/app/assets/images/common/button-background-gplus-active.png new file mode 100644 index 000000000..b198ed621 Binary files /dev/null and b/app/assets/images/common/button-background-gplus-active.png differ diff --git a/app/assets/images/common/button-background-gplus-disabled-border.png b/app/assets/images/common/button-background-gplus-disabled-border.png new file mode 100644 index 000000000..eabd86d3f Binary files /dev/null and b/app/assets/images/common/button-background-gplus-disabled-border.png differ diff --git a/app/assets/images/common/button-background-gplus-disabled.png b/app/assets/images/common/button-background-gplus-disabled.png new file mode 100644 index 000000000..494f85869 Binary files /dev/null and b/app/assets/images/common/button-background-gplus-disabled.png differ diff --git a/app/assets/images/common/button-background-gplus-pressed-border.png b/app/assets/images/common/button-background-gplus-pressed-border.png new file mode 100644 index 000000000..eadb03397 Binary files /dev/null and b/app/assets/images/common/button-background-gplus-pressed-border.png differ diff --git a/app/assets/images/common/button-background-gplus-pressed.png b/app/assets/images/common/button-background-gplus-pressed.png new file mode 100644 index 000000000..2438ad3ee Binary files /dev/null and b/app/assets/images/common/button-background-gplus-pressed.png differ diff --git a/app/assets/images/level/loading_left_wing_1366.jpg b/app/assets/images/level/loading_left_wing_1366.jpg new file mode 100644 index 000000000..29fe67e9c Binary files /dev/null and b/app/assets/images/level/loading_left_wing_1366.jpg differ diff --git a/app/assets/images/level/loading_left_wing_1920.jpg b/app/assets/images/level/loading_left_wing_1920.jpg new file mode 100644 index 000000000..73aab0978 Binary files /dev/null and b/app/assets/images/level/loading_left_wing_1920.jpg differ diff --git a/app/assets/images/level/loading_right_wing_1366.jpg b/app/assets/images/level/loading_right_wing_1366.jpg new file mode 100644 index 000000000..963feba9b Binary files /dev/null and b/app/assets/images/level/loading_right_wing_1366.jpg differ diff --git a/app/assets/images/level/loading_right_wing_1920.jpg b/app/assets/images/level/loading_right_wing_1920.jpg new file mode 100644 index 000000000..2fe4465ce Binary files /dev/null and b/app/assets/images/level/loading_right_wing_1920.jpg differ diff --git a/app/assets/images/pages/play/level/modal/reward_plate.png b/app/assets/images/pages/play/level/modal/reward_plate.png index 3ee2bba20..0a0a7b6e7 100644 Binary files a/app/assets/images/pages/play/level/modal/reward_plate.png and b/app/assets/images/pages/play/level/modal/reward_plate.png differ diff --git a/app/assets/images/pages/play/level/modal/reward_plate_wide.png b/app/assets/images/pages/play/level/modal/reward_plate_wide.png index 466c0af7f..d99ae8fac 100644 Binary files a/app/assets/images/pages/play/level/modal/reward_plate_wide.png and b/app/assets/images/pages/play/level/modal/reward_plate_wide.png differ diff --git a/app/assets/images/pages/play/map_dungeon_1366.jpg b/app/assets/images/pages/play/map_dungeon_1366.jpg new file mode 100644 index 000000000..b4bd41c6a Binary files /dev/null and b/app/assets/images/pages/play/map_dungeon_1366.jpg differ diff --git a/app/assets/images/pages/play/map_dungeon_1920.jpg b/app/assets/images/pages/play/map_dungeon_1920.jpg new file mode 100644 index 000000000..857ace0d0 Binary files /dev/null and b/app/assets/images/pages/play/map_dungeon_1920.jpg differ diff --git a/app/assets/images/pages/play/map_forest_1366.jpg b/app/assets/images/pages/play/map_forest_1366.jpg new file mode 100644 index 000000000..362124291 Binary files /dev/null and b/app/assets/images/pages/play/map_forest_1366.jpg differ diff --git a/app/assets/images/pages/play/map_forest_1920.jpg b/app/assets/images/pages/play/map_forest_1920.jpg new file mode 100644 index 000000000..3631cabd7 Binary files /dev/null and b/app/assets/images/pages/play/map_forest_1920.jpg differ diff --git a/app/locale/de-DE.coffee b/app/locale/de-DE.coffee index a122e00bf..2fb32c7d0 100644 --- a/app/locale/de-DE.coffee +++ b/app/locale/de-DE.coffee @@ -346,7 +346,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription: parents: "Für Eltern" parents_title: "Dein Kind lernt zu programmieren." parents_blurb1: "Mit CodeCombat, lernt dein Kind richtige Programme zu schreiben. Es fängt mit einfachen Befehlen an, und schreitet ganz unmerklich zu schwierigeren Themen fort." - parents_blurb2: "Für 9.99 im Monat, bekommt es jede Woche neue Herausforderungen sowie persönlichen email support von professionellen Programmierern." + parents_blurb2: "Für 9.99 im Monat, bekommt es jede Woche neue Herausforderungen sowie persönlichen email support von professionellen Programmierern." # Please update based on new wording from en.coffee parents_blurb3: "Kein Risiko: 100% Geld zurück Garantie, und 1-Klick Abokündigung." subscribe_button: "Abonniere jetzt" stripe_description: "Monatsabo" @@ -641,7 +641,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription: character_classes_title: "Charakter Klassen" introduction_desc_intro: "Wir haben hohe Erwartungen für CodeCombat." introduction_desc_pref: "Wir wollen ein Ort sein, an dem sich Programmierer aller coleur treffen, um gemeinsam zu spielen und zu lernen. Wo sie andere in die wunderbare Welt des Programmierens einführen, und dabei die besten Aspekte der Programmierer-Community verkörpern. Wir können und wollen dies nicht alleine tun. Projekte wie GitHub, Stack Overflow and Linux leben von den großartigen Leuten die sie bauen und nutzen. Darum ist, " - introduction_desc_github_url: "CodeCombat komplett OpenSource" + introduction_desc_github_url: "CodeCombat ist komplett OpenSource" introduction_desc_suf: ". Wir wollen es dir so einfach und vielfältig wie irgend möglich machen, dich an diesem Projekt zu beteiligen. Damit es genauso zu deinem Projekt wird, wie zu unserem." introduction_desc_ending: "Wir hoffen du nimmst an unserer Party teil!" introduction_desc_signature: "- Nick, George, Scott, Michael, Jeremy and Matt" diff --git a/app/locale/en.coffee b/app/locale/en.coffee index 3d2c72a16..1abce903b 100644 --- a/app/locale/en.coffee +++ b/app/locale/en.coffee @@ -227,12 +227,6 @@ victory_rate_the_level: "Rate the level: " # Only in old-style levels. victory_return_to_ladder: "Return to Ladder" victory_play_continue: "Continue" - victory_play_skip: "Skip Ahead" - victory_play_next_level: "Play Next Level" - victory_play_more_practice: "More Practice" - victory_play_too_easy: "Too Easy" - victory_play_just_right: "Just Right" - victory_play_too_hard: "Too Hard" victory_saving_progress: "Saving Progress" victory_go_home: "Go Home" # Only in old-style levels. victory_review: "Tell us more!" # Only in old-style levels. @@ -339,7 +333,7 @@ subscribe: subscribe_title: "Subscribe" - levels: "Unlock 25 levels! With 5 new ones every week!" + levels: "Unlock 17 extra levels! With 5 new ones every week!" heroes: "More powerful heroes!" gems: "3500 bonus gems every month!" items: "Over 250 bonus items!" @@ -925,7 +919,7 @@ email_settings_url: "your email settings" email_description_suffix: "or through links in the emails we send, you can change your preferences and easily unsubscribe at any time." cost_title: "Cost" - cost_description: "CodeCombat is free to play in the dungeon campaign, with a $9.99 USD/mo subscription for access to later campaigns and 3500 bonus gems per month. You can cancel with a click, and we offer a 100% money-back guarantee." + cost_description: "CodeCombat is free to play for all of its core levels, with a $9.99 USD/mo subscription for access to extra level branches and 3500 bonus gems per month. You can cancel with a click, and we offer a 100% money-back guarantee." copyrights_title: "Copyrights and Licenses" contributor_title: "Contributor License Agreement" contributor_description_prefix: "All contributions, both on the site and on our GitHub repository, are subject to our" diff --git a/app/locale/pt-PT.coffee b/app/locale/pt-PT.coffee index 00992f803..f5ce50a71 100644 --- a/app/locale/pt-PT.coffee +++ b/app/locale/pt-PT.coffee @@ -346,7 +346,7 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription: parents: "Para Educadores" parents_title: "O teu educando vai aprender a programar." parents_blurb1: "Com o CodeCombat, o teu educando aprende ao escrever código real. Começa por aprender comandos simples e progride para tópicos mais avançados." - parents_blurb2: "Por $9.99 USD/mês, recebe novos desafios todas as semanas e suporte pessoal, via e-mail, de programadores profissionais." + parents_blurb2: "Por $9.99 USD/mês, recebe novos desafios todas as semanas e suporte pessoal, via e-mail, de programadores profissionais." # Please update based on new wording from en.coffee parents_blurb3: "Sem Risco: 100% de garantia de devolução do dinheiro, com anulação fácil de 1 clique." subscribe_button: "Subscrever Agora" stripe_description: "Subscrição Mensal" diff --git a/app/locale/ru.coffee b/app/locale/ru.coffee index adc0cd7b3..4a40d9a43 100644 --- a/app/locale/ru.coffee +++ b/app/locale/ru.coffee @@ -346,7 +346,7 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi parents: "Для Родителей" parents_title: "Ваш ребенок научиться программировать." parents_blurb1: "С CodeCombat ваш ребенок учится через написание реального кода. Начиная с изучения простых команд, продолжая более продвинутыми темами." - parents_blurb2: "За $9.99/месяц они получат новые испытания каждую неделю и персональную поддержку профессиональных программистов через электронную почту." + parents_blurb2: "За $9.99/месяц они получат новые испытания каждую неделю и персональную поддержку профессиональных программистов через электронную почту." # Please update based on new wording from en.coffee parents_blurb3: "Без риска: 100% гарантия возрата денег, возможность отписаться в 1 клик." subscribe_button: "Подпишись сейчас" stripe_description: "Месячная подписка" @@ -925,7 +925,7 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi email_settings_url: "ваши email настройки" email_description_suffix: "или через ссылки в email-ах, которые мы отправляем, вы можете изменить предпочтения и легко отписаться в любой момент." cost_title: "Стоимость" - cost_description: "В настоящее время, CodeCombat 100% бесплатен! Одной из наших главных целей является сохранить его таким, чтобы как можно больше людей могли играть, независимо от места в жизни. Если небо потемнеет, мы, возможно, введём подписки, возможно, только на некоторый контент, но нам не хотелось бы. Если повезёт, мы сможем поддерживать компанию, используя" + #cost_description: "В настоящее время, CodeCombat 100% бесплатен! Одной из наших главных целей является сохранить его таким, чтобы как можно больше людей могли играть, независимо от места в жизни. Если небо потемнеет, мы, возможно, введём подписки, возможно, только на некоторый контент, но нам не хотелось бы. Если повезёт, мы сможем поддерживать компанию, используя" # Please update based on new wording from en.coffee copyrights_title: "Авторские права и лицензии" contributor_title: "Лицензионное соглашение соавторов" contributor_description_prefix: "Все вклады, как на сайте, так и на нашем репозитории GitHub, подпадают под наше" diff --git a/app/models/User.coffee b/app/models/User.coffee index 594d21c34..90a7264fa 100644 --- a/app/models/User.coffee +++ b/app/models/User.coffee @@ -102,19 +102,6 @@ module.exports = class User extends CocoModel myHeroClasses.push heroClass for heroClass, heroSlugs of ThangType.heroClasses when _.intersection(myHeroSlugs, heroSlugs).length myHeroClasses - getBranchingGroup: -> - return @branchingGroup if @branchingGroup - return 'all-practice' # A/B test paused for Hour of Code - group = me.get('testGroupNumber') % 4 - @branchingGroup = switch group - when 0 then 'no-practice' - when 1 then 'all-practice' - when 2 then 'choice-explicit' - when 3 then 'choice-implicit' - @branchingGroup = 'choice-explicit' if me.isAdmin() - application.tracker.identify branchingGroup: @branchingGroup unless me.isAdmin() - @branchingGroup - getGemPromptGroup: -> return @gemPromptGroup if @gemPromptGroup group = me.get('testGroupNumber') % 8 @@ -125,6 +112,13 @@ module.exports = class User extends CocoModel application.tracker.identify gemPromptGroup: @gemPromptGroup unless me.isAdmin() @gemPromptGroup + isPremium: -> + return false unless stripe = @get('stripe') + return true if stripe.subscriptionID + return true if stripe.free is true + return true if _.isString(stripe.free) and new Date() < new Date(stripe.free) + return false + tiersByLevel = [-1, 0, 0.05, 0.14, 0.18, 0.32, 0.41, 0.5, 0.64, 0.82, 0.91, 1.04, 1.22, 1.35, 1.48, 1.65, 1.78, 1.96, 2.1, 2.24, 2.38, 2.55, 2.69, 2.86, 3.03, 3.16, 3.29, 3.42, 3.58, 3.74, 3.89, 4.04, 4.19, 4.32, 4.47, 4.64, 4.79, 4.96, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 10, 10.5, 11, 11.5, 12, 12.5, 13, 13.5, 14, 14.5, 15 ] diff --git a/app/styles/play/level/modal/hero-victory-modal.sass b/app/styles/play/level/modal/hero-victory-modal.sass index 2ca7c5c2b..468bef75e 100644 --- a/app/styles/play/level/modal/hero-victory-modal.sass +++ b/app/styles/play/level/modal/hero-victory-modal.sass @@ -221,16 +221,6 @@ .last-submitted float: none - .next-levels-prompt - display: none - margin: 30px -21px - - .btn - width: 30% - width: -webkit-calc(33.333333% - 10px) - width: calc(33.333333% - 10px) - margin: 5px - .hour-of-code-done clear: both padding-top: 10px diff --git a/app/styles/play/level/tome/spell.sass b/app/styles/play/level/tome/spell.sass index 964288585..ca0bf50bb 100644 --- a/app/styles/play/level/tome/spell.sass +++ b/app/styles/play/level/tome/spell.sass @@ -107,9 +107,6 @@ .executed background-color: rgba(110, 110, 110, 0.12) - .locked-code - border: 1px dashed rgba(53, 45, 34, 0.5) - +keyframes(pulseRedBackground) from background-color: rgba(255, 45, 27, 0.4) @@ -175,6 +172,9 @@ // Override faint gray border-color: #BFF + .locked-code + border: 1px dashed rgba(53, 45, 34, 0.5) + // Decided it wasn't useful to show what can be hovered, since almost anything can, so we have to make it too faint to be useful if we don't want it to be really distracting. //.ace_identifier // border-bottom: 1px dotted rgba(0, 51, 255, 0.25) diff --git a/app/styles/play/world-map-view.sass b/app/styles/play/world-map-view.sass index 2e83f32bf..e4f81f0e7 100644 --- a/app/styles/play/world-map-view.sass +++ b/app/styles/play/world-map-view.sass @@ -231,7 +231,7 @@ $gameControlMargin: 30px font-size: 2vw text-shadow: 0 0 0.3vw white, 0 0 0.3vw white - &:hover + &:hover text-decoration: none &#forest-link @@ -240,9 +240,10 @@ $gameControlMargin: 30px transform: rotate(-35deg) &#dungeon-link - left: 13.01% - top: 58% + left: 9% + top: 54.5% transform: rotate(180deg) + color: fuchsia .game-controls position: absolute diff --git a/app/templates/play/level/modal/hero-victory-modal.jade b/app/templates/play/level/modal/hero-victory-modal.jade index f0d00ea4c..0537a7aee 100644 --- a/app/templates/play/level/modal/hero-victory-modal.jade +++ b/app/templates/play/level/modal/hero-victory-modal.jade @@ -42,11 +42,6 @@ block modal-body-content img(src=item.getPortraitURL()) .reward-text= animate ? 'New Item' : item.get('name') - .next-levels-prompt - for button in continueButtons - - var enabled = Boolean(button.link != '/play' || me.getBranchingGroup() == 'choice-implicit' || button.key == 'continue'); - button.btn.btn-success.btn-lg.world-map-button.next-level-branch-button(data-href=button.link, disabled=!enabled, data-dismiss="modal", data-i18n="play_level.victory_play_" + button[me.getBranchingGroup()], data-branch-key=button.key) - block modal-footer-content if me.get('anonymous') p.sign-up-poke.hide diff --git a/app/templates/play/modal/subscribe-modal.jade b/app/templates/play/modal/subscribe-modal.jade index 37681e8a1..ecf13ba2d 100644 --- a/app/templates/play/modal/subscribe-modal.jade +++ b/app/templates/play/modal/subscribe-modal.jade @@ -16,18 +16,17 @@ #selling-points #point-levels.point - .blurb(data-i18n="subscribe.levels") 25 more levels, with 5 new levels every week! + .blurb(data-i18n="subscribe.levels") #point-heroes.point - .blurb(data-i18n="subscribe.heroes") Unlock more heroes, including wizards and rangers! + .blurb(data-i18n="subscribe.heroes") #point-gems.point - .blurb(data-i18n="subscribe.gems") Subscribers get 3500 bonus gems per month! + .blurb(data-i18n="subscribe.gems") #point-items.point - .blurb(data-i18n="subscribe.items") Unlock the coding power of 275 new items! + .blurb(data-i18n="subscribe.items") #parents-info(data-i18n="subscribe.parents") button.btn.btn-lg.btn-illustrated.purchase-button(data-i18n="subscribe.subscribe_button") - span $9.99/mo - Subscribe if state === 'declined' #declined-alert.alert.alert-danger.alert-dismissible diff --git a/app/templates/play/world-map-view.jade b/app/templates/play/world-map-view.jade index 9390928d7..2a2538273 100644 --- a/app/templates/play/world-map-view.jade +++ b/app/templates/play/world-map-view.jade @@ -8,7 +8,7 @@ - var seenNext = nextLevel; each level in campaign.levels if !level.hidden - - var next = level.id == nextLevel || (!seenNext && levelStatusMap[level.id] != "complete" && !level.locked && !level.disabled && (!level.practice || me.getBranchingGroup() == 'all-practice')); + - var next = level.id == nextLevel || (!seenNext && levelStatusMap[level.id] != "complete" && !level.locked && !level.disabled); - seenNext = seenNext || next; div(style="left: #{level.x}%; bottom: #{level.y}%; background-color: #{level.color}", class="level" + (next ? " next" : "") + (level.disabled ? " disabled" : "") + (level.locked ? " locked" : "") + " " + levelStatusMap[level.id] || "", data-level-id=level.id, title=level.name + (level.disabled ? ' (Coming Soon to Adventurers)' : '')) a(href=level.type == 'hero' ? '#' : level.disabled ? "/play" : "/play/#{level.levelPath || 'level'}/#{level.id}", disabled=level.disabled, data-level-id=level.id, data-level-path=level.levelPath || 'level', data-level-name=level.name) @@ -73,15 +73,15 @@ button.btn.btn-lg.btn-inverse#volume-button(title="Adjust volume") .glyphicon.glyphicon-volume-down .glyphicon.glyphicon-volume-up -h1#campaign-status - if mapType == 'dungeon' - span.spr(data-i18n="play.campaign_dungeon") - else if mapType == 'forest' - span.spr(data-i18n="play.campaign_forest") - | - - if requiresSubscription - span.spl(data-i18n="play.subscription_required") - else if mapType == 'dungeon' - span.spl(data-i18n="play.free") - else - span.spl(data-i18n="play.subscribed") \ No newline at end of file +//h1#campaign-status +// if mapType == 'dungeon' +// span.spr(data-i18n="play.campaign_dungeon") +// else if mapType == 'forest' +// span.spr(data-i18n="play.campaign_forest") +// | - +// if requiresSubscription +// span.spl(data-i18n="play.subscription_required") +// else if mapType == 'dungeon' +// span.spl(data-i18n="play.free") +// else +// span.spl(data-i18n="play.subscribed") \ No newline at end of file diff --git a/app/views/account/PaymentsView.coffee b/app/views/account/PaymentsView.coffee index 992cbd8fa..2654afa2d 100644 --- a/app/views/account/PaymentsView.coffee +++ b/app/views/account/PaymentsView.coffee @@ -24,7 +24,7 @@ module.exports = class PaymentsView extends RootView c = super() c.payments = @payments c.subscribed = me.get('stripe')?.planID - c.active = me.get('stripe')?.subscriptionID + c.active = me.isPremium() c onClickStartSubscription: (e) -> diff --git a/app/views/play/WorldMapView.coffee b/app/views/play/WorldMapView.coffee index 90e8b05e2..4548b14b1 100644 --- a/app/views/play/WorldMapView.coffee +++ b/app/views/play/WorldMapView.coffee @@ -81,7 +81,7 @@ module.exports = class WorldMapView extends RootView $('body').append($('')) trackedHourOfCode = true - @requiresSubscription = @terrain isnt 'dungeon' and not me.get('stripe')?.subscriptionID + @requiresSubscription = @terrain isnt 'dungeon' and not me.isPremium() destroy: -> @setupManager?.destroy() @@ -118,14 +118,6 @@ module.exports = class WorldMapView extends RootView @fullyRendered = true @render() @preloadTopHeroes() unless me.get('heroConfig')?.thangType - if @requiresSubscription - modal = if me.get('anonymous') then AuthModal else SubscribeModal - _.delay => - @openModalView? new modal() unless window.currentModal - if modal is SubscribeModal - window.tracker?.trackEvent 'Show subscription modal', category: 'Subscription', label: 'world map loadded' - window.tracker?.trackPageView "subscription/show-modal", ['Google Analytics'] - , 2000 onSubscribed: -> @requiresSubscription = false @@ -144,9 +136,8 @@ module.exports = class WorldMapView extends RootView level.locked = false if me.get('slug') is 'nick' level.disabled = false if @levelStatusMap[level.id] in ['started', 'complete'] level.color = 'rgb(255, 80, 60)' - if level.practice - level.color = 'rgb(80, 130, 200)' unless me.getBranchingGroup() is 'all-practice' - level.hidden = true if me.getBranchingGroup() is 'no-practice' + if level.requiresSubscription + level.color = 'rgb(80, 130, 200)' context.levelStatusMap = @levelStatusMap context.levelPlayCountMap = @levelPlayCountMap context.isIPadApp = application.isIPadApp @@ -161,10 +152,16 @@ module.exports = class WorldMapView extends RootView @onWindowResize() unless application.isIPadApp _.defer => @$el?.find('.game-controls .btn').tooltip() # Have to defer or i18n doesn't take effect. - @$el.find('.level').tooltip() + @$el.find('.level').tooltip().each -> + return unless me.isAdmin() + $(@).draggable().on 'dragstop', -> + bg = $('.map-background') + x = ($(@).offset().left - bg.offset().left + $(@).outerWidth() / 2) / bg.width() + y = 1 - ($(@).offset().top - bg.offset().top + $(@).outerHeight() / 2) / bg.height() + console.log "#{$(@).data('level-id')}\n x: #{(100 * x).toFixed(2)}\n y: #{(100 * y).toFixed(2)}\n" @$el.addClass _.string.slugify @terrain @updateVolume() - unless window.currentModal or not @fullyRendered or @requiresSubscription + unless window.currentModal or not @fullyRendered @highlightElement '.level.next', delay: 500, duration: 60000, rotation: 0, sides: ['top'] if levelID = @$el.find('.level.next').data('level-id') @$levelInfo = @$el.find(".level-info-container[data-level-id=#{levelID}]").show() @@ -210,7 +207,7 @@ module.exports = class WorldMapView extends RootView @adjustLevelInfoPosition e @endHighlight() else - if @requiresSubscription and not @levelStatusMap[level.id] and not level.adventurer + if level.requiresSubscription and @requiresSubscription and not @levelStatusMap[level.id] and not level.adventurer modal = if me.get('anonymous') then AuthModal else SubscribeModal @openModalView new modal() if modal is SubscribeModal @@ -376,8 +373,8 @@ dungeon = [ id: 'shadow-guard' original: '54174347844506ae0195a0b8' description: 'Evade the Kithgard minion.' - x: 44 - y: 11 + x: 40.54 + y: 11.03 nextLevels: more_practice: 'kounter-kithwise' continue: 'forgetful-gemsmith' @@ -388,33 +385,34 @@ dungeon = [ id: 'kounter-kithwise' original: '54527a6257e83800009730c7' description: 'Practice your evasion skills with more guards.' - x: 55 - y: 11 + x: 35.37 + y: 20.61 + nextLevels: + continue: 'crawlways-of-kithgard' + practice: true + requiresSubscription: true + } + { + name: 'Crawlways of Kithgard' + type: 'hero' + id: 'crawlways-of-kithgard' + original: '545287ef57e83800009730d5' + description: 'Dart in and grab the gem–at the right moment.' + x: 36.48 + y: 29.03 nextLevels: - #more_practice: 'crawlways-of-kithgard' continue: 'forgetful-gemsmith' practice: true + requiresSubscription: true } - #{ - # name: 'Crawlways of Kithgard' - # type: 'hero' - # # id: 'crawlways-of-kithgard' - # original: '545287ef57e83800009730d5' - # description: 'Dart in and grab the gem–at the right moment.' - # x: 57 - # y: 12 - # nextLevels: - # continue: 'true-names' - # practice: true - #} { name: 'Forgetful Gemsmith' type: 'hero' id: 'forgetful-gemsmith' original: '544a98f62d002f0000fe331a' description: 'Grab even more gems as you practice moving.' - x: 66 - y: 11 + x: 54.98 + y: 10.53 nextLevels: continue: 'true-names' } @@ -424,8 +422,8 @@ dungeon = [ id: 'true-names' original: '541875da4c16460000ab990f' description: 'Learn an enemy\'s true name to defeat it.' - x: 76 - y: 13 + x: 68.44 + y: 10.70 nextLevels: more_practice: 'favorable-odds' continue: 'the-raised-sword' @@ -436,11 +434,12 @@ dungeon = [ id: 'favorable-odds' original: '5452972f57e83800009730de' description: 'Test out your battle skills by defeating more munchkins.' - x: 80.85 - y: 16 + x: 88.25 + y: 14.92 nextLevels: continue: 'the-raised-sword' practice: true + requiresSubscription: true } { name: 'The Raised Sword' @@ -448,23 +447,11 @@ dungeon = [ id: 'the-raised-sword' original: '5418aec24c16460000ab9aa6' description: 'Learn to equip yourself for combat.' - x: 85 - y: 20 + x: 81.51 + y: 17.92 nextLevels: continue: 'haunted-kithmaze' } - #{ - # name: 'The First Kithmaze' - # type: 'hero' - # id: 'the-first-kithmaze' - # original: '5418b9d64c16460000ab9ab4' - # description: 'The builders of Kithgard constructed many mazes to confuse travelers.' - # x: 78 - # y: 29 - # nextLevels: - # more_practice: 'descending-further' - # continue: 'the-second-kithmaze' - #} { name: 'Haunted Kithmaze' type: 'hero' @@ -477,17 +464,32 @@ dungeon = [ more_practice: 'descending-further' continue: 'the-second-kithmaze' } + { + name: 'Riddling Kithmaze' + type: 'hero' + id: 'riddling-kithmaze' + original: '5418b9d64c16460000ab9ab4' + description: 'If at first you go astray, change your loop to find the way.' + x: 69.97 + y: 28.03 + nextLevels: + more_practice: 'descending-further' + continue: 'the-second-kithmaze' + practice: true + requiresSubscription: true + } { name: 'Descending Further' type: 'hero' id: 'descending-further' original: '5452a84d57e83800009730e4' description: 'Another day, another maze.' - x: 70 - y: 28 + x: 61.68 + y: 22.80 nextLevels: continue: 'the-second-kithmaze' practice: true + requiresSubscription: true } { name: 'The Second Kithmaze' @@ -495,8 +497,8 @@ dungeon = [ id: 'the-second-kithmaze' original: '5418cf256bae62f707c7e1c3' description: 'Many have tried, few have found their way through this maze.' - x: 58 - y: 23 + x: 54.49 + y: 26.49 nextLevels: continue: 'dread-door' } @@ -506,8 +508,8 @@ dungeon = [ id: 'dread-door' original: '5418d40f4c16460000ab9ac2' description: 'Behind a dread door lies a chest full of riches.' - x: 59 - y: 32 + x: 60.52 + y: 33.70 nextLevels: continue: 'known-enemy' } @@ -562,11 +564,12 @@ dungeon = [ id: 'tactical-strike' original: '5452cfa706a59e000067e4f5' description: 'They\'re, uh, coming right for us! Sneak up behind them.' - x: 88.65 - y: 63.06 + x: 83.23 + y: 52.73 nextLevels: continue: 'the-final-kithmaze' practice: true + requiresSubscription: true } { name: 'The Final Kithmaze' @@ -574,8 +577,8 @@ dungeon = [ id: 'the-final-kithmaze' original: '541b434e1ccc8eaae19f3c33' description: 'To escape you must find your way through an Elder Kithman\'s maze.' - x: 83 - y: 68 + x: 86.95 + y: 64.70 nextLevels: more_practice: 'the-gauntlet' continue: 'kithgard-gates' @@ -586,11 +589,12 @@ dungeon = [ id: 'the-gauntlet' original: '5452d8b906a59e000067e4fa' description: 'Rush for the stairs, battling foes at every turn.' - x: 84.89 - y: 73.88 + x: 76.50 + y: 72.69 nextLevels: continue: 'kithgard-gates' practice: true + requiresSubscription: true } { name: 'Kithgard Gates' @@ -658,6 +662,8 @@ forest = [ continue: 'thornbush-farm' x: 33 y: 37 + practice: true + requiresSubscription: true } { name: 'Thornbush Farm' @@ -750,6 +756,7 @@ forest = [ continue: 'swift-dagger' x: 38 y: 72 + requiresSubscription: true } { name: 'Swift Dagger' @@ -761,6 +768,7 @@ forest = [ continue: 'shrapnel' x: 33 y: 72 + requiresSubscription: true } { name: 'Shrapnel' @@ -772,6 +780,7 @@ forest = [ continue: 'coinucopia' x: 28 y: 73 + requiresSubscription: true } # Wizard branch @@ -786,6 +795,7 @@ forest = [ x: 47 y: 71 adventurer: true + requiresSubscription: true } { name: 'Touch of Death' @@ -798,6 +808,7 @@ forest = [ x: 52 y: 70 adventurer: true + requiresSubscription: true } { name: 'Bonemender' @@ -810,6 +821,7 @@ forest = [ x: 58 y: 67 adventurer: true + requiresSubscription: true } { @@ -855,6 +867,7 @@ forest = [ continue: 'rich-forager' x: 74.5 y: 92 + requiresSubscription: true } { name: 'Rich Forager' @@ -867,6 +880,7 @@ forest = [ x: 80 y: 88 adventurer: true + requiresSubscription: true } { name: 'Siege of Stonehold' @@ -880,6 +894,7 @@ forest = [ x: 85.5 y: 83.5 adventurer: true + requiresSubscription: true } { name: 'Multiplayer Treasure Grove' diff --git a/app/views/play/level/modal/HeroVictoryModal.coffee b/app/views/play/level/modal/HeroVictoryModal.coffee index ba56c424c..7f4ac26a3 100644 --- a/app/views/play/level/modal/HeroVictoryModal.coffee +++ b/app/views/play/level/modal/HeroVictoryModal.coffee @@ -21,7 +21,6 @@ module.exports = class HeroVictoryModal extends ModalView events: 'click #continue-button': 'onClickContinue' - 'click .next-level-branch-button': 'onClickNextLevelBranch' 'click .return-to-ladder-button': 'onClickReturnToLadder' constructor: (options) -> @@ -110,13 +109,6 @@ module.exports = class HeroVictoryModal extends ModalView c.readyToRank = @level.get('type', true) is 'hero-ladder' and @session.readyToRank() c.level = @level @continueLevelLink = @getNextLevelLink 'continue' - @morePracticeLevelLink = @getNextLevelLink 'more_practice' - @skipAheadLevelLink = @getNextLevelLink 'skip_ahead' - c.continueButtons = [ - {key: 'skip_ahead', link: @skipAheadLevelLink, 'choice-explicit': 'skip', 'choice-implicit': 'too_easy'} - {key: 'continue', link: @continueLevelLink, 'choice-explicit': 'next_level', 'choice-implicit': 'just_right'} - {key: 'more_practice', link: @morePracticeLevelLink, 'choice-explicit': 'more_practice', 'choice-implicit': 'too_hard'} - ] elapsed = (new Date() - new Date(me.get('dateCreated'))) isHourOfCode = me.get('hourOfCode') or elapsed < 120 * 60 * 1000 @@ -290,34 +282,15 @@ module.exports = class HeroVictoryModal extends ModalView return link unless nextLevel = @getNextLevel type "#{link}?next=#{nextLevel}" - # Branching group testing - getNextLevel: (type) -> levelInfo = @getLevelInfoForSlug @level.get 'slug' - levelInfo?.nextLevels?[type] # 'more_practice', 'skip_ahead', 'continue' + levelInfo?.nextLevels?[type] # 'continue'; TODO: refactor to not have the object and just use single nextLevel property onClickContinue: (e) -> @playSound 'menu-button-click' nextLevelLink = @continueLevelLink - if me.getBranchingGroup() is 'all-practice' and @morePracticeLevelLink - nextLevelLink = @morePracticeLevelLink - skipPrompt = me.getBranchingGroup() in ['no-practice', 'all-practice'] - skipPrompt ||= not (@skipAheadLevelLink or @morePractiveLevelLink) and me.getBranchingGroup() is 'choice-explicit' - if skipPrompt - # Preserve the supermodel as we navigate back to the world map. - Backbone.Mediator.publish 'router:navigate', route: nextLevelLink, viewClass: require('views/play/WorldMapView'), viewArgs: [{supermodel: if @options.hasReceivedMemoryWarning then null else @supermodel}, @getNextLevelCampaign()] - else - # Hide everything except the buttons prompting them for which kind of next level to do - @$el.find('.modal-footer, .modal-body > *').hide() - @$el.find('.next-levels-prompt').show() - - onClickNextLevelBranch: (e) -> - @playSound 'menu-button-click' - e.preventDefault() - route = $(e.target).data('href') or "/play/#{@getNextLevelCampaign()}" - application.tracker?.trackEvent 'Branch Selected', level: @level.get('slug'), label: @level.get('slug'), branch: $(e.target).data('branch-key'), branchingGroup: me.getBranchingGroup(), route: route - # Preserve the supermodel as we navigate back to world map. - Backbone.Mediator.publish 'router:navigate', route: route, viewClass: require('views/play/WorldMapView'), viewArgs: [{supermodel: if @options.hasReceivedMemoryWarning then null else @supermodel}, @getNextLevelCampaign()] + # Preserve the supermodel as we navigate back to the world map. + Backbone.Mediator.publish 'router:navigate', route: nextLevelLink, viewClass: require('views/play/WorldMapView'), viewArgs: [{supermodel: if @options.hasReceivedMemoryWarning then null else @supermodel}, @getNextLevelCampaign()] onClickReturnToLadder: (e) -> @playSound 'menu-button-click' diff --git a/app/views/play/level/tome/SpellView.coffee b/app/views/play/level/tome/SpellView.coffee index cf1486b22..e089de89a 100644 --- a/app/views/play/level/tome/SpellView.coffee +++ b/app/views/play/level/tome/SpellView.coffee @@ -257,7 +257,6 @@ module.exports = class SpellView extends CocoView # TODO: Lock default indent for an empty line? return unless LevelOptions[@options.level.get('slug')]?.lockDefaultCode or CampaignOptions?.getOption?(@options?.level?.get?('slug'), 'lockDefaultCode') return unless @spell.source is @spell.originalSource or force - return if @spell.language in ['javascript', 'lua', 'clojure', 'io'] # Only works for languages without closing delimeters on blocks currently console.info 'Locking down default code.' @@ -294,16 +293,50 @@ module.exports = class SpellView extends CocoView wrapper => orig.apply obj, args obj[method] - if @lockedCodeMarkerID? - @aceSession.removeMarker @lockedCodeMarkerID - @lockedCodeMarkerID = null + finishRange = (row, startRow, startColumn) => + range = new Range startRow, startColumn, row, @aceSession.getLine(row).length - 1 + range.start = @aceDoc.createAnchor range.start + range.end = @aceDoc.createAnchor range.end + range.end.$insertRight = true + @readOnlyRanges.push range + + # Remove previous locked code highlighting + if @lockedCodeMarkerIDs? + @aceSession.removeMarker marker for marker in @lockedCodeMarkerIDs + @lockedCodeMarkerIDs = [] + + # Create locked default code text ranges @readOnlyRanges = [] - lines = @aceDoc.getAllLines() - lastRow = row for line, row in lines when not /^\s*$/.test(line) - if lastRow? - @readOnlyRanges.push new Range 0, 0, lastRow, lines[lastRow].length - 1 - @lockedCodeMarkerID = @aceSession.addMarker @readOnlyRanges[0], 'locked-code', 'fullLine' + if @spell.language in ['python', 'coffeescript'] + # Lock contiguous section of default code + # Only works for languages without closing delimeters on blocks currently + lines = @aceDoc.getAllLines() + lastRow = row for line, row in lines when not /^\s*$/.test(line) + if lastRow? + @readOnlyRanges.push new Range 0, 0, lastRow, lines[lastRow].length - 1 + + # TODO: Highlighting does not work for multiple ranges + # TODO: Everything looks correct except the actual result. + # TODO: https://github.com/codecombat/codecombat/issues/1852 + # else + # # Create a read-only range for each chunk of text not separated by an empty line + # startRow = startColumn = null + # for row in [0...@aceSession.getLength()] + # unless /^\s*$/.test @aceSession.getLine(row) + # unless startRow? and startColumn? + # startRow = row + # startColumn = 0 + # else + # if startRow? and startColumn? + # finishRange row - 1, startRow, startColumn + # startRow = startColumn = null + # if startRow? and startColumn? + # finishRange @aceSession.getLength() - 1, startRow, startColumn + + # Highlight locked ranges + for range in @readOnlyRanges + @lockedCodeMarkerIDs.push @aceSession.addMarker range, 'locked-code', 'fullLine' # Override write operations that intersect with default code interceptCommand @ace, 'onPaste', preventReadonly diff --git a/server/levels/level_handler.coffee b/server/levels/level_handler.coffee index 2f68a1280..ace23c255 100644 --- a/server/levels/level_handler.coffee +++ b/server/levels/level_handler.coffee @@ -72,7 +72,7 @@ LevelHandler = class LevelHandler extends Handler Session.findOne(sessionQuery).exec (err, doc) => return @sendDatabaseError(res, err) if err return @sendSuccess(res, doc) if doc? - return @sendPaymentRequiredError(res, err) if (not req.user.get('stripe')?.subscriptionID) and level.get('requiresSubscription') + return @sendPaymentRequiredError(res, err) if (not req.user.isPremium()) and level.get('requiresSubscription') @createAndSaveNewSession sessionQuery, req, res createAndSaveNewSession: (sessionQuery, req, res) => diff --git a/server/payments/payment_handler.coffee b/server/payments/payment_handler.coffee index cc077994b..4f9707494 100644 --- a/server/payments/payment_handler.coffee +++ b/server/payments/payment_handler.coffee @@ -8,7 +8,6 @@ sendwithus = require '../sendwithus' hipchat = require '../hipchat' config = require '../../server_config' request = require 'request' -stripe = require('stripe')(config.stripe.secretKey) async = require 'async' products = { diff --git a/server/payments/subscription_handler.coffee b/server/payments/subscription_handler.coffee index fdf428873..2ab45ed48 100644 --- a/server/payments/subscription_handler.coffee +++ b/server/payments/subscription_handler.coffee @@ -2,8 +2,6 @@ # the stripe property in the user with what's being stored in Stripe. Handler = require '../commons/Handler' -config = require '../../server_config' -stripe = require('stripe')(config.stripe.secretKey) discountHandler = require './discount_handler' subscriptions = { @@ -13,17 +11,17 @@ subscriptions = { } class SubscriptionHandler extends Handler - logSubscriptionError: (req, msg) -> - console.warn "Subscription Error: #{req.user.get('slug')} (#{req.user._id}): '#{msg}'" + logSubscriptionError: (user, msg) -> + console.warn "Subscription Error: #{user.get('slug')} (#{user._id}): '#{msg}'" subscribeUser: (req, user, done) -> - if (not req.user) or req.user.isAnonymous() + if (not req.user) or req.user.isAnonymous() or user.isAnonymous() return done({res: 'You must be signed in to subscribe.', code: 403}) token = req.body.stripe.token customerID = user.get('stripe')?.customerID if not (token or customerID) - @logSubscriptionError(req, 'Missing stripe token or customer ID.') + @logSubscriptionError(user, 'Missing stripe token or customer ID.') return done({res: 'Missing stripe token or customer ID.', code: 422}) if token @@ -31,15 +29,15 @@ class SubscriptionHandler extends Handler stripe.customers.update customerID, { card: token }, (err, customer) => if err or not customer # should not happen outside of test and production polluting each other - @logSubscriptionError(req, 'Cannot find customer: ', +customer.id + '\n\n' + err) + @logSubscriptionError(user, 'Cannot find customer: ', +customer.id + '\n\n' + err) return done({res: 'Cannot find customer.', code: 404}) @checkForExistingSubscription(req, user, customer, done) else newCustomer = { card: token - email: req.user.get('email') - metadata: { id: req.user._id + '', slug: req.user.get('slug') } + email: user.get('email') + metadata: { id: user._id + '', slug: user.get('slug') } } stripe.customers.create newCustomer, (err, customer) => @@ -47,22 +45,22 @@ class SubscriptionHandler extends Handler if err.type in ['StripeCardError', 'StripeInvalidRequestError'] return done({res: 'Card error', code: 402}) else - @logSubscriptionError(req, 'Stripe customer creation error. '+err) + @logSubscriptionError(user, 'Stripe customer creation error. '+err) return done({res: 'Database error.', code: 500}) - stripeInfo = _.cloneDeep(req.user.get('stripe') ? {}) + stripeInfo = _.cloneDeep(user.get('stripe') ? {}) stripeInfo.customerID = customer.id - req.user.set('stripe', stripeInfo) - req.user.save (err) => + user.set('stripe', stripeInfo) + user.save (err) => if err - @logSubscriptionError(req, 'Stripe customer id save db error. '+err) + @logSubscriptionError(user, 'Stripe customer id save db error. '+err) return done({res: 'Database error.', code: 500}) @checkForExistingSubscription(req, user, customer, done) else stripe.customers.retrieve(customerID, (err, customer) => if err - @logSubscriptionError(req, 'Stripe customer creation error. '+err) + @logSubscriptionError(user, 'Stripe customer creation error. '+err) return done({res: 'Database error.', code: 500}) @checkForExistingSubscription(req, user, customer, done) ) @@ -79,14 +77,14 @@ class SubscriptionHandler extends Handler # subscription a trial period that ends when the cancelled subscription would have ended. stripe.customers.cancelSubscription subscription.customer, subscription.id, (err) => if err - @logSubscriptionError(req, 'Stripe cancel subscription error. '+err) + @logSubscriptionError(user, 'Stripe cancel subscription error. '+err) return done({res: 'Database error.', code: 500}) options = { plan: 'basic', trial_end: subscription.current_period_end } options.coupon = couponID if couponID - stripe.customers.update req.user.get('stripe').customerID, options, (err, customer) => + stripe.customers.update user.get('stripe').customerID, options, (err, customer) => if err - @logSubscriptionError(req, 'Stripe customer plan setting error. '+err) + @logSubscriptionError(user, 'Stripe customer plan setting error. '+err) return done({res: 'Database error.', code: 500}) @updateUser(req, user, customer, false, done) @@ -98,9 +96,9 @@ class SubscriptionHandler extends Handler else options = { plan: 'basic' } options.coupon = couponID if couponID - stripe.customers.update req.user.get('stripe').customerID, options, (err, customer) => + stripe.customers.update user.get('stripe').customerID, options, (err, customer) => if err - @logSubscriptionError(req, 'Stripe customer plan setting error. '+err) + @logSubscriptionError(user, 'Stripe customer plan setting error. '+err) return done({res: 'Database error.', code: 500}) @updateUser(req, user, customer, true, done) @@ -123,25 +121,25 @@ class SubscriptionHandler extends Handler user.save (err) => if err - @logSubscriptionError(req, 'Stripe user plan saving error. '+err) + @logSubscriptionError(user, 'Stripe user plan saving error. '+err) return done({res: 'Database error.', code: 500}) - req.user?.saveActiveUser 'subscribe' + user?.saveActiveUser 'subscribe' return done() unsubscribeUser: (req, user, done) -> stripeInfo = _.cloneDeep(user.get('stripe')) stripe.customers.cancelSubscription stripeInfo.customerID, stripeInfo.subscriptionID, { at_period_end: true }, (err) => if err - @logSubscriptionError(req, 'Stripe cancel subscription error. '+err) + @logSubscriptionError(user, 'Stripe cancel subscription error. '+err) return done({res: 'Database error.', code: 500}) delete stripeInfo.planID user.set('stripe', stripeInfo) req.body.stripe = stripeInfo user.save (err) => if err - @logSubscriptionError(req, 'User save unsubscribe error. '+err) + @logSubscriptionError(user, 'User save unsubscribe error. '+err) return done({res: 'Database error.', code: 500}) - req.user?.saveActiveUser 'unsubscribe' + user?.saveActiveUser 'unsubscribe' return done() module.exports = new SubscriptionHandler() diff --git a/server/users/User.coffee b/server/users/User.coffee index cd9d50381..6f1ff5fbe 100644 --- a/server/users/User.coffee +++ b/server/users/User.coffee @@ -179,6 +179,13 @@ UserSchema.methods.register = (done) -> delighted.addDelightedUser @ @saveActiveUser 'register' +UserSchema.methods.isPremium = -> + return false unless stripe = @get('stripe') + return true if stripe.subscriptionID + return true if stripe.free is true + return true if _.isString(stripe.free) and new Date() < new Date(stripe.free) + return false + UserSchema.statics.saveActiveUser = (id, event, done=null) -> id = mongoose.Types.ObjectId id if _.isString id @findById id, (err, user) -> diff --git a/server/users/user_handler.coffee b/server/users/user_handler.coffee index 5e1d07de6..0e534a21a 100644 --- a/server/users/user_handler.coffee +++ b/server/users/user_handler.coffee @@ -230,9 +230,19 @@ UserHandler = class UserHandler extends Handler return @trackActivity(req, res, args[0], args[2], args[3]) if args[1] is 'track' and args[2] return @getRemark(req, res, args[0]) if args[1] is 'remark' return @searchForUser(req, res) if args[1] is 'admin_search' + return @getStripeInfo(req, res, args[0]) if args[1] is 'stripe' return @sendNotFoundError(res) super(arguments...) + getStripeInfo: (req, res, handle) -> + @getDocumentForIdOrSlug handle, (err, user) => + return @sendNotFoundError(res) if not user + return @sendForbiddenError(res) unless req.user and (req.user.isAdmin() or req.user.get('_id').equals(user.get('_id'))) + return @sendNotFoundError(res) #if not customerID = user.get('stripe')?.customerID + stripe.customers.retrieve customerID, (err, customer) => + return @sendDatabaseError(res, err) if err + @sendSuccess(res, JSON.stringify(customer, null, '\t')) + agreeToCLA: (req, res) -> return @sendForbiddenError(res) unless req.user doc = diff --git a/server_setup.coffee b/server_setup.coffee index ae76032c2..97b734a09 100644 --- a/server_setup.coffee +++ b/server_setup.coffee @@ -16,6 +16,8 @@ UserHandler = require './server/users/user_handler' hipchat = require './server/hipchat' global.tv4 = require 'tv4' # required for TreemaUtils to work global.jsondiffpatch = require 'jsondiffpatch' +global.stripe = require('stripe')(config.stripe.secretKey) + productionLogging = (tokens, req, res) -> status = res.statusCode