diff options
author | vnugent <public@vaughnnugent.com> | 2024-01-20 23:49:29 -0500 |
---|---|---|
committer | vnugent <public@vaughnnugent.com> | 2024-01-20 23:49:29 -0500 |
commit | 6cb7da37824d02a1898d08d0f9495c77fde4dd1d (patch) | |
tree | 95e37ea3c20f416d6a205ee4ab050c307b18eafe /front-end/src/components/global |
inital commit
Diffstat (limited to 'front-end/src/components/global')
-rw-r--r-- | front-end/src/components/global/ConfirmPrompt.vue | 65 | ||||
-rw-r--r-- | front-end/src/components/global/Dialog.vue | 61 | ||||
-rw-r--r-- | front-end/src/components/global/OtpInput.vue | 54 | ||||
-rw-r--r-- | front-end/src/components/global/PasswordPrompt.vue | 118 |
4 files changed, 298 insertions, 0 deletions
diff --git a/front-end/src/components/global/ConfirmPrompt.vue b/front-end/src/components/global/ConfirmPrompt.vue new file mode 100644 index 0000000..c8dc944 --- /dev/null +++ b/front-end/src/components/global/ConfirmPrompt.vue @@ -0,0 +1,65 @@ +<script setup lang="ts"> +import { defaultTo } from 'lodash-es' +import { ref } from 'vue' +import { onClickOutside } from '@vueuse/core' +import { useConfirm } from '@vnuge/vnlib.browser' +import Dialog from './Dialog.vue' + +interface MessageType{ + title: string, + text: string +} + +//Use component side of confirm +const { isRevealed, confirm, cancel, onReveal } = useConfirm() + +const dialog = ref(null) +const message = ref<MessageType | undefined>() + +//Cancel prompt when user clicks outside of dialog, only when its open +onClickOutside(dialog, () => isRevealed.value ? cancel() : null) + +//Set message on reveal +onReveal(m => message.value = defaultTo(m, {})); + + +</script> +<template> + <!-- Main modal --> + <Dialog title="Confirm?" :open="isRevealed" @cancel="cancel"> + <template #body> + <div class="p-4 space-y-4 md:p-5"> + <p class="text-base leading-relaxed text-gray-500 dark:text-gray-400"> + {{ message?.text }} + </p> + </div> + <!-- Modal footer --> + <div class="flex items-center justify-end p-4 border-t border-gray-200 rounded-b md:p-5 dark:border-gray-600"> + <button + @click="confirm()" + type="button" + class="btn blue"> + Confirm + </button> + + <button + @click="cancel()" + type="button" + class="btn light"> + Cancel + </button> + </div> + </template> + </Dialog> + +</template> + +<style scoped lang="scss"> +.modal-entry{ + @apply hidden overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full; +} + +.modal-content-container{ + @apply text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white; +} +</style>
\ No newline at end of file diff --git a/front-end/src/components/global/Dialog.vue b/front-end/src/components/global/Dialog.vue new file mode 100644 index 0000000..949e750 --- /dev/null +++ b/front-end/src/components/global/Dialog.vue @@ -0,0 +1,61 @@ +<script setup lang="ts"> +import { noop } from 'lodash-es' +import { ref, toRefs } from 'vue' +import { Dialog } from '@headlessui/vue' +import { onClickOutside, get } from '@vueuse/core' + +const emit = defineEmits(['cancel']) +const props = defineProps<{ title: string | undefined, open: boolean }>() + +const { open, title } = toRefs(props) + +const dialog = ref(null) +const cancel = () => emit('cancel') + +//Cancel prompt when user clicks outside of dialog, only when its open +onClickOutside(dialog, () => get(open) ? cancel() : noop()) + +</script> +<template> + <!-- Main modal --> + <Dialog :open="open" id="static-modal" data-modal-backdrop="static" tabindex="-1" + class="overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-10 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full"> + <div class="fixed inset-0 bg-black/30" aria-hidden="true" /> + + <div class="relative w-full max-w-xl max-h-full p-4 mx-auto mt-2 md:mt-32"> + <!-- Modal content --> + <div class="relative bg-white rounded shadow dark:bg-gray-700" ref="dialog"> + <!-- Modal header --> + <div class="flex items-center justify-between p-3 border-b rounded-t md:px-5 dark:border-gray-600"> + <h3 class="text-xl font-semibold text-gray-900 dark:text-white"> + {{ title }} + </h3> + <button @click="cancel()" type="button" + class="inline-flex items-center justify-center w-8 h-8 text-sm text-gray-400 bg-transparent rounded-lg hover:bg-gray-200 hover:text-gray-900 ms-auto dark:hover:bg-gray-600 dark:hover:text-white" + data-modal-hide="static-modal"> + <svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" + viewBox="0 0 14 14"> + <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" + d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" /> + </svg> + <span class="sr-only">Close modal</span> + </button> + </div> + + <!-- Modal body --> + <slot name="body" /> + + </div> + </div> + </Dialog> +</template> + +<style scoped lang="scss"> +.modal-entry { + @apply hidden overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full; +} + +.modal-content-container { + @apply text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white; +} +</style>
\ No newline at end of file diff --git a/front-end/src/components/global/OtpInput.vue b/front-end/src/components/global/OtpInput.vue new file mode 100644 index 0000000..59f6fa1 --- /dev/null +++ b/front-end/src/components/global/OtpInput.vue @@ -0,0 +1,54 @@ +<script setup lang="ts"> +import { toRefs } from 'vue'; +import { IMfaFlowContinuiation, apiCall, useMessage, useWait } from '@vnuge/vnlib.browser'; +import { toSafeInteger } from 'lodash-es'; +import VOtpInput from 'vue3-otp-input'; + +const emit = defineEmits(['clear']) + +const props = defineProps<{ + upgrade: IMfaFlowContinuiation +}>() + +const { upgrade } = toRefs(props) +const { waiting } = useWait(); +const { onInput } = useMessage(); + +const SubimitTotp = (code: string) => { + + //If a request is still pending, do nothing + if (waiting.value) { + return + } + + apiCall(async ({ toaster }) => { + //Submit totp code + const res = await upgrade.value.submit({ code: toSafeInteger(code) }) + res.getResultOrThrow() + + emit('clear') + + // Push a new toast message + toaster.general.success({ + title: 'Success', + text: 'You have been logged in', + }) + }) +} + +</script> + +<template> + <div id="totp-login-form"> + <div class="flex"> + <div class="mx-auto mt-4"> + <VOtpInput class="otp-input" input-type="letter-numeric" :is-disabled="waiting" separator="" + input-classes="primary input rounded" :num-inputs="6" value="" @on-change="onInput" + @on-complete="SubimitTotp" /> + </div> + </div> + </div> +</template> + +<style lang="scss"> +</style>
\ No newline at end of file diff --git a/front-end/src/components/global/PasswordPrompt.vue b/front-end/src/components/global/PasswordPrompt.vue new file mode 100644 index 0000000..a9a5fca --- /dev/null +++ b/front-end/src/components/global/PasswordPrompt.vue @@ -0,0 +1,118 @@ +<script setup lang="ts"> +import { defaultTo, noop } from 'lodash-es' +import { reactive, ref } from 'vue' +import { get, onClickOutside } from '@vueuse/core' +import { usePassConfirm, useVuelidateWrapper } from '@vnuge/vnlib.browser' +import { useVuelidate } from '@vuelidate/core' +import { helpers, required, maxLength } from '@vuelidate/validators' +import Dialog from './Dialog.vue' + +interface MessageType{ + title: string, + text: string +} + +//Use component side of pw prompt +const { isRevealed, confirm, cancel, onReveal } = usePassConfirm() + +const dialog = ref(null) +const message = ref<MessageType | undefined>() + +const pwState = reactive({ password: '' }) + +const rules = { + password: { + required: helpers.withMessage('Please enter your password', required), + maxLength: helpers.withMessage('Password must be less than 100 characters', maxLength(100)) + } +} + +const v$ = useVuelidate(rules, pwState, { $lazy: true }) + +//Wrap validator so we an display error message on validation, defaults to the form toaster +const { validate } = useVuelidateWrapper(v$ as any); + +const formSubmitted = async function () { + //Calls validate on the vuelidate instance + if (!await validate()) { + return + } + + //Store pw copy + const password = v$.value.password.$model; + + //Clear the password form + v$.value.password.$model = ''; + v$.value.$reset(); + + //Pass the password to the confirm function + confirm({ password }); +} + +const close = function () { + // Clear the password form + v$.value.password.$model = ''; + v$.value.$reset(); + + //Close prompt + cancel(null); +} + +//Cancel prompt when user clicks outside of dialog, only when its open +onClickOutside(dialog, () => get(isRevealed) ? close() : noop()) + +//Set message on reveal +onReveal(m => message.value = defaultTo(m, {})); + +</script> +<template> + <!-- Main modal --> + <Dialog title="Enter Password" :open="isRevealed" @cancel="cancel"> + <template #body> + <div class="p-4 space-y-4 md:p-5"> + <p class="text-base leading-relaxed text-gray-500 dark:text-gray-400"> + Please enter your password in order to continue. + </p> + <form class="mb-6" @submit="formSubmitted"> + <label for="password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Password</label> + <input + type="password" + id="password" + v-model.trim="v$.password.$model" + class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" + placeholder="•••••••••" + required + > + </form> + </div> + <!-- Modal footer --> + <div class="flex items-center justify-end p-4 border-t border-gray-200 rounded-b md:p-5 dark:border-gray-600"> + <button + @click="formSubmitted" + type="button" + class="btn blue" + > + Confirm + </button> + + <button + @click="close" + type="button" + class="btn light"> + Cancel + </button> + </div> + </template> + </Dialog> + +</template> + +<style scoped lang="scss"> +.modal-entry{ + @apply hidden overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full; +} + +.modal-content-container{ + @apply text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white; +} +</style>
\ No newline at end of file |