diff --git a/package.json b/package.json index 9e84a2c..086e80f 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,10 @@ "eslint": "^8.22.0", "eslint-plugin-vue": "^9.3.0", "jsdom": "^20.0.3", + "node-sass": "^9.0.0", "prettier": "^2.7.1", + "sass": "^1.62.1", + "sass-loader": "^13.3.1", "vite": "^3.2.4", "vitest": "^0.25.3" } diff --git a/src/App.vue b/src/App.vue index 07d8229..8b5c326 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,26 +1,90 @@ - + diff --git a/src/assets/base.scss b/src/assets/base.scss new file mode 100644 index 0000000..27bf672 --- /dev/null +++ b/src/assets/base.scss @@ -0,0 +1,25 @@ +//VARIABLES +$color-red: #c70000; +$color-green: #008000; +$color-yellow: #ffc300; +$color-lightgray: #d3d3d3; +$color-background: #f4f4f4; +$color-indicator: #cbcbcb; + + +//ANIMATIONS +.reduce-enter-from, +.reduce-leave-to { + transform: scale(0); +} + +.reduce-enter-to, +.reduce-leave-from { + transform: scale(1); +} + +.reduce-move, +.reduce-enter-active, +.reduce-leave-active { + transition: transform 0.3s ease-in-out; +} \ No newline at end of file diff --git a/src/assets/icomoon/icomoon.ttf b/src/assets/icomoon/icomoon.ttf new file mode 100644 index 0000000..7cdc5ce Binary files /dev/null and b/src/assets/icomoon/icomoon.ttf differ diff --git a/src/assets/icomoon/icomoon.woff b/src/assets/icomoon/icomoon.woff new file mode 100644 index 0000000..92acc1c Binary files /dev/null and b/src/assets/icomoon/icomoon.woff differ diff --git a/src/assets/icomoon/selection.json b/src/assets/icomoon/selection.json new file mode 100644 index 0000000..a116d35 --- /dev/null +++ b/src/assets/icomoon/selection.json @@ -0,0 +1 @@ +{"IcoMoonType":"selection","icons":[{"icon":{"paths":["M512.006-0.008c-0.003-0-0.006-0-0.010-0-282.771 0-512.003 229.23-512.005 512.001l-0 0c-0 0.003-0 0.006-0 0.010 0 282.773 229.232 512.005 512.005 512.005 0.003 0 0.006-0 0.009-0l0-0c282.771-0.002 512.002-229.234 512.002-512.005 0-0.003-0-0.007-0-0.010l0 0.001c-0.002-282.77-229.232-512-512.002-512.002l0.001 0zM714.755 212.512l90.768 88.145-204.034 210.064 210.013 204.034-88.145 90.768-210.064-204.034-204.034 210.013-90.781-88.196 204.047-210.013-210.026-204.034 88.158-90.768 210.051 204.034z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["notPassedIcon"]},"attrs":[{}],"properties":{"order":2,"id":1,"name":"notPassedIcon","prevSize":32,"code":59648},"setIdx":0,"setId":2,"iconIdx":0},{"icon":{"paths":["M512-0.001c-282.77 0.002-512 229.232-512.002 512.002l0-0.001c0.002 282.77 229.232 512 512.002 512.002l-0-0c282.77-0.002 512-229.232 512.002-512.002l-0 0.001c-0.002-282.77-229.232-512-512.002-512.002l0 0zM738.572 194.673l99.947 77.551-432.316 557.103-220.709-319.435 104.087-71.92 122.549 177.357z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["passedIcon"]},"attrs":[{}],"properties":{"order":3,"id":0,"name":"passedIcon","prevSize":32,"code":59649},"setIdx":0,"setId":2,"iconIdx":1}],"height":1024,"metadata":{"name":"icomoon"},"preferences":{"showGlyphs":true,"showQuickUse":true,"showQuickUse2":true,"showSVGs":true,"fontPref":{"prefix":"icon-","metadata":{"fontFamily":"icomoon","majorVersion":1,"minorVersion":0},"metrics":{"emSize":1024,"baseline":6.25,"whitespace":50},"embed":false,"cssVars":true,"cssVarsFormat":"scss","showSelector":false,"showMetrics":false,"showMetadata":false,"showVersion":false},"imagePref":{"prefix":"icon-","png":true,"useClassSelector":true,"color":0,"bgColor":16777215,"classSelector":".icon"},"historySize":50,"showCodes":true,"gridSize":16}} \ No newline at end of file diff --git a/src/assets/iconVariables.scss b/src/assets/iconVariables.scss new file mode 100644 index 0000000..e6d9299 --- /dev/null +++ b/src/assets/iconVariables.scss @@ -0,0 +1,6 @@ +$icomoon-font-family: "icomoon"; +$icomoon-font-path: "src/assets/icomoon"; + +$icon-notPassedIcon: "\e900"; +$icon-passedIcon: "\e901"; + diff --git a/src/assets/icons.scss b/src/assets/icons.scss new file mode 100644 index 0000000..c29017e --- /dev/null +++ b/src/assets/icons.scss @@ -0,0 +1,40 @@ +@import './iconVariables.scss'; + +@font-face { + font-family: '#{$icomoon-font-family}'; + src: url('#{$icomoon-font-path}/#{$icomoon-font-family}.eot?pfwaw0'); + src: url('#{$icomoon-font-path}/#{$icomoon-font-family}.eot?pfwaw0#iefix') format('embedded-opentype'), + url('#{$icomoon-font-path}/#{$icomoon-font-family}.ttf?pfwaw0') format('truetype'), + url('#{$icomoon-font-path}/#{$icomoon-font-family}.woff?pfwaw0') format('woff'), + url('#{$icomoon-font-path}/#{$icomoon-font-family}.svg?pfwaw0##{$icomoon-font-family}') format('svg'); + font-weight: normal; + font-style: normal; + font-display: block; +} + +[class^="icon-"], [class*=" icon-"] { + /* use !important to prevent issues with browser extensions that change fonts */ + font-family: '#{$icomoon-font-family}' !important; + speak: never; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-notPassedIcon { + &:before { + content: $icon-notPassedIcon; + } +} +.icon-passedIcon { + &:before { + content: $icon-passedIcon; + } +} + diff --git a/src/assets/main.css b/src/assets/main.css deleted file mode 100644 index e69de29..0000000 diff --git a/src/assets/main.scss b/src/assets/main.scss new file mode 100644 index 0000000..2dc7a30 --- /dev/null +++ b/src/assets/main.scss @@ -0,0 +1,5 @@ +@import './icons'; + +body { + background-color: $color-background; +} \ No newline at end of file diff --git a/src/components/StrengthIndicator.vue b/src/components/StrengthIndicator.vue new file mode 100644 index 0000000..ee361bc --- /dev/null +++ b/src/components/StrengthIndicator.vue @@ -0,0 +1,66 @@ + + + + + \ No newline at end of file diff --git a/src/customTests/Password.spec.js b/src/customTests/Password.spec.js new file mode 100644 index 0000000..9758914 --- /dev/null +++ b/src/customTests/Password.spec.js @@ -0,0 +1,29 @@ +import { describe, it, beforeEach, expect } from "vitest"; +import { createPinia, setActivePinia } from "pinia/dist/pinia"; +import { useStrongPasswordStore } from "@/stores/strong-password"; + +describe('Password', () => { + beforeEach(() => { + setActivePinia(createPinia()) + }); + + it('At least one letter', () => { + const strongPasswordStore = useStrongPasswordStore(); + const oneLetter = strongPasswordStore.rules.find(rule => rule.name === 'OneLetter'); + + const expectTruthy = ['a', 'B', 'abc', '1a2s', 'AAbb', 'Mot%!']; + const expectFalsy = ['', 1, '%', '1@3']; + + expectTruthy.forEach(passwordCanidate => { + strongPasswordStore.password = passwordCanidate; + strongPasswordStore.validateInput(); + expect(oneLetter.isCorrect).toBeTruthy(); + }) + + expectFalsy.forEach(passwordCanidate => { + strongPasswordStore.password = passwordCanidate; + strongPasswordStore.validateInput(); + expect(oneLetter.isCorrect).toBeFalsy(); + }) + }) +}) \ No newline at end of file diff --git a/src/main.js b/src/main.js index 6641b86..1594b74 100644 --- a/src/main.js +++ b/src/main.js @@ -2,7 +2,7 @@ import { createApp } from "vue"; import { createPinia } from "pinia"; import App from "./App.vue"; -import "./assets/main.css"; +import "./assets/main.scss"; const app = createApp(App); diff --git a/src/stores/strong-password.js b/src/stores/strong-password.js index adefaef..98107cf 100644 --- a/src/stores/strong-password.js +++ b/src/stores/strong-password.js @@ -1,5 +1,69 @@ import { defineStore } from "pinia"; -export const useStrongPasswordStore = defineStore("strong_password", () => { - return {}; +export const useStrongPasswordStore = defineStore("strong_password", { + state: () => ({ + password: '', + rules: [ + { + name: 'OneLetter', + pattern: /[a-zA-z]/, + label: 'Has at least one letter', + isCorrect: false + }, + { + name: 'UpperAndLower', + pattern: /(?=.*[a-z])(?=.*[A-Z])/, + label: 'Has at least one lower and one upper case letter', + isCorrect: false + }, + { + name: 'OneNumber', + pattern: /\d/, + label: 'Has at least one number', + isCorrect: false + }, + { + name: 'SpecialSymbol', + pattern: /[^\w]/, + label: 'Has at least one special character', + isCorrect: false + }, + { + name: 'LongerThan4', + pattern: /.{5,}/, + label: 'Has length > 4', + isCorrect: false + }, + { + name: 'LongerThan8', + pattern: /.{9,}/, + label: 'Has length > 8', + isCorrect: false + }, + { + name: 'LongerThan12', + pattern: /.{13,}/, + label: 'Has length > 12', + isCorrect: false + }, + ] + }), + + getters: { + countPassedRules: (state) => state.rules.filter(rule => rule.isCorrect).length, + isPasswordStrong() { + return this.countPassedRules >= 5; + }, + isPasswordWeak() { + return !!this.countPassedRules && this.countPassedRules < 5; + } + }, + + actions: { + validateInput() { + this.rules.forEach(rule => { + rule.pattern.test(this.password) ? rule.isCorrect = true : rule.isCorrect = false; + }); + } + } }); diff --git a/vite.config.js b/vite.config.js index 4d60b3a..7ef3850 100644 --- a/vite.config.js +++ b/vite.config.js @@ -11,4 +11,12 @@ export default defineConfig({ "@": fileURLToPath(new URL("./src", import.meta.url)), }, }, + css: { + preprocessorOptions: { + scss: { + additionalData: ` + @import "./src/assets/base.scss";` + } + } +} });