diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..49e0fc6b4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/node_modules +/package-lock.json \ No newline at end of file diff --git a/README.md b/README.md index b884f6170..5c8e76b91 100644 --- a/README.md +++ b/README.md @@ -1 +1,2 @@ -# Репозиторий для тестового задания на должность верстальщика +# Form +## Is available here: [form](https://master--musical-cocada-cdfd84.netlify.app/) diff --git a/package.json b/package.json new file mode 100644 index 000000000..05068883a --- /dev/null +++ b/package.json @@ -0,0 +1,42 @@ +{ + "name": "test-form", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "start": "set NODE_ENV=development&&webpack serve", + "dev": "set NODE_ENV=development&&webpack", + "build": "set NODE_ENV=production&&webpack", + "clean": "rd /s /q dist" + }, + "browserslist": ">0.25%, not dead", + "repository": { + "type": "git", + "url": "git+https://github.com/18AN/test-form.git" + }, + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/18AN/test-form/issues" + }, + "homepage": "https://github.com/18AN/test-form#readme", + "devDependencies": { + "@babel/core": "^7.18.9", + "@babel/preset-env": "^7.18.9", + "babel-loader": "^8.2.5", + "css-loader": "^6.7.1", + "html-loader": "^4.1.0", + "html-webpack-plugin": "^5.5.0", + "image-webpack-loader": "^8.1.0", + "mini-css-extract-plugin": "^2.6.1", + "sass": "^1.53.0", + "sass-loader": "^13.0.2", + "style-loader": "^3.3.1", + "webpack": "^5.73.0", + "webpack-cli": "^4.10.0", + "webpack-dev-server": "^4.9.3" + }, + "dependencies": { + "@babel/polyfill": "^7.12.1" + } +} diff --git a/src/assets/Arrow-black.svg b/src/assets/Arrow-black.svg new file mode 100644 index 000000000..665abfc14 --- /dev/null +++ b/src/assets/Arrow-black.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/Arrow-blue.svg b/src/assets/Arrow-blue.svg new file mode 100644 index 000000000..78fcccd13 --- /dev/null +++ b/src/assets/Arrow-blue.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/fonts/PTSans-Regular.woff b/src/fonts/PTSans-Regular.woff new file mode 100644 index 000000000..b0d0ea9f4 Binary files /dev/null and b/src/fonts/PTSans-Regular.woff differ diff --git a/src/fonts/WorkSans-Light.woff b/src/fonts/WorkSans-Light.woff new file mode 100644 index 000000000..11aaeda5e Binary files /dev/null and b/src/fonts/WorkSans-Light.woff differ diff --git a/src/fonts/WorkSans-Medium.woff b/src/fonts/WorkSans-Medium.woff new file mode 100644 index 000000000..02876d1ed Binary files /dev/null and b/src/fonts/WorkSans-Medium.woff differ diff --git a/src/fonts/WorkSans-Regular.woff b/src/fonts/WorkSans-Regular.woff new file mode 100644 index 000000000..896af6ea3 Binary files /dev/null and b/src/fonts/WorkSans-Regular.woff differ diff --git a/src/index.html b/src/index.html new file mode 100644 index 000000000..2340d77f7 --- /dev/null +++ b/src/index.html @@ -0,0 +1,128 @@ + + + + + + + Test Form + + +
+
+
+
Sign up
+
+
+
+
+
New user?
+
Use the form below to create your account.
+
+
+
First Name
+
+ +
+
+
+
Last Name
+
+ +
+
+
+
+
+
Nationality
+
+
American
open +
+
+
+
E-mail
+
+ + valid +
+
+
+
+
+
+
Date of birth
+
+
+ 21 + open +
+
+ December + open +
+
+ 1995 + open +
+
+
+
+
Gender
+
+ + + + +
+
+
+
+
+
Password
+
+ +
+
+
+
+
Confirm password
+
+ +
+
+
+
+
+
+ + +
+
+
+
+ + + + +
+
+ +
+ + \ No newline at end of file diff --git a/src/index.js b/src/index.js new file mode 100644 index 000000000..d8e179e0a --- /dev/null +++ b/src/index.js @@ -0,0 +1,45 @@ +"use strict" + +import './index.html'; +import './index.scss'; +import {showPopup,hidePopup, formValidate} from './modules/functions'; +import {svg,button,popup,form,date,nationality} from './modules/consts'; + +//Making svg animation start right after all styles loaded +document.addEventListener('DOMContentLoaded', function(){ + svg.classList.add('hide'); +}) + +window.addEventListener('load', function(){ + svg.classList.remove('hide'); +}) + +//Removing appearing animation from button after it ends +setTimeout(() => { + button.classList.add('opacity'); + button.classList.remove('appear','twelfth'); +}, 6500); + +popup.addEventListener('click', hidePopup); + +form.addEventListener('submit', formSend); +function formSend(e){ + e.preventDefault(); + let valid = formValidate(); + + if(valid === 0){ + let formData = new FormData(form); + formData.append('nationality', nationality); + formData.append('date', date); + fetch('https://jsonplaceholder.typicode.com/posts', { + method: 'POST', + body: JSON.stringify(formData), + headers: { + 'Content-type': 'application/json; charset=UTF-8', + }, + }) + .then(()=>showPopup()) + .then(()=>form.reset()) + .catch((error)=>console.log(error)) + } +} \ No newline at end of file diff --git a/src/index.scss b/src/index.scss new file mode 100644 index 000000000..5548b3cf1 --- /dev/null +++ b/src/index.scss @@ -0,0 +1,14 @@ +@import 'normalize/normalize.scss'; +@import 'style/fonts.scss'; +@import 'style/var.scss'; +@import 'style/general.scss'; +@import 'style/header.scss'; +@import 'style/main.scss'; +@import 'style/form.scss'; +@import 'style/item.scss'; +@import 'style/select.scss'; +@import 'style/options.scss'; +@import 'style/popup.scss'; +@import 'style/error.scss'; +@import 'style/animations.scss'; +@import 'style/media.scss'; diff --git a/src/modules/consts.js b/src/modules/consts.js new file mode 100644 index 000000000..bdcac59d7 --- /dev/null +++ b/src/modules/consts.js @@ -0,0 +1,14 @@ +import { dateAssemble } from "./functions"; + +export const form = document.getElementById('form'); +export const nationality = document.getElementById('nationality').textContent; +export const day = document.querySelector('.day'); +export const mounth = document.querySelector('.mounth'); +export const year = document.querySelector('.year'); +export const popup = document.querySelector('.popup'); +export const wrapper = document.querySelector('.wrapper'); +export const image = document.getElementById('valid'); +export const emailWarning = document.getElementById('email-warn'); +export const button = document.querySelector('.form__button'); +export const date = dateAssemble(day,mounth,year); +export const svg = document.getElementById('svg'); \ No newline at end of file diff --git a/src/modules/functions.js b/src/modules/functions.js new file mode 100644 index 000000000..34303e8f5 --- /dev/null +++ b/src/modules/functions.js @@ -0,0 +1,104 @@ +"use strict" +import { button, emailWarning, image, popup, wrapper } from "./consts"; +import { passwordRegEx, emailRegEx, mismatch, uncorrect, wrongEmail } from "./strings"; + +function addError(input){ + input.parentElement.classList.add('error'); +} + +function removeError(input){ + input.parentElement.classList.remove('error'); +} + +function testEmail(input){ + return !emailRegEx.test(input.value); +} +function testPassword(input){ + return !passwordRegEx.test(input.value); +} +function compare(array){ + let r =(array[0] === array[1]); + return !r ; +} +export function dateAssemble(...array){ + let date = ''; + array.forEach(function(item){ + date = date + item.textContent + '.'; + }) + return date; +} +export function showPopup(){ + popup.classList.add('show'); + wrapper.classList.add('hide'); +} + +export function hidePopup(){ + popup.classList.remove('show'); + wrapper.classList.remove('hide'); +} + +function showImage(){ + image.classList.add('block'); +} + +function hideImage(){ + image.classList.remove('block'); +} + +function shake(){ + button.classList.add('shake'); + setTimeout(()=>{removeShake()},500); + +} +function removeShake(){ + button.classList.remove('shake'); +} + +export function formValidate(){ + let error = 0; + let formCheck = document.querySelectorAll('.check'); + let passwords = []; + let passwordElements = []; + + for (let i = 0; i < formCheck.length; i++){ + let input = formCheck[i]; + removeError(input); + + if(input.id === 'email'){ + if(testEmail(input)){ + hideImage(); + emailWarning.textContent = wrongEmail; + addError(input); + error++; + }else{ + showImage(); + emailWarning.textContent=''; + } + } + else{ + passwordElements.push(input); + passwords.push(input.value); + if(testPassword(input)){ + input.closest('.item').lastElementChild.textContent = uncorrect; + addError(input); + error++ + }else{ + input.closest('.item').lastElementChild.textContent=''; + if(passwords.length === 2){ + if(compare(passwords)){ + passwordElements.forEach(input => { + input.closest('.item').lastElementChild.textContent = mismatch; + addError(input); + error++ + }); + } + } + } + } + + } + if(error!==0){ + shake(); + } + return error; +} \ No newline at end of file diff --git a/src/modules/strings.js b/src/modules/strings.js new file mode 100644 index 000000000..98c06b634 --- /dev/null +++ b/src/modules/strings.js @@ -0,0 +1,5 @@ +export const uncorrect = 'Password must contain at least 8 characters, uppercase and lowercase letters, and numbers'; +export const wrongEmail = 'wrong e-mail address'; +export const mismatch = 'passwords do not match'; +export const passwordRegEx = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$/; +export const emailRegEx = /^\w+@\w+\.\w+$/; diff --git a/src/normalize/normalize.scss b/src/normalize/normalize.scss new file mode 100644 index 000000000..c604a77c4 --- /dev/null +++ b/src/normalize/normalize.scss @@ -0,0 +1,59 @@ +/* Указываем box sizing */ +*, +*::before, +*::after { + box-sizing: border-box; +} + +/* Убираем внутренние отступы */ +ul[class], +ol[class], +input, +button { + padding: 0; +} + +/* Убираем внешние отступы */ +body, +h1, +h2, +h3, +h4, +p, +ul[class], +ol[class], +li, +figure, +figcaption, +blockquote, +dl, +dd { + margin: 0; +} + +/* Выставляем основные настройки по-умолчанию для body */ +body { + min-height: 100vh; + scroll-behavior: smooth; + text-rendering: optimizeSpeed; + line-height: 1.5; +} + +/* Элементы a, у которых нет класса, сбрасываем до дефолтных стилей */ +a:not([class]) { + text-decoration-skip-ink: auto; +} + +/* Упрощаем работу с изображениями */ +img { + max-width: 100%; + display: block; +} + +/* Наследуем шрифты для инпутов и кнопок */ +input, +button, +textarea, +select { + font: inherit; +} \ No newline at end of file diff --git a/src/style/animations.scss b/src/style/animations.scss new file mode 100644 index 000000000..254116a73 --- /dev/null +++ b/src/style/animations.scss @@ -0,0 +1,87 @@ +@keyframes shake { + 0%{ + transform: translateX(0em); + } + 25%{ + transform: translateX(-1em); + } + 50%{ + transform: translateX(1em); + } + 75%{ + transform: translateX(-1em); + } + 100%{ + transform: translateX(0em); + } +} + + +@keyframes appear{ + 0%{ + transform: translateY(1em); + opacity: 0; + } + 100%{ + transform: translateY(0em); + opacity: 1; + } +} + +.appear{ + animation-name: appear; + animation-duration: 1000ms; + animation-fill-mode: forwards; +} + +.first{ + animation-delay: 0ms; +} +.second{ + animation-delay: 500ms; +} +.third{ + animation-delay: 1000ms; +} +.fourth{ + animation-delay: 1500ms; +} +.fifth{ + animation-delay: 2000ms; +} +.sixth{ + animation-delay: 2500ms; +} +.seventh{ + animation-delay: 3000ms; +} +.eighth{ + animation-delay: 3500ms; +} +.ninth{ + animation-delay: 4000ms; +} +.tenth{ + animation-delay: 4500ms; +} +.eleventh{ + animation-delay: 5000ms; +} +.twelfth{ + animation-delay: 5500ms; +} +.shake{ + animation-name: shake; + animation-duration: 500ms; +} + +.opacity{ + opacity: 1; +} + +.svg{ + position: absolute; + right: 0; + bottom: -0.4em; + z-index: 0; +} \ No newline at end of file diff --git a/src/style/error.scss b/src/style/error.scss new file mode 100644 index 000000000..c6af9c69e --- /dev/null +++ b/src/style/error.scss @@ -0,0 +1,12 @@ +.error{ + border-bottom: 0.05em solid $red; + &>.item__input{ + color: $lightRed; + } +} +.warning{ + font-size: 0.85em; + max-width: 14em; + line-height: 1.16em; + color: $lightRed; +} \ No newline at end of file diff --git a/src/style/fonts.scss b/src/style/fonts.scss new file mode 100644 index 000000000..98453cf92 --- /dev/null +++ b/src/style/fonts.scss @@ -0,0 +1,20 @@ +@font-face { + font-family: 'WorkSans-Regular'; + src: url('fonts/WorkSans-Regular.woff') format('woff'); + font-display: swap; +} +@font-face { + font-family: 'WorkSans-Medium'; + src: url('fonts/WorkSans-Medium.woff') format('woff'); + font-display: swap; +} +@font-face { + font-family: 'PTSans-Regular'; + src: url('fonts/PTSans-Regular.woff') format('woff'); + font-display: swap; +} +@font-face { + font-family: 'WorkSans-Light'; + src: url('fonts/WorkSans-Light.woff') format('woff'); + font-display: swap; +} \ No newline at end of file diff --git a/src/style/form.scss b/src/style/form.scss new file mode 100644 index 000000000..8b53deaee --- /dev/null +++ b/src/style/form.scss @@ -0,0 +1,50 @@ +.form{ + min-height: 100%; + display: flex; + flex-direction: column; + justify-content: space-between; + font-size: 1em; + font-family: 'WorkSans-Regular','Arial', sans-serif; +} + +.form__header{ + font-family: 'WorkSans-Medium','Arial',sans-serif; + font-size: 1.57em; + line-height: 1.18em; + opacity: 0; +} + +.form__hint{ + font-family: 'WorkSans-Light', 'Arial', sans-serif; + line-height: 1.14em; + margin-bottom: 1.3em; + opacity: 0; +} + +.form__container{ + display: flex; + justify-content: space-between; + flex-wrap: wrap; + min-width: 16.75em; + align-items: center; +} + +.form__button{ + font-family: 'PTSans-Regular', 'Arial', sans-serif; + font-size: 0.87em; + line-height: 1.125em; + padding: 0.75em 2.5em; + border: none; + background-color: $blue; + cursor: pointer; + color: $white; + letter-spacing: .02em; + opacity: 0; +} + +.form__link{ + font-size: 0.92em; + font-family: 'WorkSans-Light','Arial', sans-serif; + line-height: 1.07; + opacity: 0; +} \ No newline at end of file diff --git a/src/style/general.scss b/src/style/general.scss new file mode 100644 index 000000000..89aaec9dc --- /dev/null +++ b/src/style/general.scss @@ -0,0 +1,36 @@ +html{ + font-size: 100%; +} + +body{ + font-family: 'WorkSans-Regular', 'Arial', sans-serif; + background-color: $background; + padding-top: 5vh; +} +input{ + background-color: transparent; +} +a{ + font-size: 0.92em; + font-family: 'WorkSans-Medium', 'Arial', sans-serif; + color: $blue; +} + +.container{ + display: flex; + max-width: 41.56em; + min-width: 21.66em; + min-height: 30em; + margin: 0 auto; + background-color: $white; +} + +.wrapper{ + position: relative; + min-width: 100%; + display: flex; + min-height: 100%; + z-index: 1; +} + + diff --git a/src/style/header.scss b/src/style/header.scss new file mode 100644 index 000000000..038f0703e --- /dev/null +++ b/src/style/header.scss @@ -0,0 +1,15 @@ +.header{ + display: flex; + justify-content: end; + font-size: 3.06em; + font-family: 'PTSans-Regular', 'Arial', sans-serif; + width: 3.58em; + padding-bottom: 0.44em; + background-color: $blue; + color: $white; + z-index: 1; + & > div{ + writing-mode: vertical-lr; + transform: rotate(180deg); + } +} \ No newline at end of file diff --git a/src/style/item.scss b/src/style/item.scss new file mode 100644 index 000000000..1b337ba70 --- /dev/null +++ b/src/style/item.scss @@ -0,0 +1,26 @@ +.item{ + min-width: 47.7%; + margin-bottom: 1.03em; + opacity: 0; + &__title{ + font-size: 0.85em; + font-family: 'WorkSans-Light','Arial', sans-serif; + line-height: 1.16em; + color: $gray; + margin-bottom: 0.4em; + } + &__container{ + display: flex; + align-items: center; + min-width: 16.75em; + justify-content: space-between; + border-bottom: 0.05em solid $border; + } + &__input{ + outline: none; + padding-bottom: 0.3em; + line-height: 1.14em; + min-width: 12em; + border: none; + } +} \ No newline at end of file diff --git a/src/style/main.scss b/src/style/main.scss new file mode 100644 index 000000000..7a0f162ff --- /dev/null +++ b/src/style/main.scss @@ -0,0 +1,7 @@ +.main{ + width: 100%; + font-size: 0.875em; + padding: 2.4em 1.31em 2.03em; + background-color: transparent; + z-index: 1; +} \ No newline at end of file diff --git a/src/style/media.scss b/src/style/media.scss new file mode 100644 index 000000000..7bcbb1030 --- /dev/null +++ b/src/style/media.scss @@ -0,0 +1,11 @@ +@media (max-width: 767px) { + body{ + padding-top: 0; + } +} + +@media (max-width: 480px) { + .container{ + min-height: 100vh; + } +} \ No newline at end of file diff --git a/src/style/options.scss b/src/style/options.scss new file mode 100644 index 000000000..bcf9d4873 --- /dev/null +++ b/src/style/options.scss @@ -0,0 +1,39 @@ +.options{ + display: flex; + &__radio{ + display: none; + } +} + +.options__radio:checked + .options__label::after{ + transform: scale(1); +} + +.options__label{ + display: inline-flex; + padding-bottom: 0.3em; + position: relative; + min-width: 5em; + line-height: 1.14em; + &::before{ + content: ''; + flex: 0 0 12px; + height: 12px; + background-color: transparent; + border-radius: 50%; + border: 0.7px solid $blue; + align-self: center; + margin-right: 0.428em; + } + &::after{ + content: ''; + position: absolute; + width: 6px; + height: 6px; + background-color: $blue; + border-radius: 50%; + top: 5px; + left: 3px; + transform: scale(0); + } +} \ No newline at end of file diff --git a/src/style/popup.scss b/src/style/popup.scss new file mode 100644 index 000000000..d1c01dc4e --- /dev/null +++ b/src/style/popup.scss @@ -0,0 +1,67 @@ +.popup{ + display: none; + min-width: 100%; + min-height: 100%; +} + +.popup__header{ + display: flex; + justify-content: end; + font-size: 3.06em; + font-family: 'PTSans-Regular','Arial', sans-serif; + width: 3.58em; + padding-bottom: 0.44em; + background-color: $blue; + color: $white; + & > div{ + writing-mode: vertical-lr; + transform: rotate(180deg); + } +} + +.popup-main{ + display: flex; + flex-direction: column; + justify-content: space-between; + width: 100%; + font-size: 0.875em; + padding: 2.4em 1.31em 2.03em; + background-color: $white; +} + +.popup-main__container{ + padding-left: 3.07em; + padding-top: 12.71em; + min-width: 16.75em; +} + +.popup-main__header{ + font-family: "WorkSans-Medium",'Arial', sans-serif; + font-size: 1.57em; + line-height: 1.18em; +} + +.popup-main__text{ + margin-top: 0.78em; + line-height: 1.14em; + font-family: 'WorkSans-Light','Arial', sans-serif; +} + +.popup-main__link{ + font-size: 0.92em; + font-family: 'WorkSans-Light','Arial', sans-serif; + line-height: 1.07; + opacity: 1; + margin-bottom: 0.7em; +} +.show{ + display: flex; +} + +.hide{ + display: none; +} + +.block{ + display: block; +} \ No newline at end of file diff --git a/src/style/select.scss b/src/style/select.scss new file mode 100644 index 000000000..3bcee2422 --- /dev/null +++ b/src/style/select.scss @@ -0,0 +1,39 @@ +.select{ + display: flex; + min-width: 16.75em; + justify-content: space-between; + align-items: center; + border-bottom: 0.05em $border solid; + &__item{ + width: 12em; + line-height: 1.14em; + padding-bottom: 0.3em; + } +} + +.date{ + display: flex; + min-width: 16.75em; + flex-wrap: wrap; + justify-content: space-between; + &__container{ + padding-bottom: 0.3em; + line-height: 1.14em; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 0.05em $border solid; + } +} + +.day{ + min-width: 4em; +} + +.mounth{ + min-width: 6.5em; +} + +.year{ + min-width: 4.83em; +} diff --git a/src/style/var.scss b/src/style/var.scss new file mode 100644 index 000000000..78e531ca5 --- /dev/null +++ b/src/style/var.scss @@ -0,0 +1,8 @@ +$background: #F2F2F2; +$blue: #5A61ED; +$white: #FFFFFF; +$gray: #7C7C7C; +$border: #F2F2F2; +$lightRed: #FF2222; +$red: #FF2828; +$animation: #F9FAFF; diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 000000000..7c25d0cc4 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,105 @@ +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const path = require('path'); + +const mode = process.env.NODE_ENV || 'development'; +const devMode = mode === 'development'; +const target = devMode ? 'web' : 'browserslist'; +const devtool = devMode ? 'source-map' : undefined; + + +module.exports = { + mode, + target, + devtool, + devServer:{ + port: 7000, + open: true, + hot: true, + }, + entry: ["@babel/polyfill", path.resolve(__dirname, 'src', 'index.js')], + output: { + path: path.resolve(__dirname, 'dist'), + clean: true, + filename: '[name].[contenthash].js', + assetModuleFilename: 'asset/[name][ext]', + }, + plugins: [ + new HtmlWebpackPlugin({ + template: path.resolve(__dirname, 'src', 'index.html') + }), + new MiniCssExtractPlugin({ + filename: '[name].[contenthash].css' + }), + ], + module: { + rules: [ + //Loading HTML + { + test: /\.html$/i, + loader: 'html-loader', + }, + //Loadding CSS + { + test: /\.(c|sa|sc)ss$/i, + use: [ + devMode ? "style-loader" : MiniCssExtractPlugin.loader, + "css-loader", + "sass-loader" + ], + }, + //Loading fonts + { + test: /\.woff2?$/i, + type: 'asset/resource', + generator: { + filename: "fonts/[name][ext]" + } + }, + //Loading images + { + test: /\.(jpe?g|svg|png|gif)?$/i, + use: [ + { + loader: 'image-webpack-loader', + options: { + mozjpeg: { + progressive: true, + }, + // optipng.enabled: false will disable optipng + optipng: { + enabled: false, + }, + pngquant: { + quality: [0.65, 0.90], + speed: 4 + }, + gifsicle: { + interlaced: false, + }, + // the webp option will enable WEBP + webp: { + quality: 75 + } + } + } + ], + type: 'asset/resource', + generator: { + filename: "assets/[name][ext]" + } + }, + //Loading js(Babel) + { + test: /\.m?js$/i, + exclude: /(node_modules|bower_components)/, + use: { + loader: 'babel-loader', + options: { + presets: ['@babel/preset-env'] + } + } + }, + ] + } +} \ No newline at end of file