mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
Use validation rule in password form
This commit is contained in:
+15
-16
@@ -1,29 +1,28 @@
|
||||
# See https://pre-commit.com for more information
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
repos:
|
||||
- repo: local
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: pylint
|
||||
- id: pylint
|
||||
name: pylint
|
||||
entry: pylint
|
||||
language: system
|
||||
types: [python]
|
||||
args:
|
||||
[
|
||||
"-rn",
|
||||
"-sn",
|
||||
"--fail-under=5"
|
||||
]
|
||||
- repo: https://github.com/psf/black
|
||||
rev: '24.2.0'
|
||||
args: ["-rn", "-sn", "--fail-under=5"]
|
||||
- repo: https://github.com/psf/black
|
||||
rev: "24.2.0"
|
||||
hooks:
|
||||
- id: black
|
||||
- repo: https://github.com/pre-commit/mirrors-eslint
|
||||
rev: 'v9.0.0-beta.0'
|
||||
- id: black
|
||||
- repo: https://github.com/pre-commit/mirrors-eslint
|
||||
rev: "v9.0.0-beta.0"
|
||||
hooks:
|
||||
- id: eslint
|
||||
- id: eslint
|
||||
files: \.(js|vue)$
|
||||
types: [file]
|
||||
additional_dependencies:
|
||||
- eslint
|
||||
- eslint-plugin-vue
|
||||
- eslint
|
||||
- eslint-plugin-vue
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: "v4.0.0-alpha.8" # Use the sha / tag you want to point at
|
||||
hooks:
|
||||
- id: prettier
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[*.{js,jsx,ts,tsx,vue}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
<template>
|
||||
<div>
|
||||
<template v-if="withPasswordCheck">
|
||||
<label class="m-label">{{ $gettext('Current password') }}</label>
|
||||
<label class="m-label">{{ $gettext("Current password") }}</label>
|
||||
<v-text-field
|
||||
v-model="account.currentPassword"
|
||||
autocomplete="new-password"
|
||||
variant="outlined"
|
||||
type="password"
|
||||
density="compact"
|
||||
validate-on="submit"
|
||||
:rules="disabled ? [] : [rules.required]"
|
||||
:rules="disabled ? [] : [rules.required, validPassword]"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</template>
|
||||
@@ -73,58 +74,58 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="js">
|
||||
import { useBusStore } from '@/stores'
|
||||
import accountsApi from '@/api/accounts'
|
||||
import accountApi from '@/api/account'
|
||||
import { computed } from 'vue'
|
||||
import rules from '@/plugins/rules'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useBusStore } from "@/stores";
|
||||
import accountsApi from "@/api/accounts";
|
||||
import accountApi from "@/api/account";
|
||||
import { computed } from "vue";
|
||||
import rules from "@/plugins/rules";
|
||||
import { useGettext } from "vue3-gettext";
|
||||
|
||||
const { $gettext } = useGettext()
|
||||
const busStore = useBusStore()
|
||||
const { $gettext } = useGettext();
|
||||
const busStore = useBusStore();
|
||||
const props = defineProps({
|
||||
modelValue: { type: Object, default: null },
|
||||
withPasswordCheck: { type: Boolean, default: false },
|
||||
disabled: { type: Boolean, default: false },
|
||||
editing: { type: Boolean, default: false },
|
||||
formErrors: { type: Object, required: false, default: () => {} },
|
||||
})
|
||||
});
|
||||
|
||||
const account = computed(() => props.modelValue)
|
||||
const account = computed(() => props.modelValue);
|
||||
|
||||
const isRuleActive = computed(
|
||||
() => !props.disabled && (!props.editing || account.value.password)
|
||||
)
|
||||
() => !props.disabled && (!props.editing || account.value.password),
|
||||
);
|
||||
|
||||
const passwordConfirmationRules = (value) =>
|
||||
value === account.value.password || $gettext('Password mismatch')
|
||||
value === account.value.password || $gettext("Password mismatch");
|
||||
|
||||
function copyPassword() {
|
||||
navigator.clipboard.writeText(account.value.password).then(() => {
|
||||
busStore.displayNotification({
|
||||
msg: $gettext('Password copied to clipboard'),
|
||||
})
|
||||
})
|
||||
msg: $gettext("Password copied to clipboard"),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function updatePassword(value) {
|
||||
if (value) {
|
||||
accountsApi.getRandomPassword().then((resp) => {
|
||||
account.value.password = resp.data.password
|
||||
})
|
||||
account.value.password = resp.data.password;
|
||||
});
|
||||
} else {
|
||||
account.value.password = null
|
||||
account.value.password_confirmation = null
|
||||
account.value.password = null;
|
||||
account.value.password_confirmation = null;
|
||||
}
|
||||
}
|
||||
|
||||
async function validPassword() {
|
||||
if (account.value.password) {
|
||||
async function validPassword(value) {
|
||||
if (value) {
|
||||
return accountApi
|
||||
.checkPassword(account.value.password)
|
||||
.checkPassword(value)
|
||||
.then(() => true)
|
||||
.catch(() => $gettext('Invalid password'))
|
||||
.catch(() => $gettext("Invalid password"));
|
||||
}
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,20 +1,31 @@
|
||||
<template>
|
||||
<div class="text-subtitle-1 text-grey-darken-1 mb-5" :class="{ 'label--disabled': disabled }">
|
||||
<div
|
||||
class="text-subtitle-1 text-grey-darken-1 mb-5"
|
||||
:class="{ 'label--disabled': disabled }"
|
||||
>
|
||||
<label class="m-label">{{ label }}</label>
|
||||
</div>
|
||||
<div class="d-flex" v-for="(lineChoices, index1) in formatedChoices" :key="index1">
|
||||
<div v-for="(choice, index2) in lineChoices"
|
||||
:key="index2"
|
||||
class="choice rounded pa-10 mr-4 text-center flex-grow-0 mb-4"
|
||||
:class="{ 'choice--disabled': disabled, 'choice--selected': !disabled && currentChoice === choice.value }"
|
||||
@click="selectChoice(choice.value)"
|
||||
>
|
||||
<div
|
||||
v-for="(lineChoices, index1) in formatedChoices"
|
||||
:key="index1"
|
||||
class="d-flex"
|
||||
>
|
||||
<div
|
||||
v-for="(choice, index2) in lineChoices"
|
||||
:key="index2"
|
||||
class="choice rounded pa-10 mr-4 text-center flex-grow-0 mb-4"
|
||||
:class="{
|
||||
'choice--disabled': disabled,
|
||||
'choice--selected': !disabled && currentChoice === choice.value,
|
||||
}"
|
||||
@click="selectChoice(choice.value)"
|
||||
>
|
||||
<v-icon
|
||||
v-if="choice.icon"
|
||||
class="d-block mb-2 mx-auto"
|
||||
:color="iconColor(choice.value)"
|
||||
size="x-large"
|
||||
:icon="choice.icon"
|
||||
v-if="choice.icon"
|
||||
class="d-block mb-2 mx-auto"
|
||||
:color="iconColor(choice.value)"
|
||||
size="x-large"
|
||||
:icon="choice.icon"
|
||||
/>
|
||||
<span class="text-grey-darken-1">{{ choice.label }}</span>
|
||||
</div>
|
||||
@@ -22,80 +33,80 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="js">
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { ref, onMounted, computed } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: [Number, String], default: null },
|
||||
label: { type: String, default: '' },
|
||||
label: { type: String, default: "" },
|
||||
choices: { type: Array, default: () => [] },
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
choicesPerLine: { type: Number, default: undefined },
|
||||
})
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
|
||||
const currentChoice = computed({
|
||||
get() {
|
||||
return props.modelValue
|
||||
return props.modelValue;
|
||||
},
|
||||
set(value) {
|
||||
emit('update:modelValue', value)
|
||||
emit("update:modelValue", value);
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
const formatedChoices = ref([])
|
||||
const formatedChoices = ref([]);
|
||||
|
||||
function formatChoices() {
|
||||
if (props.choicesPerLine) {
|
||||
let sliceIndex = 0
|
||||
let sliceIndex = 0;
|
||||
while (sliceIndex < props.choices.length) {
|
||||
const result = props.choices.slice(
|
||||
sliceIndex,
|
||||
sliceIndex + props.choicesPerLine
|
||||
)
|
||||
formatedChoices.value.push(result)
|
||||
sliceIndex += props.choicesPerLine
|
||||
sliceIndex + props.choicesPerLine,
|
||||
);
|
||||
formatedChoices.value.push(result);
|
||||
sliceIndex += props.choicesPerLine;
|
||||
}
|
||||
} else {
|
||||
formatedChoices.value.push(props.choices)
|
||||
formatedChoices.value.push(props.choices);
|
||||
}
|
||||
}
|
||||
|
||||
function iconColor(value) {
|
||||
return !props.disabled && value === currentChoice.value ? 'primary' : ''
|
||||
return !props.disabled && value === currentChoice.value ? "primary" : "";
|
||||
}
|
||||
function selectChoice(value) {
|
||||
if (props.disabled) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
currentChoice.value = value
|
||||
currentChoice.value = value;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
formatChoices()
|
||||
})
|
||||
formatChoices();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.choice {
|
||||
flex-basis: 200px;
|
||||
background-color: #f2f5f7;
|
||||
border: 1px solid #dbdddf;
|
||||
cursor: pointer;
|
||||
.choice {
|
||||
flex-basis: 200px;
|
||||
background-color: #f2f5f7;
|
||||
border: 1px solid #dbdddf;
|
||||
cursor: pointer;
|
||||
|
||||
&--selected {
|
||||
border-color: #046bf8 !important;
|
||||
}
|
||||
&--selected {
|
||||
border-color: #046bf8 !important;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
cursor: unset;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
.label--disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
&--disabled {
|
||||
cursor: unset;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
.label--disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user