Fixed form creation
This commit is contained in:
parent
b598a16406
commit
af5656ce81
|
@ -36,8 +36,6 @@ import { inputProps, useFormInput } from './useFormInput.js'
|
||||||
import InputWrapper from './components/InputWrapper.vue'
|
import InputWrapper from './components/InputWrapper.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CodeInput',
|
|
||||||
|
|
||||||
components: { InputWrapper, codemirror },
|
components: { InputWrapper, codemirror },
|
||||||
props: {
|
props: {
|
||||||
...inputProps
|
...inputProps
|
|
@ -46,7 +46,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { inputProps, useFormInput } from './useFormInput.js'
|
import { inputProps, useFormInput } from './useFormInput.js'
|
||||||
import InputWrapper from './components/InputWrapper.vue'
|
import InputWrapper from './components/InputWrapper.vue'
|
||||||
import { fixedClasses } from '../../plugins/config/vue-tailwind/datePicker.js'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'DateInput',
|
name: 'DateInput',
|
||||||
|
@ -68,7 +67,6 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
fixedClasses: fixedClasses,
|
|
||||||
fromDate: null,
|
fromDate: null,
|
||||||
toDate: null
|
toDate: null
|
||||||
}),
|
}),
|
||||||
|
@ -142,7 +140,6 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fixedClasses.input = this.theme.default.input
|
|
||||||
this.setInputColor()
|
this.setInputColor()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="v-select relative">
|
<div class="v-select relative" ref="select">
|
||||||
<span class="inline-block w-full rounded-md">
|
<span class="inline-block w-full rounded-md">
|
||||||
<button type="button" aria-haspopup="listbox" aria-expanded="true" aria-labelledby="listbox-label"
|
<button type="button" aria-haspopup="listbox" aria-expanded="true" aria-labelledby="listbox-label"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
<collapsible v-model="isOpen"
|
<collapsible v-model="isOpen" @click-away="onClickAway"
|
||||||
class="absolute mt-1 rounded-md bg-white dark:bg-notion-dark-light shadow-xl z-10"
|
class="absolute mt-1 rounded-md bg-white dark:bg-notion-dark-light shadow-xl z-10"
|
||||||
:class="dropdownClass"
|
:class="dropdownClass"
|
||||||
>
|
>
|
||||||
|
@ -155,6 +155,12 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
onClickAway (event) {
|
||||||
|
// Check that event target isn't children of dropdown
|
||||||
|
if (this.$refs.select && !this.$refs.select.contains(event.target)) {
|
||||||
|
this.isOpen = false
|
||||||
|
}
|
||||||
|
},
|
||||||
isSelected (value) {
|
isSelected (value) {
|
||||||
if (!this.modelValue) return false
|
if (!this.modelValue) return false
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,6 @@ const open = (event) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const close = (event) => {
|
const close = (event) => {
|
||||||
console.log('closing')
|
|
||||||
isOpen.value = false
|
isOpen.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -144,15 +144,14 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
setup () {
|
setup () {
|
||||||
const authStore = useAuthStore()
|
|
||||||
const formsStore = useFormsStore()
|
|
||||||
const workspacesStore = useWorkspacesStore()
|
|
||||||
const {openCrisp} = useCrisp()
|
const {openCrisp} = useCrisp()
|
||||||
|
const authStore = useAuthStore()
|
||||||
return {
|
return {
|
||||||
authStore,
|
authStore,
|
||||||
formsStore,
|
|
||||||
workspacesStore,
|
|
||||||
openCrisp,
|
openCrisp,
|
||||||
|
appStore: useAppStore(),
|
||||||
|
formsStore: useFormsStore(),
|
||||||
|
workspacesStore: useWorkspacesStore(),
|
||||||
config: useConfig(),
|
config: useConfig(),
|
||||||
user: computed(() => authStore.user),
|
user: computed(() => authStore.user),
|
||||||
isIframe: useIsIframe(),
|
isIframe: useIsIframe(),
|
||||||
|
@ -189,7 +188,7 @@ export default {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return !this.$root.navbarHidden
|
return !this.appStore.navbarHidden
|
||||||
},
|
},
|
||||||
userOnboarded () {
|
userOnboarded () {
|
||||||
return this.user && this.user.workspaces_count > 0
|
return this.user && this.user.workspaces_count > 0
|
||||||
|
|
|
@ -79,7 +79,8 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
switchWorkspace (workspace) {
|
switchWorkspace (workspace) {
|
||||||
this.workspacesStore.setCurrentId(workspace.id)
|
this.workspacesStore.setCurrentId(workspace.id)
|
||||||
this.$refs.dropdown.close()
|
this.formsStore.resetState()
|
||||||
|
this.formsStore.load(workspace.id)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
if (route.name !== 'home') {
|
if (route.name !== 'home') {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<transition @leave="(el,done) => motions.slide.leave(done)">
|
<transition @leave="(el,done) => sidebarMotion.leave(done)">
|
||||||
<div v-if="show" v-motion-slide-right="'slide'"
|
<div v-if="show" ref="sidebar"
|
||||||
class="absolute shadow-lg shadow-gray-800/30 top-0 h-[calc(100vh-53px)] right-0 lg:shadow-none lg:relative bg-white w-full md:w-1/2 lg:w-2/5 border-l overflow-y-scroll md:max-w-[20rem] flex-shrink-0 z-50"
|
class="absolute shadow-lg shadow-gray-800/30 top-0 h-[calc(100vh-53px)] right-0 lg:shadow-none lg:relative bg-white w-full md:w-1/2 lg:w-2/5 border-l overflow-y-scroll md:max-w-[20rem] flex-shrink-0 z-50"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
|
@ -8,21 +8,25 @@
|
||||||
</transition>
|
</transition>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import { useMotions } from '@vueuse/motion'
|
import {slideRight, useMotion} from "@vueuse/motion"
|
||||||
|
import {watch} from "vue";
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
name: 'EditorRightSidebar',
|
|
||||||
props: {
|
|
||||||
show: {
|
show: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
setup (props) {
|
|
||||||
return {
|
const sidebar = ref(null)
|
||||||
motions: useMotions()
|
const sidebarMotion = ref(null)
|
||||||
|
watch(() => props.show, (newVal) => {
|
||||||
|
if (newVal) {
|
||||||
|
nextTick(() => {
|
||||||
|
sidebarMotion.value = useMotion(sidebar.value, slideRight)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -77,17 +77,12 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="flex justify-center items-center">
|
<div v-else class="flex justify-center items-center p-8">
|
||||||
<Loader class="w-6 h-6" />
|
<Loader class="w-6 h-6" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { computed } from 'vue'
|
|
||||||
import { useAuthStore } from '../../../../stores/auth'
|
|
||||||
import { useFormsStore } from '../../../../stores/forms'
|
|
||||||
import { useWorkingFormStore } from '../../../../stores/working_form'
|
|
||||||
import { useWorkspacesStore } from '../../../../stores/workspaces'
|
|
||||||
import FormEditorSidebar from './form-components/FormEditorSidebar.vue'
|
import FormEditorSidebar from './form-components/FormEditorSidebar.vue'
|
||||||
import FormErrorModal from './form-components/FormErrorModal.vue'
|
import FormErrorModal from './form-components/FormErrorModal.vue'
|
||||||
import FormInformation from './form-components/FormInformation.vue'
|
import FormInformation from './form-components/FormInformation.vue'
|
||||||
|
@ -100,8 +95,7 @@ import FormEditorPreview from './form-components/FormEditorPreview.vue'
|
||||||
import FormSecurityPrivacy from './form-components/FormSecurityPrivacy.vue'
|
import FormSecurityPrivacy from './form-components/FormSecurityPrivacy.vue'
|
||||||
import FormCustomSeo from './form-components/FormCustomSeo.vue'
|
import FormCustomSeo from './form-components/FormCustomSeo.vue'
|
||||||
import FormAccess from './form-components/FormAccess.vue'
|
import FormAccess from './form-components/FormAccess.vue'
|
||||||
import saveUpdateAlert from '../../../../mixins/forms/saveUpdateAlert.js'
|
import {validatePropertiesLogic} from "~/composables/forms/validatePropertiesLogic.js"
|
||||||
import fieldsLogic from '../../../../mixins/forms/fieldsLogic.js'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FormEditor',
|
name: 'FormEditor',
|
||||||
|
@ -119,7 +113,6 @@ export default {
|
||||||
FormCustomSeo,
|
FormCustomSeo,
|
||||||
FormAccess
|
FormAccess
|
||||||
},
|
},
|
||||||
mixins: [saveUpdateAlert, fieldsLogic],
|
|
||||||
props: {
|
props: {
|
||||||
isEdit: {
|
isEdit: {
|
||||||
required: false,
|
required: false,
|
||||||
|
@ -144,15 +137,18 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
setup () {
|
setup () {
|
||||||
const authStore = useAuthStore()
|
const {user} = storeToRefs(useAuthStore())
|
||||||
const formsStore = useFormsStore()
|
const formsStore = useFormsStore()
|
||||||
const workingFormStore = useWorkingFormStore()
|
const {content: form} = storeToRefs(useWorkingFormStore())
|
||||||
const workspacesStore = useWorkspacesStore()
|
const {getCurrent: workspace} = storeToRefs(useWorkspacesStore())
|
||||||
return {
|
return {
|
||||||
|
appStore: useAppStore(),
|
||||||
|
crisp: useCrisp(),
|
||||||
|
amplitude: useAmplitude(),
|
||||||
|
workspace,
|
||||||
formsStore,
|
formsStore,
|
||||||
workingFormStore,
|
form,
|
||||||
workspacesStore,
|
user,
|
||||||
user: computed(() => authStore.user)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -166,21 +162,9 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
form: {
|
|
||||||
get () {
|
|
||||||
return this.workingFormStore.content
|
|
||||||
},
|
|
||||||
/* We add a setter */
|
|
||||||
set (value) {
|
|
||||||
this.workingFormStore.set(value)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
createdForm () {
|
createdForm () {
|
||||||
return this.formsStore.getBySlug(this.createdFormSlug)
|
return this.formsStore.getBySlug(this.createdFormSlug)
|
||||||
},
|
},
|
||||||
workspace () {
|
|
||||||
return this.workspacesStore.getCurrent()
|
|
||||||
},
|
|
||||||
steps () {
|
steps () {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
@ -223,23 +207,29 @@ export default {
|
||||||
|
|
||||||
mounted () {
|
mounted () {
|
||||||
this.$emit('mounted')
|
this.$emit('mounted')
|
||||||
this.$root.hideNavbar()
|
this.appStore.hideNavbar()
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeUnmount () {
|
beforeUnmount () {
|
||||||
this.$root.hideNavbar(false)
|
this.appStore.showNavbar()
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
displayFormModificationAlert (responseData) {
|
||||||
|
if (responseData.form && responseData.form.cleanings && Object.keys(responseData.form.cleanings).length > 0) {
|
||||||
|
this.alertWarning(responseData.message)
|
||||||
|
} else {
|
||||||
|
this.alertSuccess(responseData.message)
|
||||||
|
}
|
||||||
|
},
|
||||||
openCrisp () {
|
openCrisp () {
|
||||||
window.$crisp.push(['do', 'chat:show'])
|
this.crisp.openChat()
|
||||||
window.$crisp.push(['do', 'chat:open'])
|
|
||||||
},
|
},
|
||||||
showValidationErrors () {
|
showValidationErrors () {
|
||||||
this.showFormErrorModal = true
|
this.showFormErrorModal = true
|
||||||
},
|
},
|
||||||
saveForm () {
|
saveForm () {
|
||||||
this.form.properties = this.validateFieldsLogic(this.form.properties)
|
this.form.properties = validatePropertiesLogic(this.form.properties)
|
||||||
if (this.isGuest) {
|
if (this.isGuest) {
|
||||||
this.saveFormGuest()
|
this.saveFormGuest()
|
||||||
} else if (this.isEdit) {
|
} else if (this.isEdit) {
|
||||||
|
@ -253,12 +243,11 @@ export default {
|
||||||
|
|
||||||
this.updateFormLoading = true
|
this.updateFormLoading = true
|
||||||
this.validationErrorResponse = null
|
this.validationErrorResponse = null
|
||||||
this.form.put('/api/open/forms/{id}/'.replace('{id}', this.form.id)).then((response) => {
|
this.form.put('/open/forms/{id}/'.replace('{id}', this.form.id)).then((data) => {
|
||||||
const data = response.data
|
|
||||||
this.formsStore.addOrUpdate(data.form)
|
this.formsStore.addOrUpdate(data.form)
|
||||||
this.$emit('on-save')
|
this.$emit('on-save')
|
||||||
this.$router.push({ name: 'forms.show', params: { slug: this.form.slug } })
|
this.$router.push({ name: 'forms.show', params: { slug: this.form.slug } })
|
||||||
this.$logEvent('form_saved', { form_id: this.form.id, form_slug: this.form.slug })
|
this.amplitude.logEvent('form_saved', { form_id: this.form.id, form_slug: this.form.slug })
|
||||||
this.displayFormModificationAlert(data)
|
this.displayFormModificationAlert(data)
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
if (error.response.status === 422) {
|
if (error.response.status === 422) {
|
||||||
|
@ -275,27 +264,27 @@ export default {
|
||||||
this.validationErrorResponse = null
|
this.validationErrorResponse = null
|
||||||
|
|
||||||
this.updateFormLoading = true
|
this.updateFormLoading = true
|
||||||
this.form.post('/api/open/forms').then((response) => {
|
this.form.post('/open/forms').then((response) => {
|
||||||
this.formsStore.addOrUpdate(response.data.form)
|
this.formsStore.save(response.form)
|
||||||
this.$emit('on-save')
|
this.$emit('on-save')
|
||||||
this.createdFormSlug = response.data.form.slug
|
this.createdFormSlug = response.form.slug
|
||||||
|
|
||||||
this.$logEvent('form_created', { form_id: response.data.form.id, form_slug: response.data.form.slug })
|
this.amplitude.logEvent('form_created', { form_id: response.form.id, form_slug: response.form.slug })
|
||||||
this.$crisp.push(['set', 'session:event', [[['form_created', {
|
this.crisp.pushEvent('form_created',{
|
||||||
form_id: response.data.form.id,
|
form_id: response.form.id,
|
||||||
form_slug: response.data.form.slug
|
form_slug: response.form.slug
|
||||||
}, 'blue']]]])
|
})
|
||||||
this.displayFormModificationAlert(response.data)
|
this.displayFormModificationAlert(response)
|
||||||
this.$router.push({
|
useRouter().push({
|
||||||
name: 'forms.show',
|
name: 'forms-show',
|
||||||
params: {
|
params: {
|
||||||
slug: this.createdForm.slug,
|
slug: this.createdForm.slug,
|
||||||
new_form: response.data.users_first_form
|
new_form: response.users_first_form
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
if (error.response && error.response.status === 422) {
|
if (error.response && error.response.status === 422) {
|
||||||
this.validationErrorResponse = error.response.data
|
this.validationErrorResponse = error.response
|
||||||
this.showValidationErrors()
|
this.showValidationErrors()
|
||||||
}
|
}
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
|
|
|
@ -166,8 +166,6 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { computed } from 'vue'
|
|
||||||
import { useWorkingFormStore } from '../../../../stores/working_form'
|
|
||||||
import draggable from 'vuedraggable'
|
import draggable from 'vuedraggable'
|
||||||
import ProTag from '~/components/global/ProTag.vue'
|
import ProTag from '~/components/global/ProTag.vue'
|
||||||
import clonedeep from 'clone-deep'
|
import clonedeep from 'clone-deep'
|
||||||
|
@ -187,6 +185,7 @@ export default {
|
||||||
setup () {
|
setup () {
|
||||||
const workingFormStore = useWorkingFormStore()
|
const workingFormStore = useWorkingFormStore()
|
||||||
return {
|
return {
|
||||||
|
route: useRoute(),
|
||||||
workingFormStore
|
workingFormStore
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -279,7 +278,7 @@ export default {
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
init () {
|
init () {
|
||||||
if (this.$route.name === 'forms-create' || this.$route.name === 'forms-create-guest') { // Set Default fields
|
if (this.route.name === 'forms-create' || this.route.name === 'forms-create-guest') { // Set Default fields
|
||||||
this.formFields = (this.form.properties.length > 0) ? clonedeep(this.form.properties) : this.getDefaultFields()
|
this.formFields = (this.form.properties.length > 0) ? clonedeep(this.form.properties) : this.getDefaultFields()
|
||||||
} else {
|
} else {
|
||||||
this.formFields = clonedeep(this.form.properties).map((field) => {
|
this.formFields = clonedeep(this.form.properties).map((field) => {
|
||||||
|
|
|
@ -73,7 +73,9 @@ export default {
|
||||||
|
|
||||||
setup () {
|
setup () {
|
||||||
const workingFormStore = useWorkingFormStore()
|
const workingFormStore = useWorkingFormStore()
|
||||||
|
const {content: form} = storeToRefs(workingFormStore)
|
||||||
return {
|
return {
|
||||||
|
form,
|
||||||
workingFormStore,
|
workingFormStore,
|
||||||
selectedFieldIndex : computed(() => workingFormStore.selectedFieldIndex)
|
selectedFieldIndex : computed(() => workingFormStore.selectedFieldIndex)
|
||||||
}
|
}
|
||||||
|
@ -171,16 +173,6 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
form: {
|
|
||||||
get () {
|
|
||||||
return this.workingFormStore.content
|
|
||||||
},
|
|
||||||
/* We add a setter */
|
|
||||||
set (value) {
|
|
||||||
this.workingFormStore.set(value)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
defaultBlockNames () {
|
defaultBlockNames () {
|
||||||
return {
|
return {
|
||||||
text: 'Your name',
|
text: 'Your name',
|
||||||
|
@ -230,6 +222,7 @@ export default {
|
||||||
}
|
}
|
||||||
newBlock.help_position = 'below_input'
|
newBlock.help_position = 'below_input'
|
||||||
if (this.selectedFieldIndex === null || this.selectedFieldIndex === undefined) {
|
if (this.selectedFieldIndex === null || this.selectedFieldIndex === undefined) {
|
||||||
|
console.log('------',this.form)
|
||||||
const newFields = clonedeep(this.form.properties)
|
const newFields = clonedeep(this.form.properties)
|
||||||
newFields.push(newBlock)
|
newFields.push(newBlock)
|
||||||
this.form.properties = newFields
|
this.form.properties = newFields
|
||||||
|
|
|
@ -139,7 +139,9 @@ export default {
|
||||||
props: {},
|
props: {},
|
||||||
setup () {
|
setup () {
|
||||||
const workingFormStore = useWorkingFormStore()
|
const workingFormStore = useWorkingFormStore()
|
||||||
|
const {content: form} = storeToRefs(workingFormStore)
|
||||||
return {
|
return {
|
||||||
|
form,
|
||||||
workingFormStore
|
workingFormStore
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
<editor-options-panel name="Custom Code" :already-opened="false" :has-pro-tag="true">
|
<editor-options-panel name="Custom Code" :already-opened="false" :has-pro-tag="true">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M14 2.26953V6.40007C14 6.96012 14 7.24015 14.109 7.45406C14.2049 7.64222 14.3578 7.7952 14.546 7.89108C14.7599 8.00007 15.0399 8.00007 15.6 8.00007H19.7305M14 17.5L16.5 15L14 12.5M10 12.5L7.5 15L10 17.5M20 9.98822V17.2C20 18.8802 20 19.7202 19.673 20.362C19.3854 20.9265 18.9265 21.3854 18.362 21.673C17.7202 22 16.8802 22 15.2 22H8.8C7.11984 22 6.27976 22 5.63803 21.673C5.07354 21.3854 4.6146 20.9265 4.32698 20.362C4 19.7202 4 18.8802 4 17.2V6.8C4 5.11984 4 4.27976 4.32698 3.63803C4.6146 3.07354 5.07354 2.6146 5.63803 2.32698C6.27976 2 7.11984 2 8.8 2H12.0118C12.7455 2 13.1124 2 13.4577 2.08289C13.7638 2.15638 14.0564 2.27759 14.3249 2.44208C14.6276 2.6276 14.887 2.88703 15.4059 3.40589L18.5941 6.59411C19.113 7.11297 19.3724 7.3724 19.5579 7.67515C19.7224 7.94356 19.8436 8.2362 19.9171 8.5423C20 8.88757 20 9.25445 20 9.98822Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<path
|
||||||
|
d="M14 2.26953V6.40007C14 6.96012 14 7.24015 14.109 7.45406C14.2049 7.64222 14.3578 7.7952 14.546 7.89108C14.7599 8.00007 15.0399 8.00007 15.6 8.00007H19.7305M14 17.5L16.5 15L14 12.5M10 12.5L7.5 15L10 17.5M20 9.98822V17.2C20 18.8802 20 19.7202 19.673 20.362C19.3854 20.9265 18.9265 21.3854 18.362 21.673C17.7202 22 16.8802 22 15.2 22H8.8C7.11984 22 6.27976 22 5.63803 21.673C5.07354 21.3854 4.6146 20.9265 4.32698 20.362C4 19.7202 4 18.8802 4 17.2V6.8C4 5.11984 4 4.27976 4.32698 3.63803C4.6146 3.07354 5.07354 2.6146 5.63803 2.32698C6.27976 2 7.11984 2 8.8 2H12.0118C12.7455 2 13.1124 2 13.4577 2.08289C13.7638 2.15638 14.0564 2.27759 14.3249 2.44208C14.6276 2.6276 14.887 2.88703 15.4059 3.40589L18.5941 6.59411C19.113 7.11297 19.3724 7.3724 19.5579 7.67515C19.7224 7.94356 19.8436 8.2362 19.9171 8.5423C20 8.88757 20 9.25445 20 9.98822Z"
|
||||||
|
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
<p class="mt-4">
|
<p class="mt-4">
|
||||||
|
@ -17,31 +19,30 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { useWorkingFormStore } from '../../../../../stores/working_form'
|
|
||||||
import EditorOptionsPanel from '../../../editors/EditorOptionsPanel.vue'
|
import EditorOptionsPanel from '../../../editors/EditorOptionsPanel.vue'
|
||||||
import CodeInput from '../../../../forms/CodeInput.vue'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { EditorOptionsPanel, CodeInput },
|
components: {EditorOptionsPanel},
|
||||||
props: {},
|
props: {},
|
||||||
setup () {
|
setup() {
|
||||||
const workingFormStore = useWorkingFormStore()
|
const workingFormStore = useWorkingFormStore()
|
||||||
|
const {content: form} = storeToRefs(workingFormStore)
|
||||||
return {
|
return {
|
||||||
|
form,
|
||||||
workingFormStore
|
workingFormStore
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data () {
|
data() {
|
||||||
return {
|
return {}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
form: {
|
form: {
|
||||||
get () {
|
get() {
|
||||||
return this.workingFormStore.content
|
return this.workingFormStore.content
|
||||||
},
|
},
|
||||||
/* We add a setter */
|
/* We add a setter */
|
||||||
set (value) {
|
set(value) {
|
||||||
this.workingFormStore.set(value)
|
this.workingFormStore.set(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,8 +68,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import clonedeep from 'clone-deep'
|
import clonedeep from 'clone-deep'
|
||||||
import { useFormsStore } from '../../../../../stores/forms'
|
|
||||||
import { useWorkingFormStore } from '../../../../../stores/working_form'
|
|
||||||
import EditorOptionsPanel from '../../../editors/EditorOptionsPanel.vue'
|
import EditorOptionsPanel from '../../../editors/EditorOptionsPanel.vue'
|
||||||
import SelectInput from '../../../../forms/SelectInput.vue'
|
import SelectInput from '../../../../forms/SelectInput.vue'
|
||||||
|
|
||||||
|
@ -80,10 +78,11 @@ export default {
|
||||||
setup () {
|
setup () {
|
||||||
const formsStore = useFormsStore()
|
const formsStore = useFormsStore()
|
||||||
const workingFormStore = useWorkingFormStore()
|
const workingFormStore = useWorkingFormStore()
|
||||||
|
const {getAll: forms} = storeToRefs(formsStore)
|
||||||
return {
|
return {
|
||||||
|
forms,
|
||||||
formsStore,
|
formsStore,
|
||||||
workingFormStore,
|
workingFormStore,
|
||||||
forms : computed(() => formsStore.content)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -129,7 +128,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
allTagsOptions () {
|
allTagsOptions () {
|
||||||
return this.formsStore.getAllTags.map((tagname) => {
|
return this.formsStore.allTags.map((tagname) => {
|
||||||
return {
|
return {
|
||||||
name: tagname,
|
name: tagname,
|
||||||
value: tagname
|
value: tagname
|
||||||
|
|
|
@ -44,8 +44,10 @@ export default {
|
||||||
props: {},
|
props: {},
|
||||||
setup () {
|
setup () {
|
||||||
const workingFormStore = useWorkingFormStore()
|
const workingFormStore = useWorkingFormStore()
|
||||||
|
const {content: form} = storeToRefs(workingFormStore)
|
||||||
return {
|
return {
|
||||||
workingFormStore
|
workingFormStore,
|
||||||
|
form
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
|
@ -54,16 +56,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
form: {
|
zapierUrl: () => useConfig().links.zapier_integration
|
||||||
get () {
|
|
||||||
return this.workingFormStore.content
|
|
||||||
},
|
|
||||||
/* We add a setter */
|
|
||||||
set (value) {
|
|
||||||
this.workingFormStore.set(value)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
zapierUrl: () => this.$config.links.zapier_integration
|
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
|
|
|
@ -50,15 +50,15 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { useWorkingFormStore } from '../../../../../../stores/working_form'
|
|
||||||
import ProTag from '~/components/global/ProTag.vue'
|
import ProTag from '~/components/global/ProTag.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { ProTag },
|
components: { ProTag },
|
||||||
props: {},
|
|
||||||
setup () {
|
setup () {
|
||||||
const workingFormStore = useWorkingFormStore()
|
const workingFormStore = useWorkingFormStore()
|
||||||
|
const {content: form} = storeToRefs(workingFormStore)
|
||||||
return {
|
return {
|
||||||
|
form,
|
||||||
workingFormStore
|
workingFormStore
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -69,17 +69,8 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
form: {
|
|
||||||
get () {
|
|
||||||
return this.workingFormStore.content
|
|
||||||
},
|
|
||||||
/* We add a setter */
|
|
||||||
set (value) {
|
|
||||||
this.workingFormStore.set(value)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
replayToEmailField () {
|
replayToEmailField () {
|
||||||
const emailFields = this.form.properties.filter((field) => {
|
const emailFields = this.form.value.properties.filter((field) => {
|
||||||
return field.type === 'email' && !field.hidden
|
return field.type === 'email' && !field.hidden
|
||||||
})
|
})
|
||||||
if (emailFields.length === 1) return emailFields[0]
|
if (emailFields.length === 1) return emailFields[0]
|
||||||
|
|
|
@ -91,11 +91,9 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import CodeInput from '../../../../forms/CodeInput.vue'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'BlockOptions',
|
name: 'BlockOptions',
|
||||||
components: { CodeInput },
|
components: { },
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|
|
@ -387,8 +387,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import timezones from '../../../../../../data/timezones.json'
|
import timezones from '~/data/timezones.json'
|
||||||
import countryCodes from '../../../../../../data/country_codes.json'
|
import countryCodes from '~/data/country_codes.json'
|
||||||
import CountryFlag from 'vue-country-flag-next'
|
import CountryFlag from 'vue-country-flag-next'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -91,20 +91,16 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Loader from '~/components/global/Loader.vue'
|
|
||||||
import Form from 'vform'
|
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CreateFormBaseModal',
|
|
||||||
components: { Loader },
|
|
||||||
props: {
|
props: {
|
||||||
show: { type: Boolean, required: true }
|
show: { type: Boolean, required: true }
|
||||||
},
|
},
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
state: 'default',
|
state: 'default',
|
||||||
aiForm: new Form({
|
aiForm: useForm({
|
||||||
form_prompt: ''
|
form_prompt: ''
|
||||||
}),
|
}),
|
||||||
loading: false
|
loading: false
|
||||||
|
@ -112,7 +108,7 @@ export default {
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
aiFeaturesEnabled () {
|
aiFeaturesEnabled () {
|
||||||
return this.$config.ai_features_enabled
|
return useConfig().ai_features_enabled
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -41,8 +41,8 @@
|
||||||
</svg>
|
</svg>
|
||||||
View form
|
View form
|
||||||
</a>
|
</a>
|
||||||
<router-link v-if="isMainPage" v-track.edit_form_click="{form_id:form.id, form_slug:form.slug}"
|
<nuxt-link v-if="isMainPage" v-track.edit_form_click="{form_id:form.id, form_slug:form.slug}"
|
||||||
:to="{name:'forms-edit', params: {slug: form.slug}}"
|
:to="{name:'forms-slug-edit', params: {slug: form.slug}}"
|
||||||
class="block block px-4 py-2 text-md text-gray-700 dark:text-white hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
|
class="block block px-4 py-2 text-md text-gray-700 dark:text-white hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
|
||||||
>
|
>
|
||||||
<svg class="w-4 h-4 mr-2" width="18" height="17" viewBox="0 0 18 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg class="w-4 h-4 mr-2" width="18" height="17" viewBox="0 0 18 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
@ -52,7 +52,7 @@
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
Edit
|
Edit
|
||||||
</router-link>
|
</nuxt-link>
|
||||||
<a v-if="isMainPage" href="#"
|
<a v-if="isMainPage" href="#"
|
||||||
class="block block px-4 py-2 text-md text-gray-700 dark:text-white hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
|
class="block block px-4 py-2 text-md text-gray-700 dark:text-white hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
|
||||||
@click.prevent="copyLink"
|
@click.prevent="copyLink"
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
|
||||||
|
export const initForm = (options = {}) => {
|
||||||
|
return useForm({
|
||||||
|
title: 'My Form',
|
||||||
|
description: null,
|
||||||
|
visibility: 'public',
|
||||||
|
workspace_id: null,
|
||||||
|
properties: [],
|
||||||
|
|
||||||
|
notifies: false,
|
||||||
|
slack_notifies: false,
|
||||||
|
send_submission_confirmation: false,
|
||||||
|
webhook_url: null,
|
||||||
|
notification_settings: {},
|
||||||
|
|
||||||
|
// Customization
|
||||||
|
theme: 'default',
|
||||||
|
width: 'centered',
|
||||||
|
dark_mode: 'auto',
|
||||||
|
color: '#3B82F6',
|
||||||
|
hide_title: false,
|
||||||
|
no_branding: false,
|
||||||
|
uppercase_labels: true,
|
||||||
|
transparent_background: false,
|
||||||
|
closes_at: null,
|
||||||
|
closed_text: 'This form has now been closed by its owner and does not accept submissions anymore.',
|
||||||
|
auto_save: true,
|
||||||
|
|
||||||
|
// Submission
|
||||||
|
submit_button_text: 'Submit',
|
||||||
|
re_fillable: false,
|
||||||
|
re_fill_button_text: 'Fill Again',
|
||||||
|
submitted_text: 'Amazing, we saved your answers. Thank you for your time and have a great day!',
|
||||||
|
notification_sender: 'OpnForm',
|
||||||
|
notification_subject: 'We saved your answers',
|
||||||
|
notification_body: 'Hello there 👋 <br>This is a confirmation that your submission was successfully saved.',
|
||||||
|
notifications_include_submission: true,
|
||||||
|
use_captcha: false,
|
||||||
|
is_rating: false,
|
||||||
|
rating_max_value: 5,
|
||||||
|
max_submissions_count: null,
|
||||||
|
max_submissions_reached_text: 'This form has now reached the maximum number of allowed submissions and is now closed.',
|
||||||
|
editable_submissions_button_text: 'Edit submission',
|
||||||
|
confetti_on_submission: false,
|
||||||
|
|
||||||
|
// Security & Privacy
|
||||||
|
can_be_indexed: true,
|
||||||
|
|
||||||
|
// Custom SEO
|
||||||
|
seo_meta: {},
|
||||||
|
|
||||||
|
...options
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
import FormPropertyLogicRule from "~/lib/forms/FormPropertyLogicRule.js";
|
||||||
|
|
||||||
|
export const validatePropertiesLogic = (properties) => {
|
||||||
|
properties.forEach((field) => {
|
||||||
|
const isValid = (new FormPropertyLogicRule(field)).isValid()
|
||||||
|
if (!isValid) {
|
||||||
|
field.logic = {
|
||||||
|
conditions: null,
|
||||||
|
actions: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return properties
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ export const useCrisp = () => {
|
||||||
|
|
||||||
function openChat() {
|
function openChat() {
|
||||||
if (!crisp) return
|
if (!crisp) return
|
||||||
|
showChat()
|
||||||
crisp.chat.open()
|
crisp.chat.open()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,11 +30,12 @@ export const useCrisp = () => {
|
||||||
if (message) sendTextMessage(message)
|
if (message) sendTextMessage(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
function openHelpdesk(){
|
function openHelpdesk() {
|
||||||
if (!crisp) return
|
if (!crisp) return
|
||||||
openChat()
|
openChat()
|
||||||
crisp.chat.setHelpdeskView()
|
crisp.chat.setHelpdeskView()
|
||||||
}
|
}
|
||||||
|
|
||||||
function openHelpdeskArticle(articleSlug, locale = 'en') {
|
function openHelpdeskArticle(articleSlug, locale = 'en') {
|
||||||
if (!crisp) return
|
if (!crisp) return
|
||||||
crisp.chat.openHelpdeskArticle(locale, articleSlug);
|
crisp.chat.openHelpdeskArticle(locale, articleSlug);
|
||||||
|
@ -44,15 +46,15 @@ export const useCrisp = () => {
|
||||||
crisp.message.send('text', message)
|
crisp.message.send('text', message)
|
||||||
}
|
}
|
||||||
|
|
||||||
function setUser (user) {
|
function setUser(user) {
|
||||||
if (!crisp) return
|
if (!crisp) return
|
||||||
crisp.user.setEmail(user.email);
|
crisp.user.setEmail(user.email);
|
||||||
crisp.user.setNickname(user.name);
|
crisp.user.setNickname(user.name);
|
||||||
crisp.session.setData({
|
crisp.session.setData({
|
||||||
user_id : user.id,
|
user_id: user.id,
|
||||||
'pro-subscription' : user?.is_subscribed ?? false,
|
'pro-subscription': user?.is_subscribed ?? false,
|
||||||
'stripe-id' : user?.stripe_id ?? '',
|
'stripe-id': user?.stripe_id ?? '',
|
||||||
'subscription' : user?.has_enterprise_subscription ? 'enterprise' : 'pro'
|
'subscription': user?.has_enterprise_subscription ? 'enterprise' : 'pro'
|
||||||
});
|
});
|
||||||
|
|
||||||
if (user?.is_subscribed ?? false) {
|
if (user?.is_subscribed ?? false) {
|
||||||
|
@ -60,6 +62,11 @@ export const useCrisp = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pushEvent(event, data = {}) {
|
||||||
|
if (!crisp) return
|
||||||
|
crisp.pushEvent(event, data)
|
||||||
|
}
|
||||||
|
|
||||||
function setSegments(segments, overwrite = false) {
|
function setSegments(segments, overwrite = false) {
|
||||||
if (!crisp) return
|
if (!crisp) return
|
||||||
crisp.session.setSegments(segments, overwrite)
|
crisp.session.setSegments(segments, overwrite)
|
||||||
|
@ -75,6 +82,7 @@ export const useCrisp = () => {
|
||||||
openHelpdesk,
|
openHelpdesk,
|
||||||
openHelpdeskArticle,
|
openHelpdeskArticle,
|
||||||
sendTextMessage,
|
sendTextMessage,
|
||||||
|
pushEvent,
|
||||||
setUser
|
setUser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,11 +16,11 @@
|
||||||
<script>
|
<script>
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import Form from 'vform'
|
import Form from 'vform'
|
||||||
import { useFormsStore } from '../../stores/forms'
|
import { useFormsStore } from '../../../stores/forms.js'
|
||||||
import { useWorkingFormStore } from '../../stores/working_form'
|
import { useWorkingFormStore } from '../../../stores/working_form.js'
|
||||||
import { useWorkspacesStore } from '../../stores/workspaces'
|
import { useWorkspacesStore } from '../../../stores/workspaces.js'
|
||||||
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'
|
||||||
|
|
||||||
const loadForms = function () {
|
const loadForms = function () {
|
||||||
const formsStore = useFormsStore()
|
const formsStore = useFormsStore()
|
|
@ -133,15 +133,15 @@
|
||||||
<script>
|
<script>
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import Form from 'vform'
|
import Form from 'vform'
|
||||||
import { useAuthStore } from '../../../stores/auth'
|
import { useAuthStore } from '../../../../stores/auth.js'
|
||||||
import { useFormsStore } from '../../../stores/forms'
|
import { useFormsStore } from '../../../../stores/forms.js'
|
||||||
import { useWorkingFormStore } from '../../../stores/working_form'
|
import { useWorkingFormStore } from '../../../../stores/working_form.js'
|
||||||
import { useWorkspacesStore } from '../../../stores/workspaces'
|
import { useWorkspacesStore } from '../../../../stores/workspaces.js'
|
||||||
import ProTag from '~/components/global/ProTag.vue'
|
import ProTag from '~/components/global/ProTag.vue'
|
||||||
import VButton from '~/components/global/VButton.vue'
|
import VButton from '~/components/global/VButton.vue'
|
||||||
import ExtraMenu from '../../../components/pages/forms/show/ExtraMenu.vue'
|
import ExtraMenu from '../../../../components/pages/forms/show/ExtraMenu.vue'
|
||||||
import SeoMeta from '../../../mixins/seo-meta.js'
|
import SeoMeta from '../../../../mixins/seo-meta.js'
|
||||||
import FormCleanings from '../../../components/pages/forms/show/FormCleanings.vue'
|
import FormCleanings from '../../../../components/pages/forms/show/FormCleanings.vue'
|
||||||
|
|
||||||
const loadForms = function () {
|
const loadForms = function () {
|
||||||
const formsStore = useFormsStore()
|
const formsStore = useFormsStore()
|
||||||
|
@ -263,7 +263,7 @@ export default {
|
||||||
this.$router.push({ name: 'home' })
|
this.$router.push({ name: 'home' })
|
||||||
},
|
},
|
||||||
openEdit () {
|
openEdit () {
|
||||||
this.$router.push({ name: 'forms-edit', params: { slug: this.form.slug } })
|
this.$router.push({ name: 'forms-slug-edit', params: { slug: this.form.slug } })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -19,14 +19,14 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ShareLink from '../../../components/pages/forms/show/ShareLink.vue'
|
import ShareLink from '../../../../components/pages/forms/show/ShareLink.vue'
|
||||||
import EmbedCode from '../../../components/pages/forms/show/EmbedCode.vue'
|
import EmbedCode from '../../../../components/pages/forms/show/EmbedCode.vue'
|
||||||
import FormQrCode from '../../../components/pages/forms/show/FormQrCode.vue'
|
import FormQrCode from '../../../../components/pages/forms/show/FormQrCode.vue'
|
||||||
import UrlFormPrefill from '../../../components/pages/forms/show/UrlFormPrefill.vue'
|
import UrlFormPrefill from '../../../../components/pages/forms/show/UrlFormPrefill.vue'
|
||||||
import RegenerateFormLink from '../../../components/pages/forms/show/RegenerateFormLink.vue'
|
import RegenerateFormLink from '../../../../components/pages/forms/show/RegenerateFormLink.vue'
|
||||||
import SeoMeta from '../../../mixins/seo-meta.js'
|
import SeoMeta from '../../../../mixins/seo-meta.js'
|
||||||
import AdvancedFormUrlSettings from '../../../components/open/forms/components/AdvancedFormUrlSettings.vue'
|
import AdvancedFormUrlSettings from '../../../../components/open/forms/components/AdvancedFormUrlSettings.vue'
|
||||||
import EmbedFormAsPopupModal from '../../../components/pages/forms/show/EmbedFormAsPopupModal.vue'
|
import EmbedFormAsPopupModal from '../../../../components/pages/forms/show/EmbedFormAsPopupModal.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
|
@ -8,8 +8,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import FormStats from '../../../components/open/forms/components/FormStats.vue'
|
import FormStats from '../../../../components/open/forms/components/FormStats.vue'
|
||||||
import SeoMeta from '../../../mixins/seo-meta.js'
|
import SeoMeta from '../../../../mixins/seo-meta.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Stats',
|
name: 'Stats',
|
|
@ -5,8 +5,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import FormSubmissions from '../../../components/open/forms/components/FormSubmissions.vue'
|
import FormSubmissions from '../../../../components/open/forms/components/FormSubmissions.vue'
|
||||||
import SeoMeta from '../../../mixins/seo-meta.js'
|
import SeoMeta from '../../../../mixins/seo-meta.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {FormSubmissions},
|
components: {FormSubmissions},
|
|
@ -1,164 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="flex flex-wrap flex-col">
|
|
||||||
<transition v-if="stateReady" name="fade" mode="out-in">
|
|
||||||
<div key="2">
|
|
||||||
<create-form-base-modal :show="showInitialFormModal" @form-generated="formGenerated"
|
|
||||||
@close="showInitialFormModal=false"
|
|
||||||
/>
|
|
||||||
<form-editor v-if="!workspacesLoading" ref="editor"
|
|
||||||
class="w-full flex flex-grow"
|
|
||||||
:error="error"
|
|
||||||
@on-save="formInitialHash=null"
|
|
||||||
/>
|
|
||||||
<div v-else class="text-center mt-4 py-6">
|
|
||||||
<Loader class="h-6 w-6 text-nt-blue mx-auto" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import Form from 'vform'
|
|
||||||
import { computed } from 'vue'
|
|
||||||
import { useAuthStore } from '../../stores/auth'
|
|
||||||
import { useTemplatesStore } from '../../stores/templates'
|
|
||||||
import { useWorkingFormStore } from '../../stores/working_form'
|
|
||||||
import { useWorkspacesStore } from '../../stores/workspaces'
|
|
||||||
import initForm from '../../mixins/form_editor/initForm.js'
|
|
||||||
import SeoMeta from '../../mixins/seo-meta.js'
|
|
||||||
import CreateFormBaseModal from '../../components/pages/forms/create/CreateFormBaseModal.vue'
|
|
||||||
|
|
||||||
const loadTemplates = function () {
|
|
||||||
const templatesStore = useTemplatesStore()
|
|
||||||
templatesStore.startLoading()
|
|
||||||
templatesStore.loadIfEmpty().then(() => {
|
|
||||||
templatesStore.stopLoading()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'CreateForm',
|
|
||||||
components: { CreateFormBaseModal },
|
|
||||||
|
|
||||||
mixins: [initForm, SeoMeta],
|
|
||||||
middleware: 'auth',
|
|
||||||
|
|
||||||
beforeRouteEnter (to, from, next) {
|
|
||||||
loadTemplates()
|
|
||||||
next()
|
|
||||||
},
|
|
||||||
|
|
||||||
beforeRouteLeave (to, from, next) {
|
|
||||||
if (this.isDirty()) {
|
|
||||||
return this.alertConfirm('Changes you made may not be saved. Are you sure want to leave?', () => {
|
|
||||||
window.onbeforeunload = null
|
|
||||||
next()
|
|
||||||
}, () => {})
|
|
||||||
}
|
|
||||||
next()
|
|
||||||
},
|
|
||||||
|
|
||||||
setup () {
|
|
||||||
const authStore = useAuthStore()
|
|
||||||
const templatesStore = useTemplatesStore()
|
|
||||||
const workingFormStore = useWorkingFormStore()
|
|
||||||
const workspacesStore = useWorkspacesStore()
|
|
||||||
return {
|
|
||||||
templatesStore,
|
|
||||||
workingFormStore,
|
|
||||||
workspacesStore,
|
|
||||||
user: computed(() => authStore.user),
|
|
||||||
workspaces : computed(() => workspacesStore.content),
|
|
||||||
workspacesLoading : computed(() => workspacesStore.loading)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
metaTitle: 'Create a new Form',
|
|
||||||
stateReady: false,
|
|
||||||
loading: false,
|
|
||||||
error: '',
|
|
||||||
showInitialFormModal: false,
|
|
||||||
formInitialHash: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
form: {
|
|
||||||
get () {
|
|
||||||
return this.workingFormStore.content
|
|
||||||
},
|
|
||||||
/* We add a setter */
|
|
||||||
set (value) {
|
|
||||||
this.workingFormStore.set(value)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
workspace () {
|
|
||||||
return this.workspacesStore.getCurrent()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
|
||||||
workspace () {
|
|
||||||
if (this.workspace) {
|
|
||||||
this.form.workspace_id = this.workspace.id
|
|
||||||
}
|
|
||||||
},
|
|
||||||
user () {
|
|
||||||
this.stateReady = true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted () {
|
|
||||||
window.onbeforeunload = () => {
|
|
||||||
if (this.isDirty()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.initForm()
|
|
||||||
this.formInitialHash = this.hashString(JSON.stringify(this.form.data()))
|
|
||||||
if (this.$route.query.template !== undefined && this.$route.query.template) {
|
|
||||||
const template = this.templatesStore.getByKey(this.$route.query.template)
|
|
||||||
if (template && template.structure) {
|
|
||||||
this.form = new Form({ ...this.form.data(), ...template.structure })
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// No template loaded, ask how to start
|
|
||||||
this.showInitialFormModal = true
|
|
||||||
}
|
|
||||||
this.closeAlert()
|
|
||||||
this.workspacesStore.loadIfEmpty()
|
|
||||||
|
|
||||||
this.stateReady = this.user !== null
|
|
||||||
},
|
|
||||||
|
|
||||||
created () {},
|
|
||||||
unmounted () {},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
formGenerated (form) {
|
|
||||||
this.form = new Form({ ...this.form.data(), ...form })
|
|
||||||
},
|
|
||||||
isDirty () {
|
|
||||||
return !this.loading && this.formInitialHash && this.formInitialHash !== this.hashString(JSON.stringify(this.form.data()))
|
|
||||||
},
|
|
||||||
hashString (str, seed = 0) {
|
|
||||||
let h1 = 0xdeadbeef ^ seed
|
|
||||||
let h2 = 0x41c6ce57 ^ seed
|
|
||||||
for (let i = 0, ch; i < str.length; i++) {
|
|
||||||
ch = str.charCodeAt(i)
|
|
||||||
h1 = Math.imul(h1 ^ ch, 2654435761)
|
|
||||||
h2 = Math.imul(h2 ^ ch, 1597334677)
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909)
|
|
||||||
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909)
|
|
||||||
|
|
||||||
return 4294967296 * (2097151 & h2) + (h1 >>> 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -0,0 +1,117 @@
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-wrap flex-col">
|
||||||
|
<transition name="fade" mode="out-in">
|
||||||
|
<div key="2">
|
||||||
|
<create-form-base-modal :show="showInitialFormModal" @form-generated="formGenerated"
|
||||||
|
@close="showInitialFormModal=false"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<form-editor v-if="form && !workspacesLoading" ref="editor"
|
||||||
|
class="w-full flex flex-grow"
|
||||||
|
:error="error"
|
||||||
|
@on-save="formInitialHash=null"
|
||||||
|
/>
|
||||||
|
<div v-else class="text-center mt-4 py-6">
|
||||||
|
<Loader class="h-6 w-6 text-nt-blue mx-auto"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {watch} from 'vue'
|
||||||
|
import {initForm} from "~/composables/forms/initForm.js"
|
||||||
|
import FormEditor from "~/components/open/forms/components/FormEditor.vue"
|
||||||
|
import CreateFormBaseModal from '../../../components/pages/forms/create/CreateFormBaseModal.vue'
|
||||||
|
|
||||||
|
// metaTitle: 'Create a new Form',
|
||||||
|
|
||||||
|
// beforeRouteLeave (to, from, next) {
|
||||||
|
// if (this.isDirty()) {
|
||||||
|
// return this.alertConfirm('Changes you made may not be saved. Are you sure want to leave?', () => {
|
||||||
|
// window.onbeforeunload = null
|
||||||
|
// next()
|
||||||
|
// }, () => {})
|
||||||
|
// }
|
||||||
|
// next()
|
||||||
|
// },
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const authStore = useAuthStore()
|
||||||
|
const templatesStore = useTemplatesStore()
|
||||||
|
const workingFormStore = useWorkingFormStore()
|
||||||
|
const workspacesStore = useWorkspacesStore()
|
||||||
|
const formStore = useFormsStore()
|
||||||
|
|
||||||
|
const {
|
||||||
|
getCurrent: workspace,
|
||||||
|
getAll: workspaces,
|
||||||
|
workspacesLoading: workspacesLoading
|
||||||
|
} = storeToRefs(workspacesStore)
|
||||||
|
const {content: form} = storeToRefs(workingFormStore)
|
||||||
|
|
||||||
|
// State
|
||||||
|
const loading = ref(false)
|
||||||
|
const error = ref('')
|
||||||
|
const showInitialFormModal = ref(false)
|
||||||
|
const formInitialHash = ref(null)
|
||||||
|
|
||||||
|
watch(() => workspace, () => {
|
||||||
|
if (workspace) {
|
||||||
|
form.workspace_id = workspace.value.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (process.client) {
|
||||||
|
// window.onbeforeunload = () => {
|
||||||
|
if (isDirty()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!formStore.allLoaded) {
|
||||||
|
formStore.load(workspace.value.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
form.value = initForm({workspace_id: workspace.value?.id})
|
||||||
|
formInitialHash.value = hashString(JSON.stringify(form.value.data()))
|
||||||
|
// if (this.$route.query.template !== undefined && this.$route.query.template) {
|
||||||
|
// const template = this.templatesStore.getByKey(this.$route.query.template)
|
||||||
|
// if (template && template.structure) {
|
||||||
|
// this.form = new Form({...this.form.data(), ...template.structure})
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// // No template loaded, ask how to start
|
||||||
|
// this.showInitialFormModal = true
|
||||||
|
// }
|
||||||
|
// this.closeAlert()
|
||||||
|
// this.workspacesStore.loadIfEmpty()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
const formGenerated = (newForm) => {
|
||||||
|
form.value = useForm({...form.value.data(), ...newForm})
|
||||||
|
}
|
||||||
|
|
||||||
|
const isDirty = () => {
|
||||||
|
return !loading.value && formInitialHash.value && formInitialHash.value !== hashString(JSON.stringify(form.value.data()))
|
||||||
|
}
|
||||||
|
|
||||||
|
const hashString = (str, seed = 0) => {
|
||||||
|
let h1 = 0xdeadbeef ^ seed
|
||||||
|
let h2 = 0x41c6ce57 ^ seed
|
||||||
|
for (let i = 0, ch; i < str.length; i++) {
|
||||||
|
ch = str.charCodeAt(i)
|
||||||
|
h1 = Math.imul(h1 ^ ch, 2654435761)
|
||||||
|
h2 = Math.imul(h2 ^ ch, 1597334677)
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909)
|
||||||
|
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909)
|
||||||
|
|
||||||
|
return 4294967296 * (2097151 & h2) + (h1 >>> 0)
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -133,14 +133,18 @@ definePageMeta({
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const formsStore = useFormsStore()
|
const formsStore = useFormsStore()
|
||||||
|
formsStore.startLoading()
|
||||||
const workspacesStore = useWorkspacesStore()
|
const workspacesStore = useWorkspacesStore()
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
if (!formsStore.allLoaded) {
|
||||||
|
console.log('starting to load')
|
||||||
formsStore.load(workspacesStore.currentId)
|
formsStore.load(workspacesStore.currentId)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// State
|
// State
|
||||||
const {getAll: forms, loading: formsLoading} = storeToRefs(formsStore)
|
const {getAll: forms, loading: formsLoading, allTags} = storeToRefs(formsStore)
|
||||||
const showEditFormModal = ref(false)
|
const showEditFormModal = ref(false)
|
||||||
const selectedForm = ref(null)
|
const selectedForm = ref(null)
|
||||||
const search = ref('')
|
const search = ref('')
|
||||||
|
@ -167,15 +171,7 @@ const viewForm = (form) => {
|
||||||
const isFilteringForms = computed(() => {
|
const isFilteringForms = computed(() => {
|
||||||
return (search.value !== '' && search.value !== null) || selectedTags.value.size > 0
|
return (search.value !== '' && search.value !== null) || selectedTags.value.size > 0
|
||||||
})
|
})
|
||||||
const allTags = computed(() => {
|
|
||||||
let tags = []
|
|
||||||
forms.value.forEach((form) => {
|
|
||||||
if (form.tags && form.tags.length) {
|
|
||||||
tags = tags.concat(form.tags.split(','))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return [...new Set(tags)]
|
|
||||||
})
|
|
||||||
const enrichedForms = computed(() => {
|
const enrichedForms = computed(() => {
|
||||||
let enrichedForms = forms.value.map((form) => {
|
let enrichedForms = forms.value.map((form) => {
|
||||||
form.workspace = workspacesStore.getByKey(form.workspace_id)
|
form.workspace = workspacesStore.getByKey(form.workspace_id)
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { nextTick } from 'vue'
|
||||||
export const useAppStore = defineStore('app', {
|
export const useAppStore = defineStore('app', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
layout: 'default',
|
layout: 'default',
|
||||||
|
navbarHidden: false,
|
||||||
|
|
||||||
// App Loader
|
// App Loader
|
||||||
loader: {
|
loader: {
|
||||||
|
@ -17,6 +18,12 @@ export const useAppStore = defineStore('app', {
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
|
hideNavbar () {
|
||||||
|
this.navbarHidden = true
|
||||||
|
},
|
||||||
|
showNavbar () {
|
||||||
|
this.navbarHidden = false
|
||||||
|
},
|
||||||
setLayout (layout) {
|
setLayout (layout) {
|
||||||
this.layout = layout ?? 'default'
|
this.layout = layout ?? 'default'
|
||||||
},
|
},
|
||||||
|
|
|
@ -6,7 +6,7 @@ export const formsEndpoint = '/open/workspaces/{workspaceId}/forms'
|
||||||
export const useFormsStore = defineStore('forms', () => {
|
export const useFormsStore = defineStore('forms', () => {
|
||||||
|
|
||||||
const contentStore = useContentStore('slug')
|
const contentStore = useContentStore('slug')
|
||||||
contentStore.startLoading()
|
const allLoaded = ref(false)
|
||||||
const currentPage = ref(1)
|
const currentPage = ref(1)
|
||||||
|
|
||||||
const load = (workspaceId) => {
|
const load = (workspaceId) => {
|
||||||
|
@ -23,14 +23,27 @@ export const useFormsStore = defineStore('forms', () => {
|
||||||
currentPage.value++
|
currentPage.value++
|
||||||
load(workspaceId)
|
load(workspaceId)
|
||||||
} else {
|
} else {
|
||||||
|
allLoaded.value = true
|
||||||
contentStore.stopLoading()
|
contentStore.stopLoading()
|
||||||
currentPage.value = 1
|
currentPage.value = 1
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const allTags = computed(() => {
|
||||||
|
let tags = []
|
||||||
|
contentStore.getAll.value.forEach((form) => {
|
||||||
|
if (form.tags && form.tags.length) {
|
||||||
|
tags = tags.concat(form.tags.split(','))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return [...new Set(tags)]
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...contentStore,
|
...contentStore,
|
||||||
|
allLoaded,
|
||||||
|
allTags,
|
||||||
load
|
load
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -387,6 +387,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import timezones
|
||||||
import timezones from '../../../../../../data/timezones.json'
|
import timezones from '../../../../../../data/timezones.json'
|
||||||
import countryCodes from '../../../../../../data/country_codes.json'
|
import countryCodes from '../../../../../../data/country_codes.json'
|
||||||
import CountryFlag from 'vue-country-flag-next'
|
import CountryFlag from 'vue-country-flag-next'
|
||||||
|
|
|
@ -91,20 +91,16 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Loader from '../../../common/Loader.vue'
|
|
||||||
import Form from 'vform'
|
|
||||||
import axios from 'axios'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CreateFormBaseModal',
|
name: 'CreateFormBaseModal',
|
||||||
components: { Loader },
|
components: { },
|
||||||
props: {
|
props: {
|
||||||
show: { type: Boolean, required: true }
|
show: { type: Boolean, required: true }
|
||||||
},
|
},
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
state: 'default',
|
state: 'default',
|
||||||
aiForm: new Form({
|
aiForm: useForm({
|
||||||
form_prompt: ''
|
form_prompt: ''
|
||||||
}),
|
}),
|
||||||
loading: false
|
loading: false
|
||||||
|
|
Loading…
Reference in New Issue