0351d front end linting (#377)
* feat: disable custom script for trial users * after lint fix * frontend linting --------- Co-authored-by: Julien Nahum <julien@nahum.net>
This commit is contained in:
@@ -1,22 +1,30 @@
|
||||
<template>
|
||||
<div class="w-full flex flex-col">
|
||||
<form-editor v-if="!formsLoading || form" ref="editor"
|
||||
:is-edit="true"
|
||||
@on-save="formInitialHash=null"
|
||||
<form-editor
|
||||
v-if="(!formsLoading || form ) && !error "
|
||||
ref="editor"
|
||||
:is-edit="true"
|
||||
@on-save="formInitialHash = null"
|
||||
/>
|
||||
<div v-else-if="!formsLoading && error" class="mt-4 rounded-lg max-w-xl mx-auto p-6 bg-red-100 text-red-500">
|
||||
<div
|
||||
v-else-if="error && !formsLoading"
|
||||
class="mt-4 rounded-lg max-w-xl mx-auto p-6 bg-red-100 text-red-500"
|
||||
>
|
||||
{{ error }}
|
||||
</div>
|
||||
<div v-else class="text-center mt-4 py-6">
|
||||
<Loader class="h-6 w-6 text-nt-blue mx-auto"/>
|
||||
<div
|
||||
v-else
|
||||
class="text-center mt-4 py-6"
|
||||
>
|
||||
<Loader class="h-6 w-6 text-nt-blue mx-auto" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {computed} from 'vue'
|
||||
import FormEditor from "~/components/open/forms/components/FormEditor.vue";
|
||||
import {hash} from "~/lib/utils.js";
|
||||
import { computed } from "vue"
|
||||
import FormEditor from "~/components/open/forms/components/FormEditor.vue"
|
||||
import { hash } from "~/lib/utils.js"
|
||||
|
||||
const formsStore = useFormsStore()
|
||||
const workingFormStore = useWorkingFormStore()
|
||||
@@ -34,14 +42,19 @@ const formInitialHash = ref(null)
|
||||
|
||||
function isDirty() {
|
||||
try {
|
||||
return formInitialHash.value && updatedForm.value && formInitialHash.value !== hash(JSON.stringify(updatedForm?.value?.data() ?? null))
|
||||
return (
|
||||
formInitialHash.value &&
|
||||
updatedForm.value &&
|
||||
formInitialHash.value !==
|
||||
hash(JSON.stringify(updatedForm?.value?.data() ?? null))
|
||||
)
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function initUpdatedForm() {
|
||||
if (!form || !form.value) {
|
||||
if (!form.value || !form.value) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -61,11 +74,14 @@ watch(form, (form) => {
|
||||
|
||||
onBeforeRouteLeave((to, from, next) => {
|
||||
if (isDirty()) {
|
||||
return useAlert().confirm('Changes you made may not be saved. Are you sure want to leave?', () => {
|
||||
window.onbeforeunload = null
|
||||
next()
|
||||
}, () => {
|
||||
})
|
||||
return useAlert().confirm(
|
||||
"Changes you made may not be saved. Are you sure want to leave?",
|
||||
() => {
|
||||
window.onbeforeunload = null
|
||||
next()
|
||||
},
|
||||
() => {},
|
||||
)
|
||||
}
|
||||
next()
|
||||
})
|
||||
@@ -80,7 +96,7 @@ onBeforeMount(() => {
|
||||
}
|
||||
|
||||
if (!form.value && !formsStore.allLoaded) {
|
||||
formsStore.loadAll(workspacesStore.currentId).then(()=>{
|
||||
formsStore.loadAll(workspacesStore.currentId).then(() => {
|
||||
initUpdatedForm()
|
||||
})
|
||||
} else {
|
||||
@@ -89,9 +105,9 @@ onBeforeMount(() => {
|
||||
})
|
||||
|
||||
useOpnSeoMeta({
|
||||
title: 'Edit ' + ((form && form.value) ? form.value.title : 'Your Form')
|
||||
title: "Edit " + (form.value && form.value ? form.value.title : "Your Form"),
|
||||
})
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
middleware: "auth",
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -2,24 +2,39 @@
|
||||
<div class="flex flex-col">
|
||||
<div v-if="form && !isIframe && (form.logo_picture || form.cover_picture)">
|
||||
<div v-if="form.cover_picture">
|
||||
<div id="cover-picture" class="max-h-56 w-full overflow-hidden flex items-center justify-center">
|
||||
<img alt="Form Cover Picture" :src="form.cover_picture" class="w-full"/>
|
||||
<div
|
||||
id="cover-picture"
|
||||
class="max-h-56 w-full overflow-hidden flex items-center justify-center"
|
||||
>
|
||||
<img
|
||||
alt="Form Cover Picture"
|
||||
:src="form.cover_picture"
|
||||
class="w-full"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="form.logo_picture" class="w-full p-5 relative mx-auto"
|
||||
:class="{'pt-20':!form.cover_picture, 'md:w-3/5 lg:w-1/2 md:max-w-2xl': form.width === 'centered', 'max-w-7xl': (form.width === 'full' && !isIframe) }"
|
||||
<div
|
||||
v-if="form.logo_picture"
|
||||
class="w-full p-5 relative mx-auto"
|
||||
:class="{'pt-20':!form.cover_picture, 'md:w-3/5 lg:w-1/2 md:max-w-2xl': form.width === 'centered', 'max-w-7xl': (form.width === 'full' && !isIframe) }"
|
||||
>
|
||||
<img alt="Logo Picture" :src="form.logo_picture"
|
||||
:class="{'top-5':!form.cover_picture, '-top-10':form.cover_picture}"
|
||||
class="w-20 h-20 object-contain absolute left-5 transition-all"
|
||||
/>
|
||||
<img
|
||||
alt="Logo Picture"
|
||||
:src="form.logo_picture"
|
||||
:class="{'top-5':!form.cover_picture, '-top-10':form.cover_picture}"
|
||||
class="w-20 h-20 object-contain absolute left-5 transition-all"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full mx-auto px-4"
|
||||
:class="{'mt-6':!isIframe, 'md:w-3/5 lg:w-1/2 md:max-w-2xl': form && (form.width === 'centered'), 'max-w-7xl': (form && form.width === 'full' && !isIframe)}"
|
||||
<div
|
||||
class="w-full mx-auto px-4"
|
||||
:class="{'mt-6':!isIframe, 'md:w-3/5 lg:w-1/2 md:max-w-2xl': form && (form.width === 'centered'), 'max-w-7xl': (form && form.width === 'full' && !isIframe)}"
|
||||
>
|
||||
<div v-if="!formLoading && !form">
|
||||
<h1 class="mt-6" v-text="'Whoops'"/>
|
||||
<h1
|
||||
class="mt-6"
|
||||
v-text="'Whoops'"
|
||||
/>
|
||||
<p class="mt-6">
|
||||
Unfortunately we could not find this form. It may have been deleted.
|
||||
</p>
|
||||
@@ -31,17 +46,22 @@
|
||||
</div>
|
||||
<div v-else-if="formLoading">
|
||||
<p class="text-center mt-6 p-4">
|
||||
<loader class="h-6 w-6 text-nt-blue mx-auto"/>
|
||||
<loader class="h-6 w-6 text-nt-blue mx-auto" />
|
||||
</p>
|
||||
</div>
|
||||
<template v-else>
|
||||
<div v-if="recordLoading">
|
||||
<p class="text-center mt-6 p-4">
|
||||
<loader class="h-6 w-6 text-nt-blue mx-auto"/>
|
||||
<loader class="h-6 w-6 text-nt-blue mx-auto" />
|
||||
</p>
|
||||
</div>
|
||||
<open-complete-form v-show="!recordLoading" ref="openCompleteForm" :form="form" class="mb-10" :dark-mode="darkMode"
|
||||
@password-entered="passwordEntered"
|
||||
<OpenCompleteForm
|
||||
v-show="!recordLoading"
|
||||
ref="openCompleteForm"
|
||||
:form="form"
|
||||
class="mb-10"
|
||||
:dark-mode="darkMode"
|
||||
@password-entered="passwordEntered"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
@@ -70,7 +90,6 @@ const formLoading = computed(() => formsStore.loading)
|
||||
const recordLoading = computed(() => recordsStore.loading)
|
||||
const slug = useRoute().params.slug
|
||||
const form = computed(() => formsStore.getByKey(slug))
|
||||
const submitted = ref(false)
|
||||
|
||||
const openCompleteForm = ref(null)
|
||||
|
||||
@@ -145,7 +164,7 @@ onMounted(() => {
|
||||
}
|
||||
})
|
||||
|
||||
onBeforeRouteLeave((to, from) => {
|
||||
onBeforeRouteLeave(() => {
|
||||
document.body.classList.remove('public-page')
|
||||
crisp.showChat()
|
||||
disableDarkMode()
|
||||
@@ -168,16 +187,16 @@ useOpnSeoMeta({
|
||||
if (pageMeta.value.description) {
|
||||
return pageMeta.value.description
|
||||
}
|
||||
return (form && form.value?.description) ? form.value?.description.substring(0, 160) : null
|
||||
return (form.value && form.value?.description) ? form.value?.description.substring(0, 160) : null
|
||||
},
|
||||
ogImage: () => {
|
||||
if (pageMeta.value.page_thumbnail) {
|
||||
return pageMeta.value.page_thumbnail
|
||||
}
|
||||
return (form && form.value?.cover_picture) ? form.value?.cover_picture : null
|
||||
return (form.value && form.value?.cover_picture) ? form.value?.cover_picture : null
|
||||
},
|
||||
robots: () => {
|
||||
return (form && form.value?.can_be_indexed) ? null : 'noindex, nofollow'
|
||||
return (form.value && form.value?.can_be_indexed) ? null : 'noindex, nofollow'
|
||||
}
|
||||
})
|
||||
useHead({
|
||||
@@ -186,7 +205,7 @@ useHead({
|
||||
// Disable template if custom SEO title
|
||||
return titleChunk
|
||||
}
|
||||
return titleChunk ? `${titleChunk} - OpnForm` : 'OpnForm';
|
||||
return titleChunk ? `${titleChunk} - OpnForm` : 'OpnForm'
|
||||
},
|
||||
script: [ { src: '/widgets/iframeResizer.contentWindow.min.js' } ]
|
||||
})
|
||||
|
||||
@@ -4,10 +4,24 @@
|
||||
<div class="flex bg-gray-50">
|
||||
<div class="w-full md:w-4/5 lg:w-3/5 md:mx-auto md:max-w-4xl px-4">
|
||||
<div class="pt-4 pb-0">
|
||||
<a href="#" class="flex text-blue mb-2 font-semibold text-sm" @click.prevent="goBack">
|
||||
<svg class="w-3 h-3 text-blue mt-1 mr-1" viewBox="0 0 6 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5 9L1 5L5 1" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"
|
||||
stroke-linejoin="round" />
|
||||
<a
|
||||
href="#"
|
||||
class="flex text-blue mb-2 font-semibold text-sm"
|
||||
@click.prevent="goBack"
|
||||
>
|
||||
<svg
|
||||
class="w-3 h-3 text-blue mt-1 mr-1"
|
||||
viewBox="0 0 6 10"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M5 9L1 5L5 1"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
Go back
|
||||
</a>
|
||||
@@ -17,34 +31,91 @@
|
||||
{{ form.title }}
|
||||
</h2>
|
||||
<div class="flex">
|
||||
<extra-menu class="mr-2" :form="form" />
|
||||
<extra-menu
|
||||
class="mr-2"
|
||||
:form="form"
|
||||
/>
|
||||
|
||||
<v-button v-if="form.visibility === 'draft'" color="white" class="mr-2 text-blue-600 hidden sm:block"
|
||||
@click="showDraftFormWarningNotification">
|
||||
<svg class="w-6 h-6 inline -mt-1" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1 12C1 12 5 4 12 4C19 4 23 12 23 12C23 12 19 20 12 20C5 20 1 12 1 12Z" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<v-button
|
||||
v-if="form.visibility === 'draft'"
|
||||
color="white"
|
||||
class="mr-2 text-blue-600 hidden sm:block"
|
||||
@click="showDraftFormWarningNotification"
|
||||
>
|
||||
<svg
|
||||
class="w-6 h-6 inline -mt-1"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M1 12C1 12 5 4 12 4C19 4 23 12 23 12C23 12 19 20 12 20C5 20 1 12 1 12Z"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</v-button>
|
||||
<v-button v-else v-track.view_form_click="{ form_id: form.id, form_slug: form.slug }" target="_blank"
|
||||
:href="form.share_url" color="white" class="mr-2 text-blue-600 hidden sm:block">
|
||||
<svg class="w-6 h-6 inline -mt-1" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1 12C1 12 5 4 12 4C19 4 23 12 23 12C23 12 19 20 12 20C5 20 1 12 1 12Z" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<v-button
|
||||
v-else
|
||||
v-track.view_form_click="{
|
||||
form_id: form.id,
|
||||
form_slug: form.slug,
|
||||
}"
|
||||
target="_blank"
|
||||
:href="form.share_url"
|
||||
color="white"
|
||||
class="mr-2 text-blue-600 hidden sm:block"
|
||||
>
|
||||
<svg
|
||||
class="w-6 h-6 inline -mt-1"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M1 12C1 12 5 4 12 4C19 4 23 12 23 12C23 12 19 20 12 20C5 20 1 12 1 12Z"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</v-button>
|
||||
<v-button class="text-white" :to="{ name: 'forms-slug-edit', params: { slug: slug } }">
|
||||
<svg class="inline mr-1 -mt-1" width="18" height="17" viewBox="0 0 18 17" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<v-button
|
||||
class="text-white"
|
||||
:to="{ name: 'forms-slug-edit', params: { slug: slug } }"
|
||||
>
|
||||
<svg
|
||||
class="inline mr-1 -mt-1"
|
||||
width="18"
|
||||
height="17"
|
||||
viewBox="0 0 18 17"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.99998 15.6662H16.5M1.5 15.6662H2.89545C3.3031 15.6662 3.50693 15.6662 3.69874 15.6202C3.8688 15.5793 4.03138 15.512 4.1805 15.4206C4.34869 15.3175 4.49282 15.1734 4.78107 14.8852L15.25 4.4162C15.9404 3.72585 15.9404 2.60656 15.25 1.9162C14.5597 1.22585 13.4404 1.22585 12.75 1.9162L2.28105 12.3852C1.9928 12.6734 1.84867 12.8175 1.7456 12.9857C1.65422 13.1348 1.58688 13.2974 1.54605 13.4675C1.5 13.6593 1.5 13.8631 1.5 14.2708V15.6662Z"
|
||||
stroke="currentColor" stroke-width="1.67" stroke-linecap="round" stroke-linejoin="round" />
|
||||
stroke="currentColor"
|
||||
stroke-width="1.67"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
Edit form
|
||||
</v-button>
|
||||
@@ -52,51 +123,87 @@
|
||||
</div>
|
||||
|
||||
<p class="text-gray-500 text-sm">
|
||||
<span class="pr-1">{{ form.views_count }} view{{ form.views_count > 0 ? 's' : '' }}</span>
|
||||
<span class="pr-1">- {{ form.submissions_count }}
|
||||
submission{{ form.submissions_count > 0 ? 's' : '' }}
|
||||
<span class="pr-1">{{ form.views_count }} view{{
|
||||
form.views_count > 0 ? "s" : ""
|
||||
}}</span>
|
||||
<span class="pr-1">- {{ form.submissions_count }} submission{{
|
||||
form.submissions_count > 0 ? "s" : ""
|
||||
}}
|
||||
</span>
|
||||
<span>- Edited {{ form.last_edited_human }}</span>
|
||||
</p>
|
||||
<div v-if="['draft', 'closed'].includes(form.visibility) || (form.tags && form.tags.length > 0)"
|
||||
class="mt-2 flex items-center flex-wrap gap-3">
|
||||
<span v-if="form.visibility == 'draft'"
|
||||
class="inline-flex items-center rounded-full bg-yellow-100 px-2 py-1 text-xs font-medium text-yellow-600 ring-1 ring-inset ring-gray-500/10 dark:text-white dark:bg-gray-700">
|
||||
<div
|
||||
v-if="
|
||||
['draft', 'closed'].includes(form.visibility) ||
|
||||
(form.tags && form.tags.length > 0)
|
||||
"
|
||||
class="mt-2 flex items-center flex-wrap gap-3"
|
||||
>
|
||||
<span
|
||||
v-if="form.visibility == 'draft'"
|
||||
class="inline-flex items-center rounded-full bg-yellow-100 px-2 py-1 text-xs font-medium text-yellow-600 ring-1 ring-inset ring-gray-500/10 dark:text-white dark:bg-gray-700"
|
||||
>
|
||||
Draft - not publicly accessible
|
||||
</span>
|
||||
<span v-else-if="form.visibility == 'closed'"
|
||||
class="inline-flex items-center rounded-full bg-yellow-100 px-2 py-1 text-xs font-medium text-yellow-600 ring-1 ring-inset ring-gray-500/10 dark:text-white dark:bg-gray-700">
|
||||
<span
|
||||
v-else-if="form.visibility == 'closed'"
|
||||
class="inline-flex items-center rounded-full bg-yellow-100 px-2 py-1 text-xs font-medium text-yellow-600 ring-1 ring-inset ring-gray-500/10 dark:text-white dark:bg-gray-700"
|
||||
>
|
||||
Closed - won't accept new submissions
|
||||
</span>
|
||||
<span v-for="(tag, i) in form.tags" :key="tag"
|
||||
class="inline-flex items-center rounded-full bg-gray-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10 dark:text-white dark:bg-gray-700">
|
||||
<span
|
||||
v-for="(tag) in form.tags"
|
||||
:key="tag"
|
||||
class="inline-flex items-center rounded-full bg-gray-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10 dark:text-white dark:bg-gray-700"
|
||||
>
|
||||
{{ tag }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p v-if="form.closes_at" class="text-yellow-500">
|
||||
<span v-if="form.is_closed"> This form stopped accepting submissions on the {{
|
||||
displayClosesDate
|
||||
}} </span>
|
||||
<span v-else> This form will stop accepting submissions on the {{ displayClosesDate }} </span>
|
||||
<p
|
||||
v-if="form.closes_at"
|
||||
class="text-yellow-500"
|
||||
>
|
||||
<span v-if="form.is_closed">
|
||||
This form stopped accepting submissions on the
|
||||
{{ displayClosesDate }}
|
||||
</span>
|
||||
<span v-else>
|
||||
This form will stop accepting submissions on the
|
||||
{{ displayClosesDate }}
|
||||
</span>
|
||||
</p>
|
||||
<p v-if="form.max_submissions_count > 0" class="text-yellow-500">
|
||||
<span v-if="form.max_number_of_submissions_reached"> The form is now closed because it reached its limit of
|
||||
{{
|
||||
form.max_submissions_count
|
||||
}} submissions. </span>
|
||||
<span v-else> This form will stop accepting submissions after {{ form.max_submissions_count }} submissions.
|
||||
<p
|
||||
v-if="form.max_submissions_count > 0"
|
||||
class="text-yellow-500"
|
||||
>
|
||||
<span v-if="form.max_number_of_submissions_reached">
|
||||
The form is now closed because it reached its limit of
|
||||
{{ form.max_submissions_count }} submissions.
|
||||
</span>
|
||||
<span v-else>
|
||||
This form will stop accepting submissions after
|
||||
{{ form.max_submissions_count }} submissions.
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<form-cleanings class="mt-4" :form="form" />
|
||||
<form-cleanings
|
||||
class="mt-4"
|
||||
:form="form"
|
||||
/>
|
||||
|
||||
<div class="border-b border-gray-200 dark:border-gray-700">
|
||||
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center">
|
||||
<li v-for="(tab, i) in tabsList" :key="i + 1" class="mr-6">
|
||||
<nuxt-link :to="{ name: tab.route }"
|
||||
<li
|
||||
v-for="(tab, i) in tabsList"
|
||||
:key="i + 1"
|
||||
class="mr-6"
|
||||
>
|
||||
<nuxt-link
|
||||
:to="{ name: tab.route }"
|
||||
class="hover:no-underline inline-block py-4 rounded-t-lg border-b-2 text-gray-500 hover:text-gray-600"
|
||||
active-class="text-blue-600 hover:text-blue-900 dark:text-blue-500 dark:hover:text-blue-500 border-blue-600 dark:border-blue-500">
|
||||
active-class="text-blue-600 hover:text-blue-900 dark:text-blue-500 dark:hover:text-blue-500 border-blue-600 dark:border-blue-500"
|
||||
>
|
||||
{{ tab.name }}
|
||||
</nuxt-link>
|
||||
</li>
|
||||
@@ -109,31 +216,35 @@
|
||||
<NuxtPage :form="form" />
|
||||
</div>
|
||||
</template>
|
||||
<div v-else-if="loading" class="text-center w-full p-5">
|
||||
<div
|
||||
v-else-if="loading"
|
||||
class="text-center w-full p-5"
|
||||
>
|
||||
<Loader class="h-6 w-6 mx-auto" />
|
||||
</div>
|
||||
<div v-else class="text-center w-full p-5">
|
||||
<div
|
||||
v-else
|
||||
class="text-center w-full p-5"
|
||||
>
|
||||
Form not found.
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import ProTag from '~/components/global/ProTag.vue'
|
||||
import VButton from '~/components/global/VButton.vue'
|
||||
import ExtraMenu from '../../../components/pages/forms/show/ExtraMenu.vue'
|
||||
import FormCleanings from '../../../components/pages/forms/show/FormCleanings.vue'
|
||||
import { computed } from "vue"
|
||||
import VButton from "~/components/global/VButton.vue"
|
||||
import ExtraMenu from "../../../components/pages/forms/show/ExtraMenu.vue"
|
||||
import FormCleanings from "../../../components/pages/forms/show/FormCleanings.vue"
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
middleware: "auth",
|
||||
})
|
||||
useOpnSeoMeta({
|
||||
title: 'Home'
|
||||
title: "Home",
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const authStore = useAuthStore()
|
||||
const formsStore = useFormsStore()
|
||||
const workingFormStore = useWorkingFormStore()
|
||||
const workspacesStore = useWorkspacesStore()
|
||||
@@ -141,39 +252,44 @@ const workspacesStore = useWorkspacesStore()
|
||||
const slug = useRoute().params.slug
|
||||
|
||||
formsStore.startLoading()
|
||||
const user = computed(() => authStore.user)
|
||||
const form = computed(() => formsStore.getByKey(slug))
|
||||
const workspace = computed(() => workspacesStore.getByKey(form?.value?.workspace_id))
|
||||
|
||||
const loading = computed(() => formsStore.loading || workspacesStore.loading)
|
||||
const displayClosesDate = computed(() => {
|
||||
if (form.value && form.value.closes_at) {
|
||||
const dateObj = new Date(form.value.closes_at)
|
||||
return dateObj.getFullYear() + '-' +
|
||||
String(dateObj.getMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(dateObj.getDate()).padStart(2, '0') + ' ' +
|
||||
String(dateObj.getHours()).padStart(2, '0') + ':' +
|
||||
String(dateObj.getMinutes()).padStart(2, '0')
|
||||
return (
|
||||
dateObj.getFullYear() +
|
||||
"-" +
|
||||
String(dateObj.getMonth() + 1).padStart(2, "0") +
|
||||
"-" +
|
||||
String(dateObj.getDate()).padStart(2, "0") +
|
||||
" " +
|
||||
String(dateObj.getHours()).padStart(2, "0") +
|
||||
":" +
|
||||
String(dateObj.getMinutes()).padStart(2, "0")
|
||||
)
|
||||
}
|
||||
return ''
|
||||
return ""
|
||||
})
|
||||
|
||||
const tabsList = [
|
||||
{
|
||||
name: 'Submissions',
|
||||
route: 'forms-slug-show-submissions'
|
||||
name: "Submissions",
|
||||
route: "forms-slug-show-submissions",
|
||||
},
|
||||
{
|
||||
name: 'Integrations',
|
||||
route: 'forms-slug-show-integrations'
|
||||
name: "Integrations",
|
||||
route: "forms-slug-show-integrations",
|
||||
},
|
||||
{
|
||||
name: 'Analytics',
|
||||
route: 'forms-slug-show-stats'
|
||||
name: "Analytics",
|
||||
route: "forms-slug-show-stats",
|
||||
},
|
||||
{
|
||||
name: 'Share',
|
||||
route: 'forms-slug-show-share'
|
||||
}
|
||||
name: "Share",
|
||||
route: "forms-slug-show-share",
|
||||
},
|
||||
]
|
||||
|
||||
onMounted(() => {
|
||||
@@ -185,17 +301,22 @@ onMounted(() => {
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => form?.value?.id, (id) => {
|
||||
if (id) {
|
||||
workingFormStore.set(form.value)
|
||||
}
|
||||
})
|
||||
watch(
|
||||
() => form?.value?.id,
|
||||
(id) => {
|
||||
if (id) {
|
||||
workingFormStore.set(form.value)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
const goBack = () => {
|
||||
useRouter().push({ name: 'home' })
|
||||
useRouter().push({ name: "home" })
|
||||
}
|
||||
|
||||
const showDraftFormWarningNotification = () => {
|
||||
useAlert().warning('This form is currently in Draft mode and is not publicly accessible, You can change the form status on the edit form page.')
|
||||
useAlert().warning(
|
||||
"This form is currently in Draft mode and is not publicly accessible, You can change the form status on the edit form page.",
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup>
|
||||
definePageMeta({
|
||||
redirect: to => {
|
||||
return { name: 'forms-slug-show-submissions'}
|
||||
}
|
||||
redirect: () => {
|
||||
return { name: "forms-slug-show-submissions" }
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -9,76 +9,110 @@
|
||||
Read, update and create data with dozens of 3rd-party integrations
|
||||
</div>
|
||||
|
||||
<div v-if="integrationsLoading" class="my-6">
|
||||
<Loader class="h-6 w-6 mx-auto"/>
|
||||
<div
|
||||
v-if="integrationsLoading"
|
||||
class="my-6"
|
||||
>
|
||||
<Loader class="h-6 w-6 mx-auto" />
|
||||
</div>
|
||||
<div v-else-if="formIntegrationsList.length" class="my-6">
|
||||
<IntegrationCard v-for="(row) in formIntegrationsList" :key="row.id" :integration="row" :form="form"/>
|
||||
<div
|
||||
v-else-if="formIntegrationsList.length"
|
||||
class="my-6"
|
||||
>
|
||||
<IntegrationCard
|
||||
v-for="row in formIntegrationsList"
|
||||
:key="row.id"
|
||||
:integration="row"
|
||||
:form="form"
|
||||
/>
|
||||
</div>
|
||||
<div class="text-gray-500 border shadow rounded-md p-5 mt-4" v-else>
|
||||
<div
|
||||
v-else
|
||||
class="text-gray-500 border shadow rounded-md p-5 mt-4"
|
||||
>
|
||||
No integration yet form this form.
|
||||
</div>
|
||||
|
||||
|
||||
<h1 class="font-semibold mt-8 text-xl">
|
||||
Add a new integration
|
||||
</h1>
|
||||
<div v-for="(section, sectionName) in sectionsList" :key="sectionName" class="mb-8">
|
||||
<div
|
||||
v-for="(section, sectionName) in sectionsList"
|
||||
:key="sectionName"
|
||||
class="mb-8"
|
||||
>
|
||||
<h3 class="text-gray-500">
|
||||
{{ sectionName }}
|
||||
</h3>
|
||||
<div class="flex flex-wrap mt-2 gap-4">
|
||||
<IntegrationListOption v-for="(sectionItem, sectionItemKey) in section"
|
||||
@select="openIntegrationModal"
|
||||
:key="sectionItemKey" :integration="sectionItem"/>
|
||||
<IntegrationListOption
|
||||
v-for="(sectionItem, sectionItemKey) in section"
|
||||
:key="sectionItemKey"
|
||||
:integration="sectionItem"
|
||||
@select="openIntegrationModal"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<IntegrationModal v-if="form && selectedIntegrationKey && selectedIntegration" :form="form"
|
||||
:integration="selectedIntegration" :integrationKey="selectedIntegrationKey"
|
||||
:show="showIntegrationModal"
|
||||
@close="closeIntegrationModal"/>
|
||||
<IntegrationModal
|
||||
v-if="form && selectedIntegrationKey && selectedIntegration"
|
||||
:form="form"
|
||||
:integration="selectedIntegration"
|
||||
:integration-key="selectedIntegrationKey"
|
||||
:show="showIntegrationModal"
|
||||
@close="closeIntegrationModal"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {computed} from 'vue'
|
||||
import IntegrationModal from '~/components/open/integrations/components/IntegrationModal.vue'
|
||||
import { computed } from "vue"
|
||||
import IntegrationModal from "~/components/open/integrations/components/IntegrationModal.vue"
|
||||
|
||||
const props = defineProps({
|
||||
form: {type: Object, required: true}
|
||||
form: { type: Object, required: true },
|
||||
})
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
middleware: "auth",
|
||||
})
|
||||
useOpnSeoMeta({
|
||||
title: (props.form) ? 'Form Integrations - ' + props.form.title : 'Form Integrations'
|
||||
title: props.form
|
||||
? "Form Integrations - " + props.form.title
|
||||
: "Form Integrations",
|
||||
})
|
||||
|
||||
const alert = useAlert()
|
||||
const crisp = useCrisp()
|
||||
const route = useRoute()
|
||||
|
||||
const formIntegrationsStore = useFormIntegrationsStore()
|
||||
const integrationsLoading = computed(() => formIntegrationsStore.loading)
|
||||
const integrations = computed(() => formIntegrationsStore.availableIntegrations)
|
||||
const sectionsList = computed(() => formIntegrationsStore.integrationsBySection)
|
||||
const formIntegrationsList = computed(() => formIntegrationsStore.getAllByFormId(props.form.id))
|
||||
const integrations = computed(
|
||||
() => formIntegrationsStore.availableIntegrations,
|
||||
)
|
||||
const sectionsList = computed(
|
||||
() => formIntegrationsStore.integrationsBySection,
|
||||
)
|
||||
const formIntegrationsList = computed(() =>
|
||||
formIntegrationsStore.getAllByFormId(props.form.id),
|
||||
)
|
||||
|
||||
let showIntegrationModal = ref(false)
|
||||
let selectedIntegrationKey = ref(null)
|
||||
let selectedIntegration = ref(null)
|
||||
const showIntegrationModal = ref(false)
|
||||
const selectedIntegrationKey = ref(null)
|
||||
const selectedIntegration = ref(null)
|
||||
|
||||
onMounted(() => {
|
||||
formIntegrationsStore.fetchFormIntegrations(props.form.id)
|
||||
})
|
||||
|
||||
const openIntegrationModal = (itemKey) => {
|
||||
if (!itemKey || !integrations.value.has(itemKey)) return alert.error('Integration not found')
|
||||
if (integrations.value.get(itemKey).coming_soon) return alert.warning('This integration is not available yet')
|
||||
if (!itemKey || !integrations.value.has(itemKey))
|
||||
return alert.error("Integration not found")
|
||||
if (integrations.value.get(itemKey).coming_soon)
|
||||
return alert.warning("This integration is not available yet")
|
||||
selectedIntegrationKey.value = itemKey
|
||||
selectedIntegration.value = integrations.value.get(selectedIntegrationKey.value)
|
||||
selectedIntegration.value = integrations.value.get(
|
||||
selectedIntegrationKey.value,
|
||||
)
|
||||
showIntegrationModal.value = true
|
||||
}
|
||||
const closeIntegrationModal = () => {
|
||||
|
||||
@@ -1,57 +1,81 @@
|
||||
<template>
|
||||
<div class="w-full md:w-4/5 lg:w-3/5 md:mx-auto md:max-w-4xl p-4">
|
||||
<div class="mb-20">
|
||||
|
||||
<div class="mb-6 pb-6 border-b w-full flex flex-col sm:flex-row gap-2">
|
||||
<regenerate-form-link class="sm:w-1/2 flex" :form="props.form"/>
|
||||
<regenerate-form-link
|
||||
class="sm:w-1/2 flex"
|
||||
:form="props.form"
|
||||
/>
|
||||
|
||||
<url-form-prefill class="sm:w-1/2" :form="props.form" :extra-query-param="shareUrlForQueryParams"/>
|
||||
<url-form-prefill
|
||||
class="sm:w-1/2"
|
||||
:form="props.form"
|
||||
:extra-query-param="shareUrlForQueryParams"
|
||||
/>
|
||||
|
||||
<embed-form-as-popup-modal class="sm:w-1/2 flex" :form="props.form"/>
|
||||
<embed-form-as-popup-modal
|
||||
class="sm:w-1/2 flex"
|
||||
:form="props.form"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<share-link class="mt-4" :form="props.form" :extra-query-param="shareUrlForQueryParams"/>
|
||||
<share-link
|
||||
class="mt-4"
|
||||
:form="props.form"
|
||||
:extra-query-param="shareUrlForQueryParams"
|
||||
/>
|
||||
|
||||
<embed-code class="mt-6" :form="props.form" :extra-query-param="shareUrlForQueryParams"/>
|
||||
<embed-code
|
||||
class="mt-6"
|
||||
:form="props.form"
|
||||
:extra-query-param="shareUrlForQueryParams"
|
||||
/>
|
||||
|
||||
<form-qr-code class="mt-6" :form="props.form" :extra-query-param="shareUrlForQueryParams"/>
|
||||
|
||||
<advanced-form-url-settings :form="props.form" v-model="shareFormConfig"/>
|
||||
<form-qr-code
|
||||
class="mt-6"
|
||||
:form="props.form"
|
||||
:extra-query-param="shareUrlForQueryParams"
|
||||
/>
|
||||
|
||||
<advanced-form-url-settings
|
||||
v-model="shareFormConfig"
|
||||
:form="props.form"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ShareLink from '~/components/pages/forms/show/ShareLink.vue'
|
||||
import EmbedCode from '~/components/pages/forms/show/EmbedCode.vue'
|
||||
import FormQrCode from '~/components/pages/forms/show/FormQrCode.vue'
|
||||
import UrlFormPrefill from '~/components/pages/forms/show/UrlFormPrefill.vue'
|
||||
import RegenerateFormLink from '~/components/pages/forms/show/RegenerateFormLink.vue'
|
||||
import AdvancedFormUrlSettings from '~/components/open/forms/components/AdvancedFormUrlSettings.vue'
|
||||
import EmbedFormAsPopupModal from '~/components/pages/forms/show/EmbedFormAsPopupModal.vue'
|
||||
import ShareLink from "~/components/pages/forms/show/ShareLink.vue"
|
||||
import EmbedCode from "~/components/pages/forms/show/EmbedCode.vue"
|
||||
import FormQrCode from "~/components/pages/forms/show/FormQrCode.vue"
|
||||
import UrlFormPrefill from "~/components/pages/forms/show/UrlFormPrefill.vue"
|
||||
import RegenerateFormLink from "~/components/pages/forms/show/RegenerateFormLink.vue"
|
||||
import AdvancedFormUrlSettings from "~/components/open/forms/components/AdvancedFormUrlSettings.vue"
|
||||
import EmbedFormAsPopupModal from "~/components/pages/forms/show/EmbedFormAsPopupModal.vue"
|
||||
|
||||
const props = defineProps({
|
||||
form: {type: Object, required: true}
|
||||
form: { type: Object, required: true },
|
||||
})
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
middleware: "auth",
|
||||
})
|
||||
useOpnSeoMeta({
|
||||
title: (props.form) ? 'Share Form - ' + props.form.title : 'Share Form'
|
||||
title: props.form ? "Share Form - " + props.form.title : "Share Form",
|
||||
})
|
||||
|
||||
const shareFormConfig = ref({
|
||||
hide_title: false,
|
||||
auto_submit: false
|
||||
auto_submit: false,
|
||||
})
|
||||
|
||||
const shareUrlForQueryParams = computed(() => {
|
||||
let queryStr = ''
|
||||
let queryStr = ""
|
||||
for (const [key, value] of Object.entries(shareFormConfig.value)) {
|
||||
if (value && value !== 'false' && value !== false) {
|
||||
queryStr += '&' + encodeURIComponent(key) + "=" + encodeURIComponent(value)
|
||||
if (value && value !== "false" && value !== false) {
|
||||
queryStr +=
|
||||
"&" + encodeURIComponent(key) + "=" + encodeURIComponent(value)
|
||||
}
|
||||
}
|
||||
return queryStr.slice(1)
|
||||
|
||||
@@ -3,21 +3,21 @@
|
||||
<h3 class="font-semibold mt-4 text-xl">
|
||||
Form Analytics (last 30 days)
|
||||
</h3>
|
||||
<form-stats :form="form"/>
|
||||
<form-stats :form="form" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import FormStats from '~/components/open/forms/components/FormStats.vue'
|
||||
import FormStats from "~/components/open/forms/components/FormStats.vue"
|
||||
|
||||
const props = defineProps({
|
||||
form: {type: Object, required: true}
|
||||
form: { type: Object, required: true },
|
||||
})
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
middleware: "auth",
|
||||
})
|
||||
useOpnSeoMeta({
|
||||
title: (props.form) ? 'Form Analytics - ' + props.form.title : 'Form Analytics'
|
||||
title: props.form ? "Form Analytics - " + props.form.title : "Form Analytics",
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
<template>
|
||||
<form-submissions/>
|
||||
<form-submissions />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import FormSubmissions from '~/components/open/forms/components/FormSubmissions.vue'
|
||||
import FormSubmissions from "~/components/open/forms/components/FormSubmissions.vue"
|
||||
|
||||
const props = defineProps({
|
||||
form: {type: Object, required: true}
|
||||
form: { type: Object, required: true },
|
||||
})
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
middleware: "auth",
|
||||
})
|
||||
useOpnSeoMeta({
|
||||
title: (props.form) ? 'Form Submissions - ' + props.form.title : 'Form Submissions'
|
||||
title: props.form
|
||||
? "Form Submissions - " + props.form.title
|
||||
: "Form Submissions",
|
||||
})
|
||||
|
||||
onBeforeRouteLeave(() => {
|
||||
useRecordsStore().resetState()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user