mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-24 08:08:15 -05:00
Merge branch 'master' into production
This commit is contained in:
commit
e37c5645ef
49 changed files with 1413 additions and 343 deletions
|
@ -26,6 +26,7 @@ module.exports = class CocoRouter extends Backbone.Router
|
||||||
'account/subscription': go('account/SubscriptionView')
|
'account/subscription': go('account/SubscriptionView')
|
||||||
'account/subscription/sale': go('account/SubscriptionSaleView')
|
'account/subscription/sale': go('account/SubscriptionSaleView')
|
||||||
'account/invoices': go('account/InvoicesView')
|
'account/invoices': go('account/InvoicesView')
|
||||||
|
'account/prepaid': go('account/PrepaidView')
|
||||||
|
|
||||||
'admin': go('admin/MainAdminView')
|
'admin': go('admin/MainAdminView')
|
||||||
'admin/candidates': go('admin/CandidatesView')
|
'admin/candidates': go('admin/CandidatesView')
|
||||||
|
@ -65,7 +66,7 @@ module.exports = class CocoRouter extends Backbone.Router
|
||||||
'courses/mock1/:courseID': go('courses/mock1/CourseDetailsView')
|
'courses/mock1/:courseID': go('courses/mock1/CourseDetailsView')
|
||||||
'courses': go('courses/CoursesView')
|
'courses': go('courses/CoursesView')
|
||||||
'courses/enroll(/:courseID)': go('courses/CourseEnrollView')
|
'courses/enroll(/:courseID)': go('courses/CourseEnrollView')
|
||||||
'courses/:courseID': go('courses/CourseDetailsView')
|
'courses/:courseID(/:courseInstanceID)': go('courses/CourseDetailsView')
|
||||||
|
|
||||||
'db/*path': 'routeToServer'
|
'db/*path': 'routeToServer'
|
||||||
'demo(/*subpath)': go('DemoView')
|
'demo(/*subpath)': go('DemoView')
|
||||||
|
|
|
@ -243,3 +243,8 @@ module.exports.getCoursePraise = getCoursePraise = ->
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
praise[_.random(0, praise.length - 1)]
|
praise[_.random(0, praise.length - 1)]
|
||||||
|
|
||||||
|
module.exports.getPrepaidCodeAmount = getPrepaidCodeAmount = (price=999, users=0, months=0) ->
|
||||||
|
return 0 unless users > 0 and months > 0
|
||||||
|
total = price * users * months
|
||||||
|
total
|
||||||
|
|
|
@ -868,6 +868,7 @@ module.exports.thangNames = thangNames =
|
||||||
'Nordex'
|
'Nordex'
|
||||||
'Satish'
|
'Satish'
|
||||||
'Vera'
|
'Vera'
|
||||||
|
'Charlotte'
|
||||||
]
|
]
|
||||||
'Equestrian': [
|
'Equestrian': [
|
||||||
# Male
|
# Male
|
||||||
|
|
|
@ -31,7 +31,7 @@ module.exports = nativeDescription: "български език", englishDescri
|
||||||
contact: "Контакти"
|
contact: "Контакти"
|
||||||
twitter_follow: "Започни да следиш"
|
twitter_follow: "Започни да следиш"
|
||||||
teachers: "Учители"
|
teachers: "Учители"
|
||||||
# careers: "Careers"
|
careers: "Кариери"
|
||||||
|
|
||||||
modal:
|
modal:
|
||||||
close: "Затвори"
|
close: "Затвори"
|
||||||
|
@ -75,7 +75,7 @@ module.exports = nativeDescription: "български език", englishDescri
|
||||||
level_difficulty: "Трудност"
|
level_difficulty: "Трудност"
|
||||||
campaign_beginner: "Кампания за начинаещи"
|
campaign_beginner: "Кампания за начинаещи"
|
||||||
awaiting_levels_adventurer_prefix: "5 нови нива всяка седмица" # {change}
|
awaiting_levels_adventurer_prefix: "5 нови нива всяка седмица" # {change}
|
||||||
awaiting_levels_adventurer: "Стани Приключенец"
|
awaiting_levels_adventurer: "Стани Търсач на приключения"
|
||||||
awaiting_levels_adventurer_suffix: "за да бъдеш първият, който играе нови нива."
|
awaiting_levels_adventurer_suffix: "за да бъдеш първият, който играе нови нива."
|
||||||
adjust_volume: "Настрой звук"
|
adjust_volume: "Настрой звук"
|
||||||
campaign_multiplayer: "Арени за мултиплейър"
|
campaign_multiplayer: "Арени за мултиплейър"
|
||||||
|
@ -97,7 +97,7 @@ module.exports = nativeDescription: "български език", englishDescri
|
||||||
logging_in: "Влизане..."
|
logging_in: "Влизане..."
|
||||||
log_out: "Изход"
|
log_out: "Изход"
|
||||||
forgot_password: "Забравена парола?"
|
forgot_password: "Забравена парола?"
|
||||||
authenticate_gplus: "Автентикация чрез G+"
|
authenticate_gplus: "Аутентикация чрез G+"
|
||||||
load_profile: "Зареди G+ профил"
|
load_profile: "Зареди G+ профил"
|
||||||
finishing: "Завършване"
|
finishing: "Завършване"
|
||||||
sign_in_with_facebook: "Вписване чрез Facebook"
|
sign_in_with_facebook: "Вписване чрез Facebook"
|
||||||
|
@ -105,7 +105,7 @@ module.exports = nativeDescription: "български език", englishDescri
|
||||||
signup_switch: "Създаване на нов акаунт?"
|
signup_switch: "Създаване на нов акаунт?"
|
||||||
|
|
||||||
signup:
|
signup:
|
||||||
email_announcements: "Получава анонси по имейл"
|
email_announcements: "Получавай анонси по имейл"
|
||||||
creating: "Създаване на профил..."
|
creating: "Създаване на профил..."
|
||||||
sign_up: "Регистриране"
|
sign_up: "Регистриране"
|
||||||
log_in: "Вход с парола"
|
log_in: "Вход с парола"
|
||||||
|
@ -204,9 +204,9 @@ module.exports = nativeDescription: "български език", englishDescri
|
||||||
minute: "минута"
|
minute: "минута"
|
||||||
minutes: "минути"
|
minutes: "минути"
|
||||||
hour: "час"
|
hour: "час"
|
||||||
hours: "часове"
|
hours: "часа"
|
||||||
day: "ден"
|
day: "ден"
|
||||||
days: "дни"
|
days: "дена"
|
||||||
week: "седмица"
|
week: "седмица"
|
||||||
weeks: "седмици"
|
weeks: "седмици"
|
||||||
month: "месец"
|
month: "месец"
|
||||||
|
@ -256,8 +256,8 @@ module.exports = nativeDescription: "български език", englishDescri
|
||||||
victory_new_item: "Нов Предмет"
|
victory_new_item: "Нов Предмет"
|
||||||
victory_viking_code_school: "О да - това ниво беше наистина тежко! Ти или си програмист, или обезателно трябва да станеш такъв! Току що се доближи до приемането си във Викингското Училище по Програмиране, където ще научиш много нови неща и ще станеш професионален уеб програмист за 14 седмици."
|
victory_viking_code_school: "О да - това ниво беше наистина тежко! Ти или си програмист, или обезателно трябва да станеш такъв! Току що се доближи до приемането си във Викингското Училище по Програмиране, където ще научиш много нови неща и ще станеш професионален уеб програмист за 14 седмици."
|
||||||
victory_become_a_viking: "Стани Викинг"
|
victory_become_a_viking: "Стани Викинг"
|
||||||
# victory_bloc: "Great work! Your skills are improving, and someone's taking notice. If you've considered becoming a software developer, this may be your lucky day. Bloc is an online bootcamp that pairs you 1-on-1 with an expert mentor who will help train you into a professional developer! By beating A Mayhem of Munchkins, you're now eligible for a $500 price reduction with the code: CCRULES"
|
victory_bloc: "Чудесна работа! Твоите умения се подобряват и някой го е забелязал! Ако смяташ да ставаш програмист, може би това е щастливият ти ден. Bloc е online bootcamp, където ще можеш да тренираш насаме с наставник и да станеш професионален програмист! Тъй като победи в A Mayhem of Munchkins, получаваш купон за намаление от $500. Кодът е: CCRULES"
|
||||||
# victory_bloc_cta: "Meet your mentor – learn about Bloc"
|
victory_bloc_cta: "Срещни се с наставника си - научи повече за Bloc"
|
||||||
guide_title: "Упътване"
|
guide_title: "Упътване"
|
||||||
tome_minion_spells: "Заклинания на вашите Миньони' Spells" # Only in old-style levels.
|
tome_minion_spells: "Заклинания на вашите Миньони' Spells" # Only in old-style levels.
|
||||||
tome_read_only_spells: "Read-Only Заклинания" # Only in old-style levels.
|
tome_read_only_spells: "Read-Only Заклинания" # Only in old-style levels.
|
||||||
|
@ -401,17 +401,17 @@ module.exports = nativeDescription: "български език", englishDescri
|
||||||
price: "x3500 / месец"
|
price: "x3500 / месец"
|
||||||
|
|
||||||
subscribe:
|
subscribe:
|
||||||
# comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
|
comparison_blurb: "Изостри уменията си в CodeCombat с абонамент!"
|
||||||
# feature1: "110+ basic levels across 4 worlds"
|
feature1: "110+ основни нива в 4 свята"
|
||||||
# feature2: "10 powerful <strong>new heroes</strong> with unique skills!"
|
feature2: "10 силни <strong>нови герои</strong> с уникални умения!"
|
||||||
# feature3: "70+ bonus levels"
|
feature3: "70+ бонус нива"
|
||||||
# feature4: "<strong>3500 bonus gems</strong> every month!"
|
feature4: "<strong>3500 скъпоценни камъни бонус</strong> всеки месец!"
|
||||||
feature5: "Видео уроци"
|
feature5: "Видео уроци"
|
||||||
# feature6: "Premium email support"
|
feature6: "Премиум email поддръжка"
|
||||||
# feature7: "Private <strong>Clans</strong>"
|
feature7: "Частни <strong>Кланове</strong>"
|
||||||
# free: "Free"
|
free: "Безплатно"
|
||||||
# month: "month"
|
month: "месец"
|
||||||
# must_be_logged: "You must be logged in first. Please create an account or log in from the menu above."
|
must_be_logged: "Първо трябва да сте влезли. Моля, създайте си акаунт или влезте от менюто отгоре."
|
||||||
subscribe_title: "Абонирай се"
|
subscribe_title: "Абонирай се"
|
||||||
unsubscribe: "Прекрати абонамента"
|
unsubscribe: "Прекрати абонамента"
|
||||||
confirm_unsubscribe: "Подтвърди прекратяване на абонамента"
|
confirm_unsubscribe: "Подтвърди прекратяване на абонамента"
|
||||||
|
@ -431,61 +431,61 @@ module.exports = nativeDescription: "български език", englishDescri
|
||||||
parent_email_title: "Въведете email на родител?"
|
parent_email_title: "Въведете email на родител?"
|
||||||
parents: "За Родители"
|
parents: "За Родители"
|
||||||
parents_title: "Скъпи Родители: Вашето дете се учи да програмира. Ще му помогнете ли да продължи?"
|
parents_title: "Скъпи Родители: Вашето дете се учи да програмира. Ще му помогнете ли да продължи?"
|
||||||
parents_blurb1: "Вашето дете изигра __nLevels__ нива и научи основи на програмирането. Развийте интереса у тях като им купите абонамент - така те ще могат да продължат с игрите."
|
parents_blurb1: "Вашето дете изигра __nLevels__ нива и получи основни познания в програмирането. Развийте интереса в него като му купите абонамент - така то ще може да продължи с игрите."
|
||||||
parents_blurb1a: "Програмирането е важно умение което без съмнение вашето дете ще използва когато порасне. До 2020-та, основни познания по програмиране ще са необходими за повече от 77% от работните места, а софтуерните инженери ще са много търсени по света. Знаете ли че диплома в сферата на информационните технологии е предпоставка за едни от най-високите заплати в индустрията?"
|
parents_blurb1a: "Програмирането е важно умение което без съмнение вашето дете ще използва когато порасне. До 2020-та, основни познания по програмиране ще са необходими за повече от 77% от работните места, а софтуерните инженери ще са много търсени по света. Знаете ли, че диплома в сферата на информационните технологии е предпоставка за едни от най-високите заплати в индустрията?"
|
||||||
parents_blurb2: "За $9.99 USD/месец, вашето дете ще получава нови задачи всяка седмица, както и персонална помощ по електронната помощ от професионални програмисти."
|
parents_blurb2: "За $9.99 USD/месец, вашето дете ще получава нови задачи всяка седмица, както и персонална помощ по електронната поща от професионални програмисти."
|
||||||
parents_blurb3: "Без Риск: 100% гаранция за възстановяване на средствата, прекратяване на абонамаента с едно натискане на бутон."
|
parents_blurb3: "Без Риск: 100% гаранция за възстановяване на средствата, прекратяване на абонамента с едно натискане на бутон."
|
||||||
payment_methods: "Начини на плащане"
|
payment_methods: "Начини на плащане"
|
||||||
payment_methods_title: "Възможни начини на плащане"
|
payment_methods_title: "Възможни начини на плащане"
|
||||||
payment_methods_blurb1: "В момента приемаме кредитни карти и Alipay."
|
payment_methods_blurb1: "В момента приемаме кредитни карти и Alipay."
|
||||||
payment_methods_blurb2: "Ако желаете алтернативна форма на плащане, свържете се с нас"
|
payment_methods_blurb2: "Ако желаете алтернативна форма на плащане, свържете се с нас"
|
||||||
# sale_already_subscribed: "You're already subscribed!"
|
sale_already_subscribed: "Вече имате абонамент!"
|
||||||
# sale_blurb1: "Save 35%"
|
sale_blurb1: "Спестете 35%"
|
||||||
# sale_blurb2: "off regular subscription price of $120 for a whole year!" # {changed}
|
sale_blurb2: "от редовната абонаментна такса от $120 за цялата година!" # {changed}
|
||||||
# sale_button: "Sale!"
|
sale_button: "Разпродажба!"
|
||||||
# sale_button_title: "Save 35% when you purchase a 1 year subscription"
|
sale_button_title: "Спестете 35% като направите абонамент за 1 година"
|
||||||
# sale_click_here: "Click Here"
|
sale_click_here: "Кликнете Тук"
|
||||||
# sale_ends: "Ends"
|
sale_ends: "Завършва"
|
||||||
# sale_extended: "*Existing subscriptions will be extended by 1 year."
|
sale_extended: "*Съществуващият абонамент ще бъде продължен с 1 година."
|
||||||
# sale_feature_here: "Here's what you'll get:"
|
sale_feature_here: "Ето какво получавате:"
|
||||||
# sale_feature2: "Access to 9 powerful <strong>new heroes</strong> with unique skills!"
|
sale_feature2: "Достъп до 9 силни <strong>нови герои</strong> с уникални умения!"
|
||||||
# sale_feature4: "<strong>42,000 bonus gems</strong> awarded immediately!"
|
sale_feature4: "<strong>42,000 скъпоценни камъни бонус</strong> присъдени веднага!"
|
||||||
# sale_continue: "Ready to continue adventuring?"
|
sale_continue: "Готови ли сте да продължите приключението?"
|
||||||
# sale_limited_time: "Limited time offer!"
|
sale_limited_time: "Офертата е достъпна за ограничен период от време!"
|
||||||
# sale_new_heroes: "New heroes!"
|
sale_new_heroes: "Нови герои!"
|
||||||
# sale_title: "Back to School Sale"
|
sale_title: "Назад към Училищната Разпродажба"
|
||||||
# sale_view_button: "Buy 1 year subscription for"
|
sale_view_button: "Купи едногодишен абонамент за"
|
||||||
# stripe_description: "Monthly Subscription"
|
stripe_description: "Месечен Абонамент"
|
||||||
# stripe_description_year_sale: "1 Year Subscription (35% discount)"
|
stripe_description_year_sale: "Едногодишен абонамент (35% намаление)"
|
||||||
# subscription_required_to_play: "You'll need a subscription to play this level."
|
subscription_required_to_play: "Необходим ви е абонамент за да играете това ниво."
|
||||||
# unlock_help_videos: "Subscribe to unlock all video tutorials."
|
unlock_help_videos: "Абонирайте се за да отключите всичките видео уроци."
|
||||||
# personal_sub: "Personal Subscription" # Accounts Subscription View below
|
personal_sub: "Персонален абонамент" # Accounts Subscription View below
|
||||||
# loading_info: "Loading subscription information..."
|
loading_info: "Зареждане на информацията за абонамента..."
|
||||||
# managed_by: "Managed by"
|
managed_by: "Управлявана от"
|
||||||
# will_be_cancelled: "Will be cancelled on"
|
will_be_cancelled: "Ще бъде отменена"
|
||||||
# currently_free: "You currently have a free subscription"
|
currently_free: "В момента имате безплатен абонамент"
|
||||||
# currently_free_until: "You currently have a subscription until" # {changed}
|
currently_free_until: "В момента имате абонамент до" # {changed}
|
||||||
# was_free_until: "You had a free subscription until"
|
was_free_until: "Имали сте безплатен абонамент до"
|
||||||
# managed_subs: "Managed Subscriptions"
|
managed_subs: "Управлявани Абонаменти"
|
||||||
# managed_subs_desc: "Add subscriptions for other players (students, children, etc.)"
|
managed_subs_desc: "Добавете абонаменти на други играчи (студенти, деца и т.н.)"
|
||||||
# managed_subs_desc_2: "Recipients must have a CodeCombat account associated with the email address you provide."
|
managed_subs_desc_2: "Получателите трябва да имат CodeCombat акаунт, асоцииран с email адреса, който предоставяте."
|
||||||
# group_discounts: "Group discounts"
|
group_discounts: "Групови намаления"
|
||||||
# group_discounts_1: "We also offer group discounts for bulk subscriptions."
|
group_discounts_1: "Също предлагаме специални намаления за групово абониране."
|
||||||
# group_discounts_1st: "1st subscription"
|
group_discounts_1st: "Първи абонамент"
|
||||||
# group_discounts_full: "Full price"
|
group_discounts_full: "Пълната цена"
|
||||||
# group_discounts_2nd: "Subscriptions 2-11"
|
group_discounts_2nd: "Абонаменти 2-11"
|
||||||
# group_discounts_20: "20% off"
|
group_discounts_20: "20% намаление"
|
||||||
# group_discounts_12th: "Subscriptions 12+"
|
group_discounts_12th: "Абонаменти 12+"
|
||||||
# group_discounts_40: "40% off"
|
group_discounts_40: "40% намаление"
|
||||||
# subscribing: "Subscribing..."
|
subscribing: "Абониране..."
|
||||||
# recipient_emails_placeholder: "Enter email address to subscribe, one per line."
|
recipient_emails_placeholder: "Въведете email адресите на абонатите, по един на ред."
|
||||||
# subscribe_users: "Subscribe Users"
|
subscribe_users: "Абониране на потребители"
|
||||||
# users_subscribed: "Users subscribed:"
|
users_subscribed: "Абонирани потребители:"
|
||||||
# no_users_subscribed: "No users subscribed, please double check your email addresses."
|
no_users_subscribed: "Няма абонирани потребители, моля проверете email адресите отново."
|
||||||
# current_recipients: "Current Recipients"
|
current_recipients: "Текущи получатели"
|
||||||
# unsubscribing: "Unsubscribing..."
|
unsubscribing: "Прекратяване на абонамента..."
|
||||||
# subscribe_prepaid: "Click Subscribe to use prepaid code"
|
subscribe_prepaid: "Кликнете 'Абонамент', за да използвате предплатен код"
|
||||||
# using_prepaid: "Using prepaid code for monthly subscription"
|
using_prepaid: "Използване на предплатен код за месечен абонамент"
|
||||||
|
|
||||||
choose_hero:
|
choose_hero:
|
||||||
choose_hero: "Избери си герой"
|
choose_hero: "Избери си герой"
|
||||||
|
@ -494,7 +494,7 @@ module.exports = nativeDescription: "български език", englishDescri
|
||||||
default: "По подразбиране"
|
default: "По подразбиране"
|
||||||
experimental: "Експериментално"
|
experimental: "Експериментално"
|
||||||
python_blurb: "Прост,но мощен, идеален за начинаещи и експерти."
|
python_blurb: "Прост,но мощен, идеален за начинаещи и експерти."
|
||||||
javascript_blurb: "Езикът на мрежата. (Не е същия като Java.)"
|
javascript_blurb: "Езикът на Мрежата. (Не е същия като Java.)"
|
||||||
coffeescript_blurb: "По-добър синтаксис от JavaScript."
|
coffeescript_blurb: "По-добър синтаксис от JavaScript."
|
||||||
clojure_blurb: "Модерен Lisp."
|
clojure_blurb: "Модерен Lisp."
|
||||||
lua_blurb: "Скриптен език за игри."
|
lua_blurb: "Скриптен език за игри."
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
module.exports = nativeDescription: "dansk", englishDescription: "Danish", translation:
|
module.exports = nativeDescription: "dansk", englishDescription: "Danish", translation:
|
||||||
home:
|
home:
|
||||||
slogan: "Lær at Kode ved at Spille et Spil"
|
slogan: "Lær at kode ved at spille et spil"
|
||||||
no_ie: "CodeCombat kan desværre ikke køre i Internet Explorer 8 eller ældre. Beklager!" # Warning that only shows up in IE8 and older
|
no_ie: "CodeCombat kan desværre ikke køre i Internet Explorer 8 eller ældre. Beklager!" # Warning that only shows up in IE8 and older
|
||||||
no_mobile: "CodeCombat er ikke designet til mobile enheder og vil måske ikke virke!" # Warning that shows up on mobile devices
|
no_mobile: "CodeCombat er ikke designet til mobile enheder og vil måske ikke virke!" # Warning that shows up on mobile devices
|
||||||
play: "Spil" # The big play button that opens up the campaign view.
|
play: "Spil" # The big play button that opens up the campaign view.
|
||||||
|
@ -98,7 +98,7 @@ module.exports = nativeDescription: "dansk", englishDescription: "Danish", trans
|
||||||
log_out: "Log Ud"
|
log_out: "Log Ud"
|
||||||
forgot_password: "Glemt dit kodeord?"
|
forgot_password: "Glemt dit kodeord?"
|
||||||
authenticate_gplus: "Forbind med G+"
|
authenticate_gplus: "Forbind med G+"
|
||||||
load_profile: "Load G+ Profil"
|
load_profile: "Indlæs G+ Profil"
|
||||||
finishing: "Færdiggører"
|
finishing: "Færdiggører"
|
||||||
sign_in_with_facebook: "Log ind med Facebook"
|
sign_in_with_facebook: "Log ind med Facebook"
|
||||||
sign_in_with_gplus: "Sign in with G+"
|
sign_in_with_gplus: "Sign in with G+"
|
||||||
|
@ -310,8 +310,8 @@ module.exports = nativeDescription: "dansk", englishDescription: "Danish", trans
|
||||||
# tip_reticulating: "Reticulating spines."
|
# tip_reticulating: "Reticulating spines."
|
||||||
tip_harry: "Du' en troldmand, "
|
tip_harry: "Du' en troldmand, "
|
||||||
tip_great_responsibility: "Med store kodeevner kommer stort fejlfindingsansvnar."
|
tip_great_responsibility: "Med store kodeevner kommer stort fejlfindingsansvnar."
|
||||||
# tip_munchkin: "If you don't eat your vegetables, a munchkin will come after you while you're asleep."
|
tip_munchkin: "Hvis du ikke spiser dine grøntsager, kommer der en Munchkin efter dig mens du sover."
|
||||||
# tip_binary: "There are only 10 types of people in the world: those who understand binary, and those who don't."
|
tip_binary: "Der findes kun 10 slags mennesker i verdenen: Dem som forstår binært, og dem som ikke gør."
|
||||||
# tip_commitment_yoda: "A programmer must have the deepest commitment, the most serious mind. ~ Yoda"
|
# tip_commitment_yoda: "A programmer must have the deepest commitment, the most serious mind. ~ Yoda"
|
||||||
# tip_no_try: "Do. Or do not. There is no try. - Yoda"
|
# tip_no_try: "Do. Or do not. There is no try. - Yoda"
|
||||||
# tip_patience: "Patience you must have, young Padawan. - Yoda"
|
# tip_patience: "Patience you must have, young Padawan. - Yoda"
|
||||||
|
@ -334,33 +334,33 @@ module.exports = nativeDescription: "dansk", englishDescription: "Danish", trans
|
||||||
# tip_source_code: "I want to change the world but they would not give me the source code."
|
# tip_source_code: "I want to change the world but they would not give me the source code."
|
||||||
# tip_javascript_java: "Java is to JavaScript what Car is to Carpet. - Chris Heilmann"
|
# tip_javascript_java: "Java is to JavaScript what Car is to Carpet. - Chris Heilmann"
|
||||||
# tip_move_forward: "Whatever you do, keep moving forward. - Martin Luther King Jr."
|
# tip_move_forward: "Whatever you do, keep moving forward. - Martin Luther King Jr."
|
||||||
# tip_google: "Have a problem you can't solve? Google it!"
|
tip_google: "Har du et problem du ikke kan løse? Google det!"
|
||||||
# tip_adding_evil: "Adding a pinch of evil."
|
tip_adding_evil: "Tilføjer et strejf af ondskab.."
|
||||||
# tip_hate_computers: "That's the thing about people who think they hate computers. What they really hate is lousy programmers. - Larry Niven"
|
# tip_hate_computers: "That's the thing about people who think they hate computers. What they really hate is lousy programmers. - Larry Niven"
|
||||||
# tip_open_source_contribute: "You can help CodeCombat improve!"
|
# tip_open_source_contribute: "You can help CodeCombat improve!"
|
||||||
# tip_recurse: "To iterate is human, to recurse divine. - L. Peter Deutsch"
|
# tip_recurse: "To iterate is human, to recurse divine. - L. Peter Deutsch"
|
||||||
# tip_free_your_mind: "You have to let it all go, Neo. Fear, doubt, and disbelief. Free your mind. - Morpheus"
|
# tip_free_your_mind: "You have to let it all go, Neo. Fear, doubt, and disbelief. Free your mind. - Morpheus"
|
||||||
# tip_strong_opponents: "Even the strongest of opponents always has a weakness. - Itachi Uchiha"
|
# tip_strong_opponents: "Even the strongest of opponents always has a weakness. - Itachi Uchiha"
|
||||||
# tip_paper_and_pen: "Before you start coding, you can always plan with a sheet of paper and a pen."
|
tip_paper_and_pen: "Før du starter med at programmere, kan du altid sætte dig ned med et stykke papir og blyant."
|
||||||
|
|
||||||
game_menu:
|
game_menu:
|
||||||
# inventory_tab: "Inventory"
|
inventory_tab: "Dine ting"
|
||||||
# save_load_tab: "Save/Load"
|
save_load_tab: "Gem/Indlæs"
|
||||||
# options_tab: "Options"
|
options_tab: "Indstillinger"
|
||||||
# guide_tab: "Guide"
|
guide_tab: "Guide"
|
||||||
# guide_video_tutorial: "Video Tutorial"
|
# guide_video_tutorial: "Video Tutorial"
|
||||||
# guide_tips: "Tips"
|
guide_tips: "Råd"
|
||||||
multiplayer_tab: "Flere spillere"
|
multiplayer_tab: "Flere spillere"
|
||||||
# auth_tab: "Sign Up"
|
auth_tab: "Tilmeld dig"
|
||||||
# inventory_caption: "Equip your hero"
|
inventory_caption: "Udrust din helt"
|
||||||
# choose_hero_caption: "Choose hero, language"
|
choose_hero_caption: "Vælg helt, sprog"
|
||||||
# save_load_caption: "... and view history"
|
save_load_caption: "... og se historie"
|
||||||
# options_caption: "Configure settings"
|
options_caption: "Ændre indstillinger"
|
||||||
# guide_caption: "Docs and tips"
|
# guide_caption: "Docs and tips"
|
||||||
# multiplayer_caption: "Play with friends!"
|
multiplayer_caption: "Spil med venner!"
|
||||||
# auth_caption: "Save your progress."
|
auth_caption: "Gem dit spil."
|
||||||
|
|
||||||
# leaderboard:
|
leaderboard:
|
||||||
# leaderboard: "Leaderboard"
|
# leaderboard: "Leaderboard"
|
||||||
# view_other_solutions: "View Leaderboards"
|
# view_other_solutions: "View Leaderboards"
|
||||||
# scores: "Scores"
|
# scores: "Scores"
|
||||||
|
@ -371,7 +371,7 @@ module.exports = nativeDescription: "dansk", englishDescription: "Danish", trans
|
||||||
# time: "Time"
|
# time: "Time"
|
||||||
# damage_taken: "Damage Taken"
|
# damage_taken: "Damage Taken"
|
||||||
# damage_dealt: "Damage Dealt"
|
# damage_dealt: "Damage Dealt"
|
||||||
# difficulty: "Difficulty"
|
difficulty: "Sværhedsgrad"
|
||||||
# gold_collected: "Gold Collected"
|
# gold_collected: "Gold Collected"
|
||||||
|
|
||||||
# inventory:
|
# inventory:
|
||||||
|
@ -699,7 +699,7 @@ module.exports = nativeDescription: "dansk", englishDescription: "Danish", trans
|
||||||
contact_us: "Kontakt CodeCombat"
|
contact_us: "Kontakt CodeCombat"
|
||||||
welcome: "Godt at høre fra dig! Brug denne formular til at sende os en email. "
|
welcome: "Godt at høre fra dig! Brug denne formular til at sende os en email. "
|
||||||
forum_prefix: "For noget offentligt, prøv venligst "
|
forum_prefix: "For noget offentligt, prøv venligst "
|
||||||
forum_page: "vores forum"
|
forum_page: "Vores forum"
|
||||||
forum_suffix: " istedet."
|
forum_suffix: " istedet."
|
||||||
# faq_prefix: "There's also a"
|
# faq_prefix: "There's also a"
|
||||||
# faq: "FAQ"
|
# faq: "FAQ"
|
||||||
|
|
|
@ -1129,6 +1129,7 @@
|
||||||
recently_played: "Recently Played"
|
recently_played: "Recently Played"
|
||||||
no_recent_games: "No games played during the past two weeks."
|
no_recent_games: "No games played during the past two weeks."
|
||||||
payments: "Payments"
|
payments: "Payments"
|
||||||
|
prepaid: "Prepaid"
|
||||||
purchased: "Purchased"
|
purchased: "Purchased"
|
||||||
sale: "Sale"
|
sale: "Sale"
|
||||||
subscription: "Subscription"
|
subscription: "Subscription"
|
||||||
|
@ -1159,6 +1160,14 @@
|
||||||
retrying: "Server error, retrying."
|
retrying: "Server error, retrying."
|
||||||
success: "Successfully paid. Thanks!"
|
success: "Successfully paid. Thanks!"
|
||||||
|
|
||||||
|
account_prepaid:
|
||||||
|
purchase_code: "Purchase a Subscription Code"
|
||||||
|
purchase_amount: "Amount"
|
||||||
|
purchase_total: "Total"
|
||||||
|
purchase_button: "Submit Purchase"
|
||||||
|
your_codes: "Your Codes:"
|
||||||
|
redeem_codes: "Redeem a Subscription Code"
|
||||||
|
|
||||||
loading_error:
|
loading_error:
|
||||||
could_not_load: "Error loading from server"
|
could_not_load: "Error loading from server"
|
||||||
connection_failure: "Connection failed."
|
connection_failure: "Connection failed."
|
||||||
|
|
|
@ -300,11 +300,11 @@ module.exports = nativeDescription: "suomi", englishDescription: "Finnish", tran
|
||||||
tip_tell_friends: "Pidätkö CodeCombatista? Kerro meistä myös ystävillesi!"
|
tip_tell_friends: "Pidätkö CodeCombatista? Kerro meistä myös ystävillesi!"
|
||||||
tip_beta_launch: "CodeCombat avattiin betatestaukseen lokakuussa 2013."
|
tip_beta_launch: "CodeCombat avattiin betatestaukseen lokakuussa 2013."
|
||||||
tip_think_solution: "Mieti ratkaisua, älä ongelmaa."
|
tip_think_solution: "Mieti ratkaisua, älä ongelmaa."
|
||||||
# tip_theory_practice: "In theory, there is no difference between theory and practice. But in practice, there is. - Yogi Berra"
|
tip_theory_practice: "Teoriassa teoria ja käytäntö ovat sama asia. Käytännössä näin ei ole. - Yogi Berra"
|
||||||
# tip_error_free: "There are two ways to write error-free programs; only the third one works. - Alan Perlis"
|
tip_error_free: "Virheettömiä ohjelmia voi kirjoittaa kahdella eri tavalla. Ikävä kyllä kolmas tapa on ainoa toimiva. - Alan Perlis"
|
||||||
# tip_debugging_program: "If debugging is the process of removing bugs, then programming must be the process of putting them in. - Edsger W. Dijkstra"
|
tip_debugging_program: "Jos debuggaus tarkoittaa virheiden poistamista ohjelmasta, niin ohjelmoinnin on tarkoitettava niiden lisäämistä. - Edsger W. Dijkstra"
|
||||||
# tip_forums: "Head over to the forums and tell us what you think!"
|
tip_forums: "Tulepa keskustelupalstalle kertomaan mielipiteesi!"
|
||||||
# tip_baby_coders: "In the future, even babies will be Archmages."
|
tip_baby_coders: "Tulevaisuudessa jopa vauvoista tulee Arkkimaageja."
|
||||||
# tip_morale_improves: "Loading will continue until morale improves."
|
# tip_morale_improves: "Loading will continue until morale improves."
|
||||||
# tip_all_species: "We believe in equal opportunities to learn programming for all species."
|
# tip_all_species: "We believe in equal opportunities to learn programming for all species."
|
||||||
# tip_reticulating: "Reticulating spines."
|
# tip_reticulating: "Reticulating spines."
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
module.exports = nativeDescription: "Português do Brasil", englishDescription: "Portuguese (Brazil)", translation:
|
module.exports = nativeDescription: "Português do Brasil", englishDescription: "Portuguese (Brazil)", translation:
|
||||||
home:
|
home:
|
||||||
slogan: "Aprenda a programar enquanto se diverte jogando."
|
slogan: "Aprenda a programar enquanto se diverte jogando."
|
||||||
no_ie: "CodeCombat não roda em versões mais antigas que o Internet Explorer 10. Desculpe!" # Warning that only shows up in IE8 and older
|
no_ie: "O CodeCombat não roda em versões mais antigas que o Internet Explorer 10. Desculpe!" # Warning that only shows up in IE8 and older
|
||||||
no_mobile: "CodeCombat não foi projetado para dispositivos móveis e pode não funcionar!" # Warning that shows up on mobile devices
|
no_mobile: "O CodeCombat não foi projetado para dispositivos móveis e pode não funcionar!" # Warning that shows up on mobile devices
|
||||||
play: "Jogar" # The big play button that opens up the campaign view.
|
play: "Jogar" # The big play button that opens up the campaign view.
|
||||||
old_browser: "Ops, seu navegador é muito antigo para rodar o CodeCombat. Desculpe!" # Warning that shows up on really old Firefox/Chrome/Safari
|
old_browser: "Ops, seu navegador é muito antigo para rodar o CodeCombat. Desculpe!" # Warning that shows up on really old Firefox/Chrome/Safari
|
||||||
old_browser_suffix: "Você pode tentar de qualquer forma, mas provavelmente não irá funcionar."
|
old_browser_suffix: "Você pode tentar de qualquer forma, mas provavelmente não irá funcionar."
|
||||||
ipad_browser: "Más notícias:CodeCombat não é executado no navegador do iPad. Boas notícias: Nosso app nativo do iPad esta esperando a aprovação da Apple."
|
ipad_browser: "Más notícias: O CodeCombat não é executado no navegador do iPad. Boas notícias: Nosso app nativo do iPad está esperando a aprovação da Apple."
|
||||||
campaign: "Campanha"
|
campaign: "Campanha"
|
||||||
for_beginners: "Para Iniciantes"
|
for_beginners: "Para Iniciantes"
|
||||||
multiplayer: "Multijogador" # Not currently shown on home page
|
multiplayer: "Multijogador" # Not currently shown on home page
|
||||||
|
@ -46,12 +46,12 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription:
|
||||||
pitch_body: "Desenvolvemos o CodeCombat em Inglês, mas já temos jogadores de todo o mundo. Muitos deles querem jogar em Português Brasileiro por não falar Inglês, por isso, se você conhece os dois idiomas, por favor, considere inscrever-se no programa para Diplomata e assim ajudar a traduzir tanto o site do CodeCombat quanto todos os estágios para o Português Brasileiro."
|
pitch_body: "Desenvolvemos o CodeCombat em Inglês, mas já temos jogadores de todo o mundo. Muitos deles querem jogar em Português Brasileiro por não falar Inglês, por isso, se você conhece os dois idiomas, por favor, considere inscrever-se no programa para Diplomata e assim ajudar a traduzir tanto o site do CodeCombat quanto todos os estágios para o Português Brasileiro."
|
||||||
missing_translations: "Até que possamos traduzir todo o conteúdo para o Português Brasileiro, você lerá o texto em Inglês quando a versão em Português Brasileiro não estiver disponível."
|
missing_translations: "Até que possamos traduzir todo o conteúdo para o Português Brasileiro, você lerá o texto em Inglês quando a versão em Português Brasileiro não estiver disponível."
|
||||||
learn_more: "Saiba mais sobre ser um Diplomata"
|
learn_more: "Saiba mais sobre ser um Diplomata"
|
||||||
subscribe_as_diplomat: "Assinar como um Diplomata"
|
subscribe_as_diplomat: "Inscrever-se como um Diplomata"
|
||||||
|
|
||||||
play:
|
play:
|
||||||
play_as: "Jogar Como " # Ladder page
|
play_as: "Jogar Como " # Ladder page
|
||||||
spectate: "Assistir" # Ladder page
|
spectate: "Assistir" # Ladder page
|
||||||
players: "jogadores" # Hover over a level on /play
|
players: "Jogadores" # Hover over a level on /play
|
||||||
hours_played: "Horas jogadas" # Hover over a level on /play
|
hours_played: "Horas jogadas" # Hover over a level on /play
|
||||||
items: "Itens" # Tooltip on item shop button from /play
|
items: "Itens" # Tooltip on item shop button from /play
|
||||||
unlock: "Desbloquear" # For purchasing items and heroes
|
unlock: "Desbloquear" # For purchasing items and heroes
|
||||||
|
@ -128,7 +128,7 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription:
|
||||||
|
|
||||||
common:
|
common:
|
||||||
back: "Voltar" # When used as an action verb, like "Navigate backward"
|
back: "Voltar" # When used as an action verb, like "Navigate backward"
|
||||||
continue: "Continue" # When used as an action verb, like "Continue forward"
|
continue: "Continuar" # When used as an action verb, like "Continue forward"
|
||||||
loading: "Carregando..."
|
loading: "Carregando..."
|
||||||
saving: "Salvando..."
|
saving: "Salvando..."
|
||||||
sending: "Enviando..."
|
sending: "Enviando..."
|
||||||
|
@ -300,7 +300,7 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription:
|
||||||
tip_tell_friends: "Está gostando do CodeCombat? Divulgue para os seus amigos!"
|
tip_tell_friends: "Está gostando do CodeCombat? Divulgue para os seus amigos!"
|
||||||
tip_beta_launch: "CodeCombat lançou sua versão beta em outubro de 2013."
|
tip_beta_launch: "CodeCombat lançou sua versão beta em outubro de 2013."
|
||||||
tip_think_solution: "Pense na solução, não no problema."
|
tip_think_solution: "Pense na solução, não no problema."
|
||||||
tip_theory_practice: "Na teoria, não existe diferença entre teoria e prática. Mas, na prática, há. - Yogi Berra"
|
tip_theory_practice: "Na teoria, não existe diferença entre teoria e prática. Mas, na prática, existe. - Yogi Berra"
|
||||||
tip_error_free: "Existem duas formas de escrever programas sem erros; apenas a terceira funciona. - Alan Perlis"
|
tip_error_free: "Existem duas formas de escrever programas sem erros; apenas a terceira funciona. - Alan Perlis"
|
||||||
tip_debugging_program: "Se depurar é o processo de remover erros, então programar deve ser o processo de adicioná-los. - Edsger W. Dijkstra"
|
tip_debugging_program: "Se depurar é o processo de remover erros, então programar deve ser o processo de adicioná-los. - Edsger W. Dijkstra"
|
||||||
tip_forums: "Vá aos fóruns e diga-nos o que você pensa!"
|
tip_forums: "Vá aos fóruns e diga-nos o que você pensa!"
|
||||||
|
@ -422,7 +422,7 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription:
|
||||||
sorry_to_see_you_go: "É uma pena ver você indo embora! Por favor, diga o que podemos fazer para melhorar."
|
sorry_to_see_you_go: "É uma pena ver você indo embora! Por favor, diga o que podemos fazer para melhorar."
|
||||||
unsubscribe_feedback_placeholder: "Oh, o que nós fizemos?"
|
unsubscribe_feedback_placeholder: "Oh, o que nós fizemos?"
|
||||||
parent_button: "Pergunte aos seus pais"
|
parent_button: "Pergunte aos seus pais"
|
||||||
parent_email_description: "Nós enviaremos um e-mail para eles adquirir para você uma assinatura do CodeCombat."
|
parent_email_description: "Nós enviaremos um e-mail para eles adquirirem uma assinatura do CodeCombat para você."
|
||||||
parent_email_input_invalid: "Endereço de e-mail inválido."
|
parent_email_input_invalid: "Endereço de e-mail inválido."
|
||||||
parent_email_input_label: "Endereço de e-mail dos pais"
|
parent_email_input_label: "Endereço de e-mail dos pais"
|
||||||
parent_email_input_placeholder: "Informe o e-mail dos pais"
|
parent_email_input_placeholder: "Informe o e-mail dos pais"
|
||||||
|
@ -445,15 +445,15 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription:
|
||||||
sale_button: "Venda!"
|
sale_button: "Venda!"
|
||||||
sale_button_title: "Economize 35% quando você adquirir a assinatura de 1 ano"
|
sale_button_title: "Economize 35% quando você adquirir a assinatura de 1 ano"
|
||||||
sale_click_here: "Clique Aqui"
|
sale_click_here: "Clique Aqui"
|
||||||
# sale_ends: "Ends"
|
sale_ends: "Termina"
|
||||||
sale_extended: "*Assinaturas existentes serão extendidas por 1 ano."
|
sale_extended: "*Assinaturas existentes serão extendidas por 1 ano."
|
||||||
# sale_feature_here: "Here's what you'll get:"
|
sale_feature_here: "O que está incluso:"
|
||||||
sale_feature2: "Acesso a 9 poderosos <strong>novos heróis</strong> com atributos únicos!"
|
sale_feature2: "Acesso a 9 poderosos <strong>novos heróis</strong> com atributos únicos!"
|
||||||
# sale_feature4: "<strong>42,000 bonus gems</strong> awarded immediately!"
|
sale_feature4: "<strong>42,000 gemas bônus</strong> entregues imediatamente!"
|
||||||
# sale_continue: "Ready to continue adventuring?"
|
sale_continue: "Pronto para continuar a aventura?"
|
||||||
# sale_limited_time: "Limited time offer!"
|
sale_limited_time: "Oferta por tempo limitado!"
|
||||||
sale_new_heroes: "Novos heróis!"
|
sale_new_heroes: "Novos heróis!"
|
||||||
# sale_title: "Back to School Sale"
|
sale_title: "Promoção de volta às aulas"
|
||||||
# sale_view_button: "Buy 1 year subscription for"
|
# sale_view_button: "Buy 1 year subscription for"
|
||||||
stripe_description: "Inscrição Mensal"
|
stripe_description: "Inscrição Mensal"
|
||||||
stripe_description_year_sale: "Assinatura de 1 Ano (35% de desconto"
|
stripe_description_year_sale: "Assinatura de 1 Ano (35% de desconto"
|
||||||
|
@ -610,7 +610,7 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription:
|
||||||
intro_1: "CodeCombat é um jogo online que ensina programação. Estudantes criam código em linguagens de programação usadas na vida real."
|
intro_1: "CodeCombat é um jogo online que ensina programação. Estudantes criam código em linguagens de programação usadas na vida real."
|
||||||
intro_2: "Não é necessário ter experiência!"
|
intro_2: "Não é necessário ter experiência!"
|
||||||
free_title: "Quanto custa?"
|
free_title: "Quanto custa?"
|
||||||
cost_china: "CodeCombat na China é gratuito para os primeiros cinco níveis, depois disso o valor mensal para ter acesso a mais de 140 níveis nos nossos servidores exclusivos na China é de $9.99 dólares americanos."
|
cost_china: "CodeCombat na China é gratuito para os primeiros cinco níveis, depois disso o valor mesal para ter acesso a mais de 140 níveis nos nossos servidores exclusivos na China é de $9.99 dólares americanos."
|
||||||
free_1: "CodeCombat Basic é gratuito! Há mais de 80 níveis gratuitos que cobrem todos os conceitos." # {change}
|
free_1: "CodeCombat Basic é gratuito! Há mais de 80 níveis gratuitos que cobrem todos os conceitos." # {change}
|
||||||
free_2: "Uma assinatura mensal dá acesso aos vídeos tutoriais e mais níveis para praticar."
|
free_2: "Uma assinatura mensal dá acesso aos vídeos tutoriais e mais níveis para praticar."
|
||||||
teacher_subs_title: "Professores recebem assinaturas gratuitas!"
|
teacher_subs_title: "Professores recebem assinaturas gratuitas!"
|
||||||
|
@ -632,58 +632,58 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription:
|
||||||
monitor_progress_3: "Para adicionar estudantes, envie-os o link de convite para seu clã, que está na"
|
monitor_progress_3: "Para adicionar estudantes, envie-os o link de convite para seu clã, que está na"
|
||||||
monitor_progress_4: "página."
|
monitor_progress_4: "página."
|
||||||
monitor_progress_5: "Depois que eles se juntarem ao seu Clã, você verá um resumo do progresso dos estudantes na página do seu Clã."
|
monitor_progress_5: "Depois que eles se juntarem ao seu Clã, você verá um resumo do progresso dos estudantes na página do seu Clã."
|
||||||
# private_clans_1: "Private Clans provide increased privacy and detailed progress information for each student."
|
private_clans_1: "Clãs privados proporcionam mais pricacidade e informações de progresso detalhadas para cada estudante."
|
||||||
# private_clans_2: "To create a private Clan, check the 'Make clan private' checkbox when creating a"
|
private_clans_2: "Para criar um Clã privado, marque a caixa 'Tornar Clã privado' ao criar um"
|
||||||
private_clans_3: "."
|
private_clans_3: "."
|
||||||
who_for_title: "Para quem é indicado o CodeCombat?"
|
who_for_title: "Para quem é indicado o CodeCombat?"
|
||||||
who_for_1: "Nós recomendamos CodeCombat para estudantes a partir de 9 anos de idade. Nenhuma experiência anterior em programação é necessária."
|
who_for_1: "Nós recomendamos CodeCombat para estudantes a partir de 9 anos de idade. Nenhuma experiência anterior em programação é necessária."
|
||||||
# who_for_2: "We've designed CodeCombat to appeal to both boys and girls."
|
who_for_2: "Nós projetamos o CodeCombat para ser atraente à meninos e meninas."
|
||||||
# material_title: "How much material is there?"
|
material_title: "Quanto material existe?"
|
||||||
# material_china: "Approximately 40 hours of gameplay spread over 180+ subscriber-only levels so far."
|
material_china: "Aproximadamente 40 horas de jogo espalhadas por mais de 180 níveis restritos até agora."
|
||||||
# material_1: "Approximately 25 hours of free content and an additional 15 hours of subscriber content."
|
material_1: "Aproximadamente 25 horas de conteúdo livre e 15 horas adicionais de conteúdo restrito."
|
||||||
# concepts_title: "What concepts are covered?"
|
concepts_title: "Que conceitos são abordados?"
|
||||||
# how_much_title: "How much does a monthly subscription cost?"
|
how_much_title: "Quanto custa uma assinatura mensal?"
|
||||||
# how_much_1: "A"
|
how_much_1: "Uma"
|
||||||
how_much_2: "assinatura mensal"
|
how_much_2: "assinatura mensal"
|
||||||
# how_much_3: "costs $9.99, and can be cancelled anytime."
|
how_much_3: "custa $9.99, e pode ser cancelada a qualquer momento."
|
||||||
# how_much_4: "Additionally, we provide discounts for larger groups:"
|
how_much_4: "Além disso, nós provemos descontos para grupos maiores:"
|
||||||
# how_much_5: "We accept discounted one-time purchases and yearly subscription purchases for groups, such as a class or school. Please contact"
|
# how_much_5: "We accept discounted one-time purchases and yearly subscription purchases for groups, such as a class or school. Please contact"
|
||||||
how_much_6: "para mais detalhes."
|
how_much_6: "para mais detalhes."
|
||||||
more_info_title: "Onde eu posso encontrar mais informações?"
|
more_info_title: "Onde eu posso encontrar mais informações?"
|
||||||
# more_info_1: "Our"
|
more_info_1: "Nosso"
|
||||||
# more_info_2: "teachers forum"
|
more_info_2: "fórum de professores"
|
||||||
# more_info_3: "is a good place to connect with fellow educators who are using CodeCombat."
|
more_info_3: "é um bom lugar para se conectar com ótimos educadores que utilizam o CodeCombat."
|
||||||
sys_requirements_title: "Requisitos de Sistema"
|
sys_requirements_title: "Requisitos de Sistema"
|
||||||
sys_requirements_1: "Pelo motivo de CodeCombat ser um jogo, é mais intenso para ser processado em computadores do que tutoriais em vídeo ou escritos. Estamos otimizando para que rode rapidamente em todos navegadores modernos e também em computadores antigos, assim todos podem jogar. Sendo assim, aqui estão as nossas sugestões para tirar o máximo proveito da experiência de CodeCombat:" # {change}
|
sys_requirements_1: "Pelo motivo de CodeCombat ser um jogo, é mais intenso para ser processado em computadores do que tutoriais em vídeo ou escritos. Estamos otimizando para que rode rapidamente em todos navegadores modernos e também em computadores antigos, assim todos podem jogar. Sendo assim, aqui estão as nossas sugestões para tirar o máximo proveito da experiência de CodeCombat:" # {change}
|
||||||
sys_requirements_2: "Use versões novas dos navegadores Chrome ou Firefox." # {change}
|
sys_requirements_2: "Use versões novas dos navegadores Chrome ou Firefox." # {change}
|
||||||
|
|
||||||
teachers_survey:
|
teachers_survey:
|
||||||
# title: "Teacher Survey"
|
title: "Pesquisa de professor"
|
||||||
# must_be_logged: "You must be logged in first. Please create an account or log in from the menu above."
|
must_be_logged: "Você precisa fazer login primeiro. Por favor, crie uma conta ou faça login no menu acima."
|
||||||
# retrieving: "Retrieving information..."
|
retrieving: "Recuperando informações..."
|
||||||
# being_reviewed_1: "Your application for a free trial subscription is being"
|
being_reviewed_1: "Sua solicitação de teste grátis de assinatura está sendo"
|
||||||
# being_reviewed_2: "reviewed."
|
being_reviewed_2: "revisada."
|
||||||
# approved_1: "Your application for a free trial subscription was"
|
approved_1: "Sua solicitação de teste grátis de assinatura foi"
|
||||||
# approved_2: "approved."
|
approved_2: "aprovada."
|
||||||
# approved_3: "Further instructions have been sent to"
|
approved_3: "Mais intruções foram enviadas para"
|
||||||
# denied_1: "Your application for a free trial subscription has been"
|
denied_1: "Sua solicitação de teste grátis de assinatura foi"
|
||||||
# denied_2: "denied."
|
denied_2: "negada."
|
||||||
# contact_1: "Please contact"
|
contact_1: "Por favor, entre em contato"
|
||||||
# contact_2: "if you have further questions."
|
contact_2: "caso você tenha dúvidas no futuro."
|
||||||
# description_1: "We offer free subscriptions to teachers for evaluation purposes. You can find more information on our"
|
description_1: "Nós oferecemos assinaturas grátis à professores para fins de avaliação. Você pode encontrar mais informações na nossa"
|
||||||
description_2: "professores"
|
description_2: "professores"
|
||||||
description_3: "página."
|
description_3: "página."
|
||||||
# description_4: "Please fill out this quick survey and we’ll email you setup instructions."
|
description_4: "Por favor, preencha esta rápida pesquisa e nós o enviaremos as intruções de instalação por email."
|
||||||
email: "Endereço de email"
|
email: "Endereço de email"
|
||||||
school: "Nome da Escola"
|
school: "Nome da Escola"
|
||||||
location: "Nome da Cidade"
|
location: "Nome da Cidade"
|
||||||
# age_students: "How old are your students?"
|
age_students: "Qual a idade dos seus alunos?"
|
||||||
# under: "Under"
|
under: "Embaixo"
|
||||||
other: "Outro:"
|
other: "Outro:"
|
||||||
# amount_students: "How many students do you teach?"
|
amount_students: "Quantos alunos você ensina?"
|
||||||
# hear_about: "How did you hear about CodeCombat?"
|
hear_about: "Como você ouviu falar do CodeCombat?"
|
||||||
# fill_fields: "Please fill out all fields."
|
fill_fields: "Por favor, preencha todos os campos."
|
||||||
# thanks: "Thanks! We'll send you setup instructions shortly."
|
thanks: "Obrigado! Enviaremos suas instruções de instalação em breve."
|
||||||
|
|
||||||
versions:
|
versions:
|
||||||
save_version_title: "Salvar nova versão"
|
save_version_title: "Salvar nova versão"
|
||||||
|
@ -693,7 +693,7 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription:
|
||||||
cla_url: "CLA"
|
cla_url: "CLA"
|
||||||
cla_suffix: "."
|
cla_suffix: "."
|
||||||
cla_agree: "EU CONCORDO"
|
cla_agree: "EU CONCORDO"
|
||||||
# owner_approve: "An owner will need to approve it before your changes will become visible."
|
owner_approve: "Outro dono terá de aprovar isso antes de suas alterações se tornarem visíveis."
|
||||||
|
|
||||||
contact:
|
contact:
|
||||||
contact_us: "Contate-nos"
|
contact_us: "Contate-nos"
|
||||||
|
@ -948,8 +948,8 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription:
|
||||||
pop_i18n: "Popular I18N"
|
pop_i18n: "Popular I18N"
|
||||||
tasks: "Tarefas"
|
tasks: "Tarefas"
|
||||||
clear_storage: "Limpar suas alterações locais"
|
clear_storage: "Limpar suas alterações locais"
|
||||||
# add_system_title: "Add Systems to Level"
|
add_system_title: "Adicionar sistemas ao nível"
|
||||||
# done_adding: "Done Adding"
|
done_adding: "Concluir adição"
|
||||||
|
|
||||||
article:
|
article:
|
||||||
edit_btn_preview: "Prever"
|
edit_btn_preview: "Prever"
|
||||||
|
@ -1072,7 +1072,7 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription:
|
||||||
fight: "Lutem!"
|
fight: "Lutem!"
|
||||||
watch_victory: "Assista sua vitória"
|
watch_victory: "Assista sua vitória"
|
||||||
defeat_the: "Derrote"
|
defeat_the: "Derrote"
|
||||||
# tournament_started: ", started"
|
tournament_started: ", iniciado"
|
||||||
tournament_ends: "Fim do torneio"
|
tournament_ends: "Fim do torneio"
|
||||||
tournament_ended: "Torneio encerrado"
|
tournament_ended: "Torneio encerrado"
|
||||||
tournament_rules: "Regras do Torneio"
|
tournament_rules: "Regras do Torneio"
|
||||||
|
@ -1084,11 +1084,11 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription:
|
||||||
rules: "Regras"
|
rules: "Regras"
|
||||||
winners: "Vencedores"
|
winners: "Vencedores"
|
||||||
league: "Liga"
|
league: "Liga"
|
||||||
# red_ai: "Red AI" # "Red AI Wins", at end of multiplayer match playback
|
red_ai: "AI Vermelho" # "Red AI Wins", at end of multiplayer match playback
|
||||||
# blue_ai: "Blue AI"
|
blue_ai: "AI Azul"
|
||||||
# wins: "Wins" # At end of multiplayer match playback
|
wins: "Vence" # At end of multiplayer match playback
|
||||||
# humans: "Red" # Ladder page display team name
|
humans: "Vermelho" # Ladder page display team name
|
||||||
# ogres: "Blue"
|
ogres: "Azul"
|
||||||
|
|
||||||
user:
|
user:
|
||||||
stats: "Estatísticas"
|
stats: "Estatísticas"
|
||||||
|
@ -1223,21 +1223,21 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription:
|
||||||
# user_polls_record: "Poll Voting History"
|
# user_polls_record: "Poll Voting History"
|
||||||
|
|
||||||
concepts:
|
concepts:
|
||||||
# advanced_strings: "Advanced Strings"
|
advanced_strings: "Strings avançadas"
|
||||||
algorithms: "Algorítmos"
|
algorithms: "Algorítmos"
|
||||||
# arguments: "Arguments"
|
arguments: "Argumentos"
|
||||||
# arithmetic: "Arithmetic"
|
arithmetic: "Aritmética"
|
||||||
arrays: "Arrays"
|
arrays: "Arrays"
|
||||||
basic_syntax: "Sintaxe Básica"
|
basic_syntax: "Sintaxe Básica"
|
||||||
boolean_logic: "Lógica Booleana"
|
boolean_logic: "Lógica Booleana"
|
||||||
# break_statements: "Break Statements"
|
break_statements: "Comandos Break"
|
||||||
classes: "Classes"
|
classes: "Classes"
|
||||||
# continue_statements: "Continue Statements"
|
continue_statements: "Comandos continue"
|
||||||
for_loops: "Laço For"
|
for_loops: "Laço For"
|
||||||
functions: "Funções"
|
functions: "Funções"
|
||||||
# graphics: "Graphics"
|
graphics: "Gráficos"
|
||||||
# if_statements: "If Statements"
|
if_statements: "Comandos se"
|
||||||
# input_handling: "Input Handling"
|
input_handling: "Tratamento de entrada"
|
||||||
math_operations: "Operações Matemáticas"
|
math_operations: "Operações Matemáticas"
|
||||||
object_literals: "Objetos Literais"
|
object_literals: "Objetos Literais"
|
||||||
parameters: "Parâmetros"
|
parameters: "Parâmetros"
|
||||||
|
@ -1260,8 +1260,8 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription:
|
||||||
guide:
|
guide:
|
||||||
temp: "Temp"
|
temp: "Temp"
|
||||||
|
|
||||||
# temp:
|
temp:
|
||||||
# ace_of_coders_tournament: "New: play in the Ace of Coders tournament now!"
|
ace_of_coders_tournament: "Novo: jogue no torneir Era dos Programadores agora!"
|
||||||
|
|
||||||
multiplayer:
|
multiplayer:
|
||||||
multiplayer_title: "Configurações de Multijogador" # We'll be changing this around significantly soon. Until then, it's not important to translate.
|
multiplayer_title: "Configurações de Multijogador" # We'll be changing this around significantly soon. Until then, it's not important to translate.
|
||||||
|
|
14
app/models/Prepaid.coffee
Normal file
14
app/models/Prepaid.coffee
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
CocoModel = require './CocoModel'
|
||||||
|
schema = require 'schemas/models/prepaid.schema'
|
||||||
|
|
||||||
|
module.exports = class Prepaid extends CocoModel
|
||||||
|
@className: "Prepaid"
|
||||||
|
urlRoot: '/db/prepaid'
|
||||||
|
|
||||||
|
openSpots: ->
|
||||||
|
@get('maxRedeemers') - @get('redeemers')?.length
|
||||||
|
|
||||||
|
userHasRedeemed: (userID) ->
|
||||||
|
for redeemer in @get('redeemers')
|
||||||
|
return redeemer.date if redeemer.userID is userID
|
||||||
|
return null
|
3
app/styles/account/account-prepaid-view.sass
Normal file
3
app/styles/account/account-prepaid-view.sass
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
|
||||||
|
#users, #months
|
||||||
|
max-width: 100px
|
|
@ -23,4 +23,6 @@ block content
|
||||||
a.btn.btn-lg.btn-primary(href="/account/payments", data-i18n="account.payments")
|
a.btn.btn-lg.btn-primary(href="/account/payments", data-i18n="account.payments")
|
||||||
li.list-group-item
|
li.list-group-item
|
||||||
a.btn.btn-lg.btn-primary(href="/account/subscription", data-i18n="account.subscription")
|
a.btn.btn-lg.btn-primary(href="/account/subscription", data-i18n="account.subscription")
|
||||||
|
li.list-group-item
|
||||||
|
a.btn.btn-lg.btn-primary(href="/account/prepaid") Prepaid Codes
|
||||||
|
|
||||||
|
|
19
app/templates/account/prepaid-redeem-modal.jade
Normal file
19
app/templates/account/prepaid-redeem-modal.jade
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
extends /templates/core/modal-base
|
||||||
|
|
||||||
|
block modal-header-content
|
||||||
|
h3 Prepaid Code Details (#{ppc.get('code')})
|
||||||
|
|
||||||
|
block modal-body-content
|
||||||
|
if redeemedOn
|
||||||
|
p You redeemed this code: #{redeemedOn}
|
||||||
|
else
|
||||||
|
if ppc.openSpots()
|
||||||
|
p: strong Adds #{ppc.get('properties').months} month(s) to your current subscription.
|
||||||
|
p You can redeem this code.
|
||||||
|
else
|
||||||
|
p You cannot redeem this code.
|
||||||
|
|
||||||
|
block modal-footer-content
|
||||||
|
button#close.btn.btn-primary(type="button", data-dismiss="modal") Cancel
|
||||||
|
if !redeemedOn && ppc.openSpots() > 0
|
||||||
|
button#redeem.btn.btn-primary(type="button", data-dismiss="modal") Redeem Code To My Account
|
106
app/templates/account/prepaid-view.jade
Normal file
106
app/templates/account/prepaid-view.jade
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
extends /templates/base
|
||||||
|
|
||||||
|
block content
|
||||||
|
|
||||||
|
if me.get('anonymous')
|
||||||
|
p(data-i18n="account_settings.not_logged_in") Log in or create an account to change your settings.
|
||||||
|
|
||||||
|
else
|
||||||
|
ol.breadcrumb
|
||||||
|
li
|
||||||
|
a(href="/")
|
||||||
|
span.glyphicon.glyphicon-home
|
||||||
|
li
|
||||||
|
a(href="/account", data-i18n="nav.account")
|
||||||
|
li.active(data-i18n="account.prepaid")
|
||||||
|
|
||||||
|
.row
|
||||||
|
.col-md-12
|
||||||
|
.panel.panel-default
|
||||||
|
.panel-heading
|
||||||
|
.panel-title
|
||||||
|
a(data-toggle="collapse" href="#purchasepanel")
|
||||||
|
span(data-i18n="account_prepaid.purchase_code")
|
||||||
|
.panel-collapse.collapse(class=ppcQuery ? "": "in")#purchasepanel
|
||||||
|
.panel-body
|
||||||
|
p Subscription Codes can be redeemed to add premium subscription time to one or more Code Combat accounts.
|
||||||
|
p Each Code Combat account can only redeem a particular Subscription Code once.
|
||||||
|
p Subscription Code months will be added to the end of any existing subscription on the account.
|
||||||
|
.form-horizontal
|
||||||
|
.form-group
|
||||||
|
label.control-label.col-md-2(for="users") Users
|
||||||
|
.col-md-2
|
||||||
|
input#users.form-control(name="users", type="number", value="#{purchase.users}", min=1)
|
||||||
|
.form-group
|
||||||
|
label.control-label.col-md-2(for="months") Months
|
||||||
|
.col-md-2
|
||||||
|
input#months.form-control(name="months", type="number", value="#{purchase.months}", min=1)
|
||||||
|
.form-group
|
||||||
|
label.control-label.col-md-2(data-i18n="account_prepaid.purchase_total")
|
||||||
|
.col-md-10
|
||||||
|
p.form-control-static $
|
||||||
|
span#total #{purchase.total}
|
||||||
|
button#purchase-button.btn.btn-success.pull-right(data-i18n="account_prepaid.purchase_button")
|
||||||
|
.row
|
||||||
|
.col-md-12
|
||||||
|
.panel.panel-default
|
||||||
|
.panel-heading
|
||||||
|
.panel-title
|
||||||
|
a(data-toggle="collapse" href="#redeempanel")
|
||||||
|
span(data-i18n="account_prepaid.redeem_codes")
|
||||||
|
.panel-collapse.collapse.in#redeempanel
|
||||||
|
.panel-body
|
||||||
|
p
|
||||||
|
span.spr Prepaid Code:
|
||||||
|
input.input-ppc(name="ppc", type="text", value="#{ppc}", required)
|
||||||
|
if ppcInfo && ppcInfo.length > 0
|
||||||
|
p
|
||||||
|
each info in ppcInfo
|
||||||
|
div
|
||||||
|
!= info
|
||||||
|
p
|
||||||
|
span.spr
|
||||||
|
button.btn.btn-info.btn-check-code Lookup prepaid code
|
||||||
|
span
|
||||||
|
button.btn.btn-success.btn-redeem-code Apply to your account
|
||||||
|
.row
|
||||||
|
.col-md-12
|
||||||
|
.panel.panel-default
|
||||||
|
.panel-heading
|
||||||
|
.panel-title
|
||||||
|
a(data-toggle="collapse" href="#codeslist")
|
||||||
|
span(data-i18n="account_prepaid.your_codes")
|
||||||
|
.panel-collapse.collapse.in#codeslist
|
||||||
|
.panel-body
|
||||||
|
if codes && codes.length
|
||||||
|
table.table.table-striped
|
||||||
|
tr
|
||||||
|
th
|
||||||
|
span(title="You can copy the code's link and send it to someone.") Code
|
||||||
|
span.glyphicon.glyphicon-question-sign(aria-hidden="true")
|
||||||
|
th Months
|
||||||
|
th Quantity
|
||||||
|
th Status
|
||||||
|
for code in codes.models
|
||||||
|
if code.get('type') === 'terminal_subscription'
|
||||||
|
- var owner = (code.get('creator') == me.id ? true : false)
|
||||||
|
- var properties = code.get('properties')
|
||||||
|
- var redeemers = code.get('redeemers')
|
||||||
|
if redeemers
|
||||||
|
- var redeemed = redeemers.length
|
||||||
|
else
|
||||||
|
- var redeemed = '0'
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
a(href="/account/prepaid?_ppc=#{code.get('code')}")= code.get('code')
|
||||||
|
td= properties.months || '-'
|
||||||
|
if owner
|
||||||
|
td= code.get('maxRedeemers') - redeemed
|
||||||
|
else
|
||||||
|
td -
|
||||||
|
if owner
|
||||||
|
td Purchased
|
||||||
|
else
|
||||||
|
td Redeemed
|
||||||
|
else
|
||||||
|
p No codes yet!
|
|
@ -119,6 +119,15 @@ block content
|
||||||
else
|
else
|
||||||
button.start-subscription-button.btn.btn-lg.btn-success(data-i18n="subscribe.subscribe_title") Subscribe
|
button.start-subscription-button.btn.btn-lg.btn-success(data-i18n="subscribe.subscribe_title") Subscribe
|
||||||
|
|
||||||
|
// - Prepaid Codes
|
||||||
|
.panel.panel-default
|
||||||
|
.panel-heading
|
||||||
|
h3 Prepaid Codes
|
||||||
|
.panel-body
|
||||||
|
p
|
||||||
|
span You can
|
||||||
|
a(href="/account/prepaid") purchase a prepaid code
|
||||||
|
span that can be applied to your own account or given to others.
|
||||||
//- Sponsored Subscriptions
|
//- Sponsored Subscriptions
|
||||||
|
|
||||||
.panel.panel-default
|
.panel.panel-default
|
||||||
|
|
|
@ -63,6 +63,16 @@ block content
|
||||||
if freeSubLink
|
if freeSubLink
|
||||||
input#free-sub-input(type="text", readonly, value="#{freeSubLink}")
|
input#free-sub-input(type="text", readonly, value="#{freeSubLink}")
|
||||||
|
|
||||||
|
.form-inline
|
||||||
|
.form-group
|
||||||
|
label(for="users") Users
|
||||||
|
input#users.form-control(name="users", type="number", min=1)
|
||||||
|
.form-group
|
||||||
|
label(for="months") Months
|
||||||
|
input#months.form-control(name="months", type="number", min=1)
|
||||||
|
a#terminal-create.btn.btn-default Create Terminal Subscription Code
|
||||||
|
|
||||||
|
|
||||||
hr
|
hr
|
||||||
|
|
||||||
h3 Achievements
|
h3 Achievements
|
||||||
|
|
|
@ -37,6 +37,8 @@ block header
|
||||||
a(href="/account/payments", data-i18n="account.payments")
|
a(href="/account/payments", data-i18n="account.payments")
|
||||||
li
|
li
|
||||||
a(href="/account/subscription", data-i18n="account.subscription")
|
a(href="/account/subscription", data-i18n="account.subscription")
|
||||||
|
li
|
||||||
|
a(href="/account/prepaid") Prepaid Codes
|
||||||
li
|
li
|
||||||
a#logout-button(data-i18n="login.log_out")
|
a#logout-button(data-i18n="login.log_out")
|
||||||
|
|
||||||
|
|
|
@ -2,15 +2,32 @@ extends /templates/base
|
||||||
|
|
||||||
block content
|
block content
|
||||||
|
|
||||||
//- DO NOT localize / i18n
|
|
||||||
|
|
||||||
div
|
div
|
||||||
span *UNDER CONSTRUCTION, send feedback to
|
span *UNDER CONSTRUCTION, send feedback to
|
||||||
a.spl(href='mailto:team@codecombat.com') team@codecombat.com
|
a.spl(href='mailto:team@codecombat.com') team@codecombat.com
|
||||||
div(style='border-bottom: 1px solid black;')
|
div(style='border-bottom: 1px solid black;')
|
||||||
|
|
||||||
if me.isAnonymous()
|
if (noCourseInstance || noCourseInstanceSelected) && course
|
||||||
h1 TODO: logged out
|
h1= course.get('name')
|
||||||
|
if noCourseInstance
|
||||||
|
p You are not enrolled in this course.
|
||||||
|
p
|
||||||
|
span.spr Please visit the
|
||||||
|
a.spr(href="/courses") courses
|
||||||
|
span page to enroll.
|
||||||
|
else if noCourseInstanceSelected
|
||||||
|
p Select one of your classes
|
||||||
|
.container-fluid
|
||||||
|
.row
|
||||||
|
.col-md-6
|
||||||
|
select.form-control.select-instance
|
||||||
|
each courseInstance in courseInstances
|
||||||
|
if courseInstance.get('name')
|
||||||
|
option(value="#{courseInstance.id}")= courseInstance.get('name')
|
||||||
|
else
|
||||||
|
option(value="#{courseInstance.id}") *unnamed*
|
||||||
|
.col-md-6
|
||||||
|
button.btn.btn-success.btn-select-instance Select
|
||||||
else if !course || !courseInstance
|
else if !course || !courseInstance
|
||||||
h1 Loading...
|
h1 Loading...
|
||||||
else
|
else
|
||||||
|
@ -61,21 +78,28 @@ mixin progress-summary-stats
|
||||||
td
|
td
|
||||||
if courseInstance
|
if courseInstance
|
||||||
div #{courseInstance.get('members').length}
|
div #{courseInstance.get('members').length}
|
||||||
|
if instanceStats
|
||||||
tr
|
tr
|
||||||
td Average level play time:
|
td Average level play time:
|
||||||
td TODO
|
if instanceStats.averageLevelPlaytime > 0
|
||||||
|
td= moment.duration(instanceStats.averageLevelPlaytime, "seconds").humanize()
|
||||||
|
else
|
||||||
|
td 0
|
||||||
tr
|
tr
|
||||||
td Total play time:
|
td Total play time:
|
||||||
td TODO
|
if instanceStats.totalPlayTime > 0
|
||||||
|
td= moment.duration(instanceStats.totalPlayTime, "seconds").humanize()
|
||||||
|
else
|
||||||
|
td 0
|
||||||
tr
|
tr
|
||||||
td Average levels completed:
|
td Average levels completed:
|
||||||
td TODO
|
td #{instanceStats.averageLevelsCompleted.toFixed(2)}
|
||||||
tr
|
tr
|
||||||
td Total levels completed:
|
td Total levels completed:
|
||||||
td TODO
|
td= instanceStats.totalLevelsCompleted
|
||||||
tr
|
tr
|
||||||
td Furthest level completed:
|
td Furthest level completed:
|
||||||
td TODO
|
td= instanceStats.furthestLevelCompleted.replace('Course: ', '')
|
||||||
|
|
||||||
mixin progress-summary-concepts
|
mixin progress-summary-concepts
|
||||||
h3 Concepts Covered
|
h3 Concepts Covered
|
||||||
|
@ -141,9 +165,9 @@ mixin progress-members
|
||||||
mixin progress-members-individual(memberID)
|
mixin progress-members-individual(memberID)
|
||||||
- var name = memberUserMap[memberID] ? memberUserMap[memberID].get('name') : 'Anoner'
|
- var name = memberUserMap[memberID] ? memberUserMap[memberID].get('name') : 'Anoner'
|
||||||
a(href="/user/#{memberID}")= name || 'Anoner'
|
a(href="/user/#{memberID}")= name || 'Anoner'
|
||||||
div TODO: levels completed
|
if memberStats && memberStats[memberID]
|
||||||
div TODO: total time played
|
div #{memberStats[memberID].totalLevelsCompleted} levels
|
||||||
div TODO: last played
|
div Played #{moment.duration(memberStats[memberID].totalPlayTime, "seconds").humanize()}
|
||||||
|
|
||||||
mixin progress-members-concepts(memberID)
|
mixin progress-members-concepts(memberID)
|
||||||
if course && userLevelStateMap[memberID]
|
if course && userLevelStateMap[memberID]
|
||||||
|
@ -160,11 +184,11 @@ mixin progress-members-levels-expanded(memberID)
|
||||||
- var i = 0
|
- var i = 0
|
||||||
each level, levelID in campaign.get('levels')
|
each level, levelID in campaign.get('levels')
|
||||||
if userLevelStateMap[memberID][levelID] === 'complete'
|
if userLevelStateMap[memberID][levelID] === 'complete'
|
||||||
span.progress-level-cell.progress-level-cell-complete #{i + 1}
|
span.progress-level-cell.progress-level-cell-complete(data-level-id=levelID, data-level-slug=level.slug, data-user-id=memberID) #{i + 1}
|
||||||
span.spl= level.name.replace('Course: ', '')
|
span.spl= level.name.replace('Course: ', '')
|
||||||
+progress-members-popup-completed(i, level)
|
+progress-members-popup-completed(i, level)
|
||||||
else if userLevelStateMap[memberID][levelID] === 'started'
|
else if userLevelStateMap[memberID][levelID] === 'started'
|
||||||
span.progress-level-cell.progress-level-cell-started #{i + 1} #{level.name.replace('Course: ', '')}
|
span.progress-level-cell.progress-level-cell-started(data-level-id=levelID, data-level-slug=level.slug, data-user-id=memberID) #{i + 1} #{level.name.replace('Course: ', '')}
|
||||||
+progress-members-popup-started(i, level)
|
+progress-members-popup-started(i, level)
|
||||||
else
|
else
|
||||||
span.progress-level-cell #{i + 1} #{level.name.replace('Course: ', '')}
|
span.progress-level-cell #{i + 1} #{level.name.replace('Course: ', '')}
|
||||||
|
@ -179,10 +203,10 @@ mixin progress-members-levels-condensed(memberID)
|
||||||
- var i = 0
|
- var i = 0
|
||||||
each level, levelID in campaign.get('levels')
|
each level, levelID in campaign.get('levels')
|
||||||
if userLevelStateMap[memberID][levelID] === 'complete'
|
if userLevelStateMap[memberID][levelID] === 'complete'
|
||||||
span.progress-level-cell.progress-level-cell-complete(style="width:#{levelCellWidth}%;") #{i + 1}
|
span.progress-level-cell.progress-level-cell-complete(style="width:#{levelCellWidth}%;", data-level-id=levelID, data-level-slug=level.slug, data-user-id=memberID) #{i + 1}
|
||||||
+progress-members-popup-completed(i, level)
|
+progress-members-popup-completed(i, level)
|
||||||
else if userLevelStateMap[memberID][levelID] === 'started'
|
else if userLevelStateMap[memberID][levelID] === 'started'
|
||||||
span.progress-level-cell.progress-level-cell-started(style="width:#{levelCellWidth}%;") #{i + 1}
|
span.progress-level-cell.progress-level-cell-started(style="width:#{levelCellWidth}%;", data-level-id=levelID, data-level-slug=level.slug, data-user-id=memberID) #{i + 1}
|
||||||
+progress-members-popup-started(i, level)
|
+progress-members-popup-started(i, level)
|
||||||
else
|
else
|
||||||
break
|
break
|
||||||
|
@ -191,14 +215,17 @@ mixin progress-members-levels-condensed(memberID)
|
||||||
mixin progress-members-popup-completed(i, level)
|
mixin progress-members-popup-completed(i, level)
|
||||||
.progress-popup-container
|
.progress-popup-container
|
||||||
h3 #{i + 1}. #{level.name.replace('Course: ', '')}
|
h3 #{i + 1}. #{level.name.replace('Course: ', '')}
|
||||||
p TODO: Time to solve
|
p Play time: #{moment.duration(level.playtime, "seconds").humanize()}
|
||||||
p TODO: Completed on
|
p Completed: #{moment(level.changed).format('MMMM Do YYYY, h:mm:ss a')}
|
||||||
|
if adminMode
|
||||||
strong Click to view solution.
|
strong Click to view solution.
|
||||||
|
|
||||||
mixin progress-members-popup-started(i, level)
|
mixin progress-members-popup-started(i, level)
|
||||||
.progress-popup-container
|
.progress-popup-container
|
||||||
h3 #{i + 1}. #{level.name.replace('Course: ', '')}
|
h3 #{i + 1}. #{level.name.replace('Course: ', '')}
|
||||||
p TODO: last played on
|
p Play time: #{moment.duration(level.playtime, "seconds").humanize()}
|
||||||
|
p Last played: #{moment(level.changed).format('MMMM Do YYYY, h:mm:ss a')}
|
||||||
|
if adminMode
|
||||||
strong Click to view solution.
|
strong Click to view solution.
|
||||||
|
|
||||||
mixin invite-tab
|
mixin invite-tab
|
||||||
|
|
|
@ -2,8 +2,6 @@ extends /templates/base
|
||||||
|
|
||||||
block content
|
block content
|
||||||
|
|
||||||
//- DO NOT localize / i18n
|
|
||||||
|
|
||||||
div
|
div
|
||||||
span *UNDER CONSTRUCTION, send feedback to
|
span *UNDER CONSTRUCTION, send feedback to
|
||||||
a.spl(href='mailto:team@codecombat.com') team@codecombat.com
|
a.spl(href='mailto:team@codecombat.com') team@codecombat.com
|
||||||
|
|
|
@ -2,8 +2,6 @@ extends /templates/base
|
||||||
|
|
||||||
block content
|
block content
|
||||||
|
|
||||||
//- DO NOT localize / i18n
|
|
||||||
|
|
||||||
div(style='border-bottom: 1px solid black')
|
div(style='border-bottom: 1px solid black')
|
||||||
span *UNDER CONSTRUCTION, please send feedback to
|
span *UNDER CONSTRUCTION, please send feedback to
|
||||||
a.spl(href='mailto:team@codecombat.com') team@codecombat.com
|
a.spl(href='mailto:team@codecombat.com') team@codecombat.com
|
||||||
|
@ -92,8 +90,9 @@ mixin teacher-dialog(course)
|
||||||
.container-fluid
|
.container-fluid
|
||||||
.row
|
.row
|
||||||
.col-md-8
|
.col-md-8
|
||||||
select.form-control.select-session
|
select.form-control.select-session(data-course-id="#{course.id}")
|
||||||
each inst in instances
|
each inst in instances
|
||||||
|
if inst.get('courseID') == course.id
|
||||||
if inst.get('name')
|
if inst.get('name')
|
||||||
option(value="#{inst.id}")= inst.get('name')
|
option(value="#{inst.id}")= inst.get('name')
|
||||||
else
|
else
|
||||||
|
|
27
app/views/account/PrepaidRedeemModal.coffee
Normal file
27
app/views/account/PrepaidRedeemModal.coffee
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
ModalView = require 'views/core/ModalView'
|
||||||
|
template = require 'templates/account/prepaid-redeem-modal'
|
||||||
|
{me} = require 'core/auth'
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = class PrepaidRedeemModal extends ModalView
|
||||||
|
id: 'prepaid-redeem-modal'
|
||||||
|
template: template
|
||||||
|
closeButton: true
|
||||||
|
|
||||||
|
events:
|
||||||
|
'click #redeem' : 'onRedeemClicked'
|
||||||
|
|
||||||
|
constructor: (options) ->
|
||||||
|
super options
|
||||||
|
@ppc = options.ppc
|
||||||
|
hasRedeemed = @ppc.userHasRedeemed(me.get('_id'))
|
||||||
|
@redeemedOn = new moment(hasRedeemed).calendar() if hasRedeemed
|
||||||
|
|
||||||
|
getRenderData: ->
|
||||||
|
c = super()
|
||||||
|
c.ppc = @ppc
|
||||||
|
c.redeemedOn = @redeemedOn if @redeemedOn
|
||||||
|
c
|
||||||
|
|
||||||
|
onRedeemClicked: ->
|
||||||
|
@trigger 'confirm-redeem'
|
238
app/views/account/PrepaidView.coffee
Normal file
238
app/views/account/PrepaidView.coffee
Normal file
|
@ -0,0 +1,238 @@
|
||||||
|
RootView = require 'views/core/RootView'
|
||||||
|
template = require 'templates/account/prepaid-view'
|
||||||
|
stripeHandler = require 'core/services/stripe'
|
||||||
|
{getPrepaidCodeAmount} = require '../../core/utils'
|
||||||
|
CocoCollection = require 'collections/CocoCollection'
|
||||||
|
Prepaid = require '../../models/Prepaid'
|
||||||
|
utils = require 'core/utils'
|
||||||
|
RedeemModal = require 'views/account/PrepaidRedeemModal'
|
||||||
|
forms = require 'core/forms'
|
||||||
|
|
||||||
|
# TODO: remove redeem code modal
|
||||||
|
|
||||||
|
module.exports = class PrepaidView extends RootView
|
||||||
|
id: 'prepaid-view'
|
||||||
|
template: template
|
||||||
|
className: 'container-fluid'
|
||||||
|
|
||||||
|
events:
|
||||||
|
'change #users': 'onUsersChanged'
|
||||||
|
'change #months': 'onMonthsChanged'
|
||||||
|
'click #purchase-button': 'onPurchaseClicked'
|
||||||
|
'click #redeem-button': 'onRedeemClicked'
|
||||||
|
'click .btn-check-code': 'onClickCheckCode'
|
||||||
|
'click .btn-redeem-code': 'onClickRedeemCode'
|
||||||
|
|
||||||
|
subscriptions:
|
||||||
|
'stripe:received-token': 'onStripeReceivedToken'
|
||||||
|
|
||||||
|
baseAmount: 9.99
|
||||||
|
|
||||||
|
constructor: (options) ->
|
||||||
|
super(options)
|
||||||
|
@purchase =
|
||||||
|
total: @baseAmount
|
||||||
|
users: 3
|
||||||
|
months: 3
|
||||||
|
@updateTotal()
|
||||||
|
|
||||||
|
@codes = new CocoCollection([], { url: '/db/user/'+me.id+'/prepaid_codes', model: Prepaid })
|
||||||
|
@codes.on 'add', (code) =>
|
||||||
|
@render?()
|
||||||
|
@codes.on 'sync', (code) =>
|
||||||
|
@render?()
|
||||||
|
@supermodel.loadCollection(@codes, 'prepaid', {cache: false})
|
||||||
|
|
||||||
|
@ppc = utils.getQueryVariable('_ppc') ? ''
|
||||||
|
unless _.isEmpty(@ppc)
|
||||||
|
@ppcQuery = true
|
||||||
|
@loadPrepaid(@ppc)
|
||||||
|
|
||||||
|
getRenderData: ->
|
||||||
|
c = super()
|
||||||
|
c.purchase = @purchase
|
||||||
|
c.codes = @codes
|
||||||
|
c.ppc = @ppc
|
||||||
|
c.ppcInfo = @ppcInfo ? []
|
||||||
|
c.ppcQuery = @ppcQuery ? false
|
||||||
|
c
|
||||||
|
|
||||||
|
afterRender: ->
|
||||||
|
super()
|
||||||
|
@$el.find("span[title]").tooltip()
|
||||||
|
|
||||||
|
statusMessage: (message, type='alert') ->
|
||||||
|
noty text: message, layout: 'topCenter', type: type, killer: false, timeout: 5000, dismissQueue: true, maxVisible: 3
|
||||||
|
|
||||||
|
updateTotal: ->
|
||||||
|
@purchase.total = getPrepaidCodeAmount(@baseAmount, @purchase.users, @purchase.months)
|
||||||
|
@renderSelectors("#total", "#users", "#months")
|
||||||
|
|
||||||
|
# Form Input Callbacks
|
||||||
|
onUsersChanged: (e) ->
|
||||||
|
newAmount = $(e.target).val()
|
||||||
|
newAmount = 1 if newAmount < 1
|
||||||
|
@purchase.users = newAmount
|
||||||
|
el = $('#purchasepanel')
|
||||||
|
if newAmount < 3 and @purchase.months < 3
|
||||||
|
message = "Either Users or Months must be greater than 2"
|
||||||
|
err = [message: message, property: 'users', formatted: true]
|
||||||
|
forms.clearFormAlerts(el)
|
||||||
|
forms.applyErrorsToForm(el, err)
|
||||||
|
else
|
||||||
|
forms.clearFormAlerts(el)
|
||||||
|
|
||||||
|
@updateTotal()
|
||||||
|
|
||||||
|
onMonthsChanged: (e) ->
|
||||||
|
newAmount = $(e.target).val()
|
||||||
|
newAmount = 1 if newAmount < 1
|
||||||
|
@purchase.months = newAmount
|
||||||
|
el = $('#purchasepanel')
|
||||||
|
if newAmount < 3 and @purchase.users < 3
|
||||||
|
message = "Either Users or Months must be greater than 2"
|
||||||
|
err = [message: message, property: 'months', formatted: true]
|
||||||
|
forms.clearFormAlerts(el)
|
||||||
|
forms.applyErrorsToForm(el, err)
|
||||||
|
else
|
||||||
|
forms.clearFormAlerts(el)
|
||||||
|
|
||||||
|
@updateTotal()
|
||||||
|
|
||||||
|
onPurchaseClicked: (e) ->
|
||||||
|
return unless $("#users").val() >= 3 or $("#months").val() >= 3
|
||||||
|
@purchaseTimestamp = new Date().getTime()
|
||||||
|
@stripeAmount = @purchase.total * 100
|
||||||
|
@description = "Prepaid Code for " + @purchase.users + " users / " + @purchase.months + " months"
|
||||||
|
|
||||||
|
stripeHandler.open
|
||||||
|
amount: @stripeAmount
|
||||||
|
description: @description
|
||||||
|
bitcoin: true
|
||||||
|
alipay: if me.get('chinaVersion') or (me.get('preferredLanguage') or 'en-US')[...2] is 'zh' then true else 'auto'
|
||||||
|
|
||||||
|
onRedeemClicked: (e) ->
|
||||||
|
@ppc = $('#ppc').val()
|
||||||
|
|
||||||
|
unless @ppc
|
||||||
|
@statusMessage "You must enter a code.", "error"
|
||||||
|
return
|
||||||
|
options =
|
||||||
|
url: '/db/prepaid/-/code/'+ @ppc
|
||||||
|
method: 'GET'
|
||||||
|
|
||||||
|
options.success = (model, res, options) =>
|
||||||
|
redeemModal = new RedeemModal ppc: model
|
||||||
|
redeemModal.on 'confirm-redeem', @confirmRedeem
|
||||||
|
@openModalView redeemModal
|
||||||
|
|
||||||
|
options.error = (model, res, options) =>
|
||||||
|
console.warn 'Error getting Prepaid Code'
|
||||||
|
|
||||||
|
prepaid = new Prepaid()
|
||||||
|
prepaid.fetch(options)
|
||||||
|
# @supermodel.addRequestResource('get_prepaid', options, 0).load()
|
||||||
|
|
||||||
|
|
||||||
|
confirmRedeem: =>
|
||||||
|
|
||||||
|
options =
|
||||||
|
url: '/db/subscription/-/subscribe_prepaid'
|
||||||
|
method: 'POST'
|
||||||
|
data: { ppc: @ppc }
|
||||||
|
|
||||||
|
options.error = (model, res, options, foo) =>
|
||||||
|
# console.error 'FAILED redeeming prepaid code'
|
||||||
|
msg = model.responseText ? ''
|
||||||
|
@statusMessage "Error: Could not redeem prepaid code. #{msg}", "error"
|
||||||
|
|
||||||
|
options.success = (model, res, options) =>
|
||||||
|
# console.log 'SUCCESS redeeming prepaid code'
|
||||||
|
@statusMessage "Prepaid Code Redeemed!", "success"
|
||||||
|
@supermodel.loadCollection(@codes, 'prepaid', {cache: false})
|
||||||
|
@codes.fetch()
|
||||||
|
me.fetch cache: false
|
||||||
|
|
||||||
|
@supermodel.addRequestResource('subscribe_prepaid', options, 0).load()
|
||||||
|
|
||||||
|
|
||||||
|
onStripeReceivedToken: (e) ->
|
||||||
|
# TODO: show that something is happening in the UI
|
||||||
|
options =
|
||||||
|
url: '/db/prepaid/-/purchase'
|
||||||
|
method: 'POST'
|
||||||
|
|
||||||
|
options.data =
|
||||||
|
amount: @stripeAmount
|
||||||
|
description: @description
|
||||||
|
stripe:
|
||||||
|
token: e.token.id
|
||||||
|
timestamp: @purchaseTimestamp
|
||||||
|
type: 'terminal_subscription'
|
||||||
|
maxRedeemers: @purchase.users
|
||||||
|
months: @purchase.months
|
||||||
|
|
||||||
|
options.error = (model, response, options) =>
|
||||||
|
console.error 'FAILED: Prepaid purchase', response
|
||||||
|
console.error options
|
||||||
|
@statusMessage "Error purchasing prepaid code", "error"
|
||||||
|
# Not sure when this will happen. Stripe popup seems to give appropriate error messages.
|
||||||
|
|
||||||
|
options.success = (model, response, options) =>
|
||||||
|
# console.log 'SUCCESS: Prepaid purchase', model.code
|
||||||
|
@statusMessage "Successfully purchased Prepaid Code!", "success"
|
||||||
|
@codes.add(model)
|
||||||
|
|
||||||
|
@statusMessage "Finalizing purchase...", "information"
|
||||||
|
@supermodel.addRequestResource('purchase_prepaid', options, 0).load()
|
||||||
|
|
||||||
|
loadPrepaid: (ppc) ->
|
||||||
|
return unless ppc
|
||||||
|
options =
|
||||||
|
cache: false
|
||||||
|
method: 'GET'
|
||||||
|
url: "/db/prepaid/-/code/#{ppc}"
|
||||||
|
|
||||||
|
options.success = (model, res, options) =>
|
||||||
|
@ppcInfo = []
|
||||||
|
if model.get('type') is 'terminal_subscription'
|
||||||
|
months = model.get('properties')?.months ? 0
|
||||||
|
maxRedeemers = model.get('maxRedeemers') ? 0
|
||||||
|
redeemers = model.get('redeemers') ? []
|
||||||
|
unlocksLeft = maxRedeemers - redeemers.length
|
||||||
|
@ppcInfo.push "This prepaid code adds <strong>#{months} months of subscription</strong> to your account."
|
||||||
|
@ppcInfo.push "It can be used <strong>#{unlocksLeft} more</strong> times."
|
||||||
|
# TODO: user needs to know they can't apply it more than once to their account
|
||||||
|
else
|
||||||
|
@ppcInfo.push "Type: #{model.get('type')}"
|
||||||
|
@render?()
|
||||||
|
options.error = (model, res, options) =>
|
||||||
|
@statusMessage "Unable to retrieve code.", "error"
|
||||||
|
|
||||||
|
@prepaid = new Prepaid()
|
||||||
|
@prepaid.fetch(options)
|
||||||
|
|
||||||
|
onClickCheckCode: (e) ->
|
||||||
|
@ppc = $('.input-ppc').val()
|
||||||
|
unless @ppc
|
||||||
|
@statusMessage "You must enter a code.", "error"
|
||||||
|
return
|
||||||
|
@ppcInfo = []
|
||||||
|
@render?()
|
||||||
|
@loadPrepaid(@ppc)
|
||||||
|
|
||||||
|
onClickRedeemCode: (e) ->
|
||||||
|
@ppc = $('.input-ppc').val()
|
||||||
|
options =
|
||||||
|
url: '/db/subscription/-/subscribe_prepaid'
|
||||||
|
method: 'POST'
|
||||||
|
data: { ppc: @ppc }
|
||||||
|
options.error = (model, res, options, foo) =>
|
||||||
|
msg = model.responseText ? ''
|
||||||
|
@statusMessage "Error: Could not redeem prepaid code. #{msg}", "error"
|
||||||
|
options.success = (model, res, options) =>
|
||||||
|
@statusMessage "Prepaid applied to your account!", "success"
|
||||||
|
@codes.fetch cache: false
|
||||||
|
me.fetch cache: false
|
||||||
|
@loadPrepaid(@ppc)
|
||||||
|
@supermodel.addRequestResource('subscribe_prepaid', options, 0).load()
|
|
@ -15,6 +15,7 @@ module.exports = class MainAdminView extends RootView
|
||||||
'click #increment-button': 'incrementUserAttribute'
|
'click #increment-button': 'incrementUserAttribute'
|
||||||
'click #user-search-result': 'onClickUserSearchResult'
|
'click #user-search-result': 'onClickUserSearchResult'
|
||||||
'click #create-free-sub-btn': 'onClickFreeSubLink'
|
'click #create-free-sub-btn': 'onClickFreeSubLink'
|
||||||
|
'click #terminal-create': 'onClickTerminalSubLink'
|
||||||
|
|
||||||
getRenderData: ->
|
getRenderData: ->
|
||||||
context = super()
|
context = super()
|
||||||
|
@ -89,3 +90,27 @@ module.exports = class MainAdminView extends RootView
|
||||||
options.error = (model, response, options) =>
|
options.error = (model, response, options) =>
|
||||||
console.error 'Failed to create prepaid', response
|
console.error 'Failed to create prepaid', response
|
||||||
@supermodel.addRequestResource('create_prepaid', options, 0).load()
|
@supermodel.addRequestResource('create_prepaid', options, 0).load()
|
||||||
|
|
||||||
|
onClickTerminalSubLink: (e) =>
|
||||||
|
@freeSubLink = ''
|
||||||
|
return unless me.isAdmin()
|
||||||
|
|
||||||
|
options =
|
||||||
|
url: '/db/prepaid/-/create'
|
||||||
|
method: 'POST'
|
||||||
|
data:
|
||||||
|
type: 'terminal_subscription'
|
||||||
|
maxRedeemers: parseInt($("#users").val())
|
||||||
|
months: parseInt($("#months").val())
|
||||||
|
|
||||||
|
options.success = (model, response, options) =>
|
||||||
|
# TODO: Don't hardcode domain.
|
||||||
|
if application.isProduction()
|
||||||
|
@freeSubLink = "https://codecombat.com/account/prepaid?_ppc=#{model.code}"
|
||||||
|
else
|
||||||
|
@freeSubLink = "http://localhost:3000/account/prepaid?_ppc=#{model.code}"
|
||||||
|
@render?()
|
||||||
|
options.error = (model, response, options) =>
|
||||||
|
console.error 'Failed to create prepaid', response
|
||||||
|
@supermodel.addRequestResource('create_prepaid', options, 0).load()
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,6 @@ module.exports = class SubscribeModal extends ModalView
|
||||||
popoverTitle = $.i18n.t 'subscribe.parent_email_title'
|
popoverTitle = $.i18n.t 'subscribe.parent_email_title'
|
||||||
popoverTitle += '<button type="button" class="close" onclick="$('.parent-button').popover('hide');">×</button>'
|
popoverTitle += '<button type="button" class="close" onclick="$('.parent-button').popover('hide');">×</button>'
|
||||||
popoverContent = ->
|
popoverContent = ->
|
||||||
console.log 'found html', $('.parent-button-popover-content').html()
|
|
||||||
$('.parent-button-popover-content').html()
|
$('.parent-button-popover-content').html()
|
||||||
@$el.find('.parent-button').popover(
|
@$el.find('.parent-button').popover(
|
||||||
animation: true
|
animation: true
|
||||||
|
|
|
@ -8,10 +8,6 @@ template = require 'templates/courses/course-details'
|
||||||
User = require 'models/User'
|
User = require 'models/User'
|
||||||
utils = require 'core/utils'
|
utils = require 'core/utils'
|
||||||
|
|
||||||
# TODO: logged out experience
|
|
||||||
# TODO: no course instances
|
|
||||||
# TODO: no course instance selected
|
|
||||||
|
|
||||||
module.exports = class CourseDetailsView extends RootView
|
module.exports = class CourseDetailsView extends RootView
|
||||||
id: 'course-details-view'
|
id: 'course-details-view'
|
||||||
template: template
|
template: template
|
||||||
|
@ -20,20 +16,25 @@ module.exports = class CourseDetailsView extends RootView
|
||||||
'change .progress-expand-checkbox': 'onCheckExpandedProgress'
|
'change .progress-expand-checkbox': 'onCheckExpandedProgress'
|
||||||
'click .btn-play-level': 'onClickPlayLevel'
|
'click .btn-play-level': 'onClickPlayLevel'
|
||||||
'click .btn-save-settings': 'onClickSaveSettings'
|
'click .btn-save-settings': 'onClickSaveSettings'
|
||||||
|
'click .btn-select-instance': 'onClickSelectInstance'
|
||||||
'click .progress-member-header': 'onClickMemberHeader'
|
'click .progress-member-header': 'onClickMemberHeader'
|
||||||
'click .progress-header': 'onClickProgressHeader'
|
'click .progress-header': 'onClickProgressHeader'
|
||||||
|
'click .progress-level-cell': 'onClickProgressLevelCell'
|
||||||
'mouseenter .progress-level-cell': 'onMouseEnterPoint'
|
'mouseenter .progress-level-cell': 'onMouseEnterPoint'
|
||||||
'mouseleave .progress-level-cell': 'onMouseLeavePoint'
|
'mouseleave .progress-level-cell': 'onMouseLeavePoint'
|
||||||
|
|
||||||
constructor: (options, @courseID) ->
|
constructor: (options, @courseID, @courseInstanceID) ->
|
||||||
super options
|
super options
|
||||||
@courseInstanceID = utils.getQueryVariable('ciid', false) or options.courseInstanceID
|
@courseID ?= options.courseID
|
||||||
|
@courseInstanceID ?= options.courseInstanceID
|
||||||
@adminMode = me.isAdmin()
|
@adminMode = me.isAdmin()
|
||||||
@memberSort = 'nameAsc'
|
@memberSort = 'nameAsc'
|
||||||
unless me.isAnonymous()
|
@course = @supermodel.getModel(Course, @courseID) or new Course _id: @courseID
|
||||||
@course = new Course _id: @courseID
|
|
||||||
@listenTo @course, 'sync', @onCourseSync
|
@listenTo @course, 'sync', @onCourseSync
|
||||||
@supermodel.loadModel @course, 'course', cache: false
|
if @course.loaded
|
||||||
|
@onCourseSync()
|
||||||
|
else
|
||||||
|
@supermodel.loadModel @course, 'course'
|
||||||
|
|
||||||
getRenderData: ->
|
getRenderData: ->
|
||||||
context = super()
|
context = super()
|
||||||
|
@ -42,9 +43,14 @@ module.exports = class CourseDetailsView extends RootView
|
||||||
context.conceptsCompleted = @conceptsCompleted ? {}
|
context.conceptsCompleted = @conceptsCompleted ? {}
|
||||||
context.course = @course if @course?.loaded
|
context.course = @course if @course?.loaded
|
||||||
context.courseInstance = @courseInstance if @courseInstance?.loaded
|
context.courseInstance = @courseInstance if @courseInstance?.loaded
|
||||||
|
context.courseInstances = @courseInstances?.models ? []
|
||||||
|
context.instanceStats = @instanceStats
|
||||||
context.levelConceptMap = @levelConceptMap ? {}
|
context.levelConceptMap = @levelConceptMap ? {}
|
||||||
context.memberSort = @memberSort
|
context.memberSort = @memberSort
|
||||||
|
context.memberStats = @memberStats
|
||||||
context.memberUserMap = @memberUserMap ? {}
|
context.memberUserMap = @memberUserMap ? {}
|
||||||
|
context.noCourseInstance = @noCourseInstance
|
||||||
|
context.noCourseInstanceSelected = @noCourseInstanceSelected
|
||||||
context.showExpandedProgress = @showExpandedProgress
|
context.showExpandedProgress = @showExpandedProgress
|
||||||
context.sortedMembers = @sortedMembers ? []
|
context.sortedMembers = @sortedMembers ? []
|
||||||
context.userConceptStateMap = @userConceptStateMap ? {}
|
context.userConceptStateMap = @userConceptStateMap ? {}
|
||||||
|
@ -53,10 +59,18 @@ module.exports = class CourseDetailsView extends RootView
|
||||||
|
|
||||||
onCourseSync: ->
|
onCourseSync: ->
|
||||||
# console.log 'onCourseSync'
|
# console.log 'onCourseSync'
|
||||||
|
if me.isAnonymous()
|
||||||
|
@noCourseInstance = true
|
||||||
|
@render?()
|
||||||
|
return
|
||||||
return if @campaign?
|
return if @campaign?
|
||||||
@campaign = new Campaign _id: @course.get('campaignID')
|
campaignID = @course.get('campaignID')
|
||||||
|
@campaign = @supermodel.getModel(Campaign, campaignID) or new Campaign _id: campaignID
|
||||||
@listenTo @campaign, 'sync', @onCampaignSync
|
@listenTo @campaign, 'sync', @onCampaignSync
|
||||||
@supermodel.loadModel @campaign, 'campaign', cache: false
|
if @campaign.loaded
|
||||||
|
@onCampaignSync()
|
||||||
|
else
|
||||||
|
@supermodel.loadModel @campaign, 'campaign'
|
||||||
@render?()
|
@render?()
|
||||||
|
|
||||||
onCampaignSync: ->
|
onCampaignSync: ->
|
||||||
|
@ -77,19 +91,27 @@ module.exports = class CourseDetailsView extends RootView
|
||||||
loadCourseInstance: (courseInstanceID) ->
|
loadCourseInstance: (courseInstanceID) ->
|
||||||
# console.log 'loadCourseInstance'
|
# console.log 'loadCourseInstance'
|
||||||
return if @courseInstance?
|
return if @courseInstance?
|
||||||
@courseInstance = new CourseInstance _id: courseInstanceID
|
@courseInstanceID = courseInstanceID
|
||||||
|
@courseInstance = @supermodel.getModel(CourseInstance, @courseInstanceID) or new CourseInstance _id: @courseInstanceID
|
||||||
@listenTo @courseInstance, 'sync', @onCourseInstanceSync
|
@listenTo @courseInstance, 'sync', @onCourseInstanceSync
|
||||||
@supermodel.loadModel @courseInstance, 'course_instance', cache: false
|
if @courseInstance.loaded
|
||||||
|
@onCourseInstanceSync()
|
||||||
|
else
|
||||||
|
@courseInstance = @supermodel.loadModel(@courseInstance, 'course_instance').model
|
||||||
|
|
||||||
onCourseInstancesSync: ->
|
onCourseInstancesSync: ->
|
||||||
# console.log 'onCourseInstancesSync'
|
# console.log 'onCourseInstancesSync'
|
||||||
if @courseInstances.models.length is 1
|
if @courseInstances.models.length is 1
|
||||||
@loadCourseInstance(@courseInstances.models[0].id)
|
@loadCourseInstance(@courseInstances.models[0].id)
|
||||||
else if @courseInstances.models.length > 0
|
else
|
||||||
@loadCourseInstance(@courseInstances.models[0].id)
|
if @courseInstances.models.length is 0
|
||||||
|
@noCourseInstance = true
|
||||||
|
else
|
||||||
|
@noCourseInstanceSelected = true
|
||||||
|
@render?()
|
||||||
|
|
||||||
onCourseInstanceSync: ->
|
onCourseInstanceSync: ->
|
||||||
console.log 'onCourseInstanceSync', @courseInstance.get('description')
|
# console.log 'onCourseInstanceSync'
|
||||||
@adminMode = true if @courseInstance.get('ownerID') is me.id
|
@adminMode = true if @courseInstance.get('ownerID') is me.id
|
||||||
@levelSessions = new CocoCollection([], { url: "/db/course_instance/#{@courseInstance.id}/level_sessions", model: LevelSession, comparator:'_id' })
|
@levelSessions = new CocoCollection([], { url: "/db/course_instance/#{@courseInstance.id}/level_sessions", model: LevelSession, comparator:'_id' })
|
||||||
@listenToOnce @levelSessions, 'sync', @onLevelSessionsSync
|
@listenToOnce @levelSessions, 'sync', @onLevelSessionsSync
|
||||||
|
@ -101,17 +123,40 @@ module.exports = class CourseDetailsView extends RootView
|
||||||
|
|
||||||
onLevelSessionsSync: ->
|
onLevelSessionsSync: ->
|
||||||
# console.log 'onLevelSessionsSync'
|
# console.log 'onLevelSessionsSync'
|
||||||
|
@instanceStats = averageLevelsCompleted: 0, furthestLevelCompleted: '', totalLevelsCompleted: 0, totalPlayTime: 0
|
||||||
|
@memberStats = {}
|
||||||
@userConceptStateMap = {}
|
@userConceptStateMap = {}
|
||||||
|
@userLevelSessionMap = {}
|
||||||
@userLevelStateMap = {}
|
@userLevelStateMap = {}
|
||||||
|
levelStateMap = {}
|
||||||
for levelSession in @levelSessions.models
|
for levelSession in @levelSessions.models
|
||||||
userID = levelSession.get('creator')
|
userID = levelSession.get('creator')
|
||||||
levelID = levelSession.get('level').original
|
levelID = levelSession.get('level').original
|
||||||
@userConceptStateMap[userID] ?= {}
|
|
||||||
@userLevelStateMap[userID] ?= {}
|
|
||||||
state = if levelSession.get('state')?.complete then 'complete' else 'started'
|
state = if levelSession.get('state')?.complete then 'complete' else 'started'
|
||||||
@userLevelStateMap[userID][levelID] = state
|
levelStateMap[levelID] = state
|
||||||
|
|
||||||
|
@instanceStats.totalLevelsCompleted++ if state is 'complete'
|
||||||
|
@instanceStats.totalPlayTime += levelSession.get('playtime')
|
||||||
|
|
||||||
|
@memberStats[userID] ?= totalLevelsCompleted: 0, totalPlayTime: 0
|
||||||
|
@memberStats[userID].totalLevelsCompleted++ if state is 'complete'
|
||||||
|
@memberStats[userID].totalPlayTime += levelSession.get('playtime')
|
||||||
|
|
||||||
|
@userConceptStateMap[userID] ?= {}
|
||||||
for concept of @levelConceptMap[levelID]
|
for concept of @levelConceptMap[levelID]
|
||||||
@userConceptStateMap[userID][concept] = state
|
@userConceptStateMap[userID][concept] = state
|
||||||
|
|
||||||
|
@userLevelSessionMap[userID] ?= {}
|
||||||
|
@userLevelSessionMap[userID][levelID] = levelSession
|
||||||
|
|
||||||
|
@userLevelStateMap[userID] ?= {}
|
||||||
|
@userLevelStateMap[userID][levelID] = state
|
||||||
|
|
||||||
|
if @courseInstance.get('members').length > 0
|
||||||
|
@instanceStats.averageLevelsCompleted = @instanceStats.totalLevelsCompleted / @courseInstance.get('members').length
|
||||||
|
for levelID, level of @campaign.get('levels')
|
||||||
|
@instanceStats.furthestLevelCompleted = level.name if levelStateMap[levelID] is 'complete'
|
||||||
|
|
||||||
@conceptsCompleted = {}
|
@conceptsCompleted = {}
|
||||||
for userID, conceptStateMap of @userConceptStateMap
|
for userID, conceptStateMap of @userConceptStateMap
|
||||||
for concept, state of conceptStateMap
|
for concept, state of conceptStateMap
|
||||||
|
@ -148,7 +193,7 @@ module.exports = class CourseDetailsView extends RootView
|
||||||
Backbone.Mediator.publish 'router:navigate', {
|
Backbone.Mediator.publish 'router:navigate', {
|
||||||
route: "/play/level/#{levelSlug}"
|
route: "/play/level/#{levelSlug}"
|
||||||
viewClass: 'views/play/level/PlayLevelView'
|
viewClass: 'views/play/level/PlayLevelView'
|
||||||
viewArgs: [{}, levelSlug]
|
viewArgs: [{courseID: @courseID, courseInstanceID: @courseInstanceID}, levelSlug]
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickSaveSettings: (e) ->
|
onClickSaveSettings: (e) ->
|
||||||
|
@ -161,9 +206,29 @@ module.exports = class CourseDetailsView extends RootView
|
||||||
@courseInstance.patch()
|
@courseInstance.patch()
|
||||||
$('#settingsModal').modal('hide')
|
$('#settingsModal').modal('hide')
|
||||||
|
|
||||||
|
onClickSelectInstance: (e) ->
|
||||||
|
courseInstanceID = $('.select-instance').val()
|
||||||
|
@noCourseInstanceSelected = false
|
||||||
|
@loadCourseInstance(courseInstanceID)
|
||||||
|
|
||||||
|
onClickProgressLevelCell: (e) ->
|
||||||
|
return unless @adminMode
|
||||||
|
levelID = $(e.currentTarget).data('level-id')
|
||||||
|
levelSlug = $(e.currentTarget).data('level-slug')
|
||||||
|
userID = $(e.currentTarget).data('user-id')
|
||||||
|
return unless levelID and levelSlug and userID
|
||||||
|
route = "/play/level/#{levelSlug}"
|
||||||
|
if @userLevelSessionMap[userID]?[levelID]
|
||||||
|
route += "?session=#{@userLevelSessionMap[userID][levelID].id}&observing=true"
|
||||||
|
Backbone.Mediator.publish 'router:navigate', {
|
||||||
|
route: route
|
||||||
|
viewClass: 'views/play/level/PlayLevelView'
|
||||||
|
viewArgs: [{}, levelSlug]
|
||||||
|
}
|
||||||
|
|
||||||
onMouseEnterPoint: (e) ->
|
onMouseEnterPoint: (e) ->
|
||||||
$('.level-popup-container').hide()
|
$('.progress-popup-container').hide()
|
||||||
container = $(e.target).find('.level-popup-container').show()
|
container = $(e.target).find('.progress-popup-container').show()
|
||||||
margin = 20
|
margin = 20
|
||||||
offset = $(e.target).offset()
|
offset = $(e.target).offset()
|
||||||
scrollTop = $('#page-container').scrollTop()
|
scrollTop = $('#page-container').scrollTop()
|
||||||
|
@ -172,7 +237,7 @@ module.exports = class CourseDetailsView extends RootView
|
||||||
container.css('top', offset.top + scrollTop - height - margin)
|
container.css('top', offset.top + scrollTop - height - margin)
|
||||||
|
|
||||||
onMouseLeavePoint: (e) ->
|
onMouseLeavePoint: (e) ->
|
||||||
$(e.target).find('.level-popup-container').hide()
|
$(e.target).find('.progress-popup-container').hide()
|
||||||
|
|
||||||
sortMembers: ->
|
sortMembers: ->
|
||||||
# Progress sort precedence: most completed concepts, most started concepts, most levels, name sort
|
# Progress sort precedence: most completed concepts, most started concepts, most levels, name sort
|
||||||
|
|
|
@ -74,10 +74,11 @@ module.exports = class CoursesView extends RootView
|
||||||
onClickEnter: (e) ->
|
onClickEnter: (e) ->
|
||||||
$('.continue-dialog').modal('hide')
|
$('.continue-dialog').modal('hide')
|
||||||
courseID = $(e.target).data('course-id')
|
courseID = $(e.target).data('course-id')
|
||||||
courseInstanceID = $('.select-session').val()
|
courseInstanceID = $(".select-session[data-course-id=#{courseID}]").val()
|
||||||
|
route = "/courses/#{courseID}/#{courseInstanceID}"
|
||||||
viewClass = require 'views/courses/CourseDetailsView'
|
viewClass = require 'views/courses/CourseDetailsView'
|
||||||
viewArgs = [{courseInstanceID:courseInstanceID}, courseID]
|
viewArgs = [{}, courseID, courseInstanceID]
|
||||||
navigationEvent = route: "/courses/#{courseID}", viewClass: viewClass, viewArgs: viewArgs
|
navigationEvent = route: route, viewClass: viewClass, viewArgs: viewArgs
|
||||||
Backbone.Mediator.publish 'router:navigate', navigationEvent
|
Backbone.Mediator.publish 'router:navigate', navigationEvent
|
||||||
|
|
||||||
onClickStudent: (e) ->
|
onClickStudent: (e) ->
|
||||||
|
|
|
@ -76,7 +76,7 @@ module.exports = class MyMatchesTabView extends CocoView
|
||||||
state = 'tie' if match.metrics.rank is opponent.metrics.rank
|
state = 'tie' if match.metrics.rank is opponent.metrics.rank
|
||||||
fresh = match.date > (new Date(new Date() - 20 * 1000)).toISOString()
|
fresh = match.date > (new Date(new Date() - 20 * 1000)).toISOString()
|
||||||
if fresh
|
if fresh
|
||||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'chat_received'
|
@playSound 'chat_received'
|
||||||
{
|
{
|
||||||
state: state
|
state: state
|
||||||
opponentName: @nameMap[opponent.userID]
|
opponentName: @nameMap[opponent.userID]
|
||||||
|
@ -86,7 +86,7 @@ module.exports = class MyMatchesTabView extends CocoView
|
||||||
stale: match.date < submitDate
|
stale: match.date < submitDate
|
||||||
fresh: fresh
|
fresh: fresh
|
||||||
codeLanguage: match.codeLanguage
|
codeLanguage: match.codeLanguage
|
||||||
simulator: JSON.stringify(match.simulator) + ' | seed ' + match.randomSeed
|
simulator: if match.simulator then JSON.stringify(match.simulator) + ' | seed ' + match.randomSeed else ''
|
||||||
}
|
}
|
||||||
|
|
||||||
for team in @teams
|
for team in @teams
|
||||||
|
@ -105,7 +105,7 @@ module.exports = class MyMatchesTabView extends CocoView
|
||||||
team.scoreHistory = scoreHistory
|
team.scoreHistory = scoreHistory
|
||||||
|
|
||||||
if not team.isRanking and @previouslyRankingTeams[team.id]
|
if not team.isRanking and @previouslyRankingTeams[team.id]
|
||||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'cast-end'
|
@playSound 'cast-end'
|
||||||
@previouslyRankingTeams[team.id] = team.isRanking
|
@previouslyRankingTeams[team.id] = team.isRanking
|
||||||
|
|
||||||
ctx
|
ctx
|
||||||
|
|
|
@ -28,6 +28,9 @@ module.exports = class ControlBarView extends CocoView
|
||||||
'click #control-bar-sign-up-button': 'onClickSignupButton'
|
'click #control-bar-sign-up-button': 'onClickSignupButton'
|
||||||
|
|
||||||
constructor: (options) ->
|
constructor: (options) ->
|
||||||
|
@courseID = options.courseID
|
||||||
|
@courseInstanceID = options.courseInstanceID
|
||||||
|
|
||||||
@worldName = options.worldName
|
@worldName = options.worldName
|
||||||
@session = options.session
|
@session = options.session
|
||||||
@level = options.level
|
@level = options.level
|
||||||
|
@ -88,13 +91,15 @@ module.exports = class ControlBarView extends CocoView
|
||||||
@homeLink += '/' + campaign
|
@homeLink += '/' + campaign
|
||||||
@homeViewArgs.push campaign
|
@homeViewArgs.push campaign
|
||||||
else if @level.get('type', true) in ['course']
|
else if @level.get('type', true) in ['course']
|
||||||
@homeLink = '/courses/mock1'
|
@homeLink = '/courses'
|
||||||
@homeViewClass = 'views/courses/mock1/CourseDetailsView'
|
@homeViewClass = 'views/courses/CoursesView'
|
||||||
#campaign = @level.get 'campaign'
|
if @courseID
|
||||||
#@homeLink += '/' + campaign
|
@homeLink += "/#{@courseID}"
|
||||||
#@homeViewArgs.push campaign
|
@homeViewArgs.push @courseID
|
||||||
@homeLink += '/' + '0'
|
@homeViewClass = 'views/courses/CourseDetailsView'
|
||||||
@homeViewArgs.push '0'
|
if @courseInstanceID
|
||||||
|
@homeLink += "/#{@courseInstanceID}"
|
||||||
|
@homeViewArgs.push @courseInstanceID
|
||||||
else
|
else
|
||||||
@homeLink = '/'
|
@homeLink = '/'
|
||||||
@homeViewClass = 'views/HomeView'
|
@homeViewClass = 'views/HomeView'
|
||||||
|
|
|
@ -54,7 +54,7 @@ module.exports = class LevelChatView extends CocoView
|
||||||
@playNoise() if e.message.authorID isnt me.id
|
@playNoise() if e.message.authorID isnt me.id
|
||||||
|
|
||||||
playNoise: ->
|
playNoise: ->
|
||||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'chat_received'
|
@playSound 'chat_received'
|
||||||
|
|
||||||
messageObjectToJQuery: (message) ->
|
messageObjectToJQuery: (message) ->
|
||||||
td = $('<td></td>')
|
td = $('<td></td>')
|
||||||
|
|
|
@ -97,7 +97,7 @@ module.exports = class LevelGoalsView extends CocoView
|
||||||
@lastSizeTweenTime = new Date()
|
@lastSizeTweenTime = new Date()
|
||||||
@updatePlacement()
|
@updatePlacement()
|
||||||
if @soundToPlayWhenPlaybackEnded
|
if @soundToPlayWhenPlaybackEnded
|
||||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: @soundToPlayWhenPlaybackEnded, volume: 1
|
@playSound @soundToPlayWhenPlaybackEnded
|
||||||
|
|
||||||
updateHeight: ->
|
updateHeight: ->
|
||||||
return if @$el.hasClass('brighter') or @$el.hasClass('secret')
|
return if @$el.hasClass('brighter') or @$el.hasClass('secret')
|
||||||
|
@ -122,7 +122,7 @@ module.exports = class LevelGoalsView extends CocoView
|
||||||
|
|
||||||
playToggleSound: (sound) =>
|
playToggleSound: (sound) =>
|
||||||
return if @destroyed
|
return if @destroyed
|
||||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: sound, volume: 1
|
@playSound sound
|
||||||
@soundTimeout = null
|
@soundTimeout = null
|
||||||
|
|
||||||
onSetLetterbox: (e) ->
|
onSetLetterbox: (e) ->
|
||||||
|
|
|
@ -71,7 +71,7 @@ module.exports = class LevelLoadingView extends CocoView
|
||||||
@startUnveiling()
|
@startUnveiling()
|
||||||
@unveil()
|
@unveil()
|
||||||
else
|
else
|
||||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'level_loaded', volume: 0.75 # old: loading_ready
|
@playSound 'level_loaded', 0.75 # old: loading_ready
|
||||||
@$el.find('.progress').hide()
|
@$el.find('.progress').hide()
|
||||||
@$el.find('.start-level-button').show()
|
@$el.find('.start-level-button').show()
|
||||||
|
|
||||||
|
@ -97,7 +97,7 @@ module.exports = class LevelLoadingView extends CocoView
|
||||||
loadingDetails.css 'top', -loadingDetails.outerHeight(true)
|
loadingDetails.css 'top', -loadingDetails.outerHeight(true)
|
||||||
@$el.find('.left-wing').css left: '-100%', backgroundPosition: 'right -400px top 0'
|
@$el.find('.left-wing').css left: '-100%', backgroundPosition: 'right -400px top 0'
|
||||||
@$el.find('.right-wing').css right: '-100%', backgroundPosition: 'left -400px top 0'
|
@$el.find('.right-wing').css right: '-100%', backgroundPosition: 'left -400px top 0'
|
||||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'loading-view-unveil', volume: 0.5
|
@playSound 'loading-view-unveil', 0.5
|
||||||
_.delay @onUnveilEnded, duration * 1000
|
_.delay @onUnveilEnded, duration * 1000
|
||||||
$('#level-footer-background').detach().appendTo('#page-container').slideDown(duration * 1000)
|
$('#level-footer-background').detach().appendTo('#page-container').slideDown(duration * 1000)
|
||||||
|
|
||||||
|
|
|
@ -108,7 +108,7 @@ module.exports = class LevelPlaybackView extends CocoView
|
||||||
@realTime = true
|
@realTime = true
|
||||||
@togglePlaybackControls false
|
@togglePlaybackControls false
|
||||||
Backbone.Mediator.publish 'playback:real-time-playback-started', {}
|
Backbone.Mediator.publish 'playback:real-time-playback-started', {}
|
||||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'real-time-playback-start', volume: 1
|
@playSound 'real-time-playback-start'
|
||||||
|
|
||||||
onRealTimeMultiplayerCast: (e) ->
|
onRealTimeMultiplayerCast: (e) ->
|
||||||
@realTime = true
|
@realTime = true
|
||||||
|
@ -160,7 +160,7 @@ module.exports = class LevelPlaybackView extends CocoView
|
||||||
ended = button.hasClass 'ended'
|
ended = button.hasClass 'ended'
|
||||||
changed = button.hasClass('playing') isnt @playing
|
changed = button.hasClass('playing') isnt @playing
|
||||||
button.toggleClass('playing', @playing and not ended).toggleClass('paused', not @playing and not ended)
|
button.toggleClass('playing', @playing and not ended).toggleClass('paused', not @playing and not ended)
|
||||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: (if @playing then 'playback-play' else 'playback-pause'), volume: 1
|
@playSound (if @playing then 'playback-play' else 'playback-pause')
|
||||||
return # don't stripe the bar
|
return # don't stripe the bar
|
||||||
bar = @$el.find '.scrubber .progress'
|
bar = @$el.find '.scrubber .progress'
|
||||||
bar.toggleClass('progress-striped', @playing and not ended).toggleClass('active', @playing and not ended)
|
bar.toggleClass('progress-striped', @playing and not ended).toggleClass('active', @playing and not ended)
|
||||||
|
@ -266,7 +266,7 @@ module.exports = class LevelPlaybackView extends CocoView
|
||||||
return unless @realTime
|
return unless @realTime
|
||||||
@realTime = false
|
@realTime = false
|
||||||
@togglePlaybackControls true
|
@togglePlaybackControls true
|
||||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'real-time-playback-end', volume: 1
|
@playSound 'real-time-playback-end'
|
||||||
|
|
||||||
onStopRealTimePlayback: (e) ->
|
onStopRealTimePlayback: (e) ->
|
||||||
Backbone.Mediator.publish 'level:set-letterbox', on: false
|
Backbone.Mediator.publish 'level:set-letterbox', on: false
|
||||||
|
@ -287,14 +287,14 @@ module.exports = class LevelPlaybackView extends CocoView
|
||||||
if ratioChange = @getScrubRatio() - oldRatio
|
if ratioChange = @getScrubRatio() - oldRatio
|
||||||
sound = "playback-scrub-slide-#{if ratioChange > 0 then 'forward' else 'back'}-#{@slideCount % 3}"
|
sound = "playback-scrub-slide-#{if ratioChange > 0 then 'forward' else 'back'}-#{@slideCount % 3}"
|
||||||
unless /back/.test sound # We don't have the back sounds in yet: http://discourse.codecombat.com/t/bug-some-mp3-lost/4830
|
unless /back/.test sound # We don't have the back sounds in yet: http://discourse.codecombat.com/t/bug-some-mp3-lost/4830
|
||||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: sound, volume: Math.min 1, Math.abs ratioChange * 50
|
@playSound sound, (Math.min 1, Math.abs ratioChange * 50)
|
||||||
|
|
||||||
start: (event, ui) =>
|
start: (event, ui) =>
|
||||||
return if @shouldIgnore()
|
return if @shouldIgnore()
|
||||||
@slideCount = 0
|
@slideCount = 0
|
||||||
@wasPlaying = @playing and not $('#play-button').hasClass('ended')
|
@wasPlaying = @playing and not $('#play-button').hasClass('ended')
|
||||||
Backbone.Mediator.publish 'level:set-playing', {playing: false}
|
Backbone.Mediator.publish 'level:set-playing', {playing: false}
|
||||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'playback-scrub-start', volume: 0.5
|
@playSound 'playback-scrub-start', 0.5
|
||||||
|
|
||||||
|
|
||||||
stop: (event, ui) =>
|
stop: (event, ui) =>
|
||||||
|
@ -307,7 +307,7 @@ module.exports = class LevelPlaybackView extends CocoView
|
||||||
Backbone.Mediator.publish 'level:set-playing', {playing: false}
|
Backbone.Mediator.publish 'level:set-playing', {playing: false}
|
||||||
@$el.find('.scrubber-handle').effect('bounce', {times: 2})
|
@$el.find('.scrubber-handle').effect('bounce', {times: 2})
|
||||||
else
|
else
|
||||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'playback-scrub-end', volume: 0.5
|
@playSound 'playback-scrub-end', 0.5
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -94,6 +94,9 @@ module.exports = class PlayLevelView extends RootView
|
||||||
console.profile?() if PROFILE_ME
|
console.profile?() if PROFILE_ME
|
||||||
super options
|
super options
|
||||||
|
|
||||||
|
@courseID = options.courseID
|
||||||
|
@courseInstanceID = options.courseInstanceID
|
||||||
|
|
||||||
@isEditorPreview = @getQueryVariable 'dev'
|
@isEditorPreview = @getQueryVariable 'dev'
|
||||||
@sessionID = @getQueryVariable 'session'
|
@sessionID = @getQueryVariable 'session'
|
||||||
@observing = @getQueryVariable 'observing'
|
@observing = @getQueryVariable 'observing'
|
||||||
|
@ -248,7 +251,7 @@ module.exports = class PlayLevelView extends RootView
|
||||||
@insertSubView new ChatView levelID: @levelID, sessionID: @session.id, session: @session
|
@insertSubView new ChatView levelID: @levelID, sessionID: @session.id, session: @session
|
||||||
@insertSubView new ProblemAlertView session: @session, level: @level, supermodel: @supermodel
|
@insertSubView new ProblemAlertView session: @session, level: @level, supermodel: @supermodel
|
||||||
@insertSubView new DuelStatsView level: @level, session: @session, otherSession: @otherSession, supermodel: @supermodel, thangs: @world.thangs if @level.get('type') in ['hero-ladder', 'course-ladder']
|
@insertSubView new DuelStatsView level: @level, session: @session, otherSession: @otherSession, supermodel: @supermodel, thangs: @world.thangs if @level.get('type') in ['hero-ladder', 'course-ladder']
|
||||||
@insertSubView @controlBar = new ControlBarView {worldName: utils.i18n(@level.attributes, 'name'), session: @session, level: @level, supermodel: @supermodel}
|
@insertSubView @controlBar = new ControlBarView {worldName: utils.i18n(@level.attributes, 'name'), session: @session, level: @level, supermodel: @supermodel, courseID: @courseID, courseInstanceID: @courseInstanceID}
|
||||||
#_.delay (=> Backbone.Mediator.publish('level:set-debug', debug: true)), 5000 if @isIPadApp() # if me.displayName() is 'Nick'
|
#_.delay (=> Backbone.Mediator.publish('level:set-debug', debug: true)), 5000 if @isIPadApp() # if me.displayName() is 'Nick'
|
||||||
|
|
||||||
initVolume: ->
|
initVolume: ->
|
||||||
|
@ -444,7 +447,7 @@ module.exports = class PlayLevelView extends RootView
|
||||||
showVictory: ->
|
showVictory: ->
|
||||||
return if @level.hasLocalChanges() # Don't award achievements when beating level changed in level editor
|
return if @level.hasLocalChanges() # Don't award achievements when beating level changed in level editor
|
||||||
@endHighlight()
|
@endHighlight()
|
||||||
options = {level: @level, supermodel: @supermodel, session: @session, hasReceivedMemoryWarning: @hasReceivedMemoryWarning}
|
options = {level: @level, supermodel: @supermodel, session: @session, hasReceivedMemoryWarning: @hasReceivedMemoryWarning, courseID: @courseID, courseInstanceID: @courseInstanceID}
|
||||||
ModalClass = if @level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop', 'course', 'course-ladder'] then HeroVictoryModal else VictoryModal
|
ModalClass = if @level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop', 'course', 'course-ladder'] then HeroVictoryModal else VictoryModal
|
||||||
victoryModal = new ModalClass(options)
|
victoryModal = new ModalClass(options)
|
||||||
@openModalView(victoryModal)
|
@openModalView(victoryModal)
|
||||||
|
|
|
@ -42,6 +42,9 @@ module.exports = class HeroVictoryModal extends ModalView
|
||||||
|
|
||||||
constructor: (options) ->
|
constructor: (options) ->
|
||||||
super(options)
|
super(options)
|
||||||
|
@courseID = options.courseID
|
||||||
|
@courseInstanceID = options.courseInstanceID
|
||||||
|
|
||||||
@session = options.session
|
@session = options.session
|
||||||
@level = options.level
|
@level = options.level
|
||||||
@thangTypes = {}
|
@thangTypes = {}
|
||||||
|
@ -58,7 +61,7 @@ module.exports = class HeroVictoryModal extends ModalView
|
||||||
@previousLevel = me.level()
|
@previousLevel = me.level()
|
||||||
else
|
else
|
||||||
@readyToContinue = true
|
@readyToContinue = true
|
||||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'victory'
|
@playSound 'victory'
|
||||||
if @level.get('type', true) is 'course' and nextLevel = @level.get('nextLevel')
|
if @level.get('type', true) is 'course' and nextLevel = @level.get('nextLevel')
|
||||||
@nextLevel = new Level().setURL "/db/level/#{nextLevel.original}/version/#{nextLevel.majorVersion}"
|
@nextLevel = new Level().setURL "/db/level/#{nextLevel.original}/version/#{nextLevel.majorVersion}"
|
||||||
@nextLevel = @supermodel.loadModel(@nextLevel, 'level').model
|
@nextLevel = @supermodel.loadModel(@nextLevel, 'level').model
|
||||||
|
@ -219,9 +222,10 @@ module.exports = class HeroVictoryModal extends ModalView
|
||||||
initializeAnimations: ->
|
initializeAnimations: ->
|
||||||
if @level.get('type', true) is 'hero'
|
if @level.get('type', true) is 'hero'
|
||||||
@updateXPBars 0
|
@updateXPBars 0
|
||||||
|
#playVictorySound = => @playSound 'victory-title-appear' # TODO: actually add this
|
||||||
@$el.find('#victory-header').delay(250).queue(->
|
@$el.find('#victory-header').delay(250).queue(->
|
||||||
$(@).removeClass('out').dequeue()
|
$(@).removeClass('out').dequeue()
|
||||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'victory-title-appear' # TODO: actually add this
|
#playVictorySound()
|
||||||
)
|
)
|
||||||
complete = _.once(_.bind(@beginSequentialAnimations, @))
|
complete = _.once(_.bind(@beginSequentialAnimations, @))
|
||||||
@animatedPanels = $()
|
@animatedPanels = $()
|
||||||
|
@ -284,7 +288,7 @@ module.exports = class HeroVictoryModal extends ModalView
|
||||||
@XPEl.text(totalXP)
|
@XPEl.text(totalXP)
|
||||||
@updateXPBars(totalXP)
|
@updateXPBars(totalXP)
|
||||||
xpTrigger = 'xp-' + (totalXP % 6) # 6 xp sounds
|
xpTrigger = 'xp-' + (totalXP % 6) # 6 xp sounds
|
||||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: xpTrigger, volume: 0.5 + ratio / 2
|
@playSound xpTrigger, (0.5 + ratio / 2)
|
||||||
@XPEl.addClass 'four-digits' if totalXP >= 1000 and @lastTotalXP < 1000
|
@XPEl.addClass 'four-digits' if totalXP >= 1000 and @lastTotalXP < 1000
|
||||||
@XPEl.addClass 'five-digits' if totalXP >= 10000 and @lastTotalXP < 10000
|
@XPEl.addClass 'five-digits' if totalXP >= 10000 and @lastTotalXP < 10000
|
||||||
@lastTotalXP = totalXP
|
@lastTotalXP = totalXP
|
||||||
|
@ -295,14 +299,14 @@ module.exports = class HeroVictoryModal extends ModalView
|
||||||
panel.textEl.text('+' + newGems)
|
panel.textEl.text('+' + newGems)
|
||||||
@gemEl.text(totalGems)
|
@gemEl.text(totalGems)
|
||||||
gemTrigger = 'gem-' + (parseInt(panel.number * ratio) % 4) # 4 gem sounds
|
gemTrigger = 'gem-' + (parseInt(panel.number * ratio) % 4) # 4 gem sounds
|
||||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: gemTrigger, volume: 0.5 + ratio / 2
|
@playSound gemTrigger, (0.5 + ratio / 2)
|
||||||
@gemEl.addClass 'four-digits' if totalGems >= 1000 and @lastTotalGems < 1000
|
@gemEl.addClass 'four-digits' if totalGems >= 1000 and @lastTotalGems < 1000
|
||||||
@gemEl.addClass 'five-digits' if totalGems >= 10000 and @lastTotalGems < 10000
|
@gemEl.addClass 'five-digits' if totalGems >= 10000 and @lastTotalGems < 10000
|
||||||
@lastTotalGems = totalGems
|
@lastTotalGems = totalGems
|
||||||
else if panel.item
|
else if panel.item
|
||||||
thangType = @thangTypes[panel.item]
|
thangType = @thangTypes[panel.item]
|
||||||
panel.textEl.text utils.i18n(thangType.attributes, 'name')
|
panel.textEl.text utils.i18n(thangType.attributes, 'name')
|
||||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'item-unlocked', volume: 1 if 0.5 < ratio < 0.6
|
@playSound 'item-unlocked' if 0.5 < ratio < 0.6
|
||||||
else if panel.hero
|
else if panel.hero
|
||||||
thangType = @thangTypes[panel.hero]
|
thangType = @thangTypes[panel.hero]
|
||||||
panel.textEl.text(thangType.get('name'))
|
panel.textEl.text(thangType.get('name'))
|
||||||
|
@ -399,10 +403,14 @@ module.exports = class HeroVictoryModal extends ModalView
|
||||||
if @level.get('type', true) is 'course' and nextLevel = @level.get('nextLevel') and not returnToCourse
|
if @level.get('type', true) is 'course' and nextLevel = @level.get('nextLevel') and not returnToCourse
|
||||||
# need to do something more complicated to load its slug
|
# need to do something more complicated to load its slug
|
||||||
console.log 'have @nextLevel', @nextLevel, 'from nextLevel', nextLevel
|
console.log 'have @nextLevel', @nextLevel, 'from nextLevel', nextLevel
|
||||||
return "/play/level/#{@nextLevel.get('slug')}"
|
link = "/play/level/#{@nextLevel.get('slug')}"
|
||||||
else if @level.get('type', true) is 'course'
|
else if @level.get('type', true) is 'course'
|
||||||
# TODO: figure out which course it is
|
link = "/courses"
|
||||||
return '/courses/mock1/0'
|
if @courseID
|
||||||
|
link += "/#{@courseID}"
|
||||||
|
if @courseInstanceID
|
||||||
|
link += "/#{@courseInstanceID}"
|
||||||
|
else
|
||||||
link = '/play'
|
link = '/play'
|
||||||
nextCampaign = @getNextLevelCampaign()
|
nextCampaign = @getNextLevelCampaign()
|
||||||
link += '/' + nextCampaign
|
link += '/' + nextCampaign
|
||||||
|
@ -418,11 +426,20 @@ module.exports = class HeroVictoryModal extends ModalView
|
||||||
_.merge options, extraOptions if extraOptions
|
_.merge options, extraOptions if extraOptions
|
||||||
if @level.get('type', true) is 'course' and @nextLevel and not options.returnToCourse
|
if @level.get('type', true) is 'course' and @nextLevel and not options.returnToCourse
|
||||||
viewClass = require 'views/play/level/PlayLevelView'
|
viewClass = require 'views/play/level/PlayLevelView'
|
||||||
|
if @courseID
|
||||||
|
options.courseID = @courseID
|
||||||
|
if @courseInstanceID
|
||||||
|
options.courseInstanceID = @courseInstanceID
|
||||||
viewArgs = [options, @nextLevel.get('slug')]
|
viewArgs = [options, @nextLevel.get('slug')]
|
||||||
else if @level.get('type', true) is 'course'
|
else if @level.get('type', true) is 'course'
|
||||||
options.studentMode = true
|
# TODO: shouldn't set viewClass and route in different places
|
||||||
viewClass = require 'views/courses/mock1/CourseDetailsView'
|
viewClass = require 'views/courses/CoursesView'
|
||||||
viewArgs = [options, '0']
|
viewArgs = [options]
|
||||||
|
if @courseID
|
||||||
|
viewClass = require 'views/courses/CourseDetailsView'
|
||||||
|
viewArgs.push @courseID
|
||||||
|
if @courseInstanceID
|
||||||
|
viewArgs.push @courseInstanceID
|
||||||
else
|
else
|
||||||
viewClass = require 'views/play/CampaignView'
|
viewClass = require 'views/play/CampaignView'
|
||||||
viewArgs = [options, @getNextLevelCampaign()]
|
viewArgs = [options, @getNextLevelCampaign()]
|
||||||
|
|
|
@ -82,7 +82,7 @@ module.exports = class VictoryModal extends ModalView
|
||||||
|
|
||||||
afterInsert: ->
|
afterInsert: ->
|
||||||
super()
|
super()
|
||||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'victory'
|
@playSound 'victory'
|
||||||
gapi?.plusone?.go? @$el[0]
|
gapi?.plusone?.go? @$el[0]
|
||||||
FB?.XFBML?.parse? @$el[0]
|
FB?.XFBML?.parse? @$el[0]
|
||||||
twttr?.widgets?.load?()
|
twttr?.widgets?.load?()
|
||||||
|
|
|
@ -59,7 +59,7 @@ module.exports = class ProblemAlertView extends CocoView
|
||||||
if @problem?
|
if @problem?
|
||||||
@$el.addClass('alert').addClass("alert-#{@problem.aetherProblem.level}").hide().fadeIn('slow')
|
@$el.addClass('alert').addClass("alert-#{@problem.aetherProblem.level}").hide().fadeIn('slow')
|
||||||
@$el.addClass('no-hint') unless @problem.aetherProblem.hint
|
@$el.addClass('no-hint') unless @problem.aetherProblem.hint
|
||||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'error_appear', volume: 1.0
|
@playSound 'error_appear'
|
||||||
|
|
||||||
onShowProblemAlert: (data) ->
|
onShowProblemAlert: (data) ->
|
||||||
return unless $('#code-area').is(":visible")
|
return unless $('#code-area').is(":visible")
|
||||||
|
@ -80,7 +80,7 @@ module.exports = class ProblemAlertView extends CocoView
|
||||||
return unless @problem?
|
return unless @problem?
|
||||||
@$el.show() unless @$el.is(":visible")
|
@$el.show() unless @$el.is(":visible")
|
||||||
@$el.addClass 'jiggling'
|
@$el.addClass 'jiggling'
|
||||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'error_appear', volume: 1.0
|
@playSound 'error_appear'
|
||||||
pauseJiggle = =>
|
pauseJiggle = =>
|
||||||
@$el?.removeClass 'jiggling'
|
@$el?.removeClass 'jiggling'
|
||||||
_.delay pauseJiggle, 1000
|
_.delay pauseJiggle, 1000
|
||||||
|
|
|
@ -83,7 +83,7 @@ module.exports = class SpellListTabEntryView extends SpellListEntryView
|
||||||
content: docFormatter.formatPopover()
|
content: docFormatter.formatPopover()
|
||||||
container: @$el.parent()
|
container: @$el.parent()
|
||||||
).on 'show.bs.popover', =>
|
).on 'show.bs.popover', =>
|
||||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: "spell-tab-entry-open", volume: 0.75
|
@playSound 'spell-tab-entry-open', 0.75
|
||||||
|
|
||||||
onMouseEnterAvatar: (e) -> # Don't call super
|
onMouseEnterAvatar: (e) -> # Don't call super
|
||||||
onMouseLeaveAvatar: (e) -> # Don't call super
|
onMouseLeaveAvatar: (e) -> # Don't call super
|
||||||
|
@ -94,7 +94,7 @@ module.exports = class SpellListTabEntryView extends SpellListEntryView
|
||||||
onDropdownClick: (e) ->
|
onDropdownClick: (e) ->
|
||||||
return unless @controlsEnabled
|
return unless @controlsEnabled
|
||||||
Backbone.Mediator.publish 'tome:toggle-spell-list', {}
|
Backbone.Mediator.publish 'tome:toggle-spell-list', {}
|
||||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'spell-list-open', volume: 1
|
@playSound 'spell-list-open'
|
||||||
|
|
||||||
onCodeReload: (e) ->
|
onCodeReload: (e) ->
|
||||||
#return unless @controlsEnabled
|
#return unless @controlsEnabled
|
||||||
|
|
|
@ -51,7 +51,7 @@ module.exports = class SpellPaletteEntryView extends CocoView
|
||||||
).on 'shown.bs.popover', =>
|
).on 'shown.bs.popover', =>
|
||||||
Backbone.Mediator.publish 'tome:palette-hovered', thang: @thang, prop: @doc.name, entry: @
|
Backbone.Mediator.publish 'tome:palette-hovered', thang: @thang, prop: @doc.name, entry: @
|
||||||
soundIndex = Math.floor(Math.random() * 4)
|
soundIndex = Math.floor(Math.random() * 4)
|
||||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: "spell-palette-entry-open-#{soundIndex}", volume: 0.75
|
@playSound "spell-palette-entry-open-#{soundIndex}", 0.75
|
||||||
popover = @$el.data('bs.popover')
|
popover = @$el.data('bs.popover')
|
||||||
popover?.$tip?.i18n()
|
popover?.$tip?.i18n()
|
||||||
codeLanguage = @options.language
|
codeLanguage = @options.language
|
||||||
|
@ -95,7 +95,7 @@ module.exports = class SpellPaletteEntryView extends CocoView
|
||||||
@$el.add('.spell-palette-popover.popover').removeClass 'pinned'
|
@$el.add('.spell-palette-popover.popover').removeClass 'pinned'
|
||||||
$('.spell-palette-popover.popover .close').remove()
|
$('.spell-palette-popover.popover .close').remove()
|
||||||
@$el.popover 'hide'
|
@$el.popover 'hide'
|
||||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'spell-palette-entry-unpin', volume: 1
|
@playSound 'spell-palette-entry-unpin'
|
||||||
else
|
else
|
||||||
@popoverPinned = true
|
@popoverPinned = true
|
||||||
@$el.popover 'show'
|
@$el.popover 'show'
|
||||||
|
@ -103,7 +103,7 @@ module.exports = class SpellPaletteEntryView extends CocoView
|
||||||
x = $('<button type="button" data-dismiss="modal" aria-hidden="true" class="close">×</button>')
|
x = $('<button type="button" data-dismiss="modal" aria-hidden="true" class="close">×</button>')
|
||||||
$('.spell-palette-popover.popover').append x
|
$('.spell-palette-popover.popover').append x
|
||||||
x.on 'click', @onClick
|
x.on 'click', @onClick
|
||||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'spell-palette-entry-pin', volume: 1
|
@playSound 'spell-palette-entry-pin'
|
||||||
Backbone.Mediator.publish 'tome:palette-pin-toggled', entry: @, pinned: @popoverPinned
|
Backbone.Mediator.publish 'tome:palette-pin-toggled', entry: @, pinned: @popoverPinned
|
||||||
|
|
||||||
onClick: (e) =>
|
onClick: (e) =>
|
||||||
|
|
|
@ -103,7 +103,7 @@ module.exports = class PollModal extends ModalView
|
||||||
if Math.random() < randomGems / 40
|
if Math.random() < randomGems / 40
|
||||||
gemTrigger = 'gem-' + (gemNoisesPlayed % 4) # 4 gem sounds
|
gemTrigger = 'gem-' + (gemNoisesPlayed % 4) # 4 gem sounds
|
||||||
++gemNoisesPlayed
|
++gemNoisesPlayed
|
||||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: gemTrigger, volume: 0.475 + i / 2000
|
@playSound gemTrigger, (0.475 + i / 2000)
|
||||||
@$randomNumber.delay 25
|
@$randomNumber.delay 25
|
||||||
@$randomGems.delay(1100).queue ->
|
@$randomGems.delay(1100).queue ->
|
||||||
utils.replaceText $(@), commentStart + randomGems
|
utils.replaceText $(@), commentStart + randomGems
|
||||||
|
|
|
@ -3,7 +3,7 @@ _ = require 'lodash'
|
||||||
_.str = require 'underscore.string'
|
_.str = require 'underscore.string'
|
||||||
sysPath = require 'path'
|
sysPath = require 'path'
|
||||||
fs = require('fs')
|
fs = require('fs')
|
||||||
commonjsHeader = fs.readFileSync('node_modules/brunch/node_modules/commonjs-require-definition/require.js', {encoding: 'utf8'})
|
commonjsHeader = require('commonjs-require-definition')
|
||||||
TRAVIS = process.env.COCO_TRAVIS_TEST
|
TRAVIS = process.env.COCO_TRAVIS_TEST
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -83,6 +83,7 @@
|
||||||
"coffee-script-brunch": "https://github.com/brunch/coffee-script-brunch/tarball/master",
|
"coffee-script-brunch": "https://github.com/brunch/coffee-script-brunch/tarball/master",
|
||||||
"coffeelint-brunch": "> 1.0 < 1.8",
|
"coffeelint-brunch": "> 1.0 < 1.8",
|
||||||
"compressible": "~1.0.1",
|
"compressible": "~1.0.1",
|
||||||
|
"commonjs-require-definition": "~0.2.0",
|
||||||
"css-brunch": "> 1.0 < 1.8",
|
"css-brunch": "> 1.0 < 1.8",
|
||||||
"jade": "0.33.x",
|
"jade": "0.33.x",
|
||||||
"jade-brunch": "> 1.0 < 1.8",
|
"jade-brunch": "> 1.0 < 1.8",
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
AnalyticsString = require '../analytics/AnalyticsString'
|
AnalyticsString = require '../analytics/AnalyticsString'
|
||||||
log = require 'winston'
|
log = require 'winston'
|
||||||
mongoose = require 'mongoose'
|
mongoose = require 'mongoose'
|
||||||
|
config = require '../../server_config'
|
||||||
|
|
||||||
module.exports =
|
module.exports =
|
||||||
isID: (id) -> _.isString(id) and id.length is 24 and id.match(/[a-f0-9]/gi)?.length is 24
|
isID: (id) -> _.isString(id) and id.length is 24 and id.match(/[a-f0-9]/gi)?.length is 24
|
||||||
|
@ -21,6 +22,9 @@ module.exports =
|
||||||
# Grabs latest subscription (e.g. in case of a resubscribe)
|
# Grabs latest subscription (e.g. in case of a resubscribe)
|
||||||
return done() unless customerID?
|
return done() unless customerID?
|
||||||
return done() unless options.subscriptionID? or options.userID?
|
return done() unless options.subscriptionID? or options.userID?
|
||||||
|
# Some prepaid tests were calling this in such a way that stripe wasn't defined.
|
||||||
|
stripe = require('stripe')(config.stripe.secretKey) unless stripe
|
||||||
|
|
||||||
subscriptionID = options.subscriptionID
|
subscriptionID = options.subscriptionID
|
||||||
userID = options.userID
|
userID = options.userID
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ User = require '../users/User'
|
||||||
{findStripeSubscription} = require '../lib/utils'
|
{findStripeSubscription} = require '../lib/utils'
|
||||||
{getSponsoredSubsAmount} = require '../../app/core/utils'
|
{getSponsoredSubsAmount} = require '../../app/core/utils'
|
||||||
StripeUtils = require '../lib/stripe_utils'
|
StripeUtils = require '../lib/stripe_utils'
|
||||||
|
moment = require 'moment'
|
||||||
|
|
||||||
recipientCouponID = 'free'
|
recipientCouponID = 'free'
|
||||||
|
|
||||||
|
@ -38,6 +39,7 @@ class SubscriptionHandler extends Handler
|
||||||
return @getStripeSubscriptions(req, res) if args[1] is 'stripe_subscriptions'
|
return @getStripeSubscriptions(req, res) if args[1] is 'stripe_subscriptions'
|
||||||
return @getSubscribers(req, res) if args[1] is 'subscribers'
|
return @getSubscribers(req, res) if args[1] is 'subscribers'
|
||||||
return @purchaseYearSale(req, res) if args[1] is 'year_sale'
|
return @purchaseYearSale(req, res) if args[1] is 'year_sale'
|
||||||
|
return @subscribeWithPrepaidCode(req, res) if args[1] is 'subscribe_prepaid'
|
||||||
super(arguments...)
|
super(arguments...)
|
||||||
|
|
||||||
getStripeEvents: (req, res) ->
|
getStripeEvents: (req, res) ->
|
||||||
|
@ -117,11 +119,7 @@ class SubscriptionHandler extends Handler
|
||||||
log.debug 'Analytics error:\n' + err
|
log.debug 'Analytics error:\n' + err
|
||||||
@sendSuccess(res, userMap)
|
@sendSuccess(res, userMap)
|
||||||
|
|
||||||
purchaseYearSale: (req, res) ->
|
cancelSubscriptionImmediately: (user, subscription, done) =>
|
||||||
return @sendForbiddenError(res) unless req.user?
|
|
||||||
return @sendForbiddenError(res) if req.user?.get('stripe')?.sponsorID
|
|
||||||
|
|
||||||
cancelSubscriptionImmediately = (user, subscription, done) =>
|
|
||||||
return done() unless user and subscription
|
return done() unless user and subscription
|
||||||
stripe.customers.cancelSubscription subscription.customer, subscription.id, (err) =>
|
stripe.customers.cancelSubscription subscription.customer, subscription.id, (err) =>
|
||||||
return done(err) if err
|
return done(err) if err
|
||||||
|
@ -134,6 +132,11 @@ class SubscriptionHandler extends Handler
|
||||||
return done(err) if err
|
return done(err) if err
|
||||||
done()
|
done()
|
||||||
|
|
||||||
|
|
||||||
|
purchaseYearSale: (req, res) ->
|
||||||
|
return @sendForbiddenError(res) unless req.user?
|
||||||
|
return @sendForbiddenError(res) if req.user?.get('stripe')?.sponsorID
|
||||||
|
|
||||||
StripeUtils.getCustomer req.user, req.body.stripe?.token, (err, customer) =>
|
StripeUtils.getCustomer req.user, req.body.stripe?.token, (err, customer) =>
|
||||||
if err
|
if err
|
||||||
@logSubscriptionError(req.user, "Purchase year sale get customer: #{JSON.stringify(err)}")
|
@logSubscriptionError(req.user, "Purchase year sale get customer: #{JSON.stringify(err)}")
|
||||||
|
@ -142,7 +145,7 @@ class SubscriptionHandler extends Handler
|
||||||
findStripeSubscription customer.id, subscriptionID: req.user.get('stripe')?.subscriptionID, (subscription) =>
|
findStripeSubscription customer.id, subscriptionID: req.user.get('stripe')?.subscriptionID, (subscription) =>
|
||||||
stripeSubscriptionPeriodEndDate = new Date(subscription.current_period_end * 1000) if subscription
|
stripeSubscriptionPeriodEndDate = new Date(subscription.current_period_end * 1000) if subscription
|
||||||
|
|
||||||
cancelSubscriptionImmediately req.user, subscription, (err) =>
|
@cancelSubscriptionImmediately req.user, subscription, (err) =>
|
||||||
if err
|
if err
|
||||||
@logSubscriptionError(user, "Purchase year sale Stripe cancel subscription error: #{JSON.stringify(err)}")
|
@logSubscriptionError(user, "Purchase year sale Stripe cancel subscription error: #{JSON.stringify(err)}")
|
||||||
return @sendDatabaseError(res, err)
|
return @sendDatabaseError(res, err)
|
||||||
|
@ -192,6 +195,81 @@ class SubscriptionHandler extends Handler
|
||||||
@logSubscriptionError(req.user, "Year sub sale HipChat tower msg error: #{JSON.stringify(error)}")
|
@logSubscriptionError(req.user, "Year sub sale HipChat tower msg error: #{JSON.stringify(error)}")
|
||||||
@sendSuccess(res, user)
|
@sendSuccess(res, user)
|
||||||
|
|
||||||
|
subscribeWithPrepaidCode: (req, res) ->
|
||||||
|
return @sendForbiddenError(res) unless req.user?
|
||||||
|
return @sendBadInputError(res,"You must provide a valid prepaid code") unless req.body?.ppc
|
||||||
|
|
||||||
|
# Check if code exists and has room for more redeemers
|
||||||
|
Prepaid.findOne({ code: req.body.ppc?.toString() }).exec (err, prepaid) =>
|
||||||
|
if err
|
||||||
|
@logSubscriptionError(req.user, "Redeem Prepaid Code find: #{JSON.stringify(err)}")
|
||||||
|
return @sendDatabaseError(res, err)
|
||||||
|
unless prepaid
|
||||||
|
@logSubscriptionError(req.user, "Could not find prepaid code #{req.body.ppc}")
|
||||||
|
return @sendForbiddenError(res)
|
||||||
|
|
||||||
|
oldRedeemers = prepaid.get('redeemers') ? []
|
||||||
|
return @sendForbiddenError(res) if oldRedeemers.length >= prepaid.get('maxRedeemers')
|
||||||
|
months = parseInt(prepaid.get('properties')?.months)
|
||||||
|
return @sendForbiddenError(res) if isNaN(months) or months < 1
|
||||||
|
for redeemer in oldRedeemers
|
||||||
|
return @sendForbiddenError(res) if redeemer.userID.equals(req.user._id)
|
||||||
|
|
||||||
|
customerID = req.user.get('stripe')?.customerID
|
||||||
|
subscriptionID = req.user.get('stripe')?.subscriptionID
|
||||||
|
findStripeSubscription customerID, subscriptionID: subscriptionID, (subscription) =>
|
||||||
|
stripeSubscriptionPeriodEndDate = new Date(subscription.current_period_end * 1000) if subscription
|
||||||
|
|
||||||
|
@cancelSubscriptionImmediately req.user, subscription, (err) =>
|
||||||
|
if err
|
||||||
|
@logSubscriptionError(user, "Redeem Prepaid Code Stripe cancel subscription error: #{JSON.stringify(err)}")
|
||||||
|
return @sendDatabaseError(res, err)
|
||||||
|
@redeemPrepaidCode(req, res, oldRedeemers, months, stripeSubscriptionPeriodEndDate)
|
||||||
|
|
||||||
|
redeemPrepaidCode: (req, res, oldRedeemers, months, startDate=null) =>
|
||||||
|
return @sendForbiddenError(res) unless req.user?
|
||||||
|
return @sendForbiddenError(res) unless req.body?.ppc
|
||||||
|
return @sendForbiddenError(res) unless oldRedeemers
|
||||||
|
return @sendForbiddenError(res) if isNaN(months) or months < 1
|
||||||
|
|
||||||
|
newRedeemerPush = { $push: { redeemers : { date: new Date().toISOString(), userID: req.user._id } }}
|
||||||
|
|
||||||
|
# Only update the prepaid document if the length of the redeemers array hasn't changed in the db.
|
||||||
|
# This will probably fail if redeemers isn't defined. new terminal_subscriptions created should be sure to set the redeemers array
|
||||||
|
# TODO: find a better way?
|
||||||
|
Prepaid.update { 'code': req.body.ppc, 'redeemers': { $size: oldRedeemers.length }}, newRedeemerPush, (err, num, info) =>
|
||||||
|
if err
|
||||||
|
@logSubscriptionError(req.user, "Subscribe with Prepaid Code update: #{JSON.stringify(err)}")
|
||||||
|
return @sendDatabaseError(res, err)
|
||||||
|
|
||||||
|
return @sendNotFoundError(res, "Error while updating prepaid redeemer") if num isnt 1
|
||||||
|
|
||||||
|
# Add terminal subscription to User, extending existing subscriptions
|
||||||
|
# TODO: refactor this into some form useable by both this and purchaseYearSale
|
||||||
|
stripeInfo = _.cloneDeep(req.user.get('stripe') ? {})
|
||||||
|
endDate = new moment()
|
||||||
|
if startDate
|
||||||
|
endDate = new moment(startDate)
|
||||||
|
else if _.isString(stripeInfo.free) and new moment().isBefore(new moment(stripeInfo.free))
|
||||||
|
endDate = new moment(stripeInfo.free)
|
||||||
|
|
||||||
|
endDate = endDate.add(months, 'months')
|
||||||
|
stripeInfo.free = endDate.toISOString().substring(0, 10)
|
||||||
|
req.user.set('stripe', stripeInfo)
|
||||||
|
|
||||||
|
# Add gems to User
|
||||||
|
purchased = _.clone(req.user.get('purchased'))
|
||||||
|
purchased ?= {}
|
||||||
|
purchased.gems ?= 0
|
||||||
|
purchased.gems += subscriptions.basic.gems * months
|
||||||
|
req.user.set('purchased', purchased)
|
||||||
|
|
||||||
|
req.user.save (err, user) =>
|
||||||
|
if err
|
||||||
|
@logSubscriptionError(req.user, "User save error: #{JSON.stringify(err)}")
|
||||||
|
return @sendDatabaseError(res, err)
|
||||||
|
@sendSuccess(res, user)
|
||||||
|
|
||||||
subscribeUser: (req, user, done) ->
|
subscribeUser: (req, user, done) ->
|
||||||
if (not req.user) or req.user.isAnonymous() or 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})
|
return done({res: 'You must be signed in to subscribe.', code: 403})
|
||||||
|
|
|
@ -2,6 +2,8 @@ mongoose = require 'mongoose'
|
||||||
config = require '../../server_config'
|
config = require '../../server_config'
|
||||||
PrepaidSchema = new mongoose.Schema {}, {strict: false, minimize: false,read:config.mongo.readpref}
|
PrepaidSchema = new mongoose.Schema {}, {strict: false, minimize: false,read:config.mongo.readpref}
|
||||||
|
|
||||||
|
PrepaidSchema.index({code: 1}, { unique: true })
|
||||||
|
|
||||||
PrepaidSchema.statics.generateNewCode = (done) ->
|
PrepaidSchema.statics.generateNewCode = (done) ->
|
||||||
tryCode = ->
|
tryCode = ->
|
||||||
code = _.sample("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 8).join('')
|
code = _.sample("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 8).join('')
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
Handler = require '../commons/Handler'
|
Handler = require '../commons/Handler'
|
||||||
Prepaid = require './Prepaid'
|
Prepaid = require './Prepaid'
|
||||||
|
StripeUtils = require '../lib/stripe_utils'
|
||||||
|
{getPrepaidCodeAmount} = require '../../app/core/utils'
|
||||||
|
|
||||||
# TODO: Should this happen on a save() call instead of a prepaid/-/create post?
|
# TODO: Should this happen on a save() call instead of a prepaid/-/create post?
|
||||||
# TODO: Probably a better way to create a unique 8 charactor string property using db voodoo
|
# TODO: Probably a better way to create a unique 8 charactor string property using db voodoo
|
||||||
|
@ -7,29 +9,111 @@ Prepaid = require './Prepaid'
|
||||||
PrepaidHandler = class PrepaidHandler extends Handler
|
PrepaidHandler = class PrepaidHandler extends Handler
|
||||||
modelClass: Prepaid
|
modelClass: Prepaid
|
||||||
jsonSchema: require '../../app/schemas/models/prepaid.schema'
|
jsonSchema: require '../../app/schemas/models/prepaid.schema'
|
||||||
allowedMethods: ['POST']
|
allowedMethods: ['GET','POST']
|
||||||
|
|
||||||
|
baseAmount: 999
|
||||||
|
|
||||||
|
logPurchaseError: (user, msg) ->
|
||||||
|
console.warn "Prepaid Purchase Error: [#{user.get('slug')} (#{user._id})] '#{msg}'"
|
||||||
|
|
||||||
hasAccess: (req) ->
|
hasAccess: (req) ->
|
||||||
req.user?.isAdmin()
|
req.user?.isAdmin()
|
||||||
|
|
||||||
getByRelationship: (req, res, args...) ->
|
getByRelationship: (req, res, args...) ->
|
||||||
relationship = args[1]
|
relationship = args[1]
|
||||||
|
return @getPrepaid(req, res, args[2]) if relationship is 'code'
|
||||||
return @createPrepaid(req, res) if relationship is 'create'
|
return @createPrepaid(req, res) if relationship is 'create'
|
||||||
|
return @purchasePrepaid(req, res) if relationship is 'purchase'
|
||||||
super arguments...
|
super arguments...
|
||||||
|
|
||||||
|
getPrepaid: (req, res, code) ->
|
||||||
|
return @sendForbiddenError(res) unless req.user?
|
||||||
|
return @sendNotFoundError(res, "You must specify a code") unless code
|
||||||
|
|
||||||
|
Prepaid.findOne({ code: code.toString() }).exec (err, prepaid) =>
|
||||||
|
if err
|
||||||
|
console.warn "Get Prepaid Code Error [#{req.user.get('slug')} (#{req.user.id})]: #{JSON.stringify(err)}"
|
||||||
|
return @sendDatabaseError(res, err)
|
||||||
|
|
||||||
|
return @sendNotFoundError(res, "Code not found") unless prepaid
|
||||||
|
|
||||||
|
@sendSuccess(res, prepaid.toObject())
|
||||||
|
|
||||||
createPrepaid: (req, res) ->
|
createPrepaid: (req, res) ->
|
||||||
return @sendForbiddenError(res) unless @hasAccess(req)
|
return @sendForbiddenError(res) unless @hasAccess(req)
|
||||||
return @sendForbiddenError(res) unless req.body.type is 'subscription'
|
return @sendForbiddenError(res) unless req.body.type in ['subscription','terminal_subscription']
|
||||||
return @sendForbiddenError(res) unless req.body.maxRedeemers > 0
|
return @sendForbiddenError(res) unless req.body.maxRedeemers > 0
|
||||||
|
|
||||||
Prepaid.generateNewCode (code) =>
|
Prepaid.generateNewCode (code) =>
|
||||||
return @sendDatabaseError(res, 'Database error.') unless code
|
return @sendDatabaseError(res, 'Database error.') unless code
|
||||||
prepaid = new Prepaid
|
# TODO: change the creator to use ObjectID like with the terminal_subscription
|
||||||
|
options =
|
||||||
creator: req.user.id
|
creator: req.user.id
|
||||||
type: req.body.type
|
type: req.body.type
|
||||||
code: code
|
code: code
|
||||||
maxRedeemers: req.body.maxRedeemers
|
maxRedeemers: req.body.maxRedeemers
|
||||||
|
properties: {}
|
||||||
|
redeemers: []
|
||||||
|
|
||||||
|
if req.body.type is 'subscription'
|
||||||
|
options.properties.couponID = 'free'
|
||||||
|
|
||||||
|
if req.body.type is 'terminal_subscription'
|
||||||
|
options.properties.months = req.body.months
|
||||||
|
options.creator = req.user._id
|
||||||
|
|
||||||
|
prepaid = new Prepaid options
|
||||||
|
prepaid.save (err) =>
|
||||||
|
return @sendDatabaseError(res, err) if err
|
||||||
|
@sendSuccess(res, prepaid.toObject())
|
||||||
|
|
||||||
|
purchasePrepaid: (req, res) ->
|
||||||
|
return @sendForbiddenError(res) unless req.user?
|
||||||
|
return @sendForbiddenError(res) unless req.body.type is 'terminal_subscription'
|
||||||
|
|
||||||
|
maxRedeemers = parseInt(req.body.maxRedeemers)
|
||||||
|
months = parseInt(req.body.months)
|
||||||
|
|
||||||
|
return @sendForbiddenError(res) unless isNaN(maxRedeemers) is false and maxRedeemers > 0
|
||||||
|
return @sendForbiddenError(res) unless isNaN(months) is false and months > 0
|
||||||
|
return @sendError(res, 403, "Users or Months must be greater than 3") if maxRedeemers < 3 and months < 3
|
||||||
|
|
||||||
|
StripeUtils.getCustomer req.user, req.body.stripe?.token, (err, customer) =>
|
||||||
|
if err
|
||||||
|
@logPurchaseError(req.user, "getCustomer error: #{JSON.stringify(err)}")
|
||||||
|
return @sendDatabaseError(res, err)
|
||||||
|
|
||||||
|
metadata =
|
||||||
|
type: req.body.type
|
||||||
|
userID: req.user._id + ''
|
||||||
|
timestamp: parseInt(req.body.stripe?.timestamp)
|
||||||
|
description: req.body.description
|
||||||
|
maxRedeemers: maxRedeemers
|
||||||
|
months: months
|
||||||
|
productID: 'prepaid ' + req.body.type
|
||||||
|
|
||||||
|
amount = getPrepaidCodeAmount(@baseAmount, maxRedeemers, months)
|
||||||
|
|
||||||
|
StripeUtils.createCharge req.user, amount, metadata, (err, charge) =>
|
||||||
|
if err
|
||||||
|
@logPurchaseError(req.user, "createCharge error: #{JSON.stringify(err)}")
|
||||||
|
return @sendDatabaseError(res, err)
|
||||||
|
|
||||||
|
StripeUtils.createPayment req.user, charge, (err, payment) =>
|
||||||
|
if err
|
||||||
|
@logPurchaseError(req.user, "createPayment error: #{JSON.stringify(err)}")
|
||||||
|
return @sendDatabaseError(res, err)
|
||||||
|
|
||||||
|
Prepaid.generateNewCode (code) =>
|
||||||
|
return @sendDatabaseError(res, 'Database error.') unless code
|
||||||
|
prepaid = new Prepaid
|
||||||
|
creator: req.user._id
|
||||||
|
type: req.body.type
|
||||||
|
code: code
|
||||||
|
maxRedeemers: req.body.maxRedeemers
|
||||||
|
redeemers: []
|
||||||
properties:
|
properties:
|
||||||
couponID: 'free'
|
months: req.body.months
|
||||||
prepaid.save (err) =>
|
prepaid.save (err) =>
|
||||||
return @sendDatabaseError(res, err) if err
|
return @sendDatabaseError(res, err) if err
|
||||||
@sendSuccess(res, prepaid.toObject())
|
@sendSuccess(res, prepaid.toObject())
|
||||||
|
|
|
@ -46,14 +46,18 @@ module.exports.setup = (app) ->
|
||||||
|
|
||||||
invoiceID = req.body.data.object.id
|
invoiceID = req.body.data.object.id
|
||||||
stripe.invoices.retrieve invoiceID, (err, invoice) =>
|
stripe.invoices.retrieve invoiceID, (err, invoice) =>
|
||||||
return res.send(500, '') if err
|
if err
|
||||||
|
logStripeWebhookError("Retrieve invoice error: #{JSON.stringify(err)}")
|
||||||
|
return res.send(500, '')
|
||||||
unless invoice.total or invoice.discount?.coupon?.id is 'free'
|
unless invoice.total or invoice.discount?.coupon?.id is 'free'
|
||||||
# invoices made when trialing, probably given for people who resubscribe after unsubscribing
|
# invoices made when trialing, probably given for people who resubscribe after unsubscribing
|
||||||
return res.send(200, '')
|
return res.send(200, '')
|
||||||
return res.send(200, '') unless invoice.lines?.data?.length > 0
|
return res.send(200, '') unless invoice.lines?.data?.length > 0
|
||||||
|
|
||||||
getUserID invoice.customer, (err, userID) =>
|
getUserID invoice.customer, (err, userID) =>
|
||||||
return res.send(500, '') if err
|
if err
|
||||||
|
logStripeWebhookError("Get user ID error: #{JSON.stringify(err)}")
|
||||||
|
return res.send(500, '')
|
||||||
|
|
||||||
# User is recipient if no metadata.id
|
# User is recipient if no metadata.id
|
||||||
recipientID = invoice.lines.data[0].metadata?.id or userID
|
recipientID = invoice.lines.data[0].metadata?.id or userID
|
||||||
|
@ -62,7 +66,9 @@ module.exports.setup = (app) ->
|
||||||
subscriptionID = invoice.lines.data[0].subscription or invoice.lines.data[0].id
|
subscriptionID = invoice.lines.data[0].subscription or invoice.lines.data[0].id
|
||||||
|
|
||||||
User.findById recipientID, (err, recipient) =>
|
User.findById recipientID, (err, recipient) =>
|
||||||
return res.send(500, '') if err
|
if err
|
||||||
|
logStripeWebhookError("Find recipient user error: #{JSON.stringify(err)}")
|
||||||
|
return res.send(500, '')
|
||||||
return res.send(200) unless recipient # just for the sake of testing...
|
return res.send(200) unless recipient # just for the sake of testing...
|
||||||
|
|
||||||
Payment.findOne {'stripe.invoiceID': invoiceID}, (err, payment) =>
|
Payment.findOne {'stripe.invoiceID': invoiceID}, (err, payment) =>
|
||||||
|
@ -82,7 +88,9 @@ module.exports.setup = (app) ->
|
||||||
payment.set 'gems', 3500 if invoice.lines.data[0].plan?.id is 'basic'
|
payment.set 'gems', 3500 if invoice.lines.data[0].plan?.id is 'basic'
|
||||||
|
|
||||||
payment.save (err) =>
|
payment.save (err) =>
|
||||||
return res.send(500, '') if err
|
if err
|
||||||
|
logStripeWebhookError("Save payment error: #{JSON.stringify(err)}")
|
||||||
|
return res.send(500, '')
|
||||||
return res.send(201, '') if invoice.lines.data[0].plan?.id isnt 'basic'
|
return res.send(201, '') if invoice.lines.data[0].plan?.id isnt 'basic'
|
||||||
|
|
||||||
# Update purchased gems
|
# Update purchased gems
|
||||||
|
@ -94,7 +102,9 @@ module.exports.setup = (app) ->
|
||||||
purchased.gems = gems
|
purchased.gems = gems
|
||||||
recipient.set('purchased', purchased)
|
recipient.set('purchased', purchased)
|
||||||
recipient.save (err) ->
|
recipient.save (err) ->
|
||||||
return res.send(500, '') if err
|
if err
|
||||||
|
logStripeWebhookError("Save recipient user error: #{JSON.stringify(err)}")
|
||||||
|
return res.send(500, '')
|
||||||
return res.send(201, '')
|
return res.send(201, '')
|
||||||
|
|
||||||
handleSubscriptionDeleted = (req, res) ->
|
handleSubscriptionDeleted = (req, res) ->
|
||||||
|
|
|
@ -23,6 +23,7 @@ UserRemark = require './remarks/UserRemark'
|
||||||
{isID} = require '../lib/utils'
|
{isID} = require '../lib/utils'
|
||||||
hipchat = require '../hipchat'
|
hipchat = require '../hipchat'
|
||||||
sendwithus = require '../sendwithus'
|
sendwithus = require '../sendwithus'
|
||||||
|
Prepaid = require '../prepaids/Prepaid'
|
||||||
|
|
||||||
serverProperties = ['passwordHash', 'emailLower', 'nameLower', 'passwordReset', 'lastIP']
|
serverProperties = ['passwordHash', 'emailLower', 'nameLower', 'passwordReset', 'lastIP']
|
||||||
candidateProperties = [
|
candidateProperties = [
|
||||||
|
@ -305,6 +306,7 @@ UserHandler = class UserHandler extends Handler
|
||||||
return @avatar(req, res, args[0]) if args[1] is 'avatar'
|
return @avatar(req, res, args[0]) if args[1] is 'avatar'
|
||||||
return @getByIDs(req, res) if args[1] is 'users'
|
return @getByIDs(req, res) if args[1] is 'users'
|
||||||
return @getNamesByIDs(req, res) if args[1] is 'names'
|
return @getNamesByIDs(req, res) if args[1] is 'names'
|
||||||
|
return @getPrepaidCodes(req, res) if args[1] is 'prepaid_codes'
|
||||||
return @nameToID(req, res, args[0]) if args[1] is 'nameToID'
|
return @nameToID(req, res, args[0]) if args[1] is 'nameToID'
|
||||||
return @getLevelSessionsForEmployer(req, res, args[0]) if args[1] is 'level.sessions' and args[2] is 'employer'
|
return @getLevelSessionsForEmployer(req, res, args[0]) if args[1] is 'level.sessions' and args[2] is 'employer'
|
||||||
return @getLevelSessions(req, res, args[0]) if args[1] is 'level.sessions'
|
return @getLevelSessions(req, res, args[0]) if args[1] is 'level.sessions'
|
||||||
|
@ -453,6 +455,11 @@ UserHandler = class UserHandler extends Handler
|
||||||
|
|
||||||
sendMail emailParams
|
sendMail emailParams
|
||||||
|
|
||||||
|
getPrepaidCodes: (req, res) ->
|
||||||
|
orQuery = [{ creator: req.user._id }, { 'redeemers.userID' : req.user._id }]
|
||||||
|
Prepaid.find({}).or(orQuery).exec (err, documents) =>
|
||||||
|
@sendSuccess(res, documents)
|
||||||
|
|
||||||
agreeToCLA: (req, res) ->
|
agreeToCLA: (req, res) ->
|
||||||
return @sendForbiddenError(res) unless req.user
|
return @sendForbiddenError(res) unless req.user
|
||||||
doc =
|
doc =
|
||||||
|
|
|
@ -120,11 +120,33 @@ wrapUpGetUser = (email, user, done) ->
|
||||||
GLOBAL.getURL = (path) ->
|
GLOBAL.getURL = (path) ->
|
||||||
return 'http://localhost:3001' + path
|
return 'http://localhost:3001' + path
|
||||||
|
|
||||||
GLOBAL.createPrepaid = (type, maxRedeemers, done) ->
|
GLOBAL.createPrepaid = (type, maxRedeemers, months, done) ->
|
||||||
options = uri: GLOBAL.getURL('/db/prepaid/-/create')
|
options = uri: GLOBAL.getURL('/db/prepaid/-/create')
|
||||||
options.json =
|
options.json =
|
||||||
type: type
|
type: type
|
||||||
maxRedeemers: maxRedeemers
|
maxRedeemers: maxRedeemers
|
||||||
|
if months
|
||||||
|
options.json.months = months
|
||||||
|
request.post options, done
|
||||||
|
|
||||||
|
GLOBAL.fetchPrepaid = (ppc, done) ->
|
||||||
|
options = uri: GLOBAL.getURL('/db/prepaid/-/code/'+ppc)
|
||||||
|
request.get options, done
|
||||||
|
|
||||||
|
GLOBAL.purchasePrepaid = (type, maxRedeemers, months, done) ->
|
||||||
|
options = uri: GLOBAL.getURL('/db/prepaid/-/purchase')
|
||||||
|
options.json =
|
||||||
|
type: type
|
||||||
|
maxRedeemers: maxRedeemers
|
||||||
|
months: months
|
||||||
|
stripe:
|
||||||
|
timestamp: new Date().getTime()
|
||||||
|
request.post options, done
|
||||||
|
|
||||||
|
GLOBAL.subscribeWithPrepaid = (ppc, done) =>
|
||||||
|
options = url: GLOBAL.getURL('/db/subscription/-/subscribe_prepaid')
|
||||||
|
options.json =
|
||||||
|
ppc: ppc
|
||||||
request.post options, done
|
request.post options, done
|
||||||
|
|
||||||
newUserCount = 0
|
newUserCount = 0
|
||||||
|
|
|
@ -1,9 +1,18 @@
|
||||||
require '../common'
|
require '../common'
|
||||||
|
config = require '../../../server_config'
|
||||||
|
moment = require 'moment'
|
||||||
|
{findStripeSubscription} = require '../../../server/lib/utils'
|
||||||
|
|
||||||
describe '/db/prepaid', ->
|
describe '/db/prepaid', ->
|
||||||
prepaidURL = getURL('/db/prepaid')
|
prepaidURL = getURL('/db/prepaid')
|
||||||
prepaidCreateURL = getURL('/db/prepaid/-/create')
|
prepaidCreateURL = getURL('/db/prepaid/-/create')
|
||||||
|
|
||||||
|
headers = {'X-Change-Plan': 'true'}
|
||||||
|
|
||||||
|
joeData = null
|
||||||
|
stripe = require('stripe')(config.stripe.secretKey)
|
||||||
|
joeCode = null
|
||||||
|
|
||||||
verifyPrepaid = (user, prepaid, done) ->
|
verifyPrepaid = (user, prepaid, done) ->
|
||||||
expect(prepaid.creator).toEqual(user.id)
|
expect(prepaid.creator).toEqual(user.id)
|
||||||
expect(prepaid.type).toEqual('subscription')
|
expect(prepaid.type).toEqual('subscription')
|
||||||
|
@ -13,12 +22,12 @@ describe '/db/prepaid', ->
|
||||||
done()
|
done()
|
||||||
|
|
||||||
it 'Clear database users and prepaids', (done) ->
|
it 'Clear database users and prepaids', (done) ->
|
||||||
clearModels [User, Prepaid], (err) ->
|
clearModels [User, Prepaid, Payment], (err) ->
|
||||||
throw err if err
|
throw err if err
|
||||||
done()
|
done()
|
||||||
|
|
||||||
it 'Anonymous creates prepaid code', (done) ->
|
it 'Anonymous creates prepaid code', (done) ->
|
||||||
createPrepaid 'subscription', 1, (err, res, body) ->
|
createPrepaid 'subscription', 1, 0, (err, res, body) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
expect(res.statusCode).toBe(401)
|
expect(res.statusCode).toBe(401)
|
||||||
done()
|
done()
|
||||||
|
@ -26,7 +35,7 @@ describe '/db/prepaid', ->
|
||||||
it 'Non-admin creates prepaid code', (done) ->
|
it 'Non-admin creates prepaid code', (done) ->
|
||||||
loginNewUser (user1) ->
|
loginNewUser (user1) ->
|
||||||
expect(user1.isAdmin()).toEqual(false)
|
expect(user1.isAdmin()).toEqual(false)
|
||||||
createPrepaid 'subscription', 4, (err, res, body) ->
|
createPrepaid 'subscription', 4, 0, (err, res, body) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
expect(res.statusCode).toBe(403)
|
expect(res.statusCode).toBe(403)
|
||||||
done()
|
done()
|
||||||
|
@ -37,18 +46,35 @@ describe '/db/prepaid', ->
|
||||||
user1.save (err, user1) ->
|
user1.save (err, user1) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
expect(user1.isAdmin()).toEqual(true)
|
expect(user1.isAdmin()).toEqual(true)
|
||||||
createPrepaid 'subscription', 1, (err, res, body) ->
|
createPrepaid 'subscription', 1, 0, (err, res, body) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
expect(res.statusCode).toBe(200)
|
expect(res.statusCode).toBe(200)
|
||||||
verifyPrepaid user1, body, done
|
verifyPrepaid user1, body, done
|
||||||
|
|
||||||
|
it 'Admin creates prepaid code with type terminal_subscription', (done) ->
|
||||||
|
loginNewUser (user1) ->
|
||||||
|
user1.set('permissions', ['admin'])
|
||||||
|
user1.save (err, user1) ->
|
||||||
|
expect(err).toBeNull()
|
||||||
|
expect(user1.isAdmin()).toEqual(true)
|
||||||
|
createPrepaid 'terminal_subscription', 2, 3, (err, res, body) ->
|
||||||
|
expect(err).toBeNull()
|
||||||
|
expect(res.statusCode).toBe(200)
|
||||||
|
expect(body.creator).toEqual(user1.id)
|
||||||
|
expect(body.type).toEqual('terminal_subscription')
|
||||||
|
expect(body.maxRedeemers).toEqual(2)
|
||||||
|
expect(body.properties?.months).toEqual(3)
|
||||||
|
expect(body.code).toMatch(/^\w{8}$/)
|
||||||
|
done()
|
||||||
|
|
||||||
|
|
||||||
it 'Admin creates prepaid code with invalid type', (done) ->
|
it 'Admin creates prepaid code with invalid type', (done) ->
|
||||||
loginNewUser (user1) ->
|
loginNewUser (user1) ->
|
||||||
user1.set('permissions', ['admin'])
|
user1.set('permissions', ['admin'])
|
||||||
user1.save (err, user1) ->
|
user1.save (err, user1) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
expect(user1.isAdmin()).toEqual(true)
|
expect(user1.isAdmin()).toEqual(true)
|
||||||
createPrepaid 'bulldozer', 1, (err, res, body) ->
|
createPrepaid 'bulldozer', 1, 0, (err, res, body) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
expect(res.statusCode).toBe(403)
|
expect(res.statusCode).toBe(403)
|
||||||
done()
|
done()
|
||||||
|
@ -59,7 +85,7 @@ describe '/db/prepaid', ->
|
||||||
user1.save (err, user1) ->
|
user1.save (err, user1) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
expect(user1.isAdmin()).toEqual(true)
|
expect(user1.isAdmin()).toEqual(true)
|
||||||
createPrepaid null, 1, (err, res, body) ->
|
createPrepaid null, 1, 0, (err, res, body) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
expect(res.statusCode).toBe(403)
|
expect(res.statusCode).toBe(403)
|
||||||
done()
|
done()
|
||||||
|
@ -70,7 +96,7 @@ describe '/db/prepaid', ->
|
||||||
user1.save (err, user1) ->
|
user1.save (err, user1) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
expect(user1.isAdmin()).toEqual(true)
|
expect(user1.isAdmin()).toEqual(true)
|
||||||
createPrepaid 'subscription', 0, (err, res, body) ->
|
createPrepaid 'subscription', 0, 0, (err, res, body) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
expect(res.statusCode).toBe(403)
|
expect(res.statusCode).toBe(403)
|
||||||
done()
|
done()
|
||||||
|
@ -89,7 +115,7 @@ describe '/db/prepaid', ->
|
||||||
user1.save (err, user1) ->
|
user1.save (err, user1) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
expect(user1.isAdmin()).toEqual(true)
|
expect(user1.isAdmin()).toEqual(true)
|
||||||
createPrepaid 'subscription', 1, (err, res, prepaid) ->
|
createPrepaid 'subscription', 1, 0, (err, res, prepaid) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
expect(res.statusCode).toBe(200)
|
expect(res.statusCode).toBe(200)
|
||||||
request.get {uri: prepaidURL}, (err, res, body) ->
|
request.get {uri: prepaidURL}, (err, res, body) ->
|
||||||
|
@ -104,3 +130,233 @@ describe '/db/prepaid', ->
|
||||||
break
|
break
|
||||||
expect(found).toEqual(true)
|
expect(found).toEqual(true)
|
||||||
done() unless found
|
done() unless found
|
||||||
|
|
||||||
|
# *** Purchase Prepaid Codes *** #
|
||||||
|
it 'Anonymous submits a prepaid purchase', (done) ->
|
||||||
|
logoutUser () ->
|
||||||
|
purchasePrepaid 'terminal_subscription', 3, 3, (err, res, prepaid) ->
|
||||||
|
expect(err).toBeNull()
|
||||||
|
expect(res.statusCode).toBe(401)
|
||||||
|
done()
|
||||||
|
|
||||||
|
it 'Should error if type isnt terminal_subscription', (done) ->
|
||||||
|
loginNewUser (user1) ->
|
||||||
|
purchasePrepaid 'subscription', 3, 3, (err, res, prepaid) ->
|
||||||
|
expect(err).toBeNull()
|
||||||
|
expect(res.statusCode).toBe(403)
|
||||||
|
done()
|
||||||
|
|
||||||
|
it 'Should error if maxRedeemers is invalid', (done) ->
|
||||||
|
loginNewUser (user1) ->
|
||||||
|
purchasePrepaid 'terminal_subscription', -1, 3, (err, res, prepaid) ->
|
||||||
|
expect(err).toBeNull()
|
||||||
|
expect(res.statusCode).toBe(403)
|
||||||
|
done()
|
||||||
|
purchasePrepaid 'terminal_subscription', 'foo', 3, (err, res, prepaid) ->
|
||||||
|
expect(err).toBeNull()
|
||||||
|
expect(res.statusCode).toBe(403)
|
||||||
|
done()
|
||||||
|
|
||||||
|
it 'Should error if months is invalid', (done) ->
|
||||||
|
loginNewUser (user1) ->
|
||||||
|
purchasePrepaid 'terminal_subscription', 3, -1, (err, res, prepaid) ->
|
||||||
|
expect(err).toBeNull()
|
||||||
|
expect(res.statusCode).toBe(403)
|
||||||
|
done()
|
||||||
|
purchasePrepaid 'terminal_subscription', 3, 'foo', (err, res, prepaid) ->
|
||||||
|
expect(err).toBeNull()
|
||||||
|
expect(res.statusCode).toBe(403)
|
||||||
|
done()
|
||||||
|
|
||||||
|
it 'Should error if maxRedeemers and months are less than 3', (done) ->
|
||||||
|
loginNewUser (user1) ->
|
||||||
|
purchasePrepaid 'terminal_subscription', 1, 1, (err, res, prepaid) ->
|
||||||
|
expect(err).toBeNull()
|
||||||
|
expect(res.statusCode).toBe(403)
|
||||||
|
done()
|
||||||
|
|
||||||
|
it 'User submits valid prepaid code purchase', (done) ->
|
||||||
|
stripe.tokens.create {
|
||||||
|
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
|
||||||
|
}, (err, token) ->
|
||||||
|
stripeTokenID = token.id
|
||||||
|
loginJoe (joe) ->
|
||||||
|
joeData = joe.toObject()
|
||||||
|
joeData.stripe = {
|
||||||
|
token: stripeTokenID
|
||||||
|
planID: 'basic'
|
||||||
|
}
|
||||||
|
request.put {uri: getURL('/db/user'), json: joeData, headers: headers }, (err, res, body) ->
|
||||||
|
joeData = body
|
||||||
|
expect(res.statusCode).toBe(200)
|
||||||
|
expect(joeData.stripe.customerID).toBeDefined()
|
||||||
|
expect(firstSubscriptionID = joeData.stripe.subscriptionID).toBeDefined()
|
||||||
|
expect(joeData.stripe.planID).toBe('basic')
|
||||||
|
expect(joeData.stripe.token).toBeUndefined()
|
||||||
|
purchasePrepaid 'terminal_subscription', 3, 3, (err, res, prepaid) ->
|
||||||
|
expect(err).toBeNull()
|
||||||
|
expect(res.statusCode).toBe(200)
|
||||||
|
expect(prepaid.type).toEqual('terminal_subscription')
|
||||||
|
expect(prepaid.code).toBeDefined()
|
||||||
|
# Saving this code for later tests
|
||||||
|
# TODO: don't make tests dependent on each other
|
||||||
|
joeCode = prepaid.code
|
||||||
|
expect(prepaid.creator).toBeDefined()
|
||||||
|
expect(prepaid.maxRedeemers).toEqual(3)
|
||||||
|
expect(prepaid.properties).toBeDefined()
|
||||||
|
expect(prepaid.properties.months).toEqual(3)
|
||||||
|
done()
|
||||||
|
|
||||||
|
it 'Should have logged a Payment with the correct amount', (done) ->
|
||||||
|
loginJoe (joe) ->
|
||||||
|
query =
|
||||||
|
purchaser: joe._id
|
||||||
|
Payment.find query, (err, payments) ->
|
||||||
|
expect(err).toBeNull()
|
||||||
|
expect(payments).not.toBeNull()
|
||||||
|
expect(payments.length).toEqual(1)
|
||||||
|
expect(payments[0].get('amount')).toEqual(8991)
|
||||||
|
done()
|
||||||
|
|
||||||
|
|
||||||
|
it 'Anonymous cant redeem a prepaid code', (done) ->
|
||||||
|
logoutUser () ->
|
||||||
|
subscribeWithPrepaid joeCode, (err, res) ->
|
||||||
|
expect(err).toBeNull()
|
||||||
|
expect(res?.statusCode).toEqual(401)
|
||||||
|
done()
|
||||||
|
|
||||||
|
|
||||||
|
it 'User cant redeem a nonexistant prepaid code', (done) ->
|
||||||
|
loginJoe (joe) ->
|
||||||
|
subscribeWithPrepaid 'abc123', (err, res) ->
|
||||||
|
expect(err).toBeNull()
|
||||||
|
expect(res.statusCode).toEqual(403)
|
||||||
|
done()
|
||||||
|
|
||||||
|
it 'User cant redeem empty code', (done) ->
|
||||||
|
loginJoe (joe) ->
|
||||||
|
subscribeWithPrepaid '', (err, res) ->
|
||||||
|
expect(err).toBeNull()
|
||||||
|
expect(res.statusCode).toEqual(422)
|
||||||
|
done()
|
||||||
|
|
||||||
|
it 'Anonymous cant fetch a prepaid code', (done) ->
|
||||||
|
expect(joeCode).not.toBeNull()
|
||||||
|
logoutUser () ->
|
||||||
|
fetchPrepaid joeCode, (err, res) ->
|
||||||
|
expect(err).toBeNull()
|
||||||
|
expect(res.statusCode).toEqual(403)
|
||||||
|
done()
|
||||||
|
|
||||||
|
it 'User can fetch a prepaid code', (done) ->
|
||||||
|
expect(joeCode).not.toBeNull()
|
||||||
|
loginJoe (joe) ->
|
||||||
|
fetchPrepaid joeCode, (err, res, body) ->
|
||||||
|
expect(err).toBeNull()
|
||||||
|
expect(res.statusCode).toEqual(200)
|
||||||
|
|
||||||
|
expect(body).toBeDefined()
|
||||||
|
return done() unless body
|
||||||
|
|
||||||
|
prepaid = JSON.parse(body)
|
||||||
|
expect(prepaid.code).toEqual(joeCode)
|
||||||
|
expect(prepaid.maxRedeemers).toEqual(3)
|
||||||
|
expect(prepaid.properties?.months).toEqual(3)
|
||||||
|
done()
|
||||||
|
|
||||||
|
# TODO: Move redeem subscription prepaid code tests to subscription tests file
|
||||||
|
|
||||||
|
it 'Creator can redeeem a prepaid code', (done) ->
|
||||||
|
loginJoe (joe) ->
|
||||||
|
expect(joeCode).not.toBeNull()
|
||||||
|
expect(joeData.stripe?.customerID).toBeDefined()
|
||||||
|
expect(joeData.stripe?.subscriptionID).toBeDefined()
|
||||||
|
return done() unless joeData.stripe?.customerID
|
||||||
|
|
||||||
|
# joe has a stripe subscription, so test if the months are added to the end of it.
|
||||||
|
stripe.customers.retrieve joeData.stripe.customerID, (err, customer) =>
|
||||||
|
expect(err).toBeNull()
|
||||||
|
|
||||||
|
findStripeSubscription customer.id, subscriptionID: joeData.stripe?.subscriptionID, (subscription) =>
|
||||||
|
if subscription
|
||||||
|
stripeSubscriptionPeriodEndDate = new moment(subscription.current_period_end * 1000)
|
||||||
|
else
|
||||||
|
expect(stripeSubscriptionPeriodEndDate).toBeDefined()
|
||||||
|
return done()
|
||||||
|
|
||||||
|
subscribeWithPrepaid joeCode, (err, res, result) =>
|
||||||
|
expect(err).toBeNull()
|
||||||
|
expect(res.statusCode).toEqual(200)
|
||||||
|
endDate = stripeSubscriptionPeriodEndDate.add(3, 'months').toISOString().substring(0, 10)
|
||||||
|
expect(result?.stripe?.free).toEqual(endDate)
|
||||||
|
expect(result?.purchased?.gems).toEqual(14000)
|
||||||
|
findStripeSubscription customer.id, subscriptionID: joeData.stripe?.subscriptionID, (subscription) =>
|
||||||
|
expect(subscription).toBeNull()
|
||||||
|
done()
|
||||||
|
|
||||||
|
it 'User can redeem a prepaid code', (done) ->
|
||||||
|
loginSam (sam) ->
|
||||||
|
subscribeWithPrepaid joeCode, (err, res, result) ->
|
||||||
|
expect(err).toBeNull()
|
||||||
|
expect(res.statusCode).toEqual(200)
|
||||||
|
endDate = new moment().add(3, 'months').toISOString().substring(0, 10)
|
||||||
|
expect(result?.stripe?.free).toEqual(endDate)
|
||||||
|
expect(result?.purchased?.gems).toEqual(10500)
|
||||||
|
done()
|
||||||
|
|
||||||
|
it 'Wont allow the same person to redeem twice', (done) ->
|
||||||
|
loginSam (sam) ->
|
||||||
|
subscribeWithPrepaid joeCode, (err, res, result) ->
|
||||||
|
expect(err).toBeNull()
|
||||||
|
expect(res.statusCode).toEqual(403)
|
||||||
|
done()
|
||||||
|
|
||||||
|
it 'Will return redeemed code as part of codes list', (done) ->
|
||||||
|
loginSam (sam) ->
|
||||||
|
request.get "#{getURL('/db/user')}/#{sam.id}/prepaid_codes", (err, res) ->
|
||||||
|
expect(err).toBeNull()
|
||||||
|
expect(res.statusCode).toEqual(200)
|
||||||
|
codes = JSON.parse res.body
|
||||||
|
expect(codes.length).toEqual(1)
|
||||||
|
done()
|
||||||
|
|
||||||
|
it 'Third user can redeem a prepaid code', (done) ->
|
||||||
|
loginNewUser (user) ->
|
||||||
|
subscribeWithPrepaid joeCode, (err, res, result) ->
|
||||||
|
expect(err).toBeNull()
|
||||||
|
expect(res.statusCode).toEqual(200)
|
||||||
|
endDate = new moment().add(3, 'months').toISOString().substring(0, 10)
|
||||||
|
expect(result?.stripe?.free).toEqual(endDate)
|
||||||
|
expect(result?.purchased?.gems).toEqual(10500)
|
||||||
|
done()
|
||||||
|
|
||||||
|
it 'Fourth user cannot redeem code', (done) ->
|
||||||
|
loginNewUser (user) ->
|
||||||
|
subscribeWithPrepaid joeCode, (err, res, result) ->
|
||||||
|
expect(err).toBeNull()
|
||||||
|
expect(res.statusCode).toEqual(403)
|
||||||
|
done()
|
||||||
|
|
||||||
|
|
||||||
|
it 'Can fetch a list of purchased and redeemed prepaid codes', (done) ->
|
||||||
|
loginJoe (joe) ->
|
||||||
|
purchasePrepaid 'terminal_subscription', 3, 1, (err, res, prepaid) ->
|
||||||
|
request.get "#{getURL('/db/user')}/#{joe.id}/prepaid_codes", (err, res) ->
|
||||||
|
expect(err).toBeNull()
|
||||||
|
expect(res.statusCode).toEqual(200);
|
||||||
|
codes = JSON.parse res.body
|
||||||
|
expect(codes.length).toEqual(2)
|
||||||
|
expect(codes[0].maxRedeemers).toEqual(3)
|
||||||
|
expect(codes[0].properties).toBeDefined()
|
||||||
|
expect(codes[0].properties.months).toEqual(3)
|
||||||
|
done()
|
||||||
|
|
||||||
|
it 'Test for injection', (done) ->
|
||||||
|
loginNewUser (user) ->
|
||||||
|
code = { $exists: true }
|
||||||
|
subscribeWithPrepaid code, (err, res, result) ->
|
||||||
|
expect(err).toBeNull()
|
||||||
|
expect(res.statusCode).not.toEqual(200)
|
||||||
|
done()
|
||||||
|
# TODO: add a bunch of parallel tests trying to redeem a code with a high maxRedeemers (50?) to see what happens
|
||||||
|
|
|
@ -297,6 +297,8 @@ describe 'Subscriptions', ->
|
||||||
return done() unless sponsorCustomerID and sponsorStripe.sponsorSubscriptionID
|
return done() unless sponsorCustomerID and sponsorStripe.sponsorSubscriptionID
|
||||||
stripe.customers.retrieveSubscription sponsorCustomerID, sponsorStripe.sponsorSubscriptionID, (err, subscription) ->
|
stripe.customers.retrieveSubscription sponsorCustomerID, sponsorStripe.sponsorSubscriptionID, (err, subscription) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
|
expect(subscription?).toBe(true)
|
||||||
|
return done() unless subscription?
|
||||||
expect(subscription.plan.amount).toEqual(1)
|
expect(subscription.plan.amount).toEqual(1)
|
||||||
expect(subscription.customer).toEqual(sponsorCustomerID)
|
expect(subscription.customer).toEqual(sponsorCustomerID)
|
||||||
expect(subscription.quantity).toEqual(utils.getSponsoredSubsAmount(subPrice, numSponsored, sponsorStripe.subscriptionID?))
|
expect(subscription.quantity).toEqual(utils.getSponsoredSubsAmount(subPrice, numSponsored, sponsorStripe.subscriptionID?))
|
||||||
|
@ -361,6 +363,7 @@ describe 'Subscriptions', ->
|
||||||
Payment.findOne paymentQuery, (err, payment) ->
|
Payment.findOne paymentQuery, (err, payment) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
expect(payment).not.toBeNull()
|
expect(payment).not.toBeNull()
|
||||||
|
return done() if payment is null
|
||||||
expect(payment.get('amount')).toEqual(0)
|
expect(payment.get('amount')).toEqual(0)
|
||||||
expect(payment.get('gems')).toBeGreaterThan(subGems - 1)
|
expect(payment.get('gems')).toBeGreaterThan(subGems - 1)
|
||||||
done()
|
done()
|
||||||
|
@ -406,6 +409,8 @@ describe 'Subscriptions', ->
|
||||||
expect(user.get('stripe').customerID).toBeDefined()
|
expect(user.get('stripe').customerID).toBeDefined()
|
||||||
expect(user.get('stripe').planID).toBeUndefined()
|
expect(user.get('stripe').planID).toBeUndefined()
|
||||||
expect(user.get('stripe').token).toBeUndefined()
|
expect(user.get('stripe').token).toBeUndefined()
|
||||||
|
expect(user.get('stripe').subscriptionID).toBeDefined()
|
||||||
|
return done() unless user.get('stripe').subscriptionID
|
||||||
stripe.customers.retrieveSubscription user.get('stripe').customerID, user.get('stripe').subscriptionID, (err, subscription) ->
|
stripe.customers.retrieveSubscription user.get('stripe').customerID, user.get('stripe').subscriptionID, (err, subscription) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
expect(subscription).not.toBeNull()
|
expect(subscription).not.toBeNull()
|
||||||
|
@ -422,11 +427,11 @@ describe 'Subscriptions', ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
return done() if err
|
return done() if err
|
||||||
expect(res.statusCode).toBe(200)
|
expect(res.statusCode).toBe(200)
|
||||||
expect(body.stripe.customerID).toBeDefined()
|
expect(body.stripe?.customerID).toBeDefined()
|
||||||
updatedUser = body
|
updatedUser = body
|
||||||
|
|
||||||
# Call webhooks for invoices
|
# Call webhooks for invoices
|
||||||
options = customer: body.stripe.customerID, limit: 100
|
options = customer: body.stripe?.customerID, limit: 100
|
||||||
stripe.invoices.list options, (err, invoices) ->
|
stripe.invoices.list options, (err, invoices) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
expect(invoices).not.toBeNull()
|
expect(invoices).not.toBeNull()
|
||||||
|
@ -444,7 +449,7 @@ describe 'Subscriptions', ->
|
||||||
unless invoice.id of invoicesWebHooked
|
unless invoice.id of invoicesWebHooked
|
||||||
invoicesWebHooked[invoice.id] = true
|
invoicesWebHooked[invoice.id] = true
|
||||||
webhookTasks.push makeWebhookCall(invoice)
|
webhookTasks.push makeWebhookCall(invoice)
|
||||||
async.parallel webhookTasks, (err, results) ->
|
async.series webhookTasks, (err, results) ->
|
||||||
expect(err?).toEqual(false)
|
expect(err?).toEqual(false)
|
||||||
done(updatedUser)
|
done(updatedUser)
|
||||||
|
|
||||||
|
@ -569,7 +574,7 @@ describe 'Subscriptions', ->
|
||||||
user1.save (err, user1) ->
|
user1.save (err, user1) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
expect(user1.isAdmin()).toEqual(true)
|
expect(user1.isAdmin()).toEqual(true)
|
||||||
createPrepaid 'subscription', 1, (err, res, prepaid) ->
|
createPrepaid 'subscription', 1, 0, (err, res, prepaid) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
subscribeUser user1, null, prepaid.code, ->
|
subscribeUser user1, null, prepaid.code, ->
|
||||||
Prepaid.findById prepaid._id, (err, prepaid) ->
|
Prepaid.findById prepaid._id, (err, prepaid) ->
|
||||||
|
@ -584,6 +589,8 @@ describe 'Subscriptions', ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
stripeInfo = user1.get('stripe')
|
stripeInfo = user1.get('stripe')
|
||||||
expect(stripeInfo.prepaidCode).toEqual(prepaid.get('code'))
|
expect(stripeInfo.prepaidCode).toEqual(prepaid.get('code'))
|
||||||
|
expect(stripeInfo.subscriptionID).toBeDefined()
|
||||||
|
return done() unless stripeInfo.subscriptionID
|
||||||
|
|
||||||
# Delete subscription
|
# Delete subscription
|
||||||
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.subscriptionID, (err, subscription) ->
|
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.subscriptionID, (err, subscription) ->
|
||||||
|
@ -612,7 +619,7 @@ describe 'Subscriptions', ->
|
||||||
subscribeUser user1, token, null, ->
|
subscribeUser user1, token, null, ->
|
||||||
User.findById user1.id, (err, user1) ->
|
User.findById user1.id, (err, user1) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
createPrepaid 'subscription', 1, (err, res, prepaid) ->
|
createPrepaid 'subscription', 1, 0, (err, res, prepaid) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
subscribeUser user1, null, prepaid.code, ->
|
subscribeUser user1, null, prepaid.code, ->
|
||||||
Prepaid.findById prepaid._id, (err, prepaid) ->
|
Prepaid.findById prepaid._id, (err, prepaid) ->
|
||||||
|
@ -627,6 +634,7 @@ describe 'Subscriptions', ->
|
||||||
stripe.customers.retrieveSubscription customerID, subscriptionID, (err, subscription) ->
|
stripe.customers.retrieveSubscription customerID, subscriptionID, (err, subscription) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
expect(subscription).not.toBeNull()
|
expect(subscription).not.toBeNull()
|
||||||
|
return done() unless subscription
|
||||||
expect(subscription.discount?.coupon?.id).toEqual('free')
|
expect(subscription.discount?.coupon?.id).toEqual('free')
|
||||||
done()
|
done()
|
||||||
|
|
||||||
|
@ -646,7 +654,7 @@ describe 'Subscriptions', ->
|
||||||
request.put {uri: userURL, json: requestBody, headers: headers }, (err, res, updatedUser) ->
|
request.put {uri: userURL, json: requestBody, headers: headers }, (err, res, updatedUser) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
expect(res.statusCode).toBe(200)
|
expect(res.statusCode).toBe(200)
|
||||||
createPrepaid 'subscription', 1, (err, res, prepaid) ->
|
createPrepaid 'subscription', 1, 0, (err, res, prepaid) ->
|
||||||
subscribeUser user1, null, prepaid.code, ->
|
subscribeUser user1, null, prepaid.code, ->
|
||||||
Prepaid.findById prepaid._id, (err, prepaid) ->
|
Prepaid.findById prepaid._id, (err, prepaid) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
|
@ -660,7 +668,7 @@ describe 'Subscriptions', ->
|
||||||
stripe.customers.retrieveSubscription customerID, subscriptionID, (err, subscription) ->
|
stripe.customers.retrieveSubscription customerID, subscriptionID, (err, subscription) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
expect(subscription).not.toBeNull()
|
expect(subscription).not.toBeNull()
|
||||||
expect(subscription.discount?.coupon?.id).toEqual('free')
|
expect(subscription?.discount?.coupon?.id).toEqual('free')
|
||||||
done()
|
done()
|
||||||
|
|
||||||
it 'Subscribe with prepaid, then cancel', (done) ->
|
it 'Subscribe with prepaid, then cancel', (done) ->
|
||||||
|
@ -669,7 +677,7 @@ describe 'Subscriptions', ->
|
||||||
user1.save (err, user1) ->
|
user1.save (err, user1) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
expect(user1.isAdmin()).toEqual(true)
|
expect(user1.isAdmin()).toEqual(true)
|
||||||
createPrepaid 'subscription', 1, (err, res, prepaid) ->
|
createPrepaid 'subscription', 1, 0, (err, res, prepaid) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
subscribeUser user1, null, prepaid.code, ->
|
subscribeUser user1, null, prepaid.code, ->
|
||||||
Prepaid.findById prepaid._id, (err, prepaid) ->
|
Prepaid.findById prepaid._id, (err, prepaid) ->
|
||||||
|
@ -691,7 +699,7 @@ describe 'Subscriptions', ->
|
||||||
user1.save (err, user1) ->
|
user1.save (err, user1) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
expect(user1.isAdmin()).toEqual(true)
|
expect(user1.isAdmin()).toEqual(true)
|
||||||
createPrepaid 'subscription', 1, (err, res, prepaid) ->
|
createPrepaid 'subscription', 1, 0, (err, res, prepaid) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
subscribeUser user1, null, prepaid.code, ->
|
subscribeUser user1, null, prepaid.code, ->
|
||||||
loginNewUser (user2) ->
|
loginNewUser (user2) ->
|
||||||
|
@ -714,7 +722,7 @@ describe 'Subscriptions', ->
|
||||||
user1.save (err, user1) ->
|
user1.save (err, user1) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
expect(user1.isAdmin()).toEqual(true)
|
expect(user1.isAdmin()).toEqual(true)
|
||||||
createPrepaid 'subscription', 2, (err, res, prepaid) ->
|
createPrepaid 'subscription', 2, 0, (err, res, prepaid) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
subscribeUser user1, null, prepaid.code, ->
|
subscribeUser user1, null, prepaid.code, ->
|
||||||
loginNewUser (user2) ->
|
loginNewUser (user2) ->
|
||||||
|
@ -730,7 +738,7 @@ describe 'Subscriptions', ->
|
||||||
user1.save (err, user1) ->
|
user1.save (err, user1) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
expect(user1.isAdmin()).toEqual(true)
|
expect(user1.isAdmin()).toEqual(true)
|
||||||
createPrepaid 'subscription', 1, (err, res, prepaid) ->
|
createPrepaid 'subscription', 1, 0, (err, res, prepaid) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
subscribeUser user1, null, prepaid.code, ->
|
subscribeUser user1, null, prepaid.code, ->
|
||||||
Prepaid.findById prepaid._id, (err, prepaid) ->
|
Prepaid.findById prepaid._id, (err, prepaid) ->
|
||||||
|
@ -746,7 +754,7 @@ describe 'Subscriptions', ->
|
||||||
user1.save (err, user1) ->
|
user1.save (err, user1) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
expect(user1.isAdmin()).toEqual(true)
|
expect(user1.isAdmin()).toEqual(true)
|
||||||
createPrepaid 'subscription', 2, (err, res, prepaid) ->
|
createPrepaid 'subscription', 2, 0, (err, res, prepaid) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
Prepaid.findById prepaid._id, (err, prepaid) ->
|
Prepaid.findById prepaid._id, (err, prepaid) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
|
@ -795,8 +803,13 @@ describe 'Subscriptions', ->
|
||||||
createNewUser (user2) ->
|
createNewUser (user2) ->
|
||||||
loginNewUser (user1) ->
|
loginNewUser (user1) ->
|
||||||
subscribeRecipients user1, [user2], token, (updatedUser) ->
|
subscribeRecipients user1, [user2], token, (updatedUser) ->
|
||||||
customerID = updatedUser.stripe.customerID
|
expect(updatedUser).not.toBeNull()
|
||||||
subscriptionID = updatedUser.stripe.recipients[0].subscriptionID
|
return done() unless updatedUser
|
||||||
|
customerID = updatedUser.stripe?.customerID
|
||||||
|
expect(customerID).toBeDefined()
|
||||||
|
subscriptionID = updatedUser.stripe?.recipients[0]?.subscriptionID
|
||||||
|
expect(subscriptionID).toBeDefined()
|
||||||
|
return done() unless customerID and subscriptionID
|
||||||
loginUser user2, (user2) ->
|
loginUser user2, (user2) ->
|
||||||
request.del {uri: "#{userURL}/#{user2.id}"}, (err, res) ->
|
request.del {uri: "#{userURL}/#{user2.id}"}, (err, res) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
|
@ -987,7 +1000,7 @@ describe 'Subscriptions', ->
|
||||||
user2.save (err, user1) ->
|
user2.save (err, user1) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
expect(user2.isAdmin()).toEqual(true)
|
expect(user2.isAdmin()).toEqual(true)
|
||||||
createPrepaid 'subscription', 1, (err, res, prepaid) ->
|
createPrepaid 'subscription', 1, 0, (err, res, prepaid) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
requestBody = user2.toObject()
|
requestBody = user2.toObject()
|
||||||
requestBody.stripe =
|
requestBody.stripe =
|
||||||
|
@ -1022,8 +1035,11 @@ describe 'Subscriptions', ->
|
||||||
createNewUser (user2) ->
|
createNewUser (user2) ->
|
||||||
loginNewUser (user1) ->
|
loginNewUser (user1) ->
|
||||||
subscribeRecipients user1, [user2, user3], token, (updatedUser) ->
|
subscribeRecipients user1, [user2, user3], token, (updatedUser) ->
|
||||||
customerID = updatedUser.stripe.customerID
|
customerID = updatedUser?.stripe?.customerID
|
||||||
subscriptionID = updatedUser.stripe.sponsorSubscriptionID
|
subscriptionID = updatedUser?.stripe?.sponsorSubscriptionID
|
||||||
|
expect(customerID).toBeDefined()
|
||||||
|
expect(subscriptionID).toBeDefined()
|
||||||
|
return done() unless customerID and subscriptionID
|
||||||
|
|
||||||
# Find Stripe sponsor subscription
|
# Find Stripe sponsor subscription
|
||||||
stripe.customers.retrieveSubscription customerID, subscriptionID, (err, subscription) ->
|
stripe.customers.retrieveSubscription customerID, subscriptionID, (err, subscription) ->
|
||||||
|
@ -1041,6 +1057,7 @@ describe 'Subscriptions', ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
|
|
||||||
# Should have 2 cancelled recipient subs with cancel_at_period_end = true
|
# Should have 2 cancelled recipient subs with cancel_at_period_end = true
|
||||||
|
# TODO: is this correct, or do we terminate recipient subs immediately now?
|
||||||
User.findById user1.id, (err, user1) ->
|
User.findById user1.id, (err, user1) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
stripeInfo = user1.get('stripe')
|
stripeInfo = user1.get('stripe')
|
||||||
|
@ -1159,7 +1176,7 @@ describe 'Subscriptions', ->
|
||||||
user1.save (err, user1) ->
|
user1.save (err, user1) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
expect(user1.isAdmin()).toEqual(true)
|
expect(user1.isAdmin()).toEqual(true)
|
||||||
createPrepaid 'subscription', 1, (err, res, prepaid) ->
|
createPrepaid 'subscription', 1, 0, (err, res, prepaid) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
subscribeUser user1, null, prepaid.code, ->
|
subscribeUser user1, null, prepaid.code, ->
|
||||||
stripe.tokens.create {
|
stripe.tokens.create {
|
||||||
|
@ -1233,7 +1250,7 @@ describe 'Subscriptions', ->
|
||||||
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
|
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
expect(subscription).not.toBeNull()
|
expect(subscription).not.toBeNull()
|
||||||
expect(subscription.quantity).toEqual(getSubscribedQuantity(1))
|
expect(subscription?.quantity).toEqual(getSubscribedQuantity(1))
|
||||||
|
|
||||||
# Unsubscribe recipient1
|
# Unsubscribe recipient1
|
||||||
unsubscribeRecipient user1, recipients.get(1), ->
|
unsubscribeRecipient user1, recipients.get(1), ->
|
||||||
|
@ -1245,6 +1262,7 @@ describe 'Subscriptions', ->
|
||||||
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
|
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
expect(subscription).not.toBeNull()
|
expect(subscription).not.toBeNull()
|
||||||
|
return done() unless subscription
|
||||||
expect(subscription.quantity).toEqual(0)
|
expect(subscription.quantity).toEqual(0)
|
||||||
done()
|
done()
|
||||||
|
|
||||||
|
@ -1274,13 +1292,16 @@ describe 'Subscriptions', ->
|
||||||
unsubscribeRecipient user1, recipients.get(0), ->
|
unsubscribeRecipient user1, recipients.get(0), ->
|
||||||
User.findById user1.id, (err, user1) ->
|
User.findById user1.id, (err, user1) ->
|
||||||
stripeInfo = user1.get('stripe')
|
stripeInfo = user1.get('stripe')
|
||||||
|
expect(stripeInfo.customerID).toBeDefined()
|
||||||
|
expect(stripeInfo.sponsorSubscriptionID).toBeDefined()
|
||||||
|
return done() unless stripeInfo.customerID and stripeInfo.sponsorSubscriptionID
|
||||||
expect(stripeInfo.recipients.length).toEqual(recipientCount - 1)
|
expect(stripeInfo.recipients.length).toEqual(recipientCount - 1)
|
||||||
verifyNotSponsoring user1.id, recipients.get(0).id, ->
|
verifyNotSponsoring user1.id, recipients.get(0).id, ->
|
||||||
verifyNotRecipient recipients.get(0).id, ->
|
verifyNotRecipient recipients.get(0).id, ->
|
||||||
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
|
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
expect(subscription).not.toBeNull()
|
expect(subscription).not.toBeNull()
|
||||||
expect(subscription.quantity).toEqual(getSubscribedQuantity(recipientCount - 1))
|
expect(subscription?.quantity).toEqual(getSubscribedQuantity(recipientCount - 1))
|
||||||
|
|
||||||
# Unsubscribe second recipient
|
# Unsubscribe second recipient
|
||||||
unsubscribeRecipient user1, recipients.get(1), ->
|
unsubscribeRecipient user1, recipients.get(1), ->
|
||||||
|
@ -1292,7 +1313,7 @@ describe 'Subscriptions', ->
|
||||||
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
|
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
expect(subscription).not.toBeNull()
|
expect(subscription).not.toBeNull()
|
||||||
expect(subscription.quantity).toEqual(getSubscribedQuantity(recipientCount - 2))
|
expect(subscription?.quantity).toEqual(getSubscribedQuantity(recipientCount - 2))
|
||||||
|
|
||||||
# Unsubscribe self
|
# Unsubscribe self
|
||||||
User.findById user1.id, (err, user1) ->
|
User.findById user1.id, (err, user1) ->
|
||||||
|
@ -1312,7 +1333,7 @@ describe 'Subscriptions', ->
|
||||||
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
|
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
|
||||||
expect(err).toBeNull()
|
expect(err).toBeNull()
|
||||||
expect(subscription).not.toBeNull()
|
expect(subscription).not.toBeNull()
|
||||||
expect(subscription.quantity).toEqual(getSubscribedQuantity(recipientCount - 3))
|
expect(subscription?.quantity).toEqual(getSubscribedQuantity(recipientCount - 3))
|
||||||
done()
|
done()
|
||||||
|
|
||||||
xit 'Unsubscribed user1 subscribes 13 users, unsubcribes 2', (done) ->
|
xit 'Unsubscribed user1 subscribes 13 users, unsubcribes 2', (done) ->
|
||||||
|
|
Loading…
Reference in a new issue