Merge branch 'main' of https://github.com/JhumanJ/OpnForm
This commit is contained in:
commit
acb28548b8
|
@ -17,7 +17,7 @@ class TemplateController extends Controller
|
|||
|
||||
public function create(CreateTemplateRequest $request)
|
||||
{
|
||||
$this->middleware('admin');
|
||||
$this->authorize('create', Template::class);
|
||||
|
||||
// Create template
|
||||
$template = $request->getTemplate();
|
||||
|
@ -28,5 +28,4 @@ class TemplateController extends Controller
|
|||
'template_id' => $template->id
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -58,7 +58,8 @@ class FormResource extends JsonResource
|
|||
'is_closed' => $this->is_closed,
|
||||
'is_password_protected' => false,
|
||||
'has_password' => $this->has_password,
|
||||
'max_number_of_submissions_reached' => $this->max_number_of_submissions_reached
|
||||
'max_number_of_submissions_reached' => $this->max_number_of_submissions_reached,
|
||||
'form_pending_submission_key' => $this->form_pending_submission_key
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ class UserResource extends JsonResource
|
|||
'is_subscribed' => $this->is_subscribed,
|
||||
'has_enterprise_subscription' => $this->has_enterprise_subscription,
|
||||
'admin' => $this->admin,
|
||||
'template_editor' => $this->template_editor,
|
||||
'has_customer_id' => $this->has_customer_id,
|
||||
'has_forms' => $this->has_forms,
|
||||
] : [];
|
||||
|
|
|
@ -166,6 +166,14 @@ class Form extends Model
|
|||
return ($this->closes_at && now()->gt($this->closes_at));
|
||||
}
|
||||
|
||||
public function getFormPendingSubmissionKeyAttribute()
|
||||
{
|
||||
if ($this->updated_at?->timestamp) {
|
||||
return "openform-" . $this->id . "-pending-submission-" . substr($this->updated_at?->timestamp, -6);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getMaxNumberOfSubmissionsReachedAttribute()
|
||||
{
|
||||
return ($this->max_submissions_count && $this->max_submissions_count <= $this->submissions_count);
|
||||
|
|
|
@ -99,6 +99,11 @@ class User extends Authenticatable implements JWTSubject //, MustVerifyEmail
|
|||
return in_array($this->email, config('services.admin_emails'));
|
||||
}
|
||||
|
||||
public function getTemplateEditorAttribute()
|
||||
{
|
||||
return $this->admin || in_array($this->email, config('services.template_editor_emails'));
|
||||
}
|
||||
|
||||
/**
|
||||
* =================================
|
||||
* Helper Related
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\Template;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class TemplatePolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
/**
|
||||
* Determine whether the user can create models.
|
||||
*
|
||||
* @param \App\Models\User $user
|
||||
* @return \Illuminate\Auth\Access\Response|bool
|
||||
*/
|
||||
public function create(User $user)
|
||||
{
|
||||
return $user->template_editor;
|
||||
}
|
||||
}
|
|
@ -4,10 +4,12 @@ namespace App\Providers;
|
|||
|
||||
use App\Models\Forms\Form;
|
||||
use App\Models\Integration\FormZapierWebhook;
|
||||
use App\Models\Template;
|
||||
use App\Models\Workspace;
|
||||
use App\Models\User;
|
||||
use App\Policies\FormPolicy;
|
||||
use App\Policies\Integration\FormZapierWebhookPolicy;
|
||||
use App\Policies\TemplatePolicy;
|
||||
use App\Policies\WorkspacePolicy;
|
||||
use App\Policies\UserPolicy;
|
||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||
|
@ -22,7 +24,8 @@ class AuthServiceProvider extends ServiceProvider
|
|||
protected $policies = [
|
||||
Form::class => FormPolicy::class,
|
||||
Workspace::class => WorkspacePolicy::class,
|
||||
FormZapierWebhook::class => FormZapierWebhookPolicy::class
|
||||
FormZapierWebhook::class => FormZapierWebhookPolicy::class,
|
||||
Template::class => TemplatePolicy::class,
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
@ -29,7 +29,8 @@ class StorageFileNameParser
|
|||
public function getMovedFileName(): ?string
|
||||
{
|
||||
if ($this->fileName && $this->extension) {
|
||||
return substr($this->fileName,0,50).'_'.$this->uuid.'.'.$this->extension;
|
||||
$fileName = substr($this->fileName, 0, 50).'_'.$this->uuid.'.'.$this->extension;
|
||||
return mb_convert_encoding($fileName, 'UTF-8', 'UTF-8');
|
||||
}
|
||||
return $this->uuid;
|
||||
}
|
||||
|
|
|
@ -52,5 +52,6 @@ return [
|
|||
'amplitude_code' => env('AMPLITUDE_CODE'),
|
||||
'crisp_website_id' => env('CRISP_WEBSITE_ID'),
|
||||
|
||||
'admin_emails' => explode(",", env('ADMIN_EMAILS') ?? '')
|
||||
'admin_emails' => explode(",", env('ADMIN_EMAILS') ?? ''),
|
||||
'template_editor_emails' => explode(",", env('TEMPLATE_EDITOR_EMAILS') ?? '')
|
||||
];
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -37,7 +37,6 @@
|
|||
"vue-prism-editor": "^1.2.2",
|
||||
"vue-router": "^3.5.2",
|
||||
"vue-tailwind": "^2.5.0",
|
||||
"vue-tour": "^2.0.0",
|
||||
"vue2-editor": "^2.10.3",
|
||||
"vuedraggable": "^2.24.3",
|
||||
"vuex": "^3.6.2",
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
<server name="MAIL_FROM_NAME" value="NotionForms"/>
|
||||
<server name="QUEUE_CONNECTION" value="sync"/>
|
||||
<server name="SESSION_DRIVER" value="array"/>
|
||||
<server name="TELESCOPE_ENABLED" value="false"/>
|
||||
<server name="TEMPLATE_EDITOR_EMAILS" value="admin@opnform.com"/>
|
||||
<server name="JWT_SECRET" value="9K6whOetAFaokQgSIdbMQZuJuDV5uS2Y"/>
|
||||
</php>
|
||||
</phpunit>
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 63 KiB |
|
@ -6,8 +6,6 @@ import App from '~/components/App'
|
|||
import LoadScript from 'vue-plugin-load-script'
|
||||
import Base from './base'
|
||||
|
||||
import VueTour from 'vue-tour'
|
||||
|
||||
import '~/plugins'
|
||||
import '~/components'
|
||||
|
||||
|
@ -16,10 +14,6 @@ Vue.config.productionTip = false
|
|||
Vue.mixin(Base)
|
||||
Vue.use(LoadScript)
|
||||
|
||||
/* Vue Tour */
|
||||
require('vue-tour/dist/vue-tour.css')
|
||||
Vue.use(VueTour)
|
||||
|
||||
/* eslint-disable no-new */
|
||||
new Vue({
|
||||
i18n,
|
||||
|
|
|
@ -15,15 +15,17 @@
|
|||
<workspace-dropdown class="ml-6"/>
|
||||
</div>
|
||||
<div class="hidden md:block ml-auto relative">
|
||||
<router-link :to="{name:'integrations'}"
|
||||
<router-link :to="{name:'templates'}"
|
||||
class="text-sm text-gray-600 dark:text-white hover:text-gray-800 cursor-pointer mt-1 mr-8">
|
||||
Integrations
|
||||
Templates
|
||||
</router-link>
|
||||
<a href="#" class="hidden lg:inline text-sm text-gray-600 dark:text-white hover:text-gray-800 cursor-pointer mt-1 mr-8">
|
||||
Feature Requests
|
||||
</a>
|
||||
<a href="#" class="text-sm text-gray-600 dark:text-white hover:text-gray-800 cursor-pointer mt-1"
|
||||
@click.prevent="$getCrisp().push(['do', 'helpdesk:search'])"
|
||||
@click.prevent="$getCrisp().push(['do', 'helpdesk:search'])" v-if="hasCrisp"
|
||||
>
|
||||
Help
|
||||
</a>
|
||||
<a href="https://help.opnform.com/en/" class="text-sm text-gray-600 dark:text-white hover:text-gray-800 cursor-pointer mt-1"
|
||||
target="_blank" v-else
|
||||
>
|
||||
Help
|
||||
</a>
|
||||
|
@ -97,7 +99,7 @@
|
|||
{{ $t('login') }}
|
||||
</router-link>
|
||||
|
||||
<v-button size="small" :to="{ name: 'register' }" color="outline-blue" v-track.nav_create_form_click :arrow="true">
|
||||
<v-button size="small" :to="{ name: 'forms.create.guest' }" color="outline-blue" v-track.nav_create_form_click :arrow="true">
|
||||
Create a form
|
||||
</v-button>
|
||||
|
||||
|
@ -178,6 +180,9 @@ export default {
|
|||
}),
|
||||
userOnboarded() {
|
||||
return this.user && this.user.workspaces_count > 0
|
||||
},
|
||||
hasCrisp() {
|
||||
return window.config.crisp_website_id
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ export default {
|
|||
},
|
||||
|
||||
props: {
|
||||
dropdownClass: { type: String, default: 'origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white dark:bg-gray-800 ring-1 ring-black ring-opacity-5 z-50' }
|
||||
dropdownClass: { type: String, default: 'origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white dark:bg-gray-800 ring-1 ring-black ring-opacity-5 z-20' }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
|
|
|
@ -7,17 +7,17 @@
|
|||
</div>
|
||||
<modal :show="showPremiumModal" @close="showPremiumModal=false">
|
||||
<h2 class="text-nt-blue">
|
||||
OpenForm PRO
|
||||
OpnForm PRO
|
||||
</h2>
|
||||
<h4 v-if="user.is_subscribed && !user.has_enterprise_subscription" class="text-center mt-5">
|
||||
We're happy to have you as a Pro customer. If you're having any issue with OpenForm, or if you have a
|
||||
We're happy to have you as a Pro customer. If you're having any issue with OpnForm, or if you have a
|
||||
feature request, please <a href="mailto:contact@opnform.com">contact us</a>.
|
||||
<br><br>
|
||||
If you need to collaborate, or to work with multiple workspaces, or just larger file uploads, you can
|
||||
also upgrade our subscription to get an Enterprise subscription.
|
||||
</h4>
|
||||
<h4 v-if="user.is_subscribed && user.has_enterprise_subscription" class="text-center mt-5">
|
||||
We're happy to have you as an Enterprise customer. If you're having any issue with OpenForm, or if you have a
|
||||
We're happy to have you as an Enterprise customer. If you're having any issue with OpnForm, or if you have a
|
||||
feature request, please <a href="mailto:contact@opnform.com">contact us</a>.
|
||||
</h4>
|
||||
<p v-if="!user.is_subscribed" class="mt-4">
|
||||
|
@ -25,7 +25,7 @@
|
|||
class="bg-nt-blue text-white px-2 text-xs uppercase inline rounded-full font-semibold mx-1"
|
||||
>
|
||||
PRO
|
||||
</span> tag are available in the Pro plan of OpenForm. <b>You can play around and try all Pro features
|
||||
</span> tag are available in the Pro plan of OpnForm. <b>You can play around and try all Pro features
|
||||
within
|
||||
the form editor, but you can't use them in your real forms</b>. You can subscribe now to gain unlimited access
|
||||
to
|
||||
|
|
|
@ -91,7 +91,7 @@
|
|||
>
|
||||
<template #submit-btn="{submitForm}">
|
||||
<open-form-button :loading="loading" :theme="theme" :color="form.color" class="mt-2 px-8 mx-1"
|
||||
@click="submitForm"
|
||||
@click.prevent="submitForm"
|
||||
>
|
||||
{{ form.submit_button_text }}
|
||||
</open-form-button>
|
||||
|
@ -191,6 +191,9 @@ export default {
|
|||
workspace_id: this.form.workspace_id,
|
||||
form_id: this.form.id
|
||||
})
|
||||
|
||||
window.localStorage.removeItem(this.form.form_pending_submission_Key)
|
||||
|
||||
if (response.data.redirect && response.data.redirect_url) {
|
||||
window.location.href = response.data.redirect_url
|
||||
}
|
||||
|
|
|
@ -217,7 +217,15 @@ export default {
|
|||
handler () {
|
||||
this.formVersionId++
|
||||
}
|
||||
},
|
||||
dataForm: {
|
||||
deep: true,
|
||||
handler () {
|
||||
if(this.isPublicFormPage && this.form && this.dataFormValue){
|
||||
window.localStorage.setItem(this.form.form_pending_submission_Key, JSON.stringify(this.dataFormValue))
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
mounted () {
|
||||
|
@ -266,6 +274,14 @@ export default {
|
|||
}
|
||||
},
|
||||
initForm () {
|
||||
if (this.isPublicFormPage) {
|
||||
const pendingData = window.localStorage.getItem(this.form.form_pending_submission_Key)
|
||||
if(pendingData !== null && pendingData){
|
||||
this.dataForm = new Form(JSON.parse(pendingData))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const formData = clonedeep(this.dataForm ? this.dataForm.data() : {})
|
||||
let urlPrefill = null
|
||||
if (this.isPublicFormPage && this.form.is_pro) {
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
<template>
|
||||
<div v-if="form" id="form-editor" class="w-full flex flex-grow relative overflow-x-hidden">
|
||||
<!-- Form fields selection -->
|
||||
<v-tour name="tutorial" :steps="steps"/>
|
||||
<div class="w-full md:w-1/2 lg:w-2/5 border-r relative overflow-y-scroll md:max-w-sm flex-shrink-0">
|
||||
<div class="p-4 bg-blue-50 border-b text-nt-blue-dark md:hidden">
|
||||
We suggest you create this form on a device with a larger screen such as computed. That will allow you
|
||||
to preview your form changes.
|
||||
</div>
|
||||
<div class="p-4 pb-0">
|
||||
<a href="#" @click.prevent="$router.back()" class="flex text-blue mb-2 font-semibold text-sm">
|
||||
<a v-if="!isGuest" href="#" @click.prevent="$router.back()" class="flex text-blue mb-2 font-semibold text-sm">
|
||||
<svg class="w-3 h-3 text-blue mt-1 mr-1" viewBox="0 0 6 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5 9L1 5L5 1" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"
|
||||
stroke-linejoin="round"/>
|
||||
|
@ -88,6 +87,11 @@ export default {
|
|||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isGuest: {
|
||||
required: false,
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
|
@ -123,7 +127,7 @@ export default {
|
|||
{
|
||||
target: '#v-step-0',
|
||||
header: {
|
||||
title: 'Welcome to the OpenForm Editor!'
|
||||
title: 'Welcome to the OpnForm Editor!'
|
||||
},
|
||||
content: 'Discover <strong>your form Editor</strong>!'
|
||||
},
|
||||
|
@ -160,20 +164,16 @@ export default {
|
|||
|
||||
mounted() {
|
||||
this.$emit('mounted')
|
||||
this.startTour()
|
||||
},
|
||||
|
||||
methods: {
|
||||
startTour() {
|
||||
if (!this.user.has_forms) {
|
||||
this.$tours.tutorial.start()
|
||||
}
|
||||
},
|
||||
showValidationErrors() {
|
||||
this.showFormErrorModal = true
|
||||
},
|
||||
saveForm() {
|
||||
if (this.isEdit) {
|
||||
if(this.isGuest) {
|
||||
this.saveFormGuest()
|
||||
} else if (this.isEdit) {
|
||||
this.saveFormEdit()
|
||||
} else {
|
||||
this.saveFormCreate()
|
||||
|
@ -230,6 +230,9 @@ export default {
|
|||
}).finally(() => {
|
||||
this.updateFormLoading = false
|
||||
})
|
||||
},
|
||||
saveFormGuest() {
|
||||
this.$emit('openRegister')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -266,7 +266,7 @@ export default {
|
|||
];
|
||||
},
|
||||
init() {
|
||||
if (this.$route.name === 'forms.create') { // Set Default fields
|
||||
if (this.$route.name === 'forms.create' || this.$route.name === 'forms.create.guest') { // Set Default fields
|
||||
this.formFields = (this.form.properties.length > 0) ? clonedeep(this.form.properties) : this.getDefaultFields()
|
||||
} else {
|
||||
this.formFields = clonedeep(this.form.properties).map((field) => {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
</div>
|
||||
<dropdown v-else class="inline" dusk="nav-dropdown">
|
||||
<template #trigger="{toggle}">
|
||||
<v-button color="light-gray" class="mr-2" @click="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">
|
||||
<path
|
||||
|
@ -34,6 +34,26 @@
|
|||
</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}"
|
||||
>
|
||||
<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"/>
|
||||
</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"
|
||||
>
|
||||
<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"/>
|
||||
</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}"
|
||||
|
@ -47,20 +67,7 @@
|
|||
</svg>
|
||||
Duplicate form
|
||||
</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="alertConfirm('Do you really want to delete this form?',deleteForm)"
|
||||
>
|
||||
<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"
|
||||
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>
|
||||
<a href="#" v-if="user.admin"
|
||||
<a href="#" v-if="user.template_editor"
|
||||
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="showCreateTemplateModal=true"
|
||||
>
|
||||
|
@ -71,8 +78,45 @@
|
|||
</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"
|
||||
>
|
||||
<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"
|
||||
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">
|
||||
<template #icon>
|
||||
<svg class="w-10 h-10" 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"
|
||||
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>
|
||||
</template>
|
||||
<template #title>
|
||||
Delete form
|
||||
</template>
|
||||
<div class="p-3">
|
||||
<p>
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
</modal>
|
||||
|
||||
<create-template-modal :form="form" :show="showCreateTemplateModal" @close="showCreateTemplateModal=false"/>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -87,12 +131,14 @@ export default {
|
|||
name: 'ExtraMenu',
|
||||
components: { Dropdown, CreateTemplateModal },
|
||||
props: {
|
||||
form: { type: Object, required: true }
|
||||
form: { type: Object, required: true },
|
||||
isMainPage: { type: Boolean, required: false, default: false }
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
loadingDuplicate: false,
|
||||
loadingDelete: false,
|
||||
showDeleteFormModal: false,
|
||||
showCreateTemplateModal: false
|
||||
}),
|
||||
|
||||
|
@ -104,6 +150,14 @@ export default {
|
|||
},
|
||||
|
||||
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)
|
||||
},
|
||||
duplicateForm() {
|
||||
if (this.loadingDuplicate) return
|
||||
this.loadingDuplicate = true
|
||||
|
@ -127,4 +181,3 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
import Form from "vform";
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
initForm() {
|
||||
this.form = new Form({
|
||||
title: 'My Form',
|
||||
description: null,
|
||||
visibility: 'public',
|
||||
workspace_id: this.workspace?.id,
|
||||
properties: [],
|
||||
|
||||
notifies: false,
|
||||
slack_notifies: false,
|
||||
send_submission_confirmation: false,
|
||||
webhook_url: null,
|
||||
|
||||
// Customization
|
||||
theme: 'default',
|
||||
width: 'centered',
|
||||
dark_mode: 'auto',
|
||||
color: '#3B82F6',
|
||||
hide_title: false,
|
||||
no_branding: false,
|
||||
uppercase_labels: true,
|
||||
transparent_background: false,
|
||||
closes_at: null,
|
||||
closed_text: 'This form has now been closed by its owner and does not accept submissions anymore.',
|
||||
|
||||
// Submission
|
||||
submit_button_text: 'Submit',
|
||||
re_fillable: false,
|
||||
re_fill_button_text: 'Fill Again',
|
||||
submitted_text: 'Amazing, we saved your answers. Thank you for your time and have a great day!',
|
||||
notification_sender: 'OpnForm',
|
||||
notification_subject: 'We saved your answers',
|
||||
notification_body: 'Hello there 👋 <br>This is a confirmation that your submission was successfully saved.',
|
||||
notifications_include_submission: true,
|
||||
use_captcha: false,
|
||||
is_rating: false,
|
||||
rating_max_value: 5,
|
||||
max_submissions_count: null,
|
||||
max_submissions_reached_text: 'This form has now reached the maximum number of allowed submissions and is now closed.',
|
||||
|
||||
// Security & Privacy
|
||||
can_be_indexed: true
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
<template>
|
||||
<div>
|
||||
<forgot-password-modal :show="showForgotModal" @close="showForgotModal=false" />
|
||||
|
||||
<form @submit.prevent="login" @keydown="form.onKeydown($event)" class="mt-4">
|
||||
<!-- Email -->
|
||||
<text-input name="email" :form="form" :label="$t('email')" :required="true" placeholder="Your email address" />
|
||||
|
||||
<!-- Password -->
|
||||
<text-input native-type="password" placeholder="Your password"
|
||||
name="password" :form="form" :label="$t('password')" :required="true"
|
||||
/>
|
||||
|
||||
<!-- Remember Me -->
|
||||
<div class="relative flex items-center my-5">
|
||||
<v-checkbox v-model="remember" class="w-full md:w-1/2" name="remember" size="small">
|
||||
{{ $t('remember_me') }}
|
||||
</v-checkbox>
|
||||
|
||||
<div class="w-full md:w-1/2 text-right">
|
||||
<a href="#" @click.prevent="showForgotModal=true" class="text-xs hover:underline text-gray-500 sm:text-sm hover:text-gray-700">
|
||||
Forgot your password?
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<v-button dusk="btn_login" :loading="form.busy">Log in to continue</v-button>
|
||||
|
||||
<p class="text-gray-500 mt-4">
|
||||
Don't have an account?
|
||||
<a href="#" v-if="isQuick" @click.prevent="$emit('openRegister')" class="font-semibold ml-1">Sign Up</a>
|
||||
<router-link v-else :to="{name:'register'}" class="font-semibold ml-1">Sign Up</router-link>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Form from 'vform'
|
||||
import Cookies from 'js-cookie'
|
||||
import OpenFormFooter from '../../../components/pages/OpenFormFooter'
|
||||
import Testimonials from '../../../components/pages/welcome/Testimonials'
|
||||
import ForgotPasswordModal from '../ForgotPasswordModal'
|
||||
|
||||
export default {
|
||||
name: 'LoginForm',
|
||||
components: {
|
||||
OpenFormFooter,
|
||||
Testimonials,
|
||||
ForgotPasswordModal
|
||||
},
|
||||
props: {
|
||||
isQuick: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
form: new Form({
|
||||
email: '',
|
||||
password: ''
|
||||
}),
|
||||
remember: false,
|
||||
showForgotModal: false
|
||||
}),
|
||||
|
||||
methods: {
|
||||
async login () {
|
||||
// Submit the form.
|
||||
const { data } = await this.form.post('/api/login')
|
||||
|
||||
// Save the token.
|
||||
this.$store.dispatch('auth/saveToken', {
|
||||
token: data.token,
|
||||
remember: this.remember
|
||||
})
|
||||
|
||||
// Fetch the user.
|
||||
await this.$store.dispatch('auth/fetchUser')
|
||||
|
||||
// Redirect home.
|
||||
this.redirect()
|
||||
},
|
||||
|
||||
redirect () {
|
||||
if(this.isQuick){
|
||||
this.$emit('afterQuickLogin')
|
||||
return
|
||||
}
|
||||
|
||||
const intendedUrl = Cookies.get('intended_url')
|
||||
|
||||
if (intendedUrl) {
|
||||
Cookies.remove('intended_url')
|
||||
this.$router.push({ path: intendedUrl })
|
||||
} else {
|
||||
this.$router.push({ name: 'home' })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,80 @@
|
|||
<template>
|
||||
<div>
|
||||
<!-- Login modal -->
|
||||
<modal :show="showLoginModal" @close="showLoginModal=false" max-width="lg">
|
||||
<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"/>
|
||||
</svg>
|
||||
</template>
|
||||
<template #title>
|
||||
Login to OpnForm
|
||||
</template>
|
||||
<div class="px-4">
|
||||
<login-form :isQuick="true" @openRegister="openRegister" @afterQuickLogin="afterQuickLogin" />
|
||||
</div>
|
||||
</modal>
|
||||
|
||||
|
||||
<!-- Register modal -->
|
||||
<modal :show="showRegisterModal" @close="$emit('close')" max-width="lg">
|
||||
<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"/>
|
||||
</svg>
|
||||
</template>
|
||||
<template #title>
|
||||
Create an account
|
||||
</template>
|
||||
<div class="px-4">
|
||||
<register-form :isQuick="true" @openLogin="openLogin" @afterQuickLogin="afterQuickLogin" />
|
||||
</div>
|
||||
</modal>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LoginForm from './LoginForm'
|
||||
import RegisterForm from './RegisterForm'
|
||||
|
||||
export default {
|
||||
name: 'QuickRegister',
|
||||
components: {
|
||||
LoginForm,
|
||||
RegisterForm
|
||||
},
|
||||
props: {
|
||||
showRegisterModal: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
showLoginModal: false,
|
||||
}),
|
||||
|
||||
mounted() {
|
||||
},
|
||||
|
||||
methods: {
|
||||
openLogin(){
|
||||
this.showLoginModal = true
|
||||
this.$emit('close')
|
||||
},
|
||||
openRegister(){
|
||||
this.showLoginModal = false
|
||||
this.$emit('reopen')
|
||||
},
|
||||
afterQuickLogin(){
|
||||
this.showLoginModal = false
|
||||
this.$emit('afterLogin')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,128 @@
|
|||
<template>
|
||||
<div>
|
||||
<form @submit.prevent="register" @keydown="form.onKeydown($event)" class="mt-4">
|
||||
<!-- Name -->
|
||||
<text-input name="name" :form="form" :label="$t('name')" placeholder="Your name" :required="true" />
|
||||
|
||||
<!-- Email -->
|
||||
<text-input name="email" :form="form" :label="$t('email')" :required="true" placeholder="Your email address" />
|
||||
|
||||
<select-input name="hear_about_us" :options="hearAboutUsOptions" :form="form" placeholder="Select option"
|
||||
label="How did you hear about us?" :required="true"
|
||||
/>
|
||||
|
||||
<!-- Password -->
|
||||
<text-input native-type="password" placeholder="Enter password"
|
||||
name="password" :form="form" :label="$t('password')" :required="true"
|
||||
/>
|
||||
|
||||
<!-- Password Confirmation-->
|
||||
<text-input native-type="password" :form="form" :required="true" placeholder="Enter confirm password"
|
||||
name="password_confirmation" :label="$t('confirm_password')"
|
||||
/>
|
||||
|
||||
<checkbox-input :form="form" name="agree_terms" :required="true">
|
||||
<template #label>
|
||||
I agree with the <router-link :to="{name:'terms-conditions'}" target="_blank">Terms and conditions</router-link> and <router-link :to="{name:'privacy-policy'}" target="_blank">Privacy policy</router-link> of the website and I accept them.
|
||||
</template>
|
||||
</checkbox-input>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<v-button :loading="form.busy">Create an account</v-button>
|
||||
|
||||
<p class="text-gray-500 mt-4">
|
||||
Already have an account?
|
||||
<a href="#" v-if="isQuick" @click.prevent="$emit('openLogin')" class="font-semibold ml-1">Log In</a>
|
||||
<router-link v-else :to="{name:'login'}" class="font-semibold ml-1">Log In</router-link>
|
||||
</p>
|
||||
|
||||
<!-- GitHub Register Button -->
|
||||
<login-with-github />
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Form from 'vform'
|
||||
import LoginWithGithub from '~/components/LoginWithGithub'
|
||||
import SelectInput from '../../../components/forms/SelectInput'
|
||||
import { initCrisp } from '../../../middleware/check-auth'
|
||||
|
||||
export default {
|
||||
name: 'RegisterForm',
|
||||
components: {
|
||||
SelectInput,
|
||||
LoginWithGithub,
|
||||
},
|
||||
props: {
|
||||
isQuick: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
form: new Form({
|
||||
name: '',
|
||||
email: '',
|
||||
password: '',
|
||||
password_confirmation: '',
|
||||
agree_terms: false
|
||||
}),
|
||||
mustVerifyEmail: false
|
||||
}),
|
||||
|
||||
computed: {
|
||||
hearAboutUsOptions () {
|
||||
const options = [
|
||||
{ name: 'Facebook', value: 'facebook' },
|
||||
{ name: 'Twitter', value: 'twitter' },
|
||||
{ name: 'Reddit', value: 'reddit' },
|
||||
{ name: 'Github', value: 'github' },
|
||||
{ name: 'Search Engine (Google, DuckDuckGo...)', value: 'search_engine' },
|
||||
{ name: 'Friend or Colleague', value: 'friend_colleague' },
|
||||
{ name: 'Blog/Article', value: 'blog_article' }
|
||||
].map((value) => ({ value, sort: Math.random() }))
|
||||
.sort((a, b) => a.sort - b.sort)
|
||||
.map(({ value }) => value)
|
||||
options.push({ name: 'Other', value: 'other' })
|
||||
return options
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async register () {
|
||||
// Register the user.
|
||||
const { data } = await this.form.post('/api/register')
|
||||
|
||||
// Must verify email fist.
|
||||
if (data.status) {
|
||||
this.mustVerifyEmail = true
|
||||
} else {
|
||||
// Log in the user.
|
||||
const { data: { token } } = await this.form.post('/api/login')
|
||||
|
||||
// Save the token.
|
||||
this.$store.dispatch('auth/saveToken', { token })
|
||||
|
||||
// Update the user.
|
||||
await this.$store.dispatch('auth/updateUser', { user: data })
|
||||
|
||||
// Track event
|
||||
this.$logEvent('register', { source: this.form.hear_about_us })
|
||||
initCrisp(data).then(() => {
|
||||
this.$getCrisp().push(['set', 'session:event', [[['register', {}, 'blue']]]])
|
||||
})
|
||||
|
||||
// Redirect
|
||||
if(this.isQuick){
|
||||
this.$emit('afterQuickLogin')
|
||||
}else{
|
||||
this.$router.push({ name: 'forms.create' })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,7 +1,5 @@
|
|||
<template>
|
||||
<div>
|
||||
<forgot-password-modal :show="showForgotModal" @close="showForgotModal=false" />
|
||||
|
||||
<div class="flex mt-6 mb-10">
|
||||
<div class="w-full md:max-w-6xl mx-auto px-4 flex md:flex-row-reverse flex-wrap">
|
||||
<div class="w-full md:w-1/2 md:p-6">
|
||||
|
@ -11,35 +9,7 @@
|
|||
</h2>
|
||||
<small>Welcome back! Please enter your details.</small>
|
||||
|
||||
<form @submit.prevent="login" @keydown="form.onKeydown($event)" class="mt-4">
|
||||
<!-- Email -->
|
||||
<text-input name="email" :form="form" :label="$t('email')" :required="true" placeholder="Your email address" />
|
||||
|
||||
<!-- Password -->
|
||||
<text-input native-type="password" placeholder="Your password"
|
||||
name="password" :form="form" :label="$t('password')" :required="true"
|
||||
/>
|
||||
|
||||
<!-- Remember Me -->
|
||||
<div class="relative flex items-center my-5">
|
||||
<v-checkbox v-model="remember" class="w-full md:w-1/2" name="remember" size="small">
|
||||
{{ $t('remember_me') }}
|
||||
</v-checkbox>
|
||||
|
||||
<div class="w-full md:w-1/2 text-right">
|
||||
<a href="#" @click.prevent="showForgotModal=true" class="text-xs hover:underline text-gray-500 sm:text-sm hover:text-gray-700">
|
||||
Forgot your password?
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<v-button dusk="btn_login" :loading="form.busy">Log in to continue</v-button>
|
||||
|
||||
<p class="text-gray-500 mt-4">
|
||||
Don't have an account? <router-link :to="{name:'register'}" class="font-semibold ml-1">Sign Up</router-link>
|
||||
</p>
|
||||
</form>
|
||||
<login-form />
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full md:w-1/2 md:p-6 mt-8 md:mt-0 ">
|
||||
|
@ -86,17 +56,15 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import Form from 'vform'
|
||||
import Cookies from 'js-cookie'
|
||||
import OpenFormFooter from '../../components/pages/OpenFormFooter'
|
||||
import Testimonials from '../../components/pages/welcome/Testimonials'
|
||||
import ForgotPasswordModal from './ForgotPasswordModal'
|
||||
import LoginForm from './components/LoginForm'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
OpenFormFooter,
|
||||
Testimonials,
|
||||
ForgotPasswordModal
|
||||
LoginForm
|
||||
},
|
||||
|
||||
middleware: 'guest',
|
||||
|
@ -106,42 +74,11 @@ export default {
|
|||
},
|
||||
|
||||
data: () => ({
|
||||
form: new Form({
|
||||
email: '',
|
||||
password: ''
|
||||
}),
|
||||
remember: false,
|
||||
showForgotModal: false
|
||||
|
||||
}),
|
||||
|
||||
methods: {
|
||||
async login () {
|
||||
// Submit the form.
|
||||
const { data } = await this.form.post('/api/login')
|
||||
|
||||
// Save the token.
|
||||
this.$store.dispatch('auth/saveToken', {
|
||||
token: data.token,
|
||||
remember: this.remember
|
||||
})
|
||||
|
||||
// Fetch the user.
|
||||
await this.$store.dispatch('auth/fetchUser')
|
||||
|
||||
// Redirect home.
|
||||
this.redirect()
|
||||
},
|
||||
|
||||
redirect () {
|
||||
const intendedUrl = Cookies.get('intended_url')
|
||||
|
||||
if (intendedUrl) {
|
||||
Cookies.remove('intended_url')
|
||||
this.$router.push({ path: intendedUrl })
|
||||
} else {
|
||||
this.$router.push({ name: 'home' })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,56 +1,20 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="flex mt-6 mb-10">
|
||||
<div class="w-full md:max-w-6xl mx-auto px-4 flex md:flex-row-reverse flex-wrap">
|
||||
<div class="w-full md:w-1/2 md:p-6">
|
||||
<div class="w-full md:max-w-6xl mx-auto px-4 flex items-center md:flex-row-reverse flex-wrap">
|
||||
<div class="w-full lg:w-1/2 md:p-6">
|
||||
<div class="border rounded-md p-6 shadow-md sticky top-4">
|
||||
<h2 class="font-semibold text-2xl">
|
||||
Create an account
|
||||
</h2>
|
||||
<small>Sign up in less than 2 minutes.</small>
|
||||
|
||||
<form @submit.prevent="register" @keydown="form.onKeydown($event)" class="mt-4">
|
||||
<!-- Name -->
|
||||
<text-input name="name" :form="form" :label="$t('name')" placeholder="Your name" :required="true" />
|
||||
|
||||
<!-- Email -->
|
||||
<text-input name="email" :form="form" :label="$t('email')" :required="true" placeholder="Your email address" />
|
||||
|
||||
<select-input name="hear_about_us" :options="hearAboutUsOptions" :form="form" placeholder="Select option"
|
||||
label="How did you hear about us?" :required="true"
|
||||
/>
|
||||
|
||||
<!-- Password -->
|
||||
<text-input native-type="password" placeholder="Enter password"
|
||||
name="password" :form="form" :label="$t('password')" :required="true"
|
||||
/>
|
||||
|
||||
<!-- Password Confirmation-->
|
||||
<text-input native-type="password" :form="form" :required="true" placeholder="Enter confirm password"
|
||||
name="password_confirmation" :label="$t('confirm_password')"
|
||||
/>
|
||||
|
||||
<checkbox-input :form="form" name="agree_terms" :required="true">
|
||||
<template #label>
|
||||
I agree with the <router-link :to="{name:'terms-conditions'}" target="_blank">Terms and conditions</router-link> and <router-link :to="{name:'privacy-policy'}" target="_blank">Privacy policy</router-link> of the website and I accept them.
|
||||
</template>
|
||||
</checkbox-input>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<v-button :loading="form.busy">Create an account</v-button>
|
||||
|
||||
<p class="text-gray-500 mt-4">
|
||||
Already have an account? <router-link :to="{name:'login'}" class="font-semibold ml-1">Log In</router-link>
|
||||
</p>
|
||||
|
||||
<!-- GitHub Register Button -->
|
||||
<login-with-github />
|
||||
</form>
|
||||
<register-form />
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full md:w-1/2 md:p-6 mt-8 md:mt-0 ">
|
||||
<div class="w-full hidden lg:block lg:w-1/2 md:p-6 mt-8 md:mt-0 ">
|
||||
<h1 class="font-bold">
|
||||
Create beautiful Notion forms and share them anywhere
|
||||
Create beautiful forms and share them anywhere
|
||||
</h1>
|
||||
<p class="text-gray-900 my-4 text-lg">
|
||||
It takes seconds, you don't need to know how to code and it's free.
|
||||
|
@ -81,9 +45,9 @@
|
|||
Unlimited submissions
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-3 p-6">
|
||||
<testimonials />
|
||||
</div>
|
||||
<!-- <div class="mt-3 p-6">-->
|
||||
<!-- <testimonials />-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -92,19 +56,15 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import Form from 'vform'
|
||||
import LoginWithGithub from '~/components/LoginWithGithub'
|
||||
import SelectInput from '../../components/forms/SelectInput'
|
||||
import OpenFormFooter from '../../components/pages/OpenFormFooter'
|
||||
import { initCrisp } from '../../middleware/check-auth'
|
||||
import Testimonials from '../../components/pages/welcome/Testimonials'
|
||||
import RegisterForm from './components/RegisterForm'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Testimonials,
|
||||
SelectInput,
|
||||
LoginWithGithub,
|
||||
OpenFormFooter
|
||||
OpenFormFooter,
|
||||
RegisterForm
|
||||
},
|
||||
|
||||
middleware: 'guest',
|
||||
|
@ -114,62 +74,13 @@ export default {
|
|||
},
|
||||
|
||||
data: () => ({
|
||||
form: new Form({
|
||||
name: '',
|
||||
email: '',
|
||||
password: '',
|
||||
password_confirmation: '',
|
||||
agree_terms: false
|
||||
}),
|
||||
mustVerifyEmail: false
|
||||
|
||||
}),
|
||||
|
||||
computed: {
|
||||
hearAboutUsOptions () {
|
||||
const options = [
|
||||
{ name: 'Facebook', value: 'facebook' },
|
||||
{ name: 'Twitter', value: 'twitter' },
|
||||
{ name: 'Reddit', value: 'reddit' },
|
||||
{ name: 'Github', value: 'github' },
|
||||
{ name: 'Search Engine (Google, DuckDuckGo...)', value: 'search_engine' },
|
||||
{ name: 'Friend or Colleague', value: 'friend_colleague' },
|
||||
{ name: 'Blog/Article', value: 'blog_article' }
|
||||
].map((value) => ({ value, sort: Math.random() }))
|
||||
.sort((a, b) => a.sort - b.sort)
|
||||
.map(({ value }) => value)
|
||||
options.push({ name: 'Other', value: 'other' })
|
||||
return options
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async register () {
|
||||
// Register the user.
|
||||
const { data } = await this.form.post('/api/register')
|
||||
|
||||
// Must verify email fist.
|
||||
if (data.status) {
|
||||
this.mustVerifyEmail = true
|
||||
} else {
|
||||
// Log in the user.
|
||||
const { data: { token } } = await this.form.post('/api/login')
|
||||
|
||||
// Save the token.
|
||||
this.$store.dispatch('auth/saveToken', { token })
|
||||
|
||||
// Update the user.
|
||||
await this.$store.dispatch('auth/updateUser', { user: data })
|
||||
|
||||
// Track event
|
||||
this.$logEvent('register', { source: this.form.hear_about_us })
|
||||
initCrisp(data).then(() => {
|
||||
this.$getCrisp().push(['set', 'session:event', [[['register', {}, 'blue']]]])
|
||||
})
|
||||
|
||||
// Redirect home.
|
||||
this.$router.push({ name: 'forms.create' })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
<template>
|
||||
<div class="flex flex-wrap flex-col">
|
||||
<transition v-if="stateReady" name="fade" mode="out-in">
|
||||
<div key="2">
|
||||
<form-editor v-if="!workspacesLoading" ref="editor"
|
||||
class="w-full flex flex-grow"
|
||||
:style="{
|
||||
'max-height': editorMaxHeight + 'px'
|
||||
}" :error="error"
|
||||
:isGuest="isGuest"
|
||||
@mounted="onResize"
|
||||
@openRegister="openRegister"
|
||||
/>
|
||||
<div v-else class="text-center mt-4 py-6">
|
||||
<loader class="h-6 w-6 text-nt-blue mx-auto"/>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<quick-register :showRegisterModal="registerModal" @close="registerModal=false" @reopen="registerModal=true"
|
||||
@afterLogin="afterLogin"/>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import store from '~/store'
|
||||
import Form from 'vform'
|
||||
import {mapState, mapActions} from 'vuex'
|
||||
import QuickRegister from '../auth/components/QuickRegister'
|
||||
import initForm from "../../mixins/form_editor/initForm"
|
||||
|
||||
const FormEditor = () => import('../../components/open/forms/components/FormEditor')
|
||||
|
||||
const loadTemplates = function () {
|
||||
store.commit('open/templates/startLoading')
|
||||
store.dispatch('open/templates/loadIfEmpty').then(() => {
|
||||
store.commit('open/templates/stopLoading')
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'CreateFormGuest',
|
||||
mixins: [initForm],
|
||||
components: {
|
||||
FormEditor,
|
||||
QuickRegister
|
||||
},
|
||||
|
||||
middleware: 'guest',
|
||||
|
||||
metaInfo() {
|
||||
return {title: 'Create a new Form as Guest'}
|
||||
},
|
||||
|
||||
beforeRouteEnter(to, from, next) {
|
||||
loadTemplates()
|
||||
next()
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
stateReady: false,
|
||||
loading: false,
|
||||
error: '',
|
||||
editorMaxHeight: 500,
|
||||
registerModal: false,
|
||||
isGuest: true
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState({
|
||||
workspaces: state => state['open/workspaces'].content,
|
||||
workspacesLoading: state => state['open/workspaces'].loading,
|
||||
}),
|
||||
form: {
|
||||
get() {
|
||||
return this.$store.state['open/working_form'].content
|
||||
},
|
||||
/* We add a setter */
|
||||
set(value) {
|
||||
this.$store.commit('open/working_form/set', value)
|
||||
}
|
||||
},
|
||||
workspace() {
|
||||
return this.$store.getters['open/workspaces/getCurrent']()
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
workspace() {
|
||||
if (this.workspace) {
|
||||
this.form.workspace_id = this.workspace.id
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
// Set as guest user
|
||||
const guestWorkspace = {
|
||||
id: null,
|
||||
name: "Guest Workspace",
|
||||
is_enterprise: false,
|
||||
is_pro: false
|
||||
}
|
||||
this.$store.commit('open/workspaces/set', [guestWorkspace])
|
||||
this.$store.commit('open/workspaces/setCurrentId', guestWorkspace.id)
|
||||
|
||||
this.initForm()
|
||||
if (this.$route.query.template !== undefined && this.$route.query.template) {
|
||||
const template = this.$store.getters['open/templates/getBySlug'](this.$route.query.template)
|
||||
if (template && template.structure) {
|
||||
this.form = new Form({...this.form.data(), ...template.structure})
|
||||
}
|
||||
}
|
||||
this.closeAlert()
|
||||
this.stateReady = true
|
||||
},
|
||||
|
||||
created() {
|
||||
window.addEventListener('resize', this.onResize)
|
||||
},
|
||||
destroyed() {
|
||||
window.removeEventListener('resize', this.onResize)
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions({
|
||||
loadWorkspaces: 'open/workspaces/load'
|
||||
}),
|
||||
/**
|
||||
* Compute max height of editor
|
||||
*/
|
||||
onResize() {
|
||||
if (this.$refs.editor) {
|
||||
this.editorMaxHeight = window.innerHeight - this.$refs.editor.$el.offsetTop
|
||||
}
|
||||
},
|
||||
openRegister() {
|
||||
this.registerModal = true
|
||||
},
|
||||
afterLogin() {
|
||||
this.registerModal = false
|
||||
this.isGuest = false
|
||||
this.loadWorkspaces()
|
||||
setTimeout(() => {
|
||||
this.$refs.editor.saveFormCreate()
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -21,6 +21,7 @@
|
|||
import store from '~/store'
|
||||
import Form from 'vform'
|
||||
import {mapState, mapActions} from 'vuex'
|
||||
import initForm from "../../mixins/form_editor/initForm";
|
||||
|
||||
const FormEditor = () => import('../../components/open/forms/components/FormEditor')
|
||||
|
||||
|
@ -33,6 +34,8 @@ const loadTemplates = function () {
|
|||
|
||||
export default {
|
||||
name: 'CreateForm',
|
||||
|
||||
mixins: [initForm],
|
||||
components: {
|
||||
FormEditor,
|
||||
},
|
||||
|
@ -41,7 +44,7 @@ export default {
|
|||
return {title: 'Create a new Form'}
|
||||
},
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
beforeRouteEnter(to, from, next) {
|
||||
loadTemplates()
|
||||
next()
|
||||
},
|
||||
|
@ -75,12 +78,6 @@ export default {
|
|||
workspace() {
|
||||
return this.$store.getters['open/workspaces/getCurrent']()
|
||||
},
|
||||
fromOnboarding() {
|
||||
return this.$route.params.from_onboarding
|
||||
},
|
||||
fbGroupLink() {
|
||||
return window.config.links.facebook_group
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
|
@ -95,15 +92,12 @@ export default {
|
|||
},
|
||||
|
||||
mounted() {
|
||||
if(this.$route.query.template !== undefined && this.$route.query.template){
|
||||
let template = this.$store.getters['open/templates/getBySlug'](this.$route.query.template)
|
||||
if(template && template.structure){
|
||||
this.form = new Form(template.structure)
|
||||
}else{
|
||||
this.initForm()
|
||||
if (this.$route.query.template !== undefined && this.$route.query.template) {
|
||||
const template = this.$store.getters['open/templates/getBySlug'](this.$route.query.template)
|
||||
if (template && template.structure) {
|
||||
this.form = new Form({...this.form.data(), ...template.structure})
|
||||
}
|
||||
}else{
|
||||
this.initForm()
|
||||
}
|
||||
this.closeAlert()
|
||||
this.loadWorkspaces()
|
||||
|
@ -122,50 +116,6 @@ export default {
|
|||
...mapActions({
|
||||
loadWorkspaces: 'open/workspaces/loadIfEmpty'
|
||||
}),
|
||||
initForm() {
|
||||
this.form = new Form({
|
||||
title: 'My Form',
|
||||
description: null,
|
||||
visibility: 'public',
|
||||
workspace_id: this.workspace?.id,
|
||||
properties: [],
|
||||
|
||||
notifies: false,
|
||||
slack_notifies: false,
|
||||
send_submission_confirmation: false,
|
||||
webhook_url: null,
|
||||
|
||||
// Customization
|
||||
theme: 'default',
|
||||
width: 'centered',
|
||||
dark_mode: 'auto',
|
||||
color: '#3B82F6',
|
||||
hide_title: false,
|
||||
no_branding: false,
|
||||
uppercase_labels: true,
|
||||
transparent_background: false,
|
||||
closes_at: null,
|
||||
closed_text: 'This form has now been closed by its owner and does not accept submissions anymore.',
|
||||
|
||||
// Submission
|
||||
submit_button_text: 'Submit',
|
||||
re_fillable: false,
|
||||
re_fill_button_text: 'Fill Again',
|
||||
submitted_text: 'Amazing, we saved your answers. Thank you for your time and have a great day!',
|
||||
notification_sender: 'OpnForm',
|
||||
notification_subject: 'We saved your answers',
|
||||
notification_body: 'Hello there 👋 <br>This is a confirmation that your submission was successfully saved.',
|
||||
notifications_include_submission: true,
|
||||
use_captcha: false,
|
||||
is_rating: false,
|
||||
rating_max_value: 5,
|
||||
max_submissions_count: null,
|
||||
max_submissions_reached_text: 'This form has now reached the maximum number of allowed submissions and is now closed.',
|
||||
|
||||
// Security & Privacy
|
||||
can_be_indexed: true
|
||||
})
|
||||
},
|
||||
/**
|
||||
* Compute max height of editor
|
||||
*/
|
||||
|
|
|
@ -1,21 +1,27 @@
|
|||
<template>
|
||||
<div class="flex flex-col min-h-full mt-6">
|
||||
<div class="w-full flex-grow md:w-3/5 lg:w-1/2 md:mx-auto md:max-w-2xl px-4">
|
||||
<div>
|
||||
<div class="flex flex-wrap items-center mt-6 mb-4">
|
||||
<h2 class="text-nt-blue text-3xl font-bold flex-grow">
|
||||
<div class="bg-white">
|
||||
<div class="flex bg-gray-50 pb-5">
|
||||
<div class="w-full md:w-4/5 lg:w-3/5 md:mx-auto md:max-w-4xl p-4">
|
||||
<div class="pt-4 pb-0">
|
||||
<div class="flex">
|
||||
<h2 class="flex-grow text-gray-900">
|
||||
Your Forms
|
||||
</h2>
|
||||
<v-button v-track.create_form_click class="mt-4 sm:mt-0" :to="{name:'forms.create'}" @click="showCreateFormModal=true">
|
||||
<v-button v-track.create_form_click :to="{name:'forms.create'}">
|
||||
<svg class="w-4 h-4 text-white inline mr-1 -mt-1" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.99996 1.1665V12.8332M1.16663 6.99984H12.8333" stroke="currentColor" stroke-width="1.67" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
Create a new form
|
||||
</v-button>
|
||||
</div>
|
||||
|
||||
<p v-if="!formsLoading && enrichedForms.length === 0 && !isFilteringForms">
|
||||
You don't have any form yet.
|
||||
</p>
|
||||
<div v-else-if="forms.length > 0" class="mb-10">
|
||||
<text-input v-if="forms.length > 5" class="mb-6" :form="searchForm" name="search" label="Search a form"
|
||||
<small class="flex text-gray-500">Manage your forms and submissions.</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex bg-white">
|
||||
<div class="w-full md:w-4/5 lg:w-3/5 md:mx-auto md:max-w-4xl px-4">
|
||||
<div class="mt-8 pb-0">
|
||||
<text-input v-if="forms.length > 0" class="mb-6" :form="searchForm" name="search" label="Search a form"
|
||||
placeholder="Name of form to search"
|
||||
/>
|
||||
<div v-if="allTags.length > 0" class="mb-6">
|
||||
|
@ -27,18 +33,36 @@
|
|||
{{ tag }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="enrichedForms && enrichedForms.length" class="border border border-gray-300 dark:bg-notion-dark-light rounded-md w-full">
|
||||
<div v-for="(form, index) in enrichedForms" :key="form.id"
|
||||
class="p-4 w-full mx-auto border-gray-300 hover:bg-blue-100 dark:hover:bg-blue-900 transition-colors cursor-pointer relative"
|
||||
:class="{'border-t':index!==0, 'bg-gray-50 dark:bg-gray-400':form.visibility=='draft'}"
|
||||
<div v-if="!formsLoading && enrichedForms.length === 0" class="flex flex-wrap justify-center max-w-4xl">
|
||||
<img loading="lazy" class="w-56"
|
||||
:src="asset('img/pages/forms/search_notfound.png')" alt="search-not-found">
|
||||
<h3 class="w-full mt-4 text-center text-gray-900 font-semibold">No forms found</h3>
|
||||
<div v-if="isFilteringForms && enrichedForms.length === 0 && searchForm.search" class="mt-2 w-full text-center">
|
||||
Your search "{{searchForm.search}}" did not match any forms. Please try again.
|
||||
</div>
|
||||
<v-button v-if="forms.length === 0" class="mt-4" v-track.create_form_click :to="{name:'forms.create'}">
|
||||
<svg class="w-4 h-4 text-white inline mr-1 -mt-1" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.99996 1.1665V12.8332M1.16663 6.99984H12.8333" stroke="currentColor" stroke-width="1.67" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
Create a new form
|
||||
</v-button>
|
||||
</div>
|
||||
<div v-else-if="forms.length > 0" class="mb-10">
|
||||
<div v-if="enrichedForms && enrichedForms.length">
|
||||
<div v-for="(form) in enrichedForms" :key="form.id"
|
||||
class="mt-4 p-4 flex group bg-white hover:bg-gray-50 dark:bg-notion-dark items-center"
|
||||
>
|
||||
<div class="items-center space-x-4 truncate">
|
||||
<p class="truncate float-left">
|
||||
{{ form.title }} <span v-if="form.submissions_count" class="text-gray-400 ml-1">- {{
|
||||
form.submissions_count
|
||||
}} Submission{{ form.submissions_count > 0 ? 's' : '' }}</span>
|
||||
</p>
|
||||
<div v-if="form.tags && form.tags.length > 0" class="float-right hidden sm:block">
|
||||
<div class="flex-grow items-center truncate cursor-pointer" role="button" @click.prevent="viewForm(form)">
|
||||
<span class="font-semibold text-gray-900 dark:text-white">{{ form.title }}</span>
|
||||
<ul class="flex text-gray-500">
|
||||
<li class="pr-1">{{ form.views_count }} view{{ form.views_count > 0 ? 's' : '' }}</li>
|
||||
<li class="list-disc ml-6 pr-1">{{ form.submissions_count }}
|
||||
submission{{ form.submissions_count > 0 ? 's' : '' }}
|
||||
</li>
|
||||
<li class="list-disc ml-6 pr-1 text-blue-500" v-if="form.visibility=='draft'">Draft (not public)</li>
|
||||
<li class="list-disc ml-6">Edited {{ form.last_edited_human }}</li>
|
||||
</ul>
|
||||
<div v-if="form.tags && form.tags.length > 0" class="mt-1">
|
||||
<template v-for="(tag,i) in form.tags">
|
||||
<div v-if="i<1" :key="tag"
|
||||
class="bg-gray-300 dark:bg-gray-700 text-white px-2 py-1 mr-2 text-xs inline rounded-lg font-semibold"
|
||||
|
@ -53,22 +77,16 @@
|
|||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<router-link class="absolute inset-0"
|
||||
:to="{params:{slug:form.slug},name:'forms.show'}"
|
||||
/>
|
||||
<extra-menu :form="form" :isMainPage="true" />
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-gray-400 dark:text-gray-600 mt-2 px-4">
|
||||
You have {{ forms.length }} forms<template v-if="isFilteringForms">
|
||||
({{ enrichedForms.length }} matching search criteria)
|
||||
</template>.
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="formsLoading" class="text-center">
|
||||
<loader class="h-6 w-6 text-nt-blue mx-auto" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<open-form-footer class="mt-8 border-t" />
|
||||
</div>
|
||||
</template>
|
||||
|
@ -80,6 +98,7 @@ import Fuse from 'fuse.js'
|
|||
import Form from 'vform'
|
||||
import TextInput from '../components/forms/TextInput'
|
||||
import OpenFormFooter from '../components/pages/OpenFormFooter'
|
||||
import ExtraMenu from '../components/pages/forms/show/ExtraMenu'
|
||||
|
||||
const loadForms = function () {
|
||||
store.commit('open/forms/startLoading')
|
||||
|
@ -89,7 +108,7 @@ const loadForms = function () {
|
|||
}
|
||||
|
||||
export default {
|
||||
components: { OpenFormFooter, TextInput },
|
||||
components: { OpenFormFooter, TextInput, ExtraMenu },
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
loadForms()
|
||||
|
@ -127,6 +146,9 @@ export default {
|
|||
} else {
|
||||
this.selectedTags.splice(idx, 1)
|
||||
}
|
||||
},
|
||||
viewForm (form) {
|
||||
this.$router.push({name: 'forms.show', params: {slug: form.slug}})
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
<div class="flex flex-col min-h-full mt-6">
|
||||
<div class="w-full flex-grow md:w-4/5 lg:w-2/3 md:mx-auto md:max-w-4xl px-4">
|
||||
|
||||
<breadcrumb :path="breadcrumbs" />
|
||||
<breadcrumb :path="breadcrumbs"/>
|
||||
<div v-if="templatesLoading" class="text-center">
|
||||
<loader class="h-6 w-6 text-nt-blue mx-auto" />
|
||||
<loader class="h-6 w-6 text-nt-blue mx-auto"/>
|
||||
</div>
|
||||
<p v-else-if="template === null || !template">
|
||||
Template does not exist.
|
||||
|
@ -16,7 +16,9 @@
|
|||
</h2>
|
||||
</div>
|
||||
<div class="mb-10">
|
||||
<img :src="template.image_url" alt="" class="w-full shadow-xl rounded-lg my-5"/>
|
||||
<div class="w-full shadow-xl rounded-lg my-5 max-h-72 flex items-center justify-center overflow-hidden">
|
||||
<img :src="template.image_url" alt="Template cover image" class="w-full object-cover"/>
|
||||
</div>
|
||||
<div v-html="template.description"></div>
|
||||
<div class="mt-5 text-center">
|
||||
<v-button class="mt-4 sm:mt-0" :to="{path:'/forms/create?template='+template.slug}">
|
||||
|
@ -24,11 +26,13 @@
|
|||
</v-button>
|
||||
</div>
|
||||
|
||||
<h3 class="text-center text-gray-500">Template Preview</h3>
|
||||
<open-complete-form ref="open-complete-form" :form="form" :creating="true" class="my-5 p-4 bg-gray-50 rounded-lg"/>
|
||||
<h3 class="text-center text-gray-500 mt-6 mb-2">Template Preview</h3>
|
||||
<open-complete-form ref="open-complete-form" :form="form" :creating="true"
|
||||
class="mb-4 p-4 bg-gray-50 rounded-lg overflow-hidden"/>
|
||||
|
||||
<div v-if="template.questions.length > 0" id="questions">
|
||||
<h3 class="text-xl font-semibold mb-3">Frequently asked questions</h3>
|
||||
<div v-if="template.questions.length > 0" class="mt-5 pt-2">
|
||||
<div class="mt-5 pt-2">
|
||||
<div v-for="(ques,ques_key) in template.questions" :key="ques_key" class="my-3 border rounded-lg">
|
||||
<h5 class="border-b p-2">{{ ques.question }}</h5>
|
||||
<div class="p-2" v-html="ques.answer"></div>
|
||||
|
@ -37,45 +41,46 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<open-form-footer class="mt-8 border-t" />
|
||||
</div>
|
||||
</template>
|
||||
<open-form-footer class="mt-8 border-t"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import store from '~/store'
|
||||
import Form from 'vform'
|
||||
import { mapGetters, mapState } from 'vuex'
|
||||
import Fuse from 'fuse.js'
|
||||
import OpenFormFooter from '../../components/pages/OpenFormFooter'
|
||||
import OpenCompleteForm from '../../components/open/forms/OpenCompleteForm'
|
||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||
<script>
|
||||
import store from '~/store'
|
||||
import Form from 'vform'
|
||||
import {mapGetters, mapState} from 'vuex'
|
||||
import Fuse from 'fuse.js'
|
||||
import OpenFormFooter from '../../components/pages/OpenFormFooter'
|
||||
import OpenCompleteForm from '../../components/open/forms/OpenCompleteForm'
|
||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||
|
||||
const loadTemplates = function () {
|
||||
const loadTemplates = function () {
|
||||
store.commit('open/templates/startLoading')
|
||||
store.dispatch('open/templates/loadIfEmpty').then(() => {
|
||||
store.commit('open/templates/stopLoading')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
components: {Breadcrumb, OpenFormFooter, OpenCompleteForm },
|
||||
export default {
|
||||
components: {Breadcrumb, OpenFormFooter, OpenCompleteForm},
|
||||
|
||||
beforeRouteEnter (to, from, next) {
|
||||
beforeRouteEnter(to, from, next) {
|
||||
loadTemplates()
|
||||
next()
|
||||
},
|
||||
|
||||
props: {
|
||||
metaTitle: { type: String, default: 'Templates' },
|
||||
metaDescription: { type: String, default: 'Public templates for create form quickly!' }
|
||||
metaTitle: {type: String, default: 'Templates'},
|
||||
metaDescription: {type: String, default: 'Public templates for create form quickly!'}
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
}
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
|
||||
mounted () {},
|
||||
mounted() {
|
||||
},
|
||||
|
||||
methods: {},
|
||||
|
||||
|
@ -83,18 +88,18 @@
|
|||
...mapState({
|
||||
templatesLoading: state => state['open/templates'].loading
|
||||
}),
|
||||
breadcrumbs () {
|
||||
breadcrumbs() {
|
||||
if (!this.template) {
|
||||
return [{ route: { name: 'templates' }, label: 'Templates' }]
|
||||
return [{route: {name: 'templates'}, label: 'Templates'}]
|
||||
}
|
||||
return [{ route: { name: 'templates' }, label: 'Templates' }, { label: this.template.name }]
|
||||
return [{route: {name: 'templates'}, label: 'Templates'}, {label: this.template.name}]
|
||||
},
|
||||
template () {
|
||||
template() {
|
||||
return this.$store.getters['open/templates/getBySlug'](this.$route.params.slug)
|
||||
},
|
||||
form (){
|
||||
form() {
|
||||
return new Form(this.template.structure)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
>it's free</span>.
|
||||
</h3>
|
||||
<div class="mt-6 flex justify-center">
|
||||
<v-button class="mr-1" :to="{ name: 'register' }" :arrow="true">
|
||||
<v-button class="mr-1" :to="{ name: 'forms.create.guest' }" :arrow="true">
|
||||
Create a form for FREE
|
||||
</v-button>
|
||||
</div>
|
||||
|
@ -61,19 +61,19 @@
|
|||
|
||||
<more-features />
|
||||
|
||||
<div class="pt-20 pb-5 text-center bg-white dark:bg-notion-dark-light">
|
||||
<h3 class="font-semibold text-3xl">See what people are saying</h3>
|
||||
<p class="w-full mt-2 mb-8">
|
||||
These are the stories of our customers who have joined us with great pleasure when using this crazy feature.
|
||||
</p>
|
||||
<testimonials/>
|
||||
</div>
|
||||
<!-- <div class="pt-20 pb-5 text-center bg-white dark:bg-notion-dark-light">-->
|
||||
<!-- <h3 class="font-semibold text-3xl">See what people are saying</h3>-->
|
||||
<!-- <p class="w-full mt-2 mb-8">-->
|
||||
<!-- These are the stories of our customers who have joined us with great pleasure when using this crazy feature.-->
|
||||
<!-- </p>-->
|
||||
<!-- <testimonials/>-->
|
||||
<!-- </div>-->
|
||||
|
||||
<div class="w-full bg-blue-900 p-12 md:p-24 text-center">
|
||||
<h4 class="font-semibold text-3xl text-white">Take your forms to the next level</h4>
|
||||
<p class="text-gray-300 my-8">No trial. Generous, unlimited free plan.</p>
|
||||
<div class="mt-6 flex justify-center">
|
||||
<v-button :to="{ name: 'register' }" v-track.welcome_create_form_click :arrow="true" color="blue">
|
||||
<v-button :to="{ name: 'forms.create.guest' }" v-track.welcome_create_form_click :arrow="true" color="blue">
|
||||
Create a form for FREE
|
||||
</v-button>
|
||||
</div>
|
||||
|
|
|
@ -8,6 +8,7 @@ export default [
|
|||
|
||||
// Forms
|
||||
{ path: '/forms/create', name: 'forms.create', component: page('forms/create.vue') },
|
||||
{ path: '/forms/create/guest', name: 'forms.create.guest', component: page('forms/create-guest.vue') },
|
||||
{ path: '/forms/:slug/edit', name: 'forms.edit', component: page('forms/edit.vue') },
|
||||
{
|
||||
path: '/forms/:slug/show',
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
<?php
|
||||
|
||||
it('can create template', function () {
|
||||
$user = $this->actingAsUser();
|
||||
$user = $this->createUser([
|
||||
'email' => 'admin@opnform.com'
|
||||
]);
|
||||
$this->actingAsUser($user);
|
||||
|
||||
// Create Form
|
||||
$workspace = $this->createUserWorkspace($user);
|
||||
|
|
|
@ -135,9 +135,9 @@ trait TestHelpers
|
|||
return $form;
|
||||
}
|
||||
|
||||
public function createUser()
|
||||
public function createUser(array $data = [])
|
||||
{
|
||||
return \App\Models\User::factory()->create();
|
||||
return \App\Models\User::factory()->create($data);
|
||||
}
|
||||
|
||||
public function createProUser()
|
||||
|
|
|
@ -25,3 +25,12 @@ it('can parse filenames', function () {
|
|||
expect($parsedFilename->getMovedFileName())->toBeNull();
|
||||
|
||||
});
|
||||
|
||||
it('can handles non-utf characters', function () {
|
||||
$fileName = 'Образец_для_заполнения_85e16d7b-58ed-43bc-8dce-7d3ff7d69f41.png';
|
||||
$parsedFilename = \App\Service\Storage\StorageFileNameParser::parse($fileName);
|
||||
expect($parsedFilename->fileName)->toBe('Образец_для_заполнения');
|
||||
expect($parsedFilename->uuid)->toBe('85e16d7b-58ed-43bc-8dce-7d3ff7d69f41');
|
||||
expect($parsedFilename->extension)->toBe('png');
|
||||
expect($parsedFilename->getMovedFileName())->toBe($fileName);
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue