Merge branch 'master' into production

This commit is contained in:
Matt Lott 2016-08-05 13:28:36 -07:00
commit 8baa57748a
37 changed files with 4888 additions and 619 deletions

View file

@ -45,7 +45,7 @@ module.exports.applyErrorsToForm = (el, errors, warning=false) ->
for error in errors for error in errors
if error.code is tv4.errorCodes.OBJECT_REQUIRED if error.code is tv4.errorCodes.OBJECT_REQUIRED
prop = _.last(_.string.words(error.message)) # hack prop = _.last(_.string.words(error.message)) # hack
message = 'Required field' message = $.i18n.t('common.required_field')
else if error.dataPath else if error.dataPath
prop = error.dataPath[1..] prop = error.dataPath[1..]

View file

@ -22,6 +22,9 @@ LOG = false
# * Sprite map generation # * Sprite map generation
# * Connecting to Firebase # * Connecting to Firebase
# LevelLoader depends on SuperModel retrying timed out requests, as these occasionally happen during play.
# If LevelLoader ever moves away from SuperModel, it will have to manage its own retries.
module.exports = class LevelLoader extends CocoClass module.exports = class LevelLoader extends CocoClass
constructor: (options) -> constructor: (options) ->

View file

@ -356,7 +356,7 @@
submit_patch: "Submit Patch" submit_patch: "Submit Patch"
submit_changes: "Submit Changes" submit_changes: "Submit Changes"
save_changes: "Save Changes" save_changes: "Save Changes"
required_field: "Required field" required_field: "required" # {change}
general: general:
and: "and" and: "and"
@ -885,7 +885,9 @@
evaluate_recommend: "Evaluate/Recommend" evaluate_recommend: "Evaluate/Recommend"
approve_funds: "Approve Funds" approve_funds: "Approve Funds"
no_purchaser_role: "No role in purchase decisions" no_purchaser_role: "No role in purchase decisions"
organization_label: "Name of School/District" district_label: "District"
district_na: "Enter N/A if not applicable"
organization_label: "School" # {change}
city: "City" city: "City"
state: "State" state: "State"
country: "Country" country: "Country"

2019
app/locale/hr.coffee Normal file

File diff suppressed because it is too large Load diff

View file

@ -39,8 +39,10 @@ module.exports =
'id': { nativeDescription: 'Bahasa Indonesia', englishDescription: 'Indonesian' } 'id': { nativeDescription: 'Bahasa Indonesia', englishDescription: 'Indonesian' }
'it': { nativeDescription: 'Italiano', englishDescription: 'Italian' } 'it': { nativeDescription: 'Italiano', englishDescription: 'Italian' }
'he': { nativeDescription: 'עברית', englishDescription: 'Hebrew' } 'he': { nativeDescription: 'עברית', englishDescription: 'Hebrew' }
'hr': { nativeDescription: 'hrvatski jezik', englishDescription: 'Croatian' }
'hu': { nativeDescription: 'magyar', englishDescription: 'Hungarian' } 'hu': { nativeDescription: 'magyar', englishDescription: 'Hungarian' }
'lt': { nativeDescription: 'lietuvių kalba', englishDescription: 'Lithuanian' } 'lt': { nativeDescription: 'lietuvių kalba', englishDescription: 'Lithuanian' }
'mi': { nativeDescription: 'te reo Māori', englishDescription: 'Māori' }
'mk-MK': { nativeDescription: 'Македонски', englishDescription: 'Macedonian' } 'mk-MK': { nativeDescription: 'Македонски', englishDescription: 'Macedonian' }
'hi': { nativeDescription: 'मानक हिन्दी', englishDescription: 'Hindi' } 'hi': { nativeDescription: 'मानक हिन्दी', englishDescription: 'Hindi' }
'ms': { nativeDescription: 'Bahasa Melayu', englishDescription: 'Bahasa Malaysia' } 'ms': { nativeDescription: 'Bahasa Melayu', englishDescription: 'Bahasa Malaysia' }

2019
app/locale/mi.coffee Normal file

File diff suppressed because it is too large Load diff

View file

@ -132,7 +132,7 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
help_suff: "и мы свяжемся!" help_suff: "и мы свяжемся!"
modal: modal:
# cancel: "Cancel" cancel: "Отмена"
close: "Закрыть" close: "Закрыть"
okay: "OK" okay: "OK"
@ -234,7 +234,7 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
None: "значение, которое не указывает ни на один объект (ничто)" None: "значение, которое не указывает ни на один объект (ничто)"
share_progress_modal: share_progress_modal:
blurb: "Вы отлично продвигаетесь! Расскажите своим родителям, как много вы уже выучили с CodeCombat." blurb: "Вы показываете отличные результаты! Расскажите своим родителям, как много вы уже выучили с CodeCombat."
email_invalid: "Email-адрес некорректен." email_invalid: "Email-адрес некорректен."
form_blurb: "Введите их email-адреса ниже, и мы покажем им!" form_blurb: "Введите их email-адреса ниже, и мы покажем им!"
form_label: "Email-адрес" form_label: "Email-адрес"
@ -243,9 +243,9 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
login: login:
sign_up: "Создать аккаунт" sign_up: "Создать аккаунт"
# email_or_username: "Email or username" email_or_username: "Email или имя пользователя"
log_in: "Войти" log_in: "Войти"
logging_in: "Вход..." logging_in: "Входим..."
log_out: "Выйти" log_out: "Выйти"
forgot_password: "Забыли пароль?" forgot_password: "Забыли пароль?"
authenticate_gplus: "Аутентификация G+" authenticate_gplus: "Аутентификация G+"
@ -256,72 +256,72 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
signup_switch: "Хотите создать аккаунт?" signup_switch: "Хотите создать аккаунт?"
signup: signup:
# create_student_header: "Create Student Account" create_student_header: "Создать учетную запись Ученика"
# create_teacher_header: "Create Teacher Account" create_teacher_header: "Создать учетную запись Учителя"
# create_individual_header: "Create Individual Account" create_individual_header: "Создать личную учетную запись"
# create_header: "Create Account" create_header: "Создать учетную запись"
email_announcements: "Получать оповещения по email" # {change} email_announcements: "Получать оповещения по email о новых уровнях и возможностях на CodeCombat"
creating: "Создание аккаунта..." creating: "Создаем учетную запись..."
# create_account: "Create Account" create_account: "Создать учетную запись"
sign_up: "Регистрация" sign_up: "Регистрация"
log_in: "вход с паролем" log_in: "вход с паролем"
required: "Войдите для того, чтобы продолжить." required: "Войдите для того, чтобы продолжить."
login_switch: "Уже есть аккаунт?" login_switch: "Уже есть учетная запись?"
school_name: "Название школы and город" school_name: "Название школы и город"
optional: "не обязательно" optional: "не обязательно"
school_name_placeholder: "Школа № 2, город Электросталь, Московская область" school_name_placeholder: "Школа № 2, город Электросталь, Московская область"
# connect_with: "Connect with:" connect_with: "Зарегистрироваться с помощью:"
connected_gplus_header: "Вы успешно авторизовались через Google+!" connected_gplus_header: "Вы успешно авторизовались через Google+!"
connected_gplus_p: "Теперь можно войти используя аккаунт Google+." connected_gplus_p: "Теперь можно войти используя аккаунт Google+."
gplus_exists: "У вас уже имеется аккаунт связанный с Google+!" gplus_exists: "У вас уже имеется аккаунт связанный с Google+!"
connected_facebook_header: "Вы успешно авторизовались через Facebook!" connected_facebook_header: "Вы успешно авторизовались через Facebook!"
connected_facebook_p: "Теперь можно войти используя аккаунт Facebook." connected_facebook_p: "Теперь можно войти используя аккаунт Facebook."
facebook_exists: "У вас уже имеется аккаунт связанный сh Facebook!" facebook_exists: "У вас уже имеется аккаунт связанный с Facebook!"
hey_students: "Студенты, введите код от класса вашего учителя." hey_students: "Студенты, введите код от класса вашего учителя."
# birthday: "Birthday" birthday: "День рождения"
# parent_email_blurb: "We know you can't wait to learn programming — we're excited too! Your parents will receive an email with further instructions on how to create an account for you. Email {{email_link}} if you have any questions." parent_email_blurb: "Мы знаем что вы в нетерпении начать учиться программировать — мы тоже! Ваши родители получат письмо с дальнейшими инструкциями, как создать учетную запись. Пишите нам на email {{email_link}} если есть любые вопросы."
# classroom_not_found: "No classes exist with this Class Code. Check your spelling or ask your teacher for help." classroom_not_found: "Нет класса с таким кодом. Проверьте написание или попросите учителя помочь."
# checking: "Checking..." checking: "Проверяем..."
# account_exists: "This email is already in use:" # {change} account_exists: "Этот email-адрес уже используется:" # {change}
# sign_in: "Sign in" sign_in: "Зарегистрироваться"
# email_good: "Email looks good!" email_good: "С email-адресом все в порядке!"
# name_taken: "Username already taken! Try {{suggestedName}}?" name_taken: "Имя пользователя уже используется! Может {{suggestedName}}?"
# name_available: "Username available!" name_available: "Имя пользователя доступно!"
# name_is_email: "Username may not be an email" name_is_email: "Имя пользователя не должно быть email-адресом"
# choose_type: "Choose your account type:" choose_type: "Выбирите тип учетной записи:"
# teacher_type_1: "Teach programming using CodeCombat!" teacher_type_1: "Обучайте с помощью CodeCombat!"
# teacher_type_2: "Set up your class" teacher_type_2: "Настраивайте ваш класс"
# teacher_type_3: "Access Course Guides" teacher_type_3: "Получите доступ к учебным материалам "
# teacher_type_4: "View student progress" teacher_type_4: "Следите за прогрессом учеников"
# signup_as_teacher: "Sign up as a Teacher" signup_as_teacher: "Зарегистрироваться как Учитель"
# student_type_1: "Learn to program while playing an engaging game!" student_type_1: "Учитесь программировать, пока играете в захватывающую игру!"
# student_type_2: "Play with your class" student_type_2: "Играйте вместе с классом"
# student_type_3: "Compete in arenas" student_type_3: "Соревнуйтесь на аренах"
# student_type_4: "Choose your hero!" student_type_4: "Выбирайте своего героя!"
# student_type_5: "Have your Class Code ready!" student_type_5: "Нужен актуальный код для класса!"
# signup_as_student: "Sign up as a Student" signup_as_student: "Зарегистрироваться как Ученик"
# individuals_or_parents: "Individuals & Parents" individuals_or_parents: "Индивидуальный и Родители"
# individual_type: "For players learning to code outside of a class. Parents should sign up for an account here." individual_type: "Для игроков, которые учатся без класса. Родители должны создать учетную запись."
# signup_as_individual: "Sign up as an Individual" signup_as_individual: "Зарегистрироваться как индивидуальный игрок"
# enter_class_code: "Enter your Class Code" enter_class_code: "Введите ваш код для класса"
# enter_birthdate: "Enter your birthdate:" enter_birthdate: "Введите вашу дату рождения:"
# ask_teacher_1: "Ask your teacher for your Class Code." ask_teacher_1: "Спросите вашего учителя код для класса."
# ask_teacher_2: "Not part of a class? Create an " ask_teacher_2: "Не относишься к учебному классу? Создай "
# ask_teacher_3: "Individual Account" ask_teacher_3: "Личную учетную запись"
# ask_teacher_4: " instead." ask_teacher_4: " вместо этого."
# about_to_join: "You're about to join:" about_to_join: "Вы присоединяетесь к:"
# enter_parent_email: "Enter your parents email address:" enter_parent_email: "Введите email-адрес одного из родителей:"
# parent_email_error: "Something went wrong when trying to send the email. Check the email address and try again." parent_email_error: "Что-то пошло не так, когда мы отправляли письмо. Проверьте email-адрес и повторите."
# parent_email_sent: "Weve sent an email with further instructions on how to create an account. Ask your parent to check their inbox." parent_email_sent: "Мы послали электронное письмо с дальнейшими инструкциями, как создать учетную запись. Попроси родителей проверить их входящие письма."
# account_created: "Account Created!" account_created: "Учетная запись создана!"
# confirm_student_blurb: "Write down your information so that you don't forget it. Your teacher can also help you reset your password at any time." confirm_student_blurb: "Запишите ваши данные, чтобы не забыть их. Ваш учитель, если что сможет помочь сбросить пароль в любое время."
# confirm_individual_blurb: "Write down your login information in case you need it later. Verify your email so you can recover your account if you ever forget your password - check your inbox!" confirm_individual_blurb: "Write down your login information in case you need it later. Verify your email so you can recover your account if you ever forget your password - check your inbox!"
# write_this_down: "Write this down:" write_this_down: "Запишите это:"
# start_playing: "Start Playing!" start_playing: "Начать играть!"
# sso_connected: "Successfully connected with:" sso_connected: "Успешно подключились с помощью:"
recover: recover:
recover_account_title: "Восстановить аккаунт" recover_account_title: "Восстановить учетную запись"
send_password: "Отправить пароль для восстановления" send_password: "Отправить пароль для восстановления"
recovery_sent: "Письмо с паролем отправлено." recovery_sent: "Письмо с паролем отправлено."
@ -340,8 +340,8 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
saving: "Сохранение..." saving: "Сохранение..."
sending: "Отправка..." sending: "Отправка..."
send: "Отправить" send: "Отправить"
# sent: "Sent" sent: "Отправлено"
# type: "Type" type: "Тип"
cancel: "Отмена" cancel: "Отмена"
save: "Сохранить" save: "Сохранить"
publish: "Опубликовать" publish: "Опубликовать"
@ -411,8 +411,8 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
wizard: "Волшебник" wizard: "Волшебник"
first_name: "Имя" first_name: "Имя"
last_name: "Фамилия" last_name: "Фамилия"
# last_initial: "Last Initial" last_initial: "Инициалы фамилии"
username: "Имя юзера" username: "Имя пользователя"
units: units:
second: "секунда" second: "секунда"
@ -431,13 +431,13 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
years: "лет" years: "лет"
play_level: play_level:
level_complete: "Уровень завершен" level_complete: "Уровень пройден"
completed_level: "Завершённый уровень:" completed_level: "Пройденный уровень:"
course: "Курс:" course: "Курс:"
done: "Готово" done: "Готово"
next_level: "Следующий уровень" next_level: "Следующий уровень"
next_game: "Следующая игра" next_game: "Следующая игра"
# programming_language: "Programming language" programming_language: "Язык программирования"
show_menu: "Показать меню игры" show_menu: "Показать меню игры"
home: "На главную" # Not used any more, will be removed soon. home: "На главную" # Not used any more, will be removed soon.
level: "Уровень" # Like "Level: Dungeons of Kithgard" level: "Уровень" # Like "Level: Dungeons of Kithgard"
@ -458,12 +458,12 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
reload_confirm: "Перезагрузить всё" reload_confirm: "Перезагрузить всё"
victory: "Победа" victory: "Победа"
victory_title_prefix: "Уровень " victory_title_prefix: "Уровень "
victory_title_suffix: " завершён" victory_title_suffix: " пройден"
victory_sign_up: "Зарегистрируйтесь, чтобы сохранить прогресс" victory_sign_up: "Зарегистрируйтесь, чтобы сохранить прогресс"
victory_sign_up_poke: "Хотите сохранить ваш код? Создайте бесплатный аккаунт!" victory_sign_up_poke: "Хотите сохранить ваш код? Создайте бесплатную учетную запись!"
victory_rate_the_level: "Оцените уровень:" # {change} victory_rate_the_level: "Оцените уровень:" # {change}
victory_return_to_ladder: "Вернуться к ладдеру" victory_return_to_ladder: "Вернуться к таблице"
victory_saving_progress: "Процесс сохранения" victory_saving_progress: "Сохранить прогресс"
victory_go_home: "На главную" victory_go_home: "На главную"
victory_review: "Расскажите нам больше!" victory_review: "Расскажите нам больше!"
victory_review_placeholder: "Как вам уровень?" victory_review_placeholder: "Как вам уровень?"
@ -474,13 +474,13 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
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_no_progress_for_teachers: "Прогресс не сохраняется для учителей. Но, вы можете для себя добавить аккаунт студента в свою классную комнату." victory_no_progress_for_teachers: "Прогресс не сохраняется для учителей. Но, вы можете для себя добавить учетную запись ученика в свою классную комнату."
guide_title: "Руководство" guide_title: "Руководство"
tome_cast_button_run: "Запустить" tome_cast_button_run: "Запустить"
tome_cast_button_running: "В процессе" tome_cast_button_running: "В процессе"
tome_cast_button_ran: "Запущен" tome_cast_button_ran: "Запущен"
tome_submit_button: "Завершить" tome_submit_button: "Завершить"
tome_reload_method: "Загрузить оригинальный код для этого метода" # {change} tome_reload_method: "Загрузить оригинальный код и начать уровень заново" # {change}
tome_available_spells: "Доступные заклинания" tome_available_spells: "Доступные заклинания"
tome_your_skills: "Ваши навыки" tome_your_skills: "Ваши навыки"
tome_current_method: "Текущий метод" tome_current_method: "Текущий метод"
@ -491,13 +491,13 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
keyboard_shortcuts: "Горячие клавиши" keyboard_shortcuts: "Горячие клавиши"
loading_ready: "Готово!" loading_ready: "Готово!"
loading_start: "Начать уровень" loading_start: "Начать уровень"
problem_alert_title: "Исправьте ваш Код" problem_alert_title: "Исправьте код"
time_current: "Текущее:" time_current: "Текущее:"
time_total: "Максимальное:" time_total: "Максимальное:"
time_goto: "Перейти на:" time_goto: "Перейти на:"
non_user_code_problem_title: "Невозможно загрузить уровень" non_user_code_problem_title: "Невозможно загрузить уровень"
infinite_loop_title: "Обнаружен бесконечный цикл" infinite_loop_title: "Обнаружен бесконечный цикл"
infinite_loop_description: "Код сотворения мира не завершил выполнение. Это могло случиться из-за реально медленного кода или наличия бесконечного цикла. Или там может быть баг. Вы можете попытаться запустить этот код еще раз или сбросить код в состояние по умолчанию. Если проблема не будет решена, дайте нам знать." infinite_loop_description: "Код сотворения мира не завершил выполнение. Это могло случиться из-за очень медленного кода или наличия бесконечного цикла. Или там может быть баг. Вы можете попытаться запустить этот код еще раз или сбросить код в состояние по умолчанию. Если проблема не будет решена, дайте нам знать."
check_dev_console: "Вы так же можете открыть консоль разработчика, чтобы увидеть, что может идти не так." check_dev_console: "Вы так же можете открыть консоль разработчика, чтобы увидеть, что может идти не так."
check_dev_console_link: "(инструкции)" check_dev_console_link: "(инструкции)"
infinite_loop_try_again: "Попробовать снова" infinite_loop_try_again: "Попробовать снова"
@ -542,32 +542,32 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
tip_optimization_operator: "В каждом языке есть оператор оптимизации. В большинстве языков это оператор //" tip_optimization_operator: "В каждом языке есть оператор оптимизации. В большинстве языков это оператор //"
tip_lines_of_code: "Измерение прогресса программирования в строках кода - это как измерять прогресс построения самолета по его весу. — Bill Gates" tip_lines_of_code: "Измерение прогресса программирования в строках кода - это как измерять прогресс построения самолета по его весу. — Bill Gates"
tip_source_code: "Я хочу изменить мир, но они вряд ли дадут мне исходники." tip_source_code: "Я хочу изменить мир, но они вряд ли дадут мне исходники."
tip_javascript_java: "Java к JavaScript относится так же, как кол относится к колготкам. - Chris Heilmann (перефраз.)" tip_javascript_java: "Java к JavaScript относится так же, как кол относится к колготкам. (перефраз.) - Крис Хейльман"
tip_move_forward: "Что бы вы ни делали, вы должны двигаться вперед. - Martin Luther King Jr" tip_move_forward: "Что бы вы ни делали, вы должны двигаться вперед. - Мартин Лютер Кинг Мл."
tip_google: "У вас проблема, которую вы не можете решить? Гуглите!" tip_google: "У вас проблема, которую вы не можете решить? Гуглите!"
tip_adding_evil: "Добавим щепотку зла." tip_adding_evil: "Добавим щепотку зла."
tip_hate_computers: "Есть одна вещь в людях, которые думают, что они ненавидят компьютеры. Что они на самом деле ненавидят, так это плохих программистов. - Larry Niven" tip_hate_computers: "Есть одна вещь в людях, которые думают, что они ненавидят компьютеры. Что они на самом деле ненавидят, так это плохих программистов. - Ларри Нивен"
tip_open_source_contribute: "Вы можете помочь сделать CodeCombat лучше!" tip_open_source_contribute: "Ты можешь помочь сделать CodeCombat лучше!"
tip_recurse: "Итерация свойственна человеку, рекурсия божественна. - L. Peter Deutsch" tip_recurse: "Итерация свойственна человеку, рекурсия божественна. - L. Peter Deutsch"
tip_free_your_mind: "Отвлекись от всего, Нео. Страх, неверие, сомнения отбрось — очисти свой разум. - Morpheus" tip_free_your_mind: "Отвлекись от всего, Нео. Страх, неверие, сомнения отбрось — очисти свой разум. - Морфей"
tip_strong_opponents: "Даже сильнейший противник имеет слабость. - Itachi Uchiha" tip_strong_opponents: "Даже сильнейший противник имеет слабость. - Итачи Учиха"
tip_paper_and_pen: "Прежде чем начать программировать, вы всегда можете попробовать с листом бумаги и ручкой." tip_paper_and_pen: "Прежде чем начать программировать, вы всегда можете попробовать с листом бумаги и ручкой."
tip_solve_then_write: "Сперва реши задачу, затем пиши код. - Джон Джонсон" tip_solve_then_write: "Сперва реши задачу, затем пиши код. - Джон Джонсон"
tip_compiler_ignores_comments: "Порой мне кажется, что компилятор игнорирует мои комментарии." tip_compiler_ignores_comments: "Порой мне кажется, что компилятор игнорирует мои комментарии."
tip_understand_recursion: "Единственный способ понять рекурсию, это понять рекурсию." tip_understand_recursion: "Единственный способ понять рекурсию, это понять рекурсию."
tip_life_and_polymorphism: "Открытый исходный код это как полоностью полиморфная гетерогенная структура: Все типы приветствуются." tip_life_and_polymorphism: "Открытый исходный код это как полоностью полиморфная гетерогенная структура: Все типы приветствуются."
tip_mistakes_proof_of_trying: "Ошибка в коде подтвердила твои старания." tip_mistakes_proof_of_trying: "Ошибка в коде подтвердила твои старания."
# tip_adding_orgres: "Rounding up ogres." tip_adding_orgres: "Собираем огров."
# tip_sharpening_swords: "Sharpening the swords." tip_sharpening_swords: "Точим мечи."
# tip_ratatouille: "You must not let anyone define your limits because of where you come from. Your only limit is your soul. - Gusteau, Ratatouille" # tip_ratatouille: "You must not let anyone define your limits because of where you come from. Your only limit is your soul. - Gusteau, Ratatouille"
# tip_nemo: "When life gets you down, want to know what you've gotta do? Just keep swimming, just keep swimming. - Dory, Finding Nemo" # tip_nemo: "When life gets you down, want to know what you've gotta do? Just keep swimming, just keep swimming. - Dory, Finding Nemo"
# tip_internet_weather: "Just move to the internet, it's great here. We get to live inside where the weather is always awesome. - John Green" # tip_internet_weather: "Just move to the internet, it's great here. We get to live inside where the weather is always awesome. - John Green"
# tip_nerds: "Nerds are allowed to love stuff, like jump-up-and-down-in-the-chair-can't-control-yourself love it. - John Green" # tip_nerds: "Nerds are allowed to love stuff, like jump-up-and-down-in-the-chair-can't-control-yourself love it. - John Green"
# tip_self_taught: "I taught myself 90% of what I've learned. And that's normal! - Hank Green" tip_self_taught: "Я самостоятельно научился 90% из того чему учился. И это нормально! - Хэнк Грин"
# tip_luna_lovegood: "Don't worry, you're just as sane as I am. - Luna Lovegood" tip_luna_lovegood: "Не переживай, ты также в своем уме, как и я. - Луна Лавгуд"
# tip_good_idea: "The best way to have a good idea is to have a lot of ideas. - Linus Pauling" tip_good_idea: "Лучший способ найти хорошую идею - иметь множество идей. - Линус Полинг"
# tip_programming_not_about_computers: "Computer Science is no more about computers than astronomy is about telescopes. - Edsger Dijkstra" # tip_programming_not_about_computers: "Computer Science is no more about computers than astronomy is about telescopes. - Edsger Dijkstra"
# tip_mulan: "Believe you can, then you will. - Mulan" tip_mulan: "Верь, что можешь и получится. - Мулан"
game_menu: game_menu:
inventory_tab: "Инвентарь" inventory_tab: "Инвентарь"
@ -694,7 +694,7 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
javascript_blurb: "Язык для Сети." javascript_blurb: "Язык для Сети."
coffeescript_blurb: "Улучшенный синтаксис JavaScript." coffeescript_blurb: "Улучшенный синтаксис JavaScript."
lua_blurb: "Скриптовый язык для игр." lua_blurb: "Скриптовый язык для игр."
# java_blurb: "(Subscriber Only) Android and enterprise." java_blurb: "(только для подписчиков) Андроид и бизнес."
status: "Статус" status: "Статус"
hero_type: "Тип" hero_type: "Тип"
weapons: "Оружие" weapons: "Оружие"
@ -705,7 +705,7 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
health: "Жизнь" health: "Жизнь"
speed: "Скорость" speed: "Скорость"
regeneration: "Регенерация" regeneration: "Регенерация"
range: "Зона" # As in "attack or visual range" range: "Дальность" # As in "attack or visual range"
blocks: "Блокирует" # As in "this shield blocks this much damage" blocks: "Блокирует" # As in "this shield blocks this much damage"
backstab: "Со спины" # As in "this dagger does this much backstab damage" backstab: "Со спины" # As in "this dagger does this much backstab damage"
skills: "Умения" skills: "Умения"
@ -775,32 +775,33 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
story_link: "История" story_link: "История"
press_link: "Прессе" press_link: "Прессе"
mission_title: "Наша задача: сделать доступным программирование для каждого учащегося на земле." mission_title: "Наша задача: сделать доступным программирование для каждого учащегося на земле."
# mission_description_1: "<strong>Programming is magic</strong>. It's the ability to create things from pure imagination. We started CodeCombat to give learners the feeling of wizardly power at their fingertips by using <strong>typed code</strong>." mission_description_1: "<strong>Программирование - это магия</strong>.
# mission_description_2: "As it turns out, that enables them to learn faster too. WAY faster. It's like having a conversation instead of reading a manual. We want to bring that conversation to every school and to <strong>every student</strong>, because everyone should have the chance to learn the magic of programming." Это способность создавать что-то с помощью воображения. Мы создавали CodeCombat, чтобы дать учащимся чувство силы волшебства на кончиках пальцев, когда они <strong>пишут код</strong>."
mission_description_2: "Как оказалось, это позволяет им учится быстрее. СИЛЬНО быстрее. Это как живой рассказ вместо чтения учебника. Мы хотим принести этот метод в каждую школу и <strong>каждому ученику</strong>, потому что все должны иметь шанс научится магии программирования."
team_title: "Присоединяйтесь к команде CodeCombat" team_title: "Присоединяйтесь к команде CodeCombat"
# team_values: "We value open and respectful dialog, where the best idea wins. Our decisions are grounded in customer research and our process is focused on delivering tangible results for them. Everyone is hands-on, from our CEO to our GitHub contributors, because we value growth and learning in our team." team_values: "Мы ценим открытый и вежливый диалог, где побеждают лучшие идеи. Наши решения основаны на иследовании пожеланий клиентов и наш процесс направлен на то, чтобы приносить осязаемые результаты им. У нас все при деле, от CEO до контрибьютеров на GitHub, потому что мы ценим рост и обучение в нашей команде."
nick_title: "Сооснователь" # {change} nick_title: "Сооснователь, CEO"
nick_blurb: "Гуру мотивации" nick_blurb: "Гуру мотивации"
matt_title: "Сооснователь" # {change} matt_title: "Сооснователь, CTO"
cat_title: "Главный ремесленник" # {change} cat_title: "Гейм дизайнер"
cat_blurb: "Повелитель стихий" cat_blurb: "Повелитель стихий"
scott_title: "Сооснователь" # {change} scott_title: "Сооснователь, инженер программист" # {change}
scott_blurb: "Разумный" scott_blurb: "Благоразумный"
# maka_title: "Customer Advocate" maka_title: "Адвокат клиентов"
# maka_blurb: "Storyteller" maka_blurb: "Рассказчик"
# rob_title: "Software Engineer" rob_title: "Инженер программист"
# rob_blurb: "Codes things and stuff" rob_blurb: "Программирует все"
# josh_c_title: "Game Designer" josh_c_title: "Гейм дизайнер"
# josh_c_blurb: "Designs games" josh_c_blurb: "Делает игры"
# robin_title: "UX Design & Research" robin_title: "UX дизайнер & Исследования"
# robin_blurb: "Scaffolding" # robin_blurb: "Scaffolding"
josh_title: "Дизайнер игры" josh_title: "Гейм дизайнер"
josh_blurb: "Пол - это лава" josh_blurb: "Пол - это лава"
# phoenix_title: "Software Engineer" phoenix_title: "Инженер программист"
# nolan_title: "Territory Manager" nolan_title: "Региональный менеджер"
# elliot_title: "Partnership Manager" elliot_title: "Менеджер партнерства"
# elliot_blurb: "Mindreader" elliot_blurb: "Читающий мысли"
# lisa_title: "Market Development Rep" lisa_title: "Представитель по развитию рынка"
retrostyle_title: "Иллюстрирование" retrostyle_title: "Иллюстрирование"
retrostyle_blurb: "RetroStyle Games" retrostyle_blurb: "RetroStyle Games"
jose_title: "Музыка" jose_title: "Музыка"

View file

@ -298,6 +298,10 @@ class ModelResource extends Resource
@loadsAttempted = 0 @loadsAttempted = 0
load: -> load: ->
# TODO: Track progress on requests and don't retry if progress was made recently.
# Probably use _.debounce and attach event listeners to xhr objects.
# This logic is for handling failed responses for level loading.
timeToWait = 5000 timeToWait = 5000
tryLoad = => tryLoad = =>
return if this.isLoaded return if this.isLoaded

View file

@ -38,7 +38,7 @@ module.exports = class ThangType extends CocoModel
urlRoot: '/db/thang.type' urlRoot: '/db/thang.type'
building: {} building: {}
editableByArtisans: true editableByArtisans: true
@defaultActions: ['idle', 'die', 'move', 'attack'] @defaultActions: ['idle', 'die', 'move', 'attack', 'trick', 'cast']
initialize: -> initialize: ->
super() super()

View file

@ -9,7 +9,7 @@ _.extend CampaignSchema.properties, {
i18n: {type: 'object', title: 'i18n', format: 'i18n', props: ['name', 'fullName', 'description']} i18n: {type: 'object', title: 'i18n', format: 'i18n', props: ['name', 'fullName', 'description']}
fullName: { type: 'string', title: 'Full Name', description: 'Ex.: "Kithgard Dungeon"' } fullName: { type: 'string', title: 'Full Name', description: 'Ex.: "Kithgard Dungeon"' }
description: { type: 'string', format: 'string', description: 'How long it takes and what players learn.' } description: { type: 'string', format: 'string', description: 'How long it takes and what players learn.' }
type: c.shortString(title: 'Type', description: 'What kind of campaign this is.', 'enum': ['hero', 'course']) type: c.shortString(title: 'Type', description: 'What kind of campaign this is.', 'enum': ['hero', 'course','hidden'])
ambientSound: c.object {}, ambientSound: c.object {},
mp3: { type: 'string', format: 'sound-file' } mp3: { type: 'string', format: 'sound-file' }

View file

@ -1,6 +1,6 @@
#components-documentation-view #components-documentation-view
background-color: #e4cf8c background-color: #e4cf8c
height: 100% height: calc(100% - 90px)
#toggle-all-component-code #toggle-all-component-code
margin: 10px margin: 10px

View file

@ -1,96 +0,0 @@
@import "app/styles/mixins"
@import "app/styles/bootstrap/variables"
#request-quote-view
#site-content-area
margin: 50px 0 100px
.row
margin: 20px 0
#conversion-warning
margin-top: 20px
.form-group
label
margin-bottom: 0
label.checkbox
font-weight: normal
.help-block
margin: -4px 0 2px
p
margin: 0 0 20px
.checkbox, .checkbox-inline
input
margin-top: 8px
#other-education-level-input
label
display: inline-block
display: inline-block
width: 200px
margin-left: 5px
#submit-request-btn
margin-left: 10px
// After submit (anonymous)
h5
margin-top: 50px
#social-network-signups
margin: 20px 0
button
margin-left: 10px
.text-h1
margin: 40px 0 30px
.algolia-autocomplete
width: 100%;
.aa-input
width: 100%
.aa-hint
color: #999
width: 100%
.aa-dropdown-menu
background-color: #fff
border: 1px solid #999
border-top: none
width: 100%
.aa-suggestion
cursor: pointer
padding: 5px 4px
border-top: 1px solid #ccc
.school
font-family: Open Sans
font-size: 14px
line-height: 20px
font-weight: bold
.district
font-family: Open Sans
font-size: 14px
line-height: 20px
span
white-space: nowrap
.aa-suggestion.aa-cursor
background-color: #B2D7FF
em
font-weight: bold
font-style: normal

View file

@ -0,0 +1,81 @@
#create-teacher-account-view, #convert-to-teacher-account-view, #request-quote-view
.algolia-autocomplete
width: 100%;
.aa-input
width: 100%
.aa-hint
color: #999
width: 100%
.aa-dropdown-menu
background-color: #fff
border: 1px solid #999
border-top: none
width: 100%
.aa-suggestion
cursor: pointer
padding: 5px 4px
border-top: 1px solid #ccc
.school
font-family: Open Sans
font-size: 14px
line-height: 20px
font-weight: bold
.district
font-family: Open Sans
font-size: 14px
line-height: 20px
span
white-space: nowrap
.aa-suggestion.aa-cursor
background-color: #B2D7FF
em
font-weight: bold
font-style: normal
// TODO: update form validation instead of overwriting these styles
.control-label
font-weight: bold
width: 100%
.error-help-block
margin-top: inherit
margin-bottom: 0px
float: right
font-size: 13px
font-style: italic
font-weight: normal
.text-muted
float: right
font-size: 13px
font-style: italic
font-weight: normal
.nullify-form-control
display: inherit
width: inherit
height: inherit
padding: inherit
font-size: inherit
line-height: inherit
color: inherit
vertical-align: inherit
background-color: inherit
background-image: inherit
border: inherit
border-radius: inherit
-webkit-box-shadow: inherit
box-shadow: inherit
-webkit-transition: inherit
transition: inherit

View file

@ -16,6 +16,8 @@ block content
th Course th Course
each course in view.courses.models each course in view.courses.models
- var campaign = view.campaigns.get(course.get('campaignID')); - var campaign = view.campaigns.get(course.get('campaignID'));
if !campaign
- continue;
- var levels = campaign.getLevels().models; - var levels = campaign.getLevels().models;
- levelsTotal += levels.length; - levelsTotal += levels.length;
tr tr
@ -26,6 +28,8 @@ block content
td All td All
each course in view.courses.models each course in view.courses.models
- var campaign = view.campaigns.get(course.get('campaignID')); - var campaign = view.campaigns.get(course.get('campaignID'));
if !campaign
- continue;
- var levels = campaign.getLevels().models; - var levels = campaign.getLevels().models;
- levelCounts = levels.length; - levelCounts = levels.length;
strong #{course.get('name')} strong #{course.get('name')}

View file

@ -30,8 +30,8 @@ block content
thead thead
tr tr
th Created th Created
th NCES District
th School Name th School Name
th School District
th.number NCES District Schools th.number NCES District Schools
th.number NCES District Students th.number NCES District Students
th.number NCES School Students th.number NCES School Students
@ -44,8 +44,8 @@ block content
- continue; - continue;
tr tr
td.created= trialRequest.get('created').substring(0, 10) td.created= trialRequest.get('created').substring(0, 10)
td= trialRequest.get('properties').nces_district || '' td= trialRequest.get('properties').nces_name || trialRequest.get('properties').organization || ''
td= trialRequest.get('properties').organization || '' td= trialRequest.get('properties').nces_district || trialRequest.get('properties').district || ''
td= trialRequest.get('properties').nces_district_schools || '' td= trialRequest.get('properties').nces_district_schools || ''
td= trialRequest.get('properties').nces_district_students || '' td= trialRequest.get('properties').nces_district_students || ''
td= trialRequest.get('properties').nces_students || '' td= trialRequest.get('properties').nces_students || ''

View file

@ -144,12 +144,12 @@ block outer_content
div.tab-pane#related-achievements-view div.tab-pane#related-achievements-view
div.tab-pane#editor-level-documentation div.tab-pane#editor-level-documentation
ul.nav.nav-pills.nav-justified
li
a(href="#components-documentation-view", data-toggle="pill", data-i18n="resources.components") Components
li
a(href="#systems-documentation-view", data-toggle="pill", data-i18n="resources.systems") Systems
div.tab-content div.tab-content
ul.nav.nav-pills.nav-justified
li
a(href="#components-documentation-view", data-toggle="pill", data-i18n="resources.components") Components
li
a(href="#systems-documentation-view", data-toggle="pill", data-i18n="resources.systems") Systems
div.tab-pane#components-documentation-view div.tab-pane#components-documentation-view
div.tab-pane#systems-documentation-view div.tab-pane#systems-documentation-view

View file

@ -1,23 +1,25 @@
button.btn.btn-primary#new-achievement-button(disabled=(me.isAdmin() === true || me.isArtisan() === true) ? undefined : "true" data-i18n="editor.new_achievement_title") Create a New Achievement .nano.editor-nano-container
.nano-content
button.btn.btn-primary#new-achievement-button(disabled=(me.isAdmin() === true || me.isArtisan() === true) ? undefined : "true" data-i18n="editor.new_achievement_title") Create a New Achievement
if !view.achievements.models.length if !view.achievements.models.length
.panel .panel
.panel-body .panel-body
p(data-i18n="editor.no_achievements") No achievements added for this level yet. p(data-i18n="editor.no_achievements") No achievements added for this level yet.
else else
table.table.table-hover table.table.table-hover
thead thead
tr tr
th th
th(data-i18n="general.name") Name th(data-i18n="general.name") Name
th(data-i18n="general.description") Description th(data-i18n="general.description") Description
th XP th XP
tbody tbody
each achievement in view.achievements.models each achievement in view.achievements.models
tr tr
td(style="width: 20px") td(style="width: 20px")
img.achievement-icon-small(src=achievement.getImageURL() alt="#{achievement.get('name') icon") img.achievement-icon-small(src=achievement.getImageURL() alt="#{achievement.get('name') icon")
td td
a(href="/editor/achievement/#{achievement.get('slug')}")= achievement.get('name', true) a(href="/editor/achievement/#{achievement.get('slug')}")= achievement.get('name', true)
td= achievement.get('description', true) td= achievement.get('description', true)
td= achievement.get('worth', true) td= achievement.get('worth', true)

View file

@ -1,42 +1,44 @@
mixin task-row(cid) .nano.editor-nano-container
- var task = view.getTaskByCID(cid) .nano-content
- var taskName = task.get('name'); mixin task-row(cid)
- var isComplete = task.get('complete') - var task = view.getTaskByCID(cid)
tr.task-row(data-task-cid=cid) - var taskName = task.get('name');
td.task-check - var isComplete = task.get('complete')
div.checkbox tr.task-row(data-task-cid=cid)
input(type='checkbox', checked=(isComplete || false)).task-input td.task-check
if task.get('curEdit') == true div.checkbox
td.edit-cell input(type='checkbox', checked=(isComplete || false)).task-input
td.task-name if task.get('curEdit') == true
input(type="input", value=taskName)#cur-edit td.edit-cell
else td.task-name
td.edit-cell input(type="input", value=taskName)#cur-edit
span.glyphicon.glyphicon-edit.start-edit
td.task-name
- var result = view.getTaskURL(taskName)
if result !== null
// https://github.com/codecombat/codecombat/wiki/Tasks-Tab#<slug goes here>
a(href='https://github.com/codecombat/codecombat/wiki/Tasks-Tab#' + result, target='blank')= taskName
else else
span= taskName td.edit-cell
span.glyphicon.glyphicon-edit.start-edit
td.task-name
- var result = view.getTaskURL(taskName)
if result !== null
// https://github.com/codecombat/codecombat/wiki/Tasks-Tab#<slug goes here>
a(href='https://github.com/codecombat/codecombat/wiki/Tasks-Tab#' + result, target='blank')= taskName
else
span= taskName
block block
table.table.table-striped.table-hover table.table.table-striped.table-hover
tr tr
th.task-check Complete th.task-check Complete
th.edit-cell Edit th.edit-cell Edit
th Incomplete Tasks th Incomplete Tasks
for task in (view.taskArray() || []) for task in (view.taskArray() || [])
if task.get('revert').complete !== true if task.get('revert').complete !== true
+task-row(task.cid) +task-row(task.cid)
tr tr
th.task-check th.task-check
th.edit-cell th.edit-cell
th Completed Tasks th Completed Tasks
for task in (view.taskArray() || []) for task in (view.taskArray() || [])
if task.get('revert').complete === true if task.get('revert').complete === true
+task-row(task.cid) +task-row(task.cid)
button#create-task.btn.btn-primary Add Task button#create-task.btn.btn-primary Add Task
if view.missingDefaults().length !== 0 if view.missingDefaults().length !== 0
button#add-default-tasks.btn.btn-default Add Default Tasks button#add-default-tasks.btn.btn-default Add Default Tasks

View file

@ -28,40 +28,36 @@ block content
.row.m-y-2 .row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="general.username") span.control-label(data-i18n="general.username")
input.form-control(disabled=true value=me.get('name')) input.form-control(disabled=true value=me.get('name'))
.col-md-4.col-sm-6 .col-md-4.col-sm-6
#email-form-group.form-group #email-form-group.form-group
label.control-label(data-i18n="general.email") span.control-label(data-i18n="general.email")
input.form-control(name='email' disabled=true value=me.get('email')) input.form-control(name='email' disabled=true value=me.get('email'))
.row.m-y-2 .row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="general.first_name") span.control-label(data-i18n="general.first_name")
input.form-control(name="firstName" value=me.get('firstName') || '') input.form-control(name="firstName" value=me.get('firstName') || '')
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="general.last_name") span.control-label(data-i18n="general.last_name")
input.form-control(name="lastName" value=me.get('lastName') || '') input.form-control(name="lastName" value=me.get('lastName') || '')
.row.m-y-2 .row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
.form-group .form-group
label.control-label span.control-label
span(data-i18n="teachers_quote.phone_number") span(data-i18n="teachers_quote.phone_number")
span.spl.text-muted(data-i18n="signup.optional") span.spl.text-muted(data-i18n="signup.optional")
.help-block.small input.form-control(name="phoneNumber", data-i18n="[placeholder]teachers_quote.phone_number_help")
em.text-info(data-i18n="teachers_quote.phone_number_help")
input.form-control(name="phoneNumber")
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="teachers_quote.primary_role_label") span.control-label(data-i18n="teachers_quote.primary_role_label")
.help-block.small
em.text-info(data-i18n="teachers_quote.role_help")
select.form-control(name="role") select.form-control(name="role")
option(data-i18n="teachers_quote.role_default", , value='') option(data-i18n="teachers_quote.role_default", , value='')
option(data-i18n="courses.teacher", value="Teacher") option(data-i18n="courses.teacher", value="Teacher")
@ -75,30 +71,40 @@ block content
.row.m-y-2 .row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="teachers_quote.organization_label") span.control-label
span(data-i18n="teachers_quote.organization_label")
span.spl.text-muted(data-i18n="signup.optional")
input.form-control#organization-control(name="organization") input.form-control#organization-control(name="organization")
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="teachers_quote.city") //- TODO: algolia and form errors both change form-control
input.form-control(name="city") //- TODO: District not red on validation error
span.control-label.form-control.nullify-form-control(data-i18n="teachers_quote.district_label")
input.form-control#district-control(name="district", data-i18n="[placeholder]teachers_quote.district_na")
.row.m-y-2 .row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="teachers_quote.state") span.control-label(data-i18n="teachers_quote.city")
input.form-control(name="state") input.form-control(name="city")
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-labellabel.control-label(data-i18n="teachers_quote.country") span.control-label(data-i18n="teachers_quote.state")
input.form-control(name="state")
.row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6
.form-group
span.control-labelspan.control-label(data-i18n="teachers_quote.country")
input.form-control(name="country") input.form-control(name="country")
#form-students-info #form-students-info
.row.m-y-2 .row.m-y-2
.col-md-offset-2.col-md-4 .col-md-offset-2.col-md-4
.form-group .form-group
label.control-label(data-i18n="courses.number_programming_students") span.control-label(data-i18n="courses.number_programming_students")
.help-block.small .help-block.small
em.text-info(data-i18n="teachers_quote.num_students_help") em.text-info(data-i18n="teachers_quote.num_students_help")
select.form-control(name="numStudents") select.form-control(name="numStudents")
@ -113,7 +119,7 @@ block content
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-label span.control-label
span(data-i18n="courses.number_total_students") span(data-i18n="courses.number_total_students")
span.spl.text-muted(data-i18n="signup.optional") span.spl.text-muted(data-i18n="signup.optional")
select.form-control(name="numStudentsTotal") select.form-control(name="numStudentsTotal")
@ -126,11 +132,10 @@ block content
.form-group .form-group
.row.m-y-2 .row.m-y-2
.col-md-offset-2.col-md-10 .col-md-offset-2.col-md-4
label.control-label(data-i18n="teachers_quote.education_level_label") span.control-label(data-i18n="teachers_quote.education_level_label")
.help-block.small .help-block.small
em.text-info(data-i18n="teachers_quote.education_level_help") em.text-info(data-i18n="teachers_quote.education_level_help")
.col-md-offset-2.col-md-5
.checkbox .checkbox
label label
input(type="checkbox" name="educationLevel" value="Elementary") input(type="checkbox" name="educationLevel" value="Elementary")
@ -156,7 +161,7 @@ block content
#anything-else-row.row.m-y-2 #anything-else-row.row.m-y-2
.col-md-offset-2.col-md-8 .col-md-offset-2.col-md-8
label.control-label span.control-label
span(data-i18n="teachers_quote.anything_else") span(data-i18n="teachers_quote.anything_else")
span.spl.text-muted(data-i18n="signup.optional") span.spl.text-muted(data-i18n="signup.optional")
textarea.form-control(rows=8, name="notes") textarea.form-control(rows=8, name="notes")

View file

@ -34,49 +34,47 @@ block content
.row.m-y-2 .row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="general.username") span.control-label(data-i18n="general.username")
input.form-control(name="name") input.form-control(name="name")
.col-md-4.col-sm-6 .col-md-4.col-sm-6
#email-form-group.form-group #email-form-group.form-group
label.control-label(data-i18n="general.email") span.control-label(data-i18n="general.email")
input.form-control(name="email") input.form-control(name="email")
.row.m-y-2 .row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="general.first_name") span.control-label(data-i18n="general.first_name")
input.form-control(name="firstName") input.form-control(name="firstName")
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="general.last_name") span.control-label(data-i18n="general.last_name")
input.form-control(name="lastName") input.form-control(name="lastName")
.row.m-y-2 .row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="general.password") span.control-label(data-i18n="general.password")
input.form-control(name="password1", type="password") input.form-control(name="password1", type="password")
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="general.confirm_password") span.control-label(data-i18n="general.confirm_password")
input.form-control(name="password2", type="password") input.form-control(name="password2", type="password")
.row.m-y-2 .row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
.form-group .form-group
label.control-label span.control-label
span(data-i18n="teachers_quote.phone_number") span(data-i18n="teachers_quote.phone_number")
span.spl.text-muted(data-i18n="signup.optional") span.spl.text-muted(data-i18n="signup.optional")
.help-block.small input.form-control(name="phoneNumber", data-i18n="[placeholder]teachers_quote.phone_number_help")
em.text-info(data-i18n="teachers_quote.phone_number_help")
input.form-control(name="phoneNumber")
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="teachers_quote.primary_role_label") span.control-label(data-i18n="teachers_quote.primary_role_label")
select.form-control(name="role") select.form-control(name="role")
option(data-i18n="teachers_quote.role_default", , value='') option(data-i18n="teachers_quote.role_default", , value='')
option(data-i18n="courses.teacher", value="Teacher") option(data-i18n="courses.teacher", value="Teacher")
@ -90,30 +88,40 @@ block content
.row.m-y-2 .row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="teachers_quote.organization_label") span.control-label
span(data-i18n="teachers_quote.organization_label")
span.spl.text-muted(data-i18n="signup.optional")
input.form-control#organization-control(name="organization") input.form-control#organization-control(name="organization")
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="teachers_quote.city") //- TODO: algolia and form errors both change form-control
input.form-control(name="city") //- TODO: District not red on validation error
span.control-label.form-control.nullify-form-control(data-i18n="teachers_quote.district_label")
input.form-control#district-control(name="district", data-i18n="[placeholder]teachers_quote.district_na")
.row.m-y-2 .row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="teachers_quote.state") span.control-label(data-i18n="teachers_quote.city")
input.form-control(name="state") input.form-control(name="city")
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-labellabel.control-label(data-i18n="teachers_quote.country") span.control-label(data-i18n="teachers_quote.state")
input.form-control(name="state")
.row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6
.form-group
span.control-label(data-i18n="teachers_quote.country")
input.form-control(name="country") input.form-control(name="country")
#form-students-info #form-students-info
.row.m-y-2 .row.m-y-2
.col-md-offset-2.col-md-4 .col-md-offset-2.col-md-4
.form-group .form-group
label.control-label(data-i18n="courses.number_programming_students") span.control-label(data-i18n="courses.number_programming_students")
.help-block.small .help-block.small
em.text-info(data-i18n="teachers_quote.num_students_help") em.text-info(data-i18n="teachers_quote.num_students_help")
select.form-control(name="numStudents") select.form-control(name="numStudents")
@ -128,7 +136,7 @@ block content
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-label span.control-label
span(data-i18n="courses.number_total_students") span(data-i18n="courses.number_total_students")
span.spl.text-muted(data-i18n="signup.optional") span.spl.text-muted(data-i18n="signup.optional")
select.form-control(name="numStudentsTotal") select.form-control(name="numStudentsTotal")
@ -140,14 +148,11 @@ block content
option 10,000+ option 10,000+
.form-group .form-group
.row.m-y-2 .row.m-y-2
.col-md-offset-2.col-md-10 .col-md-offset-2.col-md-4
label.control-label(data-i18n="teachers_quote.education_level_label") span.control-label(data-i18n="teachers_quote.education_level_label")
.help-block.small .help-block.small
em.text-info(data-i18n="teachers_quote.education_level_help") em.text-info(data-i18n="teachers_quote.education_level_help")
.col-md-offset-2.col-md-5
.checkbox .checkbox
label label
input(type="checkbox" name="educationLevel" value="Elementary") input(type="checkbox" name="educationLevel" value="Elementary")
@ -173,7 +178,7 @@ block content
#anything-else-row.row.m-y-2 #anything-else-row.row.m-y-2
.col-md-offset-2.col-md-8 .col-md-offset-2.col-md-8
label.control-label span.control-label
span(data-i18n="teachers_quote.anything_else") span(data-i18n="teachers_quote.anything_else")
span.spl.text-muted(data-i18n="signup.optional") span.spl.text-muted(data-i18n="signup.optional")

View file

@ -13,7 +13,9 @@ if completed
.small-details.nowrap .small-details.nowrap
span.spr(data-i18n='teacher.completed') span.spr(data-i18n='teacher.completed')
| Completed | Completed
span= new Date(session.get('dateFirstCompleted')).toLocaleString() - var dateCompleted = session.get('dateFirstCompleted') || session.get('created') || session.get('changed');
if dateCompleted
span= new Date(dateCompleted).toLocaleString()
+timePlayed +timePlayed
//- .small-details //- .small-details
//- i(data-i18n='teacher.click_to_view_solution') //- i(data-i18n='teacher.click_to_view_solution')

View file

@ -36,7 +36,7 @@ block content
.row .row
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
.form-group .form-group
label.control-label span.control-label
span(data-i18n="general.username") span(data-i18n="general.username")
span.spl.text-muted(data-i18n="signup.optional") span.spl.text-muted(data-i18n="signup.optional")
- var name = me.get('name') || ''; - var name = me.get('name') || '';
@ -44,20 +44,20 @@ block content
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="share_progress_modal.form_label") span.control-label(data-i18n="share_progress_modal.form_label")
- var email = me.get('email') || ''; - var email = me.get('email') || '';
input.form-control(name="email" value=email, disabled=!!email) input.form-control(name="email" value=email, disabled=!!email)
.row .row
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="general.first_name") span.control-label(data-i18n="general.first_name")
- var firstName = me.get('firstName') || ''; - var firstName = me.get('firstName') || '';
input.form-control(name="firstName" value=firstName, disabled=!!firstName) input.form-control(name="firstName" value=firstName, disabled=!!firstName)
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="general.last_name") span.control-label(data-i18n="general.last_name")
- var lastName = me.get('lastName') || ''; - var lastName = me.get('lastName') || '';
input.form-control(name="lastName" value=lastName, disabled=!!lastName) input.form-control(name="lastName" value=lastName, disabled=!!lastName)
@ -65,33 +65,30 @@ block content
.row .row
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
.form-group .form-group
label.control-label span.control-label(data-i18n="teachers_quote.phone_number")
span(data-i18n="teachers_quote.phone_number") input.form-control(name="phoneNumber", data-i18n="[placeholder]teachers_quote.phone_number_help")
.help-block.small
em.text-info(data-i18n="teachers_quote.phone_number_help")
input.form-control(name="phoneNumber")
if me.isAnonymous() if me.isAnonymous()
.row .row
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
#email-form-group.form-group #email-form-group.form-group
label.control-label(data-i18n="general.email") span.control-label(data-i18n="general.email")
- var email = me.get('email') || ''; - var email = me.get('email') || '';
input.form-control(name="email" type="email", value=email, disabled=!!email) input.form-control(name="email" type="email", value=email, disabled=!!email)
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-label span.control-label
span(data-i18n="teachers_quote.phone_number") span(data-i18n="teachers_quote.phone_number")
.help-block.small .help-block.small
em.text-info(data-i18n="teachers_quote.phone_number_help") em.text-info(data-i18n="teachers_quote.phone_number_help")
input.form-control(name="phoneNumber") input.form-control(name="phoneNumber")
.row .row
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="teachers_quote.primary_role_label") span.control-label(data-i18n="teachers_quote.primary_role_label")
select.form-control(name="role") select.form-control(name="role")
option(data-i18n="teachers_quote.primary_role_default", , value='') option(data-i18n="teachers_quote.primary_role_default", , value='')
option(data-i18n="courses.teacher", value="Teacher") option(data-i18n="courses.teacher", value="Teacher")
@ -100,45 +97,55 @@ block content
option(data-i18n="teachers_quote.principal", value="Principal") option(data-i18n="teachers_quote.principal", value="Principal")
option(data-i18n="teachers_quote.superintendent", value="Superintendent") option(data-i18n="teachers_quote.superintendent", value="Superintendent")
option(data-i18n="teachers_quote.parent", value="Parent") option(data-i18n="teachers_quote.parent", value="Parent")
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="teachers_quote.purchaser_role_label") span.control-label(data-i18n="teachers_quote.purchaser_role_label")
select.form-control(name="purchaserRole") select.form-control(name="purchaserRole")
option(data-i18n="teachers_quote.purchaser_role_default", , value='') option(data-i18n="teachers_quote.purchaser_role_default", , value='')
option(data-i18n="teachers_quote.influence_advocate", value="Influence/Advocate") option(data-i18n="teachers_quote.influence_advocate", value="Influence/Advocate")
option(data-i18n="teachers_quote.evaluate_recommend", value="Evaluate/Recommend") option(data-i18n="teachers_quote.evaluate_recommend", value="Evaluate/Recommend")
option(data-i18n="teachers_quote.approve_funds", value="Approve Funds") option(data-i18n="teachers_quote.approve_funds", value="Approve Funds")
option(data-i18n="teachers_quote.no_purchaser_role", value="No role in purchase decisions") option(data-i18n="teachers_quote.no_purchaser_role", value="No role in purchase decisions")
#form-school-info #form-school-info
.row .row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="teachers_quote.organization_label") span.control-label
span(data-i18n="teachers_quote.organization_label")
span.spl.text-muted(data-i18n="signup.optional")
input.form-control#organization-control(name="organization") input.form-control#organization-control(name="organization")
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="teachers_quote.city") //- TODO: algolia and form errors both change form-control
input.form-control(name="city") //- TODO: District not red on validation error
span.control-label.form-control.nullify-form-control(data-i18n="teachers_quote.district_label")
input.form-control#district-control(name="district", data-i18n="[placeholder]teachers_quote.district_na")
.row .row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6 .col-md-offset-2.col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="teachers_quote.state") span.control-label(data-i18n="teachers_quote.city")
input.form-control(name="state") input.form-control(name="city")
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-labellabel.control-label(data-i18n="teachers_quote.country") span.control-label(data-i18n="teachers_quote.state")
input.form-control(name="state")
.row.m-y-2
.col-md-offset-2.col-md-4.col-sm-6
.form-group
span.control-labelspan.control-label(data-i18n="teachers_quote.country")
input.form-control(name="country") input.form-control(name="country")
#form-students-info #form-students-info
.row .row
.col-md-offset-2.col-md-4 .col-md-offset-2.col-md-4
.form-group .form-group
label.control-label(data-i18n="courses.number_programming_students") span.control-label(data-i18n="courses.number_programming_students")
.help-block.small .help-block.small
em.text-info(data-i18n="teachers_quote.num_students_help") em.text-info(data-i18n="teachers_quote.num_students_help")
select.form-control(name="numStudents") select.form-control(name="numStudents")
@ -150,10 +157,10 @@ block content
option 201-500 option 201-500
option 501-1000 option 501-1000
option 1000+ option 1000+
.col-md-4.col-sm-6 .col-md-4.col-sm-6
.form-group .form-group
label.control-label(data-i18n="courses.number_total_students") span.control-label(data-i18n="courses.number_total_students")
select.form-control(name="numStudentsTotal") select.form-control(name="numStudentsTotal")
option(data-i18n="teachers_quote.num_students_default", value='') option(data-i18n="teachers_quote.num_students_default", value='')
option 1-500 option 1-500
@ -163,14 +170,11 @@ block content
option 10,000+ option 10,000+
.form-group .form-group
.row.m-y-2
.row .col-md-offset-2.col-md-4
.col-md-offset-2.col-md-10 span.control-label(data-i18n="teachers_quote.education_level_label")
label.control-label(data-i18n="teachers_quote.education_level_label") .help-block.small
.help-block.small em.text-info(data-i18n="teachers_quote.education_level_help")
em.text-info(data-i18n="teachers_quote.education_level_help")
.col-md-offset-2.col-md-5
.checkbox .checkbox
label label
input(type="checkbox" name="educationLevel" value="Elementary") input(type="checkbox" name="educationLevel" value="Elementary")
@ -196,7 +200,7 @@ block content
#anything-else-row.row #anything-else-row.row
.col-md-offset-2.col-md-8 .col-md-offset-2.col-md-8
label.control-label span.control-label
span(data-i18n="teachers_quote.anything_else") span(data-i18n="teachers_quote.anything_else")
span.spl.text-muted(data-i18n="signup.optional") span.spl.text-muted(data-i18n="signup.optional")
@ -210,7 +214,7 @@ block content
input(type="hidden" name="nces_students") input(type="hidden" name="nces_students")
input(type="hidden" name="nces_phone") input(type="hidden" name="nces_phone")
#buttons-row.row.text-center #buttons-row.row.m-y-2.text-center
input#submit-request-btn.btn.btn-lg.btn-primary(type="submit" data-i18n="[value]teachers_quote.title") input#submit-request-btn.btn.btn-lg.btn-primary(type="submit" data-i18n="[value]teachers_quote.title")
#form-submit-success.text-center(class=showDone ? '' : 'hide') #form-submit-success.text-center(class=showDone ? '' : 'hide')
@ -219,7 +223,7 @@ block content
p p
span.spr(data-i18n="teachers_quote.thanks_p") span.spr(data-i18n="teachers_quote.thanks_p")
a.spl(href="mailto:team@codecombat.com") team@codecombat.com a.spl(href="mailto:team@codecombat.com") team@codecombat.com
unless me.isAnonymous() unless me.isAnonymous()
a.btn.btn-lg.btn-navy(href="/teachers/classes") a.btn.btn-lg.btn-navy(href="/teachers/classes")
span(data-i18n='teachers_quote.back_to_classes') span(data-i18n='teachers_quote.back_to_classes')
@ -244,17 +248,17 @@ block content
.row .row
.col-md-offset-2.col-md-4 .col-md-offset-2.col-md-4
.form-group .form-group
label.control-label(data-i18n="general.username") span.control-label(data-i18n="general.username")
input.form-control(name="name") input.form-control(name="name")
.row .row
.col-md-offset-2.col-md-4 .col-md-offset-2.col-md-4
.form-group .form-group
label.control-label(data-i18n="general.password") span.control-label(data-i18n="general.password")
input.form-control(name="password1", type="password") input.form-control(name="password1", type="password")
.col-md-4 .col-md-4
.form-group .form-group
label.control-label(data-i18n="general.confirm_password") span.control-label(data-i18n="general.confirm_password")
input.form-control(name="password2", type="password") input.form-control(name="password2", type="password")
.text-center .text-center

View file

@ -1,6 +1,6 @@
RootView = require 'views/core/RootView' RootView = require 'views/core/RootView'
CocoCollection = require 'collections/CocoCollection' CocoCollection = require 'collections/CocoCollection'
Campaigns = require 'collections/Campaigns' Campaign = require 'models/Campaign'
Course = require 'models/Course' Course = require 'models/Course'
module.exports = class AdminClassroomLevelsView extends RootView module.exports = class AdminClassroomLevelsView extends RootView
@ -9,8 +9,8 @@ module.exports = class AdminClassroomLevelsView extends RootView
initialize: -> initialize: ->
return super() unless me.isAdmin() return super() unless me.isAdmin()
@campaigns = new Campaigns() @campaigns = new CocoCollection([], { url: "/db/campaign", model: Campaign})
@supermodel.trackRequest @campaigns.fetchByType('course', { data: { project: 'levels,levelsUpdated' } }) @supermodel.loadCollection(@campaigns, 'campaigns')
@courses = new CocoCollection([], { url: "/db/course", model: Course}) @courses = new CocoCollection([], { url: "/db/course", model: Course})
@supermodel.loadCollection(@courses, 'courses') @supermodel.loadCollection(@courses, 'courses')
super() super()

View file

@ -56,6 +56,8 @@ require("locale/eo")
require("locale/uz") require("locale/uz")
require("locale/my") require("locale/my")
require("locale/et") require("locale/et")
require("locale/hr")
require("locale/mi")
module.exports = class DiplomatView extends ContributeClassView module.exports = class DiplomatView extends ContributeClassView
id: 'diplomat-view' id: 'diplomat-view'
@ -140,3 +142,5 @@ module.exports = class DiplomatView extends ContributeClassView
uz: [] # O'zbekcha, Uzbek uz: [] # O'zbekcha, Uzbek
my: [] # က, Myanmar language my: [] # က, Myanmar language
et: [] # Eesti, Estonian et: [] # Eesti, Estonian
hr: [] # hrvatski jezik, Croatian
mi: [] # te reo Māori, Māori

View file

@ -41,7 +41,10 @@ module.exports = class TeacherCoursesView extends RootView
@ownedClassrooms.fetchMine({data: {project: '_id'}}) @ownedClassrooms.fetchMine({data: {project: '_id'}})
@supermodel.trackCollection(@ownedClassrooms) @supermodel.trackCollection(@ownedClassrooms)
@courses = new Courses() @courses = new Courses()
@supermodel.trackRequest @courses.fetchReleased() if me.isAdmin()
@supermodel.trackRequest @courses.fetch()
else
@supermodel.trackRequest @courses.fetchReleased()
@campaigns = new Campaigns() @campaigns = new Campaigns()
@supermodel.trackRequest @campaigns.fetchByType('course', { data: { project: 'levels,levelsUpdated' } }) @supermodel.trackRequest @campaigns.fetchByType('course', { data: { project: 'levels,levelsUpdated' } })
@ @

View file

@ -365,12 +365,16 @@ module.exports = class CampaignView extends RootView
return experienceScore return experienceScore
createLine: (o1, o2) -> createLine: (o1, o2) ->
p1 = x: o1.x, y: 0.66 * o1.y + 0.5 mapHeight = parseFloat($(".map").css("height"))
p2 = x: o2.x, y: 0.66 * o2.y + 0.5 mapWidth = parseFloat($(".map").css("width"))
return unless mapHeight > 0
ratio = mapWidth / mapHeight
p1 = x: o1.x, y: o1.y / ratio - 0.5
p2 = x: o2.x, y: o2.y / ratio
length = Math.sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y)) length = Math.sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y))
angle = Math.atan2(p1.y - p2.y, p2.x - p1.x) * 180 / Math.PI angle = Math.atan2(p1.y - p2.y, p2.x - p1.x) * 180 / Math.PI
transform = "rotate(#{angle}deg)" transform = "rotate(#{angle}deg)"
line = $('<div>').appendTo('.map').addClass('next-level-line').css(transform: transform, width: length + '%', left: o1.x + '%', bottom: (o1.y + 0.5) + '%') line = $('<div>').appendTo('.map').addClass('next-level-line').css(transform: transform, width: length + '%', left: o1.x + '%', bottom: (o1.y - 0.5) + '%')
line.append($('<div class="line">')).append($('<div class="point">')) line.append($('<div class="line">')).append($('<div class="point">'))
applyCampaignStyles: -> applyCampaignStyles: ->

View file

@ -7,9 +7,9 @@ errors = require 'core/errors'
User = require 'models/User' User = require 'models/User'
ConfirmModal = require 'views/editor/modal/ConfirmModal' ConfirmModal = require 'views/editor/modal/ConfirmModal'
algolia = require 'core/services/algolia' algolia = require 'core/services/algolia'
NCES_KEYS = ['id', 'name', 'district', 'district_id', 'district_schools', 'district_students', 'students', 'phone']
FORM_KEY = 'request-quote-form' DISTRICT_NCES_KEYS = ['district', 'district_id', 'district_schools', 'district_students', 'phone']
SCHOOL_NCES_KEYS = DISTRICT_NCES_KEYS.concat(['id', 'name', 'students'])
module.exports = class ConvertToTeacherAccountView extends RootView module.exports = class ConvertToTeacherAccountView extends RootView
id: 'convert-to-teacher-account-view' id: 'convert-to-teacher-account-view'
@ -40,13 +40,13 @@ module.exports = class ConvertToTeacherAccountView extends RootView
return 'Your account has not been updated! If you continue, your changes will be lost.' return 'Your account has not been updated! If you continue, your changes will be lost.'
invalidateNCES: -> invalidateNCES: ->
for key in NCES_KEYS for key in SCHOOL_NCES_KEYS
@$('input[name="nces_' + key + '"]').val '' @$('input[name="nces_' + key + '"]').val ''
onLoaded: -> onLoaded: ->
if @trialRequests.size() and me.isTeacher() if @trialRequests.size() and me.isTeacher()
return application.router.navigate('/teachers', { trigger: true, replace: true }) return application.router.navigate('/teachers', { trigger: true, replace: true })
super() super()
afterRender: -> afterRender: ->
@ -75,16 +75,34 @@ module.exports = class ConvertToTeacherAccountView extends RootView
"<div class='school'> #{hr.name.value} </div>" + "<div class='school'> #{hr.name.value} </div>" +
"<div class='district'>#{hr.district.value}, " + "<div class='district'>#{hr.district.value}, " +
"<span>#{hr.city?.value}, #{hr.state.value}</span></div>" "<span>#{hr.city?.value}, #{hr.state.value}</span></div>"
]).on 'autocomplete:selected', (event, suggestion, dataset) => ]).on 'autocomplete:selected', (event, suggestion, dataset) =>
@$('input[name="district"]').val suggestion.district
@$('input[name="city"]').val suggestion.city @$('input[name="city"]').val suggestion.city
@$('input[name="state"]').val suggestion.state @$('input[name="state"]').val suggestion.state
@$('input[name="district"]').val suggestion.district
@$('input[name="country"]').val 'USA' @$('input[name="country"]').val 'USA'
for key in SCHOOL_NCES_KEYS
for key in NCES_KEYS
@$('input[name="nces_' + key + '"]').val suggestion[key] @$('input[name="nces_' + key + '"]').val suggestion[key]
@onChangeForm()
$("#district-control").algolia_autocomplete({hint: false}, [
source: (query, callback) ->
algolia.schoolsIndex.search(query, { hitsPerPage: 5, aroundLatLngViaIP: false }).then (answer) ->
callback answer.hits
, ->
callback []
displayKey: 'district',
templates:
suggestion: (suggestion) ->
hr = suggestion._highlightResult
"<div class='district'>#{hr.district.value}, " +
"<span>#{hr.city?.value}, #{hr.state.value}</span></div>"
]).on 'autocomplete:selected', (event, suggestion, dataset) =>
@$('input[name="organization"]').val '' # TODO: does not persist on tabbing: back to school, back to district
@$('input[name="city"]').val suggestion.city
@$('input[name="state"]').val suggestion.state
@$('input[name="country"]').val 'USA'
for key in DISTRICT_NCES_KEYS
@$('input[name="nces_' + key + '"]').val suggestion[key]
@onChangeForm() @onChangeForm()
onChangeForm: -> onChangeForm: ->
@ -97,28 +115,35 @@ module.exports = class ConvertToTeacherAccountView extends RootView
form = @$('form') form = @$('form')
attrs = forms.formToObject(form) attrs = forms.formToObject(form)
trialRequestAttrs = _.cloneDeep(attrs)
# Don't save n/a district entries, but do validate required district client-side
trialRequestAttrs = _.omit(trialRequestAttrs, 'district') if trialRequestAttrs.district?.replace(/\s/ig, '').match(/n\/a/ig)
if @$('#other-education-level-checkbox').is(':checked') if @$('#other-education-level-checkbox').is(':checked')
val = @$('#other-education-level-input').val() val = @$('#other-education-level-input').val()
attrs.educationLevel.push(val) if val trialRequestAttrs.educationLevel.push(val) if val
forms.clearFormAlerts(form) forms.clearFormAlerts(form)
result = tv4.validateMultiple(attrs, formSchema) result = tv4.validateMultiple(trialRequestAttrs, formSchema)
error = false error = false
if not result.valid if not result.valid
forms.applyErrorsToForm(form, result.errors) forms.applyErrorsToForm(form, result.errors)
error = true error = true
if not _.size(attrs.educationLevel) if not _.size(trialRequestAttrs.educationLevel)
forms.setErrorToProperty(form, 'educationLevel', 'Include at least one.') forms.setErrorToProperty(form, 'educationLevel', 'include at least one')
error = true
unless attrs.district
forms.setErrorToProperty(form, 'district', $.i18n.t('common.required_field'))
error = true error = true
if error if error
forms.scrollToFirstError() forms.scrollToFirstError()
return return
attrs['siteOrigin'] = 'convert teacher' trialRequestAttrs['siteOrigin'] = 'convert teacher'
@trialRequest = new TrialRequest({ @trialRequest = new TrialRequest({
type: 'course' type: 'course'
properties: attrs properties: trialRequestAttrs
}) })
if me.get('role') is 'student' and not me.isAnonymous() if me.get('role') is 'student' and not me.isAnonymous()
modal = new ConfirmModal({ modal = new ConfirmModal({
@ -151,15 +176,14 @@ module.exports = class ConvertToTeacherAccountView extends RootView
formSchema = { formSchema = {
type: 'object' type: 'object'
required: [ required: ['firstName', 'lastName', 'role', 'numStudents', 'city', 'state', 'country']
'firstName', 'lastName', 'organization', 'role', 'numStudents', 'city', 'state', 'country'
]
properties: properties:
firstName: { type: 'string' } firstName: { type: 'string' }
lastName: { type: 'string' } lastName: { type: 'string' }
phoneNumber: { type: 'string' } phoneNumber: { type: 'string' }
role: { type: 'string' } role: { type: 'string' }
organization: { type: 'string' } organization: { type: 'string' }
district: { type: 'string' }
city: { type: 'string' } city: { type: 'string' }
state: { type: 'string' } state: { type: 'string' }
country: { type: 'string' } country: { type: 'string' }
@ -172,5 +196,5 @@ formSchema = {
notes: { type: 'string' } notes: { type: 'string' }
} }
for key in NCES_KEYS for key in SCHOOL_NCES_KEYS
formSchema['nces_' + key] = type: 'string' formSchema['nces_' + key] = type: 'string'

View file

@ -7,9 +7,9 @@ errors = require 'core/errors'
User = require 'models/User' User = require 'models/User'
algolia = require 'core/services/algolia' algolia = require 'core/services/algolia'
FORM_KEY = 'request-quote-form'
SIGNUP_REDIRECT = '/teachers/classes' SIGNUP_REDIRECT = '/teachers/classes'
NCES_KEYS = ['id', 'name', 'district', 'district_id', 'district_schools', 'district_students', 'students', 'phone'] DISTRICT_NCES_KEYS = ['district', 'district_id', 'district_schools', 'district_students', 'phone']
SCHOOL_NCES_KEYS = DISTRICT_NCES_KEYS.concat(['id', 'name', 'students'])
module.exports = class CreateTeacherAccountView extends RootView module.exports = class CreateTeacherAccountView extends RootView
id: 'create-teacher-account-view' id: 'create-teacher-account-view'
@ -43,12 +43,12 @@ module.exports = class CreateTeacherAccountView extends RootView
super() super()
invalidateNCES: -> invalidateNCES: ->
for key in NCES_KEYS for key in SCHOOL_NCES_KEYS
@$('input[name="nces_' + key + '"]').val '' @$('input[name="nces_' + key + '"]').val ''
afterRender: -> afterRender: ->
super() super()
# apply existing trial request on form # apply existing trial request on form
properties = @trialRequest.get('properties') properties = @trialRequest.get('properties')
if properties if properties
@ -58,7 +58,7 @@ module.exports = class CreateTeacherAccountView extends RootView
otherLevel = _.first(_.difference(submittedLevels, commonLevels)) or '' otherLevel = _.first(_.difference(submittedLevels, commonLevels)) or ''
@$('#other-education-level-checkbox').attr('checked', !!otherLevel) @$('#other-education-level-checkbox').attr('checked', !!otherLevel)
@$('#other-education-level-input').val(otherLevel) @$('#other-education-level-input').val(otherLevel)
$("#organization-control").algolia_autocomplete({hint: false}, [ $("#organization-control").algolia_autocomplete({hint: false}, [
source: (query, callback) -> source: (query, callback) ->
algolia.schoolsIndex.search(query, { hitsPerPage: 5, aroundLatLngViaIP: false }).then (answer) -> algolia.schoolsIndex.search(query, { hitsPerPage: 5, aroundLatLngViaIP: false }).then (answer) ->
@ -72,16 +72,34 @@ module.exports = class CreateTeacherAccountView extends RootView
"<div class='school'> #{hr.name.value} </div>" + "<div class='school'> #{hr.name.value} </div>" +
"<div class='district'>#{hr.district.value}, " + "<div class='district'>#{hr.district.value}, " +
"<span>#{hr.city?.value}, #{hr.state.value}</span></div>" "<span>#{hr.city?.value}, #{hr.state.value}</span></div>"
]).on 'autocomplete:selected', (event, suggestion, dataset) => ]).on 'autocomplete:selected', (event, suggestion, dataset) =>
@$('input[name="district"]').val suggestion.district
@$('input[name="city"]').val suggestion.city @$('input[name="city"]').val suggestion.city
@$('input[name="state"]').val suggestion.state @$('input[name="state"]').val suggestion.state
@$('input[name="district"]').val suggestion.district
@$('input[name="country"]').val 'USA' @$('input[name="country"]').val 'USA'
for key in SCHOOL_NCES_KEYS
for key in NCES_KEYS
@$('input[name="nces_' + key + '"]').val suggestion[key] @$('input[name="nces_' + key + '"]').val suggestion[key]
@onChangeForm()
$("#district-control").algolia_autocomplete({hint: false}, [
source: (query, callback) ->
algolia.schoolsIndex.search(query, { hitsPerPage: 5, aroundLatLngViaIP: false }).then (answer) ->
callback answer.hits
, ->
callback []
displayKey: 'district',
templates:
suggestion: (suggestion) ->
hr = suggestion._highlightResult
"<div class='district'>#{hr.district.value}, " +
"<span>#{hr.city?.value}, #{hr.state.value}</span></div>"
]).on 'autocomplete:selected', (event, suggestion, dataset) =>
@$('input[name="organization"]').val '' # TODO: does not persist on tabbing: back to school, back to district
@$('input[name="city"]').val suggestion.city
@$('input[name="state"]').val suggestion.state
@$('input[name="country"]').val 'USA'
for key in DISTRICT_NCES_KEYS
@$('input[name="nces_' + key + '"]').val suggestion[key]
@onChangeForm() @onChangeForm()
onClickLoginLink: -> onClickLoginLink: ->
@ -95,35 +113,41 @@ module.exports = class CreateTeacherAccountView extends RootView
onSubmitForm: (e) -> onSubmitForm: (e) ->
e.preventDefault() e.preventDefault()
# Creating Trial Request first, validate user attributes but do not use them # Creating Trial Request first, validate user attributes but do not use them
form = @$('form') form = @$('form')
allAttrs = forms.formToObject(form) allAttrs = forms.formToObject(form)
trialRequestAttrs = _.omit(allAttrs, 'name', 'password1', 'password2') trialRequestAttrs = _.omit(allAttrs, 'name', 'password1', 'password2')
# Don't save n/a district entries, but do validate required district client-side
trialRequestAttrs = _.omit(trialRequestAttrs, 'district') if trialRequestAttrs.district?.replace(/\s/ig, '').match(/n\/a/ig)
if @$('#other-education-level-checkbox').is(':checked') if @$('#other-education-level-checkbox').is(':checked')
val = @$('#other-education-level-input').val() val = @$('#other-education-level-input').val()
trialRequestAttrs.educationLevel.push(val) if val trialRequestAttrs.educationLevel.push(val) if val
forms.clearFormAlerts(form) forms.clearFormAlerts(form)
result = tv4.validateMultiple(trialRequestAttrs, formSchema) result = tv4.validateMultiple(trialRequestAttrs, formSchema)
error = false error = false
if not result.valid if not result.valid
forms.applyErrorsToForm(form, result.errors) forms.applyErrorsToForm(form, result.errors)
error = true error = true
if not forms.validateEmail(trialRequestAttrs.email) if not error and not forms.validateEmail(trialRequestAttrs.email)
forms.setErrorToProperty(form, 'email', 'Invalid email.') forms.setErrorToProperty(form, 'email', 'invalid email')
error = true error = true
if not _.size(trialRequestAttrs.educationLevel) if not _.size(trialRequestAttrs.educationLevel)
forms.setErrorToProperty(form, 'educationLevel', 'Include at least one.') forms.setErrorToProperty(form, 'educationLevel', 'include at least one')
error = true
unless allAttrs.district
forms.setErrorToProperty(form, 'district', $.i18n.t('common.required_field'))
error = true error = true
unless @gplusAttrs or @facebookAttrs unless @gplusAttrs or @facebookAttrs
if not allAttrs.password1 if not allAttrs.password1
forms.setErrorToProperty(form, 'password1', 'Required field') forms.setErrorToProperty(form, 'password1', $.i18n.t('common.required_field'))
error = true error = true
else if not allAttrs.password2 else if not allAttrs.password2
forms.setErrorToProperty(form, 'password2', 'Required field') forms.setErrorToProperty(form, 'password2', $.i18n.t('common.required_field'))
error = true error = true
else if allAttrs.password1 isnt allAttrs.password2 else if allAttrs.password1 isnt allAttrs.password2
forms.setErrorToProperty(form, 'password1', 'Password fields are not equivalent') forms.setErrorToProperty(form, 'password1', 'Password fields are not equivalent')
@ -151,7 +175,7 @@ module.exports = class CreateTeacherAccountView extends RootView
.addClass('has-error') .addClass('has-error')
.append($("<div class='help-block error-help-block'>#{userExists} <a class='login-link'>#{logIn}</a>")) .append($("<div class='help-block error-help-block'>#{userExists} <a class='login-link'>#{logIn}</a>"))
forms.scrollToFirstError() forms.scrollToFirstError()
else else
errors.showNotyNetworkError(arguments...) errors.showNotyNetworkError(arguments...)
onClickEmailExistsLoginLink: -> onClickEmailExistsLoginLink: ->
@ -225,7 +249,6 @@ module.exports = class CreateTeacherAccountView extends RootView
@$('input[type="password"]').attr('disabled', true) @$('input[type="password"]').attr('disabled', true)
@$('#gplus-logged-in-row, #social-network-signups').toggleClass('hide') @$('#gplus-logged-in-row, #social-network-signups').toggleClass('hide')
# Facebook signup # Facebook signup
onClickFacebookSignupButton: -> onClickFacebookSignupButton: ->
@ -269,13 +292,9 @@ module.exports = class CreateTeacherAccountView extends RootView
@$('#facebook-logged-in-row, #social-network-signups').toggleClass('hide') @$('#facebook-logged-in-row, #social-network-signups').toggleClass('hide')
formSchema = { formSchema = {
type: 'object' type: 'object'
required: [ required: ['firstName', 'lastName', 'email', 'role', 'numStudents', 'city', 'state', 'country']
'firstName', 'lastName', 'email', 'organization', 'role', 'numStudents', 'city'
'state', 'country'
]
properties: properties:
password1: { type: 'string' } password1: { type: 'string' }
password2: { type: 'string' } password2: { type: 'string' }
@ -286,6 +305,7 @@ formSchema = {
phoneNumber: { type: 'string' } phoneNumber: { type: 'string' }
role: { type: 'string' } role: { type: 'string' }
organization: { type: 'string' } organization: { type: 'string' }
district: { type: 'string' }
city: { type: 'string' } city: { type: 'string' }
state: { type: 'string' } state: { type: 'string' }
country: { type: 'string' } country: { type: 'string' }
@ -298,5 +318,5 @@ formSchema = {
notes: { type: 'string' } notes: { type: 'string' }
} }
for key in NCES_KEYS for key in SCHOOL_NCES_KEYS
formSchema['nces_' + key] = type: 'string' formSchema['nces_' + key] = type: 'string'

View file

@ -8,7 +8,8 @@ ConfirmModal = require 'views/editor/modal/ConfirmModal'
algolia = require 'core/services/algolia' algolia = require 'core/services/algolia'
SIGNUP_REDIRECT = '/teachers' SIGNUP_REDIRECT = '/teachers'
NCES_KEYS = ['id', 'name', 'district', 'district_id', 'district_schools', 'district_students', 'students', 'phone'] DISTRICT_NCES_KEYS = ['district', 'district_id', 'district_schools', 'district_students', 'phone']
SCHOOL_NCES_KEYS = DISTRICT_NCES_KEYS.concat(['id', 'name', 'students'])
module.exports = class RequestQuoteView extends RootView module.exports = class RequestQuoteView extends RootView
id: 'request-quote-view' id: 'request-quote-view'
@ -46,7 +47,7 @@ module.exports = class RequestQuoteView extends RootView
super() super()
invalidateNCES: -> invalidateNCES: ->
for key in NCES_KEYS for key in SCHOOL_NCES_KEYS
@$('input[name="nces_' + key + '"]').val '' @$('input[name="nces_' + key + '"]').val ''
afterRender: -> afterRender: ->
@ -75,16 +76,34 @@ module.exports = class RequestQuoteView extends RootView
"<div class='school'> #{hr.name.value} </div>" + "<div class='school'> #{hr.name.value} </div>" +
"<div class='district'>#{hr.district.value}, " + "<div class='district'>#{hr.district.value}, " +
"<span>#{hr.city?.value}, #{hr.state.value}</span></div>" "<span>#{hr.city?.value}, #{hr.state.value}</span></div>"
]).on 'autocomplete:selected', (event, suggestion, dataset) => ]).on 'autocomplete:selected', (event, suggestion, dataset) =>
@$('input[name="district"]').val suggestion.district
@$('input[name="city"]').val suggestion.city @$('input[name="city"]').val suggestion.city
@$('input[name="state"]').val suggestion.state @$('input[name="state"]').val suggestion.state
@$('input[name="district"]').val suggestion.district
@$('input[name="country"]').val 'USA' @$('input[name="country"]').val 'USA'
for key in SCHOOL_NCES_KEYS
for key in NCES_KEYS
@$('input[name="nces_' + key + '"]').val suggestion[key] @$('input[name="nces_' + key + '"]').val suggestion[key]
@onChangeRequestForm()
$("#district-control").algolia_autocomplete({hint: false}, [
source: (query, callback) ->
algolia.schoolsIndex.search(query, { hitsPerPage: 5, aroundLatLngViaIP: false }).then (answer) ->
callback answer.hits
, ->
callback []
displayKey: 'district',
templates:
suggestion: (suggestion) ->
hr = suggestion._highlightResult
"<div class='district'>#{hr.district.value}, " +
"<span>#{hr.city?.value}, #{hr.state.value}</span></div>"
]).on 'autocomplete:selected', (event, suggestion, dataset) =>
@$('input[name="organization"]').val '' # TODO: does not persist on tabbing: back to school, back to district
@$('input[name="city"]').val suggestion.city
@$('input[name="state"]').val suggestion.state
@$('input[name="country"]').val 'USA'
for key in DISTRICT_NCES_KEYS
@$('input[name="nces_' + key + '"]').val suggestion[key]
@onChangeRequestForm() @onChangeRequestForm()
onChangeRequestForm: -> onChangeRequestForm: ->
@ -96,32 +115,39 @@ module.exports = class RequestQuoteView extends RootView
e.preventDefault() e.preventDefault()
form = @$('#request-form') form = @$('#request-form')
attrs = forms.formToObject(form) attrs = forms.formToObject(form)
trialRequestAttrs = _.cloneDeep(attrs)
# Don't save n/a district entries, but do validate required district client-side
trialRequestAttrs = _.omit(trialRequestAttrs, 'district') if trialRequestAttrs.district?.replace(/\s/ig, '').match(/n\/a/ig)
# custom other input logic # custom other input logic
if @$('#other-education-level-checkbox').is(':checked') if @$('#other-education-level-checkbox').is(':checked')
val = @$('#other-education-level-input').val() val = @$('#other-education-level-input').val()
attrs.educationLevel.push(val) if val trialRequestAttrs.educationLevel.push(val) if val
forms.clearFormAlerts(form) forms.clearFormAlerts(form)
requestFormSchema = if me.isAnonymous() then requestFormSchemaAnonymous else requestFormSchemaLoggedIn requestFormSchema = if me.isAnonymous() then requestFormSchemaAnonymous else requestFormSchemaLoggedIn
result = tv4.validateMultiple(attrs, requestFormSchemaAnonymous) result = tv4.validateMultiple(trialRequestAttrs, requestFormSchemaAnonymous)
error = false error = false
if not result.valid if not result.valid
forms.applyErrorsToForm(form, result.errors) forms.applyErrorsToForm(form, result.errors)
error = true error = true
if not forms.validateEmail(attrs.email) if not error and not forms.validateEmail(trialRequestAttrs.email)
forms.setErrorToProperty(form, 'email', 'Invalid email.') forms.setErrorToProperty(form, 'email', 'invalid email')
error = true error = true
if not _.size(attrs.educationLevel) if not _.size(trialRequestAttrs.educationLevel)
forms.setErrorToProperty(form, 'educationLevel', 'Include at least one.') forms.setErrorToProperty(form, 'educationLevel', 'include at least one')
error = true
unless attrs.district
forms.setErrorToProperty(form, 'district', $.i18n.t('common.required_field'))
error = true error = true
if error if error
forms.scrollToFirstError() forms.scrollToFirstError()
return return
attrs['siteOrigin'] = 'demo request' trialRequestAttrs['siteOrigin'] = 'demo request'
@trialRequest = new TrialRequest({ @trialRequest = new TrialRequest({
type: 'course' type: 'course'
properties: attrs properties: trialRequestAttrs
}) })
if me.get('role') is 'student' and not me.isAnonymous() if me.get('role') is 'student' and not me.isAnonymous()
modal = new ConfirmModal({ modal = new ConfirmModal({
@ -262,7 +288,7 @@ module.exports = class RequestQuoteView extends RootView
requestFormSchemaAnonymous = { requestFormSchemaAnonymous = {
type: 'object' type: 'object'
required: [ required: [
'firstName', 'lastName', 'email', 'organization', 'role', 'purchaserRole', 'numStudents', 'firstName', 'lastName', 'email', 'role', 'purchaserRole', 'numStudents',
'numStudentsTotal', 'phoneNumber', 'city', 'state', 'country'] 'numStudentsTotal', 'phoneNumber', 'city', 'state', 'country']
properties: properties:
firstName: { type: 'string' } firstName: { type: 'string' }
@ -273,6 +299,7 @@ requestFormSchemaAnonymous = {
role: { type: 'string' } role: { type: 'string' }
purchaserRole: { type: 'string' } purchaserRole: { type: 'string' }
organization: { type: 'string' } organization: { type: 'string' }
district: { type: 'string' }
city: { type: 'string' } city: { type: 'string' }
state: { type: 'string' } state: { type: 'string' }
country: { type: 'string' } country: { type: 'string' }
@ -285,7 +312,7 @@ requestFormSchemaAnonymous = {
notes: { type: 'string' }, notes: { type: 'string' },
} }
for key in NCES_KEYS for key in SCHOOL_NCES_KEYS
requestFormSchemaAnonymous['nces_' + key] = type: 'string' requestFormSchemaAnonymous['nces_' + key] = type: 'string'
# same form, but add username input # same form, but add username input

View file

@ -34,7 +34,8 @@ module.exports = class TeachersContactModal extends ModalView
message = """ message = """
Hi CodeCombat! I want to learn more about the Classroom experience and get licenses so that my students can access Computer Science 2 and on. Hi CodeCombat! I want to learn more about the Classroom experience and get licenses so that my students can access Computer Science 2 and on.
Name of School/District: #{props.organization or ''} Name of School #{props.nces_name or props.organization or ''}
Name of District: #{props.nces_district or props.district or ''}
Role: #{props.role or ''} Role: #{props.role or ''}
Phone Number: #{props.phoneNumber or ''} Phone Number: #{props.phoneNumber or ''}
""" """

View file

@ -16,7 +16,7 @@ if (process.argv.length !== 10) {
// TODO: Cleanup country/status lookup code // TODO: Cleanup country/status lookup code
// Save as custom fields instead of user-specific lead notes (also saving nces_ props) // Save as custom fields instead of user-specific lead notes (also saving nces_ props)
const commonTrialProperties = ['organization', 'city', 'state', 'country']; const commonTrialProperties = ['organization', 'district', 'city', 'state', 'country'];
// Old properties which are deprecated or moved // Old properties which are deprecated or moved
const customFieldsToRemove = [ const customFieldsToRemove = [
@ -327,7 +327,7 @@ function findCocoLeads(done) {
if (!trialRequest.properties || !trialRequest.properties.email) continue; if (!trialRequest.properties || !trialRequest.properties.email) continue;
const email = trialRequest.properties.email.toLowerCase(); const email = trialRequest.properties.email.toLowerCase();
emails.push(email); emails.push(email);
const name = trialRequest.properties.nces_name || trialRequest.properties.organization || trialRequest.properties.school || email; const name = trialRequest.properties.nces_name || trialRequest.properties.organization || trialRequest.properties.school || trialRequest.properties.district || trialRequest.properties.nces_district || email;
if (!leads[name]) leads[name] = new CocoLead(name); if (!leads[name]) leads[name] = new CocoLead(name);
leads[name].addTrialRequest(email, trialRequest); leads[name].addTrialRequest(email, trialRequest);
emailLeadMap[email] = leads[name]; emailLeadMap[email] = leads[name];

View file

@ -105,13 +105,13 @@ module.exports =
members = classroom.get('members') or [] members = classroom.get('members') or []
members = members.slice(memberSkip, memberSkip + memberLimit) members = members.slice(memberSkip, memberSkip + memberLimit)
dbqs = [] dbqs = []
select = 'state.complete level creator playtime changed dateFirstCompleted submitted' select = 'state.complete level creator playtime changed created dateFirstCompleted submitted'
for member in members for member in members
dbqs.push(LevelSession.find({creator: member.toHexString()}).select(select).exec()) dbqs.push(LevelSession.find({creator: member.toHexString()}).select(select).exec())
results = yield dbqs results = yield dbqs
sessions = _.flatten(results) sessions = _.flatten(results)
res.status(200).send(sessions) res.status(200).send(sessions)
fetchMembers: wrap (req, res, next) -> fetchMembers: wrap (req, res, next) ->
throw new errors.Unauthorized() unless req.user throw new errors.Unauthorized() unless req.user
memberLimit = parse.getLimitFromReq(req, {default: 10, max: 100, param: 'memberLimit'}) memberLimit = parse.getLimitFromReq(req, {default: 10, max: 100, param: 'memberLimit'})

View file

@ -11,10 +11,6 @@ cutoffDate = new Date(2015,11,11)
cutoffID = mongoose.Types.ObjectId(Math.floor(cutoffDate/1000).toString(16)+'0000000000000000') cutoffID = mongoose.Types.ObjectId(Math.floor(cutoffDate/1000).toString(16)+'0000000000000000')
module.exports = module.exports =
logError: (user, msg) ->
console.warn "Prepaid Error: [#{user.get('slug')} (#{user._id})] '#{msg}'"
post: wrap (req, res) -> post: wrap (req, res) ->
validTypes = ['course'] validTypes = ['course']
unless req.body.type in validTypes unless req.body.type in validTypes
@ -71,7 +67,7 @@ module.exports =
update = { $push: { redeemers : { date: new Date(), userID: user._id } }} update = { $push: { redeemers : { date: new Date(), userID: user._id } }}
result = yield Prepaid.update(query, update) result = yield Prepaid.update(query, update)
if result.nModified is 0 if result.nModified is 0
@logError(req.user, "POST prepaid redeemer lost race on maxRedeemers") console.error("Prepaid redeem error: [#{req.user.get('slug')} (#{req.user._id})] 'POST prepaid redeemer lost race on maxRedeemers'")
throw new errors.Forbidden('This prepaid is exhausted') throw new errors.Forbidden('This prepaid is exhausted')
update = { update = {
@ -142,7 +138,7 @@ module.exports =
trialRequests = yield TrialRequest.find({$and: [{type: 'course'}, {applicant: {$in: userIDs}}]}, {applicant: 1, properties: 1}).lean() trialRequests = yield TrialRequest.find({$and: [{type: 'course'}, {applicant: {$in: userIDs}}]}, {applicant: 1, properties: 1}).lean()
schoolPrepaidsMap = {} schoolPrepaidsMap = {}
for trialRequest in trialRequests for trialRequest in trialRequests
school = trialRequest.properties?.organization ? trialRequest.properties?.school school = trialRequest.properties?.nces_name ? trialRequest.properties?.organization ? trialRequest.properties?.school
continue unless school continue unless school
if userPrepaidsMap[trialRequest.applicant.valueOf()]?.length > 0 if userPrepaidsMap[trialRequest.applicant.valueOf()]?.length > 0
schoolPrepaidsMap[school] ?= [] schoolPrepaidsMap[school] ?= []

View file

@ -184,6 +184,7 @@ module.exports = {
email: 'an@email.com' email: 'an@email.com'
phoneNumber: '555-555-5555' phoneNumber: '555-555-5555'
organization: 'Greendale' organization: 'Greendale'
district: 'Green District'
} }
}, attrs) }, attrs)
} }

View file

@ -31,6 +31,7 @@ describe 'ConvertToTeacherAccountView (/teachers/update-account)', ->
phoneNumber: '555-555-5555' phoneNumber: '555-555-5555'
role: 'Teacher' role: 'Teacher'
organization: 'School' organization: 'School'
district: 'District'
city: 'Springfield' city: 'Springfield'
state: 'AA' state: 'AA'
country: 'asdf' country: 'asdf'
@ -168,3 +169,48 @@ describe 'ConvertToTeacherAccountView (/teachers/update-account)', ->
}) })
expect(me.get('role')).toBe(successForm.role.toLowerCase()) expect(me.get('role')).toBe(successForm.role.toLowerCase())
describe 'submitting the form without school', ->
beforeEach ->
view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented
form = view.$('form')
formData = _.omit(successForm, ['organization'])
forms.objectToForm(form, formData)
form.submit()
it 'submits a trial request, which does not include school setting', ->
request = jasmine.Ajax.requests.mostRecent()
expect(request.url).toBe('/db/trial.request')
expect(request.method).toBe('POST')
attrs = JSON.parse(request.params)
expect(attrs.properties?.organization).toBeUndefined()
expect(attrs.properties?.district).toEqual('District')
describe 'submitting the form without district', ->
beforeEach ->
view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented
form = view.$('form')
formData = _.omit(successForm, ['district'])
forms.objectToForm(form, formData)
form.submit()
it 'displays a validation error on district and not school', ->
expect(view.$('#organization-control').parent().hasClass('has-error')).toEqual(false)
expect(view.$('#district-control').parent().hasClass('has-error')).toEqual(true)
describe 'submitting the form district set to n/a', ->
beforeEach ->
view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented
form = view.$('form')
formData = _.omit(successForm, ['organization'])
formData.district = 'N/A'
forms.objectToForm(form, formData)
form.submit()
it 'submits a trial request, which does not include district setting', ->
expect(view.$('#organization-control').parent().hasClass('has-error')).toEqual(false)
expect(view.$('#district-control').parent().hasClass('has-error')).toEqual(false)
request = jasmine.Ajax.requests.mostRecent()
expect(request.url).toBe('/db/trial.request')
expect(request.method).toBe('POST')
attrs = JSON.parse(request.params)
expect(attrs.properties?.district).toBeUndefined()

View file

@ -33,6 +33,7 @@ describe 'CreateTeacherAccountView', ->
phoneNumber: '555-555-5555' phoneNumber: '555-555-5555'
role: 'Teacher' role: 'Teacher'
organization: 'School' organization: 'School'
district: 'District'
city: 'Springfield' city: 'Springfield'
state: 'AA' state: 'AA'
country: 'asdf' country: 'asdf'
@ -219,6 +220,8 @@ describe 'CreateTeacherAccountView', ->
expect(attrs.password2).toBeUndefined() expect(attrs.password2).toBeUndefined()
expect(attrs.name).toBeUndefined() expect(attrs.name).toBeUndefined()
expect(attrs.properties?.siteOrigin).toBe('create teacher') expect(attrs.properties?.siteOrigin).toBe('create teacher')
expect(attrs.properties?.organization).toEqual('School')
expect(attrs.properties?.district).toEqual('District')
describe 'after saving the new trial request', -> describe 'after saving the new trial request', ->
beforeEach -> beforeEach ->
@ -266,5 +269,49 @@ describe 'CreateTeacherAccountView', ->
spyOn(view, 'openModalView') spyOn(view, 'openModalView')
view.$('#email-form-group .login-link').click() view.$('#email-form-group .login-link').click()
expect(view.openModalView).toHaveBeenCalled() expect(view.openModalView).toHaveBeenCalled()
describe 'submitting the form without school', ->
beforeEach ->
view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented
form = view.$('form')
formData = _.omit(successForm, ['organization'])
forms.objectToForm(form, formData)
form.submit()
it 'submits a trial request, which does not include school setting', ->
request = jasmine.Ajax.requests.mostRecent()
expect(request.url).toBe('/db/trial.request')
expect(request.method).toBe('POST')
attrs = JSON.parse(request.params)
expect(attrs.properties?.organization).toBeUndefined()
expect(attrs.properties?.district).toEqual('District')
describe 'submitting the form without district', ->
beforeEach ->
view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented
form = view.$('form')
formData = _.omit(successForm, ['district'])
forms.objectToForm(form, formData)
form.submit()
it 'displays a validation error on district and not school', ->
expect(view.$('#organization-control').parent().hasClass('has-error')).toEqual(false)
expect(view.$('#district-control').parent().hasClass('has-error')).toEqual(true)
describe 'submitting the form district set to n/a', ->
beforeEach ->
view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented
form = view.$('form')
formData = _.omit(successForm, ['organization'])
formData.district = 'N/A'
forms.objectToForm(form, formData)
form.submit()
it 'submits a trial request, which does not include district setting', ->
expect(view.$('#organization-control').parent().hasClass('has-error')).toEqual(false)
expect(view.$('#district-control').parent().hasClass('has-error')).toEqual(false)
request = jasmine.Ajax.requests.mostRecent()
expect(request.url).toBe('/db/trial.request')
expect(request.method).toBe('POST')
attrs = JSON.parse(request.params)
expect(attrs.properties?.district).toBeUndefined()

View file

@ -2,16 +2,17 @@ RequestQuoteView = require 'views/teachers/RequestQuoteView'
forms = require 'core/forms' forms = require 'core/forms'
describe 'RequestQuoteView', -> describe 'RequestQuoteView', ->
view = null view = null
successFormValues = { successForm = {
firstName: 'A' firstName: 'A'
lastName: 'B' lastName: 'B'
email: 'C@D.com' email: 'C@D.com'
phoneNumber: '555-555-5555' phoneNumber: '555-555-5555'
role: 'Teacher' role: 'Teacher'
organization: 'School' organization: 'School'
district: 'District'
city: 'Springfield' city: 'Springfield'
state: 'AA' state: 'AA'
country: 'asdf' country: 'asdf'
@ -20,10 +21,10 @@ describe 'RequestQuoteView', ->
purchaserRole: 'Approve Funds' purchaserRole: 'Approve Funds'
educationLevel: ['Middle'] educationLevel: ['Middle']
} }
isSubmitRequest = (r) -> _.string.startsWith(r.url, '/db/trial.request') and r.method is 'POST' isSubmitRequest = (r) -> _.string.startsWith(r.url, '/db/trial.request') and r.method is 'POST'
describe 'when user is anonymous and has an existing trial request', -> describe 'when an anonymous user', ->
beforeEach (done) -> beforeEach (done) ->
me.clear() me.clear()
me.set('_id', '1234') me.set('_id', '1234')
@ -32,184 +33,220 @@ describe 'RequestQuoteView', ->
view = new RequestQuoteView() view = new RequestQuoteView()
view.render() view.render()
jasmine.demoEl(view.$el) jasmine.demoEl(view.$el)
request = jasmine.Ajax.requests.mostRecent()
request.respondWith({
status: 200
responseText: JSON.stringify([{
_id: '1'
properties: {
firstName: 'First'
lastName: 'Last'
}
}])
})
_.defer done # Let SuperModel finish
it 'shows request received', ->
expect(view.$('#request-form').hasClass('hide')).toBe(true)
expect(view.$('#form-submit-success').hasClass('hide')).toBe(false)
describe 'when user is signed in and has an existing trial request', ->
beforeEach (done) ->
me.clear()
me.set('_id', '1234')
me._revertAttributes = {}
spyOn(me, 'isAnonymous').and.returnValue(false)
view = new RequestQuoteView()
view.render()
jasmine.demoEl(view.$el)
request = jasmine.Ajax.requests.mostRecent()
request.respondWith({
status: 200
responseText: JSON.stringify([{
_id: '1'
properties: {
firstName: 'First'
lastName: 'Last'
}
}])
})
_.defer done # Let SuperModel finish _.defer done # Let SuperModel finish
it 'shows form with data from the most recent request', -> describe 'has an existing trial request', ->
expect(view.$('input[name="firstName"]').val()).toBe('First') beforeEach (done) ->
request = jasmine.Ajax.requests.mostRecent()
describe 'when a user is anonymous and does NOT have an existing trial request', -> request.respondWith({
beforeEach (done) -> status: 200
me.clear() responseText: JSON.stringify([{
me.set('_id', '1234') _id: '1'
me._revertAttributes = {} properties: {
spyOn(me, 'isAnonymous').and.returnValue(true) firstName: 'First'
view = new RequestQuoteView() lastName: 'Last'
view.render() }
jasmine.demoEl(view.$el) }])
request = jasmine.Ajax.requests.mostRecent()
request.respondWith({
status: 200
responseText: '[]'
})
_.defer done # Let SuperModel finish
describe 'when the form is unchanged', ->
it 'does not prevent navigating away', ->
expect(_.result(view, 'onLeaveMessage')).toBeFalsy()
describe 'when the form has changed but is not submitted', ->
beforeEach ->
view.$el.find('#request-form').trigger('change')
it 'prevents navigating away', ->
expect(_.result(view, 'onLeaveMessage')).toBeTruthy()
describe 'on successful form submit', ->
beforeEach ->
view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented
forms.objectToForm(view.$el, successFormValues)
view.$('#request-form').submit()
@submitRequest = _.last(jasmine.Ajax.requests.filter(isSubmitRequest))
@submitRequest.respondWith({
status: 201
responseText: JSON.stringify(_.extend({_id: 'a'}, successFormValues))
}) })
_.defer done # Let SuperModel finish
it 'does not prevent navigating away', -> it 'shows request received', ->
expect(_.result(view, 'onLeaveMessage')).toBeFalsy()
it 'creates a new trial request', ->
expect(@submitRequest).toBeTruthy()
expect(@submitRequest.method).toBe('POST')
attrs = JSON.parse(@submitRequest.params)
expect(attrs.properties?.siteOrigin).toBe('demo request')
it 'sets the user\'s role to the one they chose', ->
request = _.last(jasmine.Ajax.requests.filter((r) -> _.string.startsWith(r.url, '/db/user')))
expect(request).toBeTruthy()
expect(request.method).toBe('PUT')
expect(JSON.parse(request.params).role).toBe('teacher')
it 'shows a signup form', ->
expect(view.$('#form-submit-success').hasClass('hide')).toBe(false)
expect(view.$('#request-form').hasClass('hide')).toBe(true) expect(view.$('#request-form').hasClass('hide')).toBe(true)
expect(view.$('#form-submit-success').hasClass('hide')).toBe(false)
describe 'signup form', ->
describe 'does NOT have an existing trial request', ->
beforeEach (done) ->
request = jasmine.Ajax.requests.mostRecent()
request.respondWith({
status: 200
responseText: '[]'
})
_.defer done # Let SuperModel finish
describe 'when the form is unchanged', ->
it 'does not prevent navigating away', ->
expect(_.result(view, 'onLeaveMessage')).toBeFalsy()
describe 'when the form has changed but is not submitted', ->
beforeEach -> beforeEach ->
application.facebookHandler.fakeAPI() view.$el.find('#request-form').trigger('change')
application.gplusHandler.fakeAPI()
it 'prevents navigating away', ->
it 'fills the username field with the given first and last names', -> expect(_.result(view, 'onLeaveMessage')).toBeTruthy()
expect(view.$('input[name="name"]').val()).toBe('A B')
describe 'on successful form submit', ->
it 'includes a facebook button which will sign them in immediately', -> beforeEach ->
view.$('#facebook-signup-btn').click() view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented
request = jasmine.Ajax.requests.mostRecent() forms.objectToForm(view.$el, successForm)
expect(request.method).toBe('PUT') view.$('#request-form').submit()
expect(request.url).toBe('/db/user?facebookID=abcd&facebookAccessToken=1234') @submitRequest = _.last(jasmine.Ajax.requests.filter(isSubmitRequest))
@submitRequest.respondWith({
it 'includes a gplus button which will sign them in immediately', -> status: 201
view.$('#gplus-signup-btn').click() responseText: JSON.stringify(_.extend({_id: 'a'}, successForm))
request = jasmine.Ajax.requests.mostRecent()
expect(request.method).toBe('PUT')
expect(request.url).toBe('/db/user?gplusID=abcd&gplusAccessToken=1234')
it 'can sign them up with username and password', ->
form = view.$('#signup-form')
forms.objectToForm(form, {
password1: 'asdf'
password2: 'asdf'
name: 'some name'
}) })
form.submit()
request = jasmine.Ajax.requests.mostRecent() it 'does not prevent navigating away', ->
expect(_.result(view, 'onLeaveMessage')).toBeFalsy()
it 'creates a new trial request', ->
expect(@submitRequest).toBeTruthy()
expect(@submitRequest.method).toBe('POST')
attrs = JSON.parse(@submitRequest.params)
expect(attrs.properties?.siteOrigin).toBe('demo request')
it 'sets the user\'s role to the one they chose', ->
request = _.last(jasmine.Ajax.requests.filter((r) -> _.string.startsWith(r.url, '/db/user')))
expect(request).toBeTruthy()
expect(request.method).toBe('PUT') expect(request.method).toBe('PUT')
expect(request.url).toBe('/db/user/1234') expect(JSON.parse(request.params).role).toBe('teacher')
describe 'when an anonymous user tries to submit a request with an existing user\'s email', -> it 'shows a signup form', ->
expect(view.$('#form-submit-success').hasClass('hide')).toBe(false)
expect(view.$('#request-form').hasClass('hide')).toBe(true)
describe 'signup form', ->
beforeEach ->
application.facebookHandler.fakeAPI()
application.gplusHandler.fakeAPI()
it 'fills the username field with the given first and last names', ->
expect(view.$('input[name="name"]').val()).toBe('A B')
it 'includes a facebook button which will sign them in immediately', ->
view.$('#facebook-signup-btn').click()
request = jasmine.Ajax.requests.mostRecent()
expect(request.method).toBe('PUT')
expect(request.url).toBe('/db/user?facebookID=abcd&facebookAccessToken=1234')
it 'includes a gplus button which will sign them in immediately', ->
view.$('#gplus-signup-btn').click()
request = jasmine.Ajax.requests.mostRecent()
expect(request.method).toBe('PUT')
expect(request.url).toBe('/db/user?gplusID=abcd&gplusAccessToken=1234')
it 'can sign them up with username and password', ->
form = view.$('#signup-form')
forms.objectToForm(form, {
password1: 'asdf'
password2: 'asdf'
name: 'some name'
})
form.submit()
request = jasmine.Ajax.requests.mostRecent()
expect(request.method).toBe('PUT')
expect(request.url).toBe('/db/user/1234')
describe 'tries to submit a request with an existing user\'s email', ->
beforeEach -> beforeEach ->
forms.objectToForm(view.$el, successFormValues) forms.objectToForm(view.$el, successForm)
view.$('#request-form').submit() view.$('#request-form').submit()
@submitRequest = _.last(jasmine.Ajax.requests.filter(isSubmitRequest)) @submitRequest = _.last(jasmine.Ajax.requests.filter(isSubmitRequest))
@submitRequest.respondWith({ @submitRequest.respondWith({
status: 409 status: 409
responseText: '{}' responseText: '{}'
}) })
it 'shows an error that the email already exists', -> it 'shows an error that the email already exists', ->
expect(view.$('#email-form-group').hasClass('has-error')).toBe(true) expect(view.$('#email-form-group').hasClass('has-error')).toBe(true)
expect(view.$('#email-form-group .error-help-block').length).toBe(1) expect(view.$('#email-form-group .error-help-block').length).toBe(1)
describe 'when user is signed in and has role "student"', -> describe 'submits the form without school', ->
beforeEach ->
view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented
form = view.$('#request-form')
formData = _.omit(successForm, ['organization'])
forms.objectToForm(form, formData)
form.submit()
it 'submits a trial request, which does not include school setting', ->
request = jasmine.Ajax.requests.mostRecent()
expect(request.url).toBe('/db/trial.request')
expect(request.method).toBe('POST')
attrs = JSON.parse(request.params)
expect(attrs.properties?.organization).toBeUndefined()
expect(attrs.properties?.district).toEqual('District')
describe 'submits the form without district', ->
beforeEach ->
view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented
form = view.$('#request-form')
formData = _.omit(successForm, ['district'])
forms.objectToForm(form, formData)
form.submit()
it 'displays a validation error on district and not school', ->
expect(view.$('#organization-control').parent().hasClass('has-error')).toEqual(false)
expect(view.$('#district-control').parent().hasClass('has-error')).toEqual(true)
describe 'submits form with district set to n/a', ->
beforeEach ->
view.$el.find('#request-form').trigger('change') # to confirm navigating away isn't prevented
form = view.$('#request-form')
formData = _.omit(successForm, ['organization'])
formData.district = 'N/A'
forms.objectToForm(form, formData)
form.submit()
it 'submits a trial request, which does not include district setting', ->
expect(view.$('#organization-control').parent().hasClass('has-error')).toEqual(false)
expect(view.$('#district-control').parent().hasClass('has-error')).toEqual(false)
request = jasmine.Ajax.requests.mostRecent()
expect(request.url).toBe('/db/trial.request')
expect(request.method).toBe('POST')
attrs = JSON.parse(request.params)
expect(attrs.properties?.district).toBeUndefined()
describe 'when a signed in user', ->
beforeEach (done) -> beforeEach (done) ->
me.set('role', 'student') me.clear()
me.set('name', 'Some User') me.set('_id', '1234')
me._revertAttributes = {}
spyOn(me, 'isAnonymous').and.returnValue(false) spyOn(me, 'isAnonymous').and.returnValue(false)
view = new RequestQuoteView() view = new RequestQuoteView()
view.render() view.render()
jasmine.demoEl(view.$el) jasmine.demoEl(view.$el)
request = jasmine.Ajax.requests.mostRecent()
request.respondWith({ status: 200, responseText: '[]'})
_.defer done # Let SuperModel finish _.defer done # Let SuperModel finish
it 'shows a conversion warning', -> describe 'has an existing trial request', ->
expect(view.$('#conversion-warning').length).toBe(1) beforeEach (done) ->
request = jasmine.Ajax.requests.mostRecent()
it 'requires confirmation to submit the form', -> request.respondWith({
form = view.$('#request-form') status: 200
forms.objectToForm(form, successFormValues) responseText: JSON.stringify([{
spyOn(view, 'openModalView') _id: '1'
form.submit() properties: {
expect(view.openModalView).toHaveBeenCalled() firstName: 'First'
lastName: 'Last'
submitRequest = _.last(jasmine.Ajax.requests.filter(isSubmitRequest)) }
expect(submitRequest).toBeFalsy() }])
confirmModal = view.openModalView.calls.argsFor(0)[0] })
confirmModal.trigger 'confirm' _.defer done # Let SuperModel finish
submitRequest = _.last(jasmine.Ajax.requests.filter(isSubmitRequest))
expect(submitRequest).toBeTruthy() it 'shows form with data from the most recent request', ->
expect(view.$('input[name="firstName"]').val()).toBe('First')
describe 'has role "student"', ->
beforeEach (done) ->
me.clear()
me.set('role', 'student')
me.set('name', 'Some User')
request = jasmine.Ajax.requests.mostRecent()
request.respondWith({ status: 200, responseText: '[]'})
_.defer done # Let SuperModel finish
it 'shows a conversion warning', ->
expect(view.$('#conversion-warning').length).toBe(1)
it 'requires confirmation to submit the form', ->
form = view.$('#request-form')
forms.objectToForm(form, successForm)
spyOn(view, 'openModalView')
form.submit()
expect(view.openModalView).toHaveBeenCalled()
submitRequest = _.last(jasmine.Ajax.requests.filter(isSubmitRequest))
expect(submitRequest).toBeFalsy()
confirmModal = view.openModalView.calls.argsFor(0)[0]
confirmModal.trigger 'confirm'
submitRequest = _.last(jasmine.Ajax.requests.filter(isSubmitRequest))
expect(submitRequest).toBeTruthy()