Adding Custom domains (#247)
* WIP * wip * Finished doing most of the work
This commit is contained in:
@@ -22,9 +22,9 @@
|
||||
<small v-if="help && helpPosition=='below_input'" :class="theme.default.help" class="flex-grow">
|
||||
<slot name="help"><span class="field-help" v-html="help" /></slot>
|
||||
</small>
|
||||
<small v-else class="flex-grow"></small>
|
||||
<small v-else class="flex-grow" />
|
||||
<small v-if="showCharLimit && maxCharLimit" :class="theme.default.help">
|
||||
{{ charCount }}/{{ maxCharLimit }}
|
||||
{{ charCount }}/{{ maxCharLimift }}
|
||||
</small>
|
||||
</div>
|
||||
<has-error v-if="hasValidation" :form="form" :field="name" />
|
||||
@@ -40,10 +40,10 @@ export default {
|
||||
|
||||
props: {
|
||||
maxCharLimit: { type: Number, required: false, default: null },
|
||||
showCharLimit: { type: Boolean, required: false, default: false },
|
||||
showCharLimit: { type: Boolean, required: false, default: false }
|
||||
},
|
||||
computed: {
|
||||
charCount() {
|
||||
charCount () {
|
||||
return (this.compVal) ? this.compVal.length : 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,11 @@
|
||||
</svg>
|
||||
</template>
|
||||
<p class="mt-4 text-gray-500 text-sm">
|
||||
Customize the image and text that appear when you share your form on other sites (Open Graph).
|
||||
Customize the link, images and text that appear when you share your form on other sites (Open Graph).
|
||||
</p>
|
||||
<select-input v-if="customDomainAllowed" v-model="form.custom_domain" :disabled="customDomainOptions.length <= 0" :options="customDomainOptions" name="type"
|
||||
class="mt-4" label="Form Domain" placeholder="yourdomain.com"
|
||||
/>
|
||||
<text-input v-model="form.seo_meta.page_title" name="page_title" class="mt-4"
|
||||
label="Page Title" help="Under or approximately 60 characters"
|
||||
/>
|
||||
@@ -41,6 +44,20 @@ export default {
|
||||
set (value) {
|
||||
this.$store.commit('open/working_form/set', value)
|
||||
}
|
||||
},
|
||||
workspace () {
|
||||
return this.$store.getters['open/workspaces/getCurrent']()
|
||||
},
|
||||
customDomainOptions () {
|
||||
return this.workspace.custom_domains.map((domain) => {
|
||||
return {
|
||||
name: domain,
|
||||
value: domain
|
||||
}
|
||||
})
|
||||
},
|
||||
customDomainAllowed () {
|
||||
return window.config.custom_domains_enabled
|
||||
}
|
||||
},
|
||||
watch: {},
|
||||
|
||||
@@ -1,103 +1,116 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="loadingDuplicate || loadingDelete" class="pr-4 pt-2">
|
||||
<loader class="h-6 w-6 mx-auto"/>
|
||||
<loader class="h-6 w-6 mx-auto" />
|
||||
</div>
|
||||
<dropdown v-else class="inline" dusk="nav-dropdown">
|
||||
<template #trigger="{toggle}">
|
||||
<v-button color="white" class="mr-2" @click="toggle">
|
||||
<svg class="w-4 h-4 inline -mt-1" viewBox="0 0 16 4" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.00016 2.83366C8.4604 2.83366 8.8335 2.46056 8.8335 2.00033C8.8335 1.54009 8.4604 1.16699 8.00016 1.16699C7.53993 1.16699 7.16683 1.54009 7.16683 2.00033C7.16683 2.46056 7.53993 2.83366 8.00016 2.83366Z"
|
||||
stroke="#344054" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
d="M8.00016 2.83366C8.4604 2.83366 8.8335 2.46056 8.8335 2.00033C8.8335 1.54009 8.4604 1.16699 8.00016 1.16699C7.53993 1.16699 7.16683 1.54009 7.16683 2.00033C7.16683 2.46056 7.53993 2.83366 8.00016 2.83366Z"
|
||||
stroke="#344054" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M13.8335 2.83366C14.2937 2.83366 14.6668 2.46056 14.6668 2.00033C14.6668 1.54009 14.2937 1.16699 13.8335 1.16699C13.3733 1.16699 13.0002 1.54009 13.0002 2.00033C13.0002 2.46056 13.3733 2.83366 13.8335 2.83366Z"
|
||||
stroke="#344054" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
d="M13.8335 2.83366C14.2937 2.83366 14.6668 2.46056 14.6668 2.00033C14.6668 1.54009 14.2937 1.16699 13.8335 1.16699C13.3733 1.16699 13.0002 1.54009 13.0002 2.00033C13.0002 2.46056 13.3733 2.83366 13.8335 2.83366Z"
|
||||
stroke="#344054" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M2.16683 2.83366C2.62707 2.83366 3.00016 2.46056 3.00016 2.00033C3.00016 1.54009 2.62707 1.16699 2.16683 1.16699C1.70659 1.16699 1.3335 1.54009 1.3335 2.00033C1.3335 2.46056 1.70659 2.83366 2.16683 2.83366Z"
|
||||
stroke="#344054" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
d="M2.16683 2.83366C2.62707 2.83366 3.00016 2.46056 3.00016 2.00033C3.00016 1.54009 2.62707 1.16699 2.16683 1.16699C1.70659 1.16699 1.3335 1.54009 1.3335 2.00033C1.3335 2.46056 1.70659 2.83366 2.16683 2.83366Z"
|
||||
stroke="#344054" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</v-button>
|
||||
</template>
|
||||
<router-link v-if="isMainPage && user" :to="{name:'forms.show_public', params: {slug: form.slug}}" target="_blank"
|
||||
class="block px-4 py-2 text-md text-gray-700 dark:text-white hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
|
||||
v-track.view_form_click="{form_id:form.id, form_slug:form.slug}"
|
||||
>
|
||||
<a v-if="isMainPage && user" v-track.view_form_click="{form_id:form.id, form_slug:form.slug}" :href="form.share_url"
|
||||
target="_blank"
|
||||
class="block px-4 py-2 text-md text-gray-700 dark:text-white hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
|
||||
>
|
||||
<svg class="w-4 h-4 mr-2" 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
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M1 12C1 12 5 4 12 4C19 4 23 12 23 12C23 12 19 20 12 20C5 20 1 12 1 12Z"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
View form
|
||||
</router-link>
|
||||
<router-link v-if="isMainPage" :to="{name:'forms.edit', params: {slug: form.slug}}"
|
||||
class="block block px-4 py-2 text-md text-gray-700 dark:text-white hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
|
||||
v-track.edit_form_click="{form_id:form.id, form_slug:form.slug}"
|
||||
>
|
||||
</a>
|
||||
<router-link v-if="isMainPage" v-track.edit_form_click="{form_id:form.id, form_slug:form.slug}"
|
||||
:to="{name:'forms.edit', params: {slug: form.slug}}"
|
||||
class="block block px-4 py-2 text-md text-gray-700 dark:text-white hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
|
||||
>
|
||||
<svg class="w-4 h-4 mr-2" width="18" height="17" viewBox="0 0 18 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M8.99998 15.6662H16.5M1.5 15.6662H2.89545C3.3031 15.6662 3.50693 15.6662 3.69874 15.6202C3.8688 15.5793 4.03138 15.512 4.1805 15.4206C4.34869 15.3175 4.49282 15.1734 4.78107 14.8852L15.25 4.4162C15.9404 3.72585 15.9404 2.60656 15.25 1.9162C14.5597 1.22585 13.4404 1.22585 12.75 1.9162L2.28105 12.3852C1.9928 12.6734 1.84867 12.8175 1.7456 12.9857C1.65422 13.1348 1.58688 13.2974 1.54605 13.4675C1.5 13.6593 1.5 13.8631 1.5 14.2708V15.6662Z"
|
||||
stroke="currentColor" stroke-width="1.67" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
stroke="currentColor" stroke-width="1.67" stroke-linecap="round" stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</router-link>
|
||||
<a href="#" v-if="isMainPage"
|
||||
class="block block px-4 py-2 text-md text-gray-700 dark:text-white hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
|
||||
@click.prevent="copyLink"
|
||||
>
|
||||
<a v-if="isMainPage" href="#"
|
||||
class="block block px-4 py-2 text-md text-gray-700 dark:text-white hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
|
||||
@click.prevent="copyLink"
|
||||
>
|
||||
<svg class="w-4 h-4 mr-2" viewBox="0 0 16 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.00016 8.33317H4.66683C2.82588 8.33317 1.3335 6.84079 1.3335 4.99984C1.3335 3.15889 2.82588 1.6665 4.66683 1.6665H6.00016M10.0002 8.33317H11.3335C13.1744 8.33317 14.6668 6.84079 14.6668 4.99984C14.6668 3.15889 13.1744 1.6665 11.3335 1.6665H10.0002M4.66683 4.99984L11.3335 4.99984" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.00016 8.33317H4.66683C2.82588 8.33317 1.3335 6.84079 1.3335 4.99984C1.3335 3.15889 2.82588 1.6665 4.66683 1.6665H6.00016M10.0002 8.33317H11.3335C13.1744 8.33317 14.6668 6.84079 14.6668 4.99984C14.6668 3.15889 13.1744 1.6665 11.3335 1.6665H10.0002M4.66683 4.99984L11.3335 4.99984" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
Copy link to share
|
||||
</a>
|
||||
<a href="#"
|
||||
class="block block px-4 py-2 text-md text-gray-700 dark:text-white hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
|
||||
v-track.duplicate_form_click="{form_id:form.id, form_slug:form.slug}"
|
||||
@click.prevent="duplicateForm"
|
||||
>
|
||||
<a v-track.duplicate_form_click="{form_id:form.id, form_slug:form.slug}"
|
||||
href="#"
|
||||
class="block block px-4 py-2 text-md text-gray-700 dark:text-white hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
|
||||
@click.prevent="duplicateForm"
|
||||
>
|
||||
<svg class="w-4 h-4 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M8 7v8a2 2 0 002 2h6M8 7V5a2 2 0 012-2h4.586a1 1 0 01.707.293l4.414 4.414a1 1 0 01.293.707V15a2 2 0 01-2 2h-2M8 7H6a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2v-2"
|
||||
/>
|
||||
/>
|
||||
</svg>
|
||||
Duplicate form
|
||||
</a>
|
||||
<a href="#" v-if="!isMainPage" v-track.create_template_click="{form_id:form.id, form_slug:form.slug}"
|
||||
<a v-if="!isMainPage" v-track.create_template_click="{form_id:form.id, form_slug:form.slug}" href="#"
|
||||
class="block block px-4 py-2 text-md text-gray-700 dark:text-white hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
|
||||
@click.prevent="showFormTemplateModal=true"
|
||||
>
|
||||
<svg class="w-4 h-4 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor" stroke-width="2">
|
||||
stroke="currentColor" stroke-width="2"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M17 14v6m-3-3h6M6 10h2a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v2a2 2 0 002 2zm10 0h2a2 2 0 002-2V6a2 2 0 00-2-2h-2a2 2 0 00-2 2v2a2 2 0 002 2zM6 20h2a2 2 0 002-2v-2a2 2 0 00-2-2H6a2 2 0 00-2 2v2a2 2 0 002 2z"/>
|
||||
d="M17 14v6m-3-3h6M6 10h2a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v2a2 2 0 002 2zm10 0h2a2 2 0 002-2V6a2 2 0 00-2-2h-2a2 2 0 00-2 2v2a2 2 0 002 2zM6 20h2a2 2 0 002-2v-2a2 2 0 00-2-2H6a2 2 0 00-2 2v2a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
Create Template
|
||||
</a>
|
||||
<a href="#"
|
||||
class="block block px-4 py-2 text-md text-red-600 hover:bg-red-50 flex items-center"
|
||||
v-track.delete_form_click="{form_id:form.id, form_slug:form.slug}"
|
||||
@click.prevent="showDeleteFormModal=true"
|
||||
>
|
||||
<a v-track.delete_form_click="{form_id:form.id, form_slug:form.slug}"
|
||||
href="#"
|
||||
class="block block px-4 py-2 text-md text-red-600 hover:bg-red-50 flex items-center"
|
||||
@click.prevent="showDeleteFormModal=true"
|
||||
>
|
||||
<svg class="w-4 h-4 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||
/>
|
||||
/>
|
||||
</svg>
|
||||
Delete form
|
||||
</a>
|
||||
</dropdown>
|
||||
|
||||
<!-- Delete Form Modal -->
|
||||
<modal :show="showDeleteFormModal" icon-color="red" @close="showDeleteFormModal=false" max-width="sm">
|
||||
<modal :show="showDeleteFormModal" icon-color="red" max-width="sm" @close="showDeleteFormModal=false">
|
||||
<template #icon>
|
||||
<svg class="w-10 h-10" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||
/>
|
||||
@@ -111,74 +124,78 @@
|
||||
If you want to permanently delete this form and all of its data, you can do so below.
|
||||
</p>
|
||||
<div class="flex mt-4">
|
||||
<v-button class="sm:w-1/2 mr-4" color="white" @click.prevent="showDeleteFormModal=false">Cancel</v-button>
|
||||
<v-button class="sm:w-1/2" color="red" :loading="loadingDelete" @click.prevent="deleteForm">Yes, delete it</v-button>
|
||||
<v-button class="sm:w-1/2 mr-4" color="white" @click.prevent="showDeleteFormModal=false">
|
||||
Cancel
|
||||
</v-button>
|
||||
<v-button class="sm:w-1/2" color="red" :loading="loadingDelete" @click.prevent="deleteForm">
|
||||
Yes, delete it
|
||||
</v-button>
|
||||
</div>
|
||||
</div>
|
||||
</modal>
|
||||
|
||||
<form-template-modal v-if="!isMainPage && user" :form="form" :show="showFormTemplateModal" @close="showFormTemplateModal=false"/>
|
||||
<form-template-modal v-if="!isMainPage && user" :form="form" :show="showFormTemplateModal" @close="showFormTemplateModal=false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import {mapGetters, mapState} from 'vuex'
|
||||
import { mapGetters, mapState } from 'vuex'
|
||||
import Dropdown from '../../../common/Dropdown.vue'
|
||||
import FormTemplateModal from '../../../open/forms/components/templates/FormTemplateModal.vue'
|
||||
|
||||
export default {
|
||||
name: 'ExtraMenu',
|
||||
components: { Dropdown, FormTemplateModal },
|
||||
props: {
|
||||
form: { type: Object, required: true },
|
||||
isMainPage: { type: Boolean, required: false, default: false }
|
||||
},
|
||||
name: 'ExtraMenu',
|
||||
components: { Dropdown, FormTemplateModal },
|
||||
props: {
|
||||
form: { type: Object, required: true },
|
||||
isMainPage: { type: Boolean, required: false, default: false }
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
loadingDuplicate: false,
|
||||
loadingDelete: false,
|
||||
showDeleteFormModal: false,
|
||||
showFormTemplateModal: false
|
||||
data: () => ({
|
||||
loadingDuplicate: false,
|
||||
loadingDelete: false,
|
||||
showDeleteFormModal: false,
|
||||
showFormTemplateModal: false
|
||||
}),
|
||||
|
||||
computed: {
|
||||
...mapGetters({
|
||||
user: 'auth/user'
|
||||
}),
|
||||
formEndpoint: () => '/api/open/forms/{id}'
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters({
|
||||
user: 'auth/user'
|
||||
}),
|
||||
formEndpoint: () => '/api/open/forms/{id}',
|
||||
methods: {
|
||||
copyLink () {
|
||||
const el = document.createElement('textarea')
|
||||
el.value = this.form.share_url
|
||||
document.body.appendChild(el)
|
||||
el.select()
|
||||
document.execCommand('copy')
|
||||
document.body.removeChild(el)
|
||||
this.alertSuccess('Copied!')
|
||||
},
|
||||
|
||||
methods: {
|
||||
copyLink(){
|
||||
const el = document.createElement('textarea')
|
||||
el.value = this.form.share_url
|
||||
document.body.appendChild(el)
|
||||
el.select()
|
||||
document.execCommand('copy')
|
||||
document.body.removeChild(el)
|
||||
this.alertSuccess('Copied!')
|
||||
},
|
||||
duplicateForm() {
|
||||
if (this.loadingDuplicate) return
|
||||
this.loadingDuplicate = true
|
||||
axios.post(this.formEndpoint.replace('{id}', this.form.id) + '/duplicate').then((response) => {
|
||||
this.$store.commit('open/forms/addOrUpdate', response.data.new_form)
|
||||
this.$router.push({name: 'forms.show', params: {slug: response.data.new_form.slug}})
|
||||
this.alertSuccess('Form was successfully duplicated.')
|
||||
this.loadingDuplicate = false
|
||||
})
|
||||
},
|
||||
deleteForm() {
|
||||
if (this.loadingDelete) return
|
||||
this.loadingDelete = true
|
||||
axios.delete(this.formEndpoint.replace('{id}', this.form.id)).then(() => {
|
||||
this.$store.commit('open/forms/remove', this.form)
|
||||
this.$router.push({name: 'home'})
|
||||
this.alertSuccess('Form was deleted.')
|
||||
this.loadingDelete = false
|
||||
})
|
||||
},
|
||||
duplicateForm () {
|
||||
if (this.loadingDuplicate) return
|
||||
this.loadingDuplicate = true
|
||||
axios.post(this.formEndpoint.replace('{id}', this.form.id) + '/duplicate').then((response) => {
|
||||
this.$store.commit('open/forms/addOrUpdate', response.data.new_form)
|
||||
this.$router.push({ name: 'forms.show', params: { slug: response.data.new_form.slug } })
|
||||
this.alertSuccess('Form was successfully duplicated.')
|
||||
this.loadingDuplicate = false
|
||||
})
|
||||
},
|
||||
deleteForm () {
|
||||
if (this.loadingDelete) return
|
||||
this.loadingDelete = true
|
||||
axios.delete(this.formEndpoint.replace('{id}', this.form.id)).then(() => {
|
||||
this.$store.commit('open/forms/remove', this.form)
|
||||
this.$router.push({ name: 'home' })
|
||||
this.alertSuccess('Form was deleted.')
|
||||
this.loadingDelete = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -3,8 +3,12 @@
|
||||
<div class="w-full">
|
||||
<div class="rounded-lg bg-gray-50 dark:bg-gray-800 px-6 py-8 sm:p-10 lg:flex lg:items-center">
|
||||
<div class="flex-1">
|
||||
<h3 class="inline-flex px-4 py-1 rounded-full text-md font-bold tracking-wide uppercase bg-white text-gray-800">Custom plan</h3>
|
||||
<div class="mt-4 text-md text-gray-600 dark:text-gray-400">Get a custom file upload limit, enterprise-level support, custom contract, payment via invoice/PO etc.</div>
|
||||
<h3 class="inline-flex px-4 py-1 rounded-full text-md font-bold tracking-wide uppercase bg-white text-gray-800">
|
||||
Custom plan
|
||||
</h3>
|
||||
<div class="mt-4 text-md text-gray-600 dark:text-gray-400">
|
||||
Get a custom file upload limit, enterprise-level support, custom contract, dedicated application instance in a specific region, payment via invoice/PO etc.
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-6 rounded-md lg:mt-0 lg:ml-10 lg:flex-shrink-0">
|
||||
<v-button color="white" class="w-full mt-4" @click.prevent="customPlanClick">
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<div class="w-full">
|
||||
<section class="relative">
|
||||
<div v-if="!homePage" class="absolute inset-0 grid" aria-hidden="true">
|
||||
<div class="bg-gray-100"></div>
|
||||
<div class="bg-white"></div>
|
||||
<div class="bg-gray-100" />
|
||||
<div class="bg-white" />
|
||||
</div>
|
||||
|
||||
<div class="px-4 mx-auto max-w-7xl sm:px-6 lg:px-8">
|
||||
@@ -14,13 +14,14 @@
|
||||
<span class="ml-2 text-nt-blue">
|
||||
<svg class="inline w-10 h-10" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g opacity="0.12">
|
||||
<path d="M15.9998 27.3333L10.6665 12H21.3332L15.9998 27.3333Z" fill="currentColor"/>
|
||||
<path d="M13.3332 4H9.33317L2.6665 12L10.6665 12L13.3332 4Z" fill="currentColor"/>
|
||||
<path d="M18.6665 4H22.6665L29.3332 12L21.3332 12L18.6665 4Z" fill="currentColor"/>
|
||||
<path d="M15.9998 27.3333L10.6665 12H21.3332L15.9998 27.3333Z" fill="currentColor" />
|
||||
<path d="M13.3332 4H9.33317L2.6665 12L10.6665 12L13.3332 4Z" fill="currentColor" />
|
||||
<path d="M18.6665 4H22.6665L29.3332 12L21.3332 12L18.6665 4Z" fill="currentColor" />
|
||||
</g>
|
||||
<path
|
||||
d="M3.33345 12H28.6668M13.3334 4L10.6668 12L16.0001 27.3333L21.3334 12L18.6668 4M16.8195 27.0167L28.7644 12.6829C28.9668 12.4399 29.0681 12.3185 29.1067 12.1829C29.1408 12.0633 29.1408 11.9367 29.1067 11.8171C29.0681 11.6815 28.9668 11.5601 28.7644 11.3171L22.9866 4.3838C22.8691 4.24273 22.8103 4.17219 22.7382 4.12148C22.6744 4.07654 22.6031 4.04318 22.5277 4.02289C22.4426 4 22.3508 4 22.1672 4H9.83305C9.64941 4 9.55758 4 9.4725 4.02289C9.39711 4.04318 9.32586 4.07654 9.26202 4.12148C9.18996 4.17219 9.13118 4.24273 9.01361 4.3838L3.23583 11.3171C3.0334 11.5601 2.93218 11.6815 2.8935 11.8171C2.85939 11.9366 2.85939 12.0633 2.8935 12.1829C2.93218 12.3185 3.0334 12.4399 3.23583 12.6829L15.1807 27.0167C15.4621 27.3544 15.6028 27.5232 15.7713 27.5848C15.919 27.6388 16.0812 27.6388 16.229 27.5848C16.3974 27.5232 16.5381 27.3544 16.8195 27.0167Z"
|
||||
stroke="currentColor" stroke-width="2.66667" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
stroke="currentColor" stroke-width="2.66667" stroke-linecap="round" stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
Pro Features
|
||||
</span>
|
||||
@@ -37,28 +38,31 @@
|
||||
<h4 class="flex-none text-sm font-semibold leading-6 tracking-widest text-gray-400 uppercase">
|
||||
What's included
|
||||
</h4>
|
||||
<div class="flex-auto h-px bg-gray-200"></div>
|
||||
<div class="flex-auto h-px bg-gray-200" />
|
||||
</div>
|
||||
|
||||
<ul role="list"
|
||||
class="grid grid-cols-1 gap-4 mt-4 text-sm font-medium leading-6 text-gray-900 sm:grid-cols-2 sm:gap-x-6 sm:gap-y-2">
|
||||
class="grid grid-cols-1 gap-4 mt-4 text-sm font-medium leading-6 text-gray-900 sm:grid-cols-2 sm:gap-x-6 sm:gap-y-2"
|
||||
>
|
||||
<li v-for="(title, i) in pricingInfo" :key="i" class="flex gap-x-3">
|
||||
<svg aria-hidden="true" class="w-5 h-5 shrink-0 stroke-blue-600" viewBox="0 0 24 24" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20 6L9 17L4 12" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M20 6L9 17L4 12" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
{{ title }}
|
||||
</li>
|
||||
<slot name="pricing-table"></slot>
|
||||
<slot name="pricing-table" />
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="p-2 -mt-2 flex-col lg:mt-0 lg:w-full lg:max-w-md lg:flex-shrink-0">
|
||||
<div
|
||||
class="grow h-full py-10 text-center rounded-2xl bg-gray-50 ring-1 ring-inset ring-gray-900/5 lg:flex lg:flex-col lg:justify-center lg:py-12">
|
||||
class="grow h-full py-10 text-center rounded-2xl bg-gray-50 ring-1 ring-inset ring-gray-900/5 lg:flex lg:flex-col lg:justify-center lg:py-12"
|
||||
>
|
||||
<div class="max-w-xs px-8 mx-auto space-y-6">
|
||||
<div class="flex items-center justify-center mb-10">
|
||||
<monthly-yearly-selector v-model="isYearly"/>
|
||||
<monthly-yearly-selector v-model="isYearly" />
|
||||
</div><!-- lg+ -->
|
||||
|
||||
<p class="flex flex-col items-center">
|
||||
@@ -75,11 +79,12 @@
|
||||
<v-button v-if="!authenticated" class="mr-1" :to="{ name: 'register' }" :arrow="true">
|
||||
Start free trial
|
||||
</v-button>
|
||||
<v-button v-else-if="authenticated && user.is_subscribed" class="mr-1" @click.prevent="openBilling"
|
||||
:arrow="true">
|
||||
<v-button v-else-if="authenticated && user.is_subscribed" class="mr-1" :arrow="true"
|
||||
@click.prevent="openBilling"
|
||||
>
|
||||
View Billing
|
||||
</v-button>
|
||||
<v-button v-else class="mr-1" @click.prevent="openCustomerCheckout('default')" :arrow="true">
|
||||
<v-button v-else class="mr-1" :arrow="true" @click.prevent="openCustomerCheckout('default')">
|
||||
Start free trial
|
||||
</v-button>
|
||||
</div>
|
||||
@@ -102,7 +107,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapGetters} from 'vuex'
|
||||
import { mapGetters } from 'vuex'
|
||||
import axios from 'axios'
|
||||
import MonthlyYearlySelector from './MonthlyYearlySelector.vue'
|
||||
import CheckoutDetailsModal from './CheckoutDetailsModal.vue'
|
||||
@@ -130,7 +135,7 @@ export default {
|
||||
'Slack notifications',
|
||||
'Discord notifications',
|
||||
'Editable submissions',
|
||||
'Custom domain (soon)',
|
||||
'1 Custom domain',
|
||||
'Custom code',
|
||||
'Larger file uploads (50mb)',
|
||||
'Remove OpnForm branding',
|
||||
@@ -139,11 +144,11 @@ export default {
|
||||
}),
|
||||
|
||||
methods: {
|
||||
openCustomerCheckout(plan) {
|
||||
openCustomerCheckout (plan) {
|
||||
this.selectedPlan = plan
|
||||
this.showDetailsModal = true
|
||||
},
|
||||
openBilling() {
|
||||
openBilling () {
|
||||
this.billingLoading = true
|
||||
axios.get('/api/subscription/billing-portal').then((response) => {
|
||||
this.billingLoading = false
|
||||
|
||||
16
resources/js/middleware/custom-domains.js
vendored
Normal file
16
resources/js/middleware/custom-domains.js
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
export default async (to, from, next) => {
|
||||
if (!window.config.custom_domains) {
|
||||
next()
|
||||
}
|
||||
const isCustomDomain = getDomain(window.location.href) !== getDomain(window.config.app_url)
|
||||
if (isCustomDomain && !['forms.show_public'].includes(to.name)) {
|
||||
// If route isn't a public form, redirect
|
||||
window.location.href = 'https://opnform.com/'
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
function getDomain (url) {
|
||||
return (new URL(url)).hostname
|
||||
}
|
||||
17
resources/js/middleware/notion-connection.js
vendored
17
resources/js/middleware/notion-connection.js
vendored
@@ -1,17 +0,0 @@
|
||||
import store from '~/store'
|
||||
|
||||
export default async (to, from, next) => {
|
||||
/* if (store.getters['auth/check'] && store.getters['auth/user'].workspaces_count === 0) {
|
||||
if ([
|
||||
'forms.create',
|
||||
'forms.show',
|
||||
'forms.edit',
|
||||
'home'
|
||||
].includes(to.name)
|
||||
) {
|
||||
next({ name: 'onboarding' })
|
||||
}
|
||||
}*/
|
||||
|
||||
next()
|
||||
}
|
||||
8
resources/js/mixins/forms/input.js
vendored
8
resources/js/mixins/forms/input.js
vendored
@@ -52,5 +52,13 @@ export default {
|
||||
return this.content
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
value (val) {
|
||||
if (val !== this.compVal) {
|
||||
this.compVal = val
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
<div class="flex bg-gray-50">
|
||||
<div class="w-full md:w-4/5 lg:w-3/5 md:mx-auto md:max-w-4xl px-4">
|
||||
<div class="pt-4 pb-0">
|
||||
<a href="#" @click.prevent="goBack" class="flex text-blue mb-2 font-semibold text-sm">
|
||||
<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">
|
||||
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"/>
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
Go back
|
||||
</a>
|
||||
@@ -20,24 +22,30 @@
|
||||
<div class="flex">
|
||||
<extra-menu :form="form" />
|
||||
|
||||
<v-button target="_blank" :to="{name:'forms.show_public', params: {slug: form.slug}}"
|
||||
color="white" class="mr-2 text-blue-600 hidden sm:block"
|
||||
v-track.view_form_click="{form_id:form.id, form_slug:form.slug}">
|
||||
<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">
|
||||
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"/>
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</v-button>
|
||||
<v-button class="text-white" @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">
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.99998 15.6662H16.5M1.5 15.6662H2.89545C3.3031 15.6662 3.50693 15.6662 3.69874 15.6202C3.8688 15.5793 4.03138 15.512 4.1805 15.4206C4.34869 15.3175 4.49282 15.1734 4.78107 14.8852L15.25 4.4162C15.9404 3.72585 15.9404 2.60656 15.25 1.9162C14.5597 1.22585 13.4404 1.22585 12.75 1.9162L2.28105 12.3852C1.9928 12.6734 1.84867 12.8175 1.7456 12.9857C1.65422 13.1348 1.58688 13.2974 1.54605 13.4675C1.5 13.6593 1.5 13.8631 1.5 14.2708V15.6662Z"
|
||||
stroke="currentColor" stroke-width="1.67" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
stroke="currentColor" stroke-width="1.67" stroke-linecap="round" stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
Edit form
|
||||
</v-button>
|
||||
@@ -52,16 +60,18 @@
|
||||
<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">
|
||||
<span v-if="form.visibility=='draft'"
|
||||
class="inline-flex items-center rounded-full bg-yellow-100 px-2 py-1 text-xs font-medium text-yellow-600 ring-1 ring-inset ring-gray-500/10 dark:text-white dark:bg-gray-700"
|
||||
>
|
||||
Draft - not publicly accessible
|
||||
</span>
|
||||
<span v-else-if="form.visibility=='closed'"
|
||||
class="inline-flex items-center rounded-full bg-yellow-100 px-2 py-1 text-xs font-medium text-yellow-600 ring-1 ring-inset ring-gray-500/10 dark:text-white dark:bg-gray-700">
|
||||
<span v-else-if="form.visibility=='closed'"
|
||||
class="inline-flex items-center rounded-full bg-yellow-100 px-2 py-1 text-xs font-medium text-yellow-600 ring-1 ring-inset ring-gray-500/10 dark:text-white dark:bg-gray-700"
|
||||
>
|
||||
Closed - won't accept new submissions
|
||||
</span>
|
||||
<span v-for="(tag,i) in form.tags" :key="tag"
|
||||
class="inline-flex items-center rounded-full bg-gray-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10 dark:text-white dark:bg-gray-700"
|
||||
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>
|
||||
@@ -69,14 +79,14 @@
|
||||
|
||||
<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>
|
||||
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>
|
||||
form.max_submissions_count
|
||||
}} submissions. </span>
|
||||
<span v-else> This form will stop accepting submissions after {{ form.max_submissions_count }} submissions. </span>
|
||||
</p>
|
||||
|
||||
@@ -84,15 +94,16 @@
|
||||
|
||||
<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 class="mr-6" v-for="(tab, i) in tabsList" :key="i+1">
|
||||
<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>
|
||||
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>
|
||||
@@ -107,7 +118,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<div v-else-if="loading" class="text-center w-full p-5">
|
||||
<loader class="h-6 w-6 mx-auto"/>
|
||||
<loader class="h-6 w-6 mx-auto" />
|
||||
</div>
|
||||
<div v-else class="text-center w-full p-5">
|
||||
Form not found.
|
||||
@@ -119,9 +130,9 @@
|
||||
import axios from 'axios'
|
||||
import store from '~/store'
|
||||
import Form from 'vform'
|
||||
import {mapGetters, mapState} from 'vuex'
|
||||
import { mapGetters, mapState } from 'vuex'
|
||||
import ProTag from '../../../components/common/ProTag.vue'
|
||||
import VButton from "../../../components/common/Button.vue";
|
||||
import VButton from '../../../components/common/Button.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'
|
||||
@@ -143,18 +154,18 @@ export default {
|
||||
},
|
||||
mixins: [SeoMeta],
|
||||
|
||||
beforeRouteEnter(to, from, next) {
|
||||
beforeRouteEnter (to, from, next) {
|
||||
loadForms()
|
||||
next()
|
||||
},
|
||||
|
||||
beforeRouteLeave(to, from, next) {
|
||||
beforeRouteLeave (to, from, next) {
|
||||
this.workingForm = null
|
||||
next()
|
||||
},
|
||||
middleware: 'auth',
|
||||
|
||||
data() {
|
||||
data () {
|
||||
return {
|
||||
metaTitle: 'Home',
|
||||
tabsList: [
|
||||
@@ -183,59 +194,59 @@ export default {
|
||||
workspacesLoading: state => state['open/workspaces'].loading
|
||||
}),
|
||||
workingForm: {
|
||||
get() {
|
||||
get () {
|
||||
return this.$store.state['open/working_form'].content
|
||||
},
|
||||
set(value) {
|
||||
set (value) {
|
||||
this.$store.commit('open/working_form/set', value)
|
||||
}
|
||||
},
|
||||
workspace() {
|
||||
workspace () {
|
||||
if (!this.form) return null
|
||||
return this.$store.getters['open/workspaces/getById'](this.form.workspace_id)
|
||||
},
|
||||
form() {
|
||||
form () {
|
||||
return this.$store.getters['open/forms/getBySlug'](this.$route.params.slug)
|
||||
},
|
||||
formEndpoint: () => '/api/open/forms/{id}',
|
||||
loading() {
|
||||
loading () {
|
||||
return this.formsLoading || this.workspacesLoading
|
||||
},
|
||||
displayClosesDate() {
|
||||
displayClosesDate () {
|
||||
if (this.form.closes_at) {
|
||||
let dateObj = new Date(this.form.closes_at)
|
||||
return dateObj.getFullYear() + "-" +
|
||||
String(dateObj.getMonth() + 1).padStart(2, '0') + "-" +
|
||||
String(dateObj.getDate()).padStart(2, '0') + " " +
|
||||
String(dateObj.getHours()).padStart(2, '0') + ":" +
|
||||
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 "";
|
||||
return ''
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
form() {
|
||||
form () {
|
||||
this.workingForm = new Form(this.form)
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
mounted () {
|
||||
if (this.form) {
|
||||
this.workingForm = new Form(this.form)
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
openCrisp() {
|
||||
openCrisp () {
|
||||
window.$crisp.push(['do', 'chat:show'])
|
||||
window.$crisp.push(['do', 'chat:open'])
|
||||
},
|
||||
goBack() {
|
||||
this.$router.push({name: 'home'})
|
||||
goBack () {
|
||||
this.$router.push({ name: 'home' })
|
||||
},
|
||||
openEdit() {
|
||||
this.$router.push({name: 'forms.edit', params: {slug: this.form.slug}})
|
||||
openEdit () {
|
||||
this.$router.push({ name: 'forms.edit', params: { slug: this.form.slug } })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +1,92 @@
|
||||
<template>
|
||||
<div>
|
||||
<h3 class="font-semibold text-2xl text-gray-900">Workspace settings</h3>
|
||||
<small class="text-gray-600">Manage your workspaces.</small>
|
||||
|
||||
<div v-if="loading" class="w-full text-blue-500 text-center">
|
||||
<loader class="h-10 w-10 p-5"/>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-for="workspace in workspaces" :key="workspace.id"
|
||||
class="mt-4 p-4 flex group bg-white hover:bg-gray-50 dark:bg-notion-dark items-center"
|
||||
>
|
||||
<div class="flex space-x-4 flex-grow items-center cursor-pointer" role="button" @click.prevent="switchWorkspace(workspace)">
|
||||
<img v-if="isUrl(workspace.icon)" :src="workspace.icon" :alt="workspace.name + ' icon'"
|
||||
class="rounded-full h-12 w-12"
|
||||
>
|
||||
<div v-else class="rounded-2xl bg-gray-100 h-12 w-12 text-2xl pt-2 text-center overflow-hidden"
|
||||
v-text="workspace.icon"
|
||||
/>
|
||||
<div class="space-y-4 py-1">
|
||||
<div class="font-bold truncate">{{workspace.name}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="workspaces.length > 1"
|
||||
class="block md:hidden group-hover:block text-red-500 p-2 rounded hover:bg-red-50" role="button"
|
||||
@click="deleteWorkspace(workspace)">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-y-4 flex-wrap-reverse">
|
||||
<div class="flex-grow">
|
||||
<h3 class="font-semibold text-2xl text-gray-900">
|
||||
Workspace settings
|
||||
</h3>
|
||||
<small class="text-gray-600">Manage your workspaces.</small>
|
||||
</div>
|
||||
<v-button :loading="loading" class="mt-4" @click="workspaceModal=true">
|
||||
<svg class="inline text-white mr-1 h-4 w-4" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.99996 1.16699V12.8337M1.16663 7.00033H12.8333" stroke="currentColor" stroke-width="1.67" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<v-button color="outline-blue" :loading="loading" @click="workspaceModal=true">
|
||||
<svg class="inline -mt-1 mr-1 h-4 w-4" viewBox="0 0 14 14" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M6.99996 1.16699V12.8337M1.16663 7.00033H12.8333" stroke="currentColor" stroke-width="1.67"
|
||||
stroke-linecap="round" stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
Create new workspace
|
||||
</v-button>
|
||||
</div>
|
||||
|
||||
<div v-if="loading" class="w-full text-blue-500 text-center">
|
||||
<loader class="h-10 w-10 p-5" />
|
||||
</div>
|
||||
<div v-else-if="workspace">
|
||||
<div class="mt-4 flex group bg-white items-center">
|
||||
<div class="flex space-x-4 flex-grow items-center">
|
||||
<img v-if="isUrl(workspace.icon)" :src="workspace.icon" :alt="workspace.name + ' icon'"
|
||||
class="rounded-full h-12 w-12"
|
||||
>
|
||||
<div v-else class="rounded-2xl bg-gray-100 h-12 w-12 text-2xl pt-2 text-center overflow-hidden"
|
||||
v-text="workspace.icon"
|
||||
/>
|
||||
<div class="space-y-4 py-1">
|
||||
<div class="font-bold truncate">
|
||||
{{ workspace.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="customDomainsEnabled">
|
||||
<text-area-input v-model="customDomains" name="custom_domain" class="mt-4" :required="false"
|
||||
:disabled="!workspace.is_pro"
|
||||
label="Workspace Custom Domains" wrapper-class="" placeholder="yourdomain.com - 1 per line"
|
||||
/>
|
||||
<p class="text-gray-500 text-sm">
|
||||
Read our <a href="#"
|
||||
@click.prevent="$crisp.push(['do', 'helpdesk:article:open', ['en', 'how-to-use-my-own-domain-9m77g7']])"
|
||||
>custom
|
||||
domain instructions</a> to learn how to use your own domain.
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<div class="flex flex-wrap justify-between gap-2 mt-4">
|
||||
<v-button v-if="customDomainsEnabled" class="w-full sm:w-auto" :loading="customDomainsLoading" @click="saveChanges">
|
||||
<svg class="w-4 h-4 text-white inline mr-1 -mt-1" viewBox="0 0 24 24" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M17 21V13H7V21M7 3V8H15M19 21H5C4.46957 21 3.96086 20.7893 3.58579 20.4142C3.21071 20.0391 3 19.5304 3 19V5C3 4.46957 3.21071 3.96086 3.58579 3.58579C3.96086 3.21071 4.46957 3 5 3H16L21 8V19C21 19.5304 20.7893 20.0391 20.4142 20.4142C20.0391 20.7893 19.5304 21 19 21Z"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
Save Domains
|
||||
</v-button>
|
||||
<v-button v-if="workspaces.length > 1" color="white" class="group w-full sm:w-auto" :loading="loading"
|
||||
@click="deleteWorkspace(workspace)"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 -mt-1 inline group-hover:text-red-700" fill="none"
|
||||
viewBox="0 0 24 24" stroke="currentColor"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||
/>
|
||||
</svg>
|
||||
Remove workspace
|
||||
</v-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Workspace modal -->
|
||||
<modal :show="workspaceModal" @close="workspaceModal=false" max-width="lg">
|
||||
<modal :show="workspaceModal" max-width="lg" @close="workspaceModal=false">
|
||||
<template #icon>
|
||||
<svg class="w-8 h-8" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M12 8V16M8 12H16M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12Z"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
<template #title>
|
||||
@@ -62,25 +104,28 @@
|
||||
</div>
|
||||
|
||||
<div class="w-full mt-6">
|
||||
<v-button :loading="form.busy" class="w-full my-3">Save</v-button>
|
||||
<v-button :loading="form.busy" class="w-full my-3">
|
||||
Save
|
||||
</v-button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</modal>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Form from 'vform'
|
||||
import {mapActions, mapState} from 'vuex'
|
||||
import { mapActions, mapState } from 'vuex'
|
||||
import SeoMeta from '../../mixins/seo-meta.js'
|
||||
import TextAreaInput from '../../components/forms/TextAreaInput.vue'
|
||||
import axios from 'axios'
|
||||
import * as domain from 'domain'
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
scrollToTop: false,
|
||||
components: { TextAreaInput },
|
||||
mixins: [SeoMeta],
|
||||
scrollToTop: false,
|
||||
|
||||
data: () => ({
|
||||
metaTitle: 'Workspaces',
|
||||
@@ -88,37 +133,65 @@ export default {
|
||||
name: '',
|
||||
emoji: ''
|
||||
}),
|
||||
workspaceModal: false
|
||||
workspaceModal: false,
|
||||
customDomains: '',
|
||||
customDomainsLoading: false
|
||||
}),
|
||||
|
||||
mounted() {
|
||||
mounted () {
|
||||
this.loadWorkspaces()
|
||||
this.initCustomDomains()
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState({
|
||||
workspaces: state => state['open/workspaces'].content,
|
||||
loading: state => state['open/workspaces'].loading
|
||||
})
|
||||
}),
|
||||
workspace () {
|
||||
return this.$store.getters['open/workspaces/getCurrent']()
|
||||
},
|
||||
customDomainsEnabled () {
|
||||
return window.config.custom_domains_enabled
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions({
|
||||
loadWorkspaces: 'open/workspaces/loadIfEmpty'
|
||||
}),
|
||||
switchWorkspace(workspace) {
|
||||
this.$store.commit('open/workspaces/setCurrentId', workspace.id)
|
||||
this.$router.push({name: 'home'})
|
||||
this.$store.dispatch('open/forms/load', workspace.id)
|
||||
saveChanges () {
|
||||
if (this.customDomainsLoading) return
|
||||
this.customDomainsLoading = true
|
||||
// Update the workspace custom domain
|
||||
axios.put('/api/open/workspaces/' + this.workspace.id + '/custom-domains', {
|
||||
custom_domains: this.customDomains.split('\n')
|
||||
.map(domain => domain.trim())
|
||||
.filter(domain => domain && domain.length > 0)
|
||||
}).then(() => {
|
||||
this.alertSuccess('Custom domains saved.')
|
||||
}).catch((error) => {
|
||||
this.alertError('Failed to update custom domains: ' + error.response.data.message)
|
||||
}).finally(() => {
|
||||
this.customDomainsLoading = false
|
||||
})
|
||||
},
|
||||
deleteWorkspace(workspace) {
|
||||
initCustomDomains () {
|
||||
if (!this.workspace) return
|
||||
this.customDomains = this.workspace.custom_domains.join('\n')
|
||||
},
|
||||
deleteWorkspace (workspace) {
|
||||
if (this.workspaces.length <= 1) {
|
||||
this.alertError('You cannot delete your only workspace.')
|
||||
return
|
||||
}
|
||||
this.alertConfirm('Do you really want to delete this workspace? All forms created in this workspace will be removed.', () => {
|
||||
this.$store.dispatch('open/workspaces/delete', workspace.id).then(() => {
|
||||
this.alertSuccess('Workspace successfully removed.')
|
||||
})
|
||||
})
|
||||
},
|
||||
isUrl(str) {
|
||||
isUrl (str) {
|
||||
const pattern = new RegExp('^(https?:\\/\\/)?' + // protocol
|
||||
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name
|
||||
'((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address
|
||||
@@ -127,11 +200,17 @@ export default {
|
||||
'(\\#[-a-z\\d_]*)?$', 'i') // fragment locator
|
||||
return !!pattern.test(str)
|
||||
},
|
||||
async createWorkspace() {
|
||||
const {data} = await this.form.post('/api/open/workspaces/create')
|
||||
async createWorkspace () {
|
||||
const { data } = await this.form.post('/api/open/workspaces/create')
|
||||
this.$store.dispatch('open/workspaces/load')
|
||||
this.workspaceModal = false
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
workspace () {
|
||||
this.initCustomDomains()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
8
resources/js/router/index.js
vendored
8
resources/js/router/index.js
vendored
@@ -3,14 +3,14 @@ import store from '~/store'
|
||||
import Meta from 'vue-meta'
|
||||
import routes from './routes'
|
||||
import Router from 'vue-router'
|
||||
import {sync} from 'vuex-router-sync'
|
||||
import { sync } from 'vuex-router-sync'
|
||||
import * as Sentry from '@sentry/vue'
|
||||
|
||||
Vue.use(Meta)
|
||||
Vue.use(Router)
|
||||
|
||||
// The middleware for every page of the application.
|
||||
const globalMiddleware = ['locale', 'check-auth', 'notion-connection']
|
||||
const globalMiddleware = ['locale', 'check-auth', 'custom-domains']
|
||||
|
||||
// Load middleware modules dynamically.
|
||||
const requireContext = import.meta.glob('../middleware/**/*.js', { eager: true })
|
||||
@@ -253,7 +253,7 @@ function resolveMiddleware (requireContext) {
|
||||
.map(file =>
|
||||
[file.match(/[^/]*(?=\.[^.]*$)/)[0], requireContext[file]]
|
||||
).forEach(([name, middleware]) => {
|
||||
middlewares[name] = middleware.default || middleware
|
||||
})
|
||||
middlewares[name] = middleware.default || middleware
|
||||
})
|
||||
return middlewares
|
||||
}
|
||||
|
||||
@@ -61,6 +61,10 @@ export const mutations = {
|
||||
},
|
||||
remove (state, itemId) {
|
||||
state.content = state.content.filter((val) => val.id !== itemId)
|
||||
if (state.currentId === itemId) {
|
||||
state.currentId = state.content.length > 0 ? state.content[0].id : null
|
||||
localStorage.setItem(localStorageCurrentWorkspaceKey, state.currentId)
|
||||
}
|
||||
},
|
||||
startLoading () {
|
||||
state.loading = true
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
'crisp_website_id' => config('services.crisp_website_id'),
|
||||
'ai_features_enabled' => !is_null(config('services.openai.api_key')),
|
||||
's3_enabled' => config('filesystems.default') === 's3',
|
||||
'paid_plans_enabled' => !is_null(config('cashier.key'))
|
||||
'paid_plans_enabled' => !is_null(config('cashier.key')),
|
||||
'custom_domains_enabled' => config('custom-domains.enabled'),
|
||||
];
|
||||
@endphp
|
||||
<!DOCTYPE html>
|
||||
|
||||
Reference in New Issue
Block a user