add: full multi-tenancy control
This commit is contained in:
3
packages/Webkul/WebForm/.gitignore
vendored
Executable file
3
packages/Webkul/WebForm/.gitignore
vendored
Executable file
@@ -0,0 +1,3 @@
|
||||
/node_modules
|
||||
/package-lock.json
|
||||
npm-debug.log
|
||||
25
packages/Webkul/WebForm/composer.json
Executable file
25
packages/Webkul/WebForm/composer.json
Executable file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "krayin/laravel-webform",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jitendra Singh",
|
||||
"email": "jitendra@webkul.com"
|
||||
}
|
||||
],
|
||||
"require": {},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Webkul\\WebForm\\": "src/"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Webkul\\WebForm\\Providers\\WebFormServiceProvider"
|
||||
],
|
||||
"aliases": {}
|
||||
}
|
||||
},
|
||||
"minimum-stability": "dev"
|
||||
}
|
||||
24
packages/Webkul/WebForm/package.json
Normal file
24
packages/Webkul/WebForm/package.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^10.4.16",
|
||||
"axios": "^1.6.4",
|
||||
"laravel-vite-plugin": "^0.7.2",
|
||||
"postcss": "^8.4.23",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"vite": "^4.0.0",
|
||||
"vue": "^3.4.19"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vee-validate/i18n": "^4.9.1",
|
||||
"@vee-validate/rules": "^4.9.1",
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"mitt": "^3.0.0",
|
||||
"vee-validate": "^4.9.1",
|
||||
"vue-flatpickr": "^2.3.0"
|
||||
}
|
||||
}
|
||||
6
packages/Webkul/WebForm/postcss.config.js
Normal file
6
packages/Webkul/WebForm/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
30
packages/Webkul/WebForm/src/Config/acl.php
Normal file
30
packages/Webkul/WebForm/src/Config/acl.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
[
|
||||
'key' => 'settings.other_settings.web_forms',
|
||||
'name' => 'web_form::app.acl.title',
|
||||
'route' => 'admin.settings.web_forms.index',
|
||||
'sort' => 1,
|
||||
], [
|
||||
'key' => 'settings.other_settings.web_forms.view',
|
||||
'name' => 'web_form::app.acl.view',
|
||||
'route' => 'admin.settings.web_forms.view',
|
||||
'sort' => 1,
|
||||
], [
|
||||
'key' => 'settings.other_settings.web_forms.create',
|
||||
'name' => 'web_form::app.acl.create',
|
||||
'route' => ['admin.settings.web_forms.create', 'admin.settings.web_forms.store'],
|
||||
'sort' => 2,
|
||||
], [
|
||||
'key' => 'settings.other_settings.web_forms.edit',
|
||||
'name' => 'web_form::app.acl.edit',
|
||||
'route' => ['admin.settings.web_forms.edit', 'admin.settings.web_forms.update'],
|
||||
'sort' => 3,
|
||||
], [
|
||||
'key' => 'settings.other_settings.web_forms.delete',
|
||||
'name' => 'web_form::app.acl.delete',
|
||||
'route' => 'admin.settings.web_forms.delete',
|
||||
'sort' => 4,
|
||||
],
|
||||
];
|
||||
12
packages/Webkul/WebForm/src/Config/menu.php
Normal file
12
packages/Webkul/WebForm/src/Config/menu.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
[
|
||||
'key' => 'settings.other_settings.web_forms',
|
||||
'name' => 'web_form::app.menu.title',
|
||||
'info' => 'web_form::app.menu.title-info',
|
||||
'route' => 'admin.settings.web_forms.index',
|
||||
'sort' => 1,
|
||||
'icon-class' => 'icon-settings-webforms',
|
||||
],
|
||||
];
|
||||
5
packages/Webkul/WebForm/src/Contracts/WebForm.php
Normal file
5
packages/Webkul/WebForm/src/Contracts/WebForm.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace Webkul\WebForm\Contracts;
|
||||
|
||||
interface WebForm {}
|
||||
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace Webkul\WebForm\Contracts;
|
||||
|
||||
interface WebFormAttribute {}
|
||||
84
packages/Webkul/WebForm/src/DataGrids/WebFormDataGrid.php
Normal file
84
packages/Webkul/WebForm/src/DataGrids/WebFormDataGrid.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace Webkul\WebForm\DataGrids;
|
||||
|
||||
use Illuminate\Contracts\Database\Query\Builder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Webkul\DataGrid\DataGrid;
|
||||
|
||||
class WebFormDataGrid extends DataGrid
|
||||
{
|
||||
/**
|
||||
* Prepare query builder.
|
||||
*/
|
||||
public function prepareQueryBuilder(): Builder
|
||||
{
|
||||
$queryBuilder = DB::table('web_forms')
|
||||
->addSelect(
|
||||
'web_forms.id',
|
||||
'web_forms.title',
|
||||
);
|
||||
|
||||
$this->addFilter('id', 'web_forms.id');
|
||||
|
||||
return $queryBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare columns.
|
||||
*/
|
||||
public function prepareColumns(): void
|
||||
{
|
||||
$this->addColumn([
|
||||
'index' => 'id',
|
||||
'label' => trans('admin::app.settings.webforms.index.datagrid.id'),
|
||||
'type' => 'string',
|
||||
'sortable' => true,
|
||||
]);
|
||||
|
||||
$this->addColumn([
|
||||
'index' => 'title',
|
||||
'label' => trans('admin::app.settings.webforms.index.datagrid.title'),
|
||||
'type' => 'string',
|
||||
'sortable' => true,
|
||||
'searchable' => true,
|
||||
'filterable' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare actions.
|
||||
*/
|
||||
public function prepareActions(): void
|
||||
{
|
||||
if (bouncer()->hasPermission('settings.other_settings.web_forms.view')) {
|
||||
$this->addAction([
|
||||
'index' => 'view',
|
||||
'icon' => 'icon-eye',
|
||||
'title' => trans('admin::app.settings.webforms.index.datagrid.view'),
|
||||
'method' => 'GET',
|
||||
'url' => fn ($row) => route('admin.settings.web_forms.view', $row->id),
|
||||
]);
|
||||
}
|
||||
|
||||
if (bouncer()->hasPermission('settings.other_settings.web_forms.edit')) {
|
||||
$this->addAction([
|
||||
'index' => 'edit',
|
||||
'icon' => 'icon-edit',
|
||||
'title' => trans('admin::app.settings.webforms.index.datagrid.edit'),
|
||||
'method' => 'GET',
|
||||
'url' => fn ($row) => route('admin.settings.web_forms.edit', $row->id),
|
||||
]);
|
||||
}
|
||||
|
||||
if (bouncer()->hasPermission('settings.other_settings.web_forms.delete')) {
|
||||
$this->addAction([
|
||||
'index' => 'delete',
|
||||
'icon' => 'icon-delete',
|
||||
'title' => trans('admin::app.settings.webforms.index.datagrid.delete'),
|
||||
'method' => 'DELETE',
|
||||
'url' => fn ($row) => route('admin.settings.web_forms.delete', $row->id),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('web_forms', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->string('form_id')->unique();
|
||||
$table->string('title');
|
||||
$table->text('description')->nullable();
|
||||
$table->text('submit_button_label');
|
||||
$table->string('submit_success_action');
|
||||
$table->string('submit_success_content');
|
||||
$table->boolean('create_lead')->default(0);
|
||||
$table->string('background_color')->nullable();
|
||||
$table->string('form_background_color')->nullable();
|
||||
$table->string('form_title_color')->nullable();
|
||||
$table->string('form_submit_button_color')->nullable();
|
||||
$table->string('attribute_label_color')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('web_forms');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('web_form_attributes', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->string('name')->nullable();
|
||||
$table->string('placeholder')->nullable();
|
||||
$table->boolean('is_required')->default(0);
|
||||
$table->boolean('is_hidden')->default(0);
|
||||
$table->integer('sort_order')->nullable();
|
||||
|
||||
$table->integer('attribute_id')->unsigned();
|
||||
$table->foreign('attribute_id')->references('id')->on('attributes')->onDelete('cascade');
|
||||
|
||||
$table->integer('web_form_id')->unsigned();
|
||||
$table->foreign('web_form_id')->references('id')->on('web_forms')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('web_form_attributes');
|
||||
}
|
||||
};
|
||||
23
packages/Webkul/WebForm/src/Http/Controllers/Controller.php
Executable file
23
packages/Webkul/WebForm/src/Http/Controllers/Controller.php
Executable file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Webkul\WebForm\Http\Controllers;
|
||||
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
|
||||
class Controller extends BaseController
|
||||
{
|
||||
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function redirectToLogin()
|
||||
{
|
||||
return redirect()->route('admin.session.create');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
namespace Webkul\WebForm\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\View\View;
|
||||
use Webkul\Attribute\Repositories\AttributeRepository;
|
||||
use Webkul\Contact\Repositories\PersonRepository;
|
||||
use Webkul\Lead\Repositories\LeadRepository;
|
||||
use Webkul\Lead\Repositories\PipelineRepository;
|
||||
use Webkul\Lead\Repositories\SourceRepository;
|
||||
use Webkul\Lead\Repositories\TypeRepository;
|
||||
use Webkul\WebForm\Http\Requests\WebForm;
|
||||
use Webkul\WebForm\Repositories\WebFormRepository;
|
||||
|
||||
class WebFormController extends Controller
|
||||
{
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(
|
||||
protected AttributeRepository $attributeRepository,
|
||||
protected WebFormRepository $webFormRepository,
|
||||
protected PersonRepository $personRepository,
|
||||
protected LeadRepository $leadRepository,
|
||||
protected PipelineRepository $pipelineRepository,
|
||||
protected SourceRepository $sourceRepository,
|
||||
protected TypeRepository $typeRepository,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Remove the specified email template from storage.
|
||||
*/
|
||||
public function formJS(string $formId): Response
|
||||
{
|
||||
$webForm = $this->webFormRepository->findOneByField('form_id', $formId);
|
||||
|
||||
return response()->view('web_form::settings.web-forms.embed', compact('webForm'))
|
||||
->header('Content-Type', 'text/javascript');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified email template from storage.
|
||||
*/
|
||||
public function formStore(int $id): JsonResponse
|
||||
{
|
||||
$person = $this->personRepository
|
||||
->getModel()
|
||||
->where('emails', 'like', '%'.request('persons.emails.0.value').'%')
|
||||
->first();
|
||||
|
||||
if ($person) {
|
||||
request()->request->add(['persons' => array_merge(request('persons'), ['id' => $person->id])]);
|
||||
}
|
||||
|
||||
app(WebForm::class);
|
||||
|
||||
$webForm = $this->webFormRepository->findOrFail($id);
|
||||
|
||||
if ($webForm->create_lead) {
|
||||
request()->request->add(['entity_type' => 'leads']);
|
||||
|
||||
Event::dispatch('lead.create.before');
|
||||
|
||||
$data = request('leads');
|
||||
|
||||
$data['entity_type'] = 'leads';
|
||||
|
||||
$data['person'] = request('persons');
|
||||
|
||||
$data['status'] = 1;
|
||||
|
||||
$pipeline = $this->pipelineRepository->getDefaultPipeline();
|
||||
|
||||
$stage = $pipeline->stages()->first();
|
||||
|
||||
$data['lead_pipeline_id'] = $pipeline->id;
|
||||
|
||||
$data['lead_pipeline_stage_id'] = $stage->id;
|
||||
|
||||
$data['title'] = request('leads.title') ?: 'Lead From Web Form';
|
||||
|
||||
$data['lead_value'] = request('leads.lead_value') ?: 0;
|
||||
|
||||
if (! request('leads.lead_source_id')) {
|
||||
$source = $this->sourceRepository->findOneByField('name', 'Web Form');
|
||||
|
||||
if (! $source) {
|
||||
$source = $this->sourceRepository->first();
|
||||
}
|
||||
|
||||
$data['lead_source_id'] = $source->id;
|
||||
}
|
||||
|
||||
$data['lead_type_id'] = request('leads.lead_type_id') ?: $this->typeRepository->first()->id;
|
||||
|
||||
$lead = $this->leadRepository->create($data);
|
||||
|
||||
Event::dispatch('lead.create.after', $lead);
|
||||
} else {
|
||||
if (! $person) {
|
||||
Event::dispatch('contacts.person.create.before');
|
||||
|
||||
$data = request('persons');
|
||||
|
||||
request()->request->add(['entity_type' => 'persons']);
|
||||
|
||||
$data['entity_type'] = 'persons';
|
||||
|
||||
$person = $this->personRepository->create($data);
|
||||
|
||||
Event::dispatch('contacts.person.create.after', $person);
|
||||
}
|
||||
}
|
||||
|
||||
if ($webForm->submit_success_action == 'message') {
|
||||
return response()->json([
|
||||
'message' => $webForm->submit_success_content,
|
||||
], 200);
|
||||
} else {
|
||||
return response()->json([
|
||||
'redirect' => $webForm->submit_success_content,
|
||||
], 301);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified email template from storage.
|
||||
*/
|
||||
public function preview(string $id): View
|
||||
{
|
||||
$webForm = $this->webFormRepository->findOneByField('form_id', $id);
|
||||
|
||||
if (is_null($webForm)) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
return view('web_form::settings.web-forms.preview', compact('webForm'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Preview the web form from datagrid.
|
||||
*/
|
||||
public function view(int $id): View
|
||||
{
|
||||
$webForm = $this->webFormRepository->findOneByField('id', $id);
|
||||
|
||||
request()->merge(['id' => $webForm->form_id]);
|
||||
|
||||
if (is_null($webForm)) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
return view('web_form::settings.web-forms.preview', compact('webForm'));
|
||||
}
|
||||
}
|
||||
127
packages/Webkul/WebForm/src/Http/Requests/WebForm.php
Normal file
127
packages/Webkul/WebForm/src/Http/Requests/WebForm.php
Normal file
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
namespace Webkul\WebForm\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Webkul\Attribute\Repositories\AttributeRepository;
|
||||
use Webkul\Attribute\Repositories\AttributeValueRepository;
|
||||
use Webkul\Core\Contracts\Validations\Decimal;
|
||||
use Webkul\WebForm\Rules\PhoneNumber;
|
||||
|
||||
class WebForm extends FormRequest
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $rules = [];
|
||||
|
||||
/**
|
||||
* Create a new form request instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(
|
||||
protected AttributeRepository $attributeRepository,
|
||||
protected AttributeValueRepository $attributeValueRepository
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Determine if the product is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
foreach (['leads', 'persons'] as $key => $entityType) {
|
||||
$attributes = $this->attributeRepository->scopeQuery(function ($query) use ($entityType) {
|
||||
$attributeCodes = $entityType == 'persons'
|
||||
? array_keys(request('persons') ?? [])
|
||||
: array_keys(request('leads') ?? []);
|
||||
|
||||
$query = $query->whereIn('code', $attributeCodes)
|
||||
->where('entity_type', $entityType);
|
||||
|
||||
return $query;
|
||||
})->get();
|
||||
|
||||
foreach ($attributes as $attribute) {
|
||||
$attribute->code = $entityType.'.'.$attribute->code;
|
||||
|
||||
$validations = [];
|
||||
|
||||
if ($attribute->type == 'boolean') {
|
||||
continue;
|
||||
} elseif ($attribute->type == 'address') {
|
||||
if (! $attribute->is_required) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$validations = [
|
||||
$attribute->code.'.address' => 'required',
|
||||
$attribute->code.'.country' => 'required',
|
||||
$attribute->code.'.state' => 'required',
|
||||
$attribute->code.'.city' => 'required',
|
||||
$attribute->code.'.postcode' => 'required',
|
||||
];
|
||||
} elseif ($attribute->type == 'email') {
|
||||
$validations = [
|
||||
$attribute->code => [$attribute->is_required ? 'required' : 'nullable'],
|
||||
$attribute->code.'.*.value' => [$attribute->is_required ? 'required' : 'nullable', 'email'],
|
||||
$attribute->code.'.*.label' => $attribute->is_required ? 'required' : 'nullable',
|
||||
];
|
||||
} elseif ($attribute->type == 'phone') {
|
||||
$validations = [
|
||||
$attribute->code => [$attribute->is_required ? 'required' : 'nullable'],
|
||||
$attribute->code.'.*.value' => [$attribute->is_required ? 'required' : 'nullable', new PhoneNumber],
|
||||
$attribute->code.'.*.label' => $attribute->is_required ? 'required' : 'nullable',
|
||||
];
|
||||
} else {
|
||||
$validations[$attribute->code] = [$attribute->is_required ? 'required' : 'nullable'];
|
||||
|
||||
if ($attribute->type == 'text' && $attribute->validation) {
|
||||
array_push($validations[$attribute->code],
|
||||
$attribute->validation == 'decimal'
|
||||
? new Decimal
|
||||
: $attribute->validation
|
||||
);
|
||||
}
|
||||
|
||||
if ($attribute->type == 'price') {
|
||||
array_push($validations[$attribute->code], new Decimal);
|
||||
}
|
||||
}
|
||||
|
||||
if ($attribute->is_unique) {
|
||||
array_push($validations[in_array($attribute->type, ['email', 'phone'])
|
||||
? $attribute->code.'.*.value'
|
||||
: $attribute->code
|
||||
], function ($field, $value, $fail) use ($attribute, $entityType) {
|
||||
if (! $this->attributeValueRepository->isValueUnique(
|
||||
$entityType == 'persons' ? request('persons.id') : null,
|
||||
$attribute->entity_type,
|
||||
$attribute,
|
||||
request($field)
|
||||
)
|
||||
) {
|
||||
$fail('The value has already been taken.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$this->rules = array_merge($this->rules, $validations);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->rules;
|
||||
}
|
||||
}
|
||||
32
packages/Webkul/WebForm/src/Models/WebForm.php
Normal file
32
packages/Webkul/WebForm/src/Models/WebForm.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Webkul\WebForm\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Webkul\WebForm\Contracts\WebForm as WebFormContract;
|
||||
|
||||
class WebForm extends Model implements WebFormContract
|
||||
{
|
||||
protected $fillable = [
|
||||
'form_id',
|
||||
'title',
|
||||
'description',
|
||||
'submit_button_label',
|
||||
'submit_success_action',
|
||||
'submit_success_content',
|
||||
'create_lead',
|
||||
'background_color',
|
||||
'form_background_color',
|
||||
'form_title_color',
|
||||
'form_submit_button_color',
|
||||
'attribute_label_color',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that belong to the activity.
|
||||
*/
|
||||
public function attributes()
|
||||
{
|
||||
return $this->hasMany(WebFormAttributeProxy::modelClass());
|
||||
}
|
||||
}
|
||||
48
packages/Webkul/WebForm/src/Models/WebFormAttribute.php
Normal file
48
packages/Webkul/WebForm/src/Models/WebFormAttribute.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Webkul\WebForm\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Webkul\Attribute\Models\AttributeProxy;
|
||||
use Webkul\WebForm\Contracts\WebFormAttribute as WebFormAttributeContract;
|
||||
|
||||
class WebFormAttribute extends Model implements WebFormAttributeContract
|
||||
{
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $timestamps = false;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'placeholder',
|
||||
'is_required',
|
||||
'is_hidden',
|
||||
'sort_order',
|
||||
'attribute_id',
|
||||
'web_form_id',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the attribute that owns the attribute.
|
||||
*/
|
||||
public function attribute()
|
||||
{
|
||||
return $this->belongsTo(AttributeProxy::modelClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the web_form that owns the attribute.
|
||||
*/
|
||||
public function web_form()
|
||||
{
|
||||
return $this->belongsTo(WebFormProxy::modelClass());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Webkul\WebForm\Models;
|
||||
|
||||
use Konekt\Concord\Proxies\ModelProxy;
|
||||
|
||||
class WebFormAttributeProxy extends ModelProxy {}
|
||||
7
packages/Webkul/WebForm/src/Models/WebFormProxy.php
Normal file
7
packages/Webkul/WebForm/src/Models/WebFormProxy.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Webkul\WebForm\Models;
|
||||
|
||||
use Konekt\Concord\Proxies\ModelProxy;
|
||||
|
||||
class WebFormProxy extends ModelProxy {}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Webkul\WebForm\Providers;
|
||||
|
||||
use Webkul\Core\Providers\BaseModuleServiceProvider;
|
||||
|
||||
class ModuleServiceProvider extends BaseModuleServiceProvider
|
||||
{
|
||||
protected $models = [
|
||||
\Webkul\WebForm\Models\WebForm::class,
|
||||
\Webkul\WebForm\Models\WebFormAttribute::class,
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Webkul\WebForm\Providers;
|
||||
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class WebFormServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
$this->loadRoutesFrom(__DIR__.'/../Routes/routes.php');
|
||||
|
||||
$this->loadTranslationsFrom(__DIR__.'/../Resources/lang', 'web_form');
|
||||
|
||||
$this->loadViewsFrom(__DIR__.'/../Resources/views', 'web_form');
|
||||
|
||||
Blade::anonymousComponentPath(__DIR__.'/../Resources/views/components');
|
||||
|
||||
$this->loadMigrationsFrom(__DIR__.'/../Database/Migrations');
|
||||
|
||||
$this->app->register(ModuleServiceProvider::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->registerConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register package config.
|
||||
*/
|
||||
protected function registerConfig(): void
|
||||
{
|
||||
$this->mergeConfigFrom(dirname(__DIR__).'/Config/menu.php', 'menu.admin');
|
||||
|
||||
$this->mergeConfigFrom(dirname(__DIR__).'/Config/acl.php', 'acl');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Webkul\WebForm\Repositories;
|
||||
|
||||
use Webkul\Core\Eloquent\Repository;
|
||||
|
||||
class WebFormAttributeRepository extends Repository
|
||||
{
|
||||
/**
|
||||
* Specify Model class name
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function model()
|
||||
{
|
||||
return 'Webkul\WebForm\Contracts\WebFormAttribute';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace Webkul\WebForm\Repositories;
|
||||
|
||||
use Illuminate\Container\Container;
|
||||
use Illuminate\Support\Str;
|
||||
use Webkul\Core\Eloquent\Repository;
|
||||
use Webkul\WebForm\Contracts\WebForm;
|
||||
|
||||
class WebFormRepository extends Repository
|
||||
{
|
||||
/**
|
||||
* Create a new repository instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(
|
||||
protected WebFormAttributeRepository $webFormAttributeRepository,
|
||||
Container $container
|
||||
) {
|
||||
parent::__construct($container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify model class name.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function model()
|
||||
{
|
||||
return WebForm::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Web Form.
|
||||
*
|
||||
* @return \Webkul\WebForm\Contracts\WebForm
|
||||
*/
|
||||
public function create(array $data)
|
||||
{
|
||||
$webForm = $this->model->create(array_merge($data, [
|
||||
'form_id' => Str::random(50),
|
||||
]));
|
||||
|
||||
foreach ($data['attributes'] as $attributeData) {
|
||||
$this->webFormAttributeRepository->create(array_merge([
|
||||
'web_form_id' => $webForm->id,
|
||||
], $attributeData));
|
||||
}
|
||||
|
||||
return $webForm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Web Form.
|
||||
*
|
||||
* @param int $id
|
||||
* @param string $attribute
|
||||
* @return \Webkul\WebForm\Contracts\WebForm
|
||||
*/
|
||||
public function update(array $data, $id, $attribute = 'id')
|
||||
{
|
||||
$webForm = parent::update($data, $id);
|
||||
|
||||
$previousAttributeIds = $webForm->attributes()->pluck('id');
|
||||
|
||||
foreach ($data['attributes'] as $attributeId => $attributeData) {
|
||||
if (Str::contains($attributeId, 'attribute_')) {
|
||||
$this->webFormAttributeRepository->create(array_merge([
|
||||
'web_form_id' => $webForm->id,
|
||||
], $attributeData));
|
||||
} else {
|
||||
if (is_numeric($index = $previousAttributeIds->search($attributeId))) {
|
||||
$previousAttributeIds->forget($index);
|
||||
}
|
||||
|
||||
$this->webFormAttributeRepository->update($attributeData, $attributeId);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($previousAttributeIds as $attributeId) {
|
||||
$this->webFormAttributeRepository->delete($attributeId);
|
||||
}
|
||||
|
||||
return $webForm;
|
||||
}
|
||||
}
|
||||
128
packages/Webkul/WebForm/src/Resources/assets/css/app.css
Normal file
128
packages/Webkul/WebForm/src/Resources/assets/css/app.css
Normal file
@@ -0,0 +1,128 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* -------------------------------- new css -------------------------------- */
|
||||
|
||||
@font-face {
|
||||
font-family: "icomoon";
|
||||
src: url("../fonts/icomoon.woff?w2trdd") format("woff");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
}
|
||||
|
||||
[class^="icon-"],
|
||||
[class*=" icon-"] {
|
||||
/* use !important to prevent issues with browser extensions that change fonts */
|
||||
font-family: "icomoon" !important;
|
||||
speak: never;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1;
|
||||
|
||||
/* Better Font Rendering =========== */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.icon-cross-large:before {
|
||||
content: "\e91c";
|
||||
}
|
||||
|
||||
[dir="rtl"] .stage::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: -10px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
z-index: 1;
|
||||
border-radius: 0 0 0 25px;
|
||||
transform: translateY(-50%) rotate(225deg);
|
||||
border-right: 4px solid #f3f4f6;
|
||||
border-top: 4px solid #f3f4f6;
|
||||
}
|
||||
|
||||
[dir="rtl"] .stage::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.primary-button {
|
||||
@apply bg-brandColor border border-brandColor cursor-pointer flex focus:opacity-[0.9] font-semibold gap-x-1 hover:opacity-[0.9] items-center place-content-center px-3 py-1.5 rounded-md text-gray-50 transition-all;
|
||||
}
|
||||
|
||||
.secondary-button {
|
||||
@apply flex cursor-pointer place-content-center items-center gap-x-1 whitespace-nowrap rounded-md border-2 border-brandColor bg-white px-3 py-1.5 font-semibold text-brandColor transition-all hover:bg-[#eff6ff61] focus:bg-[#eff6ff61] dark:border-gray-400 dark:bg-gray-800 dark:text-white dark:hover:opacity-80;
|
||||
}
|
||||
|
||||
.transparent-button {
|
||||
@apply flex cursor-pointer appearance-none place-content-center items-center gap-x-1 whitespace-nowrap rounded-md border-2 border-transparent px-3 py-1.5 font-semibold text-gray-600 transition-all marker:shadow hover:bg-gray-100 focus:bg-gray-100 dark:hover:bg-gray-950;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #888;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #888 #f1f1f1;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background-color: rgba(0, 68, 242, 0.2);
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-gray-100 text-sm text-gray-800;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
@apply cursor-not-allowed opacity-50;
|
||||
}
|
||||
|
||||
button:disabled:hover {
|
||||
@apply cursor-not-allowed opacity-50;
|
||||
}
|
||||
|
||||
.draggable-ghost {
|
||||
opacity: 0.5;
|
||||
background: #e0e7ff;
|
||||
}
|
||||
|
||||
html.dark [class^="icon-"],
|
||||
html.dark [class*=" icon-"] {
|
||||
color: #d1d5db;
|
||||
}
|
||||
|
||||
p {
|
||||
@apply text-[14px] !leading-[17px];
|
||||
}
|
||||
|
||||
input,
|
||||
textarea,
|
||||
select {
|
||||
@apply outline-none;
|
||||
}
|
||||
|
||||
.required:after {
|
||||
@apply content-['*'];
|
||||
}
|
||||
}
|
||||
BIN
packages/Webkul/WebForm/src/Resources/assets/fonts/icomoon.woff
Normal file
BIN
packages/Webkul/WebForm/src/Resources/assets/fonts/icomoon.woff
Normal file
Binary file not shown.
57
packages/Webkul/WebForm/src/Resources/assets/js/app.js
Normal file
57
packages/Webkul/WebForm/src/Resources/assets/js/app.js
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* This will track all the images and fonts for publishing.
|
||||
*/
|
||||
import.meta.glob(["../images/**", "../fonts/**"]);
|
||||
|
||||
/**
|
||||
* Main vue bundler.
|
||||
*/
|
||||
import { createApp } from "vue/dist/vue.esm-bundler";
|
||||
|
||||
/**
|
||||
* Main root application registry.
|
||||
*/
|
||||
window.app = createApp({
|
||||
data() {
|
||||
return {
|
||||
isMenuActive: false,
|
||||
|
||||
hoveringMenu: '',
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
onSubmit() {},
|
||||
|
||||
onInvalidSubmit({ values, errors, results }) {
|
||||
setTimeout(() => {
|
||||
const errorKeys = Object.entries(errors)
|
||||
.map(([key, value]) => ({ key, value }))
|
||||
.filter(error => error["value"].length);
|
||||
|
||||
let firstErrorElement = document.querySelector('[name="' + errorKeys[0]["key"] + '"]');
|
||||
|
||||
firstErrorElement.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "center"
|
||||
});
|
||||
}, 100);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Global plugins registration.
|
||||
*/
|
||||
import Axios from "./plugins/axios";
|
||||
import Emitter from "./plugins/emitter";
|
||||
import Flatpickr from "./plugins/flatpickr";
|
||||
import VeeValidate from "./plugins/vee-validate";
|
||||
[
|
||||
Axios,
|
||||
Emitter,
|
||||
Flatpickr,
|
||||
VeeValidate,
|
||||
].forEach((plugin) => app.use(plugin));
|
||||
|
||||
export default app;
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* We'll load the axios HTTP library which allows us to easily issue requests
|
||||
* to our Laravel back-end. This library automatically handles sending the
|
||||
* CSRF token as a header based on the value of the "XSRF" token cookie.
|
||||
*/
|
||||
import axios from "axios";
|
||||
window.axios = axios;
|
||||
window.axios.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest";
|
||||
|
||||
export default {
|
||||
install(app) {
|
||||
app.config.globalProperties.$axios = axios;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
import mitt from "mitt";
|
||||
|
||||
const emitter = mitt();
|
||||
|
||||
window.emitter = emitter;
|
||||
|
||||
export default {
|
||||
install: (app, options) => {
|
||||
app.config.globalProperties.$emitter = emitter;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import Flatpickr from "flatpickr";
|
||||
import "flatpickr/dist/flatpickr.css";
|
||||
|
||||
export default {
|
||||
install: (app) => {
|
||||
window.Flatpickr = Flatpickr;
|
||||
|
||||
const changeTheme = (theme) => {
|
||||
document.getElementById('flatpickr')?.remove();
|
||||
|
||||
if (theme === 'light') {
|
||||
return;
|
||||
}
|
||||
|
||||
const linkElement = document.createElement("link");
|
||||
|
||||
linkElement.rel = "stylesheet";
|
||||
linkElement.type = "text/css";
|
||||
linkElement.href = `https://npmcdn.com/flatpickr/dist/themes/${theme}.css`;
|
||||
linkElement.id = 'flatpickr';
|
||||
|
||||
document.head.appendChild(linkElement);
|
||||
};
|
||||
|
||||
const currentTheme = document.documentElement.classList.contains("dark")
|
||||
? "dark"
|
||||
: "light";
|
||||
|
||||
changeTheme(currentTheme);
|
||||
|
||||
app.config.globalProperties.$emitter.on("change-theme", (theme) => {
|
||||
changeTheme(theme);
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,285 @@
|
||||
/**
|
||||
* We are defining all the global rules here and configuring
|
||||
* all the `vee-validate` settings.
|
||||
*/
|
||||
import { configure, defineRule, Field, Form, ErrorMessage } from "vee-validate";
|
||||
import { localize, setLocale } from "@vee-validate/i18n";
|
||||
import ar from "@vee-validate/i18n/dist/locale/ar.json";
|
||||
import bn from "@vee-validate/i18n/dist/locale/bn.json";
|
||||
import de from "@vee-validate/i18n/dist/locale/de.json";
|
||||
import en from "@vee-validate/i18n/dist/locale/en.json";
|
||||
import es from "@vee-validate/i18n/dist/locale/es.json";
|
||||
import fa from "@vee-validate/i18n/dist/locale/fa.json";
|
||||
import fr from "@vee-validate/i18n/dist/locale/fr.json";
|
||||
import he from "@vee-validate/i18n/dist/locale/he.json";
|
||||
import hi_IN from "../../locales/hi_IN.json";
|
||||
import it from "@vee-validate/i18n/dist/locale/it.json";
|
||||
import ja from "@vee-validate/i18n/dist/locale/ja.json";
|
||||
import nl from "@vee-validate/i18n/dist/locale/nl.json";
|
||||
import pl from "@vee-validate/i18n/dist/locale/pl.json";
|
||||
import pt_BR from "@vee-validate/i18n/dist/locale/pt_BR.json";
|
||||
import ru from "@vee-validate/i18n/dist/locale/ru.json";
|
||||
import sin from "../../locales/sin.json";
|
||||
import tr from "@vee-validate/i18n/dist/locale/tr.json";
|
||||
import uk from "@vee-validate/i18n/dist/locale/uk.json";
|
||||
import zh_CN from "@vee-validate/i18n/dist/locale/zh_CN.json";
|
||||
import { all } from '@vee-validate/rules';
|
||||
|
||||
window.defineRule = defineRule;
|
||||
|
||||
export default {
|
||||
install: (app) => {
|
||||
/**
|
||||
* Global components registration;
|
||||
*/
|
||||
app.component("VForm", Form);
|
||||
app.component("VField", Field);
|
||||
app.component("VErrorMessage", ErrorMessage);
|
||||
|
||||
window.addEventListener("load", () => setLocale(document.documentElement.attributes.lang.value));
|
||||
|
||||
/**
|
||||
* Registration of all global validators.
|
||||
*/
|
||||
Object.entries(all).forEach(([name, rule]) => defineRule(name, rule));
|
||||
|
||||
/**
|
||||
* This regular expression allows phone numbers with the following conditions:
|
||||
* - The phone number can start with an optional "+" sign.
|
||||
* - After the "+" sign, there should be one or more digits.
|
||||
*
|
||||
* This validation is sufficient for global-level phone number validation. If
|
||||
* someone wants to customize it, they can override this rule.
|
||||
*/
|
||||
defineRule("phone", (value) => {
|
||||
if (! value || ! value.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (! /^\+?\d+$/.test(value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
defineRule("address", (value) => {
|
||||
if (!value || !value.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
!/^[a-zA-Z0-9\s.\/*'\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\u0590-\u05FF\u3040-\u309F\u30A0-\u30FF\u0400-\u04FF\u0D80-\u0DFF\u3400-\u4DBF\u2000-\u2A6D\u00C0-\u017F\u0980-\u09FF\u0900-\u097F\u4E00-\u9FFF,\(\)-]{1,60}$/iu.test(
|
||||
value
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
defineRule("decimal", (value, { decimals = '*', separator = '.' } = {}) => {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Number(decimals) === 0) {
|
||||
return /^-?\d*$/.test(value);
|
||||
}
|
||||
|
||||
const regexPart = decimals === '*' ? '+' : `{1,${decimals}}`;
|
||||
const regex = new RegExp(`^[-+]?\\d*(\\${separator}\\d${regexPart})?([eE]{1}[-]?\\d+)?$`);
|
||||
|
||||
return regex.test(value);
|
||||
});
|
||||
|
||||
defineRule("required_if", (value, { condition = true } = {}) => {
|
||||
if (condition) {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
defineRule("", () => true);
|
||||
|
||||
// @TODO handle this
|
||||
// @suraj-webkul
|
||||
defineRule("date_format", (value) => {
|
||||
return true;
|
||||
});
|
||||
|
||||
// @TODO handle this
|
||||
// @suraj-webkul
|
||||
defineRule("after", (value) => {
|
||||
return true;
|
||||
});
|
||||
|
||||
configure({
|
||||
/**
|
||||
* Built-in error messages and custom error messages are available. Multiple
|
||||
* locales can be added in the same way.
|
||||
*/
|
||||
generateMessage: localize({
|
||||
ar: {
|
||||
...ar,
|
||||
messages: {
|
||||
...ar.messages,
|
||||
phone: "يجب أن يكون هذا {field} رقم هاتف صالحًا",
|
||||
},
|
||||
},
|
||||
|
||||
bn: {
|
||||
...bn,
|
||||
messages: {
|
||||
...bn.messages,
|
||||
phone: "এই {field} একটি বৈধ ফোন নম্বর হতে হবে",
|
||||
},
|
||||
},
|
||||
|
||||
de: {
|
||||
...de,
|
||||
messages: {
|
||||
...de.messages,
|
||||
phone: "Dieses {field} muss eine gültige Telefonnummer sein.",
|
||||
},
|
||||
},
|
||||
|
||||
en: {
|
||||
...en,
|
||||
messages: {
|
||||
...en.messages,
|
||||
phone: "This {field} must be a valid phone number",
|
||||
},
|
||||
},
|
||||
|
||||
es: {
|
||||
...es,
|
||||
messages: {
|
||||
...es.messages,
|
||||
phone: "Este {field} debe ser un número de teléfono válido.",
|
||||
},
|
||||
},
|
||||
|
||||
fa: {
|
||||
...fa,
|
||||
messages: {
|
||||
...fa.messages,
|
||||
phone: "این {field} باید یک شماره تلفن معتبر باشد.",
|
||||
},
|
||||
},
|
||||
|
||||
fr: {
|
||||
...fr,
|
||||
messages: {
|
||||
...fr.messages,
|
||||
phone: "Ce {field} doit être un numéro de téléphone valide.",
|
||||
},
|
||||
},
|
||||
|
||||
he: {
|
||||
...he,
|
||||
messages: {
|
||||
...he.messages,
|
||||
phone: "זה {field} חייב להיות מספר טלפון תקין.",
|
||||
},
|
||||
},
|
||||
|
||||
hi_IN: {
|
||||
...hi_IN,
|
||||
messages: {
|
||||
...hi_IN.messages,
|
||||
phone: "यह {field} कोई मान्य फ़ोन नंबर होना चाहिए।",
|
||||
},
|
||||
},
|
||||
|
||||
it: {
|
||||
...it,
|
||||
messages: {
|
||||
...it.messages,
|
||||
phone: "Questo {field} deve essere un numero di telefono valido.",
|
||||
},
|
||||
},
|
||||
|
||||
ja: {
|
||||
...ja,
|
||||
messages: {
|
||||
...ja.messages,
|
||||
phone: "この{field}は有効な電話番号である必要があります。",
|
||||
},
|
||||
},
|
||||
|
||||
nl: {
|
||||
...nl,
|
||||
messages: {
|
||||
...nl.messages,
|
||||
phone: "Dit {field} moet een geldig telefoonnummer zijn.",
|
||||
},
|
||||
},
|
||||
|
||||
pl: {
|
||||
...pl,
|
||||
messages: {
|
||||
...pl.messages,
|
||||
phone: "To {field} musi być prawidłowy numer telefonu.",
|
||||
},
|
||||
},
|
||||
|
||||
pt_BR: {
|
||||
...pt_BR,
|
||||
messages: {
|
||||
...pt_BR.messages,
|
||||
phone: "Este {field} deve ser um número de telefone válido.",
|
||||
},
|
||||
},
|
||||
|
||||
ru: {
|
||||
...ru,
|
||||
messages: {
|
||||
...ru.messages,
|
||||
phone: "Это {field} должно быть действительным номером телефона.",
|
||||
},
|
||||
},
|
||||
|
||||
sin: {
|
||||
...sin,
|
||||
messages: {
|
||||
...sin.messages,
|
||||
phone: "මෙම {field} වටේ වලංගු දුරකතන අංකය විය යුතුයි.",
|
||||
},
|
||||
},
|
||||
|
||||
tr: {
|
||||
...tr,
|
||||
messages: {
|
||||
...tr.messages,
|
||||
phone: "Bu {field} geçerli bir telefon numarası olmalıdır.",
|
||||
},
|
||||
},
|
||||
|
||||
uk: {
|
||||
...uk,
|
||||
messages: {
|
||||
...uk.messages,
|
||||
phone: "Це {field} повинно бути дійсним номером телефону.",
|
||||
},
|
||||
},
|
||||
|
||||
zh_CN: {
|
||||
...zh_CN,
|
||||
messages: {
|
||||
...zh_CN.messages,
|
||||
phone: "这个 {field} 必须是一个有效的电话号码。",
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
validateOnBlur: true,
|
||||
validateOnInput: true,
|
||||
validateOnChange: true,
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"code": "hi_IN",
|
||||
"messages": {
|
||||
"_default": "यह {field} मान्य नहीं है",
|
||||
"alpha": "{field} फ़ील्ड में केवल वर्णात्मक अक्षर हो सकते हैं",
|
||||
"alpha_num": "{field} फ़ील्ड में केवल वर्णात्मक और संख्यात्मक अक्षर हो सकते हैं",
|
||||
"alpha_dash": "{field} फ़ील्ड में वर्णात्मक और संख्यात्मक अक्षरों के साथ डैश और अंडरस्कोर हो सकते हैं",
|
||||
"alpha_spaces": "{field} फ़ील्ड में केवल वर्णात्मक अक्षर और अंतर हो सकते हैं",
|
||||
"between": "{field} फ़ील्ड 0:{min} और 1:{max} के बीच होना चाहिए",
|
||||
"confirmed": "{field} फ़ील्ड की पुष्टि मेल नहीं खाती",
|
||||
"digits": "{field} फ़ील्ड संख्यात्मक होनी चाहिए और बिल्कुल 0:{length} अंक होने चाहिए",
|
||||
"dimensions": "{field} फ़ील्ड 0:{width} पिक्सेल और 1:{height} पिक्सेल होना चाहिए",
|
||||
"email": "{field} फ़ील्ड में एक मान्य ईमेल होना चाहिए",
|
||||
"not_one_of": "{field} फ़ील्ड मान्य मूल्य नहीं है",
|
||||
"ext": "{field} फ़ील्ड में मान्य फ़ाइल नहीं है",
|
||||
"image": "{field} फ़ील्ड एक छवि होनी चाहिए",
|
||||
"integer": "{field} फ़ील्ड एक पूर्णांक होना चाहिए",
|
||||
"length": "{field} फ़ील्ड 0:{length} लंबा होना चाहिए",
|
||||
"max_value": "{field} फ़ील्ड 0:{max} या उससे कम होना चाहिए",
|
||||
"max": "{field} फ़ील्ड 0:{length} अक्षरों से अधिक नहीं हो सकता",
|
||||
"mimes": "{field} फ़ील्ड को मान्य फ़ाइल प्रकार होना चाहिए",
|
||||
"min_value": "{field} फ़ील्ड 0:{min} या उससे अधिक होना चाहिए",
|
||||
"min": "{field} फ़ील्ड कम से कम 0:{length} अक्षरों का होना चाहिए",
|
||||
"numeric": "{field} फ़ील्ड में केवल संख्याएँ हो सकती हैं",
|
||||
"one_of": "{field} फ़ील्ड मान्य मूल्य नहीं है",
|
||||
"regex": "{field} फ़ील्ड का प्रारूप अवैध है",
|
||||
"required_if": "{field} फ़ील्ड आवश्यक है",
|
||||
"required": "{field} फ़ील्ड आवश्यक है",
|
||||
"size": "{field} फ़ील्ड का आकार 0:{size}KB से कम होना चाहिए",
|
||||
"url": "{field} फ़ील्ड में एक मान्य URL नहीं है"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"code": "sin",
|
||||
"messages": {
|
||||
"_default": "මේ {field} වල වලංගු නොවේ",
|
||||
"alpha": "{field} ක්ෂණික සංඛ්යාවක් පිළිබඳව සියල්ල සියල්ල සහිතව හැකිය",
|
||||
"alpha_num": "{field} ක්ෂණික සහ සංඛ්යාවක් පිළිබඳව සියල්ල සහිතව හැකිය",
|
||||
"alpha_dash": "{field} ක්ෂණික සහ සංඛ්යාවක් සමග දැහැ හෝ පරිදි ලොව සහිතව හැකිය",
|
||||
"alpha_spaces": "{field} ක්ෂණික සංඛ්යාවක් සහිතව හැකිය, සහ වීඩියෝ හෝම්හෝ සහිතව හැකිය",
|
||||
"between": "{field} ක්ෂණික 0:{min} සහ 1:{max} අතර විය යුතුය",
|
||||
"confirmed": "{field} ක්ෂණික තහවුරු නොගත් බව තහවුරු කර නොයාය",
|
||||
"digits": "{field} ක්ෂණික සෂ්යෝගයක් හා සියලුමේ විය 0:{length} දිගු විය යුතුය",
|
||||
"dimensions": "{field} ක්ෂණික 0:{width} පික්සල සහ 1:{height} පික්සල විය යුතුය",
|
||||
"email": "{field} ක්ෂණික වලංගු ඊමේල් එක හෝ යුක්ත විය යුතුය",
|
||||
"not_one_of": "{field} ක්ෂණික වලංගු අගය නොවේ",
|
||||
"ext": "{field} ක්ෂණික වලංගු ගොනුව නොවේ",
|
||||
"image": "{field} ක්ෂණික වලංගු ඡායාරූපය යුතුය",
|
||||
"integer": "{field} ක්ෂණික වලංගු නික්මෙර වර්ගයේ යුතුය",
|
||||
"length": "{field} ක්ෂණික වලංගු 0:{length} හෝමාව යුතුය",
|
||||
"max_value": "{field} ක්ෂණික 0:{max} හෝමා හෝමා හෝමා යුතුය",
|
||||
"max": "{field} ක්ෂණික 0:{length} අකුරු වලංගු වී නොයාය",
|
||||
"mimes": "{field} ක්ෂණික ගොනුවේ වලංගු ගොනු වර්ගය හෝ හෝ හෝ යුතුය",
|
||||
"min_value": "{field} ක්ෂණික 0:{min} හෝමාව හෝමාව හෝමාව හෝමාව හෝමාව යුතුය",
|
||||
"min": "{field} ක්ෂණික 0:{length} හෝමාවක් හෝමාවක් හෝමාවක් හෝමාවක් යුතුය",
|
||||
"numeric": "{field} ක්ෂණික වලංගු සංඛ්යාවෙන් වයස්ක්ර සංඛ්යාවෙන් වයස්ක්ර විය ",
|
||||
"one_of": "{field} ක්ෂණික වලංගු අගය නොවේ",
|
||||
"regex": "{field} ක්ෂණික වලංගු ආකාරය අවලංගුය",
|
||||
"required_if": "{field} ක්ෂණිකයෙන් හෝයි",
|
||||
"required": "{field} ක්ෂණිකයෙන් හෝයි",
|
||||
"size": "{field} ක්ෂණික වලංගු විය හැකි ආකාරය 0:{size}KB හෝ හොයා යුතුයි",
|
||||
"url": "{field} ක්ෂණික වලංගු වර්ගවල URL නොවේ"
|
||||
}
|
||||
}
|
||||
20
packages/Webkul/WebForm/src/Resources/lang/ar/app.php
Normal file
20
packages/Webkul/WebForm/src/Resources/lang/ar/app.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'acl' => [
|
||||
'title' => 'نماذج الويب',
|
||||
'view' => 'عرض',
|
||||
'create' => 'إنشاء',
|
||||
'edit' => 'تعديل',
|
||||
'delete' => 'حذف',
|
||||
],
|
||||
|
||||
'menu' => [
|
||||
'title' => 'نماذج الويب',
|
||||
'title-info' => 'إضافة، تعديل أو حذف نماذج الويب من CRM',
|
||||
],
|
||||
|
||||
'validations' => [
|
||||
'invalid-phone-number' => 'رقم الهاتف غير صحيح',
|
||||
],
|
||||
];
|
||||
20
packages/Webkul/WebForm/src/Resources/lang/en/app.php
Normal file
20
packages/Webkul/WebForm/src/Resources/lang/en/app.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'acl' => [
|
||||
'title' => 'Web Forms',
|
||||
'view' => 'View',
|
||||
'create' => 'Create',
|
||||
'edit' => 'Edit',
|
||||
'delete' => 'Delete',
|
||||
],
|
||||
|
||||
'menu' => [
|
||||
'title' => 'Web Forms',
|
||||
'title-info' => 'Add, edit, or delete web forms from the CRM',
|
||||
],
|
||||
|
||||
'validations' => [
|
||||
'invalid-phone-number' => 'Invalid phone number',
|
||||
],
|
||||
];
|
||||
20
packages/Webkul/WebForm/src/Resources/lang/es/app.php
Normal file
20
packages/Webkul/WebForm/src/Resources/lang/es/app.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'acl' => [
|
||||
'title' => 'Formularios Web',
|
||||
'view' => 'Ver',
|
||||
'create' => 'Crear',
|
||||
'edit' => 'Editar',
|
||||
'delete' => 'Eliminar',
|
||||
],
|
||||
|
||||
'menu' => [
|
||||
'title' => 'Formularios Web',
|
||||
'title-info' => 'Agregar, editar o eliminar formularios web desde CRM',
|
||||
],
|
||||
|
||||
'validations' => [
|
||||
'invalid-phone-number' => 'Número de teléfono no válido',
|
||||
],
|
||||
];
|
||||
20
packages/Webkul/WebForm/src/Resources/lang/fa/app.php
Normal file
20
packages/Webkul/WebForm/src/Resources/lang/fa/app.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'acl' => [
|
||||
'title' => 'فرمهای وب',
|
||||
'view' => 'نمایش',
|
||||
'create' => 'ایجاد',
|
||||
'edit' => 'ویرایش',
|
||||
'delete' => 'حذف',
|
||||
],
|
||||
|
||||
'menu' => [
|
||||
'title' => 'فرمهای وب',
|
||||
'title-info' => 'افزودن، ویرایش یا حذف فرمهای وب از CRM',
|
||||
],
|
||||
|
||||
'validations' => [
|
||||
'invalid-phone-number' => 'شماره تلفن نامعتبر است',
|
||||
],
|
||||
];
|
||||
20
packages/Webkul/WebForm/src/Resources/lang/pt_BR/app.php
Normal file
20
packages/Webkul/WebForm/src/Resources/lang/pt_BR/app.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'acl' => [
|
||||
'title' => 'Formulários Web',
|
||||
'view' => 'Visualizar',
|
||||
'create' => 'Adicionar',
|
||||
'edit' => 'Editar',
|
||||
'delete' => 'Excluir',
|
||||
],
|
||||
|
||||
'menu' => [
|
||||
'title' => 'Formulários Web',
|
||||
'title-info' => 'Adicione, edite ou exclua formulários web no CRM',
|
||||
],
|
||||
|
||||
'validations' => [
|
||||
'invalid-phone-number' => 'Número de telefone inválido',
|
||||
],
|
||||
];
|
||||
20
packages/Webkul/WebForm/src/Resources/lang/tr/app.php
Normal file
20
packages/Webkul/WebForm/src/Resources/lang/tr/app.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'acl' => [
|
||||
'title' => 'Web Formları',
|
||||
'view' => 'Görüntüle',
|
||||
'create' => 'Oluştur',
|
||||
'edit' => 'Düzenle',
|
||||
'delete' => 'Sil',
|
||||
],
|
||||
|
||||
'menu' => [
|
||||
'title' => 'Web Formları',
|
||||
'title-info' => 'CRM’den web formlarını ekle, düzenle veya sil',
|
||||
],
|
||||
|
||||
'validations' => [
|
||||
'invalid-phone-number' => 'Geçersiz telefon numarası',
|
||||
],
|
||||
];
|
||||
20
packages/Webkul/WebForm/src/Resources/lang/vi/app.php
Normal file
20
packages/Webkul/WebForm/src/Resources/lang/vi/app.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'acl' => [
|
||||
'title' => 'Biểu mẫu Web',
|
||||
'view' => 'Xem',
|
||||
'create' => 'Tạo',
|
||||
'edit' => 'Chỉnh sửa',
|
||||
'delete' => 'Xóa',
|
||||
],
|
||||
|
||||
'menu' => [
|
||||
'title' => 'Biểu mẫu Web',
|
||||
'title-info' => 'Thêm, chỉnh sửa hoặc xóa biểu mẫu web từ CRM',
|
||||
],
|
||||
|
||||
'validations' => [
|
||||
'invalid-phone-number' => 'Số điện thoại không hợp lệ.',
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,40 @@
|
||||
<v-button {{ $attributes }}></v-button>
|
||||
|
||||
@pushOnce('scripts')
|
||||
<script
|
||||
type="text/x-template"
|
||||
id="v-button-template"
|
||||
>
|
||||
<button
|
||||
v-if="! loading"
|
||||
:class="[buttonClass, '']"
|
||||
>
|
||||
@{{ title }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-else
|
||||
:class="[buttonClass, '']"
|
||||
>
|
||||
<!-- Spinner -->
|
||||
<x-admin::spinner class="absolute" />
|
||||
|
||||
<span class="realative h-full w-full opacity-0">
|
||||
@{{ title }}
|
||||
</span>
|
||||
</button>
|
||||
</script>
|
||||
|
||||
<script type="module">
|
||||
app.component('v-button', {
|
||||
template: '#v-button-template',
|
||||
|
||||
props: {
|
||||
loading: Boolean,
|
||||
buttonType: String,
|
||||
title: String,
|
||||
buttonClass: String,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@endPushOnce
|
||||
@@ -0,0 +1,64 @@
|
||||
<v-flash-group ref='flashes'></v-flash-group>
|
||||
|
||||
@pushOnce('scripts')
|
||||
<script
|
||||
type="text/x-template"
|
||||
id="v-flash-group-template"
|
||||
>
|
||||
<transition-group
|
||||
tag='div'
|
||||
name="flash-group"
|
||||
enter-from-class="ltr:translate-y-full rtl:-translate-y-full"
|
||||
enter-active-class="transform transition duration-300 ease-[cubic-bezier(.4,0,.2,1)]"
|
||||
enter-to-class="ltr:translate-y-0 rtl:-translate-y-0"
|
||||
leave-from-class="ltr:translate-y-0 rtl:-translate-y-0"
|
||||
leave-active-class="transform transition duration-300 ease-[cubic-bezier(.4,0,.2,1)]"
|
||||
leave-to-class="ltr:translate-y-full rtl:-translate-y-full"
|
||||
class='fixed bottom-5 left-1/2 z-[10003] grid -translate-x-1/2 justify-items-end gap-2.5'
|
||||
>
|
||||
<x-admin::flash-group.item />
|
||||
</transition-group>
|
||||
</script>
|
||||
|
||||
<script type="module">
|
||||
app.component('v-flash-group', {
|
||||
template: '#v-flash-group-template',
|
||||
|
||||
data() {
|
||||
return {
|
||||
uid: 0,
|
||||
|
||||
flashes: []
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
@foreach (['success', 'warning', 'error', 'info'] as $key)
|
||||
@if (session()->has($key))
|
||||
this.flashes.push({'type': '{{ $key }}', 'message': "{{ session($key) }}", 'uid': this.uid++});
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
this.registerGlobalEvents();
|
||||
},
|
||||
|
||||
methods: {
|
||||
add(flash) {
|
||||
flash.uid = this.uid++;
|
||||
|
||||
this.flashes.push(flash);
|
||||
},
|
||||
|
||||
remove(flash) {
|
||||
let index = this.flashes.indexOf(flash);
|
||||
|
||||
this.flashes.splice(index, 1);
|
||||
},
|
||||
|
||||
registerGlobalEvents() {
|
||||
this.$emitter.on('add-flash', this.add);
|
||||
},
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@endpushOnce
|
||||
@@ -0,0 +1,205 @@
|
||||
<v-flash-item
|
||||
v-for='flash in flashes'
|
||||
:key='flash.uid'
|
||||
:flash="flash"
|
||||
@onRemove="remove($event)"
|
||||
>
|
||||
</v-flash-item>
|
||||
|
||||
@pushOnce('scripts')
|
||||
<script
|
||||
type="text/x-template"
|
||||
id="v-flash-item-template"
|
||||
>
|
||||
<div
|
||||
class="flex w-max items-start justify-between gap-2 rounded-lg bg-white p-3 shadow-[0px_10px_20px_0px_rgba(0,0,0,0.12)] dark:bg-gray-950"
|
||||
:style="typeStyles[flash.type]['container']"
|
||||
@mouseenter="pauseTimer"
|
||||
@mouseleave="resumeTimer"
|
||||
>
|
||||
<!-- Icon -->
|
||||
<span
|
||||
class="icon-toast-done rounded-full bg-white text-2xl dark:bg-gray-900"
|
||||
:class="iconClasses[flash.type]"
|
||||
:style="typeStyles[flash.type]['icon']"
|
||||
></span>
|
||||
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<!-- Heading -->
|
||||
<p class="text-base font-semibold dark:text-white">
|
||||
@{{ typeHeadings[flash.type] }}
|
||||
</p>
|
||||
|
||||
<!-- Message -->
|
||||
<p
|
||||
class="flex items-center break-all text-sm dark:text-white"
|
||||
:style="typeStyles[flash.type]['message']"
|
||||
>
|
||||
|
||||
@{{ flash.message }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="relative ml-4 inline-flex rounded-full bg-white p-1.5 text-gray-400 dark:bg-gray-950"
|
||||
@click="remove"
|
||||
>
|
||||
<span class="icon-cross-large text-2xl text-slate-800"></span>
|
||||
|
||||
<svg class="absolute inset-0 h-full w-full -rotate-90" viewBox="0 0 24 24">
|
||||
<circle
|
||||
class="text-gray-200"
|
||||
stroke-width="1.5"
|
||||
stroke="#D0D4DA"
|
||||
fill="transparent"
|
||||
r="10"
|
||||
cx="12"
|
||||
cy="12"
|
||||
/>
|
||||
|
||||
<circle
|
||||
class="text-blue-600 transition-all duration-100 ease-out"
|
||||
stroke-width="1.5"
|
||||
:stroke-dasharray="circumference"
|
||||
:stroke-dashoffset="strokeDashoffset"
|
||||
stroke-linecap="round"
|
||||
:stroke="typeStyles[flash.type]['stroke']"
|
||||
fill="transparent"
|
||||
r="10"
|
||||
cx="12"
|
||||
cy="12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="module">
|
||||
app.component('v-flash-item', {
|
||||
template: '#v-flash-item-template',
|
||||
|
||||
props: ['flash'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
iconClasses: {
|
||||
success: 'icon-success',
|
||||
|
||||
error: 'icon-error',
|
||||
|
||||
warning: 'icon-warning',
|
||||
|
||||
info: 'icon-info',
|
||||
},
|
||||
|
||||
typeHeadings: {
|
||||
success: "@lang('admin::app.components.flash-group.success')",
|
||||
|
||||
error: "@lang('admin::app.components.flash-group.error')",
|
||||
|
||||
warning: "@lang('admin::app.components.flash-group.warning')",
|
||||
|
||||
info: "@lang('admin::app.components.flash-group.info')",
|
||||
},
|
||||
|
||||
typeStyles: {
|
||||
success: {
|
||||
container: 'border-left: 8px solid #16A34A',
|
||||
|
||||
icon: 'color: #16A34A',
|
||||
|
||||
stroke: '#16A34A',
|
||||
},
|
||||
|
||||
error: {
|
||||
container: 'border-left: 8px solid #FF4D50',
|
||||
|
||||
icon: 'color: #FF4D50',
|
||||
|
||||
stroke: '#FF4D50',
|
||||
},
|
||||
|
||||
warning: {
|
||||
container: 'border-left: 8px solid #FBAD15',
|
||||
|
||||
icon: 'color: #FBAD15',
|
||||
|
||||
stroke: '#FBAD15',
|
||||
},
|
||||
|
||||
info: {
|
||||
container: 'border-left: 8px solid #0E90D9',
|
||||
|
||||
icon: 'color: #0E90D9',
|
||||
|
||||
stroke: '#0E90D9',
|
||||
},
|
||||
},
|
||||
|
||||
duration: 5000,
|
||||
|
||||
progress: 0,
|
||||
|
||||
circumference: 2 * Math.PI * 10,
|
||||
|
||||
timer: null,
|
||||
|
||||
isPaused: false,
|
||||
|
||||
remainingTime: 5000,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
strokeDashoffset() {
|
||||
return this.circumference - (this.progress / 100) * this.circumference;
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.startTimer();
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
this.stopTimer();
|
||||
},
|
||||
|
||||
methods: {
|
||||
remove() {
|
||||
this.$emit('onRemove', this.flash)
|
||||
},
|
||||
|
||||
startTimer() {
|
||||
const interval = 100;
|
||||
|
||||
const step = (100 / (this.duration / interval));
|
||||
|
||||
this.timer = setInterval(() => {
|
||||
if (! this.isPaused) {
|
||||
this.progress += step;
|
||||
|
||||
this.remainingTime -= interval;
|
||||
|
||||
if (this.progress >= 100) {
|
||||
this.stopTimer();
|
||||
this.remove();
|
||||
}
|
||||
}
|
||||
}, interval);
|
||||
},
|
||||
|
||||
stopTimer() {
|
||||
clearInterval(this.timer);
|
||||
},
|
||||
|
||||
pauseTimer() {
|
||||
this.isPaused = true;
|
||||
},
|
||||
|
||||
resumeTimer() {
|
||||
this.isPaused = false;
|
||||
},
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@endpushOnce
|
||||
@@ -0,0 +1,339 @@
|
||||
@props([
|
||||
'type' => 'text',
|
||||
'name' => '',
|
||||
])
|
||||
|
||||
@switch($type)
|
||||
@case('hidden')
|
||||
@case('text')
|
||||
@case('email')
|
||||
@case('password')
|
||||
@case('number')
|
||||
<v-field
|
||||
v-slot="{ field, errors }"
|
||||
{{ $attributes->only(['name', ':name', 'value', ':value', 'v-model', 'rules', ':rules', 'label', ':label']) }}
|
||||
name="{{ $name }}"
|
||||
>
|
||||
<input
|
||||
type="{{ $type }}"
|
||||
name="{{ $name }}"
|
||||
v-bind="field"
|
||||
:class="[errors.length ? 'border !border-red-600 hover:border-red-600' : '']"
|
||||
{{ $attributes->except(['value', ':value', 'v-model', 'rules', ':rules', 'label', ':label'])->merge(['class' => 'w-full rounded border border-gray-200 px-2.5 py-2 text-sm font-normal text-gray-800 transition-all hover:border-gray-400 focus:border-gray-400 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-300 dark:hover:border-gray-400 dark:focus:border-gray-400']) }}
|
||||
/>
|
||||
</v-field>
|
||||
|
||||
@break
|
||||
|
||||
@case('price')
|
||||
<v-field
|
||||
v-slot="{ field, errors }"
|
||||
{{ $attributes->only(['name', ':name', 'value', ':value', 'v-model', 'rules', ':rules', 'label', ':label']) }}
|
||||
name="{{ $name }}"
|
||||
>
|
||||
<div
|
||||
class="flex w-full items-center overflow-hidden rounded-md border text-sm text-gray-600 transition-all focus-within:border-gray-400 hover:border-gray-400 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-300 dark:hover:border-gray-400 dark:focus:border-gray-400"
|
||||
:class="[errors.length ? 'border !border-red-600 hover:border-red-600' : '']"
|
||||
>
|
||||
@if (isset($currency))
|
||||
<span {{ $currency->attributes->merge(['class' => 'py-2.5 text-gray-500 ltr:pl-4 rtl:pr-4']) }}>
|
||||
{{ $currency }}
|
||||
</span>
|
||||
@else
|
||||
<span class="py-2.5 text-gray-500 ltr:pl-4 rtl:pr-4">
|
||||
{{ config('app.currency') }}
|
||||
</span>
|
||||
@endif
|
||||
|
||||
<input
|
||||
type="text"
|
||||
name="{{ $name }}"
|
||||
v-bind="field"
|
||||
{{ $attributes->except(['value', ':value', 'v-model', 'rules', ':rules', 'label', ':label'])->merge(['class' => 'w-full p-2.5 text-sm text-gray-600 dark:bg-gray-900 dark:text-gray-300']) }}
|
||||
/>
|
||||
</div>
|
||||
</v-field>
|
||||
|
||||
@break
|
||||
|
||||
@case('file')
|
||||
<v-field
|
||||
v-slot="{ field, errors, handleChange, handleBlur }"
|
||||
{{ $attributes->only(['name', ':name', 'value', ':value', 'v-model', 'rules', ':rules', 'label', ':label']) }}
|
||||
name="{{ $name }}"
|
||||
>
|
||||
<input
|
||||
type="{{ $type }}"
|
||||
v-bind="{ name: field.name }"
|
||||
:class="[errors.length ? 'border !border-red-600 hover:border-red-600' : '']"
|
||||
{{ $attributes->except(['value', ':value', 'v-model', 'rules', ':rules', 'label', ':label'])->merge(['class' => 'w-full rounded-md border px-3 py-2.5 text-sm text-gray-600 transition-all hover:border-gray-400 focus:border-gray-400 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-300 dark:file:bg-gray-800 dark:file:dark:text-white dark:hover:border-gray-400 dark:focus:border-gray-400']) }}
|
||||
@change="handleChange"
|
||||
@blur="handleBlur"
|
||||
/>
|
||||
</v-field>
|
||||
|
||||
@break
|
||||
|
||||
@case('color')
|
||||
<v-field
|
||||
name="{{ $name }}"
|
||||
v-slot="{ field, errors }"
|
||||
{{ $attributes->except('class') }}
|
||||
>
|
||||
<input
|
||||
type="{{ $type }}"
|
||||
:class="[errors.length ? 'border border-red-500' : '']"
|
||||
v-bind="field"
|
||||
{{ $attributes->except(['value'])->merge(['class' => 'w-full appearance-none rounded-md border text-sm text-gray-600 transition-all hover:border-gray-400 dark:text-gray-300 dark:hover:border-gray-400']) }}
|
||||
>
|
||||
</v-field>
|
||||
@break
|
||||
|
||||
@case('textarea')
|
||||
<v-field
|
||||
v-slot="{ field, errors }"
|
||||
{{ $attributes->only(['name', ':name', 'value', ':value', 'v-model', 'rules', ':rules', 'label', ':label']) }}
|
||||
name="{{ $name }}"
|
||||
>
|
||||
<textarea
|
||||
type="{{ $type }}"
|
||||
name="{{ $name }}"
|
||||
v-bind="field"
|
||||
:class="[errors.length ? 'border !border-red-600 hover:border-red-600' : '']"
|
||||
{{ $attributes->except(['value', ':value', 'v-model', 'rules', ':rules', 'label', ':label'])->merge(['class' => 'w-full rounded border border-gray-200 px-2.5 py-2 text-sm font-normal text-gray-800 transition-all hover:border-gray-400 focus:border-gray-400 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-300 dark:hover:border-gray-400 dark:focus:border-gray-400']) }}
|
||||
>
|
||||
</textarea>
|
||||
|
||||
@if ($attributes->get('tinymce', false) || $attributes->get(':tinymce', false))
|
||||
<x-admin::tinymce
|
||||
:selector="'textarea#' . $attributes->get('id')"
|
||||
::field="field"
|
||||
/>
|
||||
@endif
|
||||
</v-field>
|
||||
|
||||
@break
|
||||
|
||||
@case('date')
|
||||
<v-field
|
||||
v-slot="{ field, errors }"
|
||||
{{ $attributes->only(['name', ':name', 'value', ':value', 'v-model', 'rules', ':rules', 'label', ':label'])->merge(['rules' => 'regex:^\d{4}-\d{2}-\d{2}$']) }}
|
||||
name="{{ $name }}"
|
||||
>
|
||||
<x-admin::flat-picker.date>
|
||||
<input
|
||||
name="{{ $name }}"
|
||||
v-bind="field"
|
||||
:class="[errors.length ? 'border !border-red-600 hover:border-red-600' : '']"
|
||||
{{ $attributes->except(['value', ':value', 'v-model', 'rules', ':rules', 'label', ':label'])->merge(['class' => 'w-full rounded border border-gray-200 px-2.5 py-2 text-sm font-normal text-gray-800 transition-all hover:border-gray-400 focus:border-gray-400 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-300 dark:hover:border-gray-400 dark:focus:border-gray-400']) }}
|
||||
autocomplete="off"
|
||||
/>
|
||||
</x-admin::flat-picker.date>
|
||||
</v-field>
|
||||
|
||||
@break
|
||||
|
||||
@case('datetime')
|
||||
<v-field
|
||||
v-slot="{ field, errors }"
|
||||
{{ $attributes->only(['name', ':name', 'value', ':value', 'v-model', 'rules', ':rules', 'label', ':label'])->merge(['rules' => 'regex:^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$']) }}
|
||||
name="{{ $name }}"
|
||||
>
|
||||
<x-admin::flat-picker.datetime>
|
||||
<input
|
||||
name="{{ $name }}"
|
||||
v-bind="field"
|
||||
:class="[errors.length ? 'border !border-red-600 hover:border-red-600' : '']"
|
||||
{{ $attributes->except(['value', ':value', 'v-model', 'rules', ':rules', 'label', ':label'])->merge(['class' => 'w-full rounded border border-gray-200 px-2.5 py-2 text-sm font-normal text-gray-800 transition-all hover:border-gray-400 focus:border-gray-400 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-300 dark:hover:border-gray-400 dark:focus:border-gray-400']) }}
|
||||
autocomplete="off"
|
||||
>
|
||||
</x-admin::flat-picker.datetime>
|
||||
</v-field>
|
||||
@break
|
||||
|
||||
@case('select')
|
||||
<v-field
|
||||
v-slot="{ field, errors }"
|
||||
{{ $attributes->only(['name', ':name', 'value', ':value', 'v-model', 'rules', ':rules', 'label', ':label']) }}
|
||||
name="{{ $name }}"
|
||||
>
|
||||
<select
|
||||
name="{{ $name }}"
|
||||
v-bind="field"
|
||||
:class="[errors.length ? 'border border-red-500' : '']"
|
||||
{{ $attributes->except(['value', ':value', 'v-model', 'rules', ':rules', 'label', ':label'])->merge(['class' => 'custom-select w-full rounded border border-gray-200 px-2.5 py-2 text-sm font-normal text-gray-800 transition-all hover:border-gray-400 focus:border-gray-400 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-300 dark:hover:border-gray-400']) }}
|
||||
>
|
||||
{{ $slot }}
|
||||
</select>
|
||||
</v-field>
|
||||
|
||||
@break
|
||||
|
||||
@case('multiselect')
|
||||
<v-field
|
||||
as="select"
|
||||
v-slot="{ value }"
|
||||
:class="[errors && errors['{{ $name }}'] ? 'border !border-red-600 hover:border-red-600' : '']"
|
||||
{{ $attributes->except([])->merge(['class' => 'flex w-full flex-col rounded-md border bg-white px-3 py-2.5 text-sm font-normal text-gray-600 transition-all hover:border-gray-400 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-300 dark:hover:border-gray-400']) }}
|
||||
name="{{ $name }}"
|
||||
multiple
|
||||
>
|
||||
{{ $slot }}
|
||||
</v-field>
|
||||
|
||||
@break
|
||||
|
||||
@case('checkbox')
|
||||
<v-field
|
||||
v-slot="{ field }"
|
||||
type="checkbox"
|
||||
class="hidden"
|
||||
{{ $attributes->only(['name', ':name', 'value', ':value', 'v-model', 'rules', ':rules', 'label', ':label', 'key', ':key']) }}
|
||||
name="{{ $name }}"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="{{ $name }}"
|
||||
v-bind="field"
|
||||
class="peer sr-only"
|
||||
{{ $attributes->except(['rules', 'label', ':label', 'key', ':key']) }}
|
||||
/>
|
||||
|
||||
<v-checked-handler
|
||||
:field="field"
|
||||
checked="{{ $attributes->get('checked') }}"
|
||||
>
|
||||
</v-checked-handler>
|
||||
</v-field>
|
||||
|
||||
<label
|
||||
{{
|
||||
$attributes
|
||||
->except(['value', ':value', 'v-model', 'rules', ':rules', 'label', ':label', 'key', ':key'])
|
||||
->merge(['class' => 'text-gray-500 icon-checkbox-outline peer-checked:icon-checkbox-select text-2xl peer-checked:text-blue-600'])
|
||||
->merge(['class' => $attributes->get('disabled') ? 'cursor-not-allowed opacity-70' : 'cursor-pointer'])
|
||||
}}
|
||||
>
|
||||
</label>
|
||||
|
||||
@break
|
||||
|
||||
@case('radio')
|
||||
<v-field
|
||||
type="radio"
|
||||
class="hidden"
|
||||
v-slot="{ field }"
|
||||
{{ $attributes->only(['name', ':name', 'value', ':value', 'v-model', 'rules', ':rules', 'label', ':label', 'key', ':key']) }}
|
||||
name="{{ $name }}"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="{{ $name }}"
|
||||
v-bind="field"
|
||||
{{ $attributes->except(['rules', 'label', ':label', 'key', ':key'])->merge(['class' => 'peer sr-only']) }}
|
||||
/>
|
||||
|
||||
<v-checked-handler
|
||||
class="hidden"
|
||||
:field="field"
|
||||
checked="{{ $attributes->get('checked') }}"
|
||||
>
|
||||
</v-checked-handler>
|
||||
</v-field>
|
||||
|
||||
<label
|
||||
{{ $attributes->except(['value', ':value', 'v-model', 'rules', ':rules', 'label', ':label', 'key', ':key'])->merge(['class' => 'icon-radio-normal peer-checked:icon-radio-selected cursor-pointer text-2xl peer-checked:text-blue-600']) }}
|
||||
>
|
||||
</label>
|
||||
|
||||
@break
|
||||
|
||||
@case('switch')
|
||||
<label class="relative inline-flex cursor-pointer items-center">
|
||||
<v-field
|
||||
type="checkbox"
|
||||
class="hidden"
|
||||
v-slot="{ field }"
|
||||
{{ $attributes->only(['name', ':name', 'value', ':value', 'v-model', 'rules', ':rules', 'label', ':label', 'key', ':key']) }}
|
||||
name="{{ $name }}"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="{{ $name }}"
|
||||
id="{{ $name }}"
|
||||
class="peer sr-only"
|
||||
v-bind="field"
|
||||
{{ $attributes->except(['v-model', 'rules', ':rules', 'label', ':label', 'key', ':key']) }}
|
||||
/>
|
||||
|
||||
<v-checked-handler
|
||||
class="hidden"
|
||||
:field="field"
|
||||
checked="{{ $attributes->get('checked') }}"
|
||||
>
|
||||
</v-checked-handler>
|
||||
</v-field>
|
||||
|
||||
<label
|
||||
class="peer h-5 w-9 cursor-pointer rounded-full bg-gray-200 after:absolute after:top-0.5 after:h-4 after:w-4 after:rounded-full after:border after:border-gray-300 after:bg-white after:transition-all after:content-[''] peer-checked:bg-blue-600 peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-blue-300 after:ltr:left-0.5 peer-checked:after:ltr:translate-x-full after:rtl:right-0.5 peer-checked:after:rtl:-translate-x-full dark:bg-gray-800 dark:after:border-white dark:after:bg-white dark:peer-checked:bg-gray-950"
|
||||
for="{{ $name }}"
|
||||
></label>
|
||||
</label>
|
||||
|
||||
@break
|
||||
|
||||
@case('image')
|
||||
<x-admin::media.images
|
||||
name="{{ $name }}"
|
||||
::class="[errors && errors['{{ $name }}'] ? 'border !border-red-600 hover:border-red-600' : '']"
|
||||
{{ $attributes }}
|
||||
/>
|
||||
|
||||
@break
|
||||
|
||||
@case('inline')
|
||||
<x-admin::form.control-group.controls.inline.text {{ $attributes }}/>
|
||||
|
||||
@break
|
||||
|
||||
@case('custom')
|
||||
<v-field {{ $attributes }}>
|
||||
{{ $slot }}
|
||||
</v-field>
|
||||
|
||||
@break
|
||||
|
||||
@case('tags')
|
||||
<x-admin::form.control-group.controls.tags
|
||||
:name="$name"
|
||||
:data="$attributes->get(':data') ?? $attributes->get('data')"
|
||||
{{ $attributes}}
|
||||
/>
|
||||
@break
|
||||
@endswitch
|
||||
|
||||
@pushOnce('scripts')
|
||||
<script
|
||||
type="text/x-template"
|
||||
id="v-checked-handler-template"
|
||||
>
|
||||
</script>
|
||||
|
||||
<script type="module">
|
||||
app.component('v-checked-handler', {
|
||||
template: '#v-checked-handler-template',
|
||||
|
||||
props: ['field', 'checked'],
|
||||
|
||||
mounted() {
|
||||
if (this.checked == '') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.field.checked = true;
|
||||
|
||||
this.field.onChange();
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@endpushOnce
|
||||
@@ -0,0 +1,16 @@
|
||||
@props([
|
||||
'name' => null,
|
||||
'controlName' => '',
|
||||
])
|
||||
|
||||
<v-error-message
|
||||
{{ $attributes }}
|
||||
name="{{ $name ?? $controlName }}"
|
||||
v-slot="{ message }"
|
||||
>
|
||||
<p
|
||||
{{ $attributes->merge(['class' => 'mt-1 text-xs italic text-red-600']) }}
|
||||
v-text="message"
|
||||
>
|
||||
</p>
|
||||
</v-error-message>
|
||||
@@ -0,0 +1,3 @@
|
||||
<div {{ $attributes->merge(['class' => 'mb-4']) }}>
|
||||
{{ $slot }}
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
<label {{ $attributes->merge(['class' => 'mb-1.5 flex items-center gap-1 text-sm font-normal text-gray-800 dark:text-white']) }}>
|
||||
{{ $slot }}
|
||||
</label>
|
||||
@@ -0,0 +1,40 @@
|
||||
<!--
|
||||
If a component has the as attribute, it indicates that it uses
|
||||
the ajaxified form or some customized slot form.
|
||||
-->
|
||||
@if ($attributes->has('as'))
|
||||
<v-form {{ $attributes }}>
|
||||
{{ $slot }}
|
||||
</v-form>
|
||||
|
||||
<!--
|
||||
Otherwise, a traditional form will be provided with a minimal
|
||||
set of configurations.
|
||||
-->
|
||||
@else
|
||||
@props([
|
||||
'method' => 'POST',
|
||||
])
|
||||
|
||||
@php
|
||||
$method = strtoupper($method);
|
||||
@endphp
|
||||
|
||||
<v-form
|
||||
method="{{ $method === 'GET' ? 'GET' : 'POST' }}"
|
||||
:initial-errors="{{ json_encode($errors->getMessages()) }}"
|
||||
v-slot="{ meta, errors, setValues }"
|
||||
@invalid-submit="onInvalidSubmit"
|
||||
{{ $attributes }}
|
||||
>
|
||||
@unless(in_array($method, ['HEAD', 'GET', 'OPTIONS']))
|
||||
@csrf
|
||||
@endunless
|
||||
|
||||
@if (! in_array($method, ['GET', 'POST']))
|
||||
@method($method)
|
||||
@endif
|
||||
|
||||
{{ $slot }}
|
||||
</v-form>
|
||||
@endif
|
||||
@@ -0,0 +1,113 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html
|
||||
lang="{{ app()->getLocale() }}"
|
||||
dir="{{ in_array(app()->getLocale(), ['fa', 'ar']) ? 'rtl' : 'ltr' }}"
|
||||
>
|
||||
|
||||
<head>
|
||||
<title>{{ $title ?? '' }}</title>
|
||||
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<meta
|
||||
http-equiv="X-UA-Compatible"
|
||||
content="IE=edge"
|
||||
>
|
||||
|
||||
<meta
|
||||
http-equiv="content-language"
|
||||
content="{{ app()->getLocale() }}"
|
||||
>
|
||||
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1"
|
||||
>
|
||||
<meta
|
||||
name="base-url"
|
||||
content="{{ url()->to('/') }}"
|
||||
>
|
||||
|
||||
@stack('meta')
|
||||
|
||||
{{
|
||||
vite()->set(['src/Resources/assets/css/app.css', 'src/Resources/assets/js/app.js'], 'webform')
|
||||
}}
|
||||
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700;800&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=DM+Serif+Display&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
@if ($favicon = core()->getConfigData('general.design.admin_logo.favicon'))
|
||||
<link
|
||||
type="image/x-icon"
|
||||
href="{{ Storage::url($favicon) }}"
|
||||
rel="shortcut icon"
|
||||
sizes="16x16"
|
||||
>
|
||||
@else
|
||||
<link
|
||||
type="image/x-icon"
|
||||
href="{{ vite()->asset('images/favicon.ico') }}"
|
||||
rel="shortcut icon"
|
||||
sizes="16x16"
|
||||
/>
|
||||
@endif
|
||||
|
||||
@stack('styles')
|
||||
|
||||
<style>
|
||||
{!! core()->getConfigData('general.content.custom_scripts.custom_css') !!}
|
||||
</style>
|
||||
|
||||
{!! view_render_event('webform.layout.head') !!}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{!! view_render_event('webform.layout.body.before') !!}
|
||||
|
||||
<div id="app">
|
||||
<!-- Flash Message Blade Component -->
|
||||
<x-web_form::flash-group />
|
||||
|
||||
{!! view_render_event('webform.layout.content.before') !!}
|
||||
|
||||
<!-- Page Content Blade Component -->
|
||||
{{ $slot }}
|
||||
|
||||
{!! view_render_event('webform.layout.content.after') !!}
|
||||
</div>
|
||||
|
||||
{!! view_render_event('webform.layout.body.after') !!}
|
||||
|
||||
@stack('scripts')
|
||||
|
||||
{!! view_render_event('webform.layout.vue-app-mount.before') !!}
|
||||
|
||||
<script>
|
||||
/**
|
||||
* Load event, the purpose of using the event is to mount the application
|
||||
* after all of our Vue components which is present in blade file have
|
||||
* been registered in the app. No matter what app.mount() should be
|
||||
* called in the last.
|
||||
*/
|
||||
window.addEventListener("load", function(event) {
|
||||
app.mount("#app");
|
||||
});
|
||||
</script>
|
||||
|
||||
{!! view_render_event('webform.layout.vue-app-mount.after') !!}
|
||||
|
||||
<script type="text/javascript">
|
||||
{!! core()->getConfigData('general.content.custom_scripts.custom_javascript') !!}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,27 @@
|
||||
<!-- Spinner -->
|
||||
@props(['color' => 'currentColor'])
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
aria-hidden="true"
|
||||
viewBox="0 0 24 24"
|
||||
{{ $attributes->merge(['class' => 'h-5 w-5 animate-spin']) }}
|
||||
>
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="{{ $color }}"
|
||||
stroke-width="4"
|
||||
>
|
||||
</circle>
|
||||
|
||||
<path
|
||||
class="opacity-75"
|
||||
fill="{{ $color }}"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
@@ -0,0 +1,257 @@
|
||||
@foreach ($webForm->attributes as $attribute)
|
||||
@php
|
||||
$parentAttribute = $attribute->attribute;
|
||||
|
||||
$fieldName = $parentAttribute->entity_type . '[' . $parentAttribute->code . ']';
|
||||
|
||||
$validations = $attribute->is_required ? 'required' : '';
|
||||
@endphp
|
||||
|
||||
<x-web_form::form.control-group>
|
||||
<x-web_form::form.control-group.label
|
||||
:for="$fieldName"
|
||||
class="{{ $validations }}"
|
||||
style="color: {{ $webForm->attribute_label_color }} !important;"
|
||||
>
|
||||
{{ $attribute->name ?? $parentAttribute->name }}
|
||||
</x-web_form::form.control-group.label>
|
||||
|
||||
@switch($parentAttribute->type)
|
||||
@case('text')
|
||||
<x-web_form::form.control-group.control
|
||||
type="text"
|
||||
:name="$fieldName"
|
||||
:id="$fieldName"
|
||||
:rules="$validations"
|
||||
:label="$attribute->name ?? $parentAttribute->name"
|
||||
:placeholder="$attribute->placeholder"
|
||||
/>
|
||||
|
||||
<x-web_form::form.control-group.error :control-name="$fieldName" />
|
||||
|
||||
@break
|
||||
|
||||
@case('price')
|
||||
<x-web_form::form.control-group.control
|
||||
type="text"
|
||||
:name="$fieldName"
|
||||
:id="$fieldName"
|
||||
:rules="$validations.'|numeric'"
|
||||
:label="$attribute->name ?? $parentAttribute->name"
|
||||
:placeholder="$attribute->placeholder"
|
||||
/>
|
||||
|
||||
<x-web_form::form.control-group.error :control-name="$fieldName" />
|
||||
|
||||
@break
|
||||
|
||||
@case('email')
|
||||
<x-web_form::form.control-group.control
|
||||
type="email"
|
||||
name="{{ $fieldName }}[0][value]"
|
||||
id="{{ $fieldName }}[0][value]"
|
||||
rules="{{ $validations }}|email"
|
||||
:label="$attribute->name ?? $parentAttribute->name"
|
||||
:placeholder="$attribute->placeholder"
|
||||
/>
|
||||
|
||||
<x-web_form::form.control-group.control
|
||||
type="hidden"
|
||||
name="{{ $fieldName }}[0][label]"
|
||||
id="{{ $fieldName }}[0][label]"
|
||||
rules="required"
|
||||
value="work"
|
||||
/>
|
||||
|
||||
<x-web_form::form.control-group.error control-name="{{ $fieldName }}[0][value]" />
|
||||
|
||||
@break
|
||||
|
||||
@case('checkbox')
|
||||
@php
|
||||
$options = $parentAttribute->lookup_type
|
||||
? app('Webkul\Attribute\Repositories\AttributeRepository')->getLookUpOptions($parentAttribute->lookup_type)
|
||||
: $parentAttribute->options()->orderBy('sort_order')->get();
|
||||
@endphp
|
||||
|
||||
@foreach ($options as $option)
|
||||
<x-web_form::form.control-group class="!mb-2 flex select-none items-center gap-2.5">
|
||||
<x-web_form::form.control-group.control
|
||||
type="checkbox"
|
||||
name="{{ $fieldName }}[]"
|
||||
id="{{ $fieldName }}[]"
|
||||
value="{{ $option->id }}"
|
||||
for="{{ $fieldName }}[]"
|
||||
/>
|
||||
|
||||
<label
|
||||
class="cursor-pointer text-xs font-medium text-gray-600 dark:text-gray-300"
|
||||
for="{{ $fieldName }}[]"
|
||||
>
|
||||
@lang('web_form::app.catalog.attributes.edit.is-required')
|
||||
</label>
|
||||
</x-web_form::form.control-group>
|
||||
@endforeach
|
||||
|
||||
@case('file')
|
||||
@case('image')
|
||||
<x-web_form::form.control-group.control
|
||||
type="file"
|
||||
:name="$fieldName"
|
||||
:id="$fieldName"
|
||||
:rules="$validations"
|
||||
:placeholder="$attribute->placeholder"
|
||||
:label="$attribute->name ?? $parentAttribute->name"
|
||||
/>
|
||||
|
||||
<x-web_form::form.control-group.error control-name="{{ $fieldName }}" />
|
||||
|
||||
@break;
|
||||
|
||||
@case('phone')
|
||||
<x-web_form::form.control-group.control
|
||||
type="text"
|
||||
name="{{ $fieldName }}[0][value]"
|
||||
id="{{ $fieldName }}[0][value]"
|
||||
rules="{{ $validations }}|phone"
|
||||
:label="$attribute->name ?? $parentAttribute->name"
|
||||
:placeholder="$attribute->placeholder"
|
||||
/>
|
||||
|
||||
<x-web_form::form.control-group.control
|
||||
type="hidden"
|
||||
name="{{ $fieldName }}[0][label]"
|
||||
id="{{ $fieldName }}[0][label]"
|
||||
rules="required"
|
||||
value="work"
|
||||
/>
|
||||
|
||||
<x-web_form::form.control-group.error control-name="{{ $fieldName }}[0][value]" />
|
||||
|
||||
@break
|
||||
|
||||
@case('date')
|
||||
<x-web_form::form.control-group.control
|
||||
type="date"
|
||||
:name="$fieldName"
|
||||
:id="$fieldName"
|
||||
:rules="$validations"
|
||||
:label="$attribute->name ?? $parentAttribute->name"
|
||||
:placeholder="$attribute->placeholder"
|
||||
/>
|
||||
|
||||
<x-web_form::form.control-group.error :control-name="$fieldName" />
|
||||
|
||||
@break
|
||||
|
||||
@case('datetime')
|
||||
<x-web_form::form.control-group.control
|
||||
type="datetime"
|
||||
:name="$fieldName"
|
||||
:id="$fieldName"
|
||||
:rules="$validations"
|
||||
:label="$attribute->name ?? $parentAttribute->name"
|
||||
:placeholder="$attribute->placeholder"
|
||||
/>
|
||||
|
||||
<x-web_form::form.control-group.error :control-name="$fieldName" />
|
||||
|
||||
@break
|
||||
|
||||
@case('select')
|
||||
@case('lookup')
|
||||
@php
|
||||
$options = $parentAttribute->lookup_type
|
||||
? app('Webkul\Attribute\Repositories\AttributeRepository')->getLookUpOptions($parentAttribute->lookup_type)
|
||||
: $parentAttribute->options()->orderBy('sort_order')->get();
|
||||
@endphp
|
||||
|
||||
<x-web_form::form.control-group.control
|
||||
type="select"
|
||||
:name="$fieldName"
|
||||
:id="$fieldName"
|
||||
:rules="$validations"
|
||||
:label="$attribute->name ?? $parentAttribute->name"
|
||||
:placeholder="$attribute->placeholder"
|
||||
>
|
||||
@foreach ($options as $option)
|
||||
<option value="{{ $option->id }}">{{ $option->name }}</option>
|
||||
@endforeach
|
||||
</x-web_form::form.control-group.control>
|
||||
|
||||
<x-web_form::form.control-group.error :control-name="$fieldName" />
|
||||
|
||||
@break
|
||||
|
||||
@case('multiselect')
|
||||
@php
|
||||
$options = $parentAttribute->lookup_type
|
||||
? app('Webkul\Attribute\Repositories\AttributeRepository')->getLookUpOptions($parentAttribute->lookup_type)
|
||||
: $parentAttribute->options()->orderBy('sort_order')->get();
|
||||
@endphp
|
||||
|
||||
<x-web_form::form.control-group.control
|
||||
type="select"
|
||||
id="{{ $fieldName }}"
|
||||
name="{{ $fieldName }}[]"
|
||||
:rules="$validations"
|
||||
:label="$attribute->name ?? $parentAttribute->name"
|
||||
:placeholder="$attribute->placeholder"
|
||||
>
|
||||
@foreach ($options as $option)
|
||||
<option value="{{ $option->id }}">{{ $option->name }}</option>
|
||||
@endforeach
|
||||
</x-web_form::form.control-group.control>
|
||||
|
||||
<x-web_form::form.control-group.error :control-name="$fieldName" />
|
||||
|
||||
@break
|
||||
|
||||
@case('checkbox')
|
||||
<div class="checkbox-control">
|
||||
@php
|
||||
$options = $parentAttribute->lookup_type
|
||||
? app('Webkul\Attribute\Repositories\AttributeRepository')->getLookUpOptions($parentAttribute->lookup_type)
|
||||
: $parentAttribute->options()->orderBy('sort_order')->get();
|
||||
@endphp
|
||||
|
||||
@foreach ($options as $option)
|
||||
<span class="checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="{{ $fieldName }}[]"
|
||||
value="{{ $option->id }}"
|
||||
/>
|
||||
|
||||
<label class="checkbox-view" style="display: inline;"></label>
|
||||
{{ $option->name }}
|
||||
</span>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<p
|
||||
id="{{ $fieldName }}[]-error"
|
||||
class="error-message mt-1 text-xs italic text-red-600"
|
||||
></p>
|
||||
|
||||
@break
|
||||
|
||||
@case('boolean')
|
||||
<x-web_form::form.control-group.control
|
||||
type="select"
|
||||
:name="$fieldName"
|
||||
:id="$fieldName"
|
||||
:rules="$validations"
|
||||
:label="$attribute->name ?? $parentAttribute->name"
|
||||
:placeholder="$attribute->placeholder"
|
||||
>
|
||||
<option value="1">Yes</option>
|
||||
<option value="0">No</option>
|
||||
</x-web_form::form.control-group.control>
|
||||
|
||||
<x-web_form::form.control-group.error :control-name="$fieldName" />
|
||||
|
||||
@break
|
||||
@endswitch
|
||||
</x-web_form::form.control-group>
|
||||
@endforeach
|
||||
@@ -0,0 +1,3 @@
|
||||
(function() {
|
||||
document.write(`{!! view('web_form::settings.web-forms.preview', compact('webForm'))->render() !!}`.replaceAll('$', '\$'));
|
||||
})();
|
||||
@@ -0,0 +1,156 @@
|
||||
<x-web_form::layouts>
|
||||
<x-slot:title>
|
||||
{{ $webForm->title }}
|
||||
</x-slot>
|
||||
|
||||
<!-- Web Form -->
|
||||
<v-web-form>
|
||||
<div class="flex h-[100vh] items-center justify-center">
|
||||
<div class="flex flex-col items-center gap-5">
|
||||
<x-web_form::spinner />
|
||||
</div>
|
||||
</div>
|
||||
</v-web-form>
|
||||
|
||||
@pushOnce('scripts')
|
||||
<script
|
||||
type="text/template"
|
||||
id="v-web-form-template"
|
||||
>
|
||||
<div
|
||||
class="flex h-[100vh] items-center justify-center"
|
||||
style="background-color: {{ $webForm->background_color }}"
|
||||
>
|
||||
<div class="flex flex-col items-center gap-5">
|
||||
<!-- Logo -->
|
||||
<img
|
||||
class="w-max"
|
||||
src="{{ vite()->asset('images/logo.svg') }}"
|
||||
alt="{{ config('app.name') }}"
|
||||
/>
|
||||
|
||||
<h1
|
||||
class="text-2xl font-bold"
|
||||
style="color: {{ $webForm->form_title_color }} !important;"
|
||||
>
|
||||
{{ $webForm->title }}
|
||||
</h1>
|
||||
|
||||
<p class="mt-2 text-base text-gray-600">{{ $webForm->description }}</p>
|
||||
|
||||
<div
|
||||
class="box-shadow flex min-w-[300px] flex-col rounded-lg border border-gray-200 bg-white p-4 dark:bg-gray-900"
|
||||
style="background-color: {{ $webForm->form_background_color }}"
|
||||
>
|
||||
{!! view_render_event('web_forms.web_forms.form_controls.before', ['webForm' => $webForm]) !!}
|
||||
|
||||
<!-- Webform Form -->
|
||||
<x-web_form::form
|
||||
v-slot="{ meta, values, errors, handleSubmit }"
|
||||
as="div"
|
||||
ref="modalForm"
|
||||
>
|
||||
<form
|
||||
@submit="handleSubmit($event, create)"
|
||||
ref="webForm"
|
||||
>
|
||||
@include('web_form::settings.web-forms.controls')
|
||||
|
||||
<div class="flex justify-center">
|
||||
<x-web_form::button
|
||||
class="primary-button"
|
||||
:title="$webForm->submit_button_label"
|
||||
::loading="isStoring"
|
||||
::disabled="isStoring"
|
||||
style="background-color: {{ $webForm->form_submit_button_color }} !important"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</x-web_form::form>
|
||||
|
||||
{!! view_render_event('web_forms.web_forms.form_controls.after', ['webForm' => $webForm]) !!}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="module">
|
||||
app.component('v-web-form', {
|
||||
template: '#v-web-form-template',
|
||||
|
||||
data() {
|
||||
return {
|
||||
isStoring: false,
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
create(params, { resetForm, setErrors }) {
|
||||
this.isStoring = true;
|
||||
|
||||
const formData = new FormData(this.$refs.webForm);
|
||||
|
||||
let inputNames = Array.from(formData.keys());
|
||||
|
||||
inputNames = inputNames.reduce((acc, name) => {
|
||||
const dotName = name.replace(/\[([^\]]+)\]/g, '.$1');
|
||||
|
||||
acc[dotName] = name;
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
this.$axios
|
||||
.post('{{ route('admin.settings.web_forms.form_store', $webForm->id) }}', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
})
|
||||
.then(response => {
|
||||
resetForm();
|
||||
|
||||
this.$refs.webForm.reset();
|
||||
|
||||
this.$emitter.emit('add-flash', { type: 'success', message: response.data.message });
|
||||
})
|
||||
.catch(error => {
|
||||
if (error.response.data.redirect) {
|
||||
window.location.href = error.response.data.redirect;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! error.response.data.errors) {
|
||||
this.$emitter.emit('add-flash', { type: 'error', message: error.response.data.message });
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const laravelErrors = error.response.data.errors || {};
|
||||
const mappedErrors = {};
|
||||
|
||||
for (
|
||||
const [dotKey, messages]
|
||||
of Object.entries(laravelErrors)
|
||||
) {
|
||||
const inputName = inputNames[dotKey];
|
||||
|
||||
if (
|
||||
inputName
|
||||
&& messages.length
|
||||
) {
|
||||
mappedErrors[inputName] = messages[0];
|
||||
}
|
||||
}
|
||||
|
||||
setErrors(mappedErrors);
|
||||
})
|
||||
.finally(() => {
|
||||
this.isStoring = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@endPushOnce
|
||||
</x-web_form::layouts>
|
||||
16
packages/Webkul/WebForm/src/Routes/routes.php
Normal file
16
packages/Webkul/WebForm/src/Routes/routes.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Webkul\WebForm\Http\Controllers\WebFormController;
|
||||
|
||||
Route::controller(WebFormController::class)->middleware(['web', 'admin_locale'])->prefix('web-forms')->group(function () {
|
||||
Route::get('forms/{id}/form.js', 'formJS')->name('admin.settings.web_forms.form_js');
|
||||
|
||||
Route::get('forms/{id}/form.html', 'preview')->name('admin.settings.web_forms.preview');
|
||||
|
||||
Route::post('forms/{id}', 'formStore')->name('admin.settings.web_forms.form_store');
|
||||
|
||||
Route::group(['middleware' => ['user']], function () {
|
||||
Route::get('form/{id}/form.html', 'view')->name('admin.settings.web_forms.view');
|
||||
});
|
||||
});
|
||||
33
packages/Webkul/WebForm/src/Rules/PhoneNumber.php
Normal file
33
packages/Webkul/WebForm/src/Rules/PhoneNumber.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Webkul\WebForm\Rules;
|
||||
|
||||
use Illuminate\Contracts\Validation\Rule;
|
||||
|
||||
class PhoneNumber implements Rule
|
||||
{
|
||||
/**
|
||||
* Determine if the validation rule passes.
|
||||
*
|
||||
* @param string $attribute
|
||||
* @param mixed $value
|
||||
* @return bool
|
||||
*/
|
||||
public function passes($attribute, $value)
|
||||
{
|
||||
// This regular expression allows phone numbers with the following conditions:
|
||||
// - The phone number can start with an optional "+" sign.
|
||||
// - After the "+" sign, there should be one or more digits.
|
||||
return preg_match('/^\+?\d+$/', $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation error message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function message()
|
||||
{
|
||||
return __('web_form::app.validations.invalid-phone-number');
|
||||
}
|
||||
}
|
||||
47
packages/Webkul/WebForm/tailwind.config.js
Normal file
47
packages/Webkul/WebForm/tailwind.config.js
Normal file
@@ -0,0 +1,47 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ["./src/Resources/**/*.blade.php", "./src/Resources/**/*.js"],
|
||||
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
|
||||
screens: {
|
||||
"2xl": "1920px",
|
||||
},
|
||||
|
||||
padding: {
|
||||
DEFAULT: "16px",
|
||||
},
|
||||
},
|
||||
|
||||
screens: {
|
||||
sm: "525px",
|
||||
md: "768px",
|
||||
lg: "1024px",
|
||||
xl: "1240px",
|
||||
"2xl": "1920px",
|
||||
},
|
||||
|
||||
extend: {
|
||||
colors: {
|
||||
brandColor: '#0E90D9',
|
||||
},
|
||||
|
||||
fontFamily: {
|
||||
inter: ['Inter'],
|
||||
icon: ['icomoon']
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
darkMode: 'class',
|
||||
|
||||
plugins: [],
|
||||
|
||||
safelist: [
|
||||
{
|
||||
pattern: /icon-/,
|
||||
}
|
||||
]
|
||||
};
|
||||
46
packages/Webkul/WebForm/vite.config.js
Normal file
46
packages/Webkul/WebForm/vite.config.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import { defineConfig, loadEnv } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import laravel from "laravel-vite-plugin";
|
||||
import path from "path";
|
||||
|
||||
export default defineConfig(({ mode }) => {
|
||||
const envDir = "../../../";
|
||||
|
||||
Object.assign(process.env, loadEnv(mode, envDir));
|
||||
|
||||
return {
|
||||
build: {
|
||||
emptyOutDir: true,
|
||||
},
|
||||
|
||||
envDir,
|
||||
|
||||
server: {
|
||||
host: process.env.VITE_HOST || "localhost",
|
||||
port: process.env.VITE_PORT || 5174,
|
||||
},
|
||||
|
||||
plugins: [
|
||||
vue(),
|
||||
|
||||
laravel({
|
||||
hotFile: "../../../public/webform-vite.hot",
|
||||
publicDirectory: "../../../public",
|
||||
buildDirectory: "webform/build",
|
||||
input: [
|
||||
"src/Resources/assets/css/app.css",
|
||||
"src/Resources/assets/js/app.js",
|
||||
],
|
||||
refresh: true,
|
||||
}),
|
||||
],
|
||||
|
||||
experimental: {
|
||||
renderBuiltUrl(filename, { hostId, hostType, type }) {
|
||||
if (hostType === "css") {
|
||||
return path.basename(filename);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user