Fixed most of the form/show pages

This commit is contained in:
Julien Nahum 2023-12-20 18:38:43 +01:00
parent af5656ce81
commit bab8517879
19 changed files with 317 additions and 335 deletions

View File

@ -162,7 +162,7 @@ export default {
helpUrl: () => this.config.links.help_url, helpUrl: () => this.config.links.help_url,
form () { form () {
if (this.$route.name && this.$route.name.startsWith('forms.show_public')) { if (this.$route.name && this.$route.name.startsWith('forms.show_public')) {
return this.formsStore.getBySlug(this.$route.params.slug) return this.formsStore.getByKey(this.$route.params.slug)
} }
return null return null
}, },

View File

@ -80,13 +80,13 @@ export default {
switchWorkspace (workspace) { switchWorkspace (workspace) {
this.workspacesStore.setCurrentId(workspace.id) this.workspacesStore.setCurrentId(workspace.id)
this.formsStore.resetState() this.formsStore.resetState()
this.formsStore.load(workspace.id) this.formsStore.loadAll(workspace.id)
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
if (route.name !== 'home') { if (route.name !== 'home') {
router.push({ name: 'home' }) router.push({ name: 'home' })
} }
this.formsStore.load(workspace.id) this.formsStore.loadAll(workspace.id)
}, },
isUrl (str) { isUrl (str) {
try { try {

View File

@ -9,13 +9,13 @@
</h3> </h3>
</div> </div>
</template> </template>
<toggle-switch-input :value="value.hide_title" name="hide_title" class="mt-4" <toggle-switch-input :model-value="modelValue.hide_title" name="hide_title" class="mt-4"
label="Hide Form Title" label="Hide Form Title"
:disabled="(form.hide_title===true)?true:null" :disabled="(form.hide_title===true)?true:null"
:help="hideTitleHelp" :help="hideTitleHelp"
@update:model-value="onChangeHideTitle" @update:model-value="onChangeHideTitle"
/> />
<toggle-switch-input :value="value.auto_submit" name="auto_submit" class="mt-4" <toggle-switch-input :model-value="modelValue.auto_submit" name="auto_submit" class="mt-4"
label="Auto Submit Form" label="Auto Submit Form"
help="Form will auto submit immediate after open URL" help="Form will auto submit immediate after open URL"
@update:model-value="onChangeAutoSubmit" @update:model-value="onChangeAutoSubmit"
@ -33,7 +33,7 @@ export default {
type: Object, type: Object,
required: true required: true
}, },
value: { modelValue: {
type: Object, type: Object,
required: true required: true
} }
@ -56,10 +56,10 @@ export default {
methods: { methods: {
onChangeHideTitle (val) { onChangeHideTitle (val) {
this.value.hide_title = val this.modelValue.hide_title = val
}, },
onChangeAutoSubmit (val) { onChangeAutoSubmit (val) {
this.value.auto_submit = val this.modelValue.auto_submit = val
} }
} }
} }

View File

@ -163,7 +163,7 @@ export default {
computed: { computed: {
createdForm () { createdForm () {
return this.formsStore.getBySlug(this.createdFormSlug) return this.formsStore.getByKey(this.createdFormSlug)
}, },
steps () { steps () {
return [ return [
@ -246,7 +246,7 @@ export default {
this.form.put('/open/forms/{id}/'.replace('{id}', this.form.id)).then((data) => { this.form.put('/open/forms/{id}/'.replace('{id}', this.form.id)).then((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-slug-show', params: { slug: this.form.slug } })
this.amplitude.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) => {

View File

@ -105,8 +105,7 @@ export default {
getChartData () { getChartData () {
if (!this.form) { return null } if (!this.form) { return null }
this.isLoading = true this.isLoading = true
axios.get('/api/open/workspaces/' + this.form.workspace_id + '/form-stats/' + this.form.id).then((response) => { opnFetch('/open/workspaces/' + this.form.workspace_id + '/form-stats/' + this.form.id).then((statsData) => {
const statsData = response.data
if (statsData && statsData.views !== undefined) { if (statsData && statsData.views !== undefined) {
this.chartData.labels = Object.keys(statsData.views) this.chartData.labels = Object.keys(statsData.views)
this.chartData.datasets[0].data = statsData.views this.chartData.datasets[0].data = statsData.views

View File

@ -210,9 +210,7 @@ export default {
return return
} }
this.isLoading = true this.isLoading = true
axios.get('/api/open/forms/' + this.form.id + '/submissions?page=' + this.currentPage).then((response) => { opnFetch('/open/forms/' + this.form.id + '/submissions?page=' + this.currentPage).then((resData) => {
const resData = response.data
this.tableData = this.tableData.concat(resData.data.map((record) => record.data)) this.tableData = this.tableData.concat(resData.data.map((record) => record.data))
this.dataChanged() this.dataChanged()
@ -235,6 +233,7 @@ export default {
} }
}, },
onChangeDisplayColumns () { onChangeDisplayColumns () {
if (process.client)
window.localStorage.setItem('display-columns-formid-' + this.form.id, JSON.stringify(this.displayColumns)) window.localStorage.setItem('display-columns-formid-' + this.form.id, JSON.stringify(this.displayColumns))
this.form.properties = this.properties.concat(this.removed_properties).filter((field) => { this.form.properties = this.properties.concat(this.removed_properties).filter((field) => {
return this.displayColumns[field.id] === true return this.displayColumns[field.id] === true

View File

@ -117,7 +117,7 @@ export default {
related_templates: null, related_templates: null,
questions: [] questions: []
}) })
this.templatesStore.loadIfEmpty() loadAllTemplates(this.templatesStore)
}, },
computed: { computed: {

View File

@ -169,7 +169,7 @@ export default {
}), }),
computed: { computed: {
formEndpoint: () => '/api/open/forms/{id}' formEndpoint: () => '/open/forms/{id}'
}, },
methods: { methods: {
@ -185,9 +185,9 @@ export default {
duplicateForm () { duplicateForm () {
if (this.loadingDuplicate) return if (this.loadingDuplicate) return
this.loadingDuplicate = true this.loadingDuplicate = true
axios.post(this.formEndpoint.replace('{id}', this.form.id) + '/duplicate').then((response) => { opnFetch(this.formEndpoint.replace('{id}', this.form.id) + '/duplicate',{method: 'POST'}).then((data) => {
this.formsStore.addOrUpdate(response.data.new_form) this.formsStore.save(data.new_form)
this.$router.push({ name: 'forms.show', params: { slug: response.data.new_form.slug } }) this.$router.push({ name: 'forms-show', params: { slug: data.new_form.slug } })
this.alertSuccess('Form was successfully duplicated.') this.alertSuccess('Form was successfully duplicated.')
this.loadingDuplicate = false this.loadingDuplicate = false
}) })
@ -195,7 +195,7 @@ export default {
deleteForm () { deleteForm () {
if (this.loadingDelete) return if (this.loadingDelete) return
this.loadingDelete = true this.loadingDelete = true
axios.delete(this.formEndpoint.replace('{id}', this.form.id)).then(() => { opnFetch(this.formEndpoint.replace('{id}', this.form.id),{method:'DELETE'}).then(() => {
this.formsStore.remove(this.form) this.formsStore.remove(this.form)
this.$router.push({ name: 'home' }) this.$router.push({ name: 'home' })
this.alertSuccess('Form was deleted.') this.alertSuccess('Form was deleted.')

View File

@ -105,7 +105,7 @@ export default {
this.loadingNewLink = true this.loadingNewLink = true
axios.put(this.formEndpoint.replace('{id}', this.form.id) + '/regenerate-link/' + option).then((response) => { axios.put(this.formEndpoint.replace('{id}', this.form.id) + '/regenerate-link/' + option).then((response) => {
this.formsStore.addOrUpdate(response.data.form) this.formsStore.addOrUpdate(response.data.form)
this.$router.push({name: 'forms.show', params: {slug: response.data.form.slug}}) this.$router.push({name: 'forms-slug-show', params: {slug: response.data.form.slug}})
this.alertSuccess(response.data.message) this.alertSuccess(response.data.message)
this.loadingNewLink = false this.loadingNewLink = false
}).finally(() => { }).finally(() => {

View File

@ -32,7 +32,7 @@ export const useContentStore = (mapKey = 'id') => {
}) })
} }
function remove(item) { function remove(item) {
content.value.remove(item[mapKey]) content.value.delete( typeof item === 'object' ? item[mapKey] : item)
} }
function startLoading() { function startLoading() {

View File

@ -16,37 +16,15 @@
<script> <script>
import { computed } from 'vue' import { computed } from 'vue'
import Form from 'vform' import Form from 'vform'
import { useFormsStore } from '../../../stores/forms.js'
import { useWorkingFormStore } from '../../../stores/working_form.js'
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 formsStore = useFormsStore()
const workspacesStore = useWorkspacesStore()
formsStore.startLoading()
workspacesStore.loadIfEmpty().then(() => {
formsStore.load(workspacesStore.currentId)
})
}
export default { export default {
name: 'EditForm', name: 'EditForm',
components: { Breadcrumb }, components: { Breadcrumb },
mixins: [SeoMeta], mixins: [SeoMeta],
middleware: 'auth', middleware: 'auth',
beforeRouteEnter (to, from, next) {
const formsStore = useFormsStore()
const workingFormStore = useWorkingFormStore()
if (!formsStore.getBySlug(to.params.slug)) {
loadForms()
}
workingFormStore.set(null) // Reset old working form
next()
},
beforeRouteLeave (to, from, next) { beforeRouteLeave (to, from, next) {
if (this.isDirty()) { if (this.isDirty()) {
return this.alertConfirm('Changes you made may not be saved. Are you sure want to leave?', () => { return this.alertConfirm('Changes you made may not be saved. Are you sure want to leave?', () => {
@ -61,11 +39,17 @@ export default {
const formsStore = useFormsStore() const formsStore = useFormsStore()
const workingFormStore = useWorkingFormStore() const workingFormStore = useWorkingFormStore()
const workspacesStore = useWorkspacesStore() const workspacesStore = useWorkspacesStore()
if (!formsStore.allLoaded) {
formsStore.loadAll(useWorkspacesStore().currentId)
}
workingFormStore.set(null) // Reset old working form
return { return {
formsStore, formsStore,
workingFormStore, workingFormStore,
workspacesStore, workspacesStore,
formsLoading : computed(() => formsStore.loading) formsLoading: computed(() => formsStore.loading)
} }
}, },
@ -88,7 +72,7 @@ export default {
} }
}, },
form () { form () {
return this.formsStore.getBySlug(this.$route.params.slug) return this.formsStore.getByKey(this.$route.params.slug)
}, },
pageLoaded () { pageLoaded () {
return !this.loading && this.updatedForm !== null return !this.loading && this.updatedForm !== null
@ -114,7 +98,7 @@ export default {
} }
} }
this.closeAlert() // this.closeAlert()
if (!this.form) { if (!this.form) {
loadForms() loadForms()
} else { } else {

View File

@ -0,0 +1,254 @@
<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="#" 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>
<div class="flex flex-wrap">
<h2 class="flex-grow text-gray-900 truncate">
{{ form.title }}
</h2>
<div class="flex">
<extra-menu :form="form" />
<v-button 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"
/>
</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>
<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>
<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"
>
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"
>
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"
>
{{ 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>
<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" />
<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 }"
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"
>
{{ tab.name }}
</nuxt-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">
<NuxtPage :form="form"/>
</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 { computed } from 'vue'
import Form from 'vform'
import ProTag from '~/components/global/ProTag.vue'
import VButton from '~/components/global/VButton.vue'
import ExtraMenu from '../../../components/pages/forms/show/ExtraMenu.vue'
import SeoMeta from '../../../mixins/seo-meta.js'
import FormCleanings from '../../../components/pages/forms/show/FormCleanings.vue'
export default {
name: 'ShowForm',
components: {
VButton,
ProTag,
ExtraMenu,
FormCleanings
},
mixins: [SeoMeta],
beforeUnmount() {
this.workingForm = null
},
middleware: 'auth',
setup () {
const authStore = useAuthStore()
const formsStore = useFormsStore()
const workingFormStore = useWorkingFormStore()
const workspacesStore = useWorkspacesStore()
const route = useRoute()
if (!formsStore.getByKey(route.params.slug)) {
formsStore.loadAll(useWorkspacesStore().currentId)
}
workingFormStore.set(null) // Reset old working form
return {
formsStore,
workingFormStore,
workspacesStore,
user: computed(() => authStore.user),
formsLoading: computed(() => formsStore.loading),
workspacesLoading: computed(() => workspacesStore.loading)
}
},
data () {
return {
metaTitle: 'Home',
tabsList: [
{
name: 'Submissions',
route: 'forms-slug-show-submissions'
},
{
name: 'Analytics',
route: 'forms-slug-show-stats'
},
{
name: 'Share',
route: 'forms-slug-show-share'
}
]
}
},
computed: {
workingForm: {
get () {
return this.workingFormStore.content
},
/* We add a setter */
set (value) {
this.workingFormStore.set(value)
}
},
workspace () {
if (!this.form) return null
return this.workspacesStore.getByKey(this.form.workspace_id)
},
form () {
return this.formsStore.getByKey(this.$route.params.slug)
},
formEndpoint: () => '/api/open/forms/{id}',
loading () {
return this.formsLoading || this.workspacesLoading
},
displayClosesDate () {
if (this.form.closes_at) {
const 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)
}
},
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-slug-edit', params: { slug: this.form.slug } })
}
}
}
</script>

View File

@ -1,270 +1,9 @@
<template> <script setup>
<div class="bg-white"> const route = useRoute()
<template v-if="form"> useRouter().push({
<div class="flex bg-gray-50"> name: 'forms-slug-show-submissions',
<div class="w-full md:w-4/5 lg:w-3/5 md:mx-auto md:max-w-4xl px-4"> params: {
<div class="pt-4 pb-0"> slug: route.params.slug
<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>
<div class="flex flex-wrap">
<h2 class="flex-grow text-gray-900 truncate">
{{ form.title }}
</h2>
<div class="flex">
<extra-menu :form="form" />
<v-button 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"
/>
</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>
<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>
<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"
>
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"
>
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"
>
{{ 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>
<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" />
<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">
<router-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"
>
{{ 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">
<router-view v-slot="{ Component }">
<transition name="page" mode="out-in">
<component :is="Component" :form="form" />
</transition>
</router-view>
</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 { computed } from 'vue'
import Form from 'vform'
import { useAuthStore } from '../../../../stores/auth.js'
import { useFormsStore } from '../../../../stores/forms.js'
import { useWorkingFormStore } from '../../../../stores/working_form.js'
import { useWorkspacesStore } from '../../../../stores/workspaces.js'
import ProTag from '~/components/global/ProTag.vue'
import VButton from '~/components/global/VButton.vue'
import ExtraMenu from '../../../../components/pages/forms/show/ExtraMenu.vue'
import SeoMeta from '../../../../mixins/seo-meta.js'
import FormCleanings from '../../../../components/pages/forms/show/FormCleanings.vue'
const loadForms = function () {
const formsStore = useFormsStore()
const workspacesStore = useWorkspacesStore()
formsStore.startLoading()
workspacesStore.loadIfEmpty().then(() => {
formsStore.loadIfEmpty(workspacesStore.currentId)
})
}
export default {
name: 'ShowForm',
components: {
VButton,
ProTag,
ExtraMenu,
FormCleanings
},
mixins: [SeoMeta],
beforeRouteEnter (to, from, next) {
loadForms()
next()
},
beforeRouteLeave (to, from, next) {
this.workingForm = null
next()
},
middleware: 'auth',
setup () {
const authStore = useAuthStore()
const formsStore = useFormsStore()
const workingFormStore = useWorkingFormStore()
const workspacesStore = useWorkspacesStore()
return {
formsStore,
workingFormStore,
workspacesStore,
user: computed(() => authStore.user),
formsLoading: computed(() => formsStore.loading),
workspacesLoading: computed(() => workspacesStore.loading)
} }
}, })
data () {
return {
metaTitle: 'Home',
tabsList: [
{
name: 'Submissions',
route: 'forms.show'
},
{
name: 'Analytics',
route: 'forms.show.analytics'
},
{
name: 'Share',
route: 'forms.show.share'
}
]
}
},
computed: {
workingForm: {
get () {
return this.workingFormStore.content
},
/* We add a setter */
set (value) {
this.workingFormStore.set(value)
}
},
workspace () {
if (!this.form) return null
return this.workspacesStore.getById(this.form.workspace_id)
},
form () {
return this.formsStore.getBySlug(this.$route.params.slug)
},
formEndpoint: () => '/api/open/forms/{id}',
loading () {
return this.formsLoading || this.workspacesLoading
},
displayClosesDate () {
if (this.form.closes_at) {
const 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)
}
},
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-slug-edit', params: { slug: this.form.slug } })
}
}
}
</script> </script>

View File

@ -38,10 +38,14 @@ export default {
AdvancedFormUrlSettings, AdvancedFormUrlSettings,
EmbedFormAsPopupModal EmbedFormAsPopupModal
}, },
props: { props: {
form: { type: Object, required: true } form: {type: Object, required: true},
},
mounted() {
console.log('form',this.form)
}, },
mixins: [SeoMeta],
data: () => ({ data: () => ({
shareFormConfig: { shareFormConfig: {
@ -50,8 +54,6 @@ export default {
} }
}), }),
mounted() {},
computed: { computed: {
metaTitle() { metaTitle() {
return (this.form) ? 'Form Share - '+this.form.title : 'Form Share' return (this.form) ? 'Form Share - '+this.form.title : 'Form Share'

View File

@ -12,14 +12,11 @@ import FormStats from '../../../../components/open/forms/components/FormStats.vu
import SeoMeta from '../../../../mixins/seo-meta.js' import SeoMeta from '../../../../mixins/seo-meta.js'
export default { export default {
name: 'Stats',
components: {FormStats}, components: {FormStats},
props: { props: {
form: {type: Object, required: true}, form: {type: Object, required: true},
}, },
mixins: [SeoMeta],
data: () => ({}),
computed: { computed: {
metaTitle() { metaTitle() {

View File

@ -184,7 +184,7 @@ export default {
return this.$route.params.slug return this.$route.params.slug
}, },
form () { form () {
return this.formsStore.getBySlug(this.formSlug) return this.formsStore.getByKey(this.formSlug)
}, },
isIframe () { isIframe () {
return window.location !== window.parent.location || window.frameElement return window.location !== window.parent.location || window.frameElement

View File

@ -133,13 +133,14 @@ definePageMeta({
const authStore = useAuthStore() const authStore = useAuthStore()
const formsStore = useFormsStore() const formsStore = useFormsStore()
formsStore.startLoading()
const workspacesStore = useWorkspacesStore() const workspacesStore = useWorkspacesStore()
formsStore.startLoading()
onMounted(() => { onMounted(() => {
if (!formsStore.allLoaded) { if (!formsStore.allLoaded) {
console.log('starting to load') formsStore.loadAll(workspacesStore.currentId)
formsStore.load(workspacesStore.currentId) } else {
formsStore.stopLoading()
} }
}) })
@ -164,7 +165,7 @@ const onTagClick = (tag) => {
} }
} }
const viewForm = (form) => { const viewForm = (form) => {
useRouter.push({name: 'forms.show', params: {slug: form.slug}}) useRouter().push({name: 'forms-slug-show', params: {slug: form.slug}})
} }
// Computed // Computed

View File

@ -9,7 +9,7 @@ export const useFormsStore = defineStore('forms', () => {
const allLoaded = ref(false) const allLoaded = ref(false)
const currentPage = ref(1) const currentPage = ref(1)
const load = (workspaceId) => { const loadAll = (workspaceId) => {
contentStore.startLoading() contentStore.startLoading()
return opnFetch(formsEndpoint.replace('{workspaceId}', workspaceId),{query: {page: currentPage.value}}) return opnFetch(formsEndpoint.replace('{workspaceId}', workspaceId),{query: {page: currentPage.value}})
.then((response) => { .then((response) => {
@ -21,7 +21,7 @@ export const useFormsStore = defineStore('forms', () => {
} }
if (currentPage.value < response.meta.last_page) { if (currentPage.value < response.meta.last_page) {
currentPage.value++ currentPage.value++
load(workspaceId) loadAll(workspaceId)
} else { } else {
allLoaded.value = true allLoaded.value = true
contentStore.stopLoading() contentStore.stopLoading()
@ -30,6 +30,14 @@ export const useFormsStore = defineStore('forms', () => {
}) })
} }
const load = (workspaceId, slug) => {
contentStore.startLoading()
return opnFetch(formsEndpoint.replace('{workspaceId}', workspaceId) + '/' + slug)
.then((response) => {
console.log(response.data.value)
})
}
const allTags = computed(() => { const allTags = computed(() => {
let tags = [] let tags = []
contentStore.getAll.value.forEach((form) => { contentStore.getAll.value.forEach((form) => {
@ -44,6 +52,7 @@ export const useFormsStore = defineStore('forms', () => {
...contentStore, ...contentStore,
allLoaded, allLoaded,
allTags, allTags,
loadAll,
load load
} }
}) })

View File

@ -80,7 +80,6 @@
</template> </template>
<script> <script>
import axios from 'axios'
import Fuse from 'fuse.js' import Fuse from 'fuse.js'
import Form from 'vform' import Form from 'vform'
import { useWorkingFormStore } from '../../../../stores/working_form' import { useWorkingFormStore } from '../../../../stores/working_form'
@ -88,6 +87,7 @@ import ScrollShadow from '../../../common/ScrollShadow.vue'
import OpenTable from '../../tables/OpenTable.vue' import OpenTable from '../../tables/OpenTable.vue'
import clonedeep from 'clone-deep' import clonedeep from 'clone-deep'
import VSwitch from '../../../forms/components/VSwitch.vue' import VSwitch from '../../../forms/components/VSwitch.vue'
import { opnFetch } from '~/composables/useOpnApi.js'
export default { export default {
name: 'FormSubmissions', name: 'FormSubmissions',
@ -210,9 +210,7 @@ export default {
return return
} }
this.isLoading = true this.isLoading = true
axios.get('/api/open/forms/' + this.form.id + '/submissions?page=' + this.currentPage).then((response) => { opnFetch('/open/forms/' + this.form.id + '/submissions?page=' + this.currentPage).then((resData) => {
const resData = response.data
this.tableData = this.tableData.concat(resData.data.map((record) => record.data)) this.tableData = this.tableData.concat(resData.data.map((record) => record.data))
this.dataChanged() this.dataChanged()