Merge branch 'migrate-to-nuxt' of https://github.com/JhumanJ/OpnForm into migrate-to-nuxt
This commit is contained in:
commit
8b92f24094
|
@ -44,6 +44,17 @@ export default {
|
||||||
components: {},
|
components: {},
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
|
useOpnSeoMeta({
|
||||||
|
title: 'OpnForm',
|
||||||
|
description: 'Create beautiful forms for free. Unlimited fields, unlimited submissions. It\'s free and it takes less than 1 minute to create your first form.',
|
||||||
|
ogImage: '/img/social-preview.jpg',
|
||||||
|
})
|
||||||
|
useHead({
|
||||||
|
titleTemplate: (titleChunk) => {
|
||||||
|
return titleChunk ? `${titleChunk} - OpnForm` : 'OpnForm';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -57,8 +68,6 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
metaTitle: 'OpnForm',
|
|
||||||
metaDescription: 'Create beautiful forms for free. Unlimited fields, unlimited submissions. It\'s free and it takes less than 1 minute to create your first form.',
|
|
||||||
announcement: false,
|
announcement: false,
|
||||||
alert: {
|
alert: {
|
||||||
type: null,
|
type: null,
|
||||||
|
|
|
@ -203,7 +203,7 @@ export default {
|
||||||
return !this.appStore.navbarHidden
|
return !this.appStore.navbarHidden
|
||||||
},
|
},
|
||||||
userOnboarded() {
|
userOnboarded() {
|
||||||
return this.user && this.user.workspaces_count > 0
|
return this.user && this.user.has_forms === true
|
||||||
},
|
},
|
||||||
hasCrisp() {
|
hasCrisp() {
|
||||||
return this.config.crispWebsiteId
|
return this.config.crispWebsiteId
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full sm:w-40 sm:ml-2 mt-2 sm:mt-0 shrink-0">
|
<div class="w-full sm:w-40 sm:ml-2 mt-2 sm:mt-0 shrink-0">
|
||||||
<v-button color="light-gray" class="w-full" @click="copyToClipboard(content)">
|
<v-button color="light-gray" class="w-full" @click="copyToClipboard">
|
||||||
<slot name="icon">
|
<slot name="icon">
|
||||||
<svg class="h-4 w-4 -mt-1 text-blue-600 inline mr-1" viewBox="0 0 20 20" fill="none"
|
<svg class="h-4 w-4 -mt-1 text-blue-600 inline mr-1" viewBox="0 0 20 20" fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg">
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
@ -21,47 +21,28 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
export default {
|
import { defineProps } from 'vue'
|
||||||
name: 'CopyContent',
|
const { copy } = useClipboard()
|
||||||
props: {
|
|
||||||
content: {
|
const props = defineProps({
|
||||||
type: String,
|
content: {
|
||||||
required: true
|
type: String,
|
||||||
},
|
required: true
|
||||||
isDraft: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
isDraft: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
data() {
|
const copyToClipboard = () => {
|
||||||
return {}
|
if (process.server) return
|
||||||
},
|
copy(props.content)
|
||||||
|
if(props.isDraft){
|
||||||
computed: {},
|
useAlert().warning('Copied! But other people won\'t be able to see the form since it\'s currently in draft mode')
|
||||||
|
} else {
|
||||||
watch: {},
|
useAlert().success('Copied!')
|
||||||
|
|
||||||
mounted() {
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
copyToClipboard(str) {
|
|
||||||
if (process.server) return
|
|
||||||
const el = document.createElement('textarea')
|
|
||||||
el.value = str
|
|
||||||
document.body.appendChild(el)
|
|
||||||
el.select()
|
|
||||||
document.execCommand('copy')
|
|
||||||
document.body.removeChild(el)
|
|
||||||
if(this.isDraft){
|
|
||||||
useAlert().warning('Copied! But other people won\'t be able to see the form since it\'s currently in draft mode')
|
|
||||||
} else {
|
|
||||||
useAlert().success('Copied!')
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -17,72 +17,50 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
export default {
|
import { defineProps, computed } from 'vue'
|
||||||
name: 'FormUrlPrefill',
|
const { copy } = useClipboard()
|
||||||
props: {
|
|
||||||
form: {
|
const props = defineProps({
|
||||||
type: Object,
|
form: {
|
||||||
required: true
|
type: Object,
|
||||||
},
|
required: true
|
||||||
formData: {
|
|
||||||
type: Object,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
extraQueryParam: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
formData: {
|
||||||
data () {
|
type: Object,
|
||||||
return {}
|
required: true
|
||||||
},
|
},
|
||||||
|
extraQueryParam: {
|
||||||
computed: {
|
type: String,
|
||||||
preFillUrl () {
|
default: ''
|
||||||
const url = this.form.share_url
|
|
||||||
const uriComponents = new URLSearchParams()
|
|
||||||
this.form.properties.filter((property) => {
|
|
||||||
return this.formData.hasOwnProperty(property.id) && this.formData[property.id] !== null
|
|
||||||
}).forEach((property) => {
|
|
||||||
if (Array.isArray(this.formData[property.id])) {
|
|
||||||
this.formData[property.id].forEach((value) => {
|
|
||||||
uriComponents.append(property.id + '[]', value)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
uriComponents.append(property.id, this.formData[property.id])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if(uriComponents.toString() !== ""){
|
|
||||||
return (this.extraQueryParam) ? url + '?' + uriComponents + '&' + this.extraQueryParam : url + '?' + uriComponents
|
|
||||||
}else{
|
|
||||||
return (this.extraQueryParam) ? url + '?' + this.extraQueryParam : url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: {},
|
|
||||||
|
|
||||||
mounted () {
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
getPropertyUriComponent (property) {
|
|
||||||
const prefillValue = encodeURIComponent(this.formData[property.id])
|
|
||||||
return encodeURIComponent(property.id) + '=' + prefillValue
|
|
||||||
},
|
|
||||||
copyToClipboard () {
|
|
||||||
if (process.server) return
|
|
||||||
const str = this.preFillUrl
|
|
||||||
const el = document.createElement('textarea')
|
|
||||||
el.value = str
|
|
||||||
document.body.appendChild(el)
|
|
||||||
el.select()
|
|
||||||
document.execCommand('copy')
|
|
||||||
document.body.removeChild(el)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const preFillUrl = computed(() => {
|
||||||
|
const url = props.form.share_url
|
||||||
|
const uriComponents = new URLSearchParams()
|
||||||
|
props.form.properties.filter((property) => {
|
||||||
|
return props.formData.hasOwnProperty(property.id) && props.formData[property.id] !== null
|
||||||
|
}).forEach((property) => {
|
||||||
|
if (Array.isArray(props.formData[property.id])) {
|
||||||
|
props.formData[property.id].forEach((value) => {
|
||||||
|
uriComponents.append(property.id + '[]', value)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
uriComponents.append(property.id, props.formData[property.id])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if(uriComponents.toString() !== ""){
|
||||||
|
return (props.extraQueryParam) ? url + '?' + uriComponents + '&' + props.extraQueryParam : url + '?' + uriComponents
|
||||||
|
}else{
|
||||||
|
return (props.extraQueryParam) ? url + '?' + props.extraQueryParam : url
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const copyToClipboard = () => {
|
||||||
|
if (process.server) return
|
||||||
|
copy(preFillUrl.value)
|
||||||
|
useAlert().success('Copied!')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<modal :show="show" @close="$emit('close')">
|
<modal :show="show" @close="emit('close')">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<svg class="w-10 h-10 text-blue" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg class="w-10 h-10 text-blue" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path
|
<path
|
||||||
|
@ -57,11 +57,11 @@
|
||||||
</template>
|
</template>
|
||||||
</v-button>
|
</v-button>
|
||||||
<v-button v-if="template" color="red" class="mr-2"
|
<v-button v-if="template" color="red" class="mr-2"
|
||||||
@click.prevent="alertConfirm('Do you really want to delete this template?', deleteFormTemplate)"
|
@click.prevent="useAlert().confirm('Do you really want to delete this template?', deleteFormTemplate)"
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</v-button>
|
</v-button>
|
||||||
<v-button color="white" @click.prevent="$emit('close')">
|
<v-button color="white" @click.prevent="emit('close')">
|
||||||
Close
|
Close
|
||||||
</v-button>
|
</v-button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -71,118 +71,104 @@
|
||||||
</modal>
|
</modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import { computed } from 'vue'
|
import { ref, defineProps, defineEmits, computed } from 'vue'
|
||||||
import QuestionsEditor from './QuestionsEditor.vue'
|
import QuestionsEditor from './QuestionsEditor.vue'
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
name: 'FormTemplateModal',
|
show: { type: Boolean, required: true },
|
||||||
components: { QuestionsEditor },
|
form: { type: Object, required: true },
|
||||||
props: {
|
template: { type: Object, required: false, default: () => {} }
|
||||||
show: { type: Boolean, required: true },
|
})
|
||||||
form: { type: Object, required: true },
|
|
||||||
template: { type: Object, required: false, default: () => {} }
|
|
||||||
},
|
|
||||||
|
|
||||||
setup () {
|
const authStore = useAuthStore()
|
||||||
const authStore = useAuthStore()
|
const templatesStore = useTemplatesStore()
|
||||||
const templatesStore = useTemplatesStore()
|
const router = useRouter()
|
||||||
|
let user = computed(() => authStore.user)
|
||||||
|
let templates = computed(() => [...templatesStore.content.values()])
|
||||||
|
let industries = computed(() => [...templatesStore.industries.values()])
|
||||||
|
let types = computed(() => [...templatesStore.types.values()])
|
||||||
|
|
||||||
|
let templateForm = ref(null)
|
||||||
|
const emit = defineEmits(['close'])
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
templateForm.value = useForm(props.template ?? {
|
||||||
|
publicly_listed: false,
|
||||||
|
name: '',
|
||||||
|
slug: '',
|
||||||
|
short_description: '',
|
||||||
|
description: '',
|
||||||
|
image_url: '',
|
||||||
|
types: null,
|
||||||
|
industries: null,
|
||||||
|
related_templates: null,
|
||||||
|
questions: []
|
||||||
|
})
|
||||||
|
loadAllTemplates(templatesStore)
|
||||||
|
})
|
||||||
|
|
||||||
|
let typesOptions = computed(() => {
|
||||||
|
return Object.values(types.value).map((type) => {
|
||||||
return {
|
return {
|
||||||
templatesStore,
|
name: type.name,
|
||||||
user : computed(() => authStore.user),
|
value: type.slug
|
||||||
templates : computed(() => templatesStore.content),
|
|
||||||
industries : computed(() => templatesStore.industries),
|
|
||||||
types : computed(() => templatesStore.types),
|
|
||||||
useAlert: useAlert()
|
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
|
})
|
||||||
data: () => ({
|
let industriesOptions = computed(() => {
|
||||||
templateForm: null
|
return Object.values(industries.value).map((industry) => {
|
||||||
}),
|
return {
|
||||||
|
name: industry.name,
|
||||||
mounted () {
|
value: industry.slug
|
||||||
this.templateForm = useForm(this.template ?? {
|
|
||||||
publicly_listed: false,
|
|
||||||
name: '',
|
|
||||||
slug: '',
|
|
||||||
short_description: '',
|
|
||||||
description: '',
|
|
||||||
image_url: '',
|
|
||||||
types: null,
|
|
||||||
industries: null,
|
|
||||||
related_templates: null,
|
|
||||||
questions: []
|
|
||||||
})
|
|
||||||
loadAllTemplates(this.templatesStore)
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
typesOptions () {
|
|
||||||
return Object.values(this.types).map((type) => {
|
|
||||||
return {
|
|
||||||
name: type.name,
|
|
||||||
value: type.slug
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
industriesOptions () {
|
|
||||||
return Object.values(this.industries).map((industry) => {
|
|
||||||
return {
|
|
||||||
name: industry.name,
|
|
||||||
value: industry.slug
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
templatesOptions () {
|
|
||||||
return this.templates.map((template) => {
|
|
||||||
return {
|
|
||||||
name: template.name,
|
|
||||||
value: template.slug
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
|
})
|
||||||
methods: {
|
let templatesOptions = computed(() => {
|
||||||
onSubmit () {
|
return Object.values(templates.value).map((template) => {
|
||||||
if (this.template) {
|
return {
|
||||||
this.updateFormTemplate()
|
name: template.name,
|
||||||
} else {
|
value: template.slug
|
||||||
this.createFormTemplate()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async createFormTemplate () {
|
|
||||||
this.templateForm.form = this.form
|
|
||||||
await this.templateForm.post('/api/templates').then((response) => {
|
|
||||||
if (response.data.message) {
|
|
||||||
this.useAlert.success(response.data.message)
|
|
||||||
}
|
|
||||||
this.templatesStore.save(response.data.data)
|
|
||||||
this.$emit('close')
|
|
||||||
})
|
|
||||||
},
|
|
||||||
async updateFormTemplate () {
|
|
||||||
this.templateForm.form = this.form
|
|
||||||
await this.templateForm.put('/api/templates/' + this.template.id).then((response) => {
|
|
||||||
if (response.data.message) {
|
|
||||||
this.useAlert.success(response.data.message)
|
|
||||||
}
|
|
||||||
this.templatesStore.save(response.data.data)
|
|
||||||
this.$emit('close')
|
|
||||||
})
|
|
||||||
},
|
|
||||||
async deleteFormTemplate () {
|
|
||||||
if (!this.template) return
|
|
||||||
opnFetch('/templates/' + this.template.id, {method:'DELETE'}).then((data) => {
|
|
||||||
if (data.message) {
|
|
||||||
this.useAlert.success(data.message)
|
|
||||||
}
|
|
||||||
this.$router.push({ name: 'templates' })
|
|
||||||
this.templatesStore.remove(this.template)
|
|
||||||
this.$emit('close')
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const onSubmit = () => {
|
||||||
|
if (props.template) {
|
||||||
|
updateFormTemplate()
|
||||||
|
} else {
|
||||||
|
createFormTemplate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const createFormTemplate = async () => {
|
||||||
|
templateForm.value.form = props.form
|
||||||
|
await templateForm.value.post('/templates').then((data) => {
|
||||||
|
if (data.message) {
|
||||||
|
useAlert().success(data.message)
|
||||||
|
}
|
||||||
|
templatesStore.save(data.data)
|
||||||
|
emit('close')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const updateFormTemplate = async () => {
|
||||||
|
templateForm.value.form = props.form
|
||||||
|
await templateForm.value.put('/templates/' + props.template.id).then((data) => {
|
||||||
|
if (data.message) {
|
||||||
|
useAlert().success(data.message)
|
||||||
|
}
|
||||||
|
templatesStore.save(data.data)
|
||||||
|
emit('close')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const deleteFormTemplate = async () => {
|
||||||
|
if (!props.template) return
|
||||||
|
opnFetch('/templates/' + props.template.id, {method:'DELETE'}).then((data) => {
|
||||||
|
if (data.message) {
|
||||||
|
useAlert().success(data.message)
|
||||||
|
}
|
||||||
|
router.push({ name: 'templates' })
|
||||||
|
templatesStore.remove(props.template)
|
||||||
|
emit('close')
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -59,7 +59,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<collapse class="py-5 w-full border rounded-md px-4" :model-value="false">
|
<collapse class="py-5 w-full border rounded-md px-4" :model-value="true">
|
||||||
<template #title>
|
<template #title>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<h3 class="font-semibold block text-lg">
|
<h3 class="font-semibold block text-lg">
|
||||||
|
@ -101,99 +101,87 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import Collapse from '~/components/global/Collapse.vue'
|
import { ref, defineProps, computed } from 'vue'
|
||||||
|
|
||||||
export default {
|
const { copy } = useClipboard()
|
||||||
name: 'EmbedFormAsPopupModal',
|
const crisp = useCrisp()
|
||||||
components: { Collapse },
|
const props = defineProps({
|
||||||
props: {
|
form: { type: Object, required: true }
|
||||||
form: { type: Object, required: true }
|
})
|
||||||
},
|
|
||||||
|
|
||||||
data: () => ({
|
const embedScriptUrl = '/widgets/embed-min.js'
|
||||||
showEmbedFormAsPopupModal: false,
|
let showEmbedFormAsPopupModal = ref(false)
|
||||||
embedScriptUrl: 'widgets/embed-min.js',
|
let advancedOptions = ref({
|
||||||
advancedOptions: {
|
hide_title: false,
|
||||||
hide_title: false,
|
emoji: '💬',
|
||||||
emoji: '💬',
|
position: 'right',
|
||||||
position: 'right',
|
bgcolor: '#3B82F6',
|
||||||
bgcolor: '#3B82F6',
|
width: '500'
|
||||||
width: '500'
|
})
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
computed: {
|
let hideTitleHelp = computed(() => {
|
||||||
hideTitleHelp () {
|
return props.form.hide_title ? 'This option is disabled because the form title is already hidden' : null
|
||||||
return this.form.hide_title ? 'This option is disabled because the form title is already hidden' : null
|
})
|
||||||
},
|
let shareUrl = computed(() => {
|
||||||
shareUrl () {
|
return (advancedOptions.value.hide_title) ? props.form.share_url + '?hide_title=true' : props.form.share_url
|
||||||
return (this.advancedOptions.hide_title) ? this.form.share_url + '?hide_title=true' : this.form.share_url
|
})
|
||||||
},
|
let embedPopupCode = computed(() => {
|
||||||
embedPopupCode () {
|
const nfData = {
|
||||||
const nfData = {
|
formurl: shareUrl.value,
|
||||||
formurl: this.shareUrl,
|
emoji: advancedOptions.value.emoji,
|
||||||
emoji: this.advancedOptions.emoji,
|
position: advancedOptions.value.position,
|
||||||
position: this.advancedOptions.position,
|
bgcolor: advancedOptions.value.bgcolor,
|
||||||
bgcolor: this.advancedOptions.bgcolor,
|
width: advancedOptions.value.width
|
||||||
width: this.advancedOptions.width
|
}
|
||||||
}
|
previewPopup(nfData)
|
||||||
this.previewPopup(nfData)
|
return '<script async data-nf=\'' + JSON.stringify(nfData) + '\' src=\'' + embedScriptUrl + '\'></scrip' + 't>'
|
||||||
return '<script async data-nf=\'' + JSON.stringify(nfData) + '\' src=\'' + this.asset(this.embedScriptUrl) + '\'></scrip' + 't>'
|
})
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted () {
|
onMounted(() => {
|
||||||
this.advancedOptions.bgcolor = this.form.color
|
advancedOptions.value.bgcolor = props.form.color
|
||||||
},
|
})
|
||||||
|
|
||||||
methods: {
|
|
||||||
onClose () {
|
|
||||||
this.removePreview()
|
|
||||||
this.$crisp.push(['do', 'chat:show'])
|
|
||||||
this.showEmbedFormAsPopupModal = false
|
|
||||||
},
|
|
||||||
copyToClipboard () {
|
|
||||||
if (process.server) return
|
|
||||||
const str = this.embedPopupCode
|
|
||||||
const el = document.createElement('textarea')
|
|
||||||
el.value = str
|
|
||||||
document.body.appendChild(el)
|
|
||||||
el.select()
|
|
||||||
document.execCommand('copy')
|
|
||||||
document.body.removeChild(el)
|
|
||||||
},
|
|
||||||
removePreview () {
|
|
||||||
if (process.server) return
|
|
||||||
const oldP = document.head.querySelector('#nf-popup-preview')
|
|
||||||
if (oldP) {
|
|
||||||
oldP.remove()
|
|
||||||
}
|
|
||||||
const oldM = document.body.querySelector('.nf-main')
|
|
||||||
if (oldM) {
|
|
||||||
oldM.remove()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
previewPopup (nfData) {
|
|
||||||
if (process.server) return
|
|
||||||
if (!this.showEmbedFormAsPopupModal) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove old preview, if there
|
const onClose = () => {
|
||||||
this.removePreview()
|
removePreview()
|
||||||
|
crisp.showChat()
|
||||||
// Hide crisp
|
showEmbedFormAsPopupModal.value = false
|
||||||
this.$crisp.push(['do', 'chat:hide'])
|
}
|
||||||
|
const copyToClipboard = () => {
|
||||||
// Add new preview
|
if (process.server) return
|
||||||
const el = document.createElement('script')
|
copy(embedPopupCode.value)
|
||||||
el.id = 'nf-popup-preview'
|
useAlert().success('Copied!')
|
||||||
el.async = true
|
}
|
||||||
el.src = this.asset(this.embedScriptUrl)
|
const removePreview = () => {
|
||||||
el.setAttribute('data-nf', JSON.stringify(nfData))
|
if (process.server) return
|
||||||
document.head.appendChild(el)
|
const oldP = document.head.querySelector('#nf-popup-preview')
|
||||||
}
|
if (oldP) {
|
||||||
|
oldP.remove()
|
||||||
|
}
|
||||||
|
const oldM = document.body.querySelector('.nf-main')
|
||||||
|
if (oldM) {
|
||||||
|
oldM.remove()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const previewPopup = (nfData) => {
|
||||||
|
if (process.server) return
|
||||||
|
if (!showEmbedFormAsPopupModal.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove old preview, if there
|
||||||
|
removePreview()
|
||||||
|
|
||||||
|
// Hide crisp
|
||||||
|
crisp.hideChat()
|
||||||
|
|
||||||
|
// Add new preview
|
||||||
|
const el = document.createElement('script')
|
||||||
|
el.id = 'nf-popup-preview'
|
||||||
|
el.async = true
|
||||||
|
el.src = embedScriptUrl
|
||||||
|
el.setAttribute('data-nf', JSON.stringify(nfData))
|
||||||
|
document.head.appendChild(el)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -138,70 +138,51 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import { computed } from 'vue'
|
import { ref, defineProps, computed } from 'vue'
|
||||||
import Dropdown from '~/components/global/Dropdown.vue'
|
import Dropdown from '~/components/global/Dropdown.vue'
|
||||||
import FormTemplateModal from '../../../open/forms/components/templates/FormTemplateModal.vue'
|
import FormTemplateModal from '../../../open/forms/components/templates/FormTemplateModal.vue'
|
||||||
|
|
||||||
export default {
|
const { copy } = useClipboard()
|
||||||
name: 'ExtraMenu',
|
const router = useRouter()
|
||||||
components: { Dropdown, FormTemplateModal },
|
|
||||||
props: {
|
|
||||||
form: { type: Object, required: true },
|
|
||||||
isMainPage: { type: Boolean, required: false, default: false }
|
|
||||||
},
|
|
||||||
|
|
||||||
setup () {
|
const props = defineProps({
|
||||||
const authStore = useAuthStore()
|
form: { type: Object, required: true },
|
||||||
const formsStore = useFormsStore()
|
isMainPage: { type: Boolean, required: false, default: false }
|
||||||
return {
|
})
|
||||||
formsStore,
|
|
||||||
user: computed(() => authStore.user),
|
|
||||||
useAlert: useAlert()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
data: () => ({
|
const authStore = useAuthStore()
|
||||||
loadingDuplicate: false,
|
const formsStore = useFormsStore()
|
||||||
loadingDelete: false,
|
const formEndpoint = '/open/forms/{id}'
|
||||||
showDeleteFormModal: false,
|
let user = computed(() => authStore.user)
|
||||||
showFormTemplateModal: false
|
|
||||||
}),
|
|
||||||
|
|
||||||
computed: {
|
let loadingDuplicate = ref(false)
|
||||||
formEndpoint: () => '/open/forms/{id}'
|
let loadingDelete = ref(false)
|
||||||
},
|
let showDeleteFormModal = ref(false)
|
||||||
|
let showFormTemplateModal = ref(false)
|
||||||
|
|
||||||
methods: {
|
const copyLink = () => {
|
||||||
copyLink () {
|
copy(props.form.share_url)
|
||||||
const el = document.createElement('textarea')
|
useAlert().success('Copied!')
|
||||||
el.value = this.form.share_url
|
}
|
||||||
document.body.appendChild(el)
|
const duplicateForm = () => {
|
||||||
el.select()
|
if (loadingDuplicate.value) return
|
||||||
document.execCommand('copy')
|
loadingDuplicate.value = true
|
||||||
document.body.removeChild(el)
|
opnFetch(formEndpoint.replace('{id}', props.form.id) + '/duplicate',{method: 'POST'}).then((data) => {
|
||||||
this.useAlert.success('Copied!')
|
formsStore.save(data.new_form)
|
||||||
},
|
router.push({ name: 'forms-slug-show', params: { slug: data.new_form.slug } })
|
||||||
duplicateForm () {
|
useAlert().success('Form was successfully duplicated.')
|
||||||
if (this.loadingDuplicate) return
|
loadingDuplicate.value = false
|
||||||
this.loadingDuplicate = true
|
})
|
||||||
opnFetch(this.formEndpoint.replace('{id}', this.form.id) + '/duplicate',{method: 'POST'}).then((data) => {
|
}
|
||||||
this.formsStore.save(data.new_form)
|
const deleteForm = () => {
|
||||||
this.$router.push({ name: 'forms-show', params: { slug: data.new_form.slug } })
|
if (loadingDelete.value) return
|
||||||
this.useAlert.success('Form was successfully duplicated.')
|
loadingDelete.value = true
|
||||||
this.loadingDuplicate = false
|
opnFetch(formEndpoint.replace('{id}', props.form.id),{method:'DELETE'}).then(() => {
|
||||||
})
|
formsStore.remove(props.form)
|
||||||
},
|
router.push({ name: 'home' })
|
||||||
deleteForm () {
|
useAlert().success('Form was deleted.')
|
||||||
if (this.loadingDelete) return
|
loadingDelete.value = false
|
||||||
this.loadingDelete = true
|
})
|
||||||
opnFetch(this.formEndpoint.replace('{id}', this.form.id),{method:'DELETE'}).then(() => {
|
|
||||||
this.formsStore.remove(this.form)
|
|
||||||
this.$router.push({ name: 'home' })
|
|
||||||
this.useAlert.success('Form was deleted.')
|
|
||||||
this.loadingDelete = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
export const useOpnSeoMeta = (meta) => {
|
||||||
|
return useSeoMeta({
|
||||||
|
...meta.title ? {
|
||||||
|
ogTitle: meta.title,
|
||||||
|
twitterTitle: meta.title,
|
||||||
|
} : {},
|
||||||
|
...meta.description ? {
|
||||||
|
ogDescription: meta.description,
|
||||||
|
twitterDescription: meta.description,
|
||||||
|
} : {},
|
||||||
|
...meta.ogImage ? {
|
||||||
|
twitterImage: meta.ogImage,
|
||||||
|
} : {},
|
||||||
|
...meta,
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
<template>
|
||||||
|
<NuxtLayout>
|
||||||
|
<div class="flex mt-6">
|
||||||
|
<div class="w-full md:w-2/3 md:mx-auto md:max-w-md">
|
||||||
|
<img alt="Nice plant as we have nothing else to show!" src="/img/icons/plant.png" class="w-56 mb-5">
|
||||||
|
|
||||||
|
<h1 class="mb-4 font-semibold text-3xl text-gray-900">
|
||||||
|
Page Not Found
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<div class="links">
|
||||||
|
<NuxtLink :to="{ name: 'index' }" class="hover:underline text-gray-700">
|
||||||
|
Go Home
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</NuxtLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
useOpnSeoMeta({
|
||||||
|
title: '404 - Page not found'
|
||||||
|
})
|
||||||
|
</script>
|
|
@ -1,23 +0,0 @@
|
||||||
export default {
|
|
||||||
metaInfo () {
|
|
||||||
const title = this.metaTitle ?? 'OpnForm'
|
|
||||||
const description = this.metaDescription ?? "Create beautiful forms for free. Unlimited fields, unlimited submissions. It's free and it takes less than 1 minute to create your first form."
|
|
||||||
const image = this.metaImage ?? this.asset('img/social-preview.jpg')
|
|
||||||
const metaTemplate = this.metaTemplate ?? '%s · OpnForm'
|
|
||||||
|
|
||||||
return {
|
|
||||||
title: title,
|
|
||||||
titleTemplate: metaTemplate,
|
|
||||||
meta: [
|
|
||||||
...(this.metaTags ?? []),
|
|
||||||
{ vmid: 'og:title', property: 'og:title', content: title },
|
|
||||||
{ vmid: 'twitter:title', property: 'twitter:title', content: title },
|
|
||||||
{ vmid: 'description', name: 'description', content: description },
|
|
||||||
{ vmid: 'og:description', property: 'og:description', content: description },
|
|
||||||
{ vmid: 'twitter:description', property: 'twitter:description', content: description },
|
|
||||||
{ vmid: 'twitter:image', property: 'twitter:image', content: image },
|
|
||||||
{ vmid: 'og:image', property: 'og:image', content: image }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -493,39 +493,21 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useAuthStore } from '../stores/auth'
|
import { useAuthStore } from '../stores/auth'
|
||||||
import SeoMeta from '../mixins/seo-meta.js'
|
|
||||||
|
|
||||||
export default {
|
const authStore = useAuthStore()
|
||||||
layout: 'default',
|
|
||||||
mixins: [SeoMeta],
|
|
||||||
|
|
||||||
setup () {
|
useOpnSeoMeta({
|
||||||
const authStore = useAuthStore()
|
title: 'Free AI form builder',
|
||||||
defineRouteRules({
|
description: 'Transform your ideas into fully functional forms with OpnForm AI Builder – quick, accurate, and tailored to fit any requirement.'
|
||||||
prerender: true
|
})
|
||||||
})
|
defineRouteRules({
|
||||||
|
prerender: true
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
let authenticated = computed(() => authStore.check)
|
||||||
authenticated : computed(() => authStore.check),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
data: () => ({
|
|
||||||
title: 'OpnForm',
|
|
||||||
metaTitle: 'AI form builder for free',
|
|
||||||
}),
|
|
||||||
|
|
||||||
mounted() {},
|
|
||||||
|
|
||||||
methods: {},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
configLinks: () => this.$config.links
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
@ -24,10 +24,16 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
middleware: 'guest',
|
setup () {
|
||||||
|
definePageMeta({
|
||||||
|
middleware: "guest"
|
||||||
|
})
|
||||||
|
useOpnSeoMeta({
|
||||||
|
title: 'Reset Password'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
metaTitle: 'Reset Password',
|
|
||||||
status: '',
|
status: '',
|
||||||
form: useForm({
|
form: useForm({
|
||||||
email: ''
|
email: ''
|
||||||
|
|
|
@ -34,10 +34,16 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
middleware: 'guest',
|
setup () {
|
||||||
|
definePageMeta({
|
||||||
|
middleware: "guest"
|
||||||
|
})
|
||||||
|
useOpnSeoMeta({
|
||||||
|
title: 'Reset Password'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
metaTitle: 'Reset Password',
|
|
||||||
status: '',
|
status: '',
|
||||||
form: useForm({
|
form: useForm({
|
||||||
token: '',
|
token: '',
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="flex mt-6">
|
|
||||||
<div class="w-full md:w-2/3 md:mx-auto md:max-w-md">
|
|
||||||
<img alt="Nice plant as we have nothing else to show!" src="/img/icons/plant.png" class="w-56 mb-5">
|
|
||||||
|
|
||||||
|
|
||||||
<h1 class="mb-4 font-semibold text-3xl text-gray-900">
|
|
||||||
Page Not Found
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<div class="links">
|
|
||||||
<NuxtLink :to="{ name: 'index' }" class="hover:underline text-gray-700">
|
|
||||||
Go Home
|
|
||||||
</NuxtLink>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'NotFound'
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -22,7 +22,6 @@ import {hash} from "~/lib/utils.js";
|
||||||
export default {
|
export default {
|
||||||
name: 'EditForm',
|
name: 'EditForm',
|
||||||
components: { Breadcrumb, FormEditor },
|
components: { Breadcrumb, FormEditor },
|
||||||
middleware: 'auth',
|
|
||||||
|
|
||||||
beforeRouteLeave (to, from, next) {
|
beforeRouteLeave (to, from, next) {
|
||||||
if (this.isDirty()) {
|
if (this.isDirty()) {
|
||||||
|
@ -52,6 +51,13 @@ export default {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
useOpnSeoMeta({
|
||||||
|
title: 'Edit ' + ((form && form.value) ? form.value.title : 'Your Form')
|
||||||
|
})
|
||||||
|
definePageMeta({
|
||||||
|
middleware: "auth"
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
formsStore,
|
formsStore,
|
||||||
workingFormStore,
|
workingFormStore,
|
||||||
|
@ -70,9 +76,6 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
metaTitle () {
|
|
||||||
return 'Edit ' + (this.form ? this.form.title : 'Your Form')
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async beforeMount() {
|
async beforeMount() {
|
||||||
|
|
|
@ -113,32 +113,36 @@ onMounted(() => {
|
||||||
|
|
||||||
await loadForm(slug)
|
await loadForm(slug)
|
||||||
|
|
||||||
// metaTitle () {
|
useOpnSeoMeta({
|
||||||
// if (this.form && this.form.is_pro && this.form.seo_meta.page_title) {
|
title: () => {
|
||||||
// return this.form.seo_meta.page_title
|
if (form && form.value.is_pro && form.value.seo_meta.page_title) {
|
||||||
// }
|
return form.value.seo_meta.page_title
|
||||||
// return this.form ? this.form.title : 'Create beautiful forms'
|
}
|
||||||
// },
|
return form.value ? form.value.title : 'Create beautiful forms'
|
||||||
// metaTemplate () {
|
},
|
||||||
// if (this.form && this.form.is_pro && this.form.seo_meta.page_title) {
|
description () {
|
||||||
// // Disable template if custom SEO title
|
if (form && form.value.is_pro && form.value.seo_meta.page_description) {
|
||||||
// return '%s'
|
return form.value.seo_meta.page_description
|
||||||
// }
|
}
|
||||||
// return null
|
return (form && form.value.description) ? form.value.description.substring(0, 160) : null
|
||||||
// },
|
},
|
||||||
// metaDescription () {
|
ogImage () {
|
||||||
// if (this.form && this.form.is_pro && this.form.seo_meta.page_description) {
|
if (form && form.value.is_pro && form.value.seo_meta.page_thumbnail) {
|
||||||
// return this.form.seo_meta.page_description
|
return form.value.seo_meta.page_thumbnail
|
||||||
// }
|
}
|
||||||
// return (this.form && this.form.description) ? this.form.description.substring(0, 160) : null
|
return (form && form.value.cover_picture) ? form.value.cover_picture : null
|
||||||
// },
|
},
|
||||||
// metaImage () {
|
robots () {
|
||||||
// if (this.form && this.form.is_pro && this.form.seo_meta.page_thumbnail) {
|
return (form && form.value.can_be_indexed) ? null : 'noindex, nofollow'
|
||||||
// return this.form.seo_meta.page_thumbnail
|
}
|
||||||
// }
|
})
|
||||||
// return (this.form && this.form.cover_picture) ? this.form.cover_picture : null
|
useHead({
|
||||||
// },
|
titleTemplate: (titleChunk) => {
|
||||||
// metaTags () {
|
if (form && form.value.is_pro && form.value.seo_meta.page_title) {
|
||||||
// return (this.form && this.form.can_be_indexed) ? [] : [{ name: 'robots', content: 'noindex' }]
|
// Disable template if custom SEO title
|
||||||
// }
|
return titleChunk
|
||||||
|
}
|
||||||
|
return titleChunk ? `${titleChunk} - OpnForm` : 'OpnForm';
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -141,9 +141,14 @@ export default {
|
||||||
FormCleanings
|
FormCleanings
|
||||||
},
|
},
|
||||||
|
|
||||||
middleware: 'auth',
|
|
||||||
|
|
||||||
setup () {
|
setup () {
|
||||||
|
definePageMeta({
|
||||||
|
middleware: "auth"
|
||||||
|
})
|
||||||
|
useOpnSeoMeta({
|
||||||
|
title: 'Home'
|
||||||
|
})
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const formsStore = useFormsStore()
|
const formsStore = useFormsStore()
|
||||||
const workingFormStore = useWorkingFormStore()
|
const workingFormStore = useWorkingFormStore()
|
||||||
|
@ -167,7 +172,6 @@ export default {
|
||||||
|
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
metaTitle: 'Home',
|
|
||||||
tabsList: [
|
tabsList: [
|
||||||
{
|
{
|
||||||
name: 'Submissions',
|
name: 'Submissions',
|
||||||
|
|
|
@ -26,7 +26,6 @@ import EmbedCode from '../../../../components/pages/forms/show/EmbedCode.vue'
|
||||||
import FormQrCode from '../../../../components/pages/forms/show/FormQrCode.vue'
|
import FormQrCode from '../../../../components/pages/forms/show/FormQrCode.vue'
|
||||||
import UrlFormPrefill from '../../../../components/pages/forms/show/UrlFormPrefill.vue'
|
import UrlFormPrefill from '../../../../components/pages/forms/show/UrlFormPrefill.vue'
|
||||||
import RegenerateFormLink from '../../../../components/pages/forms/show/RegenerateFormLink.vue'
|
import RegenerateFormLink from '../../../../components/pages/forms/show/RegenerateFormLink.vue'
|
||||||
import SeoMeta from '../../../../mixins/seo-meta.js'
|
|
||||||
import AdvancedFormUrlSettings from '../../../../components/open/forms/components/AdvancedFormUrlSettings.vue'
|
import AdvancedFormUrlSettings from '../../../../components/open/forms/components/AdvancedFormUrlSettings.vue'
|
||||||
import EmbedFormAsPopupModal from '../../../../components/pages/forms/show/EmbedFormAsPopupModal.vue'
|
import EmbedFormAsPopupModal from '../../../../components/pages/forms/show/EmbedFormAsPopupModal.vue'
|
||||||
|
|
||||||
|
@ -45,6 +44,15 @@ export default {
|
||||||
form: {type: Object, required: true},
|
form: {type: Object, required: true},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setup (props) {
|
||||||
|
definePageMeta({
|
||||||
|
middleware: "auth"
|
||||||
|
})
|
||||||
|
useOpnSeoMeta({
|
||||||
|
title: (props.form) ? 'Share Form - '+props.form.title : 'Share Form'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
shareFormConfig: {
|
shareFormConfig: {
|
||||||
hide_title: false,
|
hide_title: false,
|
||||||
|
@ -53,9 +61,6 @@ export default {
|
||||||
}),
|
}),
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
metaTitle() {
|
|
||||||
return (this.form) ? 'Form Share - '+this.form.title : 'Form Share'
|
|
||||||
},
|
|
||||||
shareUrlForQueryParams () {
|
shareUrlForQueryParams () {
|
||||||
let queryStr = ''
|
let queryStr = ''
|
||||||
for (const [key, value] of Object.entries(this.shareFormConfig)) {
|
for (const [key, value] of Object.entries(this.shareFormConfig)) {
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import FormStats from '../../../../components/open/forms/components/FormStats.vue'
|
import FormStats from '../../../../components/open/forms/components/FormStats.vue'
|
||||||
import SeoMeta from '../../../../mixins/seo-meta.js'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {FormStats},
|
components: {FormStats},
|
||||||
|
@ -18,10 +17,16 @@ export default {
|
||||||
form: {type: Object, required: true},
|
form: {type: Object, required: true},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setup (props) {
|
||||||
|
definePageMeta({
|
||||||
|
middleware: "auth"
|
||||||
|
})
|
||||||
|
useOpnSeoMeta({
|
||||||
|
title: (props.form) ? 'Form Analytics - '+props.form.title : 'Form Analytics'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
metaTitle() {
|
|
||||||
return (this.form ? ('Form Analytics - ' + this.form.title) : 'Form Analytics')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -6,14 +6,21 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import FormSubmissions from '../../../../components/open/forms/components/FormSubmissions.vue'
|
import FormSubmissions from '../../../../components/open/forms/components/FormSubmissions.vue'
|
||||||
import SeoMeta from '../../../../mixins/seo-meta.js'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {FormSubmissions},
|
components: {FormSubmissions},
|
||||||
props: {
|
props: {
|
||||||
form: {type: Object, required: true}
|
form: {type: Object, required: true}
|
||||||
},
|
},
|
||||||
mixins: [SeoMeta],
|
|
||||||
|
setup (props) {
|
||||||
|
definePageMeta({
|
||||||
|
middleware: "auth"
|
||||||
|
})
|
||||||
|
useOpnSeoMeta({
|
||||||
|
title: (props.form) ? 'Form Submissions - '+props.form.title : 'Form Submissions'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
data: () => ({}),
|
data: () => ({}),
|
||||||
|
|
||||||
|
@ -21,9 +28,6 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
metaTitle() {
|
|
||||||
return (this.form) ? 'Form Submissions - ' + this.form.title : 'Form Submissions'
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {}
|
methods: {}
|
||||||
|
|
|
@ -48,7 +48,13 @@ const workspace = computed(() => workspacesStore.getCurrent)
|
||||||
const workspacesLoading = computed(() => workspacesStore.loading)
|
const workspacesLoading = computed(() => workspacesStore.loading)
|
||||||
const form = storeToRefs(workingFormStore).content
|
const form = storeToRefs(workingFormStore).content
|
||||||
|
|
||||||
// metaTitle: 'Create a new Form as Guest',
|
useOpnSeoMeta({
|
||||||
|
title: 'Create a new Form for free',
|
||||||
|
})
|
||||||
|
definePageMeta({
|
||||||
|
middleware: "guest"
|
||||||
|
})
|
||||||
|
|
||||||
// Data
|
// Data
|
||||||
const stateReady = ref(false)
|
const stateReady = ref(false)
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|
|
@ -33,7 +33,9 @@ definePageMeta({
|
||||||
middleware: "auth"
|
middleware: "auth"
|
||||||
})
|
})
|
||||||
|
|
||||||
const metaTitle = 'Create a new Form'
|
useOpnSeoMeta({
|
||||||
|
title: 'Create a new Form'
|
||||||
|
})
|
||||||
|
|
||||||
onBeforeRouteLeave((to, from, next) => {
|
onBeforeRouteLeave((to, from, next) => {
|
||||||
if (isDirty()) {
|
if (isDirty()) {
|
||||||
|
|
|
@ -125,11 +125,10 @@ definePageMeta({
|
||||||
middleware: "auth"
|
middleware: "auth"
|
||||||
})
|
})
|
||||||
|
|
||||||
// metaTitle: {type: String, default: 'Your Forms'},
|
useOpnSeoMeta({
|
||||||
// metaDescription: {
|
title: 'Your Forms',
|
||||||
// type: String,
|
description: 'All of your OpnForm are here. Create new forms, or update your existing forms.'
|
||||||
// default: 'All of your OpnForm are here. Create new forms, or update your existing one!'
|
})
|
||||||
// }
|
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const formsStore = useFormsStore()
|
const formsStore = useFormsStore()
|
||||||
|
|
|
@ -191,12 +191,10 @@ import PricingTable from '../components/pages/pricing/PricingTable.vue'
|
||||||
import AiFeature from '~/components/pages/welcome/AiFeature.vue'
|
import AiFeature from '~/components/pages/welcome/AiFeature.vue'
|
||||||
import Testimonials from '../components/pages/welcome/Testimonials.vue'
|
import Testimonials from '../components/pages/welcome/Testimonials.vue'
|
||||||
import TemplatesSlider from '../components/pages/welcome/TemplatesSlider.vue'
|
import TemplatesSlider from '../components/pages/welcome/TemplatesSlider.vue'
|
||||||
import SeoMeta from '../mixins/seo-meta.js'
|
|
||||||
import opnformConfig from "~/opnform.config.js";
|
import opnformConfig from "~/opnform.config.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {Testimonials, Features, MoreFeatures, PricingTable, AiFeature, TemplatesSlider},
|
components: {Testimonials, Features, MoreFeatures, PricingTable, AiFeature, TemplatesSlider},
|
||||||
mixins: [SeoMeta],
|
|
||||||
layout: 'default',
|
layout: 'default',
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
|
@ -213,8 +211,6 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
title: 'OpnForm',
|
|
||||||
metaTitle: 'Create beautiful & open-source forms for free'
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
|
|
@ -52,29 +52,16 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import LoginForm from "~/components/pages/auth/components/LoginForm.vue"
|
import LoginForm from "~/components/pages/auth/components/LoginForm.vue"
|
||||||
|
|
||||||
export default {
|
definePageMeta({
|
||||||
components: {
|
middleware: "guest"
|
||||||
LoginForm
|
})
|
||||||
},
|
defineRouteRules({
|
||||||
|
prerender: true
|
||||||
setup() {
|
})
|
||||||
definePageMeta({
|
useOpnSeoMeta({
|
||||||
middleware: "guest"
|
title: 'Login'
|
||||||
})
|
})
|
||||||
defineRouteRules({
|
|
||||||
prerender: true
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
data: () => ({
|
|
||||||
metaTitle: 'Login',
|
|
||||||
}),
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -245,6 +245,11 @@ export default {
|
||||||
layout: 'default',
|
layout: 'default',
|
||||||
|
|
||||||
setup () {
|
setup () {
|
||||||
|
useOpnSeoMeta({
|
||||||
|
title: 'Pricing',
|
||||||
|
description: 'All of our core features are free, and there is no quantity limit. You can also created more advanced and customized forms with OpnForms Pro.'
|
||||||
|
})
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
middleware: [
|
middleware: [
|
||||||
function (to, from) {
|
function (to, from) {
|
||||||
|
@ -264,15 +269,6 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
data: () => ({
|
|
||||||
metaTitle: 'Pricing',
|
|
||||||
metaDescription: 'All of our core features are free, and there is no quantity limit. You can also created more advanced and customized forms with OpnForms Pro.',
|
|
||||||
}),
|
|
||||||
|
|
||||||
mounted() {},
|
|
||||||
|
|
||||||
computed: {},
|
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
contactUs() {
|
contactUs() {
|
||||||
window.$crisp.push(['do', 'chat:show'])
|
window.$crisp.push(['do', 'chat:show'])
|
||||||
|
|
|
@ -13,12 +13,13 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
|
||||||
// metaTitle: 'Privacy Policy',
|
|
||||||
|
|
||||||
import {useNotionPagesStore} from "~/stores/notion_pages.js";
|
import {useNotionPagesStore} from "~/stores/notion_pages.js";
|
||||||
import {computed} from "vue";
|
import {computed} from "vue";
|
||||||
|
|
||||||
|
useOpnSeoMeta({
|
||||||
|
title: 'Privacy Policy'
|
||||||
|
})
|
||||||
|
|
||||||
const notionPageStore = useNotionPagesStore()
|
const notionPageStore = useNotionPagesStore()
|
||||||
await notionPageStore.load('9c97349ceda7455aab9b341d1ff70f79')
|
await notionPageStore.load('9c97349ceda7455aab9b341d1ff70f79')
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,10 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
|
useOpnSeoMeta({
|
||||||
|
title: 'Register'
|
||||||
|
})
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
middleware: "guest"
|
middleware: "guest"
|
||||||
})
|
})
|
||||||
|
@ -72,10 +76,7 @@ export default {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
middleware: 'guest',
|
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
metaTitle: 'Register'
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
computed: {},
|
computed: {},
|
||||||
|
|
|
@ -45,6 +45,10 @@
|
||||||
middleware: "auth"
|
middleware: "auth"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
useOpnSeoMeta({
|
||||||
|
title: 'Settings'
|
||||||
|
})
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const user = computed(() => authStore.user)
|
const user = computed(() => authStore.user)
|
||||||
const tabsList = computed(() => {
|
const tabsList = computed(() => {
|
||||||
|
@ -84,4 +88,3 @@
|
||||||
return tabs
|
return tabs
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -20,9 +20,15 @@ import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const metaTitle = 'Account'
|
|
||||||
let loading = false
|
let loading = false
|
||||||
|
|
||||||
|
useOpnSeoMeta({
|
||||||
|
title: 'Account'
|
||||||
|
})
|
||||||
|
definePageMeta({
|
||||||
|
middleware: "auth"
|
||||||
|
})
|
||||||
|
|
||||||
const deleteAccount = () => {
|
const deleteAccount = () => {
|
||||||
loading = true
|
loading = true
|
||||||
opnFetch('/user', {method:'DELETE'}).then(async (data) => {
|
opnFetch('/user', {method:'DELETE'}).then(async (data) => {
|
||||||
|
|
|
@ -41,7 +41,10 @@ definePageMeta({
|
||||||
middleware: "admin"
|
middleware: "admin"
|
||||||
})
|
})
|
||||||
|
|
||||||
const metaTitle = 'Admin'
|
useOpnSeoMeta({
|
||||||
|
title: 'Admin'
|
||||||
|
})
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const workspacesStore = useWorkspacesStore()
|
const workspacesStore = useWorkspacesStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
|
@ -24,7 +24,13 @@ import { computed } from 'vue'
|
||||||
import { useAuthStore } from '../../stores/auth'
|
import { useAuthStore } from '../../stores/auth'
|
||||||
import AppSumoBilling from '../../components/vendor/appsumo/AppSumoBilling.vue'
|
import AppSumoBilling from '../../components/vendor/appsumo/AppSumoBilling.vue'
|
||||||
|
|
||||||
const metaTitle = 'Billing'
|
useOpnSeoMeta({
|
||||||
|
title: 'Billing'
|
||||||
|
})
|
||||||
|
definePageMeta({
|
||||||
|
middleware: "auth"
|
||||||
|
})
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
let user = computed(() => authStore.user)
|
let user = computed(() => authStore.user)
|
||||||
let billingLoading = false
|
let billingLoading = false
|
||||||
|
|
|
@ -25,7 +25,13 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
const metaTitle = 'Password'
|
useOpnSeoMeta({
|
||||||
|
title: 'Password'
|
||||||
|
})
|
||||||
|
definePageMeta({
|
||||||
|
middleware: "auth"
|
||||||
|
})
|
||||||
|
|
||||||
let form = useForm({
|
let form = useForm({
|
||||||
password: '',
|
password: '',
|
||||||
password_confirmation: ''
|
password_confirmation: ''
|
||||||
|
|
|
@ -23,7 +23,14 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const user = computed(() => authStore.user)
|
const user = computed(() => authStore.user)
|
||||||
const metaTitle = 'Profile'
|
|
||||||
|
useOpnSeoMeta({
|
||||||
|
title: 'Profile'
|
||||||
|
})
|
||||||
|
definePageMeta({
|
||||||
|
middleware: "auth"
|
||||||
|
})
|
||||||
|
|
||||||
let form = useForm({
|
let form = useForm({
|
||||||
name: '',
|
name: '',
|
||||||
email: ''
|
email: ''
|
||||||
|
|
|
@ -122,7 +122,14 @@ const crisp = useCrisp()
|
||||||
const workspacesStore = useWorkspacesStore()
|
const workspacesStore = useWorkspacesStore()
|
||||||
const workspaces = computed(() => workspacesStore.getAll)
|
const workspaces = computed(() => workspacesStore.getAll)
|
||||||
let loading = computed(() => workspacesStore.loading)
|
let loading = computed(() => workspacesStore.loading)
|
||||||
const metaTitle = 'Workspaces'
|
|
||||||
|
useOpnSeoMeta({
|
||||||
|
title: 'Workspaces'
|
||||||
|
})
|
||||||
|
definePageMeta({
|
||||||
|
middleware: "auth"
|
||||||
|
})
|
||||||
|
|
||||||
let form = useForm({
|
let form = useForm({
|
||||||
name: '',
|
name: '',
|
||||||
emoji: ''
|
emoji: ''
|
||||||
|
|
|
@ -3,15 +3,17 @@
|
||||||
<script>
|
<script>
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useAuthStore } from '../../stores/auth'
|
import { useAuthStore } from '../../stores/auth'
|
||||||
import SeoMeta from '../../mixins/seo-meta.js'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { },
|
components: { },
|
||||||
layout: 'default',
|
layout: 'default',
|
||||||
middleware: 'auth',
|
middleware: 'auth',
|
||||||
mixins: [SeoMeta],
|
|
||||||
|
|
||||||
setup () {
|
setup () {
|
||||||
|
useOpnSeoMeta({
|
||||||
|
title: 'Error'
|
||||||
|
})
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
return {
|
return {
|
||||||
authenticated : computed(() => authStore.check),
|
authenticated : computed(() => authStore.check),
|
||||||
|
@ -19,7 +21,6 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
metaTitle: 'Error',
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
mounted () {
|
mounted () {
|
||||||
|
|
|
@ -18,13 +18,16 @@
|
||||||
<script>
|
<script>
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useAuthStore } from '../../stores/auth'
|
import { useAuthStore } from '../../stores/auth'
|
||||||
import SeoMeta from '../../mixins/seo-meta.js'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
layout: 'default',
|
layout: 'default',
|
||||||
middleware: 'auth',
|
middleware: 'auth',
|
||||||
|
|
||||||
setup () {
|
setup () {
|
||||||
|
useOpnSeoMeta({
|
||||||
|
title: 'Subscription Success'
|
||||||
|
})
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
return {
|
return {
|
||||||
authStore,
|
authStore,
|
||||||
|
@ -34,7 +37,6 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
metaTitle: 'Subscription Success',
|
|
||||||
interval: null
|
interval: null
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,9 @@
|
||||||
<v-button color="gray" size="small" @click.prevent="showFormTemplateModal=true">
|
<v-button color="gray" size="small" @click.prevent="showFormTemplateModal=true">
|
||||||
Edit Template
|
Edit Template
|
||||||
</v-button>
|
</v-button>
|
||||||
<!-- <form-template-modal v-if="form" :form="form" :template="template" :show="showFormTemplateModal"-->
|
<form-template-modal v-if="form" :form="form" :template="template" :show="showFormTemplateModal"
|
||||||
<!-- @close="showFormTemplateModal=false"-->
|
@close="showFormTemplateModal=false"
|
||||||
<!-- />-->
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #right>
|
<template #right>
|
||||||
|
@ -199,12 +199,14 @@ import {computed} from 'vue'
|
||||||
import OpenCompleteForm from '../../components/open/forms/OpenCompleteForm.vue'
|
import OpenCompleteForm from '../../components/open/forms/OpenCompleteForm.vue'
|
||||||
import Breadcrumb from '~/components/global/Breadcrumb.vue'
|
import Breadcrumb from '~/components/global/Breadcrumb.vue'
|
||||||
import SingleTemplate from '../../components/pages/templates/SingleTemplate.vue'
|
import SingleTemplate from '../../components/pages/templates/SingleTemplate.vue'
|
||||||
import {fetchTemplate} from "~/stores/templates.js";
|
import {fetchTemplate} from "~/stores/templates.js"
|
||||||
|
import FormTemplateModal from '~/components/open/forms/components/templates/FormTemplateModal.vue'
|
||||||
|
|
||||||
defineRouteRules({
|
defineRouteRules({
|
||||||
prerender: true
|
prerender: true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { copy } = useClipboard()
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const templatesStore = useTemplatesStore()
|
const templatesStore = useTemplatesStore()
|
||||||
|
|
||||||
|
@ -255,34 +257,29 @@ const cleanQuotes = (str) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const copyTemplateUrl = () => {
|
const copyTemplateUrl = () => {
|
||||||
const str = template.value.share_url
|
copy(template.value.share_url)
|
||||||
const el = document.createElement('textarea')
|
|
||||||
el.value = str
|
|
||||||
document.body.appendChild(el)
|
|
||||||
el.select()
|
|
||||||
document.execCommand('copy')
|
|
||||||
document.body.removeChild(el)
|
|
||||||
useAlert().success('Copied!')
|
useAlert().success('Copied!')
|
||||||
}
|
}
|
||||||
|
|
||||||
// metaTitle() {
|
useOpnSeoMeta({
|
||||||
// return this.template ? this.template.name : 'Form Template'
|
title: () => {
|
||||||
// },
|
if (!template || !template.value) return 'Form Template'
|
||||||
// metaDescription() {
|
return template.value.name
|
||||||
// if (!this.template) return null
|
},
|
||||||
// // take the first 140 characters of the description
|
description () {
|
||||||
// return this.template.short_description?.substring(0, 140) + '... | Customize any template and create your own form in minutes.'
|
if (!template || !template.value) return null
|
||||||
// },
|
// take the first 140 characters of the description
|
||||||
// metaImage() {
|
return template.value.short_description?.substring(0, 140) + '... | Customize any template and create your own form in minutes.'
|
||||||
// if (!this.template) return null
|
},
|
||||||
// return this.template.image_url
|
ogImage () {
|
||||||
// },
|
if (!template || !template.value) return null
|
||||||
// metaTags() {
|
return template.value.image_url
|
||||||
// if (!this.template) {
|
},
|
||||||
// return [];
|
robots () {
|
||||||
// }
|
if (!template || !template.value) return null
|
||||||
// return this.template.publicly_listed ? [] : [{name: 'robots', content: 'noindex'}]
|
return template.value.publicly_listed ? null : 'noindex'
|
||||||
// },
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang='scss'>
|
<style lang='scss'>
|
||||||
|
|
|
@ -26,10 +26,10 @@ defineRouteRules({
|
||||||
prerender: true
|
prerender: true
|
||||||
})
|
})
|
||||||
|
|
||||||
// props: {
|
useOpnSeoMeta({
|
||||||
// metaTitle: { type: String, default: 'Templates' },
|
title: 'Form Templates',
|
||||||
// metaDescription: { type: String, default: 'Our collection of beautiful templates to create your own forms!' }
|
description: 'Our collection of beautiful templates to create your own forms!'
|
||||||
// },
|
})
|
||||||
|
|
||||||
const templatesStore = useTemplatesStore()
|
const templatesStore = useTemplatesStore()
|
||||||
loadAllTemplates(templatesStore)
|
loadAllTemplates(templatesStore)
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<breadcrumb :path="breadcrumbs"/>
|
<breadcrumb :path="breadcrumbs"/>
|
||||||
|
|
||||||
<p v-if="industry === null || !industry" class="text-center my-4">
|
<p v-if="industry === null || !industry" class="text-center my-4">
|
||||||
We could not find this type.
|
We could not find this industry.
|
||||||
</p>
|
</p>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<section class="py-12 sm:py-16 bg-gray-50 border-b border-gray-200">
|
<section class="py-12 sm:py-16 bg-gray-50 border-b border-gray-200">
|
||||||
|
@ -23,7 +23,7 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
||||||
<templates-list :templates="templates" :filter-industries="false" :show-types="false">
|
<templates-list :templates="templates" :filter-industries="false" :show-industries="false">
|
||||||
<template #before-lists>
|
<template #before-lists>
|
||||||
<section class="py-12 bg-white border-t border-gray-200 sm:py-16">
|
<section class="py-12 bg-white border-t border-gray-200 sm:py-16">
|
||||||
<div class="px-4 mx-auto sm:px-6 lg:px-8 max-w-7xl">
|
<div class="px-4 mx-auto sm:px-6 lg:px-8 max-w-7xl">
|
||||||
|
@ -70,6 +70,29 @@ const breadcrumbs = computed(() => {
|
||||||
|
|
||||||
const industry = computed(() => templatesStore.industries.get(route.params.slug))
|
const industry = computed(() => templatesStore.industries.get(route.params.slug))
|
||||||
|
|
||||||
|
useOpnSeoMeta({
|
||||||
|
title: () => {
|
||||||
|
if (!industry.value) return 'Form Templates'
|
||||||
|
if (industry.value.meta_title.length > 60) {
|
||||||
|
return industry.value.meta_title
|
||||||
|
}
|
||||||
|
return industry.value.meta_title
|
||||||
|
},
|
||||||
|
description: () => industry.value ? industry.value.meta_description: 'Our collection of beautiful templates to create your own forms!'
|
||||||
|
})
|
||||||
|
useHead({
|
||||||
|
titleTemplate: (titleChunk) => {
|
||||||
|
// Disable title template for longer titles
|
||||||
|
if (industry.value
|
||||||
|
&& industry.value.meta_title.length < 60
|
||||||
|
&& !industry.value.meta_title.toLowerCase().includes('opnform')
|
||||||
|
) {
|
||||||
|
return titleChunk ? `${titleChunk} - OpnForm` : 'Form Templates - OpnForm'
|
||||||
|
}
|
||||||
|
return titleChunk ? titleChunk : 'Form Templates - OpnForm'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang='scss'>
|
<style lang='scss'>
|
||||||
|
|
|
@ -13,30 +13,28 @@
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- <templates-list :only-my="true" />-->
|
<templates-list :templates="templates" :loading="loading" :show-types="false" :show-industries="false"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import TemplatesList from '../../components/pages/templates/TemplatesList.vue'
|
definePageMeta({
|
||||||
|
middleware: "auth"
|
||||||
|
})
|
||||||
|
|
||||||
export default {
|
useOpnSeoMeta({
|
||||||
components: { TemplatesList },
|
title: 'My Templates',
|
||||||
middleware: 'auth',
|
description: 'Our collection of beautiful templates to create your own forms!'
|
||||||
|
})
|
||||||
|
|
||||||
props: {
|
let loading = ref(false)
|
||||||
metaTitle: { type: String, default: 'My Templates' },
|
let templates = ref([])
|
||||||
metaDescription: { type: String, default: 'Our collection of beautiful templates to create your own forms!' }
|
|
||||||
},
|
|
||||||
|
|
||||||
data () {
|
onMounted(() => {
|
||||||
return {}
|
loading.value = true
|
||||||
},
|
opnFetch('templates',{query: {onlymy: true}}).then((data) => {
|
||||||
|
loading.value = false
|
||||||
mounted () {},
|
templates.value = data
|
||||||
|
})
|
||||||
computed: {},
|
})
|
||||||
|
|
||||||
methods: {}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -71,6 +71,29 @@ const breadcrumbs = computed(() => {
|
||||||
|
|
||||||
const type = computed(() => templatesStore.types.get(route.params.slug))
|
const type = computed(() => templatesStore.types.get(route.params.slug))
|
||||||
|
|
||||||
|
useOpnSeoMeta({
|
||||||
|
title: () => {
|
||||||
|
if (!type.value) return 'Form Templates'
|
||||||
|
if (type.value.meta_title.length > 60) {
|
||||||
|
return type.value.meta_title
|
||||||
|
}
|
||||||
|
return type.value.meta_title
|
||||||
|
},
|
||||||
|
description: () => type.value ? type.value.meta_description: 'Our collection of beautiful templates to create your own forms!'
|
||||||
|
})
|
||||||
|
useHead({
|
||||||
|
titleTemplate: (titleChunk) => {
|
||||||
|
// Disable title template for longer titles
|
||||||
|
if (type.value
|
||||||
|
&& type.value.meta_title.length < 60
|
||||||
|
&& !type.value.meta_title.toLowerCase().includes('opnform')
|
||||||
|
) {
|
||||||
|
return titleChunk ? `${titleChunk} - OpnForm` : 'Form Templates - OpnForm'
|
||||||
|
}
|
||||||
|
return titleChunk ? titleChunk : 'Form Templates - OpnForm'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang='scss'>
|
<style lang='scss'>
|
||||||
|
|
|
@ -13,11 +13,13 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
// metaTitle: 'Terms & Conditions',
|
|
||||||
|
|
||||||
import {useNotionPagesStore} from "~/stores/notion_pages.js";
|
import {useNotionPagesStore} from "~/stores/notion_pages.js";
|
||||||
import {computed} from "vue";
|
import {computed} from "vue";
|
||||||
|
|
||||||
|
useOpnSeoMeta({
|
||||||
|
title: 'Terms & Conditions'
|
||||||
|
})
|
||||||
|
|
||||||
const notionPageStore = useNotionPagesStore()
|
const notionPageStore = useNotionPagesStore()
|
||||||
await notionPageStore.load('246420da2834480ca04047b0c5a00929')
|
await notionPageStore.load('246420da2834480ca04047b0c5a00929')
|
||||||
|
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 15 KiB |
|
@ -0,0 +1,2 @@
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
Loading…
Reference in New Issue