|
|
|
@ -5,6 +5,10 @@ import {
|
|
|
|
|
ListboxButton,
|
|
|
|
|
ListboxOptions,
|
|
|
|
|
ListboxOption,
|
|
|
|
|
RadioGroup,
|
|
|
|
|
RadioGroupLabel,
|
|
|
|
|
RadioGroupOption,
|
|
|
|
|
TransitionRoot
|
|
|
|
|
} from "@headlessui/vue";
|
|
|
|
|
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.',
|
|
|
|
|
answerType: 'checkbox',
|
|
|
|
|
answer: [],
|
|
|
|
|
options: ['Web', 'Mobile', 'Desktop', 'SaaS', 'Other']
|
|
|
|
|
options: ['Web', 'Mobile', 'Desktop', 'SaaS', 'Embedded', 'Other']
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
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.',
|
|
|
|
|
answerType: 'select',
|
|
|
|
|
answer: '',
|
|
|
|
@ -71,9 +75,9 @@ const questions = ref<Question[]>([
|
|
|
|
|
id: 7,
|
|
|
|
|
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.',
|
|
|
|
|
answerType: 'select',
|
|
|
|
|
answerType: 'radio',
|
|
|
|
|
answer: '',
|
|
|
|
|
options: ['OIDC', 'SAML', 'Neither']
|
|
|
|
|
options: ['OIDC', 'SAML', 'Both', 'Neither']
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 8,
|
|
|
|
@ -87,9 +91,9 @@ const questions = ref<Question[]>([
|
|
|
|
|
id: 9,
|
|
|
|
|
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.',
|
|
|
|
|
answerType: 'select',
|
|
|
|
|
answerType: 'radio',
|
|
|
|
|
answer: '',
|
|
|
|
|
options: ['Basic', 'Intermediate', 'Advanced']
|
|
|
|
|
options: ['Basic', 'Intermediate', 'Advanced', 'No Customization']
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 10,
|
|
|
|
@ -125,14 +129,6 @@ const questions = ref<Question[]>([
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
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?',
|
|
|
|
|
clarification: 'The user interface and experience of an authentication tool can impact user adoption and satisfaction.',
|
|
|
|
|
answerType: 'radio',
|
|
|
|
@ -156,44 +152,115 @@ function nextQuestion() {
|
|
|
|
|
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
|
|
|
|
|
const isNextButtonDisabled = computed(() => {
|
|
|
|
|
const onLastQuestion = currentQuestionIndex.value >= questions.value.length - 1;
|
|
|
|
|
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') {
|
|
|
|
|
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') {
|
|
|
|
|
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>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<!-- <h1 class="">Authentication Tool Wizard</h1>-->
|
|
|
|
|
<div class="flex flex-col">
|
|
|
|
|
<h2 class="text-2xl">{{ questions[currentQuestionIndex].question }}</h2>
|
|
|
|
|
<p class="text-gray-500" v-if="questions[currentQuestionIndex].clarification">{{ questions[currentQuestionIndex].clarification }}</p>
|
|
|
|
|
<div class="mt-10 flex justify-center z-10">
|
|
|
|
|
<div v-if="questions[currentQuestionIndex].answerType === 'radio'">
|
|
|
|
|
<div v-for="(option, index) in questions[currentQuestionIndex].options" :key="index">
|
|
|
|
|
<input type="radio" :id="option" :value="option" v-model="questions[currentQuestionIndex].answer" />
|
|
|
|
|
<label :for="option">{{ option }}</label>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-else-if="questions[currentQuestionIndex].answerType === 'checkbox'">
|
|
|
|
|
<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"
|
|
|
|
|
<div class="relative w-full flex flex-col justify-center items-center" v-for="(question, index) in questions" :key="index">
|
|
|
|
|
<TransitionRoot
|
|
|
|
|
appear
|
|
|
|
|
:show="currentQuestionIndex === index"
|
|
|
|
|
as="template"
|
|
|
|
|
enter="transition-opacity ease-linear duration-300"
|
|
|
|
|
enter-from="opacity-0"
|
|
|
|
|
enter-to="opacity-100"
|
|
|
|
|
leave="transition-opacity ease-linear duration-300"
|
|
|
|
|
leave-from="opacity-100"
|
|
|
|
|
leave-to="opacity-0"
|
|
|
|
|
class="absolute"
|
|
|
|
|
>
|
|
|
|
|
<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
|
|
|
|
|
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
|
|
|
|
|
class="h-5 w-5 text-gray-400"
|
|
|
|
@ -206,7 +273,7 @@ const isNextButtonDisabled = computed(() => {
|
|
|
|
|
>
|
|
|
|
|
<ListboxOption
|
|
|
|
|
v-slot="{ active, selected }"
|
|
|
|
|
v-for="(option, index) in questions[currentQuestionIndex].options"
|
|
|
|
|
v-for="(option, index) in question.options"
|
|
|
|
|
:key="index"
|
|
|
|
|
:value="option"
|
|
|
|
|
as="template"
|
|
|
|
@ -233,12 +300,12 @@ const isNextButtonDisabled = computed(() => {
|
|
|
|
|
</ListboxOptions>
|
|
|
|
|
</Listbox>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-else-if="questions[currentQuestionIndex].answerType === 'select'">
|
|
|
|
|
<Listbox v-model="questions[currentQuestionIndex].answer">
|
|
|
|
|
<div v-else-if="question.answerType === 'select'">
|
|
|
|
|
<Listbox v-model="question.answer">
|
|
|
|
|
<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 ? questions[currentQuestionIndex].answer : 'Select an option'}}</span>
|
|
|
|
|
<span>{{!!question.answer ? question.answer : 'Select an option'}}</span>
|
|
|
|
|
<span
|
|
|
|
|
class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"
|
|
|
|
|
>
|
|
|
|
@ -253,7 +320,7 @@ const isNextButtonDisabled = computed(() => {
|
|
|
|
|
>
|
|
|
|
|
<ListboxOption
|
|
|
|
|
v-slot="{ active, selected }"
|
|
|
|
|
v-for="(option, index) in questions[currentQuestionIndex].options"
|
|
|
|
|
v-for="(option, index) in question.options"
|
|
|
|
|
:key="index"
|
|
|
|
|
:value="option"
|
|
|
|
|
as="template"
|
|
|
|
@ -291,12 +358,22 @@ const isNextButtonDisabled = computed(() => {
|
|
|
|
|
</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="nextQuestion"
|
|
|
|
|
:disabled="isNextButtonDisabled">
|
|
|
|
|
:disabled="isNextButtonDisabled || onLastQuestion">
|
|
|
|
|
<span>Next</span>
|
|
|
|
|
</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>
|
|
|
|
|
</TransitionRoot>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|