diff --git a/package.json b/package.json index 9a42306..6732083 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "signale": "^1.4.0", "vue": "^2.6.7", "vue-carousel": "^0.18.0", - "vue-router": "^3.0.2" + "vue-router": "^3.0.2", + "vuelidate": "^0.7.5" } } diff --git a/src/admin/App.vue b/src/admin/App.vue index e7d9d0c..c011c82 100644 --- a/src/admin/App.vue +++ b/src/admin/App.vue @@ -1,3 +1,130 @@ \ No newline at end of file + .root-container + header.header-container + Header + nav.nav-container + Navigation + main.content-container + router-view + + + + \ No newline at end of file diff --git a/src/admin/components/About/About.vue b/src/admin/components/About/About.vue new file mode 100644 index 0000000..36f7b02 --- /dev/null +++ b/src/admin/components/About/About.vue @@ -0,0 +1,161 @@ + + + \ No newline at end of file diff --git a/src/admin/components/About/Skill.vue b/src/admin/components/About/Skill.vue new file mode 100644 index 0000000..ffdc6cb --- /dev/null +++ b/src/admin/components/About/Skill.vue @@ -0,0 +1,216 @@ + + + \ No newline at end of file diff --git a/src/admin/components/About/SkillGroup.vue b/src/admin/components/About/SkillGroup.vue new file mode 100644 index 0000000..eafccff --- /dev/null +++ b/src/admin/components/About/SkillGroup.vue @@ -0,0 +1,339 @@ + + + \ No newline at end of file diff --git a/src/admin/components/AddBtn.vue b/src/admin/components/AddBtn.vue new file mode 100644 index 0000000..0061bf3 --- /dev/null +++ b/src/admin/components/AddBtn.vue @@ -0,0 +1,137 @@ + + + \ No newline at end of file diff --git a/src/admin/components/CardBtn.vue b/src/admin/components/CardBtn.vue new file mode 100644 index 0000000..7625328 --- /dev/null +++ b/src/admin/components/CardBtn.vue @@ -0,0 +1,83 @@ + + + \ No newline at end of file diff --git a/src/admin/components/CustomInput.vue b/src/admin/components/CustomInput.vue new file mode 100644 index 0000000..15e1e34 --- /dev/null +++ b/src/admin/components/CustomInput.vue @@ -0,0 +1,199 @@ + + + + + \ No newline at end of file diff --git a/src/admin/components/Header.vue b/src/admin/components/Header.vue new file mode 100644 index 0000000..cc03bd5 --- /dev/null +++ b/src/admin/components/Header.vue @@ -0,0 +1,94 @@ + + + \ No newline at end of file diff --git a/src/admin/components/Icon.vue b/src/admin/components/Icon.vue new file mode 100644 index 0000000..700e062 --- /dev/null +++ b/src/admin/components/Icon.vue @@ -0,0 +1,41 @@ + + \ No newline at end of file diff --git a/src/admin/components/InputTooltip.vue b/src/admin/components/InputTooltip.vue new file mode 100644 index 0000000..ac870b2 --- /dev/null +++ b/src/admin/components/InputTooltip.vue @@ -0,0 +1,41 @@ + + + + + + + diff --git a/src/admin/components/Login.vue b/src/admin/components/Login.vue new file mode 100644 index 0000000..8d0fe85 --- /dev/null +++ b/src/admin/components/Login.vue @@ -0,0 +1,173 @@ + + + \ No newline at end of file diff --git a/src/admin/components/Navigation.vue b/src/admin/components/Navigation.vue new file mode 100644 index 0000000..7735a1c --- /dev/null +++ b/src/admin/components/Navigation.vue @@ -0,0 +1,81 @@ + + + \ No newline at end of file diff --git a/src/admin/components/Reviews/Review.vue b/src/admin/components/Reviews/Review.vue new file mode 100644 index 0000000..4374ec3 --- /dev/null +++ b/src/admin/components/Reviews/Review.vue @@ -0,0 +1,110 @@ + + + \ No newline at end of file diff --git a/src/admin/components/Reviews/ReviewEdit.vue b/src/admin/components/Reviews/ReviewEdit.vue new file mode 100644 index 0000000..500685b --- /dev/null +++ b/src/admin/components/Reviews/ReviewEdit.vue @@ -0,0 +1,345 @@ + + + \ No newline at end of file diff --git a/src/admin/components/Reviews/Reviews.vue b/src/admin/components/Reviews/Reviews.vue new file mode 100644 index 0000000..dd18a66 --- /dev/null +++ b/src/admin/components/Reviews/Reviews.vue @@ -0,0 +1,119 @@ + + + \ No newline at end of file diff --git a/src/admin/components/Works/Work.vue b/src/admin/components/Works/Work.vue new file mode 100644 index 0000000..e42fa63 --- /dev/null +++ b/src/admin/components/Works/Work.vue @@ -0,0 +1,127 @@ + + + \ No newline at end of file diff --git a/src/admin/components/Works/WorkEdit.vue b/src/admin/components/Works/WorkEdit.vue new file mode 100644 index 0000000..6e43897 --- /dev/null +++ b/src/admin/components/Works/WorkEdit.vue @@ -0,0 +1,437 @@ + + + \ No newline at end of file diff --git a/src/admin/components/Works/Works.vue b/src/admin/components/Works/Works.vue new file mode 100644 index 0000000..ed99193 --- /dev/null +++ b/src/admin/components/Works/Works.vue @@ -0,0 +1,122 @@ + + + \ No newline at end of file diff --git a/src/admin/main.js b/src/admin/main.js index 6e47267..f4abce2 100644 --- a/src/admin/main.js +++ b/src/admin/main.js @@ -1,7 +1,15 @@ import Vue from 'vue'; import App from './App.vue' +import VueRouter from 'vue-router' +import router from './routes' +import Vuelidate from 'vuelidate' + +Vue.use(Vuelidate) +Vue.use(VueRouter) +Vue.router = router new Vue({ el: "#app-root", - render: h => h(App) + render: h => h(App), + router }); \ No newline at end of file diff --git a/src/admin/routes.js b/src/admin/routes.js new file mode 100644 index 0000000..69400fb --- /dev/null +++ b/src/admin/routes.js @@ -0,0 +1,51 @@ +import VueRouter from 'vue-router' +import About from './components/About/About' +import Reviews from './components/Reviews/Reviews' +import Works from './components/Works/Works' +import Login from './components/Login' + +const routes = [ + { + path: '/', + name: 'about', + component: About, + meta: + { + title: "Блок «Обо мне»" + } + }, + { + path: '/reviews', + component: Reviews, + meta: + { + title: "Блок «Работы»" + } + }, + { + path: '/works', + component: Works, + meta: + { + title: "Блок «Отзывы»" + } + }, + { + path: '/login', + component: Login + }, + { + path: '*', + redirect: 'about' + } +] + +const router = new VueRouter({ + routes +}) + +router.afterEach((to, from) => { + document.title = to.meta.title || '' +}) + +export default router diff --git a/src/components/about.pug b/src/components/about.pug index a2dab13..b25eaa4 100644 --- a/src/components/about.pug +++ b/src/components/about.pug @@ -5,7 +5,7 @@ section.about(id="about") .user .user__avatar .avatar - +image('userfiles/user.jpg', 'avatar__img') + +image('userfiles/admin.jpg', 'avatar__img') .user__info - var info = [ diff --git a/src/components/contacts.pug b/src/components/contacts.pug index 10a4a73..43bf798 100644 --- a/src/components/contacts.pug +++ b/src/components/contacts.pug @@ -2,13 +2,13 @@ section.contacts(id="contacts") .container.contacts__container h2.section__title.contacts__title Связаться со мной .contacts__form - form + form(novalidate)#contacts__form .form__container .form__row - var fields = [ - ["Введите ваше имя", "Иванов Иван", "user"], - ["Введите ваш email", "example@mail.com", "envelope"] + ["Введите ваше имя", "Иванов Иван", "user-empty", "text"], + ["Введите ваш email", "example@mail.com", "envelope", "email"] ] each field in fields label.form__block @@ -16,14 +16,23 @@ section.contacts(id="contacts") .form__block-wrap +icon(field[2],'form__block-icon') .form__block-field - input(placeholder=field[1]).form__block-input + input(type=field[3] placeholder=field[1] required).form__block-input + .error-tooltip .form__row.form__row--more-margin label.form__block .form__block-label Сообщение к письму .form__block-wrap.form__block-wrap--align-top +icon('message','form__block-icon') .form__block-field - textarea(placeholder="Требуется ваша помощь в создании сайта. \nИнтересуют сроки и цена вопроса" rows="3").form__block-input.form__block-textarea + textarea(required minlength="15" placeholder="Требуется ваша помощь в создании сайта. \nИнтересуют сроки и цена вопроса" rows="3").form__block-input.form__block-textarea + .error-tooltip .form__row .form__buttons - button(type="submit").custom-btn Отправить \ No newline at end of file + button(type="submit" id="contacts__form-submit").custom-btn Отправить + + script(type="template" id="modalTemplate") + .modal + .modal__container + .modal__content + .modal__content-text Письмо отправлено + button(type=button)#modal__close.custom-btn Закрыть \ No newline at end of file diff --git a/src/images/bg/bg-admin.jpg b/src/images/bg/bg-admin.jpg new file mode 100644 index 0000000..7a833e1 Binary files /dev/null and b/src/images/bg/bg-admin.jpg differ diff --git a/src/images/icons/key.svg b/src/images/icons/key.svg new file mode 100644 index 0000000..ccb2678 --- /dev/null +++ b/src/images/icons/key.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/images/icons/user-empty.svg b/src/images/icons/user-empty.svg new file mode 100644 index 0000000..2373903 --- /dev/null +++ b/src/images/icons/user-empty.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/images/icons/user.svg b/src/images/icons/user.svg index 2373903..2c40f6f 100644 --- a/src/images/icons/user.svg +++ b/src/images/icons/user.svg @@ -1,24 +1,17 @@ - + + - - - - - + viewBox="0 0 350 350" style="enable-background:new 0 0 350 350;" xml:space="preserve"> + + + + + diff --git a/src/images/userfiles/admin.jpg b/src/images/userfiles/admin.jpg new file mode 100644 index 0000000..42f9c66 Binary files /dev/null and b/src/images/userfiles/admin.jpg differ diff --git a/src/main.js b/src/main.js index 2da8888..daa4f5a 100644 --- a/src/main.js +++ b/src/main.js @@ -7,4 +7,5 @@ import "./scripts/skills"; import "./scripts/works"; import "./scripts/reviews"; import "./scripts/parallax"; -import "./scripts/nav"; \ No newline at end of file +import "./scripts/nav"; +import "./scripts/contacts"; \ No newline at end of file diff --git a/src/scripts/contacts.js b/src/scripts/contacts.js new file mode 100644 index 0000000..b32021c --- /dev/null +++ b/src/scripts/contacts.js @@ -0,0 +1,112 @@ +const submitBtn = document.querySelector("#contacts__form-submit") +const form = document.querySelector("#contacts__form") +let formInValid + +( () => { + Array.from(form.elements).forEach(elem => { + if (elem.type === "submit") return + elem.addEventListener("input", () => { + if (formInValid) { + validateElem(elem) + } + }) + }) +})() + +function switchScroll () { + document.body.classList.toggle('body--scroll-block') +} + + +function formValidate (form) { + formInValid = Array.from(form.elements).some(elem => !elem.checkValidity()) +} +submitBtn.addEventListener("click", (e) => { + e.preventDefault() + formValidate(form) + if (formInValid) { + Array.from(form.elements).forEach(elem => { + if (elem.type === "submit") return + validateElem(elem) + }) + } else { + const template = document.querySelector("#modalTemplate").innerHTML; + const modal = createModal(template); + modal.open(); + } +}) + +function validateElem(elem) { + if (!elem.checkValidity()) { + showError(elem) + } else { + hideError(elem) + } +} + +function showError (elem) { + let errorText = '' + const parent = elem.closest('.form__block') + const tooltip = parent.querySelector('.error-tooltip') + parent.classList.add('error') + if (elem.value.length === 0) { + errorText = "Поле не должно быть пустым" + } else { + switch (elem.type) { + case 'email': + errorText = "Введите корректный e-mail" + break + case 'textarea': + errorText = "Введите не менее 15 символов" + break + } + } + + tooltip.innerText = errorText + tooltip.classList.add("error-tooltip--showed") +} + +function emptyForm (form) { + form.reset() +} + +function hideError (elem) { + const parent = elem.closest('.form__block') + const tooltip = parent.querySelector('.error-tooltip') + parent.classList.remove('error') + tooltip.innerText = '' + tooltip.classList.remove("error-tooltip--showed") +} + +function createModal(template) { + const fragment = document.createElement('div'); + + fragment.innerHTML = template; + + const modalElement = fragment.querySelector(".modal"); + const closeElement = fragment.querySelector("#modal__close"); + + modalElement.addEventListener("click", e => { + if (e.target === modalElement) { + closeElement.click(e); + + } + }); + + closeElement.addEventListener("click", (e) => { + e.preventDefault() + document.querySelector('.contacts').removeChild(modalElement); + switchScroll() + emptyForm(form) + }); + + return { + open() { + document.querySelector('.contacts').appendChild(modalElement); + switchScroll() + }, + close() { + closeElement.click(); + } + }; +} \ No newline at end of file diff --git a/src/styles/blocks/error-tooltip.pcss b/src/styles/blocks/error-tooltip.pcss new file mode 100644 index 0000000..1998df8 --- /dev/null +++ b/src/styles/blocks/error-tooltip.pcss @@ -0,0 +1,26 @@ +.error-tooltip { + opacity: 0; + position: absolute; + left: 5%; + font-size: 14px; + line-height: 44px; + color: #fff; + background-color: $errors-color; + padding: 0 20px; + z-index: 9999; + user-select: none; + + &:after { + content: ''; + position: absolute; + bottom: 100%; + left: 50%; + transform: translateX(-50%); + border: 6px solid transparent; + border-bottom: 6px solid $errors-color; + } + + &--showed { + opacity: 1; + } +} \ No newline at end of file diff --git a/src/styles/blocks/form.pcss b/src/styles/blocks/form.pcss index d9d468f..3badc59 100644 --- a/src/styles/blocks/form.pcss +++ b/src/styles/blocks/form.pcss @@ -27,10 +27,12 @@ .form__block { flex: 1; margin-right: 90px; - border-bottom: 1px solid currentColor; + border-bottom: 2px solid currentColor; + position: relative; &:hover, - &:focus { + &:focus, + &:focus-within { border-color: $main; .form__block-icon { @@ -43,6 +45,14 @@ margin-right: 0; } + &.error { + border-color: #fb0000; + + .form__block-icon { + fill: #fb0000; + opacity: 1; + } + } @include tablets { margin-right: 20px; diff --git a/src/styles/blocks/modal.pcss b/src/styles/blocks/modal.pcss new file mode 100644 index 0000000..7ebf30b --- /dev/null +++ b/src/styles/blocks/modal.pcss @@ -0,0 +1,44 @@ +.modal { + display: flex; + position: fixed; + width: 100%; + height: 100%; + bottom: 0; + + &:before { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + display: block; + content: ""; + background-color: #2d3c4e; + opacity: .9; + } +} + +.modal__container { + display: flex; + justify-content: center; + align-items: center; + flex: 1; + z-index: 10; +} + +.modal__content { + background-color: $white; + width: 565px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: 50px; +} + +.modal__content-text { + margin-bottom: 30px; + font-weight: 500; + font-size: 36px; + text-align: center; +} \ No newline at end of file diff --git a/src/styles/blocks/slider-btn.pcss b/src/styles/blocks/slider-btn.pcss index de1d91c..b07621f 100644 --- a/src/styles/blocks/slider-btn.pcss +++ b/src/styles/blocks/slider-btn.pcss @@ -5,6 +5,11 @@ padding: 0; border: none; + &:disabled { + cursor: unset; + opacity: .3; + } + &:enabled { &:hover { background-color: #5500f2; diff --git a/src/styles/blocks/social.pcss b/src/styles/blocks/social.pcss index 95558db..1d9090b 100644 --- a/src/styles/blocks/social.pcss +++ b/src/styles/blocks/social.pcss @@ -21,8 +21,11 @@ opacity: .3; &:hover { + border-color: #ff9a00; + opacity: 1; + .social__icon { - fill: currentColor; + fill: #ff9a00; } } diff --git a/src/styles/variables.json b/src/styles/variables.json index 85e6c90..4d4ade2 100644 --- a/src/styles/variables.json +++ b/src/styles/variables.json @@ -13,11 +13,14 @@ "text-color": "#464d62", - "links-color": "#4b6fd7", + "links-color": "#383bcf", "main": "#5500f2", "font": "#464d62", "footer": "#252830", "href": "#586ed3", "white": "#fff", - "tag": "#859ec2" + "tag": "#859ec2", + "admin-font": "#414c63", + "errors-color": "#fb0000", + "errors-color-message": "#cd1515" } diff --git a/yarn.lock b/yarn.lock index c6c1568..0c6338f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7170,6 +7170,11 @@ vue@^2.5.17, vue@^2.6.7: resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.11.tgz#76594d877d4b12234406e84e35275c6d514125c5" integrity sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ== +vuelidate@^0.7.5: + version "0.7.5" + resolved "https://registry.yarnpkg.com/vuelidate/-/vuelidate-0.7.5.tgz#ff48c75ae9d24ea24c24e9ea08065eda0a0cba0a" + integrity sha512-GAAG8QAFVp7BFeQlNaThpTbimq3+HypBPNwdkCkHZZeVaD5zmXXfhp357dcUJXHXTZjSln0PvP6wiwLZXkFTwg== + watchpack@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.1.tgz#280da0a8718592174010c078c7585a74cd8cd0e2"