Merge branch 'master' of gitlab.com:lilcity/backend into feature/drawing-camp

remotes/origin/feature/drawing-camp
gzbender 7 years ago
commit d770acb648
  1. 6
      api/v1/serializers/config.py
  2. 9
      api/v1/views.py
  3. 28
      apps/config/migrations/0014_auto_20190605_1338.py
  4. 6
      apps/config/models.py
  5. 10
      apps/content/models.py
  6. 14
      apps/content/templates/content/blocks/video.html
  7. 1
      apps/course/templates/course/course.html
  8. 4
      apps/course/templates/course/lesson.html
  9. 19
      apps/course/templatetags/plural.py
  10. 80
      apps/notification/tasks.py
  11. 22
      apps/notification/templates/notification/email/buy_email.html
  12. 9
      apps/notification/templates/notification/email/camp_certificate.html
  13. 25
      apps/notification/templates/notification/email/certificate.html
  14. 2
      apps/payment/models.py
  15. 15
      apps/payment/views.py
  16. 14
      apps/school/templates/blocks/schedule_item.html
  17. 35
      apps/school/templates/school/livelesson_detail.html
  18. 8
      apps/school/views.py
  19. 20
      apps/user/forms.py
  20. 26
      apps/user/migrations/0032_child.py
  21. 21
      apps/user/migrations/0033_add_childs.py
  22. 29
      apps/user/migrations/0034_auto_20190612_1852.py
  23. 34
      apps/user/models.py
  24. 2
      apps/user/templates/user/bonus-history.html
  25. 99
      apps/user/templates/user/profile-settings.html
  26. 4
      apps/user/templates/user/profile.html
  27. 78
      apps/user/views.py
  28. 15
      project/templates/blocks/about.html
  29. 6
      project/templates/blocks/video.html
  30. 3
      project/tests/test_features.py
  31. 120
      web/src/components/Childs.vue
  32. 4
      web/src/components/blocks/BlockImages.vue
  33. BIN
      web/src/img/heart.png
  34. BIN
      web/src/img/video-ended-popup-like.jpg
  35. BIN
      web/src/img/video-ended-popup-logo.png
  36. BIN
      web/src/img/video-ended-popup-ref.jpg
  37. 1
      web/src/js/app.js
  38. 7
      web/src/js/modules/api.js
  39. 62
      web/src/js/modules/content.js
  40. 2
      web/src/js/modules/courses.js
  41. 2
      web/src/js/modules/popup.js
  42. 1
      web/src/js/pages/course.js
  43. 1
      web/src/js/pages/live-lesson.js
  44. 6
      web/src/js/pages/profile.js
  45. 31
      web/src/js/utils.js
  46. 90
      web/src/sass/_common.sass
  47. 2
      web/webpack.config.js

@ -27,6 +27,9 @@ class ConfigSerializer(serializers.ModelSerializer):
REFERRAL_BONUS = serializers.IntegerField() REFERRAL_BONUS = serializers.IntegerField()
MAIN_PAGE_VIDEO_URL = serializers.CharField(required=False) MAIN_PAGE_VIDEO_URL = serializers.CharField(required=False)
MAIN_PAGE_VIDEO_PREVIEW_IMG = serializers.SerializerMethodField() MAIN_PAGE_VIDEO_PREVIEW_IMG = serializers.SerializerMethodField()
NUMBER_OF_STUDENTS = serializers.IntegerField()
NUMBER_OF_COUNTRIES = serializers.IntegerField()
NUMBER_OF_CITIES = serializers.IntegerField()
class Meta: class Meta:
model = Config model = Config
@ -50,6 +53,9 @@ class ConfigSerializer(serializers.ModelSerializer):
'REFERRAL_BONUS', 'REFERRAL_BONUS',
'MAIN_PAGE_VIDEO_URL', 'MAIN_PAGE_VIDEO_URL',
'MAIN_PAGE_VIDEO_PREVIEW_IMG', 'MAIN_PAGE_VIDEO_PREVIEW_IMG',
'NUMBER_OF_STUDENTS',
'NUMBER_OF_COUNTRIES',
'NUMBER_OF_CITIES',
) )
def get_SCHOOL_LOGO_IMAGE(self, config): def get_SCHOOL_LOGO_IMAGE(self, config):

@ -212,6 +212,15 @@ class LikeViewSet(ExtendedModelViewSet):
headers = self.get_success_headers(serializer.data) headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
@action(methods=['get'], detail=False, url_path='course-liked')
def course_liked(self, request):
user_id = request.query_params.get('user_id')
course_id = request.query_params.get('course_id')
course = Course.objects.get(id=course_id)
return Response({
'is_liked': course.likes.filter(user_id=user_id).exists()
})
class CategoryViewSet(BothListFormatMixin, ExtendedModelViewSet): class CategoryViewSet(BothListFormatMixin, ExtendedModelViewSet):
queryset = Category.objects.order_by('-id') queryset = Category.objects.order_by('-id')

@ -0,0 +1,28 @@
# Generated by Django 2.0.7 on 2019-06-05 13:38
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('config', '0013_config_main_page_video_preview_img'),
]
operations = [
migrations.AddField(
model_name='config',
name='NUMBER_OF_CITIES',
field=models.IntegerField(default=107),
),
migrations.AddField(
model_name='config',
name='NUMBER_OF_COUNTRIES',
field=models.IntegerField(default=81),
),
migrations.AddField(
model_name='config',
name='NUMBER_OF_STUDENTS',
field=models.IntegerField(default=17000),
),
]

@ -23,6 +23,9 @@ class Config(models.Model):
REFERRAL_BONUS = models.IntegerField(default=10) REFERRAL_BONUS = models.IntegerField(default=10)
MAIN_PAGE_VIDEO_URL = models.URLField(max_length=126, default='https://www.youtube.com/embed/1w3amQGtQyc') MAIN_PAGE_VIDEO_URL = models.URLField(max_length=126, default='https://www.youtube.com/embed/1w3amQGtQyc')
MAIN_PAGE_VIDEO_PREVIEW_IMG = models.ImageField(null=True, blank=True) MAIN_PAGE_VIDEO_PREVIEW_IMG = models.ImageField(null=True, blank=True)
NUMBER_OF_STUDENTS = models.IntegerField(default=17000)
NUMBER_OF_COUNTRIES = models.IntegerField(default=81)
NUMBER_OF_CITIES = models.IntegerField(default=107)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
self.pk = 1 self.pk = 1
@ -57,5 +60,8 @@ class Config(models.Model):
'REFERRAL_BONUS': '', 'REFERRAL_BONUS': '',
'MAIN_PAGE_VIDEO_URL': '', 'MAIN_PAGE_VIDEO_URL': '',
'MAIN_PAGE_VIDEO_PREVIEW_IMG': '', 'MAIN_PAGE_VIDEO_PREVIEW_IMG': '',
'NUMBER_OF_STUDENTS': '',
'NUMBER_OF_COUNTRIES': '',
'NUMBER_OF_CITIES': '',
} }
return obj return obj

@ -99,7 +99,7 @@ class Video(Content):
url = models.URLField('Ссылка') url = models.URLField('Ссылка')
def video_index(self): def video_index(self):
if 'youtu.be' in self.url or 'youtube.com' in self.url and 'watch' in self.url: if self.is_youtube_video:
url = urlparse(self.url) url = urlparse(self.url)
query = url.query.split('&') query = url.query.split('&')
for q in query: for q in query:
@ -107,6 +107,14 @@ class Video(Content):
return q.split('=')[-1] return q.split('=')[-1]
return self.url.split('/')[-1] return self.url.split('/')[-1]
@property
def is_youtube_video(self):
return 'youtu.be' in self.url or 'youtube.com' in self.url and 'watch' in self.url
@property
def is_vimeo_video(self):
return 'vimeo.com' in self.url
class Gallery(Content): class Gallery(Content):
pass pass

@ -4,12 +4,14 @@
{{ content.title }} {{ content.title }}
</div> </div>
<div class="content-block__video iframe__container"> <div class="content-block__video iframe__container">
{% if 'youtube.com' in content.url or 'youtu.be' in content.url %} {% if content.is_youtube_video %}
<iframe src="https://www.youtube.com/embed/{{ content.video_index }}" frameborder="0" allow="autoplay; encrypted-media" <iframe class="js-video" data-is-youtube="1" src="https://www.youtube.com/embed/{{ content.video_index }}"
webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe> data-course-id="{% if content.lesson_id %}{{ content.lesson.course_id }}{% else %}{{ content.course_id|default:'' }}{% endif %}"
{% elif 'vimeo.com' in content.url %} frameborder="0" allow="autoplay; encrypted-media" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
<iframe src="https://player.vimeo.com/video/{{ content.video_index }}" frameborder="0" webkitallowfullscreen {% elif content.is_vimeo_video %}
mozallowfullscreen allowfullscreen> <iframe class="js-video" data-is-vimeo="1" src="https://player.vimeo.com/video/{{ content.video_index }}"
data-course-id="{% if content.lesson_id %}{{ content.lesson.course_id }}{% else %}{{ content.course_id|default:'' }}{% endif %}"
frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen>
</iframe> </iframe>
{% endif %} {% endif %}
</div> </div>

@ -394,4 +394,5 @@
{% block foot %} {% block foot %}
{% include "templates/blocks/popup_course_buy.html" %} {% include "templates/blocks/popup_course_buy.html" %}
<script type="text/javascript" src="{% static 'course.js' %}"></script>
{% endblock foot %} {% endblock foot %}

@ -123,3 +123,7 @@
</div> </div>
</div> </div>
{% endblock content %} {% endblock content %}
{% block foot %}
<script type="text/javascript" src="{% static 'course.js' %}"></script>
{% endblock foot %}

@ -4,14 +4,23 @@ from django.template.defaultfilters import stringfilter
register = template.Library() register = template.Library()
@register.filter(is_safe=False) def _ruplural(value, arg):
@stringfilter
def rupluralize(value, arg):
args = arg.split(',') args = arg.split(',')
try: try:
_value = str(0 if not value or int(value) <= 0 else value)[-1:] _value = str(0 if not value or int(value) <= 0 else value)[-1:]
value = value + ' ' + args[0 if _value == '1' else (1 if _value in '234' else 2)] return args[0 if _value == '1' else (1 if _value in '234' else 2)]
return value
except: except:
raise template.TemplateSyntaxError raise template.TemplateSyntaxError
return '' return ''
@register.filter(is_safe=False)
@stringfilter
def rupluralize(value, arg):
return value + ' ' + _ruplural(value, arg)
@register.filter(is_safe=False)
@stringfilter
def ruplural(value, arg):
return _ruplural(value, arg)

@ -3,12 +3,14 @@ from datetime import datetime, date, timedelta
from PIL import Image from PIL import Image
from PIL import ImageFont from PIL import ImageFont
from PIL import ImageDraw from PIL import ImageDraw
from unidecode import unidecode
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.contrib.staticfiles.storage import staticfiles_storage from django.contrib.staticfiles.storage import staticfiles_storage
from django.utils.timezone import now from django.utils.timezone import now
from django.conf import settings from django.conf import settings
from django.utils.text import slugify
from apps.notification.models import UserNotification from apps.notification.models import UserNotification
from apps.notification.utils import send_email from apps.notification.utils import send_email
@ -16,6 +18,7 @@ from apps.payment.models import SchoolPayment, CoursePayment, Payment, UserGiftC
from project.celery import app from project.celery import app
from project.utils.db import format_sql, execute_sql from project.utils.db import format_sql, execute_sql
from project.sengrid import get_sendgrid_client from project.sengrid import get_sendgrid_client
from apps.user.models import Child
User = get_user_model() User = get_user_model()
@ -36,7 +39,11 @@ def draw_cert(path, email, first_name, last_name, x=None, y=900, color=(29, 115,
os.mkdir(fn) os.mkdir(fn)
except: except:
pass pass
fn = os.path.join(fn, '%scertificate-for-%s.jpg' % (fn_prefix, email)) if first_name:
name = '-'.join(filter(None, [first_name, last_name]))
fn = os.path.join(fn, '%scertificate-for-%s-%s.jpg' % (fn_prefix, email, slugify(unidecode(name))))
else:
fn = os.path.join(fn, '%scertificate-for-%s.jpg' % (fn_prefix, email))
img.save(fn) img.save(fn)
img.close() img.close()
return fn return fn
@ -76,25 +83,30 @@ def send_certificates(email=None, date_end=None, dry_run=False):
if dry_run: if dry_run:
continue continue
file_names = []
un.certificate_number = un.certificate_number + 1 \ un.certificate_number = un.certificate_number + 1 \
if un.certificate_number and staticfiles_storage.exists(path_pattern % (un.certificate_number + 1)) \ if un.certificate_number and staticfiles_storage.exists(path_pattern % (un.certificate_number + 1)) \
else 1 else 1
if un.user.child_first_name: if un.user.child_filled:
fn = staticfiles_storage.path(signed_path_pattern % un.certificate_number) fn = staticfiles_storage.path(signed_path_pattern % un.certificate_number)
fn = draw_cert(fn, un.user.email, un.user.child_first_name, un.user.child_last_name) for child in un.user.childs.all():
file_names.append(draw_cert(fn, un.user.email, child.first_name, child.last_name))
else: else:
fn = staticfiles_storage.path(path_pattern % un.certificate_number) file_names.append(staticfiles_storage.path(path_pattern % un.certificate_number))
file = open(fn, 'rb') files = [open(fn, 'rb') for fn in file_names]
try: try:
send_email('Грамота от Lil School', un.user.email, 'notification/email/certificate.html', send_email('Грамота от Lil School', un.user.email, 'notification/email/certificate.html',
attachments=[(file.name, file.read(), 'image/jpeg')], user_notification=un) attachments=[(f.name, f.read(), 'image/jpeg') for f in files], user_notification=un,
many_childs=un.user.childs.all().count() > 1)
except: except:
print('Not OK') print('Not OK')
continue continue
finally: finally:
file.close() for f in files:
if un.user.child_first_name: f.close()
os.remove(fn) if un.user.child_filled:
for fn in file_names:
os.remove(fn)
un.certificate_last_email = now() un.certificate_last_email = now()
un.save() un.save()
@ -148,19 +160,20 @@ def send_gift_certificate(user_gift_certificate):
@app.task @app.task
def send_child_birthday_email_and_bonuses(): def send_child_birthday_email_and_bonuses():
for u in User.objects.exclude(child_first_name='', child_last_name='',).filter(child_birthday=now().date()): for user_id, email in set(Child.objects.filter(birthday=now().date()).select_related('user')
if not UserBonus.objects.filter(user=u, is_service=True, action_name=UserBonus.ACTION_CHILD_BIRTHDAY).count(): .values_list('user_id', 'user__email')):
print('user', u.email) print('user', email)
UserBonus.objects.create(user=u, amount=UserBonus.AMOUNT_CHILD_BIRTHDAY, is_service=True, if not UserBonus.objects.filter(user=user_id, is_service=True, action_name=UserBonus.ACTION_CHILD_BIRTHDAY).count():
UserBonus.objects.create(user=user_id, amount=UserBonus.AMOUNT_CHILD_BIRTHDAY, is_service=True,
action_name=UserBonus.ACTION_CHILD_BIRTHDAY) action_name=UserBonus.ACTION_CHILD_BIRTHDAY)
try: try:
fn = staticfiles_storage.path('img/birthday_postcard.jpg') fn = staticfiles_storage.path('img/birthday_postcard.jpg')
file = open(fn, 'rb') file = open(fn, 'rb')
send_email('С Днем Рождения!', u.email, 'notification/email/birthday_postcard.html', send_email('С Днем Рождения!', email, 'notification/email/birthday_postcard.html',
attachments=[(file.name, file.read(), 'image/jpeg')],) attachments=[(file.name, file.read(), 'image/jpeg')],)
print('email has been sent') print('email has been sent')
finally: finally:
file.close() file.close()
@app.task @app.task
def send_camp_certificates(email=None, dry_run=False, certificate_number=None): def send_camp_certificates(email=None, dry_run=False, certificate_number=None):
@ -178,7 +191,6 @@ def send_camp_certificates(email=None, dry_run=False, certificate_number=None):
font_size=120, y=1000, color=color) font_size=120, y=1000, color=color)
file = open(fn, 'rb') file = open(fn, 'rb')
try: try:
pass
send_email('Грамота от Lil School', email, 'notification/email/camp_certificate.html', send_email('Грамота от Lil School', email, 'notification/email/camp_certificate.html',
attachments=[(file.name, file.read(), 'image/jpeg')], certificate_number=certificate_number) attachments=[(file.name, file.read(), 'image/jpeg')], certificate_number=certificate_number)
except: except:
@ -205,23 +217,27 @@ def send_camp_certificates(email=None, dry_run=False, certificate_number=None):
if dry_run: if dry_run:
continue continue
if un.user.child_first_name: file_names = []
if un.user.child_filled:
fn = staticfiles_storage.path(signed_path_pattern % certificate_number) fn = staticfiles_storage.path(signed_path_pattern % certificate_number)
fn = draw_cert(fn, un.user.email, un.user.child_first_name, un.user.child_last_name, for child in un.user.childs.all():
font_size=120, y=1000, color=color) file_names.append(draw_cert(fn, un.user.email, child.first_name, child.last_name,
font_size=120, y=1000, color=color))
else: else:
fn = staticfiles_storage.path(path_pattern % certificate_number) file_names.append(staticfiles_storage.path(path_pattern % certificate_number))
file = open(fn, 'rb') files = [open(fn, 'rb') for fn in file_names]
try: try:
send_email('Грамота от Lil School', un.user.email, 'notification/email/camp_certificate.html', send_email('Грамота от Lil School', un.user.email, 'notification/email/camp_certificate.html',
attachments=[(file.name, file.read(), 'image/jpeg')], user_notification=un, attachments=[(f.name, f.read(), 'image/jpeg') for f in files], user_notification=un,
certificate_number=certificate_number) certificate_number=certificate_number, many_childs=un.user.childs.all().count() > 1)
except: except:
print('Not OK') print('Not OK')
continue continue
finally: finally:
file.close() for f in files:
if un.user.child_first_name: f.close()
os.remove(fn) if un.user.child_filled:
for fn in file_names:
os.remove(fn)
un.camp_certificate_last_email = date_end un.camp_certificate_last_email = date_end
un.save() un.save()

@ -0,0 +1,22 @@
{% extends "notification/email/_base.html" %}
{% load settings %}
{% block content %}
{% if product_type == 'course' %}
<p>Курс ждет вас по ссылке <a href="https://{% setting 'MAIN_HOST' %}{{ url }}">
https://{% setting 'MAIN_HOST' %}{{ url }}</a></p>
{% endif %}
{% if product_type == 'school' %}
<p>Школа ждет вас по ссылке <a href="https://{% setting 'MAIN_HOST' %}{% url 'school:school' %}">
https://{% setting 'MAIN_HOST' %}{% url 'school:school' %}</a></p>
{% endif %}
{% if product_type == 'drawing_camp' %}
<p>Рисовальный лагерь ждет вас по ссылке <a href="https://{% setting 'MAIN_HOST' %}{% url 'school:drawing-camp' %}">
https://{% setting 'MAIN_HOST' %}{% url 'school:drawing-camp' %}</a></p>
{% endif %}
<p>Так же вы можете найти ссылку в личном кабинете в разделе «Мои покупки».</p>
<p>Занимайтесь с удовольствием!</p>
<p>Команда «Lil School».</p>
{% endblock content %}

@ -6,20 +6,21 @@
{% if certificate_number == 1 %} {% if certificate_number == 1 %}
<p> <p>
Поздравляем! Вы прошли месяц обучения в лагере Lil School.<br> Поздравляем! Вы прошли месяц обучения в лагере Lil School.<br>
К письму прикреплена грамота. Распечатайте её и вручите вашим детям.<br> К письму {% if many_childs %}прикреплены грамоты. Распечатайте их{% else %}прикреплена грамота. Распечатайте её{% endif %} и вручите вашим детям.<br>
Ждём вас в следующем месяце на наших творческих занятиях! Ждём вас в следующем месяце на наших творческих занятиях!
</p> </p>
{% endif %} {% endif %}
{% if certificate_number == 2 %} {% if certificate_number == 2 %}
<p> <p>
Вы помните, что каждый месяц вам приходит грамота за прекрасную учебу в рисовальном лагере?<br> Вы помните, что каждый месяц вам {% if many_childs %}приходят грамоты{% else %}приходит грамота{% endif %} за прекрасную учебу в рисовальном лагере?<br>
Скачивайте. Распечатывайте. И соберите свою коллекцию! Скачивайте. Распечатывайте. И соберите свою коллекцию!
</p> </p>
{% endif %} {% endif %}
{% if certificate_number == 3 %} {% if certificate_number == 3 %}
<p> <p>
Вот и закончился лагерь, пора переходить к основным урокам.<br> Поздравляем с успешным окончаниемм рисовального лагеря!<br>
Спасибо, что были с нами, успехов вам в дальнейшем обучении! В письме вы найдёте {% if many_childs %}грамоты, они{% else %}грамоту, она{% endif %} для вашей семьи.<br>
До встречи в онлайн школе!
</p> </p>
{% endif %} {% endif %}
</div> </div>

@ -6,20 +6,20 @@
{% if not user_notification or user_notification.certificate_number == 1 %} {% if not user_notification or user_notification.certificate_number == 1 %}
<p> <p>
Поздравляем! Вы прошли месяц обучения в Lil School.<br> Поздравляем! Вы прошли месяц обучения в Lil School.<br>
К письму прикреплена грамота. Распечатайте её и вручите вашим детям.<br> К письму {% if many_childs %}прикреплены грамоты. Распечатайте их{% else %}прикреплена грамота. Распечатайте её{% endif %} и вручите вашим детям.<br>
Ждём вас в следующем месяце на наших творческих занятиях! Ждём вас в следующем месяце на наших творческих занятиях!
</p> </p>
{% endif %} {% endif %}
{% if user_notification and user_notification.certificate_number == 2 %} {% if user_notification and user_notification.certificate_number == 2 %}
<p> <p>
Вы помните, что каждый месяц вам приходит грамота за прекрасную учебу в нашей творческой школе?<br> Вы помните, что каждый месяц вам {% if many_childs %}приходят грамоты{% else %}приходит грамота{% endif %} за прекрасную учебу в нашей творческой школе?<br>
Скачивайте. Распечатывайте. И соберите свою коллекцию! Скачивайте. Распечатывайте. И соберите свою коллекцию!
</p> </p>
{% endif %} {% endif %}
{% if user_notification and user_notification.certificate_number == 3 %} {% if user_notification and user_notification.certificate_number == 3 %}
<p> <p>
Вам понравился наш творческий месяц?<br> Вам понравился наш творческий месяц?<br>
В письме вы найдёте грамоту, она для вашей семьи.<br> В письме вы найдёте {% if many_childs %}грамоты, они{% else %}грамоту, она{% endif %} для вашей семьи.<br>
Как здорово, что у нас есть такие ученики!<br> Как здорово, что у нас есть такие ученики!<br>
Ждём вас в следующем месяце. Ждём вас в следующем месяце.
</p> </p>
@ -27,22 +27,27 @@
{% if user_notification and user_notification.certificate_number == 4 %} {% if user_notification and user_notification.certificate_number == 4 %}
<p> <p>
Прошёл целый месяц обучения на платформе Lil School - месяц творчества, креатива и невероятных идей.<br> Прошёл целый месяц обучения на платформе Lil School - месяц творчества, креатива и невероятных идей.<br>
Во вложении вас ждёт грамота.<br> Во вложении вас {% if many_childs %}ждут грамоты{% else %}ждёт грамота{% endif %}.<br>
До встречи на занятиях! До встречи на занятиях!
</p> </p>
{% endif %} {% endif %}
{% if user_notification and user_notification.certificate_number == 5 %} {% if user_notification and user_notification.certificate_number == 5 %}
<p> <p>
{% if many_childs %}
Ваши грамоты за успехи в учебе в Lil School ждут вас во вложении.<br>
Скорее распечатайте их!<br>
{% else %}
Ваша грамота за успехи в учебе в Lil School ждёт вас во вложении.<br> Ваша грамота за успехи в учебе в Lil School ждёт вас во вложении.<br>
Скорее распечатайте её!<br> Скорее распечатайте её!<br>
Вам есть чем гордится!<br> {% endif %}
До встречи в следующем месяце! Вам есть чем гордится!<br>
До встречи в следующем месяце!
</p> </p>
{% endif %} {% endif %}
{% if user_notification and user_notification.certificate_number == 6 %} {% if user_notification and user_notification.certificate_number == 6 %}
<p> <p>
Как здорово вы потрудились на занятиях в этом месяце!<br> Как здорово вы потрудились на занятиях в этом месяце!<br>
И наша грамота уже ждёт вас!<br> И {% if many_childs %}наши грамоты уже ждут{% else %}наша грамота уже ждёт{% endif %} вас!<br>
Спасибо за творчество и креатив.<br> Спасибо за творчество и креатив.<br>
Ждём вас в следующем месяце! Ждём вас в следующем месяце!
</p> </p>
@ -50,7 +55,7 @@
{% if user_notification and user_notification.certificate_number == 7 %} {% if user_notification and user_notification.certificate_number == 7 %}
<p> <p>
Какой классный месяц у нас был! Вместе мы очень здорово и креативно потрудились.<br> Какой классный месяц у нас был! Вместе мы очень здорово и креативно потрудились.<br>
Во вложении вас ждёт заслуженная грамота!<br> Во вложении вас {% if many_childs %}ждут заслуженные грамоты{% else %}ждёт заслуженная грамота{% endif %}!<br>
До встречи на уроках! До встречи на уроках!
</p> </p>
{% endif %} {% endif %}
@ -58,7 +63,7 @@
<p> <p>
Месяц творчества и креатива пролетел как один миг! А как много работ мы вместе сделали!<br> Месяц творчества и креатива пролетел как один миг! А как много работ мы вместе сделали!<br>
Вы - большие молодцы.<br> Вы - большие молодцы.<br>
Во вложении ваш ждёт грамота!<br> Во вложении вас {% if many_childs %}ждут грамоты{% else %}ждёт грамота{% endif %}!<br>
До встречи на занятиях. До встречи на занятиях.
</p> </p>
{% endif %} {% endif %}

@ -356,7 +356,7 @@ class SchoolPayment(Payment):
class DrawingCampPayment(Payment): class DrawingCampPayment(Payment):
MONTH_PRICE = Decimal(1490) MONTH_PRICE = Decimal(1990)
WEEKDAYS = {1, 3, 5} WEEKDAYS = {1, 3, 5}
date_start = models.DateField('Дата начала подписки', null=True, blank=True) date_start = models.DateField('Дата начала подписки', null=True, blank=True)

@ -1,6 +1,5 @@
from decimal import Decimal from decimal import Decimal
import short_url import short_url
import arrow
import json import json
import logging import logging
@ -8,24 +7,22 @@ from datetime import timedelta
from urllib.parse import urlsplit from urllib.parse import urlsplit
import datetime import datetime
import calendar
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.http import HttpResponse, Http404 from django.http import HttpResponse, Http404
from django.shortcuts import redirect, get_object_or_404 from django.shortcuts import redirect, get_object_or_404
from django.views.generic import View, TemplateView, DetailView from django.views.generic import View, TemplateView
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.timezone import now from django.utils.timezone import now
from django.conf import settings
from paymentwall import Pingback, Product, Widget from paymentwall import Pingback, Product, Widget
from apps.course.models import Course from apps.course.models import Course
from apps.school.models import SchoolSchedule
from apps.payment.tasks import transaction_to_mixpanel, product_payment_to_mixpanel, transaction_to_roistat from apps.payment.tasks import transaction_to_mixpanel, product_payment_to_mixpanel, transaction_to_roistat
from apps.notification.utils import send_email
from .models import AuthorBalance, CoursePayment, SchoolPayment, Payment, UserBonus, GiftCertificate, \ from .models import AuthorBalance, CoursePayment, SchoolPayment, Payment, UserBonus, GiftCertificate, \
GiftCertificatePayment, UserGiftCertificate, DrawingCampPayment GiftCertificatePayment, UserGiftCertificate, DrawingCampPayment
@ -325,7 +322,6 @@ class PaymentwallCallbackView(View):
product_type_name, product_type_name,
) )
if product_type_name == 'course': if product_type_name == 'course':
properties = { properties = {
'payment_id': payment.id, 'payment_id': payment.id,
@ -386,6 +382,13 @@ class PaymentwallCallbackView(View):
payment.roistat_visit, payment.roistat_visit,
) )
if product_type_name == 'course':
send_email.delay('Спасибо за покупку!', payment.user.email, 'notification/email/buy_email.html',
product_type=product_type_name, url=payment.course.url)
elif product_type_name != 'gift_certificate':
send_email.delay('Спасибо за покупку!', payment.user.email, 'notification/email/buy_email.html',
product_type=product_type_name)
author_balance = getattr(payment, 'author_balance', None) author_balance = getattr(payment, 'author_balance', None)
if author_balance and author_balance.type == AuthorBalance.IN: if author_balance and author_balance.type == AuthorBalance.IN:
if pingback.is_deliverable(): if pingback.is_deliverable():

@ -7,7 +7,7 @@
<div class="timing__day{% if school_schedule.is_online %} active{% endif %}"> <div class="timing__day{% if school_schedule.is_online %} active{% endif %}">
{{ school_schedule }} {{ school_schedule }}
{% if not is_purchased and request.user_agent.is_mobile and school_schedule.trial_lesson %} {% if not is_purchased and request.user_agent.is_mobile and school_schedule.trial_lesson %}
<a class="timing__trial-lesson js-video-modal" href="#" data-trial-lesson="1" data-video-url="{{ school_schedule.trial_lesson }}">Пробный урок</a> <a class="timing__trial-lesson js-video-modal" href="#" data-trial-lesson="1" data-video-url="{{ school_schedule.trial_lesson|safe }}">Пробный урок</a>
{% endif %} {% endif %}
</div> </div>
{% if is_purchased and live_lesson %} {% if is_purchased and live_lesson %}
@ -24,8 +24,8 @@
{% include './day_pay_btn.html' %} {% include './day_pay_btn.html' %}
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if not is_purchased and not request.user_agent.is_mobile and school_schedule.trial_lesson and not is_drawing_camp %} {% if not is_purchased and not request.user_agent.is_mobile and school_schedule.trial_lesson %}
<a class="timing__trial-lesson js-video-modal" href="#" data-trial-lesson="1" data-video-url="{{ school_schedule.trial_lesson }}">Пробный урок</a> <a class="timing__trial-lesson js-video-modal" href="#" data-trial-lesson="1" data-video-url="{{ school_schedule.trial_lesson|safe }}">Пробный урок</a>
{% endif %} {% endif %}
</div> </div>
</div> </div>
@ -56,13 +56,11 @@
{% endif %} {% endif %}
</div> </div>
<div class="timing__content"> <div class="timing__content">
{% comment %}
{% if live_lesson and live_lesson.short_description %} {% if live_lesson and live_lesson.short_description %}
{{ live_lesson.short_description }} {{ live_lesson.short_description|safe }}
{% else %} {% else %}
{% endcomment %} {{ school_schedule.description|safe }}
{{ school_schedule.description }} {% endif %}
{% comment %}{% endif %}{% endcomment %}
</div> </div>
<div class="timing__more"> <div class="timing__more">
<div class="timing__head">Материалы</div> <div class="timing__head">Материалы</div>

@ -15,12 +15,6 @@
{% if livelesson.cover and livelesson.cover.image %}{{ livelesson.cover.image.height }}{% else %}{{ block.super }}{% endif %} {% if livelesson.cover and livelesson.cover.image %}{{ livelesson.cover.image.height }}{% else %}{{ block.super }}{% endif %}
{% endblock ogimage-height %} {% endblock ogimage-height %}
{% block head %}
{% if livelesson.stream_index %}
<script src="https://player.vimeo.com/api/player.js"></script>
{% endif %}
{% endblock head %}
{% block content %} {% block content %}
<div class="section" style="margin-bottom:0;padding-bottom:0"> <div class="section" style="margin-bottom:0;padding-bottom:0">
<div class="section__center center center_sm"> <div class="section__center center center_sm">
@ -29,12 +23,11 @@
<div class="lesson__content">{{ livelesson.short_description | safe | linebreaks }}</div> <div class="lesson__content">{{ livelesson.short_description | safe | linebreaks }}</div>
<div class="lesson__video video"> <div class="lesson__video video">
{% if livelesson.stream_index %} {% if livelesson.stream_index %}
<iframe class="lesson__video_frame" src="https://player.vimeo.com/video/{{ livelesson.stream_index }}" frameborder="0" <iframe class="lesson__video_frame js-video" data-is-vimeo="1" src="https://player.vimeo.com/video/{{ livelesson.stream_index }}" frameborder="0"
width="720" height="400" width="720" height="400"
webkitallowfullscreen mozallowfullscreen allowfullscreen> webkitallowfullscreen mozallowfullscreen allowfullscreen>
</iframe> </iframe>
<span>Если видео не загрузилось, - уменьшите качество видео или <a href="#" onclick="location.reload();">обновите страницу</a></span> <span>Если видео не загрузилось, - уменьшите качество видео или <a href="#" onclick="location.reload();">обновите страницу</a></span>
<!--<iframe class="lesson__chat_frame" src="https://vimeo.com/live-chat/{{ livelesson.stream_index }}" frameborder="0"></iframe>-->
<comments obj-type="live-lesson" obj-id="{{ livelesson.id }}" :is-chat="true"></comments> <comments obj-type="live-lesson" obj-id="{{ livelesson.id }}" :is-chat="true"></comments>
{% else %} {% else %}
{% if livelesson.cover %} {% if livelesson.cover %}
@ -60,29 +53,5 @@
{% endblock content %} {% endblock content %}
{% block foot %} {% block foot %}
{% if livelesson.stream_index %} <script type="text/javascript" src="{% static 'liveLesson.js' %}"></script>
<script>
var iframe = document.querySelector('iframe.lesson__video_frame');
var player = new Vimeo.Player(iframe);
player.on('ended', function() {
console.log('vimeo player: ended', arguments[0]);
});
player.on('error', function() {
console.log('vimeo player: error', arguments[0]);
});
player.on('loaded', function() {
console.log('vimeo player: loaded', arguments[0]);
});
player.on('ready', function() {
console.log('vimeo player: ready', arguments[0]);
});
player.on('play', function() {
console.log('vimeo player: play', arguments[0]);
});
player.on('pause', function() {
console.log('vimeo player: pause', arguments[0]);
});
player.play().catch(e => console.log(e));
</script>
{% endif %}
{% endblock foot %} {% endblock foot %}

@ -38,12 +38,12 @@ class LiveLessonsView(ListView):
def get_queryset(self): def get_queryset(self):
date_start = (now() - timedelta(days=7)).date() date_start = (now() - timedelta(days=7)).date()
date_start, date_end = SchoolPayment.get_date_range(date_start, days=17, is_camp=False) date_start, date_end = SchoolPayment.get_date_range(date_start, days=16, is_camp=False)
date_range = Q( date_range = Q(
date__range=[date_start, date_end] date__range=[date_start, date_end]
) )
exist_dates = LiveLesson.objects.filter(date_range, is_camp=False).values_list('date', flat=True) exist_dates = LiveLesson.objects.filter(date_range, is_camp=False).values_list('date', flat=True)
for i in range((date_end - date_start).days): for i in range((date_end - date_start).days + 1):
d = date_start + timedelta(days=i) d = date_start + timedelta(days=i)
if d not in exist_dates: if d not in exist_dates:
try: try:
@ -74,7 +74,7 @@ class DrawingCampLessonsView(ListView):
date__range=[date_start, date_end] date__range=[date_start, date_end]
) )
exist_dates = LiveLesson.objects.filter(date_range, is_camp=True).values_list('date', flat=True) exist_dates = LiveLesson.objects.filter(date_range, is_camp=True).values_list('date', flat=True)
for i in range((date_end - date_start).days): for i in range((date_end - date_start).days + 1):
d = date_start + timedelta(days=i) d = date_start + timedelta(days=i)
if d.isoweekday() in DrawingCampPayment.WEEKDAYS and d not in exist_dates: if d.isoweekday() in DrawingCampPayment.WEEKDAYS and d not in exist_dates:
try: try:
@ -289,7 +289,7 @@ class DrawingCampView(TemplateView):
next_schedule = school_schedule next_schedule = school_schedule
next_lesson_start = next_schedule.start_at_humanize next_lesson_start = next_schedule.start_at_humanize
if not next_schedule: if not next_schedule:
next_camp_lesson = LiveLesson.objects.filter(date__gt=date_now, is_camp=True).first() next_camp_lesson = LiveLesson.objects.filter(date__gt=date_now, is_camp=True).order_by('date').first()
if next_camp_lesson and next_camp_lesson.school_schedule: if next_camp_lesson and next_camp_lesson.school_schedule:
next_schedule = next_camp_lesson.school_schedule next_schedule = next_camp_lesson.school_schedule
next_lesson_start = arrow.get(datetime.combine(next_camp_lesson.date, next_schedule.start_at), next_lesson_start = arrow.get(datetime.combine(next_camp_lesson.date, next_schedule.start_at),

@ -2,7 +2,8 @@ from django import forms
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from phonenumber_field.formfields import PhoneNumberField from phonenumber_field.formfields import PhoneNumberField
from .fields import CreditCardField from apps.user.fields import CreditCardField
from apps.user.models import Child
User = get_user_model() User = get_user_model()
@ -31,11 +32,6 @@ class UserEditForm(forms.ModelForm):
site = forms.URLField(required=False) site = forms.URLField(required=False)
photo = forms.ImageField(required=False) photo = forms.ImageField(required=False)
child_gender = forms.CharField(required=False)
child_birthday = forms.DateField(input_formats=['%d/%m/%Y'], required=False)
child_first_name = forms.CharField(required=False)
child_last_name = forms.CharField(required=False)
class Meta: class Meta:
model = User model = User
fields = ( fields = (
@ -61,10 +57,6 @@ class UserEditForm(forms.ModelForm):
'vkontakte', 'vkontakte',
'site', 'site',
'photo', 'photo',
'child_gender',
'child_birthday',
'child_first_name',
'child_last_name',
) )
@ -79,3 +71,11 @@ class AuthorRequesForm(forms.Form):
email = forms.CharField() email = forms.CharField()
about = forms.CharField() about = forms.CharField()
facebook = forms.URLField(required=False) facebook = forms.URLField(required=False)
class ChildForm(forms.ModelForm):
birthday = forms.DateField(input_formats=['%d/%m/%Y'], required=False)
class Meta:
model = Child
fields = '__all__'

@ -0,0 +1,26 @@
# Generated by Django 2.0.7 on 2019-06-06 21:05
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('user', '0031_user_review_url'),
]
operations = [
migrations.CreateModel(
name='Child',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('first_name', models.CharField(max_length=30, verbose_name='Имя ребенка')),
('last_name', models.CharField(blank=True, default='', max_length=150, verbose_name='Фамилия ребенка')),
('gender', models.CharField(choices=[('n', 'не указан'), ('m', 'Мужчина'), ('f', 'Женщина')], default='n', max_length=1, verbose_name='Пол ребенка')),
('birthday', models.DateField(blank=True, null=True, verbose_name='День рождения ребенка')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='childs', to=settings.AUTH_USER_MODEL)),
],
),
]

@ -0,0 +1,21 @@
# Generated by Django 2.0.7 on 2019-06-07 20:48
from django.db import migrations
def add_childs(apps, schema_editor):
User = apps.get_model('user', 'User')
Child = apps.get_model('user', 'Child')
for user in User.objects.exclude(child_first_name=''):
Child.objects.get_or_create(user=user, first_name=user.child_first_name, last_name=user.child_last_name,
gender=user.child_gender, birthday=user.child_birthday)
class Migration(migrations.Migration):
dependencies = [
('user', '0032_child'),
]
operations = [
migrations.RunPython(add_childs),
]

@ -0,0 +1,29 @@
# Generated by Django 2.0.7 on 2019-06-12 18:52
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('user', '0033_add_childs'),
]
operations = [
migrations.RemoveField(
model_name='user',
name='child_birthday',
),
migrations.RemoveField(
model_name='user',
name='child_first_name',
),
migrations.RemoveField(
model_name='user',
name='child_gender',
),
migrations.RemoveField(
model_name='user',
name='child_last_name',
),
]

@ -88,12 +88,6 @@ class User(AbstractUser):
'content.Gallery', on_delete=models.CASCADE, 'content.Gallery', on_delete=models.CASCADE,
verbose_name='Галерея', null=True, blank=True, verbose_name='Галерея', null=True, blank=True,
) )
child_first_name = models.CharField('Имя ребенка', max_length=30, blank=True)
child_last_name = models.CharField('Фамилия ребенка', max_length=150, blank=True)
child_gender = models.CharField(
'Пол ребенка', max_length=1, default='n', choices=GENDER_CHOICES)
child_birthday = models.DateField('День рождения ребенка', null=True, blank=True)
review_url = models.URLField('Ссылка на видеоотзыв', blank=True, default='') review_url = models.URLField('Ссылка на видеоотзыв', blank=True, default='')
objects = UserManager() objects = UserManager()
@ -104,6 +98,14 @@ class User(AbstractUser):
class Meta(AbstractUser.Meta): class Meta(AbstractUser.Meta):
ordering = ('-date_joined',) ordering = ('-date_joined',)
@cached_property
def has_child(self):
return self.childs.all().count() > 0
def child_filled(self):
child = self.childs.all().first()
return child and child.first_name and child.last_name and child.birthday
@property @property
def url(self): def url(self):
return reverse('user', args=[self.slug or self.id]) return reverse('user', args=[self.slug or self.id])
@ -338,3 +340,23 @@ class EmailLog(models.Model):
email = models.EmailField(_('email address')) email = models.EmailField(_('email address'))
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
source = models.PositiveSmallIntegerField(choices=SOURCE_CHOICES) source = models.PositiveSmallIntegerField(choices=SOURCE_CHOICES)
class Child(models.Model):
NOT_DEFINED = 'n'
MALE = 'm'
FEMALE = 'f'
GENDER_CHOICES = (
(NOT_DEFINED, 'не указан'),
(MALE, 'Мужчина'),
(FEMALE, 'Женщина'),
)
user = models.ForeignKey(User, related_name='childs', on_delete=models.CASCADE)
first_name = models.CharField('Имя ребенка', max_length=30)
last_name = models.CharField('Фамилия ребенка', max_length=150, blank=True, default='')
gender = models.CharField(
'Пол ребенка', max_length=1, default='n', choices=GENDER_CHOICES)
birthday = models.DateField('День рождения ребенка', null=True, blank=True)
class Meta:
ordering = ('id',)

@ -83,7 +83,7 @@
</div> </div>
<div>+{{ amount_child_birthday }} LIL</div> <div>+{{ amount_child_birthday }} LIL</div>
<div><a class="btn btn_stroke" href="{% url 'user-profile' %}">ЗАПОЛНИТЬ КАРТОЧКУ РЕБЕНКА</a></div> <div><a class="btn btn_stroke" href="{% url 'user-profile' %}">ЗАПОЛНИТЬ КАРТОЧКУ РЕБЕНКА</a></div>
<div>{% if request.user.child_first_name and request.user.child_last_name and request.user.child_birthday %} <div>{% if request.user.child_filled %}
<img src="{% static 'img/done2.png' %}" />{% endif %}</div> <img src="{% static 'img/done2.png' %}" />{% endif %}</div>
</div> </div>
</div> </div>

@ -67,101 +67,41 @@
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
<div class="form__field field{% if form.email.errors %} error{% endif %}">
<div class="field__label">Почта</div>
<div class="field__wrap">
<input name='email' class="field__input" type="email" placeholder="Почта" value="{{ user.email }}">
</div>
{% for error in form.email.errors %}
<div class="field__error">{{ error }}</div>
{% endfor %}
</div>
<div class="form__field field{% if form.slug.errors %} error{% endif %}">
<div class="field__label">Ссылка</div>
<div class="field__wrap field__wrap__appended">
<div class="field__text">{% setting 'MAIN_HOST' %}/user/</div>
<input name='slug' id="user-slug" class="field__input field__input__appended" type="text"
value="{{ user.slug|default:'' }}" data-current-slug="{{ user.slug|default:'' }}">
</div>
{% for error in form.slug.errors %}
<div class="field__error">{{ error }}</div>
{% endfor %}
</div>
</div>
<!-- child form -->
<div class="user-child-form form__group">
<div class="form__title">Карточка ребёнка</div>
<div class="user-child-form__description">
Вся информация конфиденциальна и не передается третьим лицам. Необходима только для персонализации наград,
поздравлений с Днем Рождения и других персонализированных акций.
</div>
<div class="form__fieldset">
<div class="form__field field{% if form.child_first_name.errors %} error{% endif %}">
<div class="field__label">ИМЯ РЕБЕНКА</div>
<div class="field__wrap">
<input name='child_first_name' id="child-first-name" class="field__input" type="text" placeholder="Имя"
value="{{ user.child_first_name }}">
</div>
{% for error in form.child_first_name.errors %}
<div class="field__error">{{ error }}</div>
{% endfor %}
</div>
<div class="form__field field{% if form.child_last_name.errors %} error{% endif %}">
<div class="field__label">ФАМИЛИЯ РЕБЕНКА</div>
<div class="field__wrap">
<input name='child_last_name' id="child-last-name" class="field__input" type="text" placeholder="Фамилия"
value="{{ user.child_last_name }}">
</div>
{% for error in form.child_last_name.errors %}
<div class="field__error">{{ error }}</div>
{% endfor %}
</div>
</div>
<div class="form__fieldset"> <div class="form__fieldset">
<div class="form__field field{% if form.child_birthday.errors %} error{% endif %}"> <div class="form__field field{% if form.email.errors %} error{% endif %}">
<div class="field__label">ДАТА РОЖДЕНИЯ</div> <div class="field__label">Почта</div>
<div class="field__wrap"> <div class="field__wrap">
<vue-datepicker input-class="field__input" name="child_birthday" language="ru" format="dd/MM/yyyy" <input name='email' class="field__input" type="email" placeholder="Почта" value="{{ user.email }}">
value="{% if user.child_birthday %}{{ user.child_birthday | date:'Y-m-d' }}{% endif %}" placeholder="dd/mm/yyyy"/>
</div> </div>
{% for error in form.child_birthday.errors %} {% for error in form.email.errors %}
<div class="field__error">{{ error }}</div> <div class="field__error">{{ error }}</div>
{% endfor %} {% endfor %}
</div> </div>
<div class="form__field field{% if form.child_gender.errors %} error{% endif %}"> <div class="form__field field{% if form.phone.errors %} error{% endif %}">
<div class="field__label">ПОЛ</div> <div class="field__label">НОМЕР ТЕЛЕФОНА</div>
<div class="field__wrap"> <div class="field__wrap">
<div class="field__select select js-select{% if user.child_gender and user.child_gender != 'n' %} selected{% endif %}"> <input name='phone' class="field__input" type="phone" placeholder="+7 (999) 999-99-99" value="{{ user.phone|default:'' }}">
<div class="select__head js-select-head">
{% if user.child_gender == 'f' %}Ж{% elif user.child_gender == 'm' %}M{% else %}М / Ж{% endif %}
</div>
<div class="select__drop js-select-drop" data-gender-select data-input="#child-gender">
<div class="select__option js-select-option" data-gender-option data-gender="m">
<div class="select__title">М</div>
</div>
<div class="select__option js-select-option" data-gender-option data-gender="f">
<div class="select__title">Ж</div>
</div>
</div>
<input id="child-gender" name='child_gender' class="select__input" type="hidden">
</div>
</div> </div>
{% for error in form.child_gender.errors %} {% for error in form.phone.errors %}
<div class="field__error">{{ error }}</div> <div class="field__error">{{ error }}</div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
<div class="form__field field{% if form.phone.errors %} error{% endif %}"> <div class="form__field field{% if form.slug.errors %} error{% endif %}">
<div class="field__label">НОМЕР ТЕЛЕФОНА РОДИТЕЛЯ</div> <div class="field__label">Ссылка</div>
<div class="field__wrap"> <div class="field__wrap field__wrap__appended">
<input name='phone' class="field__input" type="phone" placeholder="+7 (999) 999-99-99" value="{{ user.phone|default:'' }}"> <div class="field__text">{% setting 'MAIN_HOST' %}/user/</div>
<input name='slug' id="user-slug" class="field__input field__input__appended" type="text"
value="{{ user.slug|default:'' }}" data-current-slug="{{ user.slug|default:'' }}">
</div> </div>
{% for error in form.phone.errors %} {% for error in form.slug.errors %}
<div class="field__error">{{ error }}</div> <div class="field__error">{{ error }}</div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
<!-- end child form -->
<childs :childs="$root.store.data.childs"></childs>
<div class="form__group"> <div class="form__group">
<div class="form__fieldset"> <div class="form__fieldset">
<div class="form__field field{% if form.city.errors %} error{% endif %}"> <div class="form__field field{% if form.city.errors %} error{% endif %}">
@ -393,5 +333,8 @@
{% endblock foot %} {% endblock foot %}
{% block pre_app_js %} {% block pre_app_js %}
<script>
window.LIL_STORE.data.childs = {{ childs|safe }};
</script>
<script type="text/javascript" src="{% static 'profile.js' %}"></script> <script type="text/javascript" src="{% static 'profile.js' %}"></script>
{% endblock pre_app_js %} {% endblock pre_app_js %}

@ -1,6 +1,6 @@
{% extends "templates/lilcity/index.html" %} {% load static %} {% load thumbnail %} {% block content %} {% extends "templates/lilcity/index.html" %} {% load static %} {% load thumbnail %} {% block content %}
{% if not user.child_first_name or not user.child_birthday %} {% if not user.has_child %}
<div class="section"> <div class="section">
<div class="section__center center"> <div class="section__center center">
<div class="fill-profile"> <div class="fill-profile">
@ -142,7 +142,7 @@
{% if paid.exists %} {% if paid.exists %}
{% include "course/course_items.html" with course_items=paid %} {% include "course/course_items.html" with course_items=paid %}
{% endif %} {% endif %}
{% if not is_school_purchased and not paid.exists and not user_gift_certificates.exists %} {% if not is_school_purchased and not is_camp_purchased and not paid.exists and not user_gift_certificates.exists %}
<div class="center center_xs"> <div class="center center_xs">
<div class="done"> <div class="done">
<div class="done__title">Вы пока ничего не приобрели...</div> <div class="done__title">Вы пока ничего не приобрели...</div>

@ -26,9 +26,9 @@ from apps.notification.utils import send_email
from apps.school.models import SchoolSchedule from apps.school.models import SchoolSchedule
from apps.payment.models import AuthorBalance, CoursePayment, SchoolPayment, Payment, UserGiftCertificate, UserBonus, \ from apps.payment.models import AuthorBalance, CoursePayment, SchoolPayment, Payment, UserGiftCertificate, UserBonus, \
DrawingCampPayment DrawingCampPayment
from apps.user.models import AuthorRequest, EmailSubscription, SubscriptionCategory from apps.user.models import AuthorRequest, EmailSubscription, SubscriptionCategory, Child
from .forms import AuthorRequesForm, UserEditForm, WithdrawalForm from .forms import AuthorRequesForm, UserEditForm, WithdrawalForm, ChildForm
User = get_user_model() User = get_user_model()
@ -73,7 +73,7 @@ class ProfileView(TemplateView):
).distinct() ).distinct()
school_payment = SchoolPayment.objects.filter( school_payment = SchoolPayment.objects.filter(
user=self.object, user=self.object,
date_end__gte=now(), date_end__gte=now() - timedelta(7),
status__in=[ status__in=[
Pingback.PINGBACK_TYPE_REGULAR, Pingback.PINGBACK_TYPE_REGULAR,
Pingback.PINGBACK_TYPE_GOODWILL, Pingback.PINGBACK_TYPE_GOODWILL,
@ -93,7 +93,7 @@ class ProfileView(TemplateView):
camp_payment = DrawingCampPayment.objects.filter( camp_payment = DrawingCampPayment.objects.filter(
user=self.object, user=self.object,
date_end__gte=now(), date_end__gte=now() - timedelta(7),
status__in=DrawingCampPayment.PW_PAID_STATUSES, status__in=DrawingCampPayment.PW_PAID_STATUSES,
) )
context['is_camp_purchased'] = camp_payment.exists() context['is_camp_purchased'] = camp_payment.exists()
@ -217,6 +217,26 @@ class ProfileEditView(UpdateView):
context['is_teacher'] = self.object.role == User.TEACHER_ROLE context['is_teacher'] = self.object.role == User.TEACHER_ROLE
return context return context
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
if self.object.childs.all().count():
context['childs'] = [{
'id': c.id,
'first_name': c.first_name,
'last_name': c.last_name,
'gender': c.gender or 'n',
'birthday': c.birthday.strftime('%Y-%m-%d') if c.birthday else '',
} for c in self.object.childs.all()]
else:
context['childs'] = [{
'id': '',
'first_name': '',
'last_name': '',
'gender': 'n',
'birthday': '',
}]
return self.render_to_response(context)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
# it's magic *-*-*-*-* # it's magic *-*-*-*-*
if 'photo' in request.FILES: if 'photo' in request.FILES:
@ -256,15 +276,47 @@ class ProfileEditView(UpdateView):
login(request, request.user) login(request, request.user)
else: else:
messages.error(request, 'Неверный пароль.') messages.error(request, 'Неверный пароль.')
messages.info(request, 'Данные сохранены.')
response = super().post(request, *args, **kwargs) child_ids = request.POST.getlist('child_id')
user = User.objects.get(pk=request.user.id) child_first_names = request.POST.getlist('child_first_name')
# начисляемм бонусы, если заполнил профиль child_last_names = request.POST.getlist('child_last_name')
if user.phone and user.first_name and user.last_name and not user.bonuses.filter( child_genders = request.POST.getlist('child_gender')
is_service=True, action_name=UserBonus.ACTION_FILL_PROFILE).count(): child_birthdays = request.POST.getlist('child_birthday')
UserBonus.objects.create(user=user, amount=UserBonus.AMOUNT_FILL_PROFILE, is_service=True, ziped = zip(child_ids, child_first_names, child_last_names, child_genders, child_birthdays)
action_name=UserBonus.ACTION_FILL_PROFILE) childs = [dict(zip(['id', 'first_name', 'last_name', 'gender', 'birthday'], z)) for z in ziped]
return response childs_saved = []
child_errors = False
for child in childs:
child_instance = None
if child.get('id'):
child_instance = Child.objects.get(pk=child.get('id'))
child['user'] = request.user.id
child['gender'] = child['gender'] or 'n'
child_form = ChildForm(data=child, instance=child_instance)
if child_form.is_valid():
childs_saved.append(child_form.save().id)
elif not(len(childs) == 1 and not child.get('id') and not child.get('first_name')):
child_errors = True
child['errors'] = {f: e[0] for f, e in child_form.errors.items()}
if not child_errors:
request.user.childs.exclude(id__in=childs_saved).delete()
form = self.get_form()
if form.is_valid() and not child_errors:
messages.info(request, 'Данные сохранены.')
response = self.form_valid(form)
# начисляем бонусы, если заполнил профиль
if self.object.phone and self.object.first_name and self.object.last_name and not self.object.bonuses.filter(
is_service=True, action_name=UserBonus.ACTION_FILL_PROFILE).count():
UserBonus.objects.create(user=self.object, amount=UserBonus.AMOUNT_FILL_PROFILE, is_service=True,
action_name=UserBonus.ACTION_FILL_PROFILE)
return response
else:
context = self.get_context_data(**kwargs)
context['childs'] = childs
return self.render_to_response(context)
def get_success_url(self): def get_success_url(self):
return reverse('user-edit-profile') return reverse('user-edit-profile')

@ -1,4 +1,5 @@
{% load static %} {% load static %}
{% load ruplural from plural %}
<div class="anchor" id="about"></div> <div class="anchor" id="about"></div>
<div class="section section_school" style="padding-top: 0;"> <div class="section section_school" style="padding-top: 0;">
<div class="section__center center"> <div class="section__center center">
@ -20,26 +21,26 @@
</div> </div>
<div class="school school_main"> <div class="school school_main">
<div class="school__col"> <div class="school__col">
<div class="school__preview">> 12000</div> <div class="school__preview">> {{ config.NUMBER_OF_STUDENTS }}</div>
<div class="school__title">учеников</div> <div class="school__title">{{ config.NUMBER_OF_STUDENTS|ruplural:'ученик,ученика,учеников' }}</div>
<div class="school__text">прошли обучение в&nbsp;Lil&nbsp;School</div> <div class="school__text">прошли обучение в&nbsp;Lil&nbsp;School</div>
</div> </div>
<div class="school__col"> <div class="school__col">
<div class="school__preview">{{ works_count }}</div> <div class="school__preview">{{ works_count }}</div>
<div class="school__title">работ</div> <div class="school__title">{{ works_count|ruplural:'работа,работы,работ' }}</div>
<div class="school__text"> <div class="school__text">
создано учениками Lil&nbsp;School.<br> создано учениками Lil&nbsp;School.<br>
Большую часть из&nbsp;них легко найти в&nbsp;<a href="{{ config.SERVICE_INSTAGRAM_URL }}">инстаграм</a> Большую часть из&nbsp;них легко найти в&nbsp;<a href="{{ config.SERVICE_INSTAGRAM_URL }}">инстаграм</a>
</div> </div>
</div> </div>
<div class="school__col"> <div class="school__col">
<div class="school__preview">32</div> <div class="school__preview">{{ config.NUMBER_OF_COUNTRIES }}</div>
<div class="school__title">страны</div> <div class="school__title">{{ config.NUMBER_OF_COUNTRIES|ruplural:'страна,страны,стран' }}</div>
<div class="school__text">где живут талантливые ученики Lil&nbsp;School</div> <div class="school__text">где живут талантливые ученики Lil&nbsp;School</div>
</div> </div>
<div class="school__col"> <div class="school__col">
<div class="school__preview">107</div> <div class="school__preview">{{ config.NUMBER_OF_CITIES }}</div>
<div class="school__title">городов</div> <div class="school__title">{{ config.NUMBER_OF_CITIES|ruplural:'город,города,городов' }}</div>
<div class="school__text">со&nbsp;всего мира со&nbsp;счастливыми учениками Lil&nbsp;School</div> <div class="school__text">со&nbsp;всего мира со&nbsp;счастливыми учениками Lil&nbsp;School</div>
</div> </div>
</div> </div>

@ -1,10 +1,10 @@
<div class="section section_video"> <div class="section section_video">
<div class="section__center center center_sm" style="text-align: center;"> <div class="section__center center center_sm" style="text-align: center;">
<div class="title">Пробный урок</div> <div class="title">Пробный урок</div>
<img class="main-video-preview js-video-modal" data-video-url="{{ config.MAIN_PAGE_VIDEO_URL }}" data-trial-lesson="1" <img class="main-video-preview js-video-modal" data-video-url="{{ config.MAIN_PAGE_VIDEO_URL|safe }}" data-trial-lesson="1"
src="{{ config.MAIN_PAGE_VIDEO_PREVIEW_IMG.url }}"/> src="{{ config.MAIN_PAGE_VIDEO_PREVIEW_IMG.url }}"/>
<a href="#" class="btn js-video-modal btn_stroke-black" style="margin: 20px;" <a href="#" class="btn js-video-modal btn_stroke-black" style="margin: 20px;"
data-video-url="{{ config.MAIN_PAGE_VIDEO_URL }}" data-trial-lesson="1">Смотреть бесплатно</a> data-video-url="{{ config.MAIN_PAGE_VIDEO_URL|safe }}" data-trial-lesson="1">Смотреть бесплатно</a>
<div>Много развивающих видео на&nbsp;нашем <a href="{{ config.SERVICE_YOUTUBE_URL }}">YouTube&nbsp;канале</a></div> <div>Много развивающих видео на&nbsp;нашем <a href="{{ config.SERVICE_YOUTUBE_URL|safe }}">YouTube&nbsp;канале</a></div>
</div> </div>
</div> </div>

@ -59,8 +59,7 @@ class AutoAddingUserBonusTestCase(TestCase):
user = UserFactory.create(role=User.USER_ROLE, first_name='', last_name='', phone='') user = UserFactory.create(role=User.USER_ROLE, first_name='', last_name='', phone='')
self.assertEqual(user.bonus, 0, 'При создании есть бонусы') self.assertEqual(user.bonus, 0, 'При создании есть бонусы')
self.client.force_login(user) self.client.force_login(user)
data = {k: user.__dict__[k] for k in ['email', 'slug', 'child_first_name', data = {k: user.__dict__[k] for k in ['email', 'slug', 'city',
'child_last_name', 'child_birthday', 'child_gender', 'city',
'country', 'birthday', 'gender', 'about', 'instagram', 'facebook', 'country', 'birthday', 'gender', 'about', 'instagram', 'facebook',
'twitter', 'pinterest', 'youtube', 'vkontakte', 'site']} 'twitter', 'pinterest', 'youtube', 'vkontakte', 'site']}

@ -0,0 +1,120 @@
<template>
<div class="user-child-form form__group">
<div class="form__title">Карточка ребёнка</div>
<div class="user-child-form__description">
Вся информация конфиденциальна и не передается третьим лицам. Необходима только для персонализации наград,
поздравлений с Днем Рождения и других персонализированных акций.
</div>
<div class="tabs">
<div class="user-child-form__tabs tabs__nav">
<a class="user-child-form__tab tabs__btn" v-for="(child, index) in childs" :key="index" href="#"
v-bind:class="{active: selectedIndex == index}" @click.prevent="select(index)">
{{ child.first_name ? (child.first_name + '&nbsp;' + child.last_name) : ('Ребенок ' + (index || '')) }}</a>
<a href="#" class="tabs__btn" @click.prevent="add()">Добавить ребенка</a>
</div>
</div>
<div class="tabs__container">
<div v-for="(child, index) in childs" :key="index" class="tabs__item js-tabs-item"
v-bind:style="{display: selectedIndex == index ? 'block' : ''}">
<div class="form__fieldset">
<input type="hidden" name="child_id" :value="child.id" />
<div class="form__field field" v-bind:class="{error: child.errors && child.errors.gender}">
<div class="field__label">ИМЯ РЕБЕНКА</div>
<div class="field__wrap">
<input name='child_first_name' class="field__input" type="text" placeholder="Имя"
v-model="child.first_name">
</div>
<div v-if="child.errors && child.errors.first_name" class="field__error">{{ child.errors.first_name }}</div>
</div>
<div class="form__field field" v-bind:class="{error: child.errors && child.errors.last_name}">
<div class="field__label">ФАМИЛИЯ РЕБЕНКА</div>
<div class="field__wrap">
<input name='child_last_name' id="child-last-name" class="field__input" type="text" placeholder="Фамилия"
v-model="child.last_name">
</div>
<div v-if="child.errors && child.errors.last_name" class="field__error">{{ child.errors.last_name }}</div>
</div>
</div>
<div class="form__fieldset">
<div class="form__field field" v-bind:class="{error: child.errors && child.errors.birthday}">
<div class="field__label">ДАТА РОЖДЕНИЯ</div>
<div class="field__wrap">
<vue-datepicker input-class="field__input" name="child_birthday" language="ru" format="dd/MM/yyyy"
v-model="child.birthday" placeholder="dd/mm/yyyy"/>
</div>
<div v-if="child.errors && child.errors.birthday" class="field__error">{{ child.errors.birthday }}</div>
</div>
<div class="form__field field" v-bind:class="{error: child.errors && child.errors.gender}">
<div class="field__label">ПОЛ</div>
<div class="field__wrap">
<lil-select :value.sync="child.gender" :options="genders" value-key="0" title-key="1"/>
<input name='child_gender' type="hidden" v-model="child.gender">
</div>
<div v-if="child.errors && child.errors.gender" class="field__error">{{ child.errors.gender }}</div>
</div>
</div>
<a href="#" @click.prevent="remove(index)">Удалить</a>
</div>
</div>
</div>
</template>
<script>
import DatePicker from 'vuejs-datepicker';
import LilSelect from './inputs/LilSelect';
export default {
name: "childs",
props: ['childs'],
data() {
return {
selectedIndex: 0,
selectedChild: null,
genders: [
['n', 'М / Ж'],
['f', 'Ж'],
['m', 'М'],
]
}
},
methods: {
add(){
this.childs.push({
first_name: '',
last_name: '',
birthday: '',
gender: 'n',
errors: {},
});
this.select(this.childs.length - 1);
},
remove(index){
if(! confirm('Вы действительно хотите удалить данные о ребенке?')){
return;
}
this.childs.splice(index, 1);
if(! this.childs.length){
this.add();
}
else if(this.selectedIndex == index){
this.select(0);
}
},
select(index){
this.selectedIndex = index;
this.selectedChild = this.childs[index];
}
},
mounted(){
for(let i in this.childs){
if(this.childs[i].errors){
this.select(i);
}
}
},
components: {
'vue-datepicker': DatePicker,
LilSelect,
}
}
</script>

@ -72,11 +72,11 @@
}, },
onRemoveImage(index) { onRemoveImage(index) {
let images = this.images; let images = this.images;
let id = images[index].image_id; const id = images[index].image_id;
api.removeImage(id, this.accessToken) api.removeImage(id, this.accessToken)
.then(response => { .then(response => {
images.splice(index, 1); images = images.filter(image => image.image_id != id);
this.$emit('update:images', images); this.$emit('update:images', images);
}); });
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

@ -19,7 +19,6 @@ import "./modules/mixpanel";
import "../sass/app.sass"; import "../sass/app.sass";
import $ from 'jquery';
import Vue from 'vue'; import Vue from 'vue';
import Vuelidate from 'vuelidate'; import Vuelidate from 'vuelidate';
import VueAutosize from '../components/directives/autosize' import VueAutosize from '../components/directives/autosize'

@ -75,6 +75,13 @@ export const api = {
} }
}); });
}, },
removeGalleryImage: (galleryImageId, accessToken) => {
return api.delete(`/api/v1/gallery-images/${galleryImageId}/`, {
headers: {
'Authorization': `Token ${accessToken}`,
}
});
},
loadCourse: (courseId, accessToken) => { loadCourse: (courseId, accessToken) => {
return api.get(`/api/v1/courses/${courseId}/`, { return api.get(`/api/v1/courses/${courseId}/`, {
headers: { headers: {

@ -0,0 +1,62 @@
import $ from 'jquery';
import {loadScript} from '../utils';
function getVideoPopup($videoEl){
const $container = $videoEl.parent();
let $popup = $container.children('.video-ended-popup');
if(! $popup[0]){
$popup = $('<div class="video-ended-popup">' +
'<div class="loading-loader"></div>' +
'<div class="video-ended-popup__like-btn"></div></div>');
$container.append($popup);
}
return $popup;
}
$(document).ready(function () {
$('.js-video').each(function (){
const $iframe = $(this);
if($iframe.data('isYoutube')){
}
if($iframe.data('isVimeo')){
loadScript('https://player.vimeo.com/api/player.js').then(() => {
const player = new Vimeo.Player(this);
player.on('pause', function(data) {
if(data.percent == 1){
const $popup = getVideoPopup($iframe);
$popup.show().animate({opacity: 1}, 200);
if (document.fullscreenElement){
document.exitFullscreen();
}
const courseId = $iframe.data('courseId');
if(courseId && window.LIL_STORE.user.id && !window.LIL_STORE.data.courseLiked){
$popup.addClass('video-ended-popup_loading');
$.get('/api/v1/likes/course-liked/', {
course_id: courseId,
user_id: window.LIL_STORE.user.id
}).then(response => {
if(! response.is_liked){
$popup.addClass('video-ended-popup_like');
$popup.find('.video-ended-popup__like-btn').click(() => {
$.post(`/course/${courseId}/like`).then(response => {
$popup.removeClass('video-ended-popup_like');
window.LIL_STORE.data.courseLiked = response.success;
});
});
}
$popup.removeClass('video-ended-popup_loading');
});
}
}
});
player.on('play', function() {
const $p = getVideoPopup($iframe);
if($p.is(':visible')){
$p.animate({opacity: 0}, 800).then(() => {$p.hide().attr('class', 'video-ended-popup');});
}
});
});
}
});
});

@ -91,9 +91,11 @@ $(document).ready(function () {
if (data.is_liked) { if (data.is_liked) {
likedCourseElement.addClass('active'); likedCourseElement.addClass('active');
likedCourseElement.attr('data-liked', '1'); likedCourseElement.attr('data-liked', '1');
window.LIL_STORE.data.courseLiked = true;
} else { } else {
likedCourseElement.removeClass('active'); likedCourseElement.removeClass('active');
likedCourseElement.attr('data-liked', '0'); likedCourseElement.attr('data-liked', '0');
window.LIL_STORE.data.courseLiked = false;
} }
} }
}) })

@ -81,7 +81,7 @@ $(document).ready(function () {
channel = 'youtube'; channel = 'youtube';
} }
if(url.indexOf('youtube.com') > -1 && url.indexOf('watch') > -1){ if(url.indexOf('youtube.com') > -1 && url.indexOf('watch') > -1){
const m = /[?&]v=([a-zA-Z]+)(&.)?/.exec(url); const m = /[?&]v=([^&]+)(&.+)?$/.exec(url);
channel = 'youtube'; channel = 'youtube';
videoId = m && m[1]; videoId = m && m[1];
} }

@ -0,0 +1 @@
import "../modules/content";

@ -0,0 +1 @@
import "../modules/content";

@ -2,6 +2,10 @@ import $ from 'jquery';
import slugify from 'slugify'; import slugify from 'slugify';
import ClipboardJS from 'clipboard'; import ClipboardJS from 'clipboard';
import {showNotification} from '../modules/notification'; import {showNotification} from '../modules/notification';
import Childs from '../../components/Childs.vue';
window.LIL_STORE.components['childs'] = Childs;
$(document).ready(function () { $(document).ready(function () {
if(window.LIL_STORE.urlIs('userBonuses')){ if(window.LIL_STORE.urlIs('userBonuses')){
@ -25,7 +29,7 @@ $(document).ready(function () {
$(`[data-gender=${currentGender}]`).addClass('active'); $(`[data-gender=${currentGender}]`).addClass('active');
$input.val(currentGender) $input.val(currentGender)
}); });
}) });
$('#user-photo-upload').change(file => { $('#user-photo-upload').change(file => {

@ -1,14 +1,21 @@
export const rupluralize = (value, args, addValue=true) => { export const rupluralize = (value, args, addValue = true) => {
let digit = Math.trunc(value) + ''; let digit = Math.trunc(value) + '';
digit = digit[digit.length - 1]; digit = digit[digit.length - 1];
return (addValue ? value + ' ' : '') + return (addValue ? value + ' ' : '') +
args[(+value > 10 && +value < 20) args[(+value > 10 && +value < 20)
? 2 ? 2
: (digit == '1' ? 0 : ('234'.search(digit) > -1 ? 1 : 2))]; : (digit == '1' ? 0 : ('234'.search(digit) > -1 ? 1 : 2))];
} };
export const loadScript = (url, onload) => { export const loadScript = (url, onload) => {
const tag = document.createElement('script'); return new Promise(resolve => {
tag.url = url; const script = document.createElement('script');
document.getElementsByTagName('body'); script.async = true;
} document.body.appendChild(script);
script.onload = function () {
onload && onload.apply(this, arguments);
resolve.apply(this, arguments);
};
script.src = url;
});
};

@ -4130,11 +4130,6 @@ a.grey-link
letter-spacing: 1px letter-spacing: 1px
+m +m
margin-right: 0 margin-right: 0
&__item a.timing__btn
opacity: 0;
transition: 0.5s ease;
&__item:hover a.timing__btn
opacity: 1;
&__pic &__pic
display: block display: block
width: 100% width: 100%
@ -4358,11 +4353,13 @@ a
&.pic &.pic
position: relative position: relative
width: 100% width: 100%
&__video
position: relative
&__video iframe &__video iframe
height: 360px; height: 360px
width: 640px; width: 640px
max-width: 100%; max-width: 100%
max-height: 60%; max-height: 60%
@media only screen and (max-width: 639px) @media only screen and (max-width: 639px)
.content-block__video iframe .content-block__video iframe
@ -4433,6 +4430,8 @@ a
margin: 0 auto margin: 0 auto
display: flex display: flex
padding: 0 40px padding: 0 40px
+t
width: 100%
+m +m
width: 100% width: 100%
padding: 0 10px padding: 0 10px
@ -4706,12 +4705,24 @@ a
border-radius: 20px border-radius: 20px
box-shadow: 0 10px 24px 0 rgba(0, 0, 0, 0.05) box-shadow: 0 10px 24px 0 rgba(0, 0, 0, 0.05)
+t
margin: 30px -80px 30px -60px
+m
background: white
margin: 30px -80px 30px -60px
padding: 30px 80px 40px 60px
&__description &__description
margin-bottom: 40px margin-bottom: 40px
margin-top: -20px margin-top: -20px
color: #333333 color: #333333
font-size: 12px font-size: 12px
&__tabs
justify-content: left
overflow-x: auto
overflow-y: hidden
.bonuses-table .bonuses-table
margin-left: -120px margin-left: -120px
@ -4771,3 +4782,64 @@ a
&__text &__text
margin: 40px 0 margin: 40px 0
.video-ended-popup
display: none
position: absolute
top: 90px
height: 200px
background: url(../img/video-ended-popup-ref.jpg) no-repeat center white
left: 50%
width: 600px
margin-left: -300px
border-radius: 5px
padding: 30px
box-shadow: 0 2px 20px rgba(0,0,0,0.10)
opacity: 0
+m
width: 100%
left: 0
top: 0
height: 153px
background-position: center bottom
background-size: auto 130px
border-radius: 0
background-color: #151a1e
margin-left: 0
& .loading-loader
display: none
&_like
background-image: url(../img/video-ended-popup-like.jpg)
&_black
background: url(../img/video-ended-popup-logo.png) no-repeat center #141a1d
&_loading
background: #141a1d
&_loading .loading-loader
display: block
&__like-btn
background: url(../img/heart.png) no-repeat center
position: absolute
top: 90px
display: none
width: 91px
height: 52px
cursor: pointer
left: 50%
margin-left: -45px
+m
background-size: contain
width: 54px
margin-left: -27px
bottom: 27px
top: auto
&_like &__like-btn
display: block

@ -9,10 +9,12 @@ const SpriteLoaderPlugin = require('svg-sprite-loader/plugin');
module.exports = { module.exports = {
entry: { entry: {
app: "./src/js/app.js", app: "./src/js/app.js",
course: "./src/js/pages/course.js",
courseEdit: "./src/js/pages/course-edit.js", courseEdit: "./src/js/pages/course-edit.js",
contest: "./src/js/pages/contest.js", contest: "./src/js/pages/contest.js",
contestEdit: "./src/js/pages/contest-edit.js", contestEdit: "./src/js/pages/contest-edit.js",
profile: "./src/js/pages/profile.js", profile: "./src/js/pages/profile.js",
liveLesson: "./src/js/pages/live-lesson.js",
userGalleryEdit: "./src/js/pages/user-gallery-edit.js", userGalleryEdit: "./src/js/pages/user-gallery-edit.js",
mixpanel: "./src/js/third_party/mixpanel-2-latest.js", mixpanel: "./src/js/third_party/mixpanel-2-latest.js",
sprite: glob('./src/icons/*.svg'), sprite: glob('./src/icons/*.svg'),

Loading…
Cancel
Save