validation fixes, etc...

remotes/origin/hasaccess
Sanasol 8 years ago
parent 8cd3c102f7
commit e1d33dbfee
  1. 338
      web/src/components/CourseRedactor.vue
  2. 4
      web/src/js/modules/api.js
  3. 9
      web/src/js/modules/notification.js
  4. 36
      web/src/sass/_common.sass

@ -1,6 +1,6 @@
<template> <template>
<div id="lilcity__course-redactor" v-on:course_publish="onCoursePublish" v-on:course_preview="onCoursePreview"> <div id="lilcity__course-redactor" v-on:course_publish="onCoursePublish" v-on:course_preview="onCoursePreview">
<div v-if="!courseLoading"> <div v-if="!courseLoading && !mounting">
<form v-if="viewSection !== 'lessons-edit'" @submit.prevent="onSubmit"> <form v-if="viewSection !== 'lessons-edit'" @submit.prevent="onSubmit">
<div class="info"> <div class="info">
<div class="info__section" :style="coverBackgroundStyle"> <div class="info__section" :style="coverBackgroundStyle">
@ -60,15 +60,10 @@
<div class="info__field field field_info" <div class="info__field field field_info"
v-bind:class="{ error: ($v.course.duration.$dirty || showErrors) && $v.course.duration.$invalid }"> v-bind:class="{ error: ($v.course.duration.$dirty || showErrors) && $v.course.duration.$invalid }">
<div class="field__label field__label_gray">ПРОДОЛЖИТЕЛЬНОСТЬ</div> <div class="field__label field__label_gray">ПРОДОЛЖИТЕЛЬНОСТЬ</div>
<div class="field__wrap"> <div class="field__wrap field__wrap__appended">
<input type="text" class="field__input" v-model.number="course.duration" <input type="text" class="field__input field__input__appended" v-model.number="course.duration"
@input="$v.course.duration.$touch()"> @input="$v.course.duration.$touch()">
</div> <button disabled class="field__append">{{pluralize(course.duration, ['день', 'дня', 'дней'])}}</button>
</div>
<div class="info__field field field_info">
<div class="field__label field__label_gray">СТОИМОСТЬ</div>
<div class="field__wrap">
<input type="text" class="field__input" v-model.number="displayPrice" :disabled="!course.is_paid">
</div> </div>
</div> </div>
</div> </div>
@ -85,14 +80,16 @@
<div class="field__wrap field__wrap--additional">{{ courseFullUrl }}</div> <div class="field__wrap field__wrap--additional">{{ courseFullUrl }}</div>
</div> </div>
<div v-if="live" class="info__field field"> <div v-if="live" class="info__field field"
v-bind:class="{ error: ($v.course.stream.$dirty || showErrors) && $v.course.stream.$invalid }">
<div class="field__label field__label_gray">ССЫЛКА НА VIMEO</div> <div class="field__label field__label_gray">ССЫЛКА НА VIMEO</div>
<div class="field__wrap"> <div class="field__wrap">
<input type="text" class="field__input" v-model="course.stream"> <input type="text" class="field__input" v-model="course.stream">
</div> </div>
</div> </div>
<div v-if="live" class="info__field field"> <div v-if="live" class="info__field field"
v-bind:class="{ error: ($v.course.date.$dirty || showErrors) && $v.course.date.$invalid }">
<div class="field__label">ДАТА</div> <div class="field__label">ДАТА</div>
<div class="field__wrap"> <div class="field__wrap">
<lil-select :value.sync="course.date" :options="scheduleOptions" placeholder="Выберите дату"/> <lil-select :value.sync="course.date" :options="scheduleOptions" placeholder="Выберите дату"/>
@ -102,20 +99,28 @@
<div v-if="!live" class="info__field field"> <div v-if="!live" class="info__field field">
<div class="field__label field__label_gray">ДОСТУП</div> <div class="field__label field__label_gray">ДОСТУП</div>
<div class="field__wrap"> <div class="field__wrap">
<label class="field__switch switch switch_lg switch_circle">
<input type="radio" :value="true" class="switch__input" v-model="course.is_paid">
<span class="switch__content">Платный</span>
</label>
<label class="field__switch switch switch_lg switch_circle"> <label class="field__switch switch switch_lg switch_circle">
<input type="radio" :value="false" class="switch__input" v-model="course.is_paid"> <input type="radio" :value="false" class="switch__input" v-model="course.is_paid">
<span class="switch__content">Бесплатный</span> <span class="switch__content">Бесплатный</span>
</label> </label>
<label class="field__switch switch switch_lg switch_circle">
<input type="radio" :value="true" class="switch__input" v-model="course.is_paid">
<span class="switch__content">Платный</span>
</label>
</div> </div>
</div> </div>
<label v-if="!live" class="info__switch switch switch_lg">
<input type="checkbox" class="switch__input" v-model="course.is_featured"> <div v-if="course.is_paid" class="info__field field">
<span class="switch__content">Выделить</span> <div class="field__label field__label_gray">СТОИМОСТЬ</div>
</label> <div class="field__wrap field__wrap__appended field__wrap__100px">
<input type="text" class="field__input field__input__appended" v-model.number="displayPrice">
<button disabled class="field__append">руб.</button>
</div>
</div>
<!--<label v-if="!live" class="info__switch switch switch_lg">-->
<!--<input type="checkbox" class="switch__input" v-model="course.is_featured">-->
<!--<span class="switch__content">Выделить</span>-->
<!--</label>-->
</div> </div>
<div v-if="!live" class="info__fieldset"> <div v-if="!live" class="info__fieldset">
<div class="info__field field"> <div class="info__field field">
@ -250,7 +255,7 @@
<div v-else> <div v-else>
<div class="section"> <div class="section">
<div class="section__center center"> <div class="section__center center">
Загрузка... <h1>Загрузка...</h1>
</div> </div>
</div> </div>
</div> </div>
@ -271,12 +276,13 @@
import {api} from "../js/modules/api"; import {api} from "../js/modules/api";
import BlockAdd from "./blocks/BlockAdd"; import BlockAdd from "./blocks/BlockAdd";
import $ from 'jquery'; import $ from 'jquery';
import {required, minValue, numeric } from 'vuelidate/lib/validators' import {required, minValue, numeric, url } from 'vuelidate/lib/validators'
import slugify from 'slugify'; import slugify from 'slugify';
import Draggable from 'vuedraggable'; import Draggable from 'vuedraggable';
import {showNotification} from "../js/modules/notification"; import {showNotification} from "../js/modules/notification";
import createHistory from "history/createBrowserHistory"; import createHistory from "history/createBrowserHistory";
import moment from 'moment' import moment from 'moment'
import _ from 'lodash'
const history = createHistory(); const history = createHistory();
@ -285,6 +291,7 @@
props: ["authorName", "authorPicture", "accessToken", "courseId", "live"], props: ["authorName", "authorPicture", "accessToken", "courseId", "live"],
data() { data() {
return { return {
mounting: false,
viewSection: 'course', viewSection: 'course',
me: null, me: null,
users: null, users: null,
@ -373,6 +380,14 @@
'Суббота', 'Суббота',
'Воскресенье', 'Воскресенье',
], ],
fields: {
title: "Заголовок",
short_description: "Краткое описание",
stream: "Ссылка на Vimeo",
date: "Дата",
duration: "Продолжительность",
category: "Категория",
},
showErrors: false, showErrors: false,
savingTimeout: null, savingTimeout: null,
savingDebounceTimeout: null, savingDebounceTimeout: null,
@ -381,25 +396,47 @@
courseSyncHook: false, // Если true, то watch не будет отправлять курс на обновление courseSyncHook: false, // Если true, то watch не будет отправлять курс на обновление
} }
}, },
validations: { validations() {
course: { if(this.live) {
title: { return {
required course: {
}, title: {
short_description: { required
required },
}, short_description: {
duration: { required
required, },
numeric, stream: {
minValue: minValue(1) required,
}, // url
category: { },
required, date: {
numeric, required
minValue: minValue(1) },
} },
}, };
} else {
return {
course: {
title: {
required
},
short_description: {
required
},
duration: {
required,
numeric,
minValue: minValue(1)
},
category: {
required,
numeric,
minValue: minValue(1)
}
},
};
}
}, },
methods: { methods: {
onCoverImageSelected(event) { onCoverImageSelected(event) {
@ -489,6 +526,7 @@
window.scrollTo(0, 0); window.scrollTo(0, 0);
}, },
onSubmit() { onSubmit() {
console.log('submit');
this.courseSaving = true; this.courseSaving = true;
api.saveCourse(this.course, this.accessToken) api.saveCourse(this.course, this.accessToken)
.then((response) => { .then((response) => {
@ -534,14 +572,17 @@
}); });
}, },
loadCourseDraft() { loadCourseDraft() {
console.log('loadCourseDraft');
if(this.live) { return; }
this.courseLoading = true; this.courseLoading = true;
let response = api.getCourseDraft(this.accessToken); let response = api.getCourseDraft(this.accessToken);
response response
.then((response) => { .then((response) => {
this.courseLoading = false;
this.course = api.convertCourseJson(response.data); this.course = api.convertCourseJson(response.data);
this.course.live = this.live; this.course.live = this.live;
this.courseLoading = false;
this.lessons = response.data.lessons.map((lessonJson) => { this.lessons = response.data.lessons.map((lessonJson) => {
return api.convertLessonJson(lessonJson); return api.convertLessonJson(lessonJson);
}); });
@ -554,6 +595,7 @@
return response; return response;
}, },
loadCourse() { loadCourse() {
console.log('loadCourse');
this.courseLoading = true; this.courseLoading = true;
let request = null; let request = null;
if(this.live) { if(this.live) {
@ -563,9 +605,18 @@
} }
request request
.then((response) => { .then((response) => {
this.courseLoading = false;
this.course = api.convertCourseJson(response.data); this.course = api.convertCourseJson(response.data);
this.course.live = this.live; this.course.live = this.live;
if (this.course.date) {
this.course.date = _.find(this.scheduleOptions, (item) => {
return item.value == this.course.date;
})
}
this.$nextTick(() => {
this.courseLoading = false;
});
this.lessons = response.data.lessons.map((lessonJson) => { this.lessons = response.data.lessons.map((lessonJson) => {
return api.convertLessonJson(lessonJson); return api.convertLessonJson(lessonJson);
}); });
@ -581,54 +632,84 @@
}, },
onCoursePublish() { onCoursePublish() {
if(this.validate()) {
const publishButton = $('#course-redactor__publish-button');
publishButton.attr('disabled', 'disabled');
api.publishCourse(this.course.id, this.accessToken)
.then((response) => {
if(this.live) {
window.location = '/school/lessons';
} else {
window.location = '/course/on-moderation';
}
})
.catch(() => {
publishButton.removeAttr('disabled');
});
}
},
validate(silent) {
console.log('valadte', arguments);
this.showErrors = true; this.showErrors = true;
if (this.$v.$invalid) { if (this.$v.$invalid) {
showNotification("error", "Заполните все необходимые поля"); if(!silent) {
return; for(let i in this.$v.course) {
if(this.$v.course[i].$invalid) {
showNotification("error", "Ошибка валидации поля "+this.fields[i]);
}
}
}
// showNotification("error", "Заполните все необходимые поля");
return false;
} }
const publishButton = $('#course-redactor__publish-button');
publishButton.attr('disabled', 'disabled'); return true;
api.publishCourse(this.course.id, this.accessToken)
.then((response) => {
window.location = '/course/on-moderation';
})
.catch(() => {
publishButton.removeAttr('disabled');
});
}, },
onCoursePreview() { onCoursePreview() {
let url; if(this.course.id) {
if (!this.course.url) { let url;
url = `/course/${this.course.id}`; if(this.live) {
} else { url = `/school/lessons/${this.course.id}`;
} else {
if (this.course.url) {
url = `/course/${this.course.url}`;
}
url = `/course/${this.course.id}`;
}
let newTab = window.open(url, '_blank');
newTab.focus();
} else {
showNotification("error", "Пока еще нечего посмотреть, давайте что-нибудь напишем");
} }
url = `/course/${this.course.url}`;
let newTab = window.open(url, '_blank');
newTab.focus();
}, },
saveCourseDraft: function (newValue, oldValue) { saveCourseDraft: function (newValue, oldValue) {
// if (!oldValue.id) { // if (!oldValue.id) {
// return; // return;
// } // }
if(this.live) {
if(!this.course.date || this.course.short_description == '' || this.course.title == '') { // if(this.live) {
console.log('live valiedation error'); // if(!this.course.date || this.course.short_description == '' || this.course.title == '') {
return; // console.log('live valiedation error');
} // return;
} else { // }
if(this.course.short_description == '' || this.course.title == '') { // } else {
console.log('course validation error'); // if(this.course.short_description == '' || this.course.title == '') {
return; // console.log('course validation error');
} // return;
} // }
// }
if (this.savingDebounceTimeout) { if (this.savingDebounceTimeout) {
clearTimeout(this.savingDebounceTimeout); clearTimeout(this.savingDebounceTimeout);
} }
this.courseSyncHook = false; this.courseSyncHook = false;
this.savingDebounceTimeout = setTimeout(() => { this.savingDebounceTimeout = setTimeout(() => {
if(!this.validate(true)) {
return;
}
this.courseSaving = true; this.courseSaving = true;
clearTimeout(this.savingTimeout); clearTimeout(this.savingTimeout);
document.getElementById('course-redactor__saving-status').innerText = 'СОХРАНЕНИЕ...'; document.getElementById('course-redactor__saving-status').innerText = 'СОХРАНЕНИЕ...';
@ -659,10 +740,20 @@
if (!contentElement.data.id) { if (!contentElement.data.id) {
this.$set(this.course.content[index].data, 'id', remoteUUIDMapper[contentElement.uuid]) this.$set(this.course.content[index].data, 'id', remoteUUIDMapper[contentElement.uuid])
} }
}) });
if (courseData.url) { if (courseData.url) {
this.slugChanged = true; this.slugChanged = true;
} }
if(courseData.id) {
this.course.id = courseData.id;
}
if(courseData.date) {
this.course.date = _.find(this.scheduleOptions, function(item){
return item.value == courseData.date;
});
}
this.$nextTick(() => { this.$nextTick(() => {
this.courseSyncHook = false; this.courseSyncHook = false;
}); });
@ -675,7 +766,15 @@
this.savingTimeout = setTimeout(() => { this.savingTimeout = setTimeout(() => {
document.getElementById('course-redactor__saving-status').innerText = ''; document.getElementById('course-redactor__saving-status').innerText = '';
}, 2000); }, 2000);
alert('Произошло что-то страшное: '+err.toString()); // alert('Произошло что-то страшное: '+err.toString());
console.log(err.response.data);
if(err.response.data) {
for(let i in err.response.data) {
showNotification("error", this.fields[i]+": "+err.response.data[i].join(', '));
}
} else {
showNotification("error", "Ошибка "+err.toString());
}
}); });
}, 500); }, 500);
}, },
@ -694,16 +793,24 @@
this.currentLesson = this.lessons.find((i)=>{return i.id === lessonId}); this.currentLesson = this.lessons.find((i)=>{return i.id === lessonId});
this.viewSection = 'lessons-edit'; this.viewSection = 'lessons-edit';
} }
},
pluralize(count, words) {
var cases = [2, 0, 1, 1, 1, 2];
return words[ (count % 100 > 4 && count % 100 < 20) ? 2 : cases[ Math.min(count % 10, 5)] ];
} }
}, },
mounted() { mounted() {
this.mounting = true;
moment.locale('ru'); moment.locale('ru');
this.course.live = this.live; this.course.live = this.live;
// Listen for changes to the current location. // Listen for changes to the current location.
this.unlisten = history.listen(this.updateViewSection); this.unlisten = history.listen(this.updateViewSection);
api.getCategories(this.accessToken) let promises = [];
.then((response) => {
let cats = api.getCategories(this.accessToken);
promises.push(cats);
cats.then((response) => {
if (response.data) { if (response.data) {
this.categoryOptions = response.data.results.map((category) => { this.categoryOptions = response.data.results.map((category) => {
return { return {
@ -715,25 +822,30 @@
this.updateCategory(); this.updateCategory();
}); });
api.getSchedule(this.accessToken) if(this.live) {
.then((response) => { let schedule = api.getSchedule(this.accessToken);
if (response.data) { promises.push(schedule);
this.scheduleOptions = response.data.results.map((schedule) => {
var now = new Date();
now.setDate(now.getDate() + (schedule.weekday+(7-now.getDay())) % 7);
return { schedule.then((response) => {
title: `${schedule.title} (${this.weekdays[schedule.weekday]}, ${moment(now).format("D MMM")})`, if (response.data) {
value: moment(now).format('YYYY-MM-DD') this.scheduleOptions = response.data.results.map((schedule) => {
} var now = new Date();
}); now.setDate(now.getDate() + (schedule.weekday + (7 - now.getDay())) % 7);
}
this.updateCategory(); return {
}); title: `${schedule.title} (${this.weekdays[schedule.weekday]}, ${moment(now).format("D MMM")})`,
value: moment(now).format('YYYY-MM-DD')
}
});
}
this.updateCategory();
});
}
let user = api.getCurrentUser(this.accessToken);
promises.push(user);
api.getCurrentUser(this.accessToken) user.then((response) => {
.then((response) => {
if (response.data) { if (response.data) {
this.me = response.data; this.me = response.data;
@ -753,15 +865,34 @@
} }
}); });
if (this.courseId) { // if (this.courseId) {
this.loadCourse().then(()=>{this.updateViewSection(window.location, 'load')}).catch(()=>{ // this.loadCourse().then(()=>{this.updateViewSection(window.location, 'load')}).catch(()=>{
this.updateViewSection(window.location, 'load err') // this.updateViewSection(window.location, 'load err')
}) // })
} else { // } else {
this.loadCourseDraft().then(()=>{this.updateViewSection(window.location, 'load draft')}).catch(()=>{ // this.loadCourseDraft().then(()=>{this.updateViewSection(window.location, 'load draft')}).catch(()=>{
this.updateViewSection(window.location, 'load draft err') // this.updateViewSection(window.location, 'load draft err')
}); // });
} // }
console.log('wait promises');
Promise.all(promises.map(p => p.catch(e => e))).then(()=>{
console.log('promises end');
this.mounting = false;
let load;
if (this.courseId) {
load = this.loadCourse()
} else {
load = this.loadCourseDraft();
}
load.then(()=>{
this.updateViewSection(window.location, 'load '+this.courseId)
})
.catch(()=>{
this.updateViewSection(window.location, 'load err '+this.courseId)
})
});
console.log('mounted end'); console.log('mounted end');
// this.updateViewSection(window.location); // this.updateViewSection(window.location);
@ -826,14 +957,17 @@
watch: { watch: {
'course': { 'course': {
handler: function (newValue, oldValue) { handler: function (newValue, oldValue) {
console.log('watch', newValue, oldValue); console.log('watch', JSON.stringify(newValue), JSON.stringify(oldValue));
// Если курс загрузился и есть ID - делаем кнопки превью и публикации активными // Если курс загрузился и есть ID - делаем кнопки превью и публикации активными
console.log('newValue.id', newValue.id);
if (newValue.id) { if (newValue.id) {
console.log('newValue.id disabled remove', newValue.id);
$('#course-redactor__preview-button').removeAttr('disabled'); $('#course-redactor__preview-button').removeAttr('disabled');
$('#course-redactor__publish-button').removeAttr('disabled'); $('#course-redactor__publish-button').removeAttr('disabled');
} }
console.log('courseSyncHook', this.courseSyncHook); console.log('courseSyncHook', this.courseSyncHook);
if (this.courseSyncHook) { if (this.courseSyncHook || this.courseLoading) {
console.log('abort save draft', this.courseSyncHook, this.courseLoading);
return; return;
} }
this.saveCourseDraft(newValue, oldValue); this.saveCourseDraft(newValue, oldValue);

@ -257,7 +257,7 @@ export const api = {
}, },
convertCourseJson: (courseJSON) => { convertCourseJson: (courseJSON) => {
let isDeferred = false; let isDeferred = false;
let deferredDate = ''; let deferredDate = false;
let deferredTime = ''; let deferredTime = '';
if (courseJSON.deferred_start_at) { if (courseJSON.deferred_start_at) {
let deferredDateTime = moment(courseJSON.deferred_start_at); let deferredDateTime = moment(courseJSON.deferred_start_at);
@ -275,7 +275,7 @@ export const api = {
price: parseFloat(courseJSON.price), price: parseFloat(courseJSON.price),
is_paid: parseFloat(courseJSON.price) > 0, is_paid: parseFloat(courseJSON.price) > 0,
is_deferred: isDeferred, is_deferred: isDeferred,
date: deferredDate, date: deferredDate || courseJSON.date,
time: deferredTime ? {title: deferredTime, value: deferredTime} : null, time: deferredTime ? {title: deferredTime, value: deferredTime} : null,
duration: courseJSON.duration, duration: courseJSON.duration,
is_featured: courseJSON.is_featured, is_featured: courseJSON.is_featured,

@ -3,8 +3,17 @@ import '../../sass/components/notification.scss';
export function showNotification(style, text) { export function showNotification(style, text) {
let htmlNode = document.createElement('div'); let htmlNode = document.createElement('div');
let htmlElement = $(htmlNode).addClass('notification').addClass(`notification--${style}`).text(text).appendTo($('body')); let htmlElement = $(htmlNode).addClass('notification').addClass(`notification--${style}`).text(text).appendTo($('body'));
if($('body').find('.notification').length) {
var outerHeight = 0;
$('body').find('.notification').each(function() {
outerHeight += $(this).outerHeight()+10;
});
htmlElement.css('top', outerHeight+'px');
}
setTimeout(() => { setTimeout(() => {
htmlElement.fadeOut(400, () => { htmlElement.fadeOut(400, () => {
htmlElement.remove(); htmlElement.remove();

@ -2060,14 +2060,14 @@ a.grey-link
&.active &.active
z-index: 99 z-index: 99
&.active &__head &.active &__head
border-color: $cl border-color: #686868
&:after &:after
transform: rotate(180deg) transform: rotate(180deg)
&.active &__drop &.active &__drop
opacity: 1 opacity: 1
visibility: visible visibility: visible
&.selected &__head &.selected &__head
color: $cl color: #686868
.error.info__field--light .error.info__field--light
.select .select
@ -2078,15 +2078,15 @@ a.grey-link
.info__field--light .info__field--light
.select .select
.select__head .select__head
color: #fff color: #525252
font-size: 15px font-size: 15px
&:after &:after
border-color: #fff transparent transparent transparent border-color: #525252 transparent transparent transparent
&.selected &.selected
.select__head .select__head
color: #fff color: #525252
&:after &:after
border-color: #fff transparent transparent transparent border-color: #525252 transparent transparent transparent
.field .field
margin-bottom: 20px margin-bottom: 20px
@ -2102,6 +2102,12 @@ a.grey-link
margin-left: auto margin-left: auto
&__wrap &__wrap
position: relative position: relative
&__appended
display: flex
&__100px
width: 100px
&__append
text-transform: uppercase
&__input, &__input,
&__textarea &__textarea
width: 100% width: 100%
@ -2125,6 +2131,8 @@ a.grey-link
&_bg &_bg
height: 50px height: 50px
padding: 0 20px padding: 0 20px
&__appended
flex: 1;
&__textarea &__textarea
display: block display: block
height: 36px height: 36px
@ -2156,10 +2164,10 @@ a.grey-link
color: $gray color: $gray
&_info &__input, &_info &__input,
&_info &__textarea &_info &__textarea
border-color: rgba($border,.2) border-color: rgba(#525252,.2)
color: white color: #525252
&:focus &:focus
border-color: white border-color: #686868
&.error &__input, &.error &__input,
&.error &__textarea &.error &__textarea
border-color: $pink border-color: $pink
@ -3215,7 +3223,7 @@ a.grey-link
left: 0 left: 0
width: 100% width: 100%
height: 100% height: 100%
background-image: linear-gradient(0deg, rgba(0,0,0,0.24) 0%, rgba(0,0,0,0.64) 100%) background-image: linear-gradient(135deg, rgb(255, 226, 235) 0%, rgb(216, 245, 246) 100%)
z-index: -2 z-index: -2
&__sidebar &__sidebar
flex: 0 0 calc(50% - 169px) flex: 0 0 calc(50% - 169px)
@ -3249,11 +3257,11 @@ a.grey-link
letter-spacing: 1px letter-spacing: 1px
&__value &__value
font-size: 18px font-size: 18px
color: white color: #525252
&__upload &__upload
margin-left: auto margin-left: auto
font-size: 16px font-size: 16px
color: rgba(white,.7) color: #525252
&__foot &__foot
display: flex display: flex
margin-top: auto margin-top: auto
@ -3275,7 +3283,7 @@ a.grey-link
display: flex display: flex
flex-direction: column flex-direction: column
&__wrap &__field &__wrap &__field
margin-bottom: 30px margin-bottom: 20px
&:last-child &:last-child
margin: 0 margin: 0
&__fieldset &__fieldset
@ -3283,6 +3291,8 @@ a.grey-link
margin-bottom: 50px margin-bottom: 50px
&:last-child &:last-child
margin-top: auto margin-top: auto
&:only-child
margin-top: 0px
.add .add
display: flex display: flex

Loading…
Cancel
Save