Extract password strength meter into reusable component with SCSS color variables

Co-authored-by: CommanderStorm <26258709+CommanderStorm@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-01-18 13:58:44 +00:00
parent 854c54925a
commit 3a872f78d0
4 changed files with 108 additions and 127 deletions

View File

@ -52,7 +52,7 @@ const main = async () => {
}
if (passwordValidation.warning) {
console.warn("\x1b[31m%s\x1b[0m",
"Warning: "+passwordValidation.warning);
"Warning: " + passwordValidation.warning);
}
} else {
password = await question("New Password: ");

View File

@ -0,0 +1,95 @@
<template>
<div v-if="password && strength !== null" class="password-strength mt-2">
<div class="strength-meter" :class="{ 'mx-auto': centered }">
<div
class="strength-meter-fill"
:class="strengthClass"
:style="{ width: strengthWidth }"
/>
</div>
<small v-if="strength < 3" class="text-warning d-block mt-1">
{{ $t("passwordWeakWarning") }}
</small>
</div>
</template>
<script>
export default {
props: {
password: {
type: String,
required: true,
},
strength: {
type: Number,
default: null,
},
centered: {
type: Boolean,
default: false,
},
},
computed: {
strengthClass() {
if (this.strength === null) {
return "";
}
const classes = [ "strength-very-weak", "strength-weak", "strength-fair", "strength-good", "strength-strong" ];
return classes[this.strength] || "";
},
strengthWidth() {
if (this.strength === null) {
return "0%";
}
return `${(this.strength + 1) * 20}%`;
},
},
};
</script>
<style lang="scss" scoped>
@import "../assets/vars.scss";
.password-strength {
margin-top: 0.5rem;
}
.strength-meter {
height: 8px;
width: 85%;
background-color: #e0e0e0;
border-radius: 8px;
overflow: hidden;
&.mx-auto {
margin-left: auto;
margin-right: auto;
}
}
.strength-meter-fill {
height: 100%;
transition: width 0.3s ease, background-color 0.3s ease;
}
// Color transitions using SCSS variables
.strength-very-weak {
background-color: $danger;
}
.strength-weak {
background-color: mix($danger, $warning, 50%);
}
.strength-fair {
background-color: $warning;
}
.strength-good {
background-color: mix($warning, $primary, 50%);
}
.strength-strong {
background-color: $primary;
}
</style>

View File

@ -45,18 +45,11 @@
/>
<!-- Password strength indicator -->
<div v-if="password.newPassword && passwordStrength !== null" class="password-strength mt-2">
<div class="strength-meter mx-auto">
<div
class="strength-meter-fill"
:class="strengthClass"
:style="{ width: strengthWidth }"
/>
</div>
<small v-if="passwordStrength < 3" class="text-warning d-block mt-1">
{{ $t("passwordWeakWarning") }}
</small>
</div>
<PasswordStrengthMeter
:password="password.newPassword"
:strength="passwordStrength"
:centered="true"
/>
</div>
<div class="mb-3">
@ -161,12 +154,14 @@
<script>
import Confirm from "../../components/Confirm.vue";
import TwoFADialog from "../../components/TwoFADialog.vue";
import PasswordStrengthMeter from "../../components/PasswordStrengthMeter.vue";
import zxcvbn from "zxcvbn";
export default {
components: {
Confirm,
TwoFADialog,
PasswordStrengthMeter,
},
data() {
@ -191,19 +186,6 @@ export default {
settingsLoaded() {
return this.$parent.$parent.$parent.settingsLoaded;
},
strengthClass() {
if (this.passwordStrength === null) {
return "";
}
const classes = [ "strength-very-weak", "strength-weak", "strength-fair", "strength-good", "strength-strong" ];
return classes[this.passwordStrength] || "";
},
strengthWidth() {
if (this.passwordStrength === null) {
return "0%";
}
return `${(this.passwordStrength + 1) * 20}%`;
},
},
watch: {
@ -296,41 +278,3 @@ export default {
};
</script>
<style lang="scss" scoped>
.password-strength {
margin-top: 0.5rem;
}
.strength-meter {
height: 8px;
width: 85%;
background-color: #e0e0e0;
border-radius: 8px;
overflow: hidden;
}
.strength-meter-fill {
height: 100%;
transition: width 0.3s ease, background-color 0.3s ease;
}
.strength-very-weak {
background-color: #dc3545;
}
.strength-weak {
background-color: #fd7e14;
}
.strength-fair {
background-color: #ffc107;
}
.strength-good {
background-color: #20c997;
}
.strength-strong {
background-color: #28a745;
}
</style>

View File

@ -48,18 +48,7 @@
</div>
<!-- Password strength indicator -->
<div v-if="password && passwordStrength !== null" class="password-strength mt-2">
<div class="strength-meter">
<div
class="strength-meter-fill"
:class="strengthClass"
:style="{ width: strengthWidth }"
/>
</div>
<small v-if="passwordStrength < 3" class="text-warning d-block mt-1">
{{ $t("passwordWeakWarning") }}
</small>
</div>
<PasswordStrengthMeter :password="password" :strength="passwordStrength" />
<div class="form-floating mt-3">
<input
@ -89,8 +78,12 @@
<script>
import zxcvbn from "zxcvbn";
import PasswordStrengthMeter from "../components/PasswordStrengthMeter.vue";
export default {
components: {
PasswordStrengthMeter,
},
data() {
return {
processing: false,
@ -100,21 +93,6 @@ export default {
passwordStrength: null,
};
},
computed: {
strengthClass() {
if (this.passwordStrength === null) {
return "";
}
const classes = [ "strength-very-weak", "strength-weak", "strength-fair", "strength-good", "strength-strong" ];
return classes[this.passwordStrength] || "";
},
strengthWidth() {
if (this.passwordStrength === null) {
return "0%";
}
return `${(this.passwordStrength + 1) * 20}%`;
},
},
watch: {},
mounted() {
// TODO: Check if it is a database setup
@ -210,40 +188,4 @@ export default {
margin: auto;
text-align: center;
}
.password-strength {
margin-top: 0.5rem;
}
.strength-meter {
height: 5px;
background-color: #e0e0e0;
border-radius: 3px;
overflow: hidden;
}
.strength-meter-fill {
height: 100%;
transition: width 0.3s ease, background-color 0.3s ease;
}
.strength-very-weak {
background-color: #dc3545;
}
.strength-weak {
background-color: #fd7e14;
}
.strength-fair {
background-color: #ffc107;
}
.strength-good {
background-color: #20c997;
}
.strength-strong {
background-color: #28a745;
}
</style>