feat: ✨ add image cropper to dynamic pages
This commit is contained in:
parent
5fb0263186
commit
3e464e6d1f
24 changed files with 1799 additions and 279 deletions
|
|
@ -14,6 +14,7 @@ enum DynamicFieldTypes: int
|
|||
case boolean = 4;
|
||||
case url = 5;
|
||||
case email = 6;
|
||||
case image = 7;
|
||||
|
||||
/**
|
||||
* Labels definition.
|
||||
|
|
@ -27,6 +28,7 @@ enum DynamicFieldTypes: int
|
|||
self::boolean->name => 'enums.dynamic-field-types.boolean',
|
||||
self::url->name => 'enums.dynamic-field-types.url',
|
||||
self::email->name => 'enums.dynamic-field-types.email',
|
||||
self::image->name => 'enums.dynamic-field-types.image',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -39,6 +41,7 @@ enum DynamicFieldTypes: int
|
|||
self::boolean->name => 'required|boolean',
|
||||
self::url->name => 'required|url:https',
|
||||
self::email->name => 'required|email:rfc,strict,dns,spoof,filter',
|
||||
self::image->name => 'required|file|mimes:jpg,jpeg,png,pdf|max:5120|dimensions:width=100,height=100',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
|||
103
app/Helpers/DynamicPageHelper.php
Normal file
103
app/Helpers/DynamicPageHelper.php
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use App\Enums\DynamicFieldTypes;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Uwil\LaravelCommon\Helpers\FileHelper;
|
||||
|
||||
class DynamicPageHelper
|
||||
{
|
||||
/**
|
||||
* Remove imgs from wysiwyg OR dynamic wysiwyg OR image input.
|
||||
*
|
||||
* @param Model $model
|
||||
* @param bool $forceDeleteImage In forceDelete event we need to ensure all the images will be destroyed
|
||||
* @return void
|
||||
*/
|
||||
public static function removeImgs(Model $model, $forceDeleteImage = false): void
|
||||
{
|
||||
$locale = config('app.locale');
|
||||
$model = $model->load('config');
|
||||
|
||||
$currentContent = json_decode($model->getAttributes()['content'], true);
|
||||
$originalContent = $model->getOriginal('content');
|
||||
|
||||
$currentContentLocal = is_array($currentContent[$locale]) ? $currentContent[$locale] : ["text_" => $currentContent[$locale]];
|
||||
$originalContentLocal = is_array($originalContent[$locale]) ? $originalContent[$locale] : ["text_" => $originalContent[$locale]];
|
||||
static::removeDynamicImages($model, $originalContentLocal, $currentContentLocal, $forceDeleteImage);
|
||||
static::removeWysiwygImages($originalContentLocal, $currentContentLocal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove imgs from image inputs.
|
||||
*
|
||||
* @param Model $model
|
||||
* @param array $oldContent
|
||||
* @param array $newContent
|
||||
* @param bool $forceDeleteImage In forceDelete event we need to ensure all the images will be destroyed
|
||||
* @return void
|
||||
*/
|
||||
protected static function removeDynamicImages(Model $model, array $oldContent, array $newContent, bool $forceDeleteImage): void
|
||||
{
|
||||
$prefix = DynamicFieldTypes::image->name . '_';
|
||||
$allKeys = static::getDynamicFieldKeys($oldContent, $newContent, $prefix);
|
||||
foreach ($allKeys as $key) {
|
||||
$old = $oldContent[$key] ?? null;
|
||||
$new = $newContent[$key] ?? null;
|
||||
// Cas 1 : Valeurs identiques
|
||||
if ((is_string($old) === is_string($new)) && $forceDeleteImage) {
|
||||
// In this case we need to remove the actual file, not the old as we do in other conditions
|
||||
FileHelper::removeFile($model, "content.$key");
|
||||
}
|
||||
// // Cas 2 : ancienne = string, nouvelle = null → suppression
|
||||
if (is_string($old) && is_null($new)) {
|
||||
// Action suppression
|
||||
FileHelper::removeOldFile($model, "content.$key");
|
||||
continue;
|
||||
}
|
||||
|
||||
// // Cas 3 : ancienne = null, nouvelle = string → ajout
|
||||
|
||||
// // Cas 4 : ancienne = string, nouvelle = autre string → remplacement
|
||||
if (is_string($old) && is_string($new) && $old !== $new) {
|
||||
FileHelper::removeOldFile($model, "content.$key");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove imgs from wysiwygs.
|
||||
*
|
||||
* @param array $oldContent
|
||||
* @param array $newContent
|
||||
* @return void
|
||||
*/
|
||||
protected static function removeWysiwygImages(array $oldContent, array $newContent): void
|
||||
{
|
||||
$prefix = DynamicFieldTypes::text->name . '_';
|
||||
|
||||
static::getDynamicFieldKeys($oldContent, $newContent, $prefix)
|
||||
->each(function ($key) use ($oldContent) {
|
||||
$old = $oldContent[$key] ?? null;
|
||||
FileHelper::removeOldModelFilesFromHtml('', $old);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the field keys of wysiwygs or images field input and group them.
|
||||
*
|
||||
* @param array $oldContent
|
||||
* @param array $newContent
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
protected static function getDynamicFieldKeys(array $oldContent, array $newContent, string $prefix): \Illuminate\Support\Collection
|
||||
{
|
||||
$filter = fn($value, $key) => Str::startsWith($key, $prefix);
|
||||
$old = collect($oldContent)->filter($filter);
|
||||
$new = collect($newContent)->filter($filter);
|
||||
return $new->keys()->merge($old->keys())->unique();
|
||||
}
|
||||
}
|
||||
|
|
@ -105,7 +105,8 @@ class DynamicPageController extends Controller
|
|||
{
|
||||
return DB::transaction(function () use ($request) {
|
||||
$dynamicPageModel = new DynamicPage();
|
||||
$dynamicPageModel->fill($request->validated());
|
||||
|
||||
$dynamicPageModel->inertiaFill($request->validated());
|
||||
|
||||
if ($dynamicPageModel->saveOrFail()) {
|
||||
return redirect()->route('back.dynamic-pages.edit', ['dynamic_page' => $dynamicPageModel])
|
||||
|
|
@ -147,7 +148,7 @@ class DynamicPageController extends Controller
|
|||
DynamicPage $dynamic_page
|
||||
): \Illuminate\Http\RedirectResponse {
|
||||
return DB::transaction(function () use ($request, $dynamic_page) {
|
||||
$dynamic_page->fill($request->validated());
|
||||
$dynamic_page->inertiaFill($request->validated());
|
||||
|
||||
if ($dynamic_page->saveOrFail()) {
|
||||
return redirect()->route('back.dynamic-pages.edit', ['dynamic_page' => $dynamic_page])
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ namespace App\Http\Controllers\Back;
|
|||
|
||||
use App\Http\Requests\Back\PageConfigs\StorePageConfigRequest;
|
||||
use App\Http\Requests\Back\PageConfigs\UpdatePageConfigRequest;
|
||||
use App\Models\DynamicPage;
|
||||
use App\Models\PageConfig;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ class HandleInertiaRequests extends Middleware
|
|||
'appEnv' => [
|
||||
'mode' => config('app.env'),
|
||||
'name' => config('app.name'),
|
||||
'url' => config('app.url'),
|
||||
'locale' => app()->getLocale(),
|
||||
'locales' => config('app.locales'),
|
||||
'fallbackLocale' => app()->getFallbackLocale(),
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ namespace App\Http\Requests\Back;
|
|||
|
||||
use App\Enums\DynamicFieldTypes;
|
||||
use App\Models\PageConfig;
|
||||
use App\Traits\Requests\HasInertiaPicture;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
|
@ -11,6 +12,8 @@ use Uwil\LaravelCommon\Helpers\ToolboxHelper;
|
|||
|
||||
abstract class UpdateDynamicFieldRequest extends FormRequest
|
||||
{
|
||||
use HasInertiaPicture;
|
||||
|
||||
/** @var \App\Models\PageConfig|null $configModel */
|
||||
private $configModel = null;
|
||||
|
||||
|
|
@ -35,6 +38,7 @@ abstract class UpdateDynamicFieldRequest extends FormRequest
|
|||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->configModel = PageConfig::query()->whereKey($this->input('config_id'))->firstOrFail();
|
||||
$this->mergeInertiaPicture('content');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -60,6 +64,7 @@ abstract class UpdateDynamicFieldRequest extends FormRequest
|
|||
$mergeRules = $this->compileValidate('required|string|min:15', $input);
|
||||
$mergeRules = collect($mergeRules)
|
||||
->map(function ($rule) {
|
||||
// TODO: check pour utiliser la rule wysiwyg length.
|
||||
if (\strpos($rule, 'max:') !== false) {
|
||||
$max = \intval(str_replace('max:', '', $rule));
|
||||
return function ($attribute, $value, $fail) use ($max) {
|
||||
|
|
@ -112,6 +117,12 @@ abstract class UpdateDynamicFieldRequest extends FormRequest
|
|||
$rules = array_merge($rules, [
|
||||
$ruleKey => $this->compileValidate('required|email|min:3', $input)
|
||||
]);
|
||||
case DynamicFieldTypes::image->value:
|
||||
$defaultRules = 'required|file|mimes:jpg,jpeg,png,pdf|max:5120|dimensions:width=100,height=100';
|
||||
$imgRules = $this->compileValidate($defaultRules, $input);
|
||||
$rules = array_merge($rules, [
|
||||
$ruleKey => $this->pictureRules($ruleKey, implode("|", $imgRules)),
|
||||
]);
|
||||
break;
|
||||
} //end switch
|
||||
} //end foreach
|
||||
|
|
@ -138,32 +149,22 @@ abstract class UpdateDynamicFieldRequest extends FormRequest
|
|||
/**
|
||||
* Merge native validations and custom validations.
|
||||
*
|
||||
* @param string|array $inputTypeValidate
|
||||
* @param string|array $defaultValidations
|
||||
* @param array $input
|
||||
* @return array
|
||||
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
|
||||
*/
|
||||
private function compileValidate(string|array $inputTypeValidate, array $input): array
|
||||
private function compileValidate(string|array $defaultValidations, array $input): array
|
||||
{
|
||||
// phpcs:enable
|
||||
if (\is_string($inputTypeValidate)) {
|
||||
$inputTypeValidate = explode('|', $inputTypeValidate);
|
||||
}
|
||||
/** @var array|string */
|
||||
$inputValidate = \collect($input)->get('validation', $input);
|
||||
$inputValidate = \is_string($inputValidate) ? explode('|', $inputValidate) : $inputValidate;
|
||||
foreach ($inputValidate as $validate) {
|
||||
if (!\in_array($validate, $inputTypeValidate)) {
|
||||
$inputTypeValidate[] = $validate;
|
||||
}
|
||||
}
|
||||
$newValidations = $this->mergeValidations($defaultValidations, $input);
|
||||
|
||||
$rules = \collect($inputTypeValidate)->unique()->sort(function ($valueA, $valueB) {
|
||||
// Get unique rule and priorise important rules first.
|
||||
$rules = \collect($newValidations)->unique()->sort(function ($valueA, $valueB) {
|
||||
$valueA = $this->ruleHasToComeFirst($valueA);
|
||||
$valueB = $this->ruleHasToComeFirst($valueB);
|
||||
return $valueA - $valueB;
|
||||
});
|
||||
|
||||
// Conditions the rules.
|
||||
$hasRequire = $rules->first(fn($rule) => $rule === 'required') ? true : false;
|
||||
$hasRequireCond = $rules->first(fn($rule) => \strpos($rule, 'required_') !== false) ? true : false;
|
||||
$hasNullable = $rules->first(fn($rule) => \strpos($rule, 'nullable') !== false) ? true : false;
|
||||
|
|
@ -173,9 +174,55 @@ abstract class UpdateDynamicFieldRequest extends FormRequest
|
|||
if ($hasRequire and ($hasRequireCond or $hasNullable or $hasSometime or $hasPresent)) {
|
||||
$rules = $rules->reject(fn($rule) => $rule === 'required');
|
||||
}
|
||||
|
||||
return $rules->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge new validations into default validations.
|
||||
*
|
||||
* @param string|array $defaultValidations
|
||||
* @param array $input
|
||||
* @return array
|
||||
*/
|
||||
private function mergeValidations($defaultValidations, $input): array
|
||||
{
|
||||
if (\is_string($defaultValidations)) {
|
||||
$defaultValidations = explode('|', $defaultValidations);
|
||||
}
|
||||
/** @var array|string $inputValidations */
|
||||
$inputValidations = \collect($input)->get('validation', $input);
|
||||
$inputValidations = \is_string($inputValidations) ? explode('|', $inputValidations) : $inputValidations;
|
||||
|
||||
foreach ($inputValidations as $validate) {
|
||||
// Split a string by a special character.
|
||||
$parts = explode(':', $validate, 2);
|
||||
|
||||
// Check if there is a special character.
|
||||
if (count($parts) > 1) {
|
||||
[$ruleName, $ruleValue] = $parts;
|
||||
|
||||
// Deletes any old rule that starts with “ruleName:”.
|
||||
$defaultValidations = collect($defaultValidations)
|
||||
->reject(function ($existingRule) use ($ruleName) {
|
||||
return is_string($existingRule) && str_starts_with($existingRule, sprintf('%s:', $ruleName));
|
||||
})
|
||||
->values()
|
||||
->all();
|
||||
|
||||
// Add the new rule.
|
||||
$defaultValidations[] = $validate;
|
||||
} else {
|
||||
// Check if rule without special character already exist.
|
||||
if (!in_array($validate, $defaultValidations)) {
|
||||
// Add the input rule.
|
||||
$defaultValidations[] = $validate;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $defaultValidations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the rule need to come first ?
|
||||
*
|
||||
|
|
|
|||
|
|
@ -3,12 +3,15 @@
|
|||
namespace App\Models;
|
||||
|
||||
use App\Contracts\TranslatableModel;
|
||||
use App\Enums\DynamicFieldTypes;
|
||||
use App\Enums\DynamicPages\PageStatusEnum;
|
||||
use App\Helpers\DynamicPageHelper;
|
||||
use App\Helpers\TranslationHelper;
|
||||
use App\Traits\Models\HasTranslations;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
|
@ -69,6 +72,24 @@ class DynamicPage extends Model implements TranslatableModel
|
|||
'content',
|
||||
];
|
||||
|
||||
public function inertiaFill(array $attributes)
|
||||
{
|
||||
if (!empty($attributes['content'])) {
|
||||
$attributes['content'] = collect($attributes['content'])
|
||||
->map(function ($value, $key) {
|
||||
if (
|
||||
Str::startsWith($key, DynamicFieldTypes::image->name . '_') &&
|
||||
$value instanceof UploadedFile
|
||||
) {
|
||||
return FileHelper::storeFile($this, $value);
|
||||
}
|
||||
return $value;
|
||||
})
|
||||
->all();
|
||||
}
|
||||
parent::fill($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform any actions required after the model boots.
|
||||
*
|
||||
|
|
@ -87,7 +108,7 @@ class DynamicPage extends Model implements TranslatableModel
|
|||
});
|
||||
static::updated(function (self $model) {
|
||||
self::resetCache();
|
||||
self::removeImgs($model);
|
||||
DynamicPageHelper::removeImgs($model);
|
||||
});
|
||||
static::deleted(function () {
|
||||
self::resetCache();
|
||||
|
|
@ -105,7 +126,7 @@ class DynamicPage extends Model implements TranslatableModel
|
|||
});
|
||||
static::forceDeleted(function (self $model) {
|
||||
self::resetCache();
|
||||
self::removeImgs($model);
|
||||
DynamicPageHelper::removeImgs($model, true);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -128,25 +149,6 @@ class DynamicPage extends Model implements TranslatableModel
|
|||
self::validateFallbackTranslations($model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove imgs from wysiwyg OR dynamic wysiwyg.
|
||||
*
|
||||
* @param self $model
|
||||
* @return void
|
||||
*/
|
||||
public static function removeImgs(self $model): void
|
||||
{
|
||||
$locale = config('app.locale');
|
||||
$model = $model->load('config');
|
||||
$currentContent = json_decode($model->getAttributes()['content'], true);
|
||||
$originalContent = $model->getOriginal('content');
|
||||
|
||||
$currentHtml = self::flattenContent($currentContent[$locale] ?? null);
|
||||
$originalHtml = self::flattenContent($originalContent[$locale] ?? null);
|
||||
|
||||
FileHelper::removeOldModelFilesFromHtml($currentHtml, $originalHtml);
|
||||
}
|
||||
|
||||
/**
|
||||
* Concat all wysiwyg strings in only one.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -75,8 +75,11 @@ class PageConfig extends Model implements TranslatableModel
|
|||
static::deleted(function (self $model) {
|
||||
$model->pages()->delete();
|
||||
});
|
||||
static::forceDeleted(function (self $model) {
|
||||
$model->pages()->forceDelete();
|
||||
static::forceDeleting(function (self $model) {
|
||||
$pages = $model->pages()->withTrashed()->get();
|
||||
$pages->each(function ($page) {
|
||||
$page->forceDelete();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
64
app/Traits/Requests/HasInertiaPicture.php
Normal file
64
app/Traits/Requests/HasInertiaPicture.php
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
namespace App\Traits\Requests;
|
||||
|
||||
trait HasInertiaPicture
|
||||
{
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*
|
||||
* @param string $field
|
||||
* @return void
|
||||
*/
|
||||
protected function mergeInertiaPicture(string $field = 'content'): void
|
||||
{
|
||||
// Si rien à merger (convertedFiles est null ou ne contient pas le champ), on return en douceur
|
||||
if (empty($this->convertedFiles) || empty($this->convertedFiles[$field])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$converted = $this->convertedFiles[$field]; // Ce qu'on veut merger
|
||||
|
||||
// Si l'input du champ n'existe pas, on le crée avec converted
|
||||
if (!$this->has($field)) {
|
||||
$this->merge([$field => $converted]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Si l'input existe et que converted a aussi des données => merge récursif
|
||||
$original = $this->input($field);
|
||||
|
||||
// On vérifie que les deux sont bien des tableaux (sinon array_merge_recursive plantera dans le bueno)
|
||||
if (!is_array($original)) {
|
||||
$original = [$original];
|
||||
}
|
||||
|
||||
if (!is_array($converted)) {
|
||||
$converted = [$converted];
|
||||
}
|
||||
|
||||
$merged = array_merge_recursive($original, $converted);
|
||||
$this->merge([$field => $merged]);
|
||||
}
|
||||
|
||||
protected function pictureRules(string $field = "picture", string $rules)
|
||||
{
|
||||
if (is_string($this->$field) && (\request()->method() === "PUT")) {
|
||||
$isNullable = \strpos($rules, 'nullable');
|
||||
$extensions = explode("|", explode("|mimes:", $rules)[1])[0];
|
||||
$allowedExtensions = explode(',', $extensions); // Ex: "jpg,png" → ['jpg', 'png']
|
||||
return [
|
||||
$isNullable ? 'nullable' : 'required',
|
||||
'string',
|
||||
'regex:/^storage\/modelfiles\/+[a-zA-Z0-9_]*\/+[0-9]{2}\/+[0-9]{2}\/+[0-9]{2}\/+[a-zA-Z0-9]{2}\/+[a-zA-Z0-9_-]*/',
|
||||
function ($attribute, $value, $fail) use ($allowedExtensions) {
|
||||
$extension = pathinfo($value, PATHINFO_EXTENSION);
|
||||
if (!in_array(strtolower($extension), $allowedExtensions)) {
|
||||
$fail("Le fichier doit avoir une extension valide : " . implode(', ', $allowedExtensions));
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
return $rules;
|
||||
}
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
"spatie/laravel-translatable": "^6.11",
|
||||
"tightenco/ziggy": "^2.5",
|
||||
"uwil/laravel-backend": "^3.0",
|
||||
"uwil/laravel-common": "^4.1.1"
|
||||
"uwil/laravel-common": "^4.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"fakerphp/faker": "^1.23",
|
||||
|
|
|
|||
301
composer.lock
generated
301
composer.lock
generated
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "40d7c92cb446269a04483548cf3ddc61",
|
||||
"content-hash": "7b52ba13bb6f291b8488e22d72ccecbc",
|
||||
"packages": [
|
||||
{
|
||||
"name": "24slides/laravel-saml2",
|
||||
|
|
@ -1314,16 +1314,16 @@
|
|||
},
|
||||
{
|
||||
"name": "inertiajs/inertia-laravel",
|
||||
"version": "v2.0.7",
|
||||
"version": "v2.0.10",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/inertiajs/inertia-laravel.git",
|
||||
"reference": "a08aea5a16fed187dcf815c9037b62cc544ab809"
|
||||
"reference": "07da425d58a3a0e3ace9c296e67bd897a6e47009"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/inertiajs/inertia-laravel/zipball/a08aea5a16fed187dcf815c9037b62cc544ab809",
|
||||
"reference": "a08aea5a16fed187dcf815c9037b62cc544ab809",
|
||||
"url": "https://api.github.com/repos/inertiajs/inertia-laravel/zipball/07da425d58a3a0e3ace9c296e67bd897a6e47009",
|
||||
"reference": "07da425d58a3a0e3ace9c296e67bd897a6e47009",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -1378,9 +1378,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/inertiajs/inertia-laravel/issues",
|
||||
"source": "https://github.com/inertiajs/inertia-laravel/tree/v2.0.7"
|
||||
"source": "https://github.com/inertiajs/inertia-laravel/tree/v2.0.10"
|
||||
},
|
||||
"time": "2025-09-24T16:49:48+00:00"
|
||||
"time": "2025-09-28T21:21:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "intervention/gif",
|
||||
|
|
@ -1650,16 +1650,16 @@
|
|||
},
|
||||
{
|
||||
"name": "laravel/framework",
|
||||
"version": "v12.31.1",
|
||||
"version": "v12.33.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/framework.git",
|
||||
"reference": "281b711710c245dd8275d73132e92635be3094df"
|
||||
"reference": "124efc5f09d4668a4dc13f94a1018c524a58bcb1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/281b711710c245dd8275d73132e92635be3094df",
|
||||
"reference": "281b711710c245dd8275d73132e92635be3094df",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/124efc5f09d4668a4dc13f94a1018c524a58bcb1",
|
||||
"reference": "124efc5f09d4668a4dc13f94a1018c524a58bcb1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -1687,7 +1687,6 @@
|
|||
"monolog/monolog": "^3.0",
|
||||
"nesbot/carbon": "^3.8.4",
|
||||
"nunomaduro/termwind": "^2.0",
|
||||
"phiki/phiki": "^2.0.0",
|
||||
"php": "^8.2",
|
||||
"psr/container": "^1.1.1|^2.0.1",
|
||||
"psr/log": "^1.0|^2.0|^3.0",
|
||||
|
|
@ -1866,7 +1865,7 @@
|
|||
"issues": "https://github.com/laravel/framework/issues",
|
||||
"source": "https://github.com/laravel/framework"
|
||||
},
|
||||
"time": "2025-09-23T15:33:04+00:00"
|
||||
"time": "2025-10-07T14:30:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/prompts",
|
||||
|
|
@ -1990,16 +1989,16 @@
|
|||
},
|
||||
{
|
||||
"name": "laravel/telescope",
|
||||
"version": "v5.12.0",
|
||||
"version": "v5.14.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/telescope.git",
|
||||
"reference": "b4fb98b57f4aeb48b74f79aa7c684a20e302dacd"
|
||||
"reference": "99e9487604e67424d07b83b5ab3401850d237685"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/telescope/zipball/b4fb98b57f4aeb48b74f79aa7c684a20e302dacd",
|
||||
"reference": "b4fb98b57f4aeb48b74f79aa7c684a20e302dacd",
|
||||
"url": "https://api.github.com/repos/laravel/telescope/zipball/99e9487604e67424d07b83b5ab3401850d237685",
|
||||
"reference": "99e9487604e67424d07b83b5ab3401850d237685",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -2053,9 +2052,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/telescope/issues",
|
||||
"source": "https://github.com/laravel/telescope/tree/v5.12.0"
|
||||
"source": "https://github.com/laravel/telescope/tree/v5.14.0"
|
||||
},
|
||||
"time": "2025-09-18T15:08:51+00:00"
|
||||
"time": "2025-10-06T14:52:49+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/tinker",
|
||||
|
|
@ -2125,16 +2124,16 @@
|
|||
},
|
||||
{
|
||||
"name": "ldc/ldccrmv4client",
|
||||
"version": "4.1.4",
|
||||
"version": "4.1.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "git@lab.s-web.fr:ldc-web/projets/crm-client.git",
|
||||
"reference": "9aa66a8b3f62c401b0fd43616ba5fd52fe15541a"
|
||||
"reference": "9725ed12914d75c39f0ee66990a1bff6c77f457c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://lab.s-web.fr/api/v4/projects/26/packages/composer/archives/ldc/ldccrmv4client.zip?sha=9aa66a8b3f62c401b0fd43616ba5fd52fe15541a",
|
||||
"reference": "9aa66a8b3f62c401b0fd43616ba5fd52fe15541a",
|
||||
"url": "https://lab.s-web.fr/api/v4/projects/26/packages/composer/archives/ldc/ldccrmv4client.zip?sha=9725ed12914d75c39f0ee66990a1bff6c77f457c",
|
||||
"reference": "9725ed12914d75c39f0ee66990a1bff6c77f457c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -2164,21 +2163,16 @@
|
|||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jérémy Munsch",
|
||||
"email": "jeremy.munsch@ldc.fr",
|
||||
"homepage": "https://jeremydev.ovh"
|
||||
}
|
||||
],
|
||||
"description": "LDC CRM Client V4",
|
||||
"homepage": "https://github.com/kwaadpepper",
|
||||
"keywords": [
|
||||
"CRM",
|
||||
"Client",
|
||||
"LDC",
|
||||
"Laravel"
|
||||
]
|
||||
],
|
||||
"support": {
|
||||
"email": "digital@ldc.fr"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "league/commonmark",
|
||||
|
|
@ -3531,77 +3525,6 @@
|
|||
],
|
||||
"time": "2025-05-25T14:28:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phiki/phiki",
|
||||
"version": "v2.0.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phikiphp/phiki.git",
|
||||
"reference": "160785c50c01077780ab217e5808f00ab8f05a13"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phikiphp/phiki/zipball/160785c50c01077780ab217e5808f00ab8f05a13",
|
||||
"reference": "160785c50c01077780ab217e5808f00ab8f05a13",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
"league/commonmark": "^2.5.3",
|
||||
"php": "^8.2",
|
||||
"psr/simple-cache": "^3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"illuminate/support": "^11.45",
|
||||
"laravel/pint": "^1.18.1",
|
||||
"orchestra/testbench": "^9.15",
|
||||
"pestphp/pest": "^3.5.1",
|
||||
"phpstan/extension-installer": "^1.4.3",
|
||||
"phpstan/phpstan": "^2.0",
|
||||
"symfony/var-dumper": "^7.1.6"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Phiki\\Adapters\\Laravel\\PhikiServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Phiki\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ryan Chandler",
|
||||
"email": "support@ryangjchandler.co.uk",
|
||||
"homepage": "https://ryangjchandler.co.uk",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "Syntax highlighting using TextMate grammars in PHP.",
|
||||
"support": {
|
||||
"issues": "https://github.com/phikiphp/phiki/issues",
|
||||
"source": "https://github.com/phikiphp/phiki/tree/v2.0.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/sponsors/ryangjchandler",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://buymeacoffee.com/ryangjchandler",
|
||||
"type": "other"
|
||||
}
|
||||
],
|
||||
"time": "2025-09-20T17:21:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpoption/phpoption",
|
||||
"version": "1.9.4",
|
||||
|
|
@ -4927,16 +4850,16 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
"version": "v7.3.3",
|
||||
"version": "v7.3.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/console.git",
|
||||
"reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7"
|
||||
"reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7",
|
||||
"reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/2b9c5fafbac0399a20a2e82429e2bd735dcfb7db",
|
||||
"reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -5001,7 +4924,7 @@
|
|||
"terminal"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/console/tree/v7.3.3"
|
||||
"source": "https://github.com/symfony/console/tree/v7.3.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -5021,7 +4944,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-08-25T06:35:40+00:00"
|
||||
"time": "2025-09-22T15:31:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/css-selector",
|
||||
|
|
@ -5157,16 +5080,16 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/error-handler",
|
||||
"version": "v7.3.2",
|
||||
"version": "v7.3.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/error-handler.git",
|
||||
"reference": "0b31a944fcd8759ae294da4d2808cbc53aebd0c3"
|
||||
"reference": "99f81bc944ab8e5dae4f21b4ca9972698bbad0e4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/error-handler/zipball/0b31a944fcd8759ae294da4d2808cbc53aebd0c3",
|
||||
"reference": "0b31a944fcd8759ae294da4d2808cbc53aebd0c3",
|
||||
"url": "https://api.github.com/repos/symfony/error-handler/zipball/99f81bc944ab8e5dae4f21b4ca9972698bbad0e4",
|
||||
"reference": "99f81bc944ab8e5dae4f21b4ca9972698bbad0e4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -5214,7 +5137,7 @@
|
|||
"description": "Provides tools to manage errors and ease debugging PHP code",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/error-handler/tree/v7.3.2"
|
||||
"source": "https://github.com/symfony/error-handler/tree/v7.3.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -5234,7 +5157,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-07-07T08:17:57+00:00"
|
||||
"time": "2025-09-11T10:12:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/event-dispatcher",
|
||||
|
|
@ -5466,16 +5389,16 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/http-foundation",
|
||||
"version": "v7.3.3",
|
||||
"version": "v7.3.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/http-foundation.git",
|
||||
"reference": "7475561ec27020196c49bb7c4f178d33d7d3dc00"
|
||||
"reference": "c061c7c18918b1b64268771aad04b40be41dd2e6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/7475561ec27020196c49bb7c4f178d33d7d3dc00",
|
||||
"reference": "7475561ec27020196c49bb7c4f178d33d7d3dc00",
|
||||
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/c061c7c18918b1b64268771aad04b40be41dd2e6",
|
||||
"reference": "c061c7c18918b1b64268771aad04b40be41dd2e6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -5525,7 +5448,7 @@
|
|||
"description": "Defines an object-oriented layer for the HTTP specification",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/http-foundation/tree/v7.3.3"
|
||||
"source": "https://github.com/symfony/http-foundation/tree/v7.3.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -5545,20 +5468,20 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-08-20T08:04:18+00:00"
|
||||
"time": "2025-09-16T08:38:17+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-kernel",
|
||||
"version": "v7.3.3",
|
||||
"version": "v7.3.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/http-kernel.git",
|
||||
"reference": "72c304de37e1a1cec6d5d12b81187ebd4850a17b"
|
||||
"reference": "b796dffea7821f035047235e076b60ca2446e3cf"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/72c304de37e1a1cec6d5d12b81187ebd4850a17b",
|
||||
"reference": "72c304de37e1a1cec6d5d12b81187ebd4850a17b",
|
||||
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/b796dffea7821f035047235e076b60ca2446e3cf",
|
||||
"reference": "b796dffea7821f035047235e076b60ca2446e3cf",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -5643,7 +5566,7 @@
|
|||
"description": "Provides a structured process for converting a Request into a Response",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/http-kernel/tree/v7.3.3"
|
||||
"source": "https://github.com/symfony/http-kernel/tree/v7.3.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -5663,20 +5586,20 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-08-29T08:23:45+00:00"
|
||||
"time": "2025-09-27T12:32:17+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/mailer",
|
||||
"version": "v7.3.3",
|
||||
"version": "v7.3.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/mailer.git",
|
||||
"reference": "a32f3f45f1990db8c4341d5122a7d3a381c7e575"
|
||||
"reference": "ab97ef2f7acf0216955f5845484235113047a31d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/mailer/zipball/a32f3f45f1990db8c4341d5122a7d3a381c7e575",
|
||||
"reference": "a32f3f45f1990db8c4341d5122a7d3a381c7e575",
|
||||
"url": "https://api.github.com/repos/symfony/mailer/zipball/ab97ef2f7acf0216955f5845484235113047a31d",
|
||||
"reference": "ab97ef2f7acf0216955f5845484235113047a31d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -5727,7 +5650,7 @@
|
|||
"description": "Helps sending emails",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/mailer/tree/v7.3.3"
|
||||
"source": "https://github.com/symfony/mailer/tree/v7.3.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -5747,20 +5670,20 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-08-13T11:49:31+00:00"
|
||||
"time": "2025-09-17T05:51:54+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/mime",
|
||||
"version": "v7.3.2",
|
||||
"version": "v7.3.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/mime.git",
|
||||
"reference": "e0a0f859148daf1edf6c60b398eb40bfc96697d1"
|
||||
"reference": "b1b828f69cbaf887fa835a091869e55df91d0e35"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/mime/zipball/e0a0f859148daf1edf6c60b398eb40bfc96697d1",
|
||||
"reference": "e0a0f859148daf1edf6c60b398eb40bfc96697d1",
|
||||
"url": "https://api.github.com/repos/symfony/mime/zipball/b1b828f69cbaf887fa835a091869e55df91d0e35",
|
||||
"reference": "b1b828f69cbaf887fa835a091869e55df91d0e35",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -5815,7 +5738,7 @@
|
|||
"mime-type"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/mime/tree/v7.3.2"
|
||||
"source": "https://github.com/symfony/mime/tree/v7.3.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -5835,7 +5758,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-07-15T13:41:35+00:00"
|
||||
"time": "2025-09-16T08:38:17+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/options-resolver",
|
||||
|
|
@ -6739,16 +6662,16 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/process",
|
||||
"version": "v7.3.3",
|
||||
"version": "v7.3.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/process.git",
|
||||
"reference": "32241012d521e2e8a9d713adb0812bb773b907f1"
|
||||
"reference": "f24f8f316367b30810810d4eb30c543d7003ff3b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/32241012d521e2e8a9d713adb0812bb773b907f1",
|
||||
"reference": "32241012d521e2e8a9d713adb0812bb773b907f1",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/f24f8f316367b30810810d4eb30c543d7003ff3b",
|
||||
"reference": "f24f8f316367b30810810d4eb30c543d7003ff3b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -6780,7 +6703,7 @@
|
|||
"description": "Executes commands in sub-processes",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/process/tree/v7.3.3"
|
||||
"source": "https://github.com/symfony/process/tree/v7.3.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -6800,7 +6723,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-08-18T09:42:54+00:00"
|
||||
"time": "2025-09-11T10:12:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/psr-http-message-bridge",
|
||||
|
|
@ -6887,16 +6810,16 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/routing",
|
||||
"version": "v7.3.2",
|
||||
"version": "v7.3.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/routing.git",
|
||||
"reference": "7614b8ca5fa89b9cd233e21b627bfc5774f586e4"
|
||||
"reference": "8dc648e159e9bac02b703b9fbd937f19ba13d07c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/routing/zipball/7614b8ca5fa89b9cd233e21b627bfc5774f586e4",
|
||||
"reference": "7614b8ca5fa89b9cd233e21b627bfc5774f586e4",
|
||||
"url": "https://api.github.com/repos/symfony/routing/zipball/8dc648e159e9bac02b703b9fbd937f19ba13d07c",
|
||||
"reference": "8dc648e159e9bac02b703b9fbd937f19ba13d07c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -6948,7 +6871,7 @@
|
|||
"url"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/routing/tree/v7.3.2"
|
||||
"source": "https://github.com/symfony/routing/tree/v7.3.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -6968,7 +6891,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-07-15T11:36:08+00:00"
|
||||
"time": "2025-09-11T10:12:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/service-contracts",
|
||||
|
|
@ -7055,16 +6978,16 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/string",
|
||||
"version": "v7.3.3",
|
||||
"version": "v7.3.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/string.git",
|
||||
"reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c"
|
||||
"reference": "f96476035142921000338bad71e5247fbc138872"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/string/zipball/17a426cce5fd1f0901fefa9b2a490d0038fd3c9c",
|
||||
"reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c",
|
||||
"url": "https://api.github.com/repos/symfony/string/zipball/f96476035142921000338bad71e5247fbc138872",
|
||||
"reference": "f96476035142921000338bad71e5247fbc138872",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -7079,7 +7002,6 @@
|
|||
},
|
||||
"require-dev": {
|
||||
"symfony/emoji": "^7.1",
|
||||
"symfony/error-handler": "^6.4|^7.0",
|
||||
"symfony/http-client": "^6.4|^7.0",
|
||||
"symfony/intl": "^6.4|^7.0",
|
||||
"symfony/translation-contracts": "^2.5|^3.0",
|
||||
|
|
@ -7122,7 +7044,7 @@
|
|||
"utf8"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/string/tree/v7.3.3"
|
||||
"source": "https://github.com/symfony/string/tree/v7.3.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -7142,20 +7064,20 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-08-25T06:35:40+00:00"
|
||||
"time": "2025-09-11T14:36:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/translation",
|
||||
"version": "v7.3.3",
|
||||
"version": "v7.3.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/translation.git",
|
||||
"reference": "e0837b4cbcef63c754d89a4806575cada743a38d"
|
||||
"reference": "ec25870502d0c7072d086e8ffba1420c85965174"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/translation/zipball/e0837b4cbcef63c754d89a4806575cada743a38d",
|
||||
"reference": "e0837b4cbcef63c754d89a4806575cada743a38d",
|
||||
"url": "https://api.github.com/repos/symfony/translation/zipball/ec25870502d0c7072d086e8ffba1420c85965174",
|
||||
"reference": "ec25870502d0c7072d086e8ffba1420c85965174",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -7222,7 +7144,7 @@
|
|||
"description": "Provides tools to internationalize your application",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/translation/tree/v7.3.3"
|
||||
"source": "https://github.com/symfony/translation/tree/v7.3.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -7242,7 +7164,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-08-01T21:02:37+00:00"
|
||||
"time": "2025-09-07T11:39:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/translation-contracts",
|
||||
|
|
@ -7398,16 +7320,16 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/var-dumper",
|
||||
"version": "v7.3.3",
|
||||
"version": "v7.3.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/var-dumper.git",
|
||||
"reference": "34d8d4c4b9597347306d1ec8eb4e1319b1e6986f"
|
||||
"reference": "b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/34d8d4c4b9597347306d1ec8eb4e1319b1e6986f",
|
||||
"reference": "34d8d4c4b9597347306d1ec8eb4e1319b1e6986f",
|
||||
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb",
|
||||
"reference": "b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -7461,7 +7383,7 @@
|
|||
"dump"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/var-dumper/tree/v7.3.3"
|
||||
"source": "https://github.com/symfony/var-dumper/tree/v7.3.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -7481,7 +7403,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-08-13T11:49:31+00:00"
|
||||
"time": "2025-09-11T10:12:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "tightenco/ziggy",
|
||||
|
|
@ -7726,16 +7648,16 @@
|
|||
},
|
||||
{
|
||||
"name": "uwil/laravel-common",
|
||||
"version": "4.1.1",
|
||||
"version": "4.1.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "git@lab.s-web.fr:ldc-web/projets/laravel-common.git",
|
||||
"reference": "7c08c4f545307bdba8bd09835b59982094b06e49"
|
||||
"reference": "ae2ef93a05ae8fbeae1799ad12f9dc5172f50f60"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://lab.s-web.fr/api/v4/projects/215/packages/composer/archives/uwil/laravel-common.zip?sha=7c08c4f545307bdba8bd09835b59982094b06e49",
|
||||
"reference": "7c08c4f545307bdba8bd09835b59982094b06e49",
|
||||
"url": "https://lab.s-web.fr/api/v4/projects/215/packages/composer/archives/uwil/laravel-common.zip?sha=ae2ef93a05ae8fbeae1799ad12f9dc5172f50f60",
|
||||
"reference": "ae2ef93a05ae8fbeae1799ad12f9dc5172f50f60",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -8966,16 +8888,11 @@
|
|||
},
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "2.1.29",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan-phar-composer-source.git",
|
||||
"reference": "git"
|
||||
},
|
||||
"version": "2.1.30",
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/d618573eed4a1b6b75e37b2e0b65ac65c885d88e",
|
||||
"reference": "d618573eed4a1b6b75e37b2e0b65ac65c885d88e",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/a4a7f159927983dd4f7c8020ed227d80b7f39d7d",
|
||||
"reference": "a4a7f159927983dd4f7c8020ed227d80b7f39d7d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -9020,7 +8937,7 @@
|
|||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-09-25T06:58:18+00:00"
|
||||
"time": "2025-10-02T16:07:52+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
|
|
@ -9359,16 +9276,16 @@
|
|||
},
|
||||
{
|
||||
"name": "phpunit/phpunit",
|
||||
"version": "11.5.41",
|
||||
"version": "11.5.42",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||
"reference": "b42782bcb947d2c197aea42ce9714ee2d974b283"
|
||||
"reference": "1c6cb5dfe412af3d0dfd414cfd110e3b9cfdbc3c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b42782bcb947d2c197aea42ce9714ee2d974b283",
|
||||
"reference": "b42782bcb947d2c197aea42ce9714ee2d974b283",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1c6cb5dfe412af3d0dfd414cfd110e3b9cfdbc3c",
|
||||
"reference": "1c6cb5dfe412af3d0dfd414cfd110e3b9cfdbc3c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -9440,7 +9357,7 @@
|
|||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
||||
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.41"
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.42"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -9464,7 +9381,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-09-24T06:32:10+00:00"
|
||||
"time": "2025-09-28T12:09:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/cli-parser",
|
||||
|
|
@ -10701,12 +10618,12 @@
|
|||
],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": {},
|
||||
"stability-flags": [],
|
||||
"prefer-stable": true,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"php": "^8.2"
|
||||
},
|
||||
"platform-dev": {},
|
||||
"plugin-api-version": "2.6.0"
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.3.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,4 +93,39 @@ return [
|
|||
"missing_translations" => "The translation of the :field for the language :locales is missing.|The translation
|
||||
of the :field for :locales languages are missing.",
|
||||
"archives" => "archives",
|
||||
"validate" => "confirm",
|
||||
"cancel" => "cancel",
|
||||
"crop" => "crop",
|
||||
"filter" => "filter|filters",
|
||||
"image_cropper" => [
|
||||
"action_the_crop" => ":action the crop",
|
||||
"toast" => [
|
||||
"too_big" => "Max size : :maxSize. This file is too big",
|
||||
"no_file" => "no file selected !",
|
||||
"forbidden_file" => "file not allowed.",
|
||||
"too_many" => "the max file allowed exceeded (1)",
|
||||
"defaultError" => "an error occured !"
|
||||
],
|
||||
"validation" => [
|
||||
"rules_between" => "media must do :width px width and :height px height.",
|
||||
"rules_weight" => "max size : :size",
|
||||
"allowed_formats" => "allowed formats : :formats"
|
||||
],
|
||||
"filters" => [
|
||||
'zoomIn' => "zoom in",
|
||||
"zoomOut" => "zoom out",
|
||||
"zoom" => "zoom",
|
||||
"rotation_forward" => "rotation forward",
|
||||
"rotation_back" => "rotation back",
|
||||
"rotation" => "rotation",
|
||||
"grayscale" => "grayscale",
|
||||
"sepia" => "sepia",
|
||||
"saturation" => "saturation",
|
||||
"contrast" => "contrast",
|
||||
"brightness" => "brightness",
|
||||
"reset" => "reset",
|
||||
"vertical_flip" => "vertical flip",
|
||||
"horizontal_flip" => "horizontal flip",
|
||||
]
|
||||
]
|
||||
];
|
||||
|
|
|
|||
|
|
@ -89,8 +89,45 @@ return [
|
|||
en conséquence.",
|
||||
"config_save_warning_list" => "Voici la liste des :models concernés:|Voici la liste des :models concernées:",
|
||||
"config_archived" => "Cette configuration est actuellement archivée",
|
||||
"config_complete_label" => "Label complet de l'input:",
|
||||
"unsupported_type" => "Type non supporté:",
|
||||
"missing_translations" => "La traduction du champs :field pour la langue :locales est manquante.|La traduction
|
||||
du champs :field pour les langues :locales sont manquantes.",
|
||||
"archives" => "archives",
|
||||
"validate" => "valider",
|
||||
"cancel" => "annuler",
|
||||
"crop" => "recadrer",
|
||||
"filter" => "filtre|filtres",
|
||||
"image_cropper" => [
|
||||
"action_the_crop" => ":action le cadrage",
|
||||
"toast" => [
|
||||
"too_big" => "taille maximale autorisée : :maxSize. Le fichier est trop volumineux" ,
|
||||
"no_file" => "aucun fichier sélectionné !",
|
||||
"forbidden_file" => "fichier non autorisé.",
|
||||
"too_many" => "le nombre de fichier autorisé est dépassé (1)",
|
||||
"defaultError" => "une erreur est survenue !"
|
||||
],
|
||||
"validation" => [
|
||||
"rules_between" => "le média doit faire :width px de large et :height px de haut.",
|
||||
"rules_weight" => "taille max : :size",
|
||||
"allowed_formats" => "formats autorisés : :formats",
|
||||
"all_formats" => "tous"
|
||||
],
|
||||
"filters" => [
|
||||
'zoomIn' => "zoom avant",
|
||||
"zoomOut" => "zoom arrière",
|
||||
"zoom" => "zoom",
|
||||
"rotation_forward" => "rotation avant",
|
||||
"rotation_back" => "rotation arrière",
|
||||
"rotation" => "rotation",
|
||||
"grayscale" => "noir & Blanc",
|
||||
"sepia" => "sépia",
|
||||
"saturation" => "saturation",
|
||||
"contrast" => "contraste",
|
||||
"brightness" => "luminosité",
|
||||
"reset" => "réinitialiser",
|
||||
"vertical_flip" => "inverser verticalement",
|
||||
"horizontal_flip" => "inverser horizontalement",
|
||||
]
|
||||
]
|
||||
];
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ return [
|
|||
'boolean' => 'Booléen',
|
||||
'url' => 'Lien',
|
||||
'email' => 'E-mail',
|
||||
'image' => 'Image'
|
||||
],
|
||||
'contactFormType' => [
|
||||
'collaborator' => [
|
||||
|
|
|
|||
104
package-lock.json
generated
104
package-lock.json
generated
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "ldc",
|
||||
"name": "srcs",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
|
@ -10,6 +10,8 @@
|
|||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@inertiajs/react": "^2.0.17",
|
||||
"@ldc-web/cookie-notice": "^6.1.1",
|
||||
"@radix-ui/react-progress": "^1.1.7",
|
||||
"@radix-ui/react-slider": "^1.3.6",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"@types/pluralize": "^0.0.33",
|
||||
"@types/react": "^19.1.9",
|
||||
|
|
@ -19,6 +21,7 @@
|
|||
"laravel-vite-plugin": "^1.3.0",
|
||||
"lucide-react": "^0.503.0",
|
||||
"motion": "^12.23.12",
|
||||
"react-dropzone": "^14.3.8",
|
||||
"reactjs-tiptap-editor": "^0.3.23",
|
||||
"slugify": "^1.6.6",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
|
|
@ -3289,6 +3292,30 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-progress": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.7.tgz",
|
||||
"integrity": "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-context": "1.1.2",
|
||||
"@radix-ui/react-primitive": "2.1.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-roving-focus": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz",
|
||||
|
|
@ -3386,6 +3413,39 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-slider": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.3.6.tgz",
|
||||
"integrity": "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/number": "1.1.1",
|
||||
"@radix-ui/primitive": "1.1.3",
|
||||
"@radix-ui/react-collection": "1.1.7",
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-context": "1.1.2",
|
||||
"@radix-ui/react-direction": "1.1.1",
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-use-controllable-state": "1.2.2",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.1",
|
||||
"@radix-ui/react-use-previous": "1.1.1",
|
||||
"@radix-ui/react-use-size": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-slot": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
|
||||
|
|
@ -5947,6 +6007,15 @@
|
|||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/attr-accept": {
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz",
|
||||
"integrity": "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/autoprefixer": {
|
||||
"version": "10.4.21",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
|
||||
|
|
@ -8600,6 +8669,18 @@
|
|||
"node": "^10.12.0 || >=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/file-selector": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/file-selector/-/file-selector-2.1.2.tgz",
|
||||
"integrity": "sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
|
|
@ -9950,7 +10031,6 @@
|
|||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
|
|
@ -10558,7 +10638,6 @@
|
|||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
|
|
@ -12307,7 +12386,6 @@
|
|||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
|
|
@ -12647,6 +12725,23 @@
|
|||
"react": "^19.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dropzone": {
|
||||
"version": "14.3.8",
|
||||
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.3.8.tgz",
|
||||
"integrity": "sha512-sBgODnq+lcA4P296DY4wacOZz3JFpD99fp+hb//iBO2HHnyeZU3FwWyXJ6salNpqQdsZrgMrotuko/BdJMV8Ug==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"attr-accept": "^2.2.4",
|
||||
"file-selector": "^2.1.0",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.13"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 16.8 || 18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-image-crop": {
|
||||
"version": "11.0.10",
|
||||
"resolved": "https://registry.npmjs.org/react-image-crop/-/react-image-crop-11.0.10.tgz",
|
||||
|
|
@ -12660,7 +12755,6 @@
|
|||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
|
|
|
|||
|
|
@ -66,6 +66,8 @@
|
|||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@inertiajs/react": "^2.0.17",
|
||||
"@ldc-web/cookie-notice": "^6.1.1",
|
||||
"@radix-ui/react-progress": "^1.1.7",
|
||||
"@radix-ui/react-slider": "^1.3.6",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"@types/pluralize": "^0.0.33",
|
||||
"@types/react": "^19.1.9",
|
||||
|
|
@ -75,6 +77,7 @@
|
|||
"laravel-vite-plugin": "^1.3.0",
|
||||
"lucide-react": "^0.503.0",
|
||||
"motion": "^12.23.12",
|
||||
"react-dropzone": "^14.3.8",
|
||||
"reactjs-tiptap-editor": "^0.3.23",
|
||||
"slugify": "^1.6.6",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
|
|
|
|||
|
|
@ -8,12 +8,14 @@ import SimpleWysiwyg from "@/back/Components/Inputs/SimpleWysiwyg";
|
|||
import { DynamicFieldTypes, DynamicField } from "@/back/types/global";
|
||||
import str from "@/hooks/use-string";
|
||||
import useTrans from "@/hooks/use-translations";
|
||||
import ImageCropper from "./Inputs/ImageCropper";
|
||||
|
||||
type DynamicInputsProps = {
|
||||
name: string;
|
||||
config: DynamicField[];
|
||||
content: Record<string, string | boolean | number> | null;
|
||||
setData: (name: string, value: string | boolean) => void;
|
||||
setData: (name: string, value: string | boolean | File | null) => void;
|
||||
reset: (name?: string) => void;
|
||||
};
|
||||
|
||||
type MinMax = {
|
||||
|
|
@ -26,6 +28,7 @@ export default function DynamicInputs({
|
|||
config,
|
||||
content = {},
|
||||
setData,
|
||||
reset,
|
||||
}: DynamicInputsProps) {
|
||||
const enumDynamicFieldTypes = usePage().props.enumDynamicFieldTypes as DynamicFieldTypes;
|
||||
const transAssociateTo = useTrans("back.form_associate_to");
|
||||
|
|
@ -81,6 +84,47 @@ export default function DynamicInputs({
|
|||
return validation.split("|").includes("required");
|
||||
};
|
||||
|
||||
/**
|
||||
* Extracts a number value from a Laravel validation rule.
|
||||
*
|
||||
* Supports both:
|
||||
* - Simple rules like "max:255"
|
||||
* - Complex rules like "dimensions:width=350,height=250"
|
||||
*
|
||||
* @param validation Full validation string (e.g. "required|max:255|dimensions:width=350,height=250")
|
||||
* @param ruleName Rule to look for (e.g. "max" or "dimensions")
|
||||
* @param key Optional. If the rule contains key=value pairs (e.g. "width=350"), specify the key
|
||||
* @returns number|null
|
||||
*/
|
||||
const extractLaravelValidationValue = (
|
||||
validation: string,
|
||||
ruleName: string,
|
||||
key?: string
|
||||
): string | null => {
|
||||
const rules = validation.split("|");
|
||||
const rule = rules.find(r => r.startsWith(`${ruleName}:`));
|
||||
|
||||
if (!rule) return null;
|
||||
|
||||
const [, rawValue] = rule.split(":");
|
||||
|
||||
if (key) {
|
||||
// Complex case like dimensions:width=350,height=250
|
||||
const pairs = rawValue.split(",");
|
||||
const match = pairs.find(pair => pair.startsWith(`${key}=`));
|
||||
if (match) {
|
||||
const [, value] = match.split("=");
|
||||
return value;
|
||||
}
|
||||
} else {
|
||||
return rawValue;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 items-start gap-6">
|
||||
{Object.entries(config).map(([id, field]) => {
|
||||
|
|
@ -161,6 +205,25 @@ export default function DynamicInputs({
|
|||
setData={setData}
|
||||
/>
|
||||
);
|
||||
case enumDynamicFieldTypes.image.value:
|
||||
return (
|
||||
<ImageCropper
|
||||
key={id}
|
||||
fileName="fichier"
|
||||
name={fieldName}
|
||||
label={getTrans(field.label)}
|
||||
setData={setData}
|
||||
reset={reset}
|
||||
value={String(fieldValue)}
|
||||
required={extractRequiredValidation(field.validation)}
|
||||
maxFileSize={extractLaravelValidationValue(field.validation, 'max')}
|
||||
allowedFormats={extractLaravelValidationValue(field.validation, 'mimes')}
|
||||
validationRules={{
|
||||
width: extractLaravelValidationValue(field.validation, "dimensions", 'width'),
|
||||
height: extractLaravelValidationValue(field.validation, "dimensions", 'height')
|
||||
}}
|
||||
/>
|
||||
);
|
||||
// Email input.
|
||||
case enumDynamicFieldTypes.email.value:
|
||||
return (
|
||||
|
|
|
|||
1036
resources/ts/back/Components/Inputs/ImageCropper.tsx
Normal file
1036
resources/ts/back/Components/Inputs/ImageCropper.tsx
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -30,13 +30,17 @@ export default function SimpleNumber({
|
|||
label,
|
||||
id = name,
|
||||
step = 1,
|
||||
limits = { min: 0, max: 1000 },
|
||||
limits,
|
||||
placeholder = "back.enter_number_here",
|
||||
required = true,
|
||||
disabled = false,
|
||||
setData,
|
||||
}: SimpleNumberProps) {
|
||||
const safeValue = value >= limits.min && value <= limits.max ? value : limits.min;
|
||||
const localLimits = {
|
||||
min: limits?.min ? limits.min : 1,
|
||||
max: limits?.max ? limits.max : 1000
|
||||
}
|
||||
const safeValue = value >= localLimits.min && value <= localLimits.max ? value : localLimits.min;
|
||||
const { errors } = usePage().props;
|
||||
|
||||
const [inputValue, setInputValue] = useState(safeValue);
|
||||
|
|
@ -55,8 +59,8 @@ export default function SimpleNumber({
|
|||
type="number"
|
||||
placeholder={useTrans(placeholder)}
|
||||
value={inputValue}
|
||||
min={limits.min}
|
||||
max={limits.max}
|
||||
min={localLimits.min}
|
||||
max={localLimits.max}
|
||||
step={step}
|
||||
onChange={(e) => {
|
||||
setInputValue(Number(e.target.value));
|
||||
|
|
|
|||
|
|
@ -167,12 +167,12 @@ export default function AppSidebar() {
|
|||
icon: History,
|
||||
show: authPolicies.activityLogs.viewAny,
|
||||
},
|
||||
{
|
||||
title: useTrans("back.telescope"),
|
||||
link: route("telescope"),
|
||||
icon: Telescope,
|
||||
show: appEnv.mode === "local",
|
||||
},
|
||||
// {
|
||||
// title: useTrans("back.telescope"),
|
||||
// link: route("telescope"),
|
||||
// icon: Telescope,
|
||||
// show: appEnv.mode === "local",
|
||||
// },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// * Packages
|
||||
import { TrashIcon, GripVerticalIcon } from "lucide-react";
|
||||
import { TrashIcon, GripVerticalIcon, InfoIcon, Copy } from "lucide-react";
|
||||
import { usePage } from "@inertiajs/react";
|
||||
import { useSortable } from "@dnd-kit/sortable";
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
|
|
@ -15,6 +15,9 @@ import EnumSelect from "@/back/Components/Inputs/EnumSelect";
|
|||
import SimpleString from "@/back/Components/Inputs/SimpleString";
|
||||
import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip";
|
||||
import { DynamicField, DynamicFieldTypes } from "@/back/types/global";
|
||||
import useTrans from "@/hooks/use-translations";
|
||||
import str from "@/hooks/use-string";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
type SortableFieldProps = {
|
||||
id: string;
|
||||
|
|
@ -47,6 +50,31 @@ export default function SortableField({
|
|||
transDragAndDrop,
|
||||
}: SortableFieldProps) {
|
||||
const enumDynamicFieldTypes = usePage().props.enumDynamicFieldTypes as DynamicFieldTypes;
|
||||
const getEnumDynamicFieldType = (value: number) => {
|
||||
return Object.values(enumDynamicFieldTypes).find((def) => def.value === value);
|
||||
};
|
||||
|
||||
const enumCurrent = getEnumDynamicFieldType(field.type)?.name + "_";
|
||||
const splitAndGetEditableKey = (localeEditableKey: string): string => {
|
||||
const baseKeyArray = localeEditableKey.split(enumCurrent);
|
||||
return baseKeyArray[baseKeyArray.length - 1];
|
||||
}
|
||||
const [editableKey, setEditableKey] = useState(splitAndGetEditableKey(field.key))
|
||||
const [isTypeChanged, setIsTypeChanged] = useState(false)
|
||||
const completeKey = enumCurrent + editableKey
|
||||
|
||||
useEffect(() => {
|
||||
if (isTypeChanged) {
|
||||
setEditableKey(splitAndGetEditableKey(editableKey))
|
||||
}
|
||||
setIsTypeChanged(false)
|
||||
}, [isTypeChanged])
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
updateField(id, { key: completeKey })
|
||||
}, [fieldKey, editableKey, completeKey])
|
||||
|
||||
const {
|
||||
attributes,
|
||||
|
|
@ -63,7 +91,9 @@ export default function SortableField({
|
|||
opacity: isDragging ? 0.5 : 1,
|
||||
zIndex: isDragging ? 10 : 1,
|
||||
};
|
||||
|
||||
const copyToClipBoard = (data: string) => {
|
||||
navigator.clipboard.writeText(data)
|
||||
}
|
||||
return (
|
||||
<div
|
||||
ref={setNodeRef}
|
||||
|
|
@ -109,22 +139,41 @@ export default function SortableField({
|
|||
value={field.label}
|
||||
setData={(_, value) => updateField(id, { label: value })}
|
||||
/>
|
||||
<SimpleString
|
||||
name={`config.${fieldKey}.key`}
|
||||
label={getTransInput("key", index + 1)}
|
||||
placeholder="ex: John Doe"
|
||||
value={field.key}
|
||||
setData={(_, value) => updateField(id, { key: value })}
|
||||
/>
|
||||
<div className="flex items-end gap-2">
|
||||
<SimpleString
|
||||
name={`config.${fieldKey}.key`}
|
||||
label={getTransInput("key", index + 1)}
|
||||
placeholder="ex: John Doe"
|
||||
value={editableKey}
|
||||
setData={(_, value) => {
|
||||
setEditableKey(value)
|
||||
// updateField(id, { key: value })
|
||||
}}
|
||||
/>
|
||||
<div className="flex flex-col items-center gap-1.5">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<InfoIcon className="text-blue-500 size-4" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<h4 className="font-semibold">{str(useTrans('back.config_complete_label')).ucFirst().value()}</h4>
|
||||
<p className="text-muted-foreground mt-1">{completeKey}
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Button type="button" variant={"ghost"} size={"icon"} onClick={() => copyToClipBoard(completeKey)}><Copy className="size-4" /></Button>
|
||||
</div>
|
||||
</div>
|
||||
<EnumSelect
|
||||
name={`config.${fieldKey}.type`}
|
||||
label={getTransInput("type", index + 1)}
|
||||
placeholder={`ex: ${enumDynamicFieldTypes?.string?.label}`}
|
||||
selected={String(field.type)}
|
||||
options={enumDynamicFieldTypes}
|
||||
setData={(_, value) =>
|
||||
setData={(_, value) => {
|
||||
setIsTypeChanged(true)
|
||||
updateField(id, { type: Number(value) })
|
||||
}
|
||||
}}
|
||||
required={required}
|
||||
disabled={disabled}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -116,15 +116,20 @@ export default function Form({
|
|||
const { toast } = useToast();
|
||||
|
||||
/** Form section */
|
||||
const { post, patch, setData, data } = useForm({
|
||||
const { post, setData, data, reset } = useForm({
|
||||
_method: method === Methods.POST ? 'post' : 'put',
|
||||
title: model?.title ? model?.title[locale]: "",
|
||||
content: model?.content ? model?.content[locale]: "",
|
||||
config_id: model?.config_id ?? configOptions[defaultConfigId].id,
|
||||
status: model?.status ?? enumDynamicPageStatus.draft.value,
|
||||
isLocked: model?.isLocked ?? false,
|
||||
});
|
||||
const routeOptions = () => {
|
||||
return {
|
||||
|
||||
const submit = (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
const routeUrl = route(`back.${modelRoute}.${action}`, model?.id);
|
||||
if (method !== Methods.PATCH && method !== Methods.POST) throw new Error("The method must be post or patch");
|
||||
post(routeUrl, {
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
toast({
|
||||
|
|
@ -139,21 +144,7 @@ export default function Form({
|
|||
description: messages.descriptionError,
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
const submit = (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
const routeUrl = route(`back.${modelRoute}.${action}`, model?.id);
|
||||
switch (method) {
|
||||
case Methods.POST:
|
||||
post(routeUrl, routeOptions());
|
||||
break;
|
||||
case Methods.PATCH:
|
||||
patch(routeUrl, routeOptions());
|
||||
break;
|
||||
default:
|
||||
throw new Error("The method must be post or patch");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -289,6 +280,7 @@ export default function Form({
|
|||
config={getConfig(data.config_id)}
|
||||
content={typeof data.content === "object" ? data.content : {}}
|
||||
setData={setData}
|
||||
reset={reset}
|
||||
/>
|
||||
)}
|
||||
</FieldsetWrapper>
|
||||
|
|
|
|||
1
resources/ts/back/types/global.d.ts
vendored
1
resources/ts/back/types/global.d.ts
vendored
|
|
@ -6,6 +6,7 @@ import { ElementType } from "react";
|
|||
type AppEnv = {
|
||||
mode: string;
|
||||
name: string;
|
||||
url: string;
|
||||
locale: string;
|
||||
locales: string;
|
||||
fallbackLocale: string;
|
||||
|
|
|
|||
63
resources/ts/components/ui/slider.tsx
Normal file
63
resources/ts/components/ui/slider.tsx
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as SliderPrimitive from "@radix-ui/react-slider"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Slider({
|
||||
className,
|
||||
defaultValue,
|
||||
value,
|
||||
min = 0,
|
||||
max = 100,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SliderPrimitive.Root>) {
|
||||
const _values = React.useMemo(
|
||||
() =>
|
||||
Array.isArray(value)
|
||||
? value
|
||||
: Array.isArray(defaultValue)
|
||||
? defaultValue
|
||||
: [min, max],
|
||||
[value, defaultValue, min, max]
|
||||
)
|
||||
|
||||
return (
|
||||
<SliderPrimitive.Root
|
||||
data-slot="slider"
|
||||
defaultValue={defaultValue}
|
||||
value={value}
|
||||
min={min}
|
||||
max={max}
|
||||
className={cn(
|
||||
"relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<SliderPrimitive.Track
|
||||
data-slot="slider-track"
|
||||
className={cn(
|
||||
"bg-muted relative grow overflow-hidden rounded-full data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5"
|
||||
)}
|
||||
>
|
||||
<SliderPrimitive.Range
|
||||
data-slot="slider-range"
|
||||
className={cn(
|
||||
"bg-primary absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full"
|
||||
)}
|
||||
/>
|
||||
</SliderPrimitive.Track>
|
||||
{Array.from({ length: _values.length }, (_, index) => (
|
||||
<SliderPrimitive.Thumb
|
||||
data-slot="slider-thumb"
|
||||
key={index}
|
||||
className="border-primary bg-background ring-ring/50 block size-4 shrink-0 rounded-full border shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50"
|
||||
/>
|
||||
))}
|
||||
</SliderPrimitive.Root>
|
||||
)
|
||||
}
|
||||
|
||||
export { Slider }
|
||||
Loading…
Add table
Reference in a new issue