add quiz questions

This commit is contained in:
2024-04-08 09:34:45 -06:00
parent 56e660f49d
commit 5ab5cf62db
6 changed files with 316 additions and 5 deletions

9
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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

View 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>

View File

@ -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'

View File

@ -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>