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)
|
public function create(CreateTemplateRequest $request)
|
||||||
{
|
{
|
||||||
$this->middleware('admin');
|
$this->authorize('create', Template::class);
|
||||||
|
|
||||||
// Create template
|
// Create template
|
||||||
$template = $request->getTemplate();
|
$template = $request->getTemplate();
|
||||||
|
@ -28,5 +28,4 @@ class TemplateController extends Controller
|
||||||
'template_id' => $template->id
|
'template_id' => $template->id
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,8 @@ class FormResource extends JsonResource
|
||||||
'is_closed' => $this->is_closed,
|
'is_closed' => $this->is_closed,
|
||||||
'is_password_protected' => false,
|
'is_password_protected' => false,
|
||||||
'has_password' => $this->has_password,
|
'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,
|
'is_subscribed' => $this->is_subscribed,
|
||||||
'has_enterprise_subscription' => $this->has_enterprise_subscription,
|
'has_enterprise_subscription' => $this->has_enterprise_subscription,
|
||||||
'admin' => $this->admin,
|
'admin' => $this->admin,
|
||||||
|
'template_editor' => $this->template_editor,
|
||||||
'has_customer_id' => $this->has_customer_id,
|
'has_customer_id' => $this->has_customer_id,
|
||||||
'has_forms' => $this->has_forms,
|
'has_forms' => $this->has_forms,
|
||||||
] : [];
|
] : [];
|
||||||
|
|
|
@ -166,6 +166,14 @@ class Form extends Model
|
||||||
return ($this->closes_at && now()->gt($this->closes_at));
|
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()
|
public function getMaxNumberOfSubmissionsReachedAttribute()
|
||||||
{
|
{
|
||||||
return ($this->max_submissions_count && $this->max_submissions_count <= $this->submissions_count);
|
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'));
|
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
|
* 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\Forms\Form;
|
||||||
use App\Models\Integration\FormZapierWebhook;
|
use App\Models\Integration\FormZapierWebhook;
|
||||||
|
use App\Models\Template;
|
||||||
use App\Models\Workspace;
|
use App\Models\Workspace;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Policies\FormPolicy;
|
use App\Policies\FormPolicy;
|
||||||
use App\Policies\Integration\FormZapierWebhookPolicy;
|
use App\Policies\Integration\FormZapierWebhookPolicy;
|
||||||
|
use App\Policies\TemplatePolicy;
|
||||||
use App\Policies\WorkspacePolicy;
|
use App\Policies\WorkspacePolicy;
|
||||||
use App\Policies\UserPolicy;
|
use App\Policies\UserPolicy;
|
||||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||||
|
@ -22,7 +24,8 @@ class AuthServiceProvider extends ServiceProvider
|
||||||
protected $policies = [
|
protected $policies = [
|
||||||
Form::class => FormPolicy::class,
|
Form::class => FormPolicy::class,
|
||||||
Workspace::class => WorkspacePolicy::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
|
public function getMovedFileName(): ?string
|
||||||
{
|
{
|
||||||
if ($this->fileName && $this->extension) {
|
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;
|
return $this->uuid;
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,5 +52,6 @@ return [
|
||||||
'amplitude_code' => env('AMPLITUDE_CODE'),
|
'amplitude_code' => env('AMPLITUDE_CODE'),
|
||||||
'crisp_website_id' => env('CRISP_WEBSITE_ID'),
|
'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-prism-editor": "^1.2.2",
|
||||||
"vue-router": "^3.5.2",
|
"vue-router": "^3.5.2",
|
||||||
"vue-tailwind": "^2.5.0",
|
"vue-tailwind": "^2.5.0",
|
||||||
"vue-tour": "^2.0.0",
|
|
||||||
"vue2-editor": "^2.10.3",
|
"vue2-editor": "^2.10.3",
|
||||||
"vuedraggable": "^2.24.3",
|
"vuedraggable": "^2.24.3",
|
||||||
"vuex": "^3.6.2",
|
"vuex": "^3.6.2",
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
<server name="MAIL_FROM_NAME" value="NotionForms"/>
|
<server name="MAIL_FROM_NAME" value="NotionForms"/>
|
||||||
<server name="QUEUE_CONNECTION" value="sync"/>
|
<server name="QUEUE_CONNECTION" value="sync"/>
|
||||||
<server name="SESSION_DRIVER" value="array"/>
|
<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"/>
|
<server name="JWT_SECRET" value="9K6whOetAFaokQgSIdbMQZuJuDV5uS2Y"/>
|
||||||
</php>
|
</php>
|
||||||
</phpunit>
|
</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 LoadScript from 'vue-plugin-load-script'
|
||||||
import Base from './base'
|
import Base from './base'
|
||||||
|
|
||||||
import VueTour from 'vue-tour'
|
|
||||||
|
|
||||||
import '~/plugins'
|
import '~/plugins'
|
||||||
import '~/components'
|
import '~/components'
|
||||||
|
|
||||||
|
@ -16,10 +14,6 @@ Vue.config.productionTip = false
|
||||||
Vue.mixin(Base)
|
Vue.mixin(Base)
|
||||||
Vue.use(LoadScript)
|
Vue.use(LoadScript)
|
||||||
|
|
||||||
/* Vue Tour */
|
|
||||||
require('vue-tour/dist/vue-tour.css')
|
|
||||||
Vue.use(VueTour)
|
|
||||||
|
|
||||||
/* eslint-disable no-new */
|
/* eslint-disable no-new */
|
||||||
new Vue({
|
new Vue({
|
||||||
i18n,
|
i18n,
|
||||||
|
|
|
@ -15,15 +15,17 @@
|
||||||
<workspace-dropdown class="ml-6"/>
|
<workspace-dropdown class="ml-6"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="hidden md:block ml-auto relative">
|
<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">
|
class="text-sm text-gray-600 dark:text-white hover:text-gray-800 cursor-pointer mt-1 mr-8">
|
||||||
Integrations
|
Templates
|
||||||
</router-link>
|
</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"
|
<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
|
Help
|
||||||
</a>
|
</a>
|
||||||
|
@ -97,7 +99,7 @@
|
||||||
{{ $t('login') }}
|
{{ $t('login') }}
|
||||||
</router-link>
|
</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
|
Create a form
|
||||||
</v-button>
|
</v-button>
|
||||||
|
|
||||||
|
@ -178,6 +180,9 @@ export default {
|
||||||
}),
|
}),
|
||||||
userOnboarded() {
|
userOnboarded() {
|
||||||
return this.user && this.user.workspaces_count > 0
|
return this.user && this.user.workspaces_count > 0
|
||||||
|
},
|
||||||
|
hasCrisp() {
|
||||||
|
return window.config.crisp_website_id
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
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 () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -7,17 +7,17 @@
|
||||||
</div>
|
</div>
|
||||||
<modal :show="showPremiumModal" @close="showPremiumModal=false">
|
<modal :show="showPremiumModal" @close="showPremiumModal=false">
|
||||||
<h2 class="text-nt-blue">
|
<h2 class="text-nt-blue">
|
||||||
OpenForm PRO
|
OpnForm PRO
|
||||||
</h2>
|
</h2>
|
||||||
<h4 v-if="user.is_subscribed && !user.has_enterprise_subscription" class="text-center mt-5">
|
<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>.
|
feature request, please <a href="mailto:contact@opnform.com">contact us</a>.
|
||||||
<br><br>
|
<br><br>
|
||||||
If you need to collaborate, or to work with multiple workspaces, or just larger file uploads, you can
|
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.
|
also upgrade our subscription to get an Enterprise subscription.
|
||||||
</h4>
|
</h4>
|
||||||
<h4 v-if="user.is_subscribed && user.has_enterprise_subscription" class="text-center mt-5">
|
<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>.
|
feature request, please <a href="mailto:contact@opnform.com">contact us</a>.
|
||||||
</h4>
|
</h4>
|
||||||
<p v-if="!user.is_subscribed" class="mt-4">
|
<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"
|
class="bg-nt-blue text-white px-2 text-xs uppercase inline rounded-full font-semibold mx-1"
|
||||||
>
|
>
|
||||||
PRO
|
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
|
within
|
||||||
the form editor, but you can't use them in your real forms</b>. You can subscribe now to gain unlimited access
|
the form editor, but you can't use them in your real forms</b>. You can subscribe now to gain unlimited access
|
||||||
to
|
to
|
||||||
|
|
|
@ -91,7 +91,7 @@
|
||||||
>
|
>
|
||||||
<template #submit-btn="{submitForm}">
|
<template #submit-btn="{submitForm}">
|
||||||
<open-form-button :loading="loading" :theme="theme" :color="form.color" class="mt-2 px-8 mx-1"
|
<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 }}
|
{{ form.submit_button_text }}
|
||||||
</open-form-button>
|
</open-form-button>
|
||||||
|
@ -191,6 +191,9 @@ export default {
|
||||||
workspace_id: this.form.workspace_id,
|
workspace_id: this.form.workspace_id,
|
||||||
form_id: this.form.id
|
form_id: this.form.id
|
||||||
})
|
})
|
||||||
|
|
||||||
|
window.localStorage.removeItem(this.form.form_pending_submission_Key)
|
||||||
|
|
||||||
if (response.data.redirect && response.data.redirect_url) {
|
if (response.data.redirect && response.data.redirect_url) {
|
||||||
window.location.href = response.data.redirect_url
|
window.location.href = response.data.redirect_url
|
||||||
}
|
}
|
||||||
|
|
|
@ -217,7 +217,15 @@ export default {
|
||||||
handler () {
|
handler () {
|
||||||
this.formVersionId++
|
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 () {
|
mounted () {
|
||||||
|
@ -266,6 +274,14 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
initForm () {
|
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() : {})
|
const formData = clonedeep(this.dataForm ? this.dataForm.data() : {})
|
||||||
let urlPrefill = null
|
let urlPrefill = null
|
||||||
if (this.isPublicFormPage && this.form.is_pro) {
|
if (this.isPublicFormPage && this.form.is_pro) {
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="form" id="form-editor" class="w-full flex flex-grow relative overflow-x-hidden">
|
<div v-if="form" id="form-editor" class="w-full flex flex-grow relative overflow-x-hidden">
|
||||||
<!-- Form fields selection -->
|
<!-- 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="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">
|
<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
|
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.
|
to preview your form changes.
|
||||||
</div>
|
</div>
|
||||||
<div class="p-4 pb-0">
|
<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">
|
<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"
|
<path d="M5 9L1 5L5 1" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"
|
||||||
stroke-linejoin="round"/>
|
stroke-linejoin="round"/>
|
||||||
|
@ -88,6 +87,11 @@ export default {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
isGuest: {
|
||||||
|
required: false,
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
|
@ -123,7 +127,7 @@ export default {
|
||||||
{
|
{
|
||||||
target: '#v-step-0',
|
target: '#v-step-0',
|
||||||
header: {
|
header: {
|
||||||
title: 'Welcome to the OpenForm Editor!'
|
title: 'Welcome to the OpnForm Editor!'
|
||||||
},
|
},
|
||||||
content: 'Discover <strong>your form Editor</strong>!'
|
content: 'Discover <strong>your form Editor</strong>!'
|
||||||
},
|
},
|
||||||
|
@ -160,20 +164,16 @@ export default {
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$emit('mounted')
|
this.$emit('mounted')
|
||||||
this.startTour()
|
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
startTour() {
|
|
||||||
if (!this.user.has_forms) {
|
|
||||||
this.$tours.tutorial.start()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
showValidationErrors() {
|
showValidationErrors() {
|
||||||
this.showFormErrorModal = true
|
this.showFormErrorModal = true
|
||||||
},
|
},
|
||||||
saveForm() {
|
saveForm() {
|
||||||
if (this.isEdit) {
|
if(this.isGuest) {
|
||||||
|
this.saveFormGuest()
|
||||||
|
} else if (this.isEdit) {
|
||||||
this.saveFormEdit()
|
this.saveFormEdit()
|
||||||
} else {
|
} else {
|
||||||
this.saveFormCreate()
|
this.saveFormCreate()
|
||||||
|
@ -230,6 +230,9 @@ export default {
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
this.updateFormLoading = false
|
this.updateFormLoading = false
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
saveFormGuest() {
|
||||||
|
this.$emit('openRegister')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -266,7 +266,7 @@ export default {
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
init() {
|
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()
|
this.formFields = (this.form.properties.length > 0) ? clonedeep(this.form.properties) : this.getDefaultFields()
|
||||||
} else {
|
} else {
|
||||||
this.formFields = clonedeep(this.form.properties).map((field) => {
|
this.formFields = clonedeep(this.form.properties).map((field) => {
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
</div>
|
</div>
|
||||||
<dropdown v-else class="inline" dusk="nav-dropdown">
|
<dropdown v-else class="inline" dusk="nav-dropdown">
|
||||||
<template #trigger="{toggle}">
|
<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"
|
<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
|
<path
|
||||||
|
@ -34,6 +34,26 @@
|
||||||
</svg>
|
</svg>
|
||||||
View form
|
View form
|
||||||
</router-link>
|
</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="#"
|
<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"
|
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}"
|
v-track.duplicate_form_click="{form_id:form.id, form_slug:form.slug}"
|
||||||
|
@ -47,10 +67,21 @@
|
||||||
</svg>
|
</svg>
|
||||||
Duplicate form
|
Duplicate form
|
||||||
</a>
|
</a>
|
||||||
|
<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"
|
||||||
|
>
|
||||||
|
<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">
|
||||||
|
<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"/>
|
||||||
|
</svg>
|
||||||
|
Create Template
|
||||||
|
</a>
|
||||||
<a href="#"
|
<a href="#"
|
||||||
class="block block px-4 py-2 text-md text-red-600 hover:bg-red-50 flex items-center"
|
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}"
|
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)"
|
@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"
|
<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="currentColor">
|
||||||
|
@ -60,23 +91,36 @@
|
||||||
</svg>
|
</svg>
|
||||||
Delete form
|
Delete form
|
||||||
</a>
|
</a>
|
||||||
<a href="#" v-if="user.admin"
|
|
||||||
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"
|
|
||||||
>
|
|
||||||
<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">
|
|
||||||
<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"/>
|
|
||||||
</svg>
|
|
||||||
Create Template
|
|
||||||
</a>
|
|
||||||
</dropdown>
|
</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"/>
|
<create-template-modal :form="form" :show="showCreateTemplateModal" @close="showCreateTemplateModal=false"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import {mapGetters, mapState} from 'vuex'
|
import {mapGetters, mapState} from 'vuex'
|
||||||
|
@ -87,12 +131,14 @@ export default {
|
||||||
name: 'ExtraMenu',
|
name: 'ExtraMenu',
|
||||||
components: { Dropdown, CreateTemplateModal },
|
components: { Dropdown, CreateTemplateModal },
|
||||||
props: {
|
props: {
|
||||||
form: { type: Object, required: true }
|
form: { type: Object, required: true },
|
||||||
|
isMainPage: { type: Boolean, required: false, default: false }
|
||||||
},
|
},
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
loadingDuplicate: false,
|
loadingDuplicate: false,
|
||||||
loadingDelete: false,
|
loadingDelete: false,
|
||||||
|
showDeleteFormModal: false,
|
||||||
showCreateTemplateModal: false
|
showCreateTemplateModal: false
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
@ -104,6 +150,14 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
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() {
|
duplicateForm() {
|
||||||
if (this.loadingDuplicate) return
|
if (this.loadingDuplicate) return
|
||||||
this.loadingDuplicate = true
|
this.loadingDuplicate = true
|
||||||
|
@ -127,4 +181,3 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</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>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<forgot-password-modal :show="showForgotModal" @close="showForgotModal=false" />
|
|
||||||
|
|
||||||
<div class="flex mt-6 mb-10">
|
<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: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:w-1/2 md:p-6">
|
||||||
|
@ -11,35 +9,7 @@
|
||||||
</h2>
|
</h2>
|
||||||
<small>Welcome back! Please enter your details.</small>
|
<small>Welcome back! Please enter your details.</small>
|
||||||
|
|
||||||
<form @submit.prevent="login" @keydown="form.onKeydown($event)" class="mt-4">
|
<login-form />
|
||||||
<!-- 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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full md:w-1/2 md:p-6 mt-8 md:mt-0 ">
|
<div class="w-full md:w-1/2 md:p-6 mt-8 md:mt-0 ">
|
||||||
|
@ -86,17 +56,15 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Form from 'vform'
|
|
||||||
import Cookies from 'js-cookie'
|
|
||||||
import OpenFormFooter from '../../components/pages/OpenFormFooter'
|
import OpenFormFooter from '../../components/pages/OpenFormFooter'
|
||||||
import Testimonials from '../../components/pages/welcome/Testimonials'
|
import Testimonials from '../../components/pages/welcome/Testimonials'
|
||||||
import ForgotPasswordModal from './ForgotPasswordModal'
|
import LoginForm from './components/LoginForm'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
OpenFormFooter,
|
OpenFormFooter,
|
||||||
Testimonials,
|
Testimonials,
|
||||||
ForgotPasswordModal
|
LoginForm
|
||||||
},
|
},
|
||||||
|
|
||||||
middleware: 'guest',
|
middleware: 'guest',
|
||||||
|
@ -106,42 +74,11 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
form: new Form({
|
|
||||||
email: '',
|
|
||||||
password: ''
|
|
||||||
}),
|
|
||||||
remember: false,
|
|
||||||
showForgotModal: false
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
methods: {
|
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>
|
</script>
|
||||||
|
|
|
@ -1,56 +1,20 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="flex mt-6 mb-10">
|
<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:max-w-6xl mx-auto px-4 flex items-center md:flex-row-reverse flex-wrap">
|
||||||
<div class="w-full md:w-1/2 md:p-6">
|
<div class="w-full lg:w-1/2 md:p-6">
|
||||||
<div class="border rounded-md p-6 shadow-md sticky top-4">
|
<div class="border rounded-md p-6 shadow-md sticky top-4">
|
||||||
<h2 class="font-semibold text-2xl">
|
<h2 class="font-semibold text-2xl">
|
||||||
Create an account
|
Create an account
|
||||||
</h2>
|
</h2>
|
||||||
<small>Sign up in less than 2 minutes.</small>
|
<small>Sign up in less than 2 minutes.</small>
|
||||||
|
|
||||||
<form @submit.prevent="register" @keydown="form.onKeydown($event)" class="mt-4">
|
<register-form />
|
||||||
<!-- 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>
|
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<h1 class="font-bold">
|
||||||
Create beautiful Notion forms and share them anywhere
|
Create beautiful forms and share them anywhere
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-gray-900 my-4 text-lg">
|
<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.
|
It takes seconds, you don't need to know how to code and it's free.
|
||||||
|
@ -81,9 +45,9 @@
|
||||||
Unlimited submissions
|
Unlimited submissions
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-3 p-6">
|
<!-- <div class="mt-3 p-6">-->
|
||||||
<testimonials />
|
<!-- <testimonials />-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -92,19 +56,15 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Form from 'vform'
|
|
||||||
import LoginWithGithub from '~/components/LoginWithGithub'
|
|
||||||
import SelectInput from '../../components/forms/SelectInput'
|
|
||||||
import OpenFormFooter from '../../components/pages/OpenFormFooter'
|
import OpenFormFooter from '../../components/pages/OpenFormFooter'
|
||||||
import { initCrisp } from '../../middleware/check-auth'
|
|
||||||
import Testimonials from '../../components/pages/welcome/Testimonials'
|
import Testimonials from '../../components/pages/welcome/Testimonials'
|
||||||
|
import RegisterForm from './components/RegisterForm'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Testimonials,
|
Testimonials,
|
||||||
SelectInput,
|
OpenFormFooter,
|
||||||
LoginWithGithub,
|
RegisterForm
|
||||||
OpenFormFooter
|
|
||||||
},
|
},
|
||||||
|
|
||||||
middleware: 'guest',
|
middleware: 'guest',
|
||||||
|
@ -114,62 +74,13 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
form: new Form({
|
|
||||||
name: '',
|
|
||||||
email: '',
|
|
||||||
password: '',
|
|
||||||
password_confirmation: '',
|
|
||||||
agree_terms: false
|
|
||||||
}),
|
|
||||||
mustVerifyEmail: false
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
computed: {
|
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: {
|
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>
|
</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,18 +21,21 @@
|
||||||
import store from '~/store'
|
import store from '~/store'
|
||||||
import Form from 'vform'
|
import Form from 'vform'
|
||||||
import {mapState, mapActions} from 'vuex'
|
import {mapState, mapActions} from 'vuex'
|
||||||
|
import initForm from "../../mixins/form_editor/initForm";
|
||||||
|
|
||||||
const FormEditor = () => import('../../components/open/forms/components/FormEditor')
|
const FormEditor = () => import('../../components/open/forms/components/FormEditor')
|
||||||
|
|
||||||
const loadTemplates = function () {
|
const loadTemplates = function () {
|
||||||
store.commit('open/templates/startLoading')
|
store.commit('open/templates/startLoading')
|
||||||
store.dispatch('open/templates/loadIfEmpty').then(() => {
|
store.dispatch('open/templates/loadIfEmpty').then(() => {
|
||||||
store.commit('open/templates/stopLoading')
|
store.commit('open/templates/stopLoading')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CreateForm',
|
name: 'CreateForm',
|
||||||
|
|
||||||
|
mixins: [initForm],
|
||||||
components: {
|
components: {
|
||||||
FormEditor,
|
FormEditor,
|
||||||
},
|
},
|
||||||
|
@ -41,7 +44,7 @@ export default {
|
||||||
return {title: 'Create a new Form'}
|
return {title: 'Create a new Form'}
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeRouteEnter (to, from, next) {
|
beforeRouteEnter(to, from, next) {
|
||||||
loadTemplates()
|
loadTemplates()
|
||||||
next()
|
next()
|
||||||
},
|
},
|
||||||
|
@ -75,12 +78,6 @@ export default {
|
||||||
workspace() {
|
workspace() {
|
||||||
return this.$store.getters['open/workspaces/getCurrent']()
|
return this.$store.getters['open/workspaces/getCurrent']()
|
||||||
},
|
},
|
||||||
fromOnboarding() {
|
|
||||||
return this.$route.params.from_onboarding
|
|
||||||
},
|
|
||||||
fbGroupLink() {
|
|
||||||
return window.config.links.facebook_group
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
|
@ -95,15 +92,12 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
if(this.$route.query.template !== undefined && this.$route.query.template){
|
this.initForm()
|
||||||
let template = this.$store.getters['open/templates/getBySlug'](this.$route.query.template)
|
if (this.$route.query.template !== undefined && this.$route.query.template) {
|
||||||
if(template && template.structure){
|
const template = this.$store.getters['open/templates/getBySlug'](this.$route.query.template)
|
||||||
this.form = new Form(template.structure)
|
if (template && template.structure) {
|
||||||
}else{
|
this.form = new Form({...this.form.data(), ...template.structure})
|
||||||
this.initForm()
|
|
||||||
}
|
}
|
||||||
}else{
|
|
||||||
this.initForm()
|
|
||||||
}
|
}
|
||||||
this.closeAlert()
|
this.closeAlert()
|
||||||
this.loadWorkspaces()
|
this.loadWorkspaces()
|
||||||
|
@ -122,50 +116,6 @@ export default {
|
||||||
...mapActions({
|
...mapActions({
|
||||||
loadWorkspaces: 'open/workspaces/loadIfEmpty'
|
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
|
* Compute max height of editor
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,71 +1,89 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col min-h-full mt-6">
|
<div class="bg-white">
|
||||||
<div class="w-full flex-grow md:w-3/5 lg:w-1/2 md:mx-auto md:max-w-2xl px-4">
|
<div class="flex bg-gray-50 pb-5">
|
||||||
<div>
|
<div class="w-full md:w-4/5 lg:w-3/5 md:mx-auto md:max-w-4xl p-4">
|
||||||
<div class="flex flex-wrap items-center mt-6 mb-4">
|
<div class="pt-4 pb-0">
|
||||||
<h2 class="text-nt-blue text-3xl font-bold flex-grow">
|
<div class="flex">
|
||||||
Your Forms
|
<h2 class="flex-grow text-gray-900">
|
||||||
</h2>
|
Your Forms
|
||||||
<v-button v-track.create_form_click class="mt-4 sm:mt-0" :to="{name:'forms.create'}" @click="showCreateFormModal=true">
|
</h2>
|
||||||
Create a new form
|
<v-button v-track.create_form_click :to="{name:'forms.create'}">
|
||||||
</v-button>
|
<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>
|
||||||
|
<small class="flex text-gray-500">Manage your forms and submissions.</small>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<p v-if="!formsLoading && enrichedForms.length === 0 && !isFilteringForms">
|
</div>
|
||||||
You don't have any form yet.
|
<div class="flex bg-white">
|
||||||
</p>
|
<div class="w-full md:w-4/5 lg:w-3/5 md:mx-auto md:max-w-4xl px-4">
|
||||||
<div v-else-if="forms.length > 0" class="mb-10">
|
<div class="mt-8 pb-0">
|
||||||
<text-input v-if="forms.length > 5" class="mb-6" :form="searchForm" name="search" label="Search a form"
|
<text-input v-if="forms.length > 0" class="mb-6" :form="searchForm" name="search" label="Search a form"
|
||||||
placeholder="Name of form to search"
|
placeholder="Name of form to search"
|
||||||
/>
|
/>
|
||||||
<div v-if="allTags.length > 0" class="mb-6">
|
<div v-if="allTags.length > 0" class="mb-6">
|
||||||
<div v-for="tag in allTags" :key="tag"
|
<div v-for="tag in allTags" :key="tag"
|
||||||
:class="['text-white p-2 text-xs inline rounded-lg font-semibold cursor-pointer mr-2',{'bg-gray-500 dark:bg-gray-400':selectedTags.includes(tag), 'bg-gray-300 dark:bg-gray-700':!selectedTags.includes(tag)}]"
|
:class="['text-white p-2 text-xs inline rounded-lg font-semibold cursor-pointer mr-2',{'bg-gray-500 dark:bg-gray-400':selectedTags.includes(tag), 'bg-gray-300 dark:bg-gray-700':!selectedTags.includes(tag)}]"
|
||||||
title="Click for filter by tag(s)"
|
title="Click for filter by tag(s)"
|
||||||
@click="onTagClick(tag)"
|
@click="onTagClick(tag)"
|
||||||
>
|
>
|
||||||
{{ tag }}
|
{{ tag }}
|
||||||
</div>
|
</div>
|
||||||
</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-if="!formsLoading && enrichedForms.length === 0" class="flex flex-wrap justify-center max-w-4xl">
|
||||||
<div v-for="(form, index) in enrichedForms" :key="form.id"
|
<img loading="lazy" class="w-56"
|
||||||
class="p-4 w-full mx-auto border-gray-300 hover:bg-blue-100 dark:hover:bg-blue-900 transition-colors cursor-pointer relative"
|
:src="asset('img/pages/forms/search_notfound.png')" alt="search-not-found">
|
||||||
:class="{'border-t':index!==0, 'bg-gray-50 dark:bg-gray-400':form.visibility=='draft'}"
|
<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">
|
||||||
<div class="items-center space-x-4 truncate">
|
Your search "{{searchForm.search}}" did not match any forms. Please try again.
|
||||||
<p class="truncate float-left">
|
</div>
|
||||||
{{ form.title }} <span v-if="form.submissions_count" class="text-gray-400 ml-1">- {{
|
<v-button v-if="forms.length === 0" class="mt-4" v-track.create_form_click :to="{name:'forms.create'}">
|
||||||
form.submissions_count
|
<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">
|
||||||
}} Submission{{ form.submissions_count > 0 ? 's' : '' }}</span>
|
<path d="M6.99996 1.1665V12.8332M1.16663 6.99984H12.8333" stroke="currentColor" stroke-width="1.67" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
</p>
|
</svg>
|
||||||
<div v-if="form.tags && form.tags.length > 0" class="float-right hidden sm:block">
|
Create a new form
|
||||||
<template v-for="(tag,i) in form.tags">
|
</v-button>
|
||||||
<div v-if="i<1" :key="tag"
|
</div>
|
||||||
class="bg-gray-300 dark:bg-gray-700 text-white px-2 py-1 mr-2 text-xs inline rounded-lg font-semibold"
|
<div v-else-if="forms.length > 0" class="mb-10">
|
||||||
>
|
<div v-if="enrichedForms && enrichedForms.length">
|
||||||
{{ tag }}
|
<div v-for="(form) in enrichedForms" :key="form.id"
|
||||||
</div>
|
class="mt-4 p-4 flex group bg-white hover:bg-gray-50 dark:bg-notion-dark items-center"
|
||||||
<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"
|
<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>
|
||||||
{{ form.tags.length-1 }} more
|
<ul class="flex text-gray-500">
|
||||||
</div>
|
<li class="pr-1">{{ form.views_count }} view{{ form.views_count > 0 ? 's' : '' }}</li>
|
||||||
</template>
|
<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"
|
||||||
|
>
|
||||||
|
{{ tag }}
|
||||||
|
</div>
|
||||||
|
<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"
|
||||||
|
>
|
||||||
|
{{ form.tags.length-1 }} more
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<extra-menu :form="form" :isMainPage="true" />
|
||||||
</div>
|
</div>
|
||||||
<router-link class="absolute inset-0"
|
|
||||||
:to="{params:{slug:form.slug},name:'forms.show'}"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-gray-400 dark:text-gray-600 mt-2 px-4">
|
<div v-if="formsLoading" class="text-center">
|
||||||
You have {{ forms.length }} forms<template v-if="isFilteringForms">
|
<loader class="h-6 w-6 text-nt-blue mx-auto" />
|
||||||
({{ enrichedForms.length }} matching search criteria)
|
</div>
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
|
@ -80,6 +98,7 @@ import Fuse from 'fuse.js'
|
||||||
import Form from 'vform'
|
import Form from 'vform'
|
||||||
import TextInput from '../components/forms/TextInput'
|
import TextInput from '../components/forms/TextInput'
|
||||||
import OpenFormFooter from '../components/pages/OpenFormFooter'
|
import OpenFormFooter from '../components/pages/OpenFormFooter'
|
||||||
|
import ExtraMenu from '../components/pages/forms/show/ExtraMenu'
|
||||||
|
|
||||||
const loadForms = function () {
|
const loadForms = function () {
|
||||||
store.commit('open/forms/startLoading')
|
store.commit('open/forms/startLoading')
|
||||||
|
@ -89,7 +108,7 @@ const loadForms = function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { OpenFormFooter, TextInput },
|
components: { OpenFormFooter, TextInput, ExtraMenu },
|
||||||
|
|
||||||
beforeRouteEnter (to, from, next) {
|
beforeRouteEnter (to, from, next) {
|
||||||
loadForms()
|
loadForms()
|
||||||
|
@ -127,6 +146,9 @@ export default {
|
||||||
} else {
|
} else {
|
||||||
this.selectedTags.splice(idx, 1)
|
this.selectedTags.splice(idx, 1)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
viewForm (form) {
|
||||||
|
this.$router.push({name: 'forms.show', params: {slug: form.slug}})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -1,34 +1,38 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col min-h-full mt-6">
|
<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">
|
<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">
|
<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.
|
||||||
|
</p>
|
||||||
|
<div v-else>
|
||||||
|
<div class="flex flex-wrap items-center mt-6 mb-4">
|
||||||
|
<h2 class="text-nt-blue text-3xl font-bold flex-grow">
|
||||||
|
{{ template.name }}
|
||||||
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<p v-else-if="template === null || !template">
|
<div class="mb-10">
|
||||||
Template does not exist.
|
<div class="w-full shadow-xl rounded-lg my-5 max-h-72 flex items-center justify-center overflow-hidden">
|
||||||
</p>
|
<img :src="template.image_url" alt="Template cover image" class="w-full object-cover"/>
|
||||||
<div v-else>
|
</div>
|
||||||
<div class="flex flex-wrap items-center mt-6 mb-4">
|
<div v-html="template.description"></div>
|
||||||
<h2 class="text-nt-blue text-3xl font-bold flex-grow">
|
<div class="mt-5 text-center">
|
||||||
{{ template.name }}
|
<v-button class="mt-4 sm:mt-0" :to="{path:'/forms/create?template='+template.slug}">
|
||||||
</h2>
|
Use this template
|
||||||
|
</v-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-10">
|
|
||||||
<img :src="template.image_url" alt="" class="w-full shadow-xl rounded-lg my-5"/>
|
|
||||||
<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}">
|
|
||||||
Use this template
|
|
||||||
</v-button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3 class="text-center text-gray-500">Template Preview</h3>
|
<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="my-5 p-4 bg-gray-50 rounded-lg"/>
|
<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>
|
<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">
|
<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>
|
<h5 class="border-b p-2">{{ ques.question }}</h5>
|
||||||
<div class="p-2" v-html="ques.answer"></div>
|
<div class="p-2" v-html="ques.answer"></div>
|
||||||
|
@ -37,64 +41,65 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<open-form-footer class="mt-8 border-t" />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
<open-form-footer class="mt-8 border-t"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import store from '~/store'
|
import store from '~/store'
|
||||||
import Form from 'vform'
|
import Form from 'vform'
|
||||||
import { mapGetters, mapState } from 'vuex'
|
import {mapGetters, mapState} from 'vuex'
|
||||||
import Fuse from 'fuse.js'
|
import Fuse from 'fuse.js'
|
||||||
import OpenFormFooter from '../../components/pages/OpenFormFooter'
|
import OpenFormFooter from '../../components/pages/OpenFormFooter'
|
||||||
import OpenCompleteForm from '../../components/open/forms/OpenCompleteForm'
|
import OpenCompleteForm from '../../components/open/forms/OpenCompleteForm'
|
||||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||||
|
|
||||||
const loadTemplates = function () {
|
const loadTemplates = function () {
|
||||||
store.commit('open/templates/startLoading')
|
store.commit('open/templates/startLoading')
|
||||||
store.dispatch('open/templates/loadIfEmpty').then(() => {
|
store.dispatch('open/templates/loadIfEmpty').then(() => {
|
||||||
store.commit('open/templates/stopLoading')
|
store.commit('open/templates/stopLoading')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {Breadcrumb, OpenFormFooter, OpenCompleteForm },
|
components: {Breadcrumb, OpenFormFooter, OpenCompleteForm},
|
||||||
|
|
||||||
beforeRouteEnter (to, from, next) {
|
beforeRouteEnter(to, from, next) {
|
||||||
loadTemplates()
|
loadTemplates()
|
||||||
next()
|
next()
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
metaTitle: { type: String, default: 'Templates' },
|
metaTitle: {type: String, default: 'Templates'},
|
||||||
metaDescription: { type: String, default: 'Public templates for create form quickly!' }
|
metaDescription: {type: String, default: 'Public templates for create form quickly!'}
|
||||||
},
|
},
|
||||||
|
|
||||||
data () {
|
data() {
|
||||||
return {
|
return {}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
templatesLoading: state => state['open/templates'].loading
|
||||||
|
}),
|
||||||
|
breadcrumbs() {
|
||||||
|
if (!this.template) {
|
||||||
|
return [{route: {name: 'templates'}, label: 'Templates'}]
|
||||||
}
|
}
|
||||||
|
return [{route: {name: 'templates'}, label: 'Templates'}, {label: this.template.name}]
|
||||||
},
|
},
|
||||||
|
template() {
|
||||||
mounted () {},
|
return this.$store.getters['open/templates/getBySlug'](this.$route.params.slug)
|
||||||
|
},
|
||||||
methods: {},
|
form() {
|
||||||
|
return new Form(this.template.structure)
|
||||||
computed: {
|
|
||||||
...mapState({
|
|
||||||
templatesLoading: state => state['open/templates'].loading
|
|
||||||
}),
|
|
||||||
breadcrumbs () {
|
|
||||||
if (!this.template) {
|
|
||||||
return [{ route: { name: 'templates' }, label: 'Templates' }]
|
|
||||||
}
|
|
||||||
return [{ route: { name: 'templates' }, label: 'Templates' }, { label: this.template.name }]
|
|
||||||
},
|
|
||||||
template () {
|
|
||||||
return this.$store.getters['open/templates/getBySlug'](this.$route.params.slug)
|
|
||||||
},
|
|
||||||
form (){
|
|
||||||
return new Form(this.template.structure)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
}
|
||||||
|
</script>
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
>it's free</span>.
|
>it's free</span>.
|
||||||
</h3>
|
</h3>
|
||||||
<div class="mt-6 flex justify-center">
|
<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
|
Create a form for FREE
|
||||||
</v-button>
|
</v-button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -61,19 +61,19 @@
|
||||||
|
|
||||||
<more-features />
|
<more-features />
|
||||||
|
|
||||||
<div class="pt-20 pb-5 text-center bg-white dark:bg-notion-dark-light">
|
<!-- <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>
|
<!-- <h3 class="font-semibold text-3xl">See what people are saying</h3>-->
|
||||||
<p class="w-full mt-2 mb-8">
|
<!-- <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.
|
<!-- These are the stories of our customers who have joined us with great pleasure when using this crazy feature.-->
|
||||||
</p>
|
<!-- </p>-->
|
||||||
<testimonials/>
|
<!-- <testimonials/>-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
|
|
||||||
<div class="w-full bg-blue-900 p-12 md:p-24 text-center">
|
<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>
|
<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>
|
<p class="text-gray-300 my-8">No trial. Generous, unlimited free plan.</p>
|
||||||
<div class="mt-6 flex justify-center">
|
<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
|
Create a form for FREE
|
||||||
</v-button>
|
</v-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -8,6 +8,7 @@ export default [
|
||||||
|
|
||||||
// Forms
|
// Forms
|
||||||
{ path: '/forms/create', name: 'forms.create', component: page('forms/create.vue') },
|
{ 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/edit', name: 'forms.edit', component: page('forms/edit.vue') },
|
||||||
{
|
{
|
||||||
path: '/forms/:slug/show',
|
path: '/forms/:slug/show',
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
it('can create template', function () {
|
it('can create template', function () {
|
||||||
$user = $this->actingAsUser();
|
$user = $this->createUser([
|
||||||
|
'email' => 'admin@opnform.com'
|
||||||
|
]);
|
||||||
|
$this->actingAsUser($user);
|
||||||
|
|
||||||
// Create Form
|
// Create Form
|
||||||
$workspace = $this->createUserWorkspace($user);
|
$workspace = $this->createUserWorkspace($user);
|
||||||
$form = $this->makeForm($user, $workspace);
|
$form = $this->makeForm($user, $workspace);
|
||||||
|
|
||||||
// Create Template
|
// Create Template
|
||||||
$templateData = [
|
$templateData = [
|
||||||
'name' => 'Demo Template',
|
'name' => 'Demo Template',
|
||||||
|
@ -22,4 +25,4 @@ it('can create template', function () {
|
||||||
'type' => 'success',
|
'type' => 'success',
|
||||||
'message' => 'Template created.'
|
'message' => 'Template created.'
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
|
@ -125,7 +125,7 @@ trait TestHelpers
|
||||||
->withProperties(FormFactory::formatProperties($dbProperties))
|
->withProperties(FormFactory::formatProperties($dbProperties))
|
||||||
->forWorkspace($workspace)
|
->forWorkspace($workspace)
|
||||||
->createdBy($user)
|
->createdBy($user)
|
||||||
->make($data);
|
->make($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createForm(User $user, Workspace $workspace, array $data = [])
|
public function createForm(User $user, Workspace $workspace, array $data = [])
|
||||||
|
@ -135,9 +135,9 @@ trait TestHelpers
|
||||||
return $form;
|
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()
|
public function createProUser()
|
||||||
|
@ -191,5 +191,5 @@ trait TestHelpers
|
||||||
}
|
}
|
||||||
$this->assertGuest();
|
$this->assertGuest();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,3 +25,12 @@ it('can parse filenames', function () {
|
||||||
expect($parsedFilename->getMovedFileName())->toBeNull();
|
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