add quiz questions
This commit is contained in:
9
package-lock.json
generated
9
package-lock.json
generated
@ -9,6 +9,7 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/vue": "^1.7.19",
|
"@headlessui/vue": "^1.7.19",
|
||||||
|
"@heroicons/vue": "^2.1.3",
|
||||||
"appwrite": "^13.0.2",
|
"appwrite": "^13.0.2",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"vue": "^3.4.21",
|
"vue": "^3.4.21",
|
||||||
@ -1150,6 +1151,14 @@
|
|||||||
"vue": "^3.2.0"
|
"vue": "^3.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@heroicons/vue": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@heroicons/vue/-/vue-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-CP4ipIwFbV4NEn8ULUCN110wkV0wZq6dsViDL3HwgIh+jn5yQGlRm6QaRN+Mv+o+UsUBbRDei3Je/q0NZHf5Gg==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": ">= 3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@humanwhocodes/config-array": {
|
"node_modules/@humanwhocodes/config-array": {
|
||||||
"version": "0.11.14",
|
"version": "0.11.14",
|
||||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
|
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/vue": "^1.7.19",
|
"@headlessui/vue": "^1.7.19",
|
||||||
|
"@heroicons/vue": "^2.1.3",
|
||||||
"appwrite": "^13.0.2",
|
"appwrite": "^13.0.2",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"vue": "^3.4.21",
|
"vue": "^3.4.21",
|
||||||
|
@ -1,11 +1,20 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {Dialog, DialogDescription, DialogPanel, DialogTitle, TransitionChild, TransitionRoot} from "@headlessui/vue";
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogDescription,
|
||||||
|
DialogPanel,
|
||||||
|
DialogTitle, Switch,
|
||||||
|
SwitchGroup, SwitchLabel,
|
||||||
|
TransitionChild,
|
||||||
|
TransitionRoot
|
||||||
|
} from "@headlessui/vue";
|
||||||
import {ref} from "vue";
|
import {ref} from "vue";
|
||||||
import {account, ID} from "@/lib/appwrite";
|
import {account, ID} from "@/lib/appwrite";
|
||||||
import type {Models} from "appwrite";
|
import type {Models} from "appwrite";
|
||||||
const firstName = ref('');
|
const firstName = ref('');
|
||||||
const lastName = ref('');
|
const lastName = ref('');
|
||||||
const email = ref('');
|
const email = ref('');
|
||||||
|
const newsletterConsent = ref(true);
|
||||||
const isOpen = ref(true);
|
const isOpen = ref(true);
|
||||||
function submitForm() {
|
function submitForm() {
|
||||||
console.log('Form submitted');
|
console.log('Form submitted');
|
||||||
@ -33,9 +42,9 @@ async function setupAccount(): Promise<Models.Token> {
|
|||||||
async function submitEmail() {
|
async function submitEmail() {
|
||||||
let data = new FormData();
|
let data = new FormData();
|
||||||
data.append('EMAIL', email.value);
|
data.append('EMAIL', email.value);
|
||||||
data.append('FIRST_NAME', firstName.value);
|
data.append('FNAME', firstName.value);
|
||||||
data.append('LAST_NAME', lastName.value);
|
data.append('LNAME', lastName.value);
|
||||||
data.append('NEWSLETTER_CONSENT', '1');
|
data.append('NEWSLETTER_CONSENT', newsletterConsent.value ? '1' : '0');
|
||||||
return fetch('https://mw.sa.vin/index.php/lists/zq107l51gw5fc/subscribe', {
|
return fetch('https://mw.sa.vin/index.php/lists/zq107l51gw5fc/subscribe', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: data
|
body: data
|
||||||
@ -109,12 +118,33 @@ function openModal() {
|
|||||||
<div class="grid grid-cols-1">
|
<div class="grid grid-cols-1">
|
||||||
<input type="email" placeholder="Email" class="border border-gray-300 rounded-md p-2 m-2" v-model="email" />
|
<input type="email" placeholder="Email" class="border border-gray-300 rounded-md p-2 m-2" v-model="email" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="grid grid-cols-1">
|
||||||
|
<SwitchGroup>
|
||||||
|
<div class="flex items-center justify-start">
|
||||||
|
<Switch
|
||||||
|
v-model="newsletterConsent"
|
||||||
|
:class="newsletterConsent ? 'bg-amber-500' : 'bg-amber-950'"
|
||||||
|
class="relative inline-flex h-[38px] w-[74px] shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-white/75"
|
||||||
|
>
|
||||||
|
<span class="sr-only">Send me updates via email</span>
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
:class="newsletterConsent ? 'translate-x-9' : 'translate-x-0'"
|
||||||
|
class="pointer-events-none inline-block h-[34px] w-[34px] transform rounded-full bg-white shadow-lg ring-0 transition duration-200 ease-in-out"
|
||||||
|
/>
|
||||||
|
</Switch>
|
||||||
|
<SwitchLabel class="ml-4">Send me updates via email</SwitchLabel>
|
||||||
|
</div>
|
||||||
|
</SwitchGroup>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="flex justify-center mt-4">
|
<div class="flex justify-center mt-4">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="flex-grow inline-flex justify-center rounded-md border border-transparent bg-blue-100 px-4 py-2 text-sm font-medium text-blue-900 hover:bg-blue-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
class="flex-grow inline-flex justify-center 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"
|
||||||
@click="submitForm"
|
@click="submitForm"
|
||||||
>
|
>
|
||||||
Begin
|
Begin
|
||||||
|
259
src/components/wizard-questions.vue
Normal file
259
src/components/wizard-questions.vue
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {computed, ref} from "vue";
|
||||||
|
import {
|
||||||
|
Listbox,
|
||||||
|
ListboxButton,
|
||||||
|
ListboxOptions,
|
||||||
|
ListboxOption,
|
||||||
|
} from "@headlessui/vue";
|
||||||
|
import { CheckIcon, ChevronUpDownIcon } from '@heroicons/vue/20/solid';
|
||||||
|
|
||||||
|
interface Question {
|
||||||
|
id: number;
|
||||||
|
question: string;
|
||||||
|
clarification: string;
|
||||||
|
answerType: 'radio' | 'checkbox' | 'select';
|
||||||
|
answer: string | number | boolean | string[];
|
||||||
|
options: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentQuestionIndex = ref(1);
|
||||||
|
const questions = ref<Question[]>([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
question: 'What type of application are you building (web, mobile, desktop, SaaS, etc.)?',
|
||||||
|
clarification: 'Some authentication tools are better suited for specific types of applications.',
|
||||||
|
answerType: 'checkbox',
|
||||||
|
answer: [],
|
||||||
|
options: ['Web', 'Mobile', 'Desktop', 'SaaS', 'Other']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
question: 'How many users do you expect to have in the short term and long term?',
|
||||||
|
clarification: 'This will help determine the scalability requirements of the authentication tool.',
|
||||||
|
answerType: 'select',
|
||||||
|
answer: '',
|
||||||
|
options: ['10 - 99', '100 - 999', '1000 - 9999', '10000 - 49999', '50000+', 'Not Sure', 'No Users Yet', 'No Limit']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
question: 'Do you require the authentication tool to be open source or closed source?',
|
||||||
|
clarification: 'Open-source tools are typically more customizable and transparent, while closed-source tools may offer additional support and security features.',
|
||||||
|
answerType: 'radio',
|
||||||
|
answer: '',
|
||||||
|
options: ['Open Source', 'Closed Source', 'No Preference']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
question: 'Are you looking for a self-hosted solution or a cloud-based one?',
|
||||||
|
clarification: 'Self-hosted solutions give you more control over your data and infrastructure, while cloud-based solutions may be easier to set up and maintain.',
|
||||||
|
answerType: 'radio',
|
||||||
|
answer: '',
|
||||||
|
options: ['Self-Hosted', 'Cloud-Based', 'No Preference']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
question: 'What is your annual budget for this tool, if any?',
|
||||||
|
clarification: 'Some authentication tools are free, while others may require a subscription or one-time payment.',
|
||||||
|
answerType: 'select',
|
||||||
|
answer: '',
|
||||||
|
options: ['Free', 'Under $100', '$100 - $499', '$500 - $999', '$1000 - $4999', '$5000 - $9999', '$10000+', 'No Budget']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
question: 'Which identity providers do you want to support (LDAP, Active Directory, Google, etc.)?',
|
||||||
|
clarification: 'Supporting multiple identity providers can make it easier for users to sign in to your application.',
|
||||||
|
answerType: 'checkbox',
|
||||||
|
answer: [],
|
||||||
|
options: ['LDAP', 'Active Directory', 'Google', 'Facebook', 'Twitter', 'GitHub', 'Other']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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',
|
||||||
|
answer: '',
|
||||||
|
options: ['OIDC', 'SAML', 'Neither']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 8,
|
||||||
|
question: 'How important is multi-factor authentication (MFA) to your application\'s security?',
|
||||||
|
clarification: 'MFA adds an extra layer of security by requiring users to provide multiple forms of verification to access their accounts.',
|
||||||
|
answerType: 'radio',
|
||||||
|
answer: '',
|
||||||
|
options: ['Not Important', 'Somewhat Important', 'Very Important', 'Critical']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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',
|
||||||
|
answer: '',
|
||||||
|
options: ['Basic', 'Intermediate', 'Advanced']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 10,
|
||||||
|
question: 'Do you need advanced security features like breached password detection, risk-based authentication, or adaptive authentication?',
|
||||||
|
clarification: 'These features can help prevent unauthorized access and protect user data.',
|
||||||
|
answerType: 'checkbox',
|
||||||
|
answer: [],
|
||||||
|
options: ['Breached Password Detection', 'Risk-Based Authentication', 'Adaptive Authentication', 'None']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 11,
|
||||||
|
question: 'How important is audit logging and reporting to your organization?',
|
||||||
|
clarification: 'Audit logging and reporting can help you track user activity, monitor security events, and comply with regulations.',
|
||||||
|
answerType: 'radio',
|
||||||
|
answer: '',
|
||||||
|
options: ['Not Important', 'Somewhat Important', 'Very Important', 'Critical']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 12,
|
||||||
|
question: 'Do you have any specific compliance requirements (e.g., HIPAA, GDPR, PCI DSS)?',
|
||||||
|
clarification: 'Compliance requirements may dictate the security and privacy features you need in an authentication tool.',
|
||||||
|
answerType: 'checkbox',
|
||||||
|
answer: [],
|
||||||
|
options: ['HIPAA', 'GDPR', 'PCI DSS', 'Other']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 13,
|
||||||
|
question: 'What level of support do you require from the vendor (community-based, premium, enterprise)?',
|
||||||
|
clarification: 'Different levels of support may include different response times, service level agreements, and access to support resources.',
|
||||||
|
answerType: 'select',
|
||||||
|
answer: '',
|
||||||
|
options: ['Community-Based', 'Premium', 'Enterprise']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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',
|
||||||
|
answer: '',
|
||||||
|
options: ['Simple customization only', 'Fully Customizable', 'No Preference']
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
function previousQuestion() {
|
||||||
|
if (currentQuestionIndex.value > 0) {
|
||||||
|
currentQuestionIndex.value--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextQuestion() {
|
||||||
|
if (currentQuestionIndex.value < questions.value.length - 1) {
|
||||||
|
currentQuestionIndex.value++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
if (questions.value[currentQuestionIndex.value].answerType === 'select') {
|
||||||
|
return (questions.value[currentQuestionIndex.value].answer as string).trim().length <= 0 || onLastQuestion;
|
||||||
|
}
|
||||||
|
if (questions.value[currentQuestionIndex.value].answerType === 'radio') {
|
||||||
|
return (questions.value[currentQuestionIndex.value].answer as string).trim().length <= 0 || onLastQuestion;
|
||||||
|
}
|
||||||
|
return onLastQuestion;
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<h1>Authentication Tool Wizard</h1>
|
||||||
|
<div>
|
||||||
|
<h2>{{ questions[currentQuestionIndex].question }}</h2>
|
||||||
|
<p v-if="questions[currentQuestionIndex].clarification">{{ questions[currentQuestionIndex].clarification }}</p>
|
||||||
|
<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"
|
||||||
|
>
|
||||||
|
<span>{{ !!(questions[currentQuestionIndex].answer as string[]).length ? (questions[currentQuestionIndex].answer as string[]).join(', ') : 'Select options'}}</span>
|
||||||
|
<span
|
||||||
|
class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"
|
||||||
|
>
|
||||||
|
<ChevronUpDownIcon
|
||||||
|
class="h-5 w-5 text-gray-400"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</ListboxButton>
|
||||||
|
<ListboxOptions
|
||||||
|
class="absolute mt-1 max-h-60 overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black/5 focus:outline-none sm:text-sm"
|
||||||
|
>
|
||||||
|
<ListboxOption
|
||||||
|
v-slot="{ active, selected }"
|
||||||
|
v-for="(option, index) in questions[currentQuestionIndex].options"
|
||||||
|
:key="index"
|
||||||
|
:value="option"
|
||||||
|
as="template"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
:class="[
|
||||||
|
active ? 'bg-amber-100 text-amber-900' : 'text-gray-900',
|
||||||
|
'relative cursor-default select-none py-2 pl-10 pr-4',
|
||||||
|
]">
|
||||||
|
<span
|
||||||
|
:class="[
|
||||||
|
selected ? 'font-medium' : 'font-normal',
|
||||||
|
'block truncate',
|
||||||
|
]"
|
||||||
|
>{{ option }}</span>
|
||||||
|
<span
|
||||||
|
v-if="selected"
|
||||||
|
class="absolute inset-y-0 left-0 flex items-center pl-3 text-amber-600"
|
||||||
|
>
|
||||||
|
<CheckIcon class="h-5 w-5" aria-hidden="true" />
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ListboxOption>
|
||||||
|
</ListboxOptions>
|
||||||
|
</Listbox>
|
||||||
|
<!-- <div v-for="(option, index) in questions[currentQuestionIndex].options" :key="index">-->
|
||||||
|
<!-- <input type="checkbox" :id="option" :value="option" v-model="questions[currentQuestionIndex].answer" />-->
|
||||||
|
<!-- <label :for="option">{{ option }}</label>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
</div>
|
||||||
|
<div v-else-if="questions[currentQuestionIndex].answerType === 'select'">
|
||||||
|
<select v-model="questions[currentQuestionIndex].answer">
|
||||||
|
<option v-for="(option, index) in questions[currentQuestionIndex].options" :key="index" :value="option">{{ option }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="button-container flex justify-center">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="nav-button rounded-md border border-transparent bg-amber-200 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"
|
||||||
|
@click="previousQuestion"
|
||||||
|
:disabled="currentQuestionIndex === 0">Previous</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"
|
||||||
|
@click="nextQuestion"
|
||||||
|
:disabled="isNextButtonDisabled">Next</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.nav-button:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,5 +1,11 @@
|
|||||||
import './assets/main.css'
|
import './assets/main.css'
|
||||||
|
// colors: https://huemint.com/brand-intersection/#palette=312624-fa9048-ff5473-4b83f6
|
||||||
|
// blue colors: https://huemint.com/back-gradient-3/#palette=090e13-3a6282-628baf-99b7c6
|
||||||
|
|
||||||
|
// #FF5473 - pinkish
|
||||||
|
// #FA9048 - orange
|
||||||
|
// #f2f9f7 - very light green
|
||||||
|
// #243043 - dark blue
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
|
|
||||||
|
@ -1,12 +1,18 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
import EnterDetailsModal from "@/components/enter-details-modal.vue";
|
import EnterDetailsModal from "@/components/enter-details-modal.vue";
|
||||||
|
import WizardQuestions from "@/components/wizard-questions.vue";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h1>Wizard</h1>
|
<h1>Wizard</h1>
|
||||||
<enter-details-modal />
|
<enter-details-modal />
|
||||||
|
|
||||||
|
<!-- <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 />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user