finish the wizard UI
This commit is contained in:
@ -6,9 +6,10 @@
|
|||||||
/*padding: 2rem;*/
|
/*padding: 2rem;*/
|
||||||
/*font-weight: normal;*/
|
/*font-weight: normal;*/
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
display: flex;
|
width: 100vw;
|
||||||
flex-direction: column;
|
/*display: flex;*/
|
||||||
justify-content: center;
|
/*flex-direction: column;*/
|
||||||
|
/*justify-content: center;*/
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ const lastName = ref('');
|
|||||||
const email = ref('');
|
const email = ref('');
|
||||||
const newsletterConsent = ref(true);
|
const newsletterConsent = ref(true);
|
||||||
const isOpen = ref(true);
|
const isOpen = ref(true);
|
||||||
|
const formSubmitted = ref(false);
|
||||||
function submitForm() {
|
function submitForm() {
|
||||||
console.log('Form submitted');
|
console.log('Form submitted');
|
||||||
console.log('First name:', firstName.value);
|
console.log('First name:', firstName.value);
|
||||||
@ -32,6 +33,7 @@ function submitForm() {
|
|||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
console.error('Error submitting email:', e);
|
console.error('Error submitting email:', e);
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
|
formSubmitted.value = true;
|
||||||
closeModal();
|
closeModal();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -57,6 +59,9 @@ async function submitEmail() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
function closeModal() {
|
function closeModal() {
|
||||||
|
if (!formSubmitted.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
isOpen.value = false
|
isOpen.value = false
|
||||||
}
|
}
|
||||||
function openModal() {
|
function openModal() {
|
||||||
@ -66,13 +71,13 @@ function openModal() {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="inset-0 flex items-center justify-center">
|
<div class="inset-0 flex items-center justify-center">
|
||||||
<button
|
<!-- <button-->
|
||||||
type="button"
|
<!-- type="button"-->
|
||||||
@click="openModal"
|
<!-- @click="openModal"-->
|
||||||
class="rounded-md bg-black/20 px-4 py-2 text-sm font-medium text-white hover:bg-black/30 focus:outline-none focus-visible:ring-2 focus-visible:ring-white/75"
|
<!-- class="rounded-md bg-black/20 px-4 py-2 text-sm font-medium text-white hover:bg-black/30 focus:outline-none focus-visible:ring-2 focus-visible:ring-white/75"-->
|
||||||
>
|
<!-- >-->
|
||||||
Open dialog
|
<!-- Open dialog-->
|
||||||
</button>
|
<!-- </button>-->
|
||||||
</div>
|
</div>
|
||||||
<TransitionRoot appear :show="isOpen" as="template">
|
<TransitionRoot appear :show="isOpen" as="template">
|
||||||
<Dialog as="div" @close="closeModal" class="relative z-10">
|
<Dialog as="div" @close="closeModal" class="relative z-10">
|
||||||
|
@ -5,6 +5,10 @@ import {
|
|||||||
ListboxButton,
|
ListboxButton,
|
||||||
ListboxOptions,
|
ListboxOptions,
|
||||||
ListboxOption,
|
ListboxOption,
|
||||||
|
RadioGroup,
|
||||||
|
RadioGroupLabel,
|
||||||
|
RadioGroupOption,
|
||||||
|
TransitionRoot
|
||||||
} from "@headlessui/vue";
|
} from "@headlessui/vue";
|
||||||
import { CheckIcon, ChevronUpDownIcon } from '@heroicons/vue/20/solid';
|
import { CheckIcon, ChevronUpDownIcon } from '@heroicons/vue/20/solid';
|
||||||
|
|
||||||
@ -25,11 +29,11 @@ const questions = ref<Question[]>([
|
|||||||
clarification: 'Some authentication tools are better suited for specific types of applications.',
|
clarification: 'Some authentication tools are better suited for specific types of applications.',
|
||||||
answerType: 'checkbox',
|
answerType: 'checkbox',
|
||||||
answer: [],
|
answer: [],
|
||||||
options: ['Web', 'Mobile', 'Desktop', 'SaaS', 'Other']
|
options: ['Web', 'Mobile', 'Desktop', 'SaaS', 'Embedded', 'Other']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
question: 'How many users do you expect to have in the short term and long term?',
|
question: 'How many users do you plan to support?',
|
||||||
clarification: 'This will help determine the scalability requirements of the authentication tool.',
|
clarification: 'This will help determine the scalability requirements of the authentication tool.',
|
||||||
answerType: 'select',
|
answerType: 'select',
|
||||||
answer: '',
|
answer: '',
|
||||||
@ -71,9 +75,9 @@ const questions = ref<Question[]>([
|
|||||||
id: 7,
|
id: 7,
|
||||||
question: 'Do you need to support third-party logins via OIDC or SAML?',
|
question: 'Do you need to support third-party logins via OIDC or SAML?',
|
||||||
clarification: 'Supporting third-party logins can simplify the sign-in process for users who already have accounts with these providers.',
|
clarification: 'Supporting third-party logins can simplify the sign-in process for users who already have accounts with these providers.',
|
||||||
answerType: 'select',
|
answerType: 'radio',
|
||||||
answer: '',
|
answer: '',
|
||||||
options: ['OIDC', 'SAML', 'Neither']
|
options: ['OIDC', 'SAML', 'Both', 'Neither']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 8,
|
id: 8,
|
||||||
@ -87,9 +91,9 @@ const questions = ref<Question[]>([
|
|||||||
id: 9,
|
id: 9,
|
||||||
question: 'What level of customization do you require for the MFA features?',
|
question: 'What level of customization do you require for the MFA features?',
|
||||||
clarification: 'Some authentication tools offer more customization options for MFA, such as custom branding, custom workflows, or custom policies.',
|
clarification: 'Some authentication tools offer more customization options for MFA, such as custom branding, custom workflows, or custom policies.',
|
||||||
answerType: 'select',
|
answerType: 'radio',
|
||||||
answer: '',
|
answer: '',
|
||||||
options: ['Basic', 'Intermediate', 'Advanced']
|
options: ['Basic', 'Intermediate', 'Advanced', 'No Customization']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 10,
|
id: 10,
|
||||||
@ -125,14 +129,6 @@ const questions = ref<Question[]>([
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 14,
|
id: 14,
|
||||||
question: 'How often do you anticipate needing to upgrade or update the authentication tool?',
|
|
||||||
clarification: 'Regular updates can help keep your authentication tool secure and up-to-date with the latest features and standards.',
|
|
||||||
answerType: 'select',
|
|
||||||
answer: '',
|
|
||||||
options: ['Daily', 'Weekly', 'Monthly', 'Quarterly', 'Annually', 'Never']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 15,
|
|
||||||
question: 'Do you have any preferences for user interface and experience?',
|
question: 'Do you have any preferences for user interface and experience?',
|
||||||
clarification: 'The user interface and experience of an authentication tool can impact user adoption and satisfaction.',
|
clarification: 'The user interface and experience of an authentication tool can impact user adoption and satisfaction.',
|
||||||
answerType: 'radio',
|
answerType: 'radio',
|
||||||
@ -156,44 +152,115 @@ function nextQuestion() {
|
|||||||
currentQuestionIndex.value++;
|
currentQuestionIndex.value++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const onLastQuestion = computed(() => {
|
||||||
|
return currentQuestionIndex.value >= questions.value.length - 1;
|
||||||
|
});
|
||||||
|
|
||||||
// should return true if we are on the last button and the answer is not filled out
|
// should return true if we are on the last button and the answer is not filled out
|
||||||
const isNextButtonDisabled = computed(() => {
|
const isNextButtonDisabled = computed(() => {
|
||||||
const onLastQuestion = currentQuestionIndex.value >= questions.value.length - 1;
|
|
||||||
if (questions.value[currentQuestionIndex.value].answerType === 'checkbox') {
|
if (questions.value[currentQuestionIndex.value].answerType === 'checkbox') {
|
||||||
return !(questions.value[currentQuestionIndex.value].answer as string[]).length || onLastQuestion;
|
return !(questions.value[currentQuestionIndex.value].answer as string[]).length;
|
||||||
}
|
}
|
||||||
if (questions.value[currentQuestionIndex.value].answerType === 'select') {
|
if (questions.value[currentQuestionIndex.value].answerType === 'select') {
|
||||||
return (questions.value[currentQuestionIndex.value].answer as string).trim().length <= 0 || onLastQuestion;
|
return (questions.value[currentQuestionIndex.value].answer as string).trim().length <= 0;
|
||||||
}
|
}
|
||||||
if (questions.value[currentQuestionIndex.value].answerType === 'radio') {
|
if (questions.value[currentQuestionIndex.value].answerType === 'radio') {
|
||||||
return (questions.value[currentQuestionIndex.value].answer as string).trim().length <= 0 || onLastQuestion;
|
return (questions.value[currentQuestionIndex.value].answer as string).trim().length <= 0;
|
||||||
}
|
}
|
||||||
return onLastQuestion;
|
return onLastQuestion.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function done() {
|
||||||
|
alert('done');
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- <h1 class="">Authentication Tool Wizard</h1>-->
|
<div class="relative w-full flex flex-col justify-center items-center" v-for="(question, index) in questions" :key="index">
|
||||||
<div class="flex flex-col">
|
<TransitionRoot
|
||||||
<h2 class="text-2xl">{{ questions[currentQuestionIndex].question }}</h2>
|
appear
|
||||||
<p class="text-gray-500" v-if="questions[currentQuestionIndex].clarification">{{ questions[currentQuestionIndex].clarification }}</p>
|
:show="currentQuestionIndex === index"
|
||||||
<div class="mt-10 flex justify-center z-10">
|
as="template"
|
||||||
<div v-if="questions[currentQuestionIndex].answerType === 'radio'">
|
enter="transition-opacity ease-linear duration-300"
|
||||||
<div v-for="(option, index) in questions[currentQuestionIndex].options" :key="index">
|
enter-from="opacity-0"
|
||||||
<input type="radio" :id="option" :value="option" v-model="questions[currentQuestionIndex].answer" />
|
enter-to="opacity-100"
|
||||||
<label :for="option">{{ option }}</label>
|
leave="transition-opacity ease-linear duration-300"
|
||||||
</div>
|
leave-from="opacity-100"
|
||||||
</div>
|
leave-to="opacity-0"
|
||||||
<div v-else-if="questions[currentQuestionIndex].answerType === 'checkbox'">
|
class="absolute"
|
||||||
<Listbox v-model="questions[currentQuestionIndex].answer" multiple>
|
|
||||||
<ListboxButton
|
|
||||||
class="relative cursor-default rounded-lg bg-white py-2 pl-3 pr-10 text-left shadow-md focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white/75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300 sm:text-sm"
|
|
||||||
>
|
>
|
||||||
<span>{{ !!(questions[currentQuestionIndex].answer as string[]).length ? (questions[currentQuestionIndex].answer as string[]).join(', ') : 'Select options'}}</span>
|
<div class="flex flex-col">
|
||||||
|
<h2 class="text-2xl">{{ question.question }}</h2>
|
||||||
|
<p class="text-gray-500" v-if="question.clarification">{{ question.clarification }}</p>
|
||||||
|
<div class="mt-10 flex justify-center z-10">
|
||||||
|
<div class="mx-auto w-full max-w-md" v-if="question.answerType === 'radio'">
|
||||||
|
|
||||||
|
<RadioGroup v-model="question.answer">
|
||||||
|
<RadioGroupLabel class="sr-only">Options</RadioGroupLabel>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<RadioGroupOption
|
||||||
|
v-for="(option, index) in question.options"
|
||||||
|
as="template"
|
||||||
|
v-slot="{ active, checked }"
|
||||||
|
:key="index"
|
||||||
|
:value="option"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
:class="[
|
||||||
|
active
|
||||||
|
? 'ring-2 ring-white/60 ring-offset-2 ring-offset-amber-300'
|
||||||
|
: '',
|
||||||
|
checked ? 'bg-amber-900/75 text-white ' : 'bg-white ',
|
||||||
|
]"
|
||||||
|
class="relative flex cursor-pointer rounded-lg px-5 py-4 shadow-md focus:outline-none"
|
||||||
|
>
|
||||||
|
<div class="flex w-full items-center justify-between">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="text-sm">
|
||||||
|
<RadioGroupLabel
|
||||||
|
as="p"
|
||||||
|
:class="checked ? 'text-white' : 'text-gray-900'"
|
||||||
|
class="font-medium"
|
||||||
|
>
|
||||||
|
{{ option }}
|
||||||
|
</RadioGroupLabel>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-show="checked" class="shrink-0 text-white">
|
||||||
|
<svg class="h-6 w-6" viewBox="0 0 24 24" fill="none">
|
||||||
|
<circle
|
||||||
|
cx="12"
|
||||||
|
cy="12"
|
||||||
|
r="12"
|
||||||
|
fill="#fff"
|
||||||
|
fill-opacity="0.2"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M7 13l3 3 7-7"
|
||||||
|
stroke="#fff"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</RadioGroupOption>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</RadioGroup>
|
||||||
|
</div>
|
||||||
|
<div class="mx-auto" v-else-if="question.answerType === 'checkbox'">
|
||||||
|
<Listbox v-model="question.answer" multiple>
|
||||||
|
<ListboxButton
|
||||||
|
class="flex justify-between items-center cursor-default rounded-lg bg-white py-2 pl-3 text-left shadow-md focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white/75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300 sm:text-sm"
|
||||||
|
style="width: 250px"
|
||||||
|
>
|
||||||
|
<span>{{ !!(question.answer as string[]).length ? (question.answer as string[]).join(', ') : 'Select options'}}</span>
|
||||||
<span
|
<span
|
||||||
class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"
|
class="pointer-events-none flex items-center pr-2"
|
||||||
>
|
>
|
||||||
<ChevronUpDownIcon
|
<ChevronUpDownIcon
|
||||||
class="h-5 w-5 text-gray-400"
|
class="h-5 w-5 text-gray-400"
|
||||||
@ -206,7 +273,7 @@ const isNextButtonDisabled = computed(() => {
|
|||||||
>
|
>
|
||||||
<ListboxOption
|
<ListboxOption
|
||||||
v-slot="{ active, selected }"
|
v-slot="{ active, selected }"
|
||||||
v-for="(option, index) in questions[currentQuestionIndex].options"
|
v-for="(option, index) in question.options"
|
||||||
:key="index"
|
:key="index"
|
||||||
:value="option"
|
:value="option"
|
||||||
as="template"
|
as="template"
|
||||||
@ -233,12 +300,12 @@ const isNextButtonDisabled = computed(() => {
|
|||||||
</ListboxOptions>
|
</ListboxOptions>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="questions[currentQuestionIndex].answerType === 'select'">
|
<div v-else-if="question.answerType === 'select'">
|
||||||
<Listbox v-model="questions[currentQuestionIndex].answer">
|
<Listbox v-model="question.answer">
|
||||||
<ListboxButton
|
<ListboxButton
|
||||||
class="relative cursor-default rounded-lg bg-white py-2 pl-3 pr-10 text-left shadow-md focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white/75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300 sm:text-sm"
|
class="relative cursor-default rounded-lg bg-white py-2 pl-3 pr-10 text-left shadow-md focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white/75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300 sm:text-sm"
|
||||||
>
|
>
|
||||||
<span>{{!!questions[currentQuestionIndex].answer ? questions[currentQuestionIndex].answer : 'Select an option'}}</span>
|
<span>{{!!question.answer ? question.answer : 'Select an option'}}</span>
|
||||||
<span
|
<span
|
||||||
class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"
|
class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"
|
||||||
>
|
>
|
||||||
@ -253,7 +320,7 @@ const isNextButtonDisabled = computed(() => {
|
|||||||
>
|
>
|
||||||
<ListboxOption
|
<ListboxOption
|
||||||
v-slot="{ active, selected }"
|
v-slot="{ active, selected }"
|
||||||
v-for="(option, index) in questions[currentQuestionIndex].options"
|
v-for="(option, index) in question.options"
|
||||||
:key="index"
|
:key="index"
|
||||||
:value="option"
|
:value="option"
|
||||||
as="template"
|
as="template"
|
||||||
@ -291,12 +358,22 @@ const isNextButtonDisabled = computed(() => {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="nav-button ml-2 rounded-md border border-transparent bg-amber-500 px-4 py-2 text-sm font-medium text-orange-950 hover:bg-amber-300 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
class="nav-button ml-2 rounded-md border border-transparent bg-amber-500 px-4 py-2 text-sm font-medium text-orange-950 hover:bg-amber-300 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||||
|
v-show="currentQuestionIndex < questions.length - 1"
|
||||||
@click="nextQuestion"
|
@click="nextQuestion"
|
||||||
:disabled="isNextButtonDisabled">
|
:disabled="isNextButtonDisabled || onLastQuestion">
|
||||||
<span>Next</span>
|
<span>Next</span>
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
class="nav-button ml-2 rounded-md border border-transparent bg-amber-500 px-4 py-2 text-sm font-medium text-orange-950 hover:bg-amber-300 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||||
|
v-show="currentQuestionIndex >= questions.length - 1"
|
||||||
|
@click="done"
|
||||||
|
:disabled="isNextButtonDisabled && onLastQuestion">
|
||||||
|
<span>See Results</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</TransitionRoot>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@ -6,11 +6,12 @@ import WizardQuestions from "@/components/wizard-questions.vue";
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="h-full w-full flex flex-col justify-center items-center">
|
<div class="h-full w-full flex flex-col justify-center items-center">
|
||||||
<enter-details-modal />
|
<!-- <div class="h-full w-full flex flex-col justify-center items-center">-->
|
||||||
|
<enter-details-modal class="absolute" />
|
||||||
|
|
||||||
<!-- <iframe src="https://mw.sa.vin/index.php/lists/zq107l51gw5fc/subscribe?output=embed&width=400&height=400" width="400" height="400" frameborder="0" scrolling="yes"></iframe>-->
|
<!-- <iframe src="https://mw.sa.vin/index.php/lists/zq107l51gw5fc/subscribe?output=embed&width=400&height=400" width="400" height="400" frameborder="0" scrolling="yes"></iframe>-->
|
||||||
|
|
||||||
<wizard-questions class="" />
|
<wizard-questions />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
Reference in New Issue
Block a user