This commit is contained in:
parent
7c2db2052a
commit
3b798c12fd
|
@ -7,7 +7,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<input :id="id?id:name" v-model="compVal" :disabled="disabled?true:null"
|
<input :id="id?id:name" v-model="compVal" :disabled="disabled?true:null"
|
||||||
:type="nativeType"
|
:type="nativeType" :autocomplete="autocomplete"
|
||||||
:pattern="pattern"
|
:pattern="pattern"
|
||||||
:style="inputStyle"
|
:style="inputStyle"
|
||||||
:class="[theme.default.input, { '!ring-red-500 !ring-2': hasError, '!cursor-not-allowed !bg-gray-200': disabled }]"
|
:class="[theme.default.input, { '!ring-red-500 !ring-2': hasError, '!cursor-not-allowed !bg-gray-200': disabled }]"
|
||||||
|
@ -42,6 +42,7 @@ export default {
|
||||||
accept: { type: String, default: null },
|
accept: { type: String, default: null },
|
||||||
min: { type: Number, required: false, default: null },
|
min: { type: Number, required: false, default: null },
|
||||||
max: { type: Number, required: false, default: null },
|
max: { type: Number, required: false, default: null },
|
||||||
|
autocomplete: { default: null },
|
||||||
maxCharLimit: { type: Number, required: false, default: null },
|
maxCharLimit: { type: Number, required: false, default: null },
|
||||||
showCharLimit: { type: Boolean, required: false, default: false },
|
showCharLimit: { type: Boolean, required: false, default: false },
|
||||||
pattern: { type: String, default: null }
|
pattern: { type: String, default: null }
|
||||||
|
|
|
@ -73,7 +73,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Collapsible from '~/components/global/transitions/Collapsible.vue'
|
import Collapsible from '~/components/global/transitions/Collapsible.vue'
|
||||||
import { themes } from '../../../config/form-themes'
|
import { themes } from '../../../lib/forms/form-themes.js'
|
||||||
import TextInput from '../TextInput.vue'
|
import TextInput from '../TextInput.vue'
|
||||||
import debounce from 'debounce'
|
import debounce from 'debounce'
|
||||||
import Fuse from 'fuse.js'
|
import Fuse from 'fuse.js'
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { ref, computed, watch } from 'vue'
|
import { ref, computed, watch } from 'vue'
|
||||||
import { themes } from '~/config/form-themes.js'
|
import { themes } from '~/lib/forms/form-themes.js'
|
||||||
|
|
||||||
export const inputProps = {
|
export const inputProps = {
|
||||||
id: { type: String, default: null },
|
id: { type: String, default: null },
|
||||||
|
|
|
@ -111,7 +111,7 @@
|
||||||
import Form from 'vform'
|
import Form from 'vform'
|
||||||
import OpenForm from './OpenForm.vue'
|
import OpenForm from './OpenForm.vue'
|
||||||
import OpenFormButton from './OpenFormButton.vue'
|
import OpenFormButton from './OpenFormButton.vue'
|
||||||
import { themes } from '~/config/form-themes.js'
|
import { themes } from '~/lib/forms/form-themes.js'
|
||||||
import VButton from '~/components/global/VButton.vue'
|
import VButton from '~/components/global/VButton.vue'
|
||||||
import VTransition from '~/components/global/transitions/VTransition.vue'
|
import VTransition from '~/components/global/transitions/VTransition.vue'
|
||||||
import FormPendingSubmissionKey from '../../../mixins/forms/form-pending-submission-key.js'
|
import FormPendingSubmissionKey from '../../../mixins/forms/form-pending-submission-key.js'
|
||||||
|
|
|
@ -62,12 +62,9 @@
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import Form from 'vform'
|
import Form from 'vform'
|
||||||
import { computed } from 'vue'
|
|
||||||
import { useRecordsStore } from '../../../stores/records'
|
|
||||||
import { useWorkingFormStore } from '../../../stores/working_form'
|
|
||||||
import OpenFormButton from './OpenFormButton.vue'
|
import OpenFormButton from './OpenFormButton.vue'
|
||||||
import clonedeep from 'clone-deep'
|
import clonedeep from 'clone-deep'
|
||||||
import FormLogicPropertyResolver from '../../../forms/FormLogicPropertyResolver.js'
|
import FormLogicPropertyResolver from "~/lib/forms/FormLogicPropertyResolver.js";
|
||||||
import OpenFormField from './OpenFormField.vue'
|
import OpenFormField from './OpenFormField.vue'
|
||||||
import draggable from 'vuedraggable'
|
import draggable from 'vuedraggable'
|
||||||
import FormPendingSubmissionKey from '../../../mixins/forms/form-pending-submission-key.js'
|
import FormPendingSubmissionKey from '../../../mixins/forms/form-pending-submission-key.js'
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { themes } from '~/config/form-themes.js'
|
import { themes } from '~/lib/forms/form-themes.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'OpenFormButton',
|
name: 'OpenFormButton',
|
||||||
|
|
|
@ -80,9 +80,8 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useWorkingFormStore } from '../../../stores/working_form'
|
import FormLogicPropertyResolver from "~/lib/forms/FormLogicPropertyResolver.js"
|
||||||
import FormLogicPropertyResolver from '../../../forms/FormLogicPropertyResolver.js'
|
import FormPendingSubmissionKey from '~/mixins/forms/form-pending-submission-key.js'
|
||||||
import FormPendingSubmissionKey from '../../../mixins/forms/form-pending-submission-key.js'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'OpenFormField',
|
name: 'OpenFormField',
|
||||||
|
|
|
@ -67,7 +67,7 @@
|
||||||
import FormUrlPrefill from '../../../open/forms/components/FormUrlPrefill.vue'
|
import FormUrlPrefill from '../../../open/forms/components/FormUrlPrefill.vue'
|
||||||
import ProTag from '~/components/global/ProTag.vue'
|
import ProTag from '~/components/global/ProTag.vue'
|
||||||
import OpenForm from '../../../open/forms/OpenForm.vue'
|
import OpenForm from '../../../open/forms/OpenForm.vue'
|
||||||
import { themes } from '~/config/form-themes.js'
|
import { themes } from '~/lib/forms/form-themes.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'UrlFormPrefill',
|
name: 'UrlFormPrefill',
|
||||||
|
|
|
@ -39,45 +39,18 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { computed } from 'vue'
|
|
||||||
import { useTemplatesStore } from '../../../stores/templates'
|
|
||||||
import TemplateTags from './TemplateTags.vue'
|
import TemplateTags from './TemplateTags.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { TemplateTags },
|
components: { TemplateTags },
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
slug: {
|
template: {
|
||||||
type: String,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setup () {
|
|
||||||
const templatesStore = useTemplatesStore()
|
|
||||||
return {
|
|
||||||
templatesStore
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
data: () => ({}),
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
template () {
|
|
||||||
return this.templatesStore.getBySlug(this.slug)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
|
||||||
slug () {
|
|
||||||
this.templatesStore.loadTemplate(this.slug)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted () {
|
|
||||||
this.templatesStore.loadTemplate(this.slug)
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
cleanQuotes (str) {
|
cleanQuotes (str) {
|
||||||
// Remove starting and ending quotes if any
|
// Remove starting and ending quotes if any
|
||||||
|
|
|
@ -71,10 +71,12 @@ export default {
|
||||||
},
|
},
|
||||||
types () {
|
types () {
|
||||||
if (!this.template) return null
|
if (!this.template) return null
|
||||||
|
console.log('template in types',this.template)
|
||||||
return this.templatesStore.getTemplateTypes(this.template.types)
|
return this.templatesStore.getTemplateTypes(this.template.types)
|
||||||
},
|
},
|
||||||
industries () {
|
industries () {
|
||||||
if (!this.template) return null
|
if (!this.template) return null
|
||||||
|
console.log('template in types',this.template)
|
||||||
return this.templatesStore.getTemplateIndustries(this.template.industries)
|
return this.templatesStore.getTemplateIndustries(this.template.industries)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -16,25 +16,25 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1 w-full md:max-w-xs">
|
<div class="flex-1 w-full md:max-w-xs">
|
||||||
<text-input name="search" :form="searchTemplate" placeholder="Search..." />
|
<text-input autocomplete="off" name="search" :form="searchTemplate" placeholder="Search..."/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="templatesLoading" class="text-center mt-4">
|
<div v-if="templatesLoading" class="text-center mt-4">
|
||||||
<Loader class="h-6 w-6 text-nt-blue mx-auto" />
|
<Loader class="h-6 w-6 text-nt-blue mx-auto"/>
|
||||||
</div>
|
</div>
|
||||||
<p v-else-if="enrichedTemplates.length === 0" class="text-center mt-4">
|
<p v-else-if="enrichedTemplates.length === 0" class="text-center mt-4">
|
||||||
No templates found.
|
No templates found.
|
||||||
</p>
|
</p>
|
||||||
<div v-else class="relative z-10">
|
<div v-else class="relative z-10">
|
||||||
<div class="grid grid-cols-1 mt-8 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-8 sm:gap-y-12">
|
<div class="grid grid-cols-1 mt-8 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-8 sm:gap-y-12">
|
||||||
<single-template v-for="template in enrichedTemplates" :key="template.id" :slug="template.slug" />
|
<single-template v-for="template in enrichedTemplates" :key="template.id" :template="template"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<template v-if="!onlyMy">
|
|
||||||
<section class="py-12 bg-white border-t border-gray-200 sm:py-16">
|
<section class="py-12 bg-white border-t border-gray-200 sm:py-16">
|
||||||
<div class="px-4 mx-auto sm:px-6 lg:px-8 max-w-7xl">
|
<div class="px-4 mx-auto sm:px-6 lg:px-8 max-w-7xl">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
|
@ -74,46 +74,45 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { computed } from 'vue'
|
import {computed} from 'vue'
|
||||||
import { useAuthStore } from '../../../stores/auth'
|
|
||||||
import { useTemplatesStore } from '../../../stores/templates'
|
|
||||||
import Form from 'vform'
|
import Form from 'vform'
|
||||||
import Fuse from 'fuse.js'
|
import Fuse from 'fuse.js'
|
||||||
import SingleTemplate from './SingleTemplate.vue'
|
import SingleTemplate from './SingleTemplate.vue'
|
||||||
|
|
||||||
const loadTemplates = function (onlyMy) {
|
// const loadTemplates = function (onlyMy) {
|
||||||
const templatesStore = useTemplatesStore()
|
// const templatesStore = useTemplatesStore()
|
||||||
if(onlyMy){
|
// if(onlyMy){
|
||||||
templatesStore.loadAll({'onlymy':true})
|
// templatesStore.loadAll({'onlymy':true})
|
||||||
} else {
|
// } else {
|
||||||
templatesStore.loadIfEmpty()
|
// templatesStore.loadIfEmpty()
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TemplatesList',
|
name: 'TemplatesList',
|
||||||
components: { SingleTemplate },
|
components: {SingleTemplate},
|
||||||
props: {
|
props: {
|
||||||
onlyMy: {
|
templates: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setup () {
|
setup() {
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const templatesStore = useTemplatesStore()
|
const templatesStore = useTemplatesStore()
|
||||||
return {
|
return {
|
||||||
user : computed(() => authStore.user),
|
user: computed(() => authStore.user),
|
||||||
templates : computed(() => templatesStore.content),
|
industries: computed(() => templatesStore.industries),
|
||||||
templatesLoading : computed(() => templatesStore.loading),
|
types: computed(() => templatesStore.types)
|
||||||
industries : computed(() => templatesStore.industries),
|
|
||||||
types : computed(() => templatesStore.types)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -127,29 +126,25 @@ export default {
|
||||||
|
|
||||||
watch: {},
|
watch: {},
|
||||||
|
|
||||||
mounted () {
|
|
||||||
loadTemplates(this.onlyMy)
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
industriesOptions () {
|
industriesOptions() {
|
||||||
return [{ name: 'All Industries', value: 'all' }].concat(Object.values(this.industries).map((industry) => {
|
return [{name: 'All Industries', value: 'all'}].concat(Object.values(this.industries).map((industry) => {
|
||||||
return {
|
return {
|
||||||
name: industry.name,
|
name: industry.name,
|
||||||
value: industry.slug
|
value: industry.slug
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
typesOptions () {
|
typesOptions() {
|
||||||
return [{ name: 'All Types', value: 'all' }].concat(Object.values(this.types).map((type) => {
|
return [{name: 'All Types', value: 'all'}].concat(Object.values(this.types).map((type) => {
|
||||||
return {
|
return {
|
||||||
name: type.name,
|
name: type.name,
|
||||||
value: type.slug
|
value: type.slug
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
enrichedTemplates () {
|
enrichedTemplates() {
|
||||||
let enrichedTemplates = (this.onlyMy && this.user) ? this.templates.filter((item) => { return item.creator_id === this.user.id}) : this.templates
|
let enrichedTemplates = this.templates
|
||||||
|
|
||||||
// Filter by Selected Type
|
// Filter by Selected Type
|
||||||
if (this.selectedType && this.selectedType !== 'all') {
|
if (this.selectedType && this.selectedType !== 'all') {
|
||||||
|
@ -184,7 +179,5 @@ export default {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -0,0 +1,325 @@
|
||||||
|
export function conditionsMet (conditions, formData) {
|
||||||
|
if (conditions === undefined || conditions === null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's not a group, just a single condition
|
||||||
|
if (conditions.operatorIdentifier === undefined) {
|
||||||
|
return propertyConditionMet(conditions.value, conditions.value ? formData[conditions.value.property_meta.id] : null)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conditions.operatorIdentifier === 'and') {
|
||||||
|
let isvalid = true
|
||||||
|
conditions.children.forEach(childrenCondition => {
|
||||||
|
if (!conditionsMet(childrenCondition, formData)) {
|
||||||
|
isvalid = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return isvalid
|
||||||
|
} else if (conditions.operatorIdentifier === 'or') {
|
||||||
|
let isvalid = false
|
||||||
|
conditions.children.forEach(childrenCondition => {
|
||||||
|
if (conditionsMet(childrenCondition, formData)) {
|
||||||
|
isvalid = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return isvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Unexcepted operatorIdentifier:' + conditions.operatorIdentifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
function propertyConditionMet (propertyCondition, value) {
|
||||||
|
if (!propertyCondition) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch (propertyCondition.property_meta.type) {
|
||||||
|
case 'text':
|
||||||
|
case 'url':
|
||||||
|
case 'email':
|
||||||
|
case 'phone_number':
|
||||||
|
return textConditionMet(propertyCondition, value)
|
||||||
|
case 'number':
|
||||||
|
return numberConditionMet(propertyCondition, value)
|
||||||
|
case 'checkbox':
|
||||||
|
return checkboxConditionMet(propertyCondition, value)
|
||||||
|
case 'select':
|
||||||
|
return selectConditionMet(propertyCondition, value)
|
||||||
|
case 'date':
|
||||||
|
return dateConditionMet(propertyCondition, value)
|
||||||
|
case 'multi_select':
|
||||||
|
return multiSelectConditionMet(propertyCondition, value)
|
||||||
|
case 'files':
|
||||||
|
return filesConditionMet(propertyCondition, value)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkEquals (condition, fieldValue) {
|
||||||
|
return condition.value === fieldValue
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkContains (condition, fieldValue) {
|
||||||
|
return (fieldValue) ? fieldValue.includes(condition.value) : false
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkListContains (condition, fieldValue) {
|
||||||
|
if (!fieldValue) return false
|
||||||
|
|
||||||
|
if (Array.isArray(condition.value)) {
|
||||||
|
return condition.value.every(r => fieldValue.includes(r))
|
||||||
|
} else {
|
||||||
|
return fieldValue.includes(condition.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkStartsWith (condition, fieldValue) {
|
||||||
|
return fieldValue.startsWith(condition.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkendsWith (condition, fieldValue) {
|
||||||
|
return fieldValue && fieldValue.endsWith(condition.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkIsEmpty (condition, fieldValue) {
|
||||||
|
return (!fieldValue || fieldValue.length === 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkGreaterThan (condition, fieldValue) {
|
||||||
|
return (condition.value && fieldValue && parseFloat(fieldValue) > parseFloat(condition.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkGreaterThanEqual (condition, fieldValue) {
|
||||||
|
return (condition.value && fieldValue && parseFloat(fieldValue) >= parseFloat(condition.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkLessThan (condition, fieldValue) {
|
||||||
|
return (condition.value && fieldValue && parseFloat(fieldValue) < parseFloat(condition.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkLessThanEqual (condition, fieldValue) {
|
||||||
|
return (condition.value && fieldValue && parseFloat(fieldValue) <= parseFloat(condition.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkBefore (condition, fieldValue) {
|
||||||
|
return (condition.value && fieldValue && fieldValue > condition.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkAfter (condition, fieldValue) {
|
||||||
|
return (condition.value && fieldValue && fieldValue < condition.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkOnOrBefore (condition, fieldValue) {
|
||||||
|
return (condition.value && fieldValue && fieldValue >= condition.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkOnOrAfter (condition, fieldValue) {
|
||||||
|
return (condition.value && fieldValue && fieldValue <= condition.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkPastWeek (condition, fieldValue) {
|
||||||
|
if (!fieldValue) return false
|
||||||
|
const fieldDate = new Date(fieldValue)
|
||||||
|
const today = new Date()
|
||||||
|
return (fieldDate <= today && fieldDate >= new Date(today.getFullYear(), today.getMonth(), today.getDate() - 7))
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkPastMonth (condition, fieldValue) {
|
||||||
|
if (!fieldValue) return false
|
||||||
|
const fieldDate = new Date(fieldValue)
|
||||||
|
const today = new Date()
|
||||||
|
return (fieldDate <= today && fieldDate >= new Date(today.getFullYear(), today.getMonth() - 1, today.getDate()))
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkPastYear (condition, fieldValue) {
|
||||||
|
if (!fieldValue) return false
|
||||||
|
const fieldDate = new Date(fieldValue)
|
||||||
|
const today = new Date()
|
||||||
|
return (fieldDate <= today && fieldDate >= new Date(today.getFullYear() - 1, today.getMonth(), today.getDate()))
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkNextWeek (condition, fieldValue) {
|
||||||
|
if (!fieldValue) return false
|
||||||
|
const fieldDate = new Date(fieldValue)
|
||||||
|
const today = new Date()
|
||||||
|
return (fieldDate >= today && fieldDate <= new Date(today.getFullYear(), today.getMonth(), today.getDate() + 7))
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkNextMonth (condition, fieldValue) {
|
||||||
|
if (!fieldValue) return false
|
||||||
|
const fieldDate = new Date(fieldValue)
|
||||||
|
const today = new Date()
|
||||||
|
return (fieldDate >= today && fieldDate <= new Date(today.getFullYear(), today.getMonth() + 1, today.getDate()))
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkNextYear (condition, fieldValue) {
|
||||||
|
if (!fieldValue) return false
|
||||||
|
const fieldDate = new Date(fieldValue)
|
||||||
|
const today = new Date()
|
||||||
|
return (fieldDate >= today && fieldDate <= new Date(today.getFullYear() + 1, today.getMonth(), today.getDate()))
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkLength (condition, fieldValue, operator = '===') {
|
||||||
|
if(!fieldValue || fieldValue.length === 0) return false;
|
||||||
|
switch (operator) {
|
||||||
|
case '===':
|
||||||
|
return fieldValue.length === parseInt(condition.value)
|
||||||
|
case '!==':
|
||||||
|
return fieldValue.length !== parseInt(condition.value)
|
||||||
|
case '>':
|
||||||
|
return fieldValue.length > parseInt(condition.value)
|
||||||
|
case '>=':
|
||||||
|
return fieldValue.length >= parseInt(condition.value)
|
||||||
|
case '<':
|
||||||
|
return fieldValue.length < parseInt(condition.value)
|
||||||
|
case '<=':
|
||||||
|
return fieldValue.length <= parseInt(condition.value)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function textConditionMet (propertyCondition, value) {
|
||||||
|
switch (propertyCondition.operator) {
|
||||||
|
case 'equals':
|
||||||
|
return checkEquals(propertyCondition, value)
|
||||||
|
case 'does_not_equal':
|
||||||
|
return !checkEquals(propertyCondition, value)
|
||||||
|
case 'contains':
|
||||||
|
return checkContains(propertyCondition, value)
|
||||||
|
case 'does_not_contain':
|
||||||
|
return !checkContains(propertyCondition, value)
|
||||||
|
case 'starts_with':
|
||||||
|
return checkStartsWith(propertyCondition, value)
|
||||||
|
case 'ends_with':
|
||||||
|
return checkendsWith(propertyCondition, value)
|
||||||
|
case 'is_empty':
|
||||||
|
return checkIsEmpty(propertyCondition, value)
|
||||||
|
case 'is_not_empty':
|
||||||
|
return !checkIsEmpty(propertyCondition, value)
|
||||||
|
case 'content_length_equals':
|
||||||
|
return checkLength(propertyCondition, value, '===')
|
||||||
|
case 'content_length_does_not_equal':
|
||||||
|
return checkLength(propertyCondition, value, '!==')
|
||||||
|
case 'content_length_greater_than':
|
||||||
|
return checkLength(propertyCondition, value, '>')
|
||||||
|
case 'content_length_greater_than_or_equal_to':
|
||||||
|
return checkLength(propertyCondition, value, '>=')
|
||||||
|
case 'content_length_less_than':
|
||||||
|
return checkLength(propertyCondition, value, '<')
|
||||||
|
case 'content_length_less_than_or_equal_to':
|
||||||
|
return checkLength(propertyCondition, value, '<=')
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function numberConditionMet (propertyCondition, value) {
|
||||||
|
switch (propertyCondition.operator) {
|
||||||
|
case 'equals':
|
||||||
|
return checkEquals(propertyCondition, value)
|
||||||
|
case 'does_not_equal':
|
||||||
|
return !checkEquals(propertyCondition, value)
|
||||||
|
case 'greater_than':
|
||||||
|
return checkGreaterThan(propertyCondition, value)
|
||||||
|
case 'less_than':
|
||||||
|
return checkLessThan(propertyCondition, value)
|
||||||
|
case 'greater_than_or_equal_to':
|
||||||
|
return checkGreaterThanEqual(propertyCondition, value)
|
||||||
|
case 'less_than_or_equal_to':
|
||||||
|
return checkLessThanEqual(propertyCondition, value)
|
||||||
|
case 'is_empty':
|
||||||
|
return checkIsEmpty(propertyCondition, value)
|
||||||
|
case 'is_not_empty':
|
||||||
|
return checkIsEmpty(propertyCondition, value)
|
||||||
|
case 'content_length_equals':
|
||||||
|
return checkLength(propertyCondition, value, '===')
|
||||||
|
case 'content_length_does_not_equal':
|
||||||
|
return checkLength(propertyCondition, value, '!==')
|
||||||
|
case 'content_length_greater_than':
|
||||||
|
return checkLength(propertyCondition, value, '>')
|
||||||
|
case 'content_length_greater_than_or_equal_to':
|
||||||
|
return checkLength(propertyCondition, value, '>=')
|
||||||
|
case 'content_length_less_than':
|
||||||
|
return checkLength(propertyCondition, value, '<')
|
||||||
|
case 'content_length_less_than_or_equal_to':
|
||||||
|
return checkLength(propertyCondition, value, '<=')
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkboxConditionMet (propertyCondition, value) {
|
||||||
|
switch (propertyCondition.operator) {
|
||||||
|
case 'equals':
|
||||||
|
return checkEquals(propertyCondition, value)
|
||||||
|
case 'does_not_equal':
|
||||||
|
return !checkEquals(propertyCondition, value)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectConditionMet (propertyCondition, value) {
|
||||||
|
switch (propertyCondition.operator) {
|
||||||
|
case 'equals':
|
||||||
|
return checkEquals(propertyCondition, value)
|
||||||
|
case 'does_not_equal':
|
||||||
|
return !checkEquals(propertyCondition, value)
|
||||||
|
case 'is_empty':
|
||||||
|
return checkIsEmpty(propertyCondition, value)
|
||||||
|
case 'is_not_empty':
|
||||||
|
return !checkIsEmpty(propertyCondition, value)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function dateConditionMet (propertyCondition, value) {
|
||||||
|
switch (propertyCondition.operator) {
|
||||||
|
case 'equals':
|
||||||
|
return checkEquals(propertyCondition, value)
|
||||||
|
case 'before':
|
||||||
|
return checkBefore(propertyCondition, value)
|
||||||
|
case 'after':
|
||||||
|
return checkAfter(propertyCondition, value)
|
||||||
|
case 'on_or_before':
|
||||||
|
return checkOnOrBefore(propertyCondition, value)
|
||||||
|
case 'on_or_after':
|
||||||
|
return checkOnOrAfter(propertyCondition, value)
|
||||||
|
case 'is_empty':
|
||||||
|
return checkIsEmpty(propertyCondition, value)
|
||||||
|
case 'past_week':
|
||||||
|
return checkPastWeek(propertyCondition, value)
|
||||||
|
case 'past_month':
|
||||||
|
return checkPastMonth(propertyCondition, value)
|
||||||
|
case 'past_year':
|
||||||
|
return checkPastYear(propertyCondition, value)
|
||||||
|
case 'next_week':
|
||||||
|
return checkNextWeek(propertyCondition, value)
|
||||||
|
case 'next_month':
|
||||||
|
return checkNextMonth(propertyCondition, value)
|
||||||
|
case 'next_year':
|
||||||
|
return checkNextYear(propertyCondition, value)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function multiSelectConditionMet (propertyCondition, value) {
|
||||||
|
switch (propertyCondition.operator) {
|
||||||
|
case 'contains':
|
||||||
|
return checkListContains(propertyCondition, value)
|
||||||
|
case 'does_not_contain':
|
||||||
|
return !checkListContains(propertyCondition, value)
|
||||||
|
case 'is_empty':
|
||||||
|
return checkIsEmpty(propertyCondition, value)
|
||||||
|
case 'is_not_empty':
|
||||||
|
return !checkIsEmpty(propertyCondition, value)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function filesConditionMet (propertyCondition, value) {
|
||||||
|
switch (propertyCondition.operator) {
|
||||||
|
case 'is_empty':
|
||||||
|
return checkIsEmpty(propertyCondition, value)
|
||||||
|
case 'is_not_empty':
|
||||||
|
return !checkIsEmpty(propertyCondition, value)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
import { conditionsMet } from './FormLogicConditionChecker'
|
||||||
|
class FormLogicPropertyResolver {
|
||||||
|
conditionsMet = conditionsMet;
|
||||||
|
property = null;
|
||||||
|
formData = null;
|
||||||
|
logic = false;
|
||||||
|
|
||||||
|
constructor (property, formData) {
|
||||||
|
this.property = property
|
||||||
|
this.formData = formData
|
||||||
|
this.logic = (property.logic !== undefined) ? property.logic : false
|
||||||
|
}
|
||||||
|
|
||||||
|
isHidden () {
|
||||||
|
if (!this.logic) {
|
||||||
|
return this.property.hidden
|
||||||
|
}
|
||||||
|
|
||||||
|
const conditionsMet = this.conditionsMet(this.logic.conditions, this.formData)
|
||||||
|
if (conditionsMet && this.property.hidden && this.logic.actions.length > 0 && this.logic.actions.includes('show-block')) {
|
||||||
|
return false
|
||||||
|
} else if (conditionsMet && !this.property.hidden && this.logic.actions.length > 0 && this.logic.actions.includes('hide-block')) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return this.property.hidden
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isRequired () {
|
||||||
|
if (!this.logic) {
|
||||||
|
return this.property.required
|
||||||
|
}
|
||||||
|
|
||||||
|
const conditionsMet = this.conditionsMet(this.logic.conditions, this.formData)
|
||||||
|
if (conditionsMet && this.property.required && this.logic.actions.length > 0 && this.logic.actions.includes('make-it-optional')) {
|
||||||
|
return false
|
||||||
|
} else if (conditionsMet && !this.property.required && this.logic.actions.length > 0 && this.logic.actions.includes('require-answer')) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return this.property.required
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isDisabled () {
|
||||||
|
if (!this.logic) {
|
||||||
|
return this.property.disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
const conditionsMet = this.conditionsMet(this.logic.conditions, this.formData)
|
||||||
|
if (conditionsMet && this.property.disabled && this.logic.actions.length > 0 && this.logic.actions.includes('enable-block')) {
|
||||||
|
return false
|
||||||
|
} else if (conditionsMet && !this.property.disabled && this.logic.actions.length > 0 && this.logic.actions.includes('disable-block')) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return this.property.disabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FormLogicPropertyResolver
|
|
@ -0,0 +1,123 @@
|
||||||
|
import OpenFilters from '../../data/open_filters.json'
|
||||||
|
class FormPropertyLogicRule {
|
||||||
|
property = null
|
||||||
|
logic = null
|
||||||
|
isConditionCorrect = true
|
||||||
|
isActionCorrect = true
|
||||||
|
ACTIONS_VALUES = [
|
||||||
|
'show-block',
|
||||||
|
'hide-block',
|
||||||
|
'make-it-optional',
|
||||||
|
'require-answer',
|
||||||
|
'enable-block',
|
||||||
|
'disable-block'
|
||||||
|
]
|
||||||
|
CONDITION_MAPPING = OpenFilters
|
||||||
|
|
||||||
|
constructor (property) {
|
||||||
|
this.property = property
|
||||||
|
this.logic = (property.logic !== undefined && property.logic) ? property.logic : null
|
||||||
|
}
|
||||||
|
|
||||||
|
isValid () {
|
||||||
|
if (this.logic && this.logic['conditions']) {
|
||||||
|
this.checkConditions(this.logic['conditions'])
|
||||||
|
this.checkActions((this.logic && this.logic['actions']) ? this.logic['actions'] : null)
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.isConditionCorrect && this.isActionCorrect
|
||||||
|
}
|
||||||
|
|
||||||
|
checkConditions (conditions) {
|
||||||
|
if (conditions && conditions['operatorIdentifier']) {
|
||||||
|
if ((conditions['operatorIdentifier'] !== 'and') && (conditions['operatorIdentifier'] !== 'or')) {
|
||||||
|
this.isConditionCorrect = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conditions['operatorIdentifier']['children'] !== undefined || !Array.isArray(conditions['children'])) {
|
||||||
|
this.isConditionCorrect = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
conditions['children'].forEach(childrenCondition => {
|
||||||
|
this.checkConditions(childrenCondition)
|
||||||
|
})
|
||||||
|
} else if (conditions && conditions['identifier']) {
|
||||||
|
this.checkBaseCondition(conditions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkBaseCondition (condition) {
|
||||||
|
if (condition['value'] === undefined ||
|
||||||
|
condition['value']['property_meta'] === undefined ||
|
||||||
|
condition['value']['property_meta']['type'] === undefined ||
|
||||||
|
condition['value']['operator'] === undefined ||
|
||||||
|
condition['value']['value'] === undefined
|
||||||
|
) {
|
||||||
|
this.isConditionCorrect = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const typeField = condition['value']['property_meta']['type']
|
||||||
|
const operator = condition['value']['operator']
|
||||||
|
const value = condition['value']['value']
|
||||||
|
|
||||||
|
if (this.CONDITION_MAPPING[typeField] === undefined ||
|
||||||
|
this.CONDITION_MAPPING[typeField]['comparators'][operator] === undefined
|
||||||
|
) {
|
||||||
|
this.isConditionCorrect = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const type = this.CONDITION_MAPPING[typeField]['comparators'][operator]['expected_type']
|
||||||
|
if (Array.isArray(type)) {
|
||||||
|
let foundCorrectType = false
|
||||||
|
type.forEach(subtype => {
|
||||||
|
if (this.valueHasCorrectType(subtype, value)) {
|
||||||
|
foundCorrectType = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (!foundCorrectType) {
|
||||||
|
this.isConditionCorrect = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!this.valueHasCorrectType(type, value)) {
|
||||||
|
this.isConditionCorrect = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
valueHasCorrectType (type, value) {
|
||||||
|
if (
|
||||||
|
(type === 'string' && typeof value !== 'string') ||
|
||||||
|
(type === 'boolean' && typeof value !== 'boolean') ||
|
||||||
|
(type === 'number' && typeof value !== 'number') ||
|
||||||
|
(type === 'object' && !Array.isArray(value))
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
checkActions (conditions) {
|
||||||
|
if (Array.isArray(conditions) && conditions.length > 0) {
|
||||||
|
conditions.forEach(val => {
|
||||||
|
if (this.ACTIONS_VALUES.indexOf(val) === -1 ||
|
||||||
|
(['nf-text', 'nf-code', 'nf-page-break', 'nf-divider', 'nf-image'].indexOf(this.property["type"]) > -1 && ['hide-block', 'show-block'].indexOf(val) === -1) ||
|
||||||
|
(this.property["hidden"] !== undefined && this.property["hidden"] && ['show-block', 'require-answer'].indexOf(val) === -1) ||
|
||||||
|
(this.property["required"] !== undefined && this.property["required"] && ['make-it-optional', 'hide-block', 'disable-block'].indexOf(val) === -1) ||
|
||||||
|
(this.property["disabled"] !== undefined && this.property["disabled"] && ['enable-block', 'require-answer', 'make-it-optional'].indexOf(val) === -1)
|
||||||
|
) {
|
||||||
|
this.isActionCorrect = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.isActionCorrect = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FormPropertyLogicRule
|
|
@ -1,4 +1,4 @@
|
||||||
import { themes } from '~/config/form-themes.js'
|
import { themes } from '~/lib/forms/form-themes.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
|
|
@ -11,6 +11,16 @@ const modules = [
|
||||||
if (opnformConfig.sentry_dsn) {
|
if (opnformConfig.sentry_dsn) {
|
||||||
modules.push('@nuxtjs/sentry')
|
modules.push('@nuxtjs/sentry')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cachedRoutes = [
|
||||||
|
'/',
|
||||||
|
'/ai-form-builder',
|
||||||
|
'/login',
|
||||||
|
'/register',
|
||||||
|
'/privacy-policy',
|
||||||
|
'/terms-conditions',
|
||||||
|
].reduce((acc, curr) => (acc[curr] = {swr: 60 * 60}, acc), {});
|
||||||
|
|
||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
devtools: {enabled: true},
|
devtools: {enabled: true},
|
||||||
css: ['~/scss/app.scss'],
|
css: ['~/scss/app.scss'],
|
||||||
|
@ -39,17 +49,11 @@ export default defineNuxtConfig({
|
||||||
path: '~/components/global',
|
path: '~/components/global',
|
||||||
pathPrefix: false,
|
pathPrefix: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '~/components/pages',
|
||||||
|
pathPrefix: false,
|
||||||
|
},
|
||||||
'~/components',
|
'~/components',
|
||||||
],
|
],
|
||||||
routeRules: {
|
routeRules: { ... cachedRoutes}
|
||||||
'/ai-form-builder': {
|
|
||||||
swr: 60 * 60
|
|
||||||
},
|
|
||||||
'/privacy-policy': {
|
|
||||||
swr: 60 * 60
|
|
||||||
},
|
|
||||||
'/terms-conditions': {
|
|
||||||
swr: 60 * 60
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -54,7 +54,6 @@ import { computed } from 'vue'
|
||||||
import { useFormsStore } from '../../stores/forms'
|
import { useFormsStore } from '../../stores/forms'
|
||||||
import { useRecordsStore } from '../../stores/records'
|
import { useRecordsStore } from '../../stores/records'
|
||||||
import OpenCompleteForm from '../../components/open/forms/OpenCompleteForm.vue'
|
import OpenCompleteForm from '../../components/open/forms/OpenCompleteForm.vue'
|
||||||
import Cookies from 'js-cookie'
|
|
||||||
import sha256 from 'js-sha256'
|
import sha256 from 'js-sha256'
|
||||||
import SeoMeta from '../../mixins/seo-meta.js'
|
import SeoMeta from '../../mixins/seo-meta.js'
|
||||||
|
|
||||||
|
@ -169,7 +168,9 @@ export default {
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
passwordEntered (password) {
|
passwordEntered (password) {
|
||||||
Cookies.set('password-' + this.form.slug, sha256(password), { expires: 7, sameSite: 'None', secure: true })
|
if (process.client) {
|
||||||
|
useCookie('password-' + this.form.slug, {maxAge: { expires: 60*60*7}}).value = sha256(password)
|
||||||
|
}
|
||||||
loadForm(this.formSlug).then(() => {
|
loadForm(this.formSlug).then(() => {
|
||||||
if (this.form.is_password_protected) {
|
if (this.form.is_password_protected) {
|
||||||
this.$refs['open-complete-form'].addPasswordError('Invalid password.')
|
this.$refs['open-complete-form'].addPasswordError('Invalid password.')
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
</breadcrumb>
|
</breadcrumb>
|
||||||
|
|
||||||
<div v-if="templatesLoading" class="text-center my-4">
|
<div v-if="templatesLoading" class="text-center my-4">
|
||||||
<Loader class="h-6 w-6 text-nt-blue mx-auto" />
|
<Loader class="h-6 w-6 text-nt-blue mx-auto"/>
|
||||||
</div>
|
</div>
|
||||||
<p v-else-if="template === null || !template" class="text-center my-4">
|
<p v-else-if="template === null || !template" class="text-center my-4">
|
||||||
We could not find this template.
|
We could not find this template.
|
||||||
|
@ -73,7 +73,8 @@
|
||||||
<div class="absolute bottom-0 translate-y-full inset-x-0">
|
<div class="absolute bottom-0 translate-y-full inset-x-0">
|
||||||
<div class="px-4 mx-auto sm:px-6 lg:px-8 max-w-7xl -mt-[20px]">
|
<div class="px-4 mx-auto sm:px-6 lg:px-8 max-w-7xl -mt-[20px]">
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
<v-button v-track.use_template_button_clicked class="mx-auto w-full max-w-[300px]" :to="{path: createFormWithTemplateUrl}">
|
<v-button v-track.use_template_button_clicked class="mx-auto w-full max-w-[300px]"
|
||||||
|
:to="{path: createFormWithTemplateUrl}">
|
||||||
Use this template
|
Use this template
|
||||||
</v-button>
|
</v-button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -91,7 +92,7 @@
|
||||||
<section class="pt-20 pb-12 bg-white sm:pb-16">
|
<section class="pt-20 pb-12 bg-white sm:pb-16">
|
||||||
<div class="px-4 mx-auto sm:px-6 lg:px-8 max-w-7xl">
|
<div class="px-4 mx-auto sm:px-6 lg:px-8 max-w-7xl">
|
||||||
<div class="max-w-2xl mx-auto mt-16 space-y-12 sm:mt-16 sm:space-y-16">
|
<div class="max-w-2xl mx-auto mt-16 space-y-12 sm:mt-16 sm:space-y-16">
|
||||||
<div class="nf-text" v-html="template.description" />
|
<div class="nf-text" v-html="template.description"/>
|
||||||
|
|
||||||
<template v-if="template.questions.length > 0">
|
<template v-if="template.questions.length > 0">
|
||||||
<hr class="mt-12 border-gray-200">
|
<hr class="mt-12 border-gray-200">
|
||||||
|
@ -109,7 +110,7 @@
|
||||||
<dt class="font-semibold text-gray-900 dark:text-gray-100">
|
<dt class="font-semibold text-gray-900 dark:text-gray-100">
|
||||||
{{ ques.question }}
|
{{ ques.question }}
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="mt-2 leading-6 text-gray-600 dark:text-gray-400" v-html="ques.answer" />
|
<dd class="mt-2 leading-6 text-gray-600 dark:text-gray-400" v-html="ques.answer"/>
|
||||||
</div>
|
</div>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
|
@ -130,7 +131,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 gap-8 mt-8 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 sm:gap-y-12">
|
<div class="grid grid-cols-1 gap-8 mt-8 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 sm:gap-y-12">
|
||||||
<single-template v-for="related in template.related_templates" :key="related" :slug="related" />
|
<single-template v-for="related in template.related_templates" :key="related.id" :template="related"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
@ -197,10 +198,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Form from 'vform'
|
import Form from 'vform'
|
||||||
import { computed } from 'vue'
|
import {computed} from 'vue'
|
||||||
import { useAuthStore } from '../../stores/auth'
|
|
||||||
import { useTemplatesStore } from '../../stores/templates'
|
|
||||||
import OpenFormFooter from '../../components/pages/OpenFormFooter.vue'
|
|
||||||
import OpenCompleteForm from '../../components/open/forms/OpenCompleteForm.vue'
|
import OpenCompleteForm from '../../components/open/forms/OpenCompleteForm.vue'
|
||||||
import Breadcrumb from '~/components/global/Breadcrumb.vue'
|
import Breadcrumb from '~/components/global/Breadcrumb.vue'
|
||||||
import SeoMeta from '../../mixins/seo-meta.js'
|
import SeoMeta from '../../mixins/seo-meta.js'
|
||||||
|
@ -210,43 +208,43 @@ import FormTemplateModal from '../../components/open/forms/components/templates/
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
||||||
components: { Breadcrumb, OpenFormFooter, OpenCompleteForm, TemplateTags, SingleTemplate, FormTemplateModal },
|
components: {Breadcrumb, OpenCompleteForm, TemplateTags, SingleTemplate, FormTemplateModal},
|
||||||
mixins: [SeoMeta],
|
mixins: [SeoMeta],
|
||||||
|
|
||||||
beforeRouteEnter (to, from, next) {
|
setup() {
|
||||||
const templatesStore = useTemplatesStore()
|
|
||||||
if (to.params?.slug) {
|
|
||||||
templatesStore.loadTemplate(to.params?.slug)
|
|
||||||
templatesStore.loadTypesAndIndustries()
|
|
||||||
}
|
|
||||||
next()
|
|
||||||
},
|
|
||||||
|
|
||||||
setup () {
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const templatesStore = useTemplatesStore()
|
const templatesStore = useTemplatesStore()
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const slug = computed(() => route.params.slug)
|
||||||
|
if (slug) {
|
||||||
|
templatesStore.loadTemplate(slug)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
templatesStore,
|
templatesStore,
|
||||||
authenticated : computed(() => authStore.check),
|
authenticated: computed(() => authStore.check),
|
||||||
user : computed(() => authStore.user),
|
user: computed(() => authStore.user),
|
||||||
templatesLoading : computed(() => templatesStore.loading)
|
templatesLoading: computed(() => templatesStore.loading)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
data () {
|
data() {
|
||||||
return {
|
return {
|
||||||
showFormTemplateModal: false
|
showFormTemplateModal: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted () {},
|
mounted() {
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
cleanQuotes (str) {
|
cleanQuotes(str) {
|
||||||
// Remove starting and ending quotes if any
|
// Remove starting and ending quotes if any
|
||||||
return (str) ? str.replace(/^"/, '').replace(/"$/, '') : ''
|
return (str) ? str.replace(/^"/, '').replace(/"$/, '') : ''
|
||||||
},
|
},
|
||||||
copyTemplateUrl(){
|
copyTemplateUrl() {
|
||||||
const str = this.template.share_url
|
const str = this.template.share_url
|
||||||
const el = document.createElement('textarea')
|
const el = document.createElement('textarea')
|
||||||
el.value = str
|
el.value = str
|
||||||
|
@ -259,41 +257,41 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
breadcrumbs () {
|
breadcrumbs() {
|
||||||
if (!this.template) {
|
if (!this.template) {
|
||||||
return [{ route: { name: 'templates' }, label: 'Templates' }]
|
return [{route: {name: 'templates'}, label: 'Templates'}]
|
||||||
}
|
}
|
||||||
return [{ route: { name: 'templates' }, label: 'Templates' }, { label: this.template.name }]
|
return [{route: {name: 'templates'}, label: 'Templates'}, {label: this.template.name}]
|
||||||
},
|
},
|
||||||
template () {
|
template() {
|
||||||
return this.templatesStore.getBySlug(this.$route.params.slug)
|
return this.templatesStore.getBySlug(this.$route.params.slug)
|
||||||
},
|
},
|
||||||
form () {
|
form() {
|
||||||
return this.template ? new Form(this.template.structure) : null
|
return this.template ? new Form(this.template.structure) : null
|
||||||
},
|
},
|
||||||
canEditTemplate () {
|
canEditTemplate() {
|
||||||
return this.user && this.template && (this.user.admin || this.user.template_editor || this.template.creator_id === this.user.id)
|
return this.user && this.template && (this.user.admin || this.user.template_editor || this.template.creator_id === this.user.id)
|
||||||
},
|
},
|
||||||
metaTitle () {
|
metaTitle() {
|
||||||
return this.template ? this.template.name : 'Form Template'
|
return this.template ? this.template.name : 'Form Template'
|
||||||
},
|
},
|
||||||
metaDescription () {
|
metaDescription() {
|
||||||
if (!this.template) return null
|
if (!this.template) return null
|
||||||
// take the first 140 characters of the description
|
// take the first 140 characters of the description
|
||||||
return this.template.short_description?.substring(0, 140) + '... | Customize any template and create your own form in minutes.'
|
return this.template.short_description?.substring(0, 140) + '... | Customize any template and create your own form in minutes.'
|
||||||
},
|
},
|
||||||
metaImage () {
|
metaImage() {
|
||||||
if (!this.template) return null
|
if (!this.template) return null
|
||||||
return this.template.image_url
|
return this.template.image_url
|
||||||
},
|
},
|
||||||
metaTags () {
|
metaTags() {
|
||||||
if (!this.template) {
|
if (!this.template) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return this.template.publicly_listed ? [] : [{ name: 'robots', content: 'noindex' }]
|
return this.template.publicly_listed ? [] : [{name: 'robots', content: 'noindex'}]
|
||||||
},
|
},
|
||||||
createFormWithTemplateUrl () {
|
createFormWithTemplateUrl() {
|
||||||
if(this.authenticated) {
|
if (this.authenticated) {
|
||||||
return '/forms/create?template=' + this.template?.slug
|
return '/forms/create?template=' + this.template?.slug
|
||||||
}
|
}
|
||||||
return '/forms/create/guest?template=' + this.template?.slug
|
return '/forms/create/guest?template=' + this.template?.slug
|
||||||
|
|
|
@ -13,21 +13,18 @@
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<templates-list />
|
<templates-list :templates="templates" :loading="loading" />
|
||||||
|
|
||||||
<open-form-footer class="mt-8 border-t"/>
|
<open-form-footer class="mt-8 border-t"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import OpenFormFooter from '../components/pages/OpenFormFooter.vue'
|
import SeoMeta from '../../mixins/seo-meta.js'
|
||||||
import TemplatesList from '../components/pages/templates/TemplatesList.vue'
|
|
||||||
import SeoMeta from '../mixins/seo-meta.js'
|
|
||||||
import {useTemplatesStore} from "~/stores/templates.js";
|
import {useTemplatesStore} from "~/stores/templates.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
||||||
components: { OpenFormFooter, TemplatesList },
|
|
||||||
mixins: [SeoMeta],
|
mixins: [SeoMeta],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
|
@ -38,16 +35,10 @@ export default {
|
||||||
setup() {
|
setup() {
|
||||||
const templatesStore = useTemplatesStore()
|
const templatesStore = useTemplatesStore()
|
||||||
templatesStore.loadAll()
|
templatesStore.loadAll()
|
||||||
|
return {
|
||||||
|
templates: templatesStore.content,
|
||||||
|
loading: templatesStore.loading
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
data () {
|
|
||||||
return {}
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted () {},
|
|
||||||
|
|
||||||
computed: {},
|
|
||||||
|
|
||||||
methods: {}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
|
@ -3,7 +3,7 @@ import {defineStore} from 'pinia'
|
||||||
export const templatesEndpoint = '/templates'
|
export const templatesEndpoint = '/templates'
|
||||||
export const useTemplatesStore = defineStore('templates', {
|
export const useTemplatesStore = defineStore('templates', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
content: [],
|
content: [], // TODO: convert to a map
|
||||||
industries: {},
|
industries: {},
|
||||||
types: {},
|
types: {},
|
||||||
allLoaded: false,
|
allLoaded: false,
|
||||||
|
@ -61,12 +61,14 @@ export const useTemplatesStore = defineStore('templates', {
|
||||||
},
|
},
|
||||||
async loadTypesAndIndustries() {
|
async loadTypesAndIndustries() {
|
||||||
if (Object.keys(this.industries).length === 0 || Object.keys(this.types).length === 0) {
|
if (Object.keys(this.industries).length === 0 || Object.keys(this.types).length === 0) {
|
||||||
const files = import.meta.glob('~/data/forms/templates/*.json')
|
// const files = import.meta.glob('~/data/forms/templates/*.json')
|
||||||
this.industries = await files['/data/forms/templates/industries.json']()
|
// console.log(await files['/data/forms/templates/industries.json']())
|
||||||
this.types = await files['/data/forms/templates/types.json']()
|
// this.industries = await files['/data/forms/templates/industries.json']()
|
||||||
|
// this.types = await files['/data/forms/templates/types.json']()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
loadTemplate(slug) {
|
loadTemplate(slug) {
|
||||||
|
console.log('loading template',slug)
|
||||||
this.startLoading()
|
this.startLoading()
|
||||||
this.loadTypesAndIndustries()
|
this.loadTypesAndIndustries()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue