opnform-host-nginx/client/components/open/forms/OpenCompleteForm.vue

390 lines
11 KiB
Vue

<template>
<div
v-if="form"
class="open-complete-form"
:dir="form?.layout_rtl ? 'rtl' : 'ltr'"
:style="{ '--font-family': form.font_family, 'direction': form?.layout_rtl ? 'rtl' : 'ltr' }"
>
<link
v-if="adminPreview && form.font_family"
rel="stylesheet"
:href="getFontUrl"
>
<template v-if="!isHideTitle">
<EditableTag
v-if="adminPreview"
v-model="form.title"
element="h1"
class="mb-2"
/>
<h1
v-else
class="mb-2 px-2"
v-text="form.title"
/>
</template>
<div v-if="isPublicFormPage && form.is_password_protected">
<p class="form-description mb-4 text-gray-700 dark:text-gray-300 px-2">
{{ $t('forms.password_protected') }}
</p>
<div class="form-group flex flex-wrap w-full">
<div class="relative mb-3 w-full px-2">
<text-input
:theme="theme"
:form="passwordForm"
name="password"
native-type="password"
label="Password"
/>
</div>
</div>
<div class="flex flex-wrap justify-center w-full text-center">
<open-form-button
:theme="theme"
:color="form.color"
class="my-4"
@click="passwordEntered"
>
{{ $t('forms.submit') }}
</open-form-button>
</div>
</div>
<v-transition name="fade">
<div
v-if="!form.is_password_protected && form.password && !hidePasswordDisabledMsg"
class="m-2 my-4 flex flex-grow items-end p-4 rounded-md dark:text-yellow-500 bg-yellow-50 dark:bg-yellow-600/20 dark:border-yellow-500"
>
<p class="mb-0 text-yellow-600 dark:text-yellow-600 text-sm">
We disabled the password protection for this form because you are an owner of it.
</p>
<UButton
color="yellow"
size="xs"
@click="hidePasswordDisabledMsg = true"
>
Close
</ubutton>
</div>
</v-transition>
<div
v-if="isPublicFormPage && (form.is_closed || form.visibility=='closed')"
class="border shadow-sm p-2 my-4 flex items-center rounded-md bg-yellow-100 dark:bg-yellow-600/20 border-yellow-500 dark:border-yellow-500/20"
>
<div class="flex-grow">
<div
class="mb-0 py-2 px-4 text-yellow-600"
v-html="form.closed_text"
/>
</div>
</div>
<div
v-if="isPublicFormPage && form.max_number_of_submissions_reached"
class="border shadow-sm p-2 my-4 flex items-center rounded-md bg-yellow-100 dark:bg-yellow-600/20 border-yellow-500 dark:border-yellow-500/20"
>
<div class="flex-grow">
<div
class="mb-0 py-2 px-4 text-yellow-600 dark:text-yellow-600"
v-html="form.max_submissions_reached_text"
/>
</div>
</div>
<form-cleanings
v-if="!adminPreview"
:hideable="true"
class="mb-4 mx-2"
:form="form"
:specify-form-owner="true"
/>
<v-transition name="fade">
<div
v-if="!submitted"
key="form"
>
<open-form
v-if="form && !form.is_closed"
:form="form"
:loading="loading"
:fields="form.properties"
:theme="theme"
:dark-mode="darkMode"
:admin-preview="adminPreview"
@submit="submitForm"
>
<template #submit-btn="{submitForm}">
<open-form-button
:loading="loading"
:theme="theme"
:color="form.color"
class="mt-2 px-8 mx-1"
:class="submitButtonClass"
@click.prevent="submitForm"
>
{{ form.submit_button_text }}
</open-form-button>
</template>
</open-form>
<p
v-if="!form.no_branding"
class="text-center w-full mt-2"
>
<a
href="https://opnform.com?utm_source=form&utm_content=powered_by"
class="text-gray-400 hover:text-gray-500 dark:text-gray-600 dark:hover:text-gray-500 cursor-pointer hover:underline text-xs"
target="_blank"
>
{{ $t('forms.powered_by') }} <span class="font-semibold">{{ $t('app.name') }}</span>
</a>
</p>
</div>
<div
v-else
key="submitted"
class="px-2"
>
<TextBlock
v-if="form.submitted_text"
class="form-description text-gray-700 dark:text-gray-300 whitespace-pre-wrap"
:content="form.submitted_text"
:mentions-allowed="true"
:form="form"
:form-data="submittedData"
/>
<open-form-button
v-if="form.re_fillable"
:theme="theme"
:color="form.color"
class="my-4"
@click="restart"
>
{{ form.re_fill_button_text }}
</open-form-button>
<p
v-if="form.editable_submissions && submissionId"
class="mt-5"
>
<a
target="_parent"
:href="form.share_url+'?submission_id='+submissionId"
class="text-nt-blue hover:underline"
>
{{ form.editable_submissions_button_text }}
</a>
</p>
<p
v-if="!form.no_branding"
class="mt-5"
>
<a
target="_parent"
href="https://opnform.com/?utm_source=form&utm_content=create_form_free"
class="text-nt-blue hover:underline"
>
{{ $t('forms.create_form_free') }}
</a>
</p>
</div>
</v-transition>
<FirstSubmissionModal
:show="showFirstSubmissionModal"
:form="form"
@close="showFirstSubmissionModal=false"
/>
</div>
</template>
<script>
import OpenForm from './OpenForm.vue'
import OpenFormButton from './OpenFormButton.vue'
import FormCleanings from '../../pages/forms/show/FormCleanings.vue'
import VTransition from '~/components/global/transitions/VTransition.vue'
import {pendingSubmission} from "~/composables/forms/pendingSubmission.js"
import clonedeep from "clone-deep"
import ThemeBuilder from "~/lib/forms/themes/ThemeBuilder.js"
import FirstSubmissionModal from '~/components/open/forms/components/FirstSubmissionModal.vue'
export default {
components: { VTransition, OpenFormButton, OpenForm, FormCleanings, FirstSubmissionModal },
props: {
form: { type: Object, required: true },
creating: { type: Boolean, default: false }, // If true, fake form submit
adminPreview: { type: Boolean, default: false }, // If used in FormEditorPreview
submitButtonClass: { type: String, default: '' },
darkMode: {
type: Boolean,
default: false
}
},
setup(props) {
const { setLocale } = useI18n()
const authStore = useAuthStore()
return {
setLocale,
authStore,
authenticated: computed(() => authStore.check),
isIframe: useIsIframe(),
pendingSubmission: pendingSubmission(props.form),
confetti: useConfetti()
}
},
data () {
return {
loading: false,
submitted: false,
passwordForm: useForm({
password: null
}),
hidePasswordDisabledMsg: false,
submissionId: false,
submittedData: null,
showFirstSubmissionModal: false
}
},
computed: {
isEmbedPopup () {
return import.meta.client && window.location.href.includes('popup=true')
},
theme () {
return new ThemeBuilder(this.form.theme, {
size: this.form.size,
borderRadius: this.form.border_radius
}).getAllComponents()
},
isPublicFormPage () {
return this.$route.name === 'forms-slug'
},
isHideTitle () {
return this.form.hide_title || (import.meta.client && window.location.href.includes('hide_title=true'))
},
getFontUrl() {
if(!this.form || !this.form.font_family) return null
const family = this.form?.font_family.replace(/ /g, '+')
return `https://fonts.googleapis.com/css?family=${family}:wght@400,500,700,800,900&display=swap`
},
isFormOwner() {
return this.authenticated && this.form && this.form.creator_id === this.authStore.user.id
}
},
watch: {
'form.language': {
handler(newLanguage) {
this.setLocale(newLanguage)
},
immediate: true
}
},
beforeUnmount() {
this.setLocale('en')
},
methods: {
submitForm (form, onFailure) {
if (this.creating) {
this.submitted = true
this.$emit('submitted', true)
return
}
if (form.busy) return
this.loading = true
form.post('/forms/' + this.form.slug + '/answer').then((data) => {
this.submittedData = form.data()
useAmplitude().logEvent('form_submission', {
workspace_id: this.form.workspace_id,
form_id: this.form.id
})
const payload = clonedeep({
type: 'form-submitted',
form: {
slug: this.form.slug,
id: this.form.id,
redirect_target_url: (this.form.is_pro && data.redirect && data.redirect_url) ? data.redirect_url : null
},
submission_data: form.data(),
completion_time: form['completion_time']
})
if (this.isIframe) {
window.parent.postMessage(payload, '*')
}
window.postMessage(payload, '*')
this.pendingSubmission.remove()
this.pendingSubmission.removeTimer()
if (data.redirect && data.redirect_url) {
window.location.href = data.redirect_url
}
if (data.submission_id) {
this.submissionId = data.submission_id
}
if (this.isFormOwner && !this.isIframe && data?.is_first_submission) {
this.showFirstSubmissionModal = true
}
this.loading = false
this.submitted = true
this.$emit('submitted', true)
// If enabled display confetti
if (this.form.confetti_on_submission) {
this.confetti.play()
}
}).catch((error) => {
console.error(error)
if (error.response && error.data && error.data.message) {
useAlert().error(error.data.message)
}
this.loading = false
onFailure()
})
},
restart () {
this.submitted = false
this.$emit('restarted', true)
},
passwordEntered () {
if (this.passwordForm.password !== '' && this.passwordForm.password !== null) {
this.$emit('password-entered', this.passwordForm.password)
} else {
this.addPasswordError(this.$t('forms.password_required'))
}
},
addPasswordError (msg) {
this.passwordForm.errors.set('password', msg)
}
}
}
</script>
<style lang="scss">
.open-complete-form {
* {
font-family: var(--font-family) !important;
}
.form-description, .nf-text {
ol {
@apply list-decimal list-inside;
margin-left: 10px;
}
ul {
@apply list-disc list-inside;
margin-left: 10px;
}
}
}
</style>