-
+
+
-
+
-
- HINT
+
+
+
+
+
+
+
{{ rule.label }}
- Strong or Weak?
+
-
+
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 @@
+
+
+
Your password is {{ message }}
+
+
+
+
+
+
+
\ 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";`
+ }
+ }
+}
});