9a3d4 form page (#20)
* Form page new ui * UI polishing, code cleanign & bug fixes * form page divide into separate component & user route-view for tabs * new pages change meta info * Display columns modal Co-authored-by: Julien Nahum <jhumanj@MacBook-Pro-de-Julien.local>
This commit is contained in:
@@ -1,481 +0,0 @@
|
||||
<template>
|
||||
<div class="flex mt-6">
|
||||
<div class="w-full md:w-4/5 lg:w-3/5 md:mx-auto md:max-w-4xl px-4">
|
||||
<breadcrumb class="sm:px-6" :path="breadcrumbs" />
|
||||
<div v-if="form" class="sm:px-6">
|
||||
<h2 class="text-nt-blue text-3xl font-bold z-10 mt-6 mb-3">
|
||||
{{ form.title }}
|
||||
<span v-if="form.visibility=='draft'" class="float-right text-white p-2 text-xs inline rounded-lg font-semibold mr-2 bg-gray-400 dark:bg-gray-700">Draft (not public)</span>
|
||||
</h2>
|
||||
|
||||
<p class="mb-3">
|
||||
<span v-if="form.views_count">This form has been seen
|
||||
<span class="font-semibold">{{ form.views_count }}</span> time{{ form.views_count > 0 ? 's' : '' }}
|
||||
and it has received
|
||||
<span class="font-semibold">{{ form.submissions_count }}</span> submission{{ form.submissions_count > 0 ? 's' : '' }}.</span>
|
||||
</p>
|
||||
|
||||
<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. </span>
|
||||
</p>
|
||||
|
||||
<div class="flex justify-center">
|
||||
<share-form-url :form="form" :link="true" />
|
||||
</div>
|
||||
|
||||
<!-- Open Form -->
|
||||
<div class="flex flex-wrap -mx-2">
|
||||
<!-- Edit Form -->
|
||||
<div class="w-full sm:w-1/2 px-2 flex">
|
||||
<div v-track.edit_form_click="{form_id:form.id, form_slug:form.slug}"
|
||||
class="group relative transition-all mt-4 flex items-center p-3 px-6 w-full rounded-md bg-gray-50 dark:bg-gray-700 hover:bg-blue-50 dark:hover:bg-blue-900 cursor-pointer hover:text-blue-500"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 mr-4 "
|
||||
fill="none" viewBox="0 0 24 24" stroke="currentColor"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
|
||||
/>
|
||||
</svg>
|
||||
<span class="font-semibold group relative-hover:text-blue-500">
|
||||
Edit form
|
||||
</span>
|
||||
<router-link :to="{name:'forms.edit',params:{slug:form.slug}}" class="absolute inset-0" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- Open Form -->
|
||||
<div class="w-full sm:w-1/2 px-2 flex">
|
||||
<div
|
||||
v-track.open_form_click="{form_id:form.id, form_slug:form.slug}" class="group relative transition-all mt-4 flex items-center p-3 px-6 w-full rounded-md bg-gray-50 dark:bg-gray-700
|
||||
hover:bg-blue-50 dark:hover:bg-blue-500 cursor-pointer hover:text-blue-500 dark:hover:text-white"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 mr-4 "
|
||||
fill="none" viewBox="0 0 24 24" stroke="currentColor"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
|
||||
/>
|
||||
</svg>
|
||||
<span class="font-semibold group relative-hover:text-blue-500">
|
||||
Open form
|
||||
</span>
|
||||
<a target="_blank" :href="form.share_url" class="absolute inset-0" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Share/Embed form table -->
|
||||
<div class="w-full sm:w-1/2 px-2 flex">
|
||||
<div
|
||||
v-track.share_embed_form_click="{form_id:form.id, form_slug:form.slug}"
|
||||
class="group relative transition-all mt-4 flex items-center p-3 px-6 w-full rounded-md bg-gray-50 dark:bg-gray-700 hover:bg-blue-50 dark:hover:bg-blue-900 cursor-pointer hover:text-blue-500"
|
||||
@click.prevent="showShareEmbedFormModal=true"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 mr-4 "
|
||||
fill="none" viewBox="0 0 24 24" stroke="currentColor"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z"
|
||||
/>
|
||||
</svg>
|
||||
<span class="font-semibold group relative-hover:text-blue-500">
|
||||
Share/Embed form
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Regenerate form link -->
|
||||
<div class="w-full sm:w-1/2 px-2 flex">
|
||||
<div v-track.regenerate_form_link_click="{form_id:form.id, form_slug:form.slug}"
|
||||
class="group relative transition-all mt-4 flex items-center p-3 px-6 w-full rounded-md bg-gray-50 dark:bg-gray-700 hover:bg-blue-50 dark:hover:bg-blue-900 cursor-pointer hover:text-blue-500"
|
||||
@click="showGenerateFormLinkModal=true"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 mr-4 "
|
||||
fill="none" viewBox="0 0 24 24" stroke="currentColor"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"
|
||||
/>
|
||||
</svg>
|
||||
<span class="font-semibold group relative-hover:text-blue-500">
|
||||
Regenerate form link
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full sm:w-1/2 px-2 flex">
|
||||
<div v-track.url_form_prefill_click="{form_id:form.id, form_slug:form.slug}"
|
||||
class="group relative transition-all mt-4 flex items-center p-3 px-6 w-full rounded-md bg-gray-50 dark:bg-gray-700 hover:bg-blue-50 dark:hover:bg-blue-900 cursor-pointer hover:text-blue-500"
|
||||
@click="showUrlFormPrefillModal=true"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 mr-4" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M17 16v2a2 2 0 01-2 2H5a2 2 0 01-2-2v-7a2 2 0 012-2h2m3-4H9a2 2 0 00-2 2v7a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-1m-1 4l-3 3m0 0l-3-3m3 3V3"
|
||||
/>
|
||||
</svg>
|
||||
<span class="font-semibold group relative-hover:text-blue-500">
|
||||
Url form pre-fill <pro-tag class="ml-2" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full sm:w-1/2 px-2 flex">
|
||||
<div v-track.duplicate_form_click="{form_id:form.id, form_slug:form.slug}"
|
||||
class="group relative transition-all mt-4 flex items-center p-3 px-6 w-full rounded-md bg-gray-50 dark:bg-gray-700 hover:bg-blue-50 dark:hover:bg-blue-900 cursor-pointer hover:text-blue-500"
|
||||
@click="duplicateForm"
|
||||
>
|
||||
<template v-if="!loadingDuplicate">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 mr-4 "
|
||||
fill="none" viewBox="0 0 24 24" stroke="currentColor"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M8 7v8a2 2 0 002 2h6M8 7V5a2 2 0 012-2h4.586a1 1 0 01.707.293l4.414 4.414a1 1 0 01.293.707V15a2 2 0 01-2 2h-2M8 7H6a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2v-2"
|
||||
/>
|
||||
</svg>
|
||||
<span class="font-semibold group relative-hover:text-blue-500">
|
||||
Duplicate form
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<loader class="h-6 w-6 text-nt-blue mx-auto" />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full sm:w-1/2 px-2 flex mb-5">
|
||||
<div v-track.delete_form_click="{form_id:form.id, form_slug:form.slug}"
|
||||
class="group relative transition-all mt-4 flex items-center p-3 px-6 w-full rounded-md bg-gray-50 dark:bg-gray-700 hover:bg-red-50 dark:hover:bg-red-900 cursor-pointer hover:text-red-500"
|
||||
@click="alertConfirm('Do you really want to delete this form?',deleteForm)"
|
||||
>
|
||||
<template v-if="!loadingDelete">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 mr-4"
|
||||
fill="none" viewBox="0 0 24 24" stroke="currentColor"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||
/>
|
||||
</svg>
|
||||
<span class="font-semibold group relative-hover:text-red-500">
|
||||
Delete form
|
||||
</span>
|
||||
</template>
|
||||
<loader v-else class="h-6 w-6 text-nt-blue mx-auto" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full sm:w-1/2 px-2 flex mb-5" v-if="user.admin">
|
||||
<div class="group relative transition-all mt-4 flex items-center p-3 px-6 w-full rounded-md bg-gray-50 dark:bg-gray-700 hover:bg-blue-50 dark:hover:bg-blue-900 cursor-pointer hover:text-blue-500"
|
||||
@click="showCreateTemplateModal=true"
|
||||
>
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 mr-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M17 14v6m-3-3h6M6 10h2a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v2a2 2 0 002 2zm10 0h2a2 2 0 002-2V6a2 2 0 00-2-2h-2a2 2 0 00-2 2v2a2 2 0 002 2zM6 20h2a2 2 0 002-2v-2a2 2 0 00-2-2H6a2 2 0 00-2 2v2a2 2 0 002 2z" />
|
||||
</svg>
|
||||
<span class="font-semibold group relative-hover:text-blue-500">
|
||||
Create template
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form Submissions -->
|
||||
<div class="pt-5 mt-5 border-t" id="table-page" v-if="form">
|
||||
<form-submissions />
|
||||
</div>
|
||||
|
||||
<!-- Form Analytics -->
|
||||
<div class="pt-5 mt-5 border-t">
|
||||
<h3 class="font-semibold">
|
||||
Form Analytics (last 30 days)
|
||||
</h3>
|
||||
<form-stats :form="form" />
|
||||
</div>
|
||||
|
||||
<!-- Share/Embed form modal -->
|
||||
<modal :show="showShareEmbedFormModal" @close="showShareEmbedFormModal=false">
|
||||
<div class="px-4">
|
||||
<h2 class="text-nt-blue text-3xl font-bold mb-6">
|
||||
Share/Embed your form
|
||||
</h2>
|
||||
|
||||
<!-- Link -->
|
||||
<h3 class="font-bold text-xl border-t pt-4">
|
||||
Share
|
||||
</h3>
|
||||
<p>Share your form using the link below:</p>
|
||||
<share-form-url :form="form" />
|
||||
|
||||
<!-- Embed -->
|
||||
<h3 class="font-bold text-xl border-t pt-4">
|
||||
Embed
|
||||
</h3>
|
||||
<p>
|
||||
Embed your form on your website by copying the html code below.
|
||||
</p>
|
||||
<embed-form-code :form="form" />
|
||||
|
||||
<div class="flex justify-end mt-4">
|
||||
<v-button color="gray" shade="light" @click="showShareEmbedFormModal=false">Close</v-button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</modal>
|
||||
|
||||
<!-- Regenerate form link modal -->
|
||||
<modal :show="showGenerateFormLinkModal" @close="showGenerateFormLinkModal=false">
|
||||
<div class="-m-6">
|
||||
<div class="p-6">
|
||||
<h2 class="text-nt-blue text-3xl font-bold mb-6">
|
||||
Generate new form link
|
||||
</h2>
|
||||
<p>
|
||||
You can choose between two different URL formats for your form. <span class="font-semibold">Be careful, changing your form URL
|
||||
is not a reversible operation</span>. Make sure to udpate your form URL everywhere where it's used.
|
||||
</p>
|
||||
</div>
|
||||
<div class="border-t py-4 mt-4 px-6">
|
||||
<h3 class="text-xl text-gray-700 font-semibold">
|
||||
Human Readable URL
|
||||
</h3>
|
||||
<p>If your users are going to see this url, you might want to make nice and readable. Example:</p>
|
||||
<p class="text-gray-600 p-4 bg-gray-100 rounded-md mt-4">
|
||||
https://opnform.com/forms/contact
|
||||
</p>
|
||||
<div class="text-center mt-4">
|
||||
<v-button :loading="loadingNewLink" @click="regenerateLink('slug')">
|
||||
Generate a Human Readable URL
|
||||
</v-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t pt-4 mt-4 px-6 pb-10">
|
||||
<h3 class="text-xl text-gray-700 font-semibold">
|
||||
Random ID URL
|
||||
</h3>
|
||||
<p>
|
||||
If your user are not going to see your form url (if it's embedded), and if you prefer to have a random
|
||||
non-guessable URL. Example:
|
||||
</p>
|
||||
<p class="text-gray-600 p-4 bg-gray-100 rounded-md mt-4">
|
||||
https://opnform.com/forms/b4417f9c-34ae-4421-8006-832ee47786e7
|
||||
</p>
|
||||
<div class="text-center mt-4">
|
||||
<v-button :loading="loadingNewLink" @click="regenerateLink('uuid')">
|
||||
Generate a Random ID URL
|
||||
</v-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end mt-4 pb-5 px-6">
|
||||
<v-button color="gray" shade="light" @click="showGenerateFormLinkModal=false">Close</v-button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</modal>
|
||||
|
||||
<create-template-modal :form="form" :show="showCreateTemplateModal" @close="showCreateTemplateModal=false" />
|
||||
|
||||
<url-form-prefill-modal :form="form" :show="showUrlFormPrefillModal" @close="showUrlFormPrefillModal=false" />
|
||||
</div>
|
||||
<div v-else-if="loading" class="text-center w-full p-5">
|
||||
<loader class="h-6 w-6 mx-auto" />
|
||||
</div>
|
||||
<div v-else>
|
||||
Form not found.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import store from '~/store'
|
||||
import Form from 'vform'
|
||||
import ShareFormUrl from '../../components/open/forms/components/ShareFormUrl'
|
||||
import EmbedFormCode from '../../components/open/forms/components/EmbedFormCode'
|
||||
import Breadcrumb from '../../components/common/Breadcrumb'
|
||||
import { mapGetters, mapState } from 'vuex'
|
||||
import ProTag from '../../components/common/ProTag'
|
||||
import UrlFormPrefillModal from '../../components/pages/forms/UrlFormPrefillModal'
|
||||
import CreateTemplateModal from '../../components/pages/forms/CreateTemplateModal'
|
||||
import FormStats from '../../components/open/forms/components/FormStats'
|
||||
import FormSubmissions from '../../components/open/forms/components/FormSubmissions'
|
||||
|
||||
const loadForms = function () {
|
||||
store.commit('open/forms/startLoading')
|
||||
store.dispatch('open/workspaces/loadIfEmpty').then(() => {
|
||||
store.dispatch('open/forms/loadIfEmpty', store.state['open/workspaces'].currentId)
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'EditForm',
|
||||
components: { UrlFormPrefillModal, CreateTemplateModal, ProTag, Breadcrumb, ShareFormUrl, EmbedFormCode, FormStats, FormSubmissions },
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
loadForms()
|
||||
next()
|
||||
},
|
||||
|
||||
beforeRouteLeave (to, from, next) {
|
||||
this.workingForm = null
|
||||
next()
|
||||
},
|
||||
middleware: 'auth',
|
||||
|
||||
data () {
|
||||
return {
|
||||
loadingDuplicate: false,
|
||||
loadingDelete: false,
|
||||
loadingNewLink: false,
|
||||
showNotionEmbedModal: false,
|
||||
showShareEmbedFormModal: false,
|
||||
showUrlFormPrefillModal: false,
|
||||
showGenerateFormLinkModal: false,
|
||||
showCreateTemplateModal: false
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters({
|
||||
user: 'auth/user'
|
||||
}),
|
||||
...mapState({
|
||||
formsLoading: state => state['open/forms'].loading,
|
||||
workspacesLoading: state => state['open/workspaces'].loading
|
||||
}),
|
||||
workingForm: {
|
||||
get () {
|
||||
return this.$store.state['open/working_form'].content
|
||||
},
|
||||
set (value) {
|
||||
this.$store.commit('open/working_form/set', value)
|
||||
}
|
||||
},
|
||||
workspace () {
|
||||
if (!this.form) return null
|
||||
return this.$store.getters['open/workspaces/getById'](this.form.workspace_id)
|
||||
},
|
||||
form () {
|
||||
return this.$store.getters['open/forms/getBySlug'](this.$route.params.slug)
|
||||
},
|
||||
formEndpoint: () => '/api/open/forms/{id}',
|
||||
breadcrumbs () {
|
||||
if (!this.form) {
|
||||
return [{ route: { name: 'home' }, label: 'Your Forms' }]
|
||||
}
|
||||
return [{ route: { name: 'home' }, label: 'Your Forms' }, { label: this.form.title }]
|
||||
},
|
||||
loading () {
|
||||
return this.formsLoading || this.workspacesLoading
|
||||
},
|
||||
displayClosesDate(){
|
||||
if(this.form.closes_at){
|
||||
let dateObj = new Date(this.form.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 "";
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
form () {
|
||||
this.workingForm = new Form(this.form)
|
||||
}
|
||||
},
|
||||
|
||||
mounted () {
|
||||
this.updatedForm = new Form(this.form)
|
||||
|
||||
if (this.$route.params.hasOwnProperty('new_form') && this.$route.params.new_form) {
|
||||
// if (!this.user.is_subscribed && !this.user.has_customer_id) {
|
||||
// // Crisp offer
|
||||
// this.$getCrisp().push(['set', 'session:event', [[['first_form_created', { form_id: this.form.id, form_slug: this.form.slug }, 'blue']]]])
|
||||
//
|
||||
// setTimeout(
|
||||
// function () {
|
||||
// window.$crisp.push(['do', 'chat:show'])
|
||||
// window.$crisp.push(['do', 'chat:open'])
|
||||
// window.$crisp.push([
|
||||
// 'do',
|
||||
// 'message:show',
|
||||
// ['text',
|
||||
// 'Hey there! I\m Julien the founder of NotionForms. Congrats on setting up your first OpnForm 🎉']
|
||||
// ])
|
||||
// setTimeout(
|
||||
// function () {
|
||||
// window.$crisp.push(['do', 'chat:show'])
|
||||
// window.$crisp.push(['do', 'chat:open'])
|
||||
// window.$crisp.push([
|
||||
// 'do',
|
||||
// 'message:show',
|
||||
// ['text',
|
||||
// 'A small gift to congratulate you? 🎁 I\'d be happy to offer you a 40% discount on your first month of a Pro subscription. Let me know if you\'re interested!']
|
||||
// ])
|
||||
// setTimeout(
|
||||
// function () {
|
||||
// window.$crisp.push(['do', 'chat:show'])
|
||||
// window.$crisp.push(['do', 'chat:open'])
|
||||
// window.$crisp.push([
|
||||
// 'do',
|
||||
// 'message:show',
|
||||
// ['text',
|
||||
// 'Just use the code "FIRSTFORM40" in the next 24 hours to get the discount! 🎉']
|
||||
// ])
|
||||
// }, 20000)
|
||||
// }, 4000)
|
||||
// }, 4000)
|
||||
// }
|
||||
}
|
||||
},
|
||||
|
||||
metaInfo () {
|
||||
return { title: this.$t('home') }
|
||||
},
|
||||
|
||||
methods: {
|
||||
openCrisp () {
|
||||
window.$crisp.push(['do', 'chat:show'])
|
||||
window.$crisp.push(['do', 'chat:open'])
|
||||
},
|
||||
duplicateForm () {
|
||||
if (this.loadingDuplicate) return
|
||||
this.loadingDuplicate = true
|
||||
axios.post(this.formEndpoint.replace('{id}', this.form.id) + '/duplicate').then((response) => {
|
||||
this.$store.commit('open/forms/addOrUpdate', response.data.new_form)
|
||||
this.$router.push({ name: 'forms.show', params: { slug: response.data.new_form.slug } })
|
||||
this.alertSuccess('Form was successfully duplicated.')
|
||||
this.loadingDuplicate = false
|
||||
})
|
||||
},
|
||||
regenerateLink (option) {
|
||||
if (this.loadingNewLink) return
|
||||
this.loadingNewLink = true
|
||||
axios.put(this.formEndpoint.replace('{id}', this.form.id) + '/regenerate-link/' + option).then((response) => {
|
||||
this.$store.commit('open/forms/addOrUpdate', response.data.form)
|
||||
this.$router.push({ name: 'forms.show', params: { slug: response.data.form.slug } })
|
||||
this.alertSuccess(response.data.message)
|
||||
this.loadingNewLink = false
|
||||
}).finally(() => {
|
||||
this.showGenerateFormLinkModal = false
|
||||
})
|
||||
},
|
||||
deleteForm () {
|
||||
if (this.loadingDelete) return
|
||||
this.loadingDelete = true
|
||||
axios.delete(this.formEndpoint.replace('{id}', this.form.id)).then(() => {
|
||||
this.$store.commit('open/forms/remove', this.form)
|
||||
this.$router.push({ name: 'home' })
|
||||
this.alertSuccess('Form was deleted.')
|
||||
this.loadingDelete = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
34
resources/js/pages/forms/show/analytics.vue
Normal file
34
resources/js/pages/forms/show/analytics.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<div>
|
||||
<h3 class="font-semibold mt-4 text-xl">
|
||||
Form Analytics (last 30 days)
|
||||
</h3>
|
||||
<form-stats :form="form"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormStats from '../../../components/open/forms/components/FormStats'
|
||||
|
||||
export default {
|
||||
components: {FormStats},
|
||||
props: {
|
||||
form: { type: Object, required: true }
|
||||
},
|
||||
|
||||
metaInfo() {
|
||||
return {title: (this.form) ? 'Form Analytics - '+this.form.title : 'Form Analytics'}
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
}),
|
||||
|
||||
mounted() {},
|
||||
|
||||
computed: {},
|
||||
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
225
resources/js/pages/forms/show/index.vue
Normal file
225
resources/js/pages/forms/show/index.vue
Normal file
@@ -0,0 +1,225 @@
|
||||
<template>
|
||||
<div class="bg-white">
|
||||
<template v-if="form">
|
||||
<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="#" @click.prevent="goBack" class="flex text-blue mb-2 font-semibold text-sm">
|
||||
<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>
|
||||
|
||||
<div class="flex">
|
||||
<h2 class="flex-grow text-gray-900 truncate">
|
||||
{{ form.title }}
|
||||
</h2>
|
||||
<div class="flex">
|
||||
<extra-menu :form="form" />
|
||||
|
||||
<v-button target="_blank" :to="{name:'forms.show_public', params: {slug: form.slug}}"
|
||||
color="white" class="mr-2 text-blue-600 hidden sm:block"
|
||||
v-track.view_form_click="{form_id:form.id, form_slug:form.slug}">
|
||||
<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"/>
|
||||
</svg>
|
||||
</v-button>
|
||||
<v-button class="text-white" @click="openEdit">
|
||||
<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"/>
|
||||
</svg>
|
||||
Edit form
|
||||
</v-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="flex text-gray-500">
|
||||
<li class="pr-1">{{ form.views_count }} view{{ form.views_count > 0 ? 's' : '' }}</li>
|
||||
<li class="list-disc ml-6 pr-1">{{ form.submissions_count }}
|
||||
submission{{ form.submissions_count > 0 ? 's' : '' }}
|
||||
</li>
|
||||
<li class="list-disc ml-6 pr-1 text-blue-500" v-if="form.visibility=='draft'">Draft (not public)</li>
|
||||
<li class="list-disc ml-6">Edited {{ form.last_edited_human }}</li>
|
||||
</ul>
|
||||
|
||||
<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. </span>
|
||||
</p>
|
||||
|
||||
<div class="mt-4 border-b border-gray-200 dark:border-gray-700">
|
||||
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center">
|
||||
<li class="mr-6" v-for="(tab, i) in tabsList" :key="i+1">
|
||||
<router-link :to="{ name: tab.route }"
|
||||
class="hover:no-underline inline-block py-4 rounded-t-lg border-b-2 border-transparent 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"
|
||||
>{{tab.name}}</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex bg-white">
|
||||
<div class="w-full md:w-4/5 lg:w-3/5 md:mx-auto md:max-w-4xl px-4">
|
||||
<div class="py-4">
|
||||
<transition name="fade" mode="out-in">
|
||||
<router-view :form="form" />
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<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">
|
||||
Form not found.
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import store from '~/store'
|
||||
import Form from 'vform'
|
||||
import {mapGetters, mapState} from 'vuex'
|
||||
import ProTag from '../../../components/common/ProTag'
|
||||
import VButton from "../../../components/common/Button";
|
||||
import ExtraMenu from '../../../components/pages/forms/show/ExtraMenu'
|
||||
|
||||
const loadForms = function () {
|
||||
store.commit('open/forms/startLoading')
|
||||
store.dispatch('open/workspaces/loadIfEmpty').then(() => {
|
||||
store.dispatch('open/forms/loadIfEmpty', store.state['open/workspaces'].currentId)
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'ShowForm',
|
||||
components: {
|
||||
VButton,
|
||||
ProTag,
|
||||
ExtraMenu
|
||||
},
|
||||
|
||||
beforeRouteEnter(to, from, next) {
|
||||
loadForms()
|
||||
next()
|
||||
},
|
||||
|
||||
beforeRouteLeave(to, from, next) {
|
||||
this.workingForm = null
|
||||
next()
|
||||
},
|
||||
middleware: 'auth',
|
||||
|
||||
data() {
|
||||
return {
|
||||
tabsList: [
|
||||
{
|
||||
name: 'Submissions',
|
||||
route: 'forms.show'
|
||||
},
|
||||
{
|
||||
name: 'Analytics',
|
||||
route: 'forms.show.analytics'
|
||||
},
|
||||
{
|
||||
name: 'Share',
|
||||
route: 'forms.show.share'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters({
|
||||
user: 'auth/user'
|
||||
}),
|
||||
...mapState({
|
||||
formsLoading: state => state['open/forms'].loading,
|
||||
workspacesLoading: state => state['open/workspaces'].loading
|
||||
}),
|
||||
workingForm: {
|
||||
get() {
|
||||
return this.$store.state['open/working_form'].content
|
||||
},
|
||||
set(value) {
|
||||
this.$store.commit('open/working_form/set', value)
|
||||
}
|
||||
},
|
||||
workspace() {
|
||||
if (!this.form) return null
|
||||
return this.$store.getters['open/workspaces/getById'](this.form.workspace_id)
|
||||
},
|
||||
form() {
|
||||
return this.$store.getters['open/forms/getBySlug'](this.$route.params.slug)
|
||||
},
|
||||
formEndpoint: () => '/api/open/forms/{id}',
|
||||
loading() {
|
||||
return this.formsLoading || this.workspacesLoading
|
||||
},
|
||||
displayClosesDate() {
|
||||
if (this.form.closes_at) {
|
||||
let dateObj = new Date(this.form.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 "";
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
form() {
|
||||
this.workingForm = new Form(this.form)
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.form) {
|
||||
this.workingForm = new Form(this.form)
|
||||
}
|
||||
},
|
||||
|
||||
metaInfo() {
|
||||
return {title: this.$t('home')}
|
||||
},
|
||||
|
||||
methods: {
|
||||
openCrisp() {
|
||||
window.$crisp.push(['do', 'chat:show'])
|
||||
window.$crisp.push(['do', 'chat:open'])
|
||||
},
|
||||
goBack() {
|
||||
this.$router.push({name: 'home'})
|
||||
},
|
||||
openEdit() {
|
||||
this.$router.push({name: 'forms.edit', params: {slug: this.form.slug}})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
47
resources/js/pages/forms/show/share.vue
Normal file
47
resources/js/pages/forms/show/share.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<div>
|
||||
<share-link class="mt-4" :form="form" />
|
||||
|
||||
<embed-code class="mt-6" :form="form" />
|
||||
|
||||
<div class="mt-6 pt-6 border-t w-full flex">
|
||||
<regenerate-form-link class="sm:w-1/2 mr-4" :form="form" />
|
||||
|
||||
<url-form-prefill class="sm:w-1/2" :form="form" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ShareLink from '../../../components/pages/forms/show/ShareLink'
|
||||
import EmbedCode from '../../../components/pages/forms/show/EmbedCode'
|
||||
import UrlFormPrefill from '../../../components/pages/forms/show/UrlFormPrefill'
|
||||
import RegenerateFormLink from '../../../components/pages/forms/show/RegenerateFormLink'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ShareLink,
|
||||
EmbedCode,
|
||||
UrlFormPrefill,
|
||||
RegenerateFormLink
|
||||
},
|
||||
props: {
|
||||
form: { type: Object, required: true }
|
||||
},
|
||||
|
||||
metaInfo() {
|
||||
return {title: (this.form) ? 'Form Share - '+this.form.title : 'Form Share'}
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
}),
|
||||
|
||||
mounted() {},
|
||||
|
||||
computed: {},
|
||||
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
31
resources/js/pages/forms/show/submissions.vue
Normal file
31
resources/js/pages/forms/show/submissions.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<div id="table-page">
|
||||
<form-submissions/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormSubmissions from '../../../components/open/forms/components/FormSubmissions'
|
||||
|
||||
export default {
|
||||
components: {FormSubmissions},
|
||||
props: {
|
||||
form: { type: Object, required: true }
|
||||
},
|
||||
|
||||
metaInfo() {
|
||||
return {title: (this.form) ? 'Form Submissions - '+this.form.title : 'Form Submissions'}
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
}),
|
||||
|
||||
mounted() {},
|
||||
|
||||
computed: {},
|
||||
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user