tethys.backend/resources/js/Components/SimplePasswordMeter/password-meter.vue
Arno Kaimbacher 36cd7a757b
All checks were successful
CI / container-job (push) Successful in 41s
feat: Integrate official drive_provider, update user profile features & UI improvements
- adonisrc.ts: Load official drive_provider and unload custom driver_provider.
- packages.json: Add @headlessui/vue dependency for tab components.
- AvatarController.ts: Rewrite avatar generation logic to always return the same avatar per user.
- auth/UserController.ts: Add profile and profileUpdate methods to support user profile editing.
- Submitter/datasetController.ts & app/models/file.ts: Adapt code to use the official drive_provider.
- app/models/user.ts: Introduce “isAdmin” getter.
- config/drive.ts: Create new configuration for the official drive_provider.
- providers/vinejs_provider.ts: Adapt allowedExtensions control to use provided options or database enabled extensions.
- resource/js/app.ts: Load default Head and Link components.
- resources/js/menu.ts: Add settings-profile.edit menu point.
- resources/js/Components/action-message.vue: Add new component for improved user feedback after form submissions.
- New avatar-input.vue component: Enable profile picture selection.
- Components/CardBox.vue: Alter layout to optionally show HeaderIcon in title bar.
- FormControl.vue: Define a readonly prop for textareas.
- Improve overall UI with updates to NavBar.vue, UserAvatar.vue, UserAvatarCurrentUser.vue, and add v-model support to password-meter.vue.
- Remove profile editing logic from AccountInfo.vue and introduce new profile components (show.vue, update-password-form.vue, update-profile-information.vue).
- app.edge: Modify page (add @inertiaHead tag) for better meta management.
- routes.ts: Add new routes for editing user profiles.
- General npm updates.
2025-02-27 16:24:25 +01:00

147 lines
4.7 KiB
Vue

<script lang="ts" setup>
import { computed } from 'vue';
import { checkStrength } from './logic/index';
import { mdiFormTextboxPassword } from '@mdi/js';
import FormField from '@/Components/FormField.vue';
import FormControl from '@/Components/FormControl.vue';
// Define props
const props = defineProps<{
modelValue: string;
errors: Partial<Record<"new_password" | "old_password" | "confirm_password", string>>;
}>();
const emit = defineEmits(['update:modelValue', 'score']);
// // A local reactive variable for password input
// const localPassword = ref(props.modelValue);
// // Watch localPassword and emit changes back to the parent
// watch(localPassword, (newValue) => {
// emit('update:modelValue', newValue);
// });
const localPassword = computed({
get: () => props.modelValue,
set: (value) => {
emit('update:modelValue', value);
// const { score } = checkStrength(localPassword.value);
// emit('score', score);
},
});
type PasswordMetrics = {
score: number;
scoreLabel: string | null;
hints: string[];
isSecure: boolean;
};
// Combined computed property for password strength metrics
const passwordMetrics = computed<PasswordMetrics>(() => {
if (!localPassword.value) {
return {
score: 0,
scoreLabel: null,
hints: [],
isSecure: false
};
}
const { score, scoreLabel, hints } = checkStrength(localPassword.value);
emit('score', score);
return {
score,
scoreLabel,
hints,
isSecure: score >= 4
};
});
</script>
<template>
<!-- Password input Form -->
<FormField label="New password" help="Required. New password" :class="{'text-red-400': errors.new_password }">
<FormControl v-model="localPassword" :icon="mdiFormTextboxPassword" name="new_password" type="password" required
:error="errors.new_password">
<!-- Secure Icon -->
<template #right>
<span v-if="passwordMetrics.isSecure"
class="inline-flex justify-center items-center w-10 h-full absolute inset-y-0 right-2 pointer-events-none">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-green-600" viewBox="0 0 20 20"
fill="currentColor">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-10.707a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd" />
</svg>
</span>
</template>
<div class="text-red-400 text-sm" v-if="errors.new_password">
{{ errors.new_password }}
</div>
</FormControl>
<!-- Score Display -->
<div class="text-gray-700 text-sm">
{{ passwordMetrics.score }} / 6 points max
</div>
</FormField>
<!-- Password Strength Bar -->
<div class="po-password-strength-bar w-full h-2 rounded transition-all duration-200 mb-4"
:class="passwordMetrics.scoreLabel" :style="{ width: `${(passwordMetrics.score / 6) * 100}%` }"
role="progressbar" :aria-valuenow="passwordMetrics.score" aria-valuemin="0" aria-valuemax="6"
:aria-label="`Password strength: ${passwordMetrics.scoreLabel || 'unknown'}`">
</div>
<!-- Hint Message -->
<div v-if="passwordMetrics.hints.length > 0"
class="hint-message bg-gray-50 border-l-4 border-gray-300 text-gray-600 p-3 mb-4 rounded-md shadow-sm">
<p class="font-medium text-sm mb-2">To improve your password strength:</p>
<ul class="list-disc list-inside text-xs space-y-1">
<li v-for="(hint, index) in passwordMetrics.hints" :key="index"
class="hover:text-gray-800 transition-colors">
{{ hint }}
</li>
</ul>
</div>
<!-- Score Display -->
<!-- <div class="text-gray-700 text-sm">
{{ passwordMetrics.score }} / 6 points max
</div> -->
</template>
<style lang="css" scoped>
.po-password-strength-bar {
border-radius: 2px;
transition: all 0.2s linear;
height: 10px;
margin-top: 8px;
}
.po-password-strength-bar.risky {
background-color: #f95e68;
width: 10%;
}
.po-password-strength-bar.guessable {
background-color: #fb964d;
width: 32.5%;
}
.po-password-strength-bar.weak {
background-color: #fdd244;
width: 55%;
}
.po-password-strength-bar.safe {
background-color: #b0dc53;
width: 77.5%;
}
.po-password-strength-bar.secure,
.po-password-strength-bar.safe-secure,
.po-password-strength-bar.optimal {
background-color: #35cc62;
width: 100%;
}
</style>