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",
|
||||
"dependencies": {
|
||||
"@headlessui/vue": "^1.7.19",
|
||||
"@heroicons/vue": "^2.1.3",
|
||||
"appwrite": "^13.0.2",
|
||||
"pinia": "^2.1.7",
|
||||
"vue": "^3.4.21",
|
||||
@ -1150,6 +1151,14 @@
|
||||
"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": {
|
||||
"version": "0.11.14",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
|
||||
|
@ -15,6 +15,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@headlessui/vue": "^1.7.19",
|
||||
"@heroicons/vue": "^2.1.3",
|
||||
"appwrite": "^13.0.2",
|
||||
"pinia": "^2.1.7",
|
||||
"vue": "^3.4.21",
|
||||
|
@ -1,11 +1,20 @@
|
||||
<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 {account, ID} from "@/lib/appwrite";
|
||||
import type {Models} from "appwrite";
|
||||
const firstName = ref('');
|
||||
const lastName = ref('');
|
||||
const email = ref('');
|
||||
const newsletterConsent = ref(true);
|
||||
const isOpen = ref(true);
|
||||
function submitForm() {
|
||||
console.log('Form submitted');
|
||||
@ -33,9 +42,9 @@ async function setupAccount(): Promise<Models.Token> {
|
||||
async function submitEmail() {
|
||||
let data = new FormData();
|
||||
data.append('EMAIL', email.value);
|
||||
data.append('FIRST_NAME', firstName.value);
|
||||
data.append('LAST_NAME', lastName.value);
|
||||
data.append('NEWSLETTER_CONSENT', '1');
|
||||
data.append('FNAME', firstName.value);
|
||||
data.append('LNAME', lastName.value);
|
||||
data.append('NEWSLETTER_CONSENT', newsletterConsent.value ? '1' : '0');
|
||||
return fetch('https://mw.sa.vin/index.php/lists/zq107l51gw5fc/subscribe', {
|
||||
method: 'POST',
|
||||
body: data
|
||||
@ -109,12 +118,33 @@ function openModal() {
|
||||
<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" />
|
||||
</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 class="flex justify-center mt-4">
|
||||
<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"
|
||||
>
|
||||
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'
|
||||
// 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 { createPinia } from 'pinia'
|
||||
|
||||
|
@ -1,12 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import EnterDetailsModal from "@/components/enter-details-modal.vue";
|
||||
import WizardQuestions from "@/components/wizard-questions.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1>Wizard</h1>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
|
Reference in New Issue
Block a user