diff --git a/app/Exceptions/AuthServiceException.php b/app/Exceptions/AuthServiceException.php new file mode 100644 index 00000000..ffbaf907 --- /dev/null +++ b/app/Exceptions/AuthServiceException.php @@ -0,0 +1,13 @@ + $query + * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator + */ + protected function paginate( + Builder $query, + ?ItemsPerPaginationEnum $pagination = null + ): LengthAwarePaginator { + $route = $this->request->route(); + + throw_if( + $route === null, + \UnexpectedValueException::class, + 'Route is required in order to paginate' + ); + + $cacheKey = $this->getCacheKey($route); + $perPage = $this->getPerPage($cacheKey); + + $this->persistPerPage($cacheKey, $perPage); + + return $query->paginate(perPage: $perPage)->withQueryString(); + } + + private function persistPerPage(string $cacheKey, int $perPage): void + { + if ($this->session->get($cacheKey) !== $perPage) { + $this->session->put($cacheKey, $perPage); + } + } + + private function getPerPage(string $cacheKey): int + { + $perPage = $this->session->get($cacheKey); + $perPage = $perPage ?? ItemsPerPaginationEnum::twelve->value(); + + if (! is_scalar($perPage)) { + throw new \UnexpectedValueException('perPage shall be a scalar valuye'); + } + + return ToolboxHelper::getValidatedEnum( + intval($perPage), + 'pagination', + '\Uwil\LaravelBackend\Enums\ItemsPerPaginationEnum', + ); + } + + private function getCacheKey(Route $route): string + { + $currentRoutePath = Str::slug($route->getName() ?? 'unknown'); + + return "pagination.{$currentRoutePath}"; + } +} diff --git a/app/Http/Controllers/Back/ProductController.php b/app/Http/Controllers/Back/ProductController.php new file mode 100644 index 00000000..d4270bac --- /dev/null +++ b/app/Http/Controllers/Back/ProductController.php @@ -0,0 +1,241 @@ +authorizeResource(Product::class); + $this->modelName = Product::modelName(); + $this->modelPage = Product::modelPage(); + } + + /** + * Display a listing of the resource. + */ + public function index(IndexProductRequest $request): Response + { + /** @var \Illuminate\Database\Eloquent\Builder<\App\Models\Product> $query */ + $query = Product::query(); + + if ($request->routeIs("back.{$this->modelName}.trash")) { + $query = $query->onlyTrashed(); + } + + $searchValue = $request->getSearchValue(); + if ($searchValue) { + $this->searchQuery( + $query, + $searchValue, + null, + 'name' + ); + } + $searchFields = \implode(', ', [ + trans('validation.attributes.name'), + ]); + + /** Sort columns with a query */ + $this->sortQuery($query); + + /** Custom pagination */ + $paginatedModels = $this->paginate($query); + + return $this->inertiaRender("back/Pages/{$this->modelPage}/Index", [ + 'paginatedModels' => CommonResource::collection($paginatedModels), + 'search' => $searchValue, + 'searchFields' => $searchFields, + ]); + } + + /** + * Show the form for creating a new resource. + */ + public function create(Product $product): Response + { + return $this->inertiaRender("back/Pages/{$this->modelPage}/Create", [ + 'data' => CommonResource::make($product), + ]); + } + + /** + * Show a specific resource. + */ + public function show(Product $product): Response + { + return $this->inertiaRender("back/Pages/{$this->modelPage}/Show", [ + 'data' => CommonResource::make($product), + ]); + } + + /** + * Store a newly created resource. + */ + public function store(StoreProductRequest $request): RedirectResponse + { + return DB::transaction(function () use ($request) { + $product = new Product(); + $product->inertiaFill($request->validated()); + + if ($product->saveOrFail()) { + return redirect()->route("back.{$this->modelName}.edit", $product) + ->with('success', trans('uwil-backend::crud.messages.has_been_created', [ + 'model' => Str::ucfirst(trans_choice("models.classes.{$this->modelName}", 1)), + ])); + } + + return redirect()->back() + ->with('error', trans('uwil-backend::crud.messages.cannot_be_created', [ + 'model' => Str::ucfirst(trans_choice("models.classes.{$this->modelName}", 1)), + ])); + }); + } + + /** + * Show the form for editing the specified resource. + */ + public function edit(Product $product): Response + { + return $this->inertiaRender("back/Pages/{$this->modelPage}/Edit", [ + 'data' => CommonResource::make($product), + ]); + } + + /** + * Update the specified resource. + */ + public function update( + UpdateProductRequest $request, + Product $product + ): RedirectResponse { + return DB::transaction(function () use ($request, $product) { + $product->inertiaFill($request->validated()); + + if ($product->saveOrFail()) { + return redirect()->route("back.{$this->modelName}.edit", $product) + ->with('success', trans('uwil-backend::crud.messages.has_been_updated', [ + 'model' => Str::ucfirst(trans_choice("models.classes.{$this->modelName}", 1)), + ])); + } + + return redirect()->route("back.{$this->modelName}.edit", $product) + ->with('error', trans('uwil-backend::crud.messages.cannot_be_updated', [ + 'model' => Str::ucfirst(trans_choice("models.classes.{$this->modelName}", 1)), + ])); + }); + } + + /** + * Duplicate the specified resource. + */ + public function duplicate(Product $product): Response + { + return $this->create($product->replicate()); + } + + /** + * Restore the specified resource. + */ + public function restore(int $product): RedirectResponse + { + $product = Product::withTrashed()->findOrFail($product); + + $previousQueries = $this->getPreviousQueries(); + + if ($product->restore()) { + return redirect()->route("back.{$this->modelName}.trash", $previousQueries) + ->with('success', trans('uwil-backend::crud.messages.has_been_restored', [ + 'model' => Str::ucfirst(trans_choice("models.classes.{$this->modelName}", 1)), + ])); + } + + return redirect()->back() + ->with('error', trans('uwil-backend::crud.messages.cannot_be_restored', [ + 'model' => Str::ucfirst(trans_choice("models.classes.{$this->modelName}", 1)), + ])); + } + + /** + * Destroy the specified resource. + */ + public function destroy(Product $product): RedirectResponse + { + $previousQueries = $this->getPreviousQueries(); + + if ($product->delete()) { + return redirect()->route("back.{$this->modelName}.index", $previousQueries) + ->with('success', trans('uwil-backend::crud.messages.has_been_deleted', [ + 'model' => Str::ucfirst(trans_choice("models.classes.{$this->modelName}", 1)), + ])); + } + + return redirect()->back() + ->with('error', trans('uwil-backend::crud.messages.cannot_be_deleted', [ + 'model' => Str::ucfirst(trans_choice("models.classes.{$this->modelName}", 1)), + ])); + } + + /** + * Destroy the specified resource. + */ + public function forceDestroy(Product | int $product): RedirectResponse + { + if (is_int($product)) { + $product = Product::withTrashed()->findOrFail($product); + } + + $previousQueries = $this->getPreviousQueries(); + + if ($product->forceDelete()) { + return redirect()->route("back.{$this->modelName}.trash", $previousQueries) + ->with('success', trans('uwil-backend::crud.messages.has_been_deleted', [ + 'model' => Str::ucfirst(trans_choice("models.classes.{$this->modelName}", 1)), + ])); + } + + return redirect()->back() + ->with('error', trans('uwil-backend::crud.messages.cannot_be_deleted', [ + 'model' => Str::ucfirst(trans_choice("models.classes.{$this->modelName}", 1)), + ])); + } + + /** @return array> */ + private function getPreviousQueries(): array + { + $previousQueries = []; + $previousQueryString = \parse_url($this->getPreviousUrl(), \PHP_URL_QUERY) ?? '' ?: ''; + \parse_str($previousQueryString, $previousQueries); + + return $previousQueries; + } + + private function getPreviousUrl(): string + { + return url()->previous(route('back.dashboard.index')); + } +} diff --git a/app/Http/Controllers/Back/RecipeController.php b/app/Http/Controllers/Back/RecipeController.php new file mode 100644 index 00000000..49f4a76c --- /dev/null +++ b/app/Http/Controllers/Back/RecipeController.php @@ -0,0 +1,241 @@ +authorizeResource(Recipe::class); + $this->modelName = Recipe::modelName(); + $this->modelPage = Recipe::modelPage(); + } + + /** + * Display a listing of the resource. + */ + public function index(IndexRecipeRequest $request): Response + { + /** @var \Illuminate\Database\Eloquent\Builder<\App\Models\Recipe> $query */ + $query = Recipe::query(); + + if ($request->routeIs("back.{$this->modelName}.trash")) { + $query = $query->onlyTrashed(); + } + + $searchValue = $request->getSearchValue(); + if ($searchValue) { + $this->searchQuery( + $query, + $searchValue, + null, + 'name' + ); + } + $searchFields = \implode(', ', [ + trans('validation.attributes.name'), + ]); + + /** Sort columns with a query */ + $this->sortQuery($query); + + /** Custom pagination */ + $paginatedModels = $this->paginate($query); + + return $this->inertiaRender("back/Pages/{$this->modelPage}/Index", [ + 'paginatedModels' => CommonResource::collection($paginatedModels), + 'search' => $searchValue, + 'searchFields' => $searchFields, + ]); + } + + /** + * Show the form for creating a new resource. + */ + public function create(Recipe $recipe): Response + { + return $this->inertiaRender("back/Pages/{$this->modelPage}/Create", [ + 'data' => CommonResource::make($recipe), + ]); + } + + /** + * Show a specific resource. + */ + public function show(Recipe $recipe): Response + { + return $this->inertiaRender("back/Pages/{$this->modelPage}/Show", [ + 'data' => CommonResource::make($recipe), + ]); + } + + /** + * Store a newly created resource. + */ + public function store(StoreRecipeRequest $request): RedirectResponse + { + return DB::transaction(function () use ($request) { + $recipe = new Recipe(); + $recipe->inertiaFill($request->validated()); + + if ($recipe->saveOrFail()) { + return redirect()->route("back.{$this->modelName}.edit", $recipe) + ->with('success', trans('uwil-backend::crud.messages.has_been_created', [ + 'model' => Str::ucfirst(trans_choice("models.classes.{$this->modelName}", 1)), + ])); + } + + return redirect()->back() + ->with('error', trans('uwil-backend::crud.messages.cannot_be_created', [ + 'model' => Str::ucfirst(trans_choice("models.classes.{$this->modelName}", 1)), + ])); + }); + } + + /** + * Show the form for editing the specified resource. + */ + public function edit(Recipe $recipe): Response + { + return $this->inertiaRender("back/Pages/{$this->modelPage}/Edit", [ + 'data' => CommonResource::make($recipe), + ]); + } + + /** + * Update the specified resource. + */ + public function update( + UpdateRecipeRequest $request, + Recipe $recipe + ): RedirectResponse { + return DB::transaction(function () use ($request, $recipe) { + $recipe->inertiaFill($request->validated()); + + if ($recipe->saveOrFail()) { + return redirect()->route("back.{$this->modelName}.edit", $recipe) + ->with('success', trans('uwil-backend::crud.messages.has_been_updated', [ + 'model' => Str::ucfirst(trans_choice("models.classes.{$this->modelName}", 1)), + ])); + } + + return redirect()->route("back.{$this->modelName}.edit", $recipe) + ->with('error', trans('uwil-backend::crud.messages.cannot_be_updated', [ + 'model' => Str::ucfirst(trans_choice("models.classes.{$this->modelName}", 1)), + ])); + }); + } + + /** + * Duplicate the specified resource. + */ + public function duplicate(Recipe $recipe): Response + { + return $this->create($recipe->replicate()); + } + + /** + * Restore the specified resource. + */ + public function restore(int $recipe): RedirectResponse + { + $recipe = Recipe::withTrashed()->findOrFail($recipe); + + $previousQueries = $this->getPreviousQueries(); + + if ($recipe->restore()) { + return redirect()->route("back.{$this->modelName}.trash", $previousQueries) + ->with('success', trans('uwil-backend::crud.messages.has_been_restored', [ + 'model' => Str::ucfirst(trans_choice("models.classes.{$this->modelName}", 1)), + ])); + } + + return redirect()->back() + ->with('error', trans('uwil-backend::crud.messages.cannot_be_restored', [ + 'model' => Str::ucfirst(trans_choice("models.classes.{$this->modelName}", 1)), + ])); + } + + /** + * Destroy the specified resource. + */ + public function destroy(Recipe $recipe): RedirectResponse + { + $previousQueries = $this->getPreviousQueries(); + + if ($recipe->delete()) { + return redirect()->route("back.{$this->modelName}.index", $previousQueries) + ->with('success', trans('uwil-backend::crud.messages.has_been_deleted', [ + 'model' => Str::ucfirst(trans_choice("models.classes.{$this->modelName}", 1)), + ])); + } + + return redirect()->back() + ->with('error', trans('uwil-backend::crud.messages.cannot_be_deleted', [ + 'model' => Str::ucfirst(trans_choice("models.classes.{$this->modelName}", 1)), + ])); + } + + /** + * Destroy the specified resource. + */ + public function forceDestroy(Recipe | int $recipe): RedirectResponse + { + if (is_int($recipe)) { + $recipe = Recipe::withTrashed()->findOrFail($recipe); + } + + $previousQueries = $this->getPreviousQueries(); + + if ($recipe->forceDelete()) { + return redirect()->route("back.{$this->modelName}.trash", $previousQueries) + ->with('success', trans('uwil-backend::crud.messages.has_been_deleted', [ + 'model' => Str::ucfirst(trans_choice("models.classes.{$this->modelName}", 1)), + ])); + } + + return redirect()->back() + ->with('error', trans('uwil-backend::crud.messages.cannot_be_deleted', [ + 'model' => Str::ucfirst(trans_choice("models.classes.{$this->modelName}", 1)), + ])); + } + + /** @return array> */ + private function getPreviousQueries(): array + { + $previousQueries = []; + $previousQueryString = \parse_url($this->getPreviousUrl(), \PHP_URL_QUERY) ?? '' ?: ''; + \parse_str($previousQueryString, $previousQueries); + + return $previousQueries; + } + + private function getPreviousUrl(): string + { + return url()->previous(route('back.dashboard.index')); + } +} diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php new file mode 100644 index 00000000..f3d25c0a --- /dev/null +++ b/app/Http/Controllers/Controller.php @@ -0,0 +1,17 @@ + + * @return array */ public function share(Request $request): array { @@ -30,18 +31,13 @@ class HandleInertiaRequests extends Middleware foreach ($dynamicPageModels as $dynamicPageModel) { $pascalCase = - str_replace(' ', '', ucwords(str_replace('-', ' ', $dynamicPageModel->id_key))); + str_replace(' ', '', ucwords(str_replace('-', ' ', $dynamicPageModel->id_key))); $dynamicPages["page{$pascalCase}"] = $dynamicPageModel; } - - $props = [ ...InertiaHelper::errorProps(parent::share($request)), - 'recaptchaKey' => config('services.google.recaptcha.public'), 'dynamicPages' => $dynamicPages, - 'activitySectorEnum' => ActivitySectorEnum::toArray(), - 'objectEnum' => ObjectEnum::toArray() // More props here. ]; @@ -50,4 +46,13 @@ class HandleInertiaRequests extends Middleware InertiaSharedRegistry::resolve($request) ); } + + public function shareOnce(Request $request): array + { + return array_merge(parent::shareOnce($request), [ + 'recaptchaKey' => fn() => Config::string('services.google.recaptcha.public'), + 'activitySectorEnum' => fn() => ActivitySectorEnum::toArray(), + 'objectEnum' => fn() => ObjectEnum::toArray() + ]); + } } diff --git a/app/Http/Middlewares/SecureHeaders.php b/app/Http/Middlewares/SecureHeaders.php new file mode 100644 index 00000000..511927cf --- /dev/null +++ b/app/Http/Middlewares/SecureHeaders.php @@ -0,0 +1,167 @@ +routeIs('bo*') ? "'unsafe-eval'" : ''; + $jsdelivrUrl = 'https://cdn.jsdelivr.net'; + $openStreetMapTiles = 'https://*.tile.openstreetmap.org'; + + $this->declareNonce($nonce); + $this->removeUnwantedHeaders($this->unwantedHeaderList); + + $response = $next($request); + + $response->headers->set('Referrer-Policy', 'no-referrer-when-downgrade'); + $response->headers->set('X-Content-Type-Options', 'nosniff'); + $response->headers->set('X-XSS-Protection', '1; mode=block'); + $response->headers->set('X-Frame-Options', 'SAMEORIGIN'); + $response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload'); + + // * Ignore CSP on some conditions. + if ( + $appDebug && ( + $response->isClientError() || // Ignition + $response->isServerError() || // Ignition + Vite::isRunningHot() || // Vite + ($telescopeEnabled && str_starts_with($request->path(), 'telescope')) // Telescope + ) + ) { + return $response; + } + + $cspPolicies = [ + "default-src 'none';", + "base-uri 'self';", + "manifest-src 'self';", + "media-src 'self' blob: {$appUrl};", + // * CKEDITOR Specific https://ckeditor.com/docs/ckeditor5/latest/installation/advanced/csp.html + "connect-src 'self' blob: " . implode(' ', [ + 'https://yl.s-web.fr/', + // * BAN address autocomplete (api-adresse.data.gouv.fr) + 'https://api-adresse.data.gouv.fr', + 'https://maps.googleapis.com', + // * VITE DEV + $viteDevSocket, + $viteDevOrigin, + ]) . ';', + "img-src 'self' data: blob: " . implode(' ', [ + 'https://maps.googleapis.com', + 'https://maps.gstatic.com', + $viteDevOrigin, + $openStreetMapTiles, + ]) . ' data:;', + 'child-src ' . implode(' ', []) . ';', + "frame-src 'self' " . implode(' ', [ + 'https://www.google.com/recaptcha/', + 'https://recaptcha.google.com/recaptcha/', + 'https://www.google.com/maps/', + 'https://www.youtube.com/embed/', + 'https://player.vimeo.com/', + 'https://e.issuu.com/embed.html?', + ]) . ';', + "object-src 'none';", + "font-src 'self' data: " . \implode(' ', [ + 'https://fonts.gstatic.com', + // * VITE DEV + $viteDevOrigin, + ]) . ';', + // ! Using CKEditor will must have unsafe-inline style + "style-src 'self' 'unsafe-inline' " . \implode(' ', [ + $jsdelivrUrl, + 'https://cdnjs.cloudflare.com', + 'https://fonts.googleapis.com', + 'https://use.fontawesome.com', + // * File Manager + // * VITE DEV + $viteDevOrigin, + ]) . ';', + + // ! If you dont use ckeditor use this + // "style-src 'self' 'nonce-$nonce';", + "script-src 'self' 'nonce-$nonce' 'strict-dynamic' " . \implode(' ', [ + 'https: blob:', + $jsBo, + 'https://www.googletagmanager.com', + 'https://cdnjs.cloudflare.com', + 'https://www.google-analytics.com', + // * VITE DEV + $viteDevOrigin, + ]) . ';', + ]; + + $response->headers->set( + 'Content-Security-Policy', + implode(' ', $cspPolicies) + ); + $response->headers->set('Feature-Policy', 'fullscreen \'self\'; geolocation \'self\';'); + + return $response; + } + + /** + * Remove unwanted headers + * + * @param string[] $headerList List of headers to remove from the response + * @return void + */ + private function removeUnwantedHeaders(array $headerList) + { + foreach ($headerList as $header) { + header_remove($header); + } + } + + private function declareNonce(string $value): void + { + Vite::useCspNonce($value); + $this->container->singleton('csp_nonce', $value); + } +} diff --git a/app/Http/Requests/Back/Products/IndexProductRequest.php b/app/Http/Requests/Back/Products/IndexProductRequest.php new file mode 100644 index 00000000..75216525 --- /dev/null +++ b/app/Http/Requests/Back/Products/IndexProductRequest.php @@ -0,0 +1,40 @@ +authService->check(); + } + + public function getSearchValue(): ?string + { + return $this->has('search') ? + $this->string('search')->value() : null; + } + + /** + * Get the validation rules that apply to the request. + * + * @return array|string> + */ + public function rules(): array + { + return [ + 'search' => ['sometimes', 'string'], + ]; + } +} diff --git a/app/Http/Requests/Back/Products/StoreProductRequest.php b/app/Http/Requests/Back/Products/StoreProductRequest.php new file mode 100644 index 00000000..a98e4300 --- /dev/null +++ b/app/Http/Requests/Back/Products/StoreProductRequest.php @@ -0,0 +1,36 @@ +authService->check(); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array|string> + */ + public function rules(): array + { + return [ + // + ]; + } +} diff --git a/app/Http/Requests/Back/Products/UpdateProductRequest.php b/app/Http/Requests/Back/Products/UpdateProductRequest.php new file mode 100644 index 00000000..4ac40439 --- /dev/null +++ b/app/Http/Requests/Back/Products/UpdateProductRequest.php @@ -0,0 +1,22 @@ +|string> + */ + public function rules(): array + { + return [ + // + ]; + } +} diff --git a/app/Http/Requests/Back/Recipes/IndexRecipeRequest.php b/app/Http/Requests/Back/Recipes/IndexRecipeRequest.php new file mode 100644 index 00000000..e626fb4c --- /dev/null +++ b/app/Http/Requests/Back/Recipes/IndexRecipeRequest.php @@ -0,0 +1,43 @@ +authService->check(); + } + + public function getSearchValue(): ?string + { + return $this->has('search') ? + $this->string('search')->value() : null; + } + + /** + * Get the validation rules that apply to the request. + * + * @return array|string> + */ + public function rules(): array + { + return [ + 'search' => ['sometimes', 'string'], + ]; + } +} diff --git a/app/Http/Requests/Back/Recipes/StoreRecipeRequest.php b/app/Http/Requests/Back/Recipes/StoreRecipeRequest.php new file mode 100644 index 00000000..55d0eb66 --- /dev/null +++ b/app/Http/Requests/Back/Recipes/StoreRecipeRequest.php @@ -0,0 +1,36 @@ +authService->check(); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array|string> + */ + public function rules(): array + { + return [ + // + ]; + } +} diff --git a/app/Http/Requests/Back/Recipes/UpdateRecipeRequest.php b/app/Http/Requests/Back/Recipes/UpdateRecipeRequest.php new file mode 100644 index 00000000..a7e3b39c --- /dev/null +++ b/app/Http/Requests/Back/Recipes/UpdateRecipeRequest.php @@ -0,0 +1,36 @@ +authService->check(); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array|string> + */ + public function rules(): array + { + return [ + // + ]; + } +} diff --git a/app/Models/Product.php b/app/Models/Product.php index f7a57a3d..f922e34c 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -16,7 +16,14 @@ use Illuminate\Database\Eloquent\Casts\AsEnumCollection; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; +use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Str; +use Spatie\Translatable\HasTranslations; +use Uwil\LaravelActivityLogs\Traits\HasActivityLog; +use Uwil\LaravelBackend\Traits\Models\HasCache; +use Uwil\LaravelBackend\Traits\Models\HasModelNaming; +use Uwil\LaravelBackend\Traits\Models\HasUwilImages; +use Uwil\LaravelI18n\Interfaces\TranslatableModel; /** * @property int $id @@ -48,11 +55,19 @@ use Illuminate\Support\Str; * * @mixin \Eloquent */ -class Product extends Model +class Product extends Model implements TranslatableModel { + use HasActivityLog; + use HasCache; + /** @use HasFactory<\Database\Factories\ProductFactory> */ use HasFactory; + use HasModelNaming; + use HasTranslations; + use HasUwilImages; + use SoftDeletes; + protected $fillable = [ 'seo_title', 'seo_description', diff --git a/app/Models/Recipe.php b/app/Models/Recipe.php index db0294e8..926ba018 100644 --- a/app/Models/Recipe.php +++ b/app/Models/Recipe.php @@ -6,14 +6,21 @@ namespace App\Models; use App\Lib\Helpers\FileStorageHelper; use App\Lib\Helpers\ToolboxHelper; -use Domain\Models\ValueObjects\Recipes\Inspiration; -use Domain\Models\ValueObjects\Recipes\Season; use Domain\Models\ValueObjects\Products\ProductRange; use Domain\Models\ValueObjects\Recipes\ConsumptionTime; +use Domain\Models\ValueObjects\Recipes\Inspiration; +use Domain\Models\ValueObjects\Recipes\Season; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; +use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Str; +use Spatie\Translatable\HasTranslations; +use Uwil\LaravelActivityLogs\Traits\HasActivityLog; +use Uwil\LaravelBackend\Traits\Models\HasCache; +use Uwil\LaravelBackend\Traits\Models\HasModelNaming; +use Uwil\LaravelBackend\Traits\Models\HasUwilImages; +use Uwil\LaravelI18n\Interfaces\TranslatableModel; /** * @property int $id @@ -36,14 +43,24 @@ use Illuminate\Support\Str; * @property \Illuminate\Support\Carbon|null $created_at * @property \Illuminate\Support\Carbon|null $updated_at * @property-read \Illuminate\Database\Eloquent\Collection $products + * * @method static \Database\Factories\RecipeFactory factory($count = null, $state = []) + * * @mixin \Eloquent */ -class Recipe extends Model +class Recipe extends Model implements TranslatableModel { + use HasActivityLog; + use HasCache; + /** @use HasFactory<\Database\Factories\RecipeFactory> */ use HasFactory; + use HasModelNaming; + use HasTranslations; + use HasUwilImages; + use SoftDeletes; + protected $fillable = [ 'seo_title', 'seo_description', @@ -59,7 +76,7 @@ class Recipe extends Model 'details', 'ingredients_list', 'preparation_steps', - 'tips_and_advices' + 'tips_and_advices', ]; protected $casts = [ diff --git a/app/Policies/ProductPolicy.php b/app/Policies/ProductPolicy.php new file mode 100644 index 00000000..e0288dbf --- /dev/null +++ b/app/Policies/ProductPolicy.php @@ -0,0 +1,31 @@ +app->singleton( + AdminAuthService::class, + function () { + $guard = Auth::guard('backend'); + return new AdminAuthService($guard); + } + ); + } +} diff --git a/app/Services/AdminAuthService.php b/app/Services/AdminAuthService.php new file mode 100644 index 00000000..1a4fe6a9 --- /dev/null +++ b/app/Services/AdminAuthService.php @@ -0,0 +1,58 @@ +getUser()?->can($abilities, $arguments) ?? false; + } + + public function check(): bool + { + return $this->guard->check(); + } + + public function guest(): bool + { + return ! $this->check(); + } + + public function getUser(): ?User + { + $user = $this->guard->user(); + + if ($user && ! ($user instanceof User)) { + throw new \InvalidArgumentException('Auth service needs a backend guard'); + } + + return $user ? clone $user : null; + } + + public function getUserAsMandatory(): User + { + $user = $this->getUser(); + + if ($user === null) { + throw new AuthServiceException('Authenticated user is required from here'); + } + + return clone $user; + } + + public function getMandatoryUserId(): string + { + return (string) $this->getUserAsMandatory()->id; + } +} diff --git a/bootstrap/app.php b/bootstrap/app.php index 33107821..7838075d 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -12,12 +12,15 @@ return Application::configure(basePath: dirname(__DIR__)) ->withRouting( health: '/up', ) + ->withProviders([ + \App\Providers\AppServiceProvider::class + ]) ->withMiddleware(function (Middleware $middleware) { $middleware->web(append: [ \Uwil\LaravelI18n\Middlewares\ForceLocale::class, \App\Http\Middlewares\HandleInertiaRequests::class, \Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets::class, - \Uwil\LaravelCommon\Middlewares\SecureHeaders::class, + \App\Http\Middlewares\SecureHeaders::class, \Uwil\LaravelCommon\Middlewares\TrimStrings::class, \Uwil\LaravelCommon\Middlewares\TrustHosts::class, \Uwil\LaravelCommon\Middlewares\TrustProxies::class, diff --git a/bun.lock b/bun.lock index 908f5506..888f1411 100644 --- a/bun.lock +++ b/bun.lock @@ -58,6 +58,7 @@ "slugify": "^1.6.9", "swiper": "^12.2.0", "tailwind-merge": "^3.6.0", + "typescript": "^6.0.3", "vaul": "^1.1.2", "ziggy-js": "^2.6.3", "zod": "^4.4.3", @@ -86,6 +87,7 @@ "@typescript-eslint/eslint-plugin": "^8.62.0", "@typescript-eslint/parser": "^8.62.0", "@uwil/husky-config": "^1.0.1", + "@vitejs/plugin-basic-ssl": "^2.3.0", "@vitejs/plugin-react": "^6.0.3", "autoprefixer": "^10.5.2", "concurrently": "^9.2.3", @@ -113,6 +115,7 @@ "stylelint-scss": "^6.14.0", "tailwindcss": "^3.4.19", "tailwindcss-animate": "^1.0.7", + "typescript-eslint": "^8.62.1", "vite": "^8.1.0", "vite-plugin-eslint": "^1.8.1", "vite-plugin-stylelint": "^6.3.0", @@ -608,46 +611,6 @@ "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.62.0", "", { "dependencies": { "@typescript-eslint/types": "8.62.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-CY3uyFSRbcQv3nnSv8S0+lDftMVz6P963PoRlxrV7ew/Md564g9ut60PYzdLM5qW4jFn93GBF+Soi90ISAN+GQ=="], - "@typescript/typescript-aix-ppc64": ["@typescript/typescript-aix-ppc64@7.0.1-rc", "", { "os": "aix", "cpu": "ppc64" }, "sha512-oqq2ZfEJ7BQuufcC3QBQndZLPNyamYNHLao8lKRBeeSkZKypBqxPSgkzrcFZtbYcIaBvpiyUnQP9MT7DEYHWbw=="], - - "@typescript/typescript-darwin-arm64": ["@typescript/typescript-darwin-arm64@7.0.1-rc", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Slc0yTftT2F/uGDmtPst8ijydneL6uZaLEyr2UjahxZpbhTjHFBJ5agXtVz/TL4A+ldxzjzj+E8QtLZlh/5mXw=="], - - "@typescript/typescript-darwin-x64": ["@typescript/typescript-darwin-x64@7.0.1-rc", "", { "os": "darwin", "cpu": "x64" }, "sha512-h68iFW/LbA1/BsGgSRGFw981/3s1f/rY27YrmeZNuN+ly7dI+fiDduwT9ZT9866x2onoKNRq7PTyxSKyKDzfAQ=="], - - "@typescript/typescript-freebsd-arm64": ["@typescript/typescript-freebsd-arm64@7.0.1-rc", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-DE+ppd8Ix2c6OMuRkKY4PJ4hngMGJ9M95OQUP17p9xL/1IKXof7npIeuusMN/bgL5o5JzMfSGh+N+5scTYRg0Q=="], - - "@typescript/typescript-freebsd-x64": ["@typescript/typescript-freebsd-x64@7.0.1-rc", "", { "os": "freebsd", "cpu": "x64" }, "sha512-ST1ozHMw0u+CLOnWkcTyWDMV4Qn9osZ6fd1V1lnKDM1t0hZIp81mdGpdHxyHJjd7jdGrb6Gb/QXcZ1uqZ0t5zw=="], - - "@typescript/typescript-linux-arm": ["@typescript/typescript-linux-arm@7.0.1-rc", "", { "os": "linux", "cpu": "arm" }, "sha512-gHmHwT5Naq5CKM8g9bbaGeEpnwQEvWCLn3fwP4K2m61VQdDKkPk0Dhab/OoZ4LV2SrMddmclYXTzpyg23YGt5g=="], - - "@typescript/typescript-linux-arm64": ["@typescript/typescript-linux-arm64@7.0.1-rc", "", { "os": "linux", "cpu": "arm64" }, "sha512-N46pRihK3t5zD5MUtTQcdmQUqr1WI4U2nxno1gLwOtRSsB4krFkRjPHcQNG7h2DtRkX64rQiReX6WKwg2wprMA=="], - - "@typescript/typescript-linux-loong64": ["@typescript/typescript-linux-loong64@7.0.1-rc", "", { "os": "linux", "cpu": "none" }, "sha512-G17Sao312rgiPBTh2F4nOpLpa3CcnBSaNhqNghZk2LNhnsp1RaMO5HMq2me21gqu9xLpc6CIgHtOzU6JBgNlfg=="], - - "@typescript/typescript-linux-mips64el": ["@typescript/typescript-linux-mips64el@7.0.1-rc", "", { "os": "linux", "cpu": "none" }, "sha512-0FQspOb5UsQ4tQKvWgUO3pS9OIWkP7/8dPRWq+CRazJUeQZ4LBjtYK52jg5iIOrvItrVl2CwvRtrU3/9OihwJg=="], - - "@typescript/typescript-linux-ppc64": ["@typescript/typescript-linux-ppc64@7.0.1-rc", "", { "os": "linux", "cpu": "ppc64" }, "sha512-SUmwfVBEv6A2Ld0eWfcvW0FqrgemfQL8jFGOmV1qYxsDqumjE5DekHXqbstgmbE4SHr4rrjHjvmuGCY+kTH/vw=="], - - "@typescript/typescript-linux-riscv64": ["@typescript/typescript-linux-riscv64@7.0.1-rc", "", { "os": "linux", "cpu": "none" }, "sha512-rxeqnNnGiYzv/LlPHi/3+4p0ooR1cNJLjRIHXKovtiVmxXGJq6gtw8VSpbHuWPekyFMXgIAoLCZN0SQ51rAALQ=="], - - "@typescript/typescript-linux-s390x": ["@typescript/typescript-linux-s390x@7.0.1-rc", "", { "os": "linux", "cpu": "s390x" }, "sha512-RYWCHCiPypxajdRHM2CNK/eM22e4Ex5TTjV2pXf7PTtBowGr0xX8i8kIMknyZS0LX2QfleYHouaoMVsFDSle3g=="], - - "@typescript/typescript-linux-x64": ["@typescript/typescript-linux-x64@7.0.1-rc", "", { "os": "linux", "cpu": "x64" }, "sha512-PfLJSu0JzroDkqw2m4nqflPEcn8yev0m/vHFQlY9EzHorzjR6QG0wL8AJHvnD1e6h1s76AZngJ5u+z1K/D/HKw=="], - - "@typescript/typescript-netbsd-arm64": ["@typescript/typescript-netbsd-arm64@7.0.1-rc", "", { "os": "none", "cpu": "arm64" }, "sha512-FfbPxH3dTfp8yVIaNM7bdWTixXuyxpzoemluqcqMROSIz+ImpCG3Q9HO9Ptzp9/giv+P9YYEnCMSXh61migj2w=="], - - "@typescript/typescript-netbsd-x64": ["@typescript/typescript-netbsd-x64@7.0.1-rc", "", { "os": "none", "cpu": "x64" }, "sha512-FzdTfSzhRYb6hlav6K3cI5RVgcvCTvNAu/vc+t7B6AmZkThQ+t/1ntnvT5fnHmY1Az2RIBw7/b+qtCEG61HJTQ=="], - - "@typescript/typescript-openbsd-arm64": ["@typescript/typescript-openbsd-arm64@7.0.1-rc", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-PQGhlxfNig+0YQ9Wwzd0USPBkt6w/ZqkBQWsU7G/0JkTzunJel+jSWwhKw4947pak/m7hGSeYiI04xReDLGZww=="], - - "@typescript/typescript-openbsd-x64": ["@typescript/typescript-openbsd-x64@7.0.1-rc", "", { "os": "openbsd", "cpu": "x64" }, "sha512-WJ7NYgO2mHmLUkI/tZ+hl8lFd26QPJqO8ONOHNuYbdsybLvSB6B6sep222JIVrOfPRDGvFinbGGB+l3m1FWRWA=="], - - "@typescript/typescript-sunos-x64": ["@typescript/typescript-sunos-x64@7.0.1-rc", "", { "os": "sunos", "cpu": "x64" }, "sha512-UYjDeUxd765V9qcwlUPk4pEXyL0i3G76CJm9baK4i99u1pGO1psf3nXDw4MMmElVOPvGbZag99ZR/O59E2OX6w=="], - - "@typescript/typescript-win32-arm64": ["@typescript/typescript-win32-arm64@7.0.1-rc", "", { "os": "win32", "cpu": "arm64" }, "sha512-KzXzFSXZOm7zvEt2Aw0MsB2LbTL88znAiVqTDNAOHdlEb7brgmUQh/X2wM/8Be+N0fjEqWKl65cBKNwpWEbJiw=="], - - "@typescript/typescript-win32-x64": ["@typescript/typescript-win32-x64@7.0.1-rc", "", { "os": "win32", "cpu": "x64" }, "sha512-98R3+OqDr/r0/PLWEoXu88AE0lGVLNd335Ew8ONgzK1JWkNs4ou/5BGt3Or1ij4iXjH+c7PRL+jFjCbtWze+EA=="], - "@ungap/structured-clone": ["@ungap/structured-clone@1.3.2", "", {}, "sha512-5jsZFwgR5rTdKwidH9Qmat75RKwqfpKlWWB1frDkljN127mwqBu8K0PYo7/hFpF03IEJpfVPpCQDY/eDx3iHvA=="], "@unrs/resolver-binding-android-arm-eabi": ["@unrs/resolver-binding-android-arm-eabi@1.12.2", "", { "os": "android", "cpu": "arm" }, "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w=="], @@ -696,6 +659,8 @@ "@uwil/husky-config": ["@uwil/husky-config@1.0.1", "https://lab.s-web.fr/api/v4/projects/259/packages/npm/@uwil/husky-config/-/@uwil/husky-config-1.0.1.tgz", { "dependencies": { "@commitlint/cli": "^20.5.0", "@commitlint/config-conventional": "^20.5.0", "husky": "^9.1.7" } }, "sha1-VGchal1LcEFXNFsQZxh5pn4495Q="], + "@vitejs/plugin-basic-ssl": ["@vitejs/plugin-basic-ssl@2.3.0", "", { "peerDependencies": { "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-bdyo8rB3NnQbikdMpHaML9Z1OZPBu6fFOBo+OtxsBlvMJtysWskmBcnbIDhUqgC8tcxNv/a+BcV5U+2nQMm1OQ=="], + "@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.3", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.1" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg=="], "acorn": ["acorn@8.17.0", "", { "bin": "bin/acorn" }, "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg=="], @@ -1672,7 +1637,9 @@ "typed-array-length": ["typed-array-length@1.0.8", "", { "dependencies": { "call-bind": "^1.0.9", "for-each": "^0.3.5", "gopd": "^1.2.0", "is-typed-array": "^1.1.15", "possible-typed-array-names": "^1.1.0", "reflect.getprototypeof": "^1.0.10" } }, "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g=="], - "typescript": ["typescript@7.0.1-rc", "", { "optionalDependencies": { "@typescript/typescript-aix-ppc64": "7.0.1-rc", "@typescript/typescript-darwin-arm64": "7.0.1-rc", "@typescript/typescript-darwin-x64": "7.0.1-rc", "@typescript/typescript-freebsd-arm64": "7.0.1-rc", "@typescript/typescript-freebsd-x64": "7.0.1-rc", "@typescript/typescript-linux-arm": "7.0.1-rc", "@typescript/typescript-linux-arm64": "7.0.1-rc", "@typescript/typescript-linux-loong64": "7.0.1-rc", "@typescript/typescript-linux-mips64el": "7.0.1-rc", "@typescript/typescript-linux-ppc64": "7.0.1-rc", "@typescript/typescript-linux-riscv64": "7.0.1-rc", "@typescript/typescript-linux-s390x": "7.0.1-rc", "@typescript/typescript-linux-x64": "7.0.1-rc", "@typescript/typescript-netbsd-arm64": "7.0.1-rc", "@typescript/typescript-netbsd-x64": "7.0.1-rc", "@typescript/typescript-openbsd-arm64": "7.0.1-rc", "@typescript/typescript-openbsd-x64": "7.0.1-rc", "@typescript/typescript-sunos-x64": "7.0.1-rc", "@typescript/typescript-win32-arm64": "7.0.1-rc", "@typescript/typescript-win32-x64": "7.0.1-rc" }, "bin": { "tsc": "bin/tsc" } }, "sha512-drEP77wK7CCDlPfXZH4e008UUQOsw1DFmHmZOZjuNA+yoDLLnSNMZRXi90NbV/1LVo7SbNLq1bs3jjvk49TEqQ=="], + "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], + + "typescript-eslint": ["typescript-eslint@8.62.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.62.1", "@typescript-eslint/parser": "8.62.1", "@typescript-eslint/typescript-estree": "8.62.1", "@typescript-eslint/utils": "8.62.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-vymnnM5g0AKQDSAyfP12nMIBvgwgA42syg74kkuZ4x1VuTzwQKwc5h9rGxeShCjny5o+zWAb6OEoz7XLgrIkIw=="], "unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="], @@ -1846,6 +1813,14 @@ "tailwindcss/resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": "bin/resolve" }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="], + "typescript-eslint/@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.62.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.62.1", "@typescript-eslint/type-utils": "8.62.1", "@typescript-eslint/utils": "8.62.1", "@typescript-eslint/visitor-keys": "8.62.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.62.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-4EQM77WgVNxj7OkL/5b/D/xZsw00G577+UriYTC7JF5opcF3T2AuoeY7ueLaZgSVjSgCS6yOAJB5bRGLPSJUzA=="], + + "typescript-eslint/@typescript-eslint/parser": ["@typescript-eslint/parser@8.62.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.62.1", "@typescript-eslint/types": "8.62.1", "@typescript-eslint/typescript-estree": "8.62.1", "@typescript-eslint/visitor-keys": "8.62.1", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-sPhE4iHuJDSvoAiec+Ro8JyXw8f0ql13HFR82P99nCm9GwTEKG0KYLvDe6REk8BCXuit6vJAv/Yxg5ABaNS2rA=="], + + "typescript-eslint/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.62.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.62.1", "@typescript-eslint/tsconfig-utils": "8.62.1", "@typescript-eslint/types": "8.62.1", "@typescript-eslint/visitor-keys": "8.62.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-xMcW9oP9u7fAMXYs9A65CVmtLQe2r//oXINHfi8HV+oiqhih17sbLdhXr4540YWlgpDKQdY854OL5ZrdCiQsAA=="], + + "typescript-eslint/@typescript-eslint/utils": ["@typescript-eslint/utils@8.62.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.62.1", "@typescript-eslint/types": "8.62.1", "@typescript-eslint/typescript-estree": "8.62.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-sHtbPfuKNZCG+ih8SyjjucqRntSVmp8XgL5u6o9mAhiSn8ds5o/M/XdM0abweme2Tln3szOstOrZ9OXitvPh0g=="], + "vite-plugin-full-reload/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], "vite-plugin-stylelint/@rollup/pluginutils": ["@rollup/pluginutils@5.4.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" } }, "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg=="], @@ -1864,6 +1839,54 @@ "table/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + "typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.62.1", "", { "dependencies": { "@typescript-eslint/types": "8.62.1", "@typescript-eslint/visitor-keys": "8.62.1" } }, "sha512-r4d249KbQ1SFdpeStvob8Ih6aPPIzfqllPVOtvhve6ZcpuVcYo5/7zUWckKpHE7StASX4kTKZTLf0WQm/wPkcg=="], + + "typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.62.1", "", { "dependencies": { "@typescript-eslint/types": "8.62.1", "@typescript-eslint/typescript-estree": "8.62.1", "@typescript-eslint/utils": "8.62.1", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-aXM5xlqXiTxPibXB93cLAURfT3rlizf7uMXISCXy66Isr/9hISJx3yDsKl0L7lKa51b8JpFuNKby0/O0pEm9jg=="], + + "typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.62.1", "", { "dependencies": { "@typescript-eslint/types": "8.62.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-4g3BLxfdTMy8iZG0MaBkadnlRrCJ74cQiFbyEVMrkwIoqdyaXXQM22cotDvrl4x28wgIZ9rEJRoM+mmhSJpJ1g=="], + + "typescript-eslint/@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.62.1", "", { "dependencies": { "@typescript-eslint/types": "8.62.1", "@typescript-eslint/visitor-keys": "8.62.1" } }, "sha512-r4d249KbQ1SFdpeStvob8Ih6aPPIzfqllPVOtvhve6ZcpuVcYo5/7zUWckKpHE7StASX4kTKZTLf0WQm/wPkcg=="], + + "typescript-eslint/@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.62.1", "", {}, "sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q=="], + + "typescript-eslint/@typescript-eslint/parser/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.62.1", "", { "dependencies": { "@typescript-eslint/types": "8.62.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-4g3BLxfdTMy8iZG0MaBkadnlRrCJ74cQiFbyEVMrkwIoqdyaXXQM22cotDvrl4x28wgIZ9rEJRoM+mmhSJpJ1g=="], + + "typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.62.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.62.1", "@typescript-eslint/types": "^8.62.1", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-yQ3RgY5RkSBpsNS1Bx/JQEcA24FOSdfGktoyprAr5u18390UQdtVcfnEv4nIrIshNnavlVyZBKxQwT1fIAE6cg=="], + + "typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.62.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-xadytJqX9vJVQ2fdQjkcIVigwaOJNWkpjdLt6cEQ+xPnrI1fkp+/jZE/I97k9KUjqtpd25i0HeyZf3T6dutv2g=="], + + "typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.62.1", "", {}, "sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q=="], + + "typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.62.1", "", { "dependencies": { "@typescript-eslint/types": "8.62.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-4g3BLxfdTMy8iZG0MaBkadnlRrCJ74cQiFbyEVMrkwIoqdyaXXQM22cotDvrl4x28wgIZ9rEJRoM+mmhSJpJ1g=="], + + "typescript-eslint/@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], + + "typescript-eslint/@typescript-eslint/typescript-estree/semver": ["semver@7.8.5", "", { "bin": "bin/semver.js" }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], + + "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.62.1", "", { "dependencies": { "@typescript-eslint/types": "8.62.1", "@typescript-eslint/visitor-keys": "8.62.1" } }, "sha512-r4d249KbQ1SFdpeStvob8Ih6aPPIzfqllPVOtvhve6ZcpuVcYo5/7zUWckKpHE7StASX4kTKZTLf0WQm/wPkcg=="], + + "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.62.1", "", {}, "sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q=="], + "@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + + "typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.62.1", "", {}, "sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q=="], + + "typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils/@typescript-eslint/types": ["@typescript-eslint/types@8.62.1", "", {}, "sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q=="], + + "typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.62.1", "", {}, "sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q=="], + + "typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + + "typescript-eslint/@typescript-eslint/parser/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + + "typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + + "typescript-eslint/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="], + + "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.62.1", "", { "dependencies": { "@typescript-eslint/types": "8.62.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-4g3BLxfdTMy8iZG0MaBkadnlRrCJ74cQiFbyEVMrkwIoqdyaXXQM22cotDvrl4x28wgIZ9rEJRoM+mmhSJpJ1g=="], + + "typescript-eslint/@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + + "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], } } diff --git a/database/migrations/2026_07_02_114944_create_products.php b/database/migrations/2026_07_02_114944_create_products.php index 8ab803cd..d2047968 100644 --- a/database/migrations/2026_07_02_114944_create_products.php +++ b/database/migrations/2026_07_02_114944_create_products.php @@ -6,7 +6,7 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -return new class() extends Migration +return new class () extends Migration { public function up(): void { @@ -42,6 +42,8 @@ return new class() extends Migration $table->string('pdf')->nullable(); + $table->softDeletes(); + $table->timestamps(); }); } diff --git a/database/migrations/2026_07_02_114954_create_recipes.php b/database/migrations/2026_07_02_114954_create_recipes.php index 695d5dd5..433f9084 100644 --- a/database/migrations/2026_07_02_114954_create_recipes.php +++ b/database/migrations/2026_07_02_114954_create_recipes.php @@ -6,7 +6,7 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -return new class() extends Migration +return new class () extends Migration { public function up(): void { @@ -35,6 +35,8 @@ return new class() extends Migration $table->text('preparation_steps'); $table->text('tips_and_advices')->nullable(); + $table->softDeletes(); + $table->timestamps(); }); } diff --git a/lang/en/models.php b/lang/en/models.php index 690ae381..6c6f81d7 100644 --- a/lang/en/models.php +++ b/lang/en/models.php @@ -3,7 +3,8 @@ declare(strict_types=1); return [ - "classes" => [ - // "users" => "utilisateur|utilisateurs", + 'classes' => [ + 'product' => 'product|products', + 'recipe' => 'recipe|recipes', ], ]; diff --git a/lang/fr/models.php b/lang/fr/models.php index 690ae381..c144bbf4 100644 --- a/lang/fr/models.php +++ b/lang/fr/models.php @@ -3,7 +3,8 @@ declare(strict_types=1); return [ - "classes" => [ - // "users" => "utilisateur|utilisateurs", + 'classes' => [ + 'product' => 'produit|produits', + 'recipe' => 'recette|recettes', ], ]; diff --git a/lang/fr/validation.php b/lang/fr/validation.php index 1e697590..3cdc759a 100644 --- a/lang/fr/validation.php +++ b/lang/fr/validation.php @@ -168,12 +168,14 @@ return [ 'available' => 'disponible', 'birthday' => 'anniversaire', 'body' => 'corps', + 'category' => 'catégorie', 'city' => 'ville', 'color' => 'color', 'company' => 'entreprise', 'compilation' => 'compilation', 'concept' => 'concept', 'conditions' => 'conditions', + 'consomption_time' => 'moment de consommation', 'content' => 'contenu', 'contest' => 'contest', 'country' => 'pays', @@ -206,7 +208,9 @@ return [ 'image_main' => 'image principale', 'image_mobile' => 'image mobile', 'images' => 'images', + 'inspiration' => 'inspiration', 'is_audience_winner' => 'est le gagnant du public', + 'is_egalim' => 'EGalim', 'is_hidden' => 'est caché', 'is_subscribed' => 'est abonné', 'is_visible' => 'est visible', @@ -235,6 +239,7 @@ return [ 'preview' => 'aperçu', 'price' => 'prix', 'product_id' => 'identifiant du produit', + 'product_range' => 'gamme', 'product_uid' => 'UID du produit', 'product_uuid' => 'UUID du produit', 'promo_code' => 'code promo', @@ -252,6 +257,7 @@ return [ 'role' => 'rôle', 'rule' => 'règle', 'rules' => 'règles', + 'season' => 'saison', 'second' => 'seconde', 'sex' => 'sexe', 'shipment' => 'expédition', @@ -269,6 +275,7 @@ return [ 'tag' => 'mot clé', 'tags' => 'mots clés', 'teacher' => 'professeur', + 'technology' => 'technologie', 'terms' => 'conditions', 'test_description' => 'description du test', 'test_locale' => 'localisation du test', diff --git a/package-lock.json b/package-lock.json index affe8ccd..cf6688b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -60,6 +60,7 @@ "slugify": "^1.6.9", "swiper": "^12.2.0", "tailwind-merge": "^3.6.0", + "typescript": "^6.0.3", "vaul": "^1.1.2", "ziggy-js": "^2.6.3", "zod": "^4.4.3", @@ -88,6 +89,7 @@ "@typescript-eslint/eslint-plugin": "^8.62.0", "@typescript-eslint/parser": "^8.62.0", "@uwil/husky-config": "^1.0.1", + "@vitejs/plugin-basic-ssl": "^2.3.0", "@vitejs/plugin-react": "^6.0.3", "autoprefixer": "^10.5.2", "concurrently": "^9.2.3", @@ -115,6 +117,7 @@ "stylelint-scss": "^6.14.0", "tailwindcss": "^3.4.19", "tailwindcss-animate": "^1.0.7", + "typescript-eslint": "^8.62.1", "vite": "^8.1.0", "vite-plugin-eslint": "^1.8.1", "vite-plugin-stylelint": "^6.3.0" @@ -4343,17 +4346,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.62.0.tgz", - "integrity": "sha512-o+mpz7EYiMzXoySXiKmzlabIvTVqUuK5yLrAedRPRDA0IpPFMUV1IXt6OqljIxX/kumN6EjUYp41Hqelh6p/Dw==", + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.62.1.tgz", + "integrity": "sha512-4EQM77WgVNxj7OkL/5b/D/xZsw00G577+UriYTC7JF5opcF3T2AuoeY7ueLaZgSVjSgCS6yOAJB5bRGLPSJUzA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.62.0", - "@typescript-eslint/type-utils": "8.62.0", - "@typescript-eslint/utils": "8.62.0", - "@typescript-eslint/visitor-keys": "8.62.0", + "@typescript-eslint/scope-manager": "8.62.1", + "@typescript-eslint/type-utils": "8.62.1", + "@typescript-eslint/utils": "8.62.1", + "@typescript-eslint/visitor-keys": "8.62.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -4366,21 +4369,103 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.62.0", + "@typescript-eslint/parser": "^8.62.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.62.0.tgz", - "integrity": "sha512-+g5O3j0w2ldzC86Pv6fvbO/xhAonbJFIdf/MKQ1d30gndlsVzUOE83ldfSE15Qrl9fhFjK6AovHs5Wpp6vx86w==", + "node_modules/@typescript-eslint/parser": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.62.1.tgz", + "integrity": "sha512-sPhE4iHuJDSvoAiec+Ro8JyXw8f0ql13HFR82P99nCm9GwTEKG0KYLvDe6REk8BCXuit6vJAv/Yxg5ABaNS2rA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.62.0", - "@typescript-eslint/typescript-estree": "8.62.0", - "@typescript-eslint/utils": "8.62.0", + "@typescript-eslint/scope-manager": "8.62.1", + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/typescript-estree": "8.62.1", + "@typescript-eslint/visitor-keys": "8.62.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.62.1.tgz", + "integrity": "sha512-yQ3RgY5RkSBpsNS1Bx/JQEcA24FOSdfGktoyprAr5u18390UQdtVcfnEv4nIrIshNnavlVyZBKxQwT1fIAE6cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.62.1", + "@typescript-eslint/types": "^8.62.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.62.1.tgz", + "integrity": "sha512-r4d249KbQ1SFdpeStvob8Ih6aPPIzfqllPVOtvhve6ZcpuVcYo5/7zUWckKpHE7StASX4kTKZTLf0WQm/wPkcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/visitor-keys": "8.62.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.1.tgz", + "integrity": "sha512-xadytJqX9vJVQ2fdQjkcIVigwaOJNWkpjdLt6cEQ+xPnrI1fkp+/jZE/I97k9KUjqtpd25i0HeyZf3T6dutv2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.62.1.tgz", + "integrity": "sha512-aXM5xlqXiTxPibXB93cLAURfT3rlizf7uMXISCXy66Isr/9hISJx3yDsKl0L7lKa51b8JpFuNKby0/O0pEm9jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/typescript-estree": "8.62.1", + "@typescript-eslint/utils": "8.62.1", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -4396,17 +4481,31 @@ "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.62.0.tgz", - "integrity": "sha512-+hVbNxtW64pIcZWDPGbyaKF7vp2IBTVY5ma1blwwksrjdsbdqqEKvJWMGbBofei4F6Dovx1M0RJgoFeNu2279A==", + "node_modules/@typescript-eslint/types": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.62.1.tgz", + "integrity": "sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.62.1.tgz", + "integrity": "sha512-xMcW9oP9u7fAMXYs9A65CVmtLQe2r//oXINHfi8HV+oiqhih17sbLdhXr4540YWlgpDKQdY854OL5ZrdCiQsAA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.62.0", - "@typescript-eslint/tsconfig-utils": "8.62.0", - "@typescript-eslint/types": "8.62.0", - "@typescript-eslint/visitor-keys": "8.62.0", + "@typescript-eslint/project-service": "8.62.1", + "@typescript-eslint/tsconfig-utils": "8.62.1", + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/visitor-keys": "8.62.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -4424,56 +4523,17 @@ "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/project-service": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.62.0.tgz", - "integrity": "sha512-wexnCqiTg7BOGtbLDftYpRWlmLq4xfoMd7BKFR6Y75sZS3QmRKLdN3yWLhmIYgqMmP/OXWpj3H8odkb5nGURCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.62.0", - "@typescript-eslint/types": "^8.62.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.0.tgz", - "integrity": "sha512-y2GAdB6ykaXUvuspbYnizQc4oDDz0Tz/Yc7iWrXf9mx8vm/L/0vLHCe0tS2boG96Zy+DivnVDQ9ZUEWoHqqx1g==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.62.0.tgz", - "integrity": "sha512-82r66fi9zYwZ+mTq3vKgwjbZ1PVk/DJzrXFLpG6RnBbdvH8TEGVHIs9H4d2drhkOzf0syZuD/OZvvlu6GDbP4g==", + "node_modules/@typescript-eslint/utils": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.62.1.tgz", + "integrity": "sha512-sHtbPfuKNZCG+ih8SyjjucqRntSVmp8XgL5u6o9mAhiSn8ds5o/M/XdM0abweme2Tln3szOstOrZ9OXitvPh0g==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.62.0", - "@typescript-eslint/types": "8.62.0", - "@typescript-eslint/typescript-estree": "8.62.0" + "@typescript-eslint/scope-manager": "8.62.1", + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/typescript-estree": "8.62.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4487,231 +4547,14 @@ "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.62.0.tgz", - "integrity": "sha512-+hVbNxtW64pIcZWDPGbyaKF7vp2IBTVY5ma1blwwksrjdsbdqqEKvJWMGbBofei4F6Dovx1M0RJgoFeNu2279A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.62.0", - "@typescript-eslint/tsconfig-utils": "8.62.0", - "@typescript-eslint/types": "8.62.0", - "@typescript-eslint/visitor-keys": "8.62.0", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/project-service": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.62.0.tgz", - "integrity": "sha512-wexnCqiTg7BOGtbLDftYpRWlmLq4xfoMd7BKFR6Y75sZS3QmRKLdN3yWLhmIYgqMmP/OXWpj3H8odkb5nGURCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.62.0", - "@typescript-eslint/types": "^8.62.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.0.tgz", - "integrity": "sha512-y2GAdB6ykaXUvuspbYnizQc4oDDz0Tz/Yc7iWrXf9mx8vm/L/0vLHCe0tS2boG96Zy+DivnVDQ9ZUEWoHqqx1g==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ts-api-utils": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", - "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.62.0.tgz", - "integrity": "sha512-dzHeT2gySzZtLDsuqxU9AkYgIsQoHAHtRBpOqM+Ofzx1Bwrd2RcCjQJ+6iQbsHOIR6NS33bF2W1k3blN1zLDrA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "8.62.0", - "@typescript-eslint/types": "8.62.0", - "@typescript-eslint/typescript-estree": "8.62.0", - "@typescript-eslint/visitor-keys": "8.62.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.62.0.tgz", - "integrity": "sha512-+hVbNxtW64pIcZWDPGbyaKF7vp2IBTVY5ma1blwwksrjdsbdqqEKvJWMGbBofei4F6Dovx1M0RJgoFeNu2279A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.62.0", - "@typescript-eslint/tsconfig-utils": "8.62.0", - "@typescript-eslint/types": "8.62.0", - "@typescript-eslint/visitor-keys": "8.62.0", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/project-service": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.62.0.tgz", - "integrity": "sha512-wexnCqiTg7BOGtbLDftYpRWlmLq4xfoMd7BKFR6Y75sZS3QmRKLdN3yWLhmIYgqMmP/OXWpj3H8odkb5nGURCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.62.0", - "@typescript-eslint/types": "^8.62.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.0.tgz", - "integrity": "sha512-y2GAdB6ykaXUvuspbYnizQc4oDDz0Tz/Yc7iWrXf9mx8vm/L/0vLHCe0tS2boG96Zy+DivnVDQ9ZUEWoHqqx1g==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree/node_modules/ts-api-utils": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", - "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.62.0.tgz", - "integrity": "sha512-1lX38kNxXIRb8mEc3lbq5mdHq1Pf2+U0nFU65KfT18mtPxxl0fvjuEE92mHuXPuCtElJhOrddOpyMlM3Z0umEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.62.0", - "@typescript-eslint/visitor-keys": "8.62.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.62.0.tgz", - "integrity": "sha512-KvAclkktORPvM54TgLgA4z9HIV1M8zOgw9ZVNXl9f/8dLYfXYX1wkMXP7qmabpijQRV5bHJLOmoyGQbLMaUYeg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.62.0.tgz", - "integrity": "sha512-CY3uyFSRbcQv3nnSv8S0+lDftMVz6P963PoRlxrV7ew/Md564g9ut60PYzdLM5qW4jFn93GBF+Soi90ISAN+GQ==", + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.62.1.tgz", + "integrity": "sha512-4g3BLxfdTMy8iZG0MaBkadnlRrCJ74cQiFbyEVMrkwIoqdyaXXQM22cotDvrl4x28wgIZ9rEJRoM+mmhSJpJ1g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.62.0", + "@typescript-eslint/types": "8.62.1", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -5070,6 +4913,19 @@ "node": ">=18.0.0" } }, + "node_modules/@vitejs/plugin-basic-ssl": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.3.0.tgz", + "integrity": "sha512-bdyo8rB3NnQbikdMpHaML9Z1OZPBu6fFOBo+OtxsBlvMJtysWskmBcnbIDhUqgC8tcxNv/a+BcV5U+2nQMm1OQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "peerDependencies": { + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/@vitejs/plugin-react": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.3.tgz", @@ -12753,6 +12609,19 @@ "tree-kill": "cli.js" } }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -12883,6 +12752,43 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.62.1.tgz", + "integrity": "sha512-vymnnM5g0AKQDSAyfP12nMIBvgwgA42syg74kkuZ4x1VuTzwQKwc5h9rGxeShCjny5o+zWAb6OEoz7XLgrIkIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.62.1", + "@typescript-eslint/parser": "8.62.1", + "@typescript-eslint/typescript-estree": "8.62.1", + "@typescript-eslint/utils": "8.62.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", diff --git a/package.json b/package.json index 7082dd20..520ba0e2 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "@typescript-eslint/eslint-plugin": "^8.62.0", "@typescript-eslint/parser": "^8.62.0", "@uwil/husky-config": "^1.0.1", + "@vitejs/plugin-basic-ssl": "^2.3.0", "@vitejs/plugin-react": "^6.0.3", "autoprefixer": "^10.5.2", "concurrently": "^9.2.3", @@ -65,6 +66,7 @@ "stylelint-scss": "^6.14.0", "tailwindcss": "^3.4.19", "tailwindcss-animate": "^1.0.7", + "typescript-eslint": "^8.62.1", "vite": "^8.1.0", "vite-plugin-eslint": "^1.8.1", "vite-plugin-stylelint": "^6.3.0" @@ -124,6 +126,7 @@ "slugify": "^1.6.9", "swiper": "^12.2.0", "tailwind-merge": "^3.6.0", + "typescript": "^6.0.3", "vaul": "^1.1.2", "ziggy-js": "^2.6.3", "zod": "^4.4.3", diff --git a/resources/ts/back/Components/Layout/Sidebar/AppSidebar.tsx b/resources/ts/back/Components/Layout/Sidebar/AppSidebar.tsx index 76a317ba..c5f02ba9 100644 --- a/resources/ts/back/Components/Layout/Sidebar/AppSidebar.tsx +++ b/resources/ts/back/Components/Layout/Sidebar/AppSidebar.tsx @@ -1,28 +1,23 @@ // * Packages import { Link, usePage } from "@inertiajs/react"; -import { ElementType, ReactNode, useEffect, useRef } from "react"; import { ChevronDownIcon, - MoreHorizontalIcon, + CookingPotIcon, + EggIcon, HomeIcon, + MoreHorizontalIcon, SquareArrowOutUpRightIcon, - TelescopeIcon, + TelescopeIcon } from "lucide-react"; +import { ElementType, ReactNode, useEffect, useRef } from "react"; import { route } from "ziggy-js"; // * Components shadcn +import { Button } from "@/components/ui/button"; import { - Sidebar, - SidebarHeader, - SidebarContent, - SidebarGroup, - SidebarGroupContent, - SidebarGroupLabel, - SidebarMenu, - SidebarMenuButton, - SidebarMenuItem, - SidebarMenuAction, - useSidebar, -} from "@/components/ui/sidebar"; + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/components/ui/collapsible"; import { DropdownMenu, DropdownMenuContent, @@ -30,25 +25,32 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { - Collapsible, - CollapsibleTrigger, - CollapsibleContent, -} from "@/components/ui/collapsible"; + Sidebar, + SidebarContent, + SidebarGroup, + SidebarGroupContent, + SidebarGroupLabel, + SidebarHeader, + SidebarMenu, + SidebarMenuAction, + SidebarMenuButton, + SidebarMenuItem, + useSidebar, +} from "@/components/ui/sidebar"; import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip"; -import { Button } from "@/components/ui/button"; // * Other import ApplicationLogo from "@/back/Components/ApplicationLogo"; import { SidebarRegistry } from "@/back/Components/Layout/Sidebar/SidebarRegistry"; import Views from "@/back/lib/actions"; +import { AuthProp } from "@/back/types/global"; import str from "@/hooks/use-string"; import useTrans from "@/hooks/use-translations"; -import { AppEnv, LaravelEnum } from "@/types/global"; -import { AuthProp } from "@/back/types/global"; import { GetTranslation } from "@/lib/utils"; +import { AppEnv, LaravelEnum } from "@/types/global"; export type NavListLinksProps = { /** Title of the button. */ @@ -137,6 +139,8 @@ export default function AppSidebar() { const transModules = useTrans("uwil-backend::back.modules"); const transAdmin = useTrans("uwil-backend::enums.user-role.admin"); const transTelescope = useTrans("uwil-backend::back.telescope"); + const transProducts = useTrans("models.classes.product", { choice: Infinity }); + const transRecipes = useTrans("models.classes.recipe", { choice: Infinity }); const navList: NavListProps = [ { @@ -156,6 +160,18 @@ export default function AppSidebar() { icon: SquareArrowOutUpRightIcon, }, ...SidebarRegistry.getAll(auth, "pages"), + { + title: str(transProducts).ucFirst().value(), + model: "products", + show: true, + icon: EggIcon, + }, + { + title: str(transRecipes).ucFirst().value(), + model: "recipes", + show: true, + icon: CookingPotIcon, + }, ], }, { diff --git a/resources/ts/back/Pages/Dashboard/Index.tsx b/resources/ts/back/Pages/Dashboard/Index.tsx index 469b013b..8dd6f136 100644 --- a/resources/ts/back/Pages/Dashboard/Index.tsx +++ b/resources/ts/back/Pages/Dashboard/Index.tsx @@ -1,23 +1,23 @@ // * Packages -import { AlertCircleIcon, SquareArrowOutUpRightIcon } from "lucide-react"; import { usePage } from "@inertiajs/react"; +import { AlertCircleIcon, SquareArrowOutUpRightIcon } from "lucide-react"; // * Components shadcn import AuthenticatedLayout from "@/back/Layouts/AuthenticatedLayout"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Card, CardContent, CardDescription, CardHeader, } from "@/components/ui/card"; -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; // * Other +import logoUwilReportDark from "&/images/logos/uwil-report-dark.svg"; +import logoUwilReportLight from "&/images/logos/uwil-report-light.svg"; +import logoUwilSupportDark from "&/images/logos/uwil-support-dark.svg"; +import logoUwilSupportLight from "&/images/logos/uwil-support-light.svg"; import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; import useTrans from "@/hooks/use-translations"; import { AppEnv } from "@/types/global"; -import logoUwilReportLight from "&/images/logos/uwil-report-light.svg"; -import logoUwilReportDark from "&/images/logos/uwil-report-dark.svg"; -import logoUwilSupportLight from "&/images/logos/uwil-support-light.svg"; -import logoUwilSupportDark from "&/images/logos/uwil-support-dark.svg"; export default function Dashboard() { const appEnv = usePage().props.appEnv as AppEnv; diff --git a/resources/ts/back/Pages/Products/Index.tsx b/resources/ts/back/Pages/Products/Index.tsx new file mode 100644 index 00000000..60991b88 --- /dev/null +++ b/resources/ts/back/Pages/Products/Index.tsx @@ -0,0 +1,190 @@ +// * Packages +import { Row } from "@tanstack/react-table"; +import { CheckIcon, PlusIcon, XIcon } from "lucide-react"; +import { route } from "ziggy-js"; +// * Components shadcn +import { Badge } from "@/components/ui/badge"; +import { usePage } from "@inertiajs/react"; +// * Other +import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; +import DataTable from "@/back/Components/Table/DataTable"; +import DataTableFilterButton from "@/back/Components/Table/DataTableFilterButton"; +import DataTableSearch from "@/back/Components/Table/DataTableSearch"; +import AuthenticatedLayout from "@/back/Layouts/AuthenticatedLayout"; +import Views from "@/back/lib/actions"; +import { AuthProp, DefaultModel, Resource } from "@/back/types/global"; +import str from "@/hooks/use-string"; +import useTrans from "@/hooks/use-translations"; +import { GetFormatedDate, GetTranslation } from "@/lib/utils"; + +const getColumns = () => { + return [ + { + accessorKey: "name", + header: ({ column }: { column: { id: string } }) => ( + + ), + }, + { + accessorKey: "category", + header: ({ column }: { column: { id: string } }) => ( + + ), + cell: ({ row }: { row: Row> }) => { + // Handle enum object or string + const val = row.getValue("category") as any; + const label = typeof val === 'object' && val !== null ? val.label : val; + return ( + + {str(String(label)).ucFirst().value()} + + ); + }, + }, + { + accessorKey: "technology", + header: ({ column }: { column: { id: string } }) => ( + + ), + cell: ({ row }: { row: Row> }) => { + // Handle enum object or string + const val = row.getValue("technology") as any; + const label = typeof val === 'object' && val !== null ? val.label : val; + return ( + + {str(String(label)).ucFirst().value()} + + ); + }, + }, + { + accessorKey: "is_egalim", + header: ({ column }: { column: { id: string } }) => ( + + ), + cell: ({ row }: { row: Row> }) => ( + + {row.getValue("is_egalim") ? ( + + ) : ( + + )} + {str( + GetTranslation( + row.getValue("is_egalim") + ? "uwil-backend::back.yes" + : "uwil-backend::back.no", + ), + ) + .ucFirst() + .value()} + + ), + }, + { + accessorKey: "updated_at", + header: ({ column }: { column: { id: string } }) => ( + + ), + cell: ({ row }: { row: Row> }) => ( + + {GetFormatedDate(row.getValue("updated_at"))} + + ), + }, + ]; +}; + +export default function Index({ + paginatedModels, + searchFields, +}: Readonly<{ + paginatedModels: Resource; + searchFields: string; +}>) { + const { auth } = usePage().props as unknown as { + auth: AuthProp; + }; + const { create } = auth.policies.products || { create: true }; + const actionModels = [ + Views.VISUALIZATION, + Views.EDITION, + Views.DUPLICATION, + Views.DELETION, + ]; + + const columns = getColumns(); + const transCrudAddEntry = useTrans("uwil-backend::back.crud_add_entry"); + const afterNode = () => { + return ( + create && ( + } + tooltipContent={

{transCrudAddEntry}

} + /> + ) + ); + }; + + return ( + + + + + ); +} diff --git a/resources/ts/back/Pages/Recipes/Index.tsx b/resources/ts/back/Pages/Recipes/Index.tsx new file mode 100644 index 00000000..150738f0 --- /dev/null +++ b/resources/ts/back/Pages/Recipes/Index.tsx @@ -0,0 +1,194 @@ +// * Packages +import { Row } from "@tanstack/react-table"; +import { CheckIcon, PlusIcon, XIcon } from "lucide-react"; +import { route } from "ziggy-js"; +// * Components shadcn +import { Badge } from "@/components/ui/badge"; +import { usePage } from "@inertiajs/react"; +// * Other +import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip"; +import DataTable from "@/back/Components/Table/DataTable"; +import DataTableFilterButton from "@/back/Components/Table/DataTableFilterButton"; +import DataTableSearch from "@/back/Components/Table/DataTableSearch"; +import AuthenticatedLayout from "@/back/Layouts/AuthenticatedLayout"; +import Views from "@/back/lib/actions"; +import { AuthProp, DefaultModel, Resource } from "@/back/types/global"; +import str from "@/hooks/use-string"; +import useTrans from "@/hooks/use-translations"; +import { GetFormatedDate, GetTranslation } from "@/lib/utils"; + +const getColumns = () => { + return [ + { + accessorKey: "name", + header: ({ column }: { column: { id: string } }) => ( + + ), + }, + { + accessorKey: "product_range", + header: ({ column }: { column: { id: string } }) => ( + + ), + cell: ({ row }: { row: Row> }) => { + const val = row.getValue("product_range") as any; + const label = typeof val === 'object' && val !== null ? val.label : val; + return ( + + {str(String(label)).ucFirst().value()} + + ); + }, + }, + { + accessorKey: "consomption_time", + header: ({ column }: { column: { id: string } }) => ( + + ), + cell: ({ row }: { row: Row> }) => { + const val = row.getValue("consomption_time") as any; + const label = typeof val === 'object' && val !== null ? val.label : val; + return ( + + {str(String(label)).ucFirst().value()} + + ); + }, + }, + { + accessorKey: "inspiration", + header: ({ column }: { column: { id: string } }) => ( + + ), + cell: ({ row }: { row: Row> }) => { + const val = row.getValue("inspiration") as any; + const label = typeof val === 'object' && val !== null ? val.label : val; + return ( + + {str(String(label)).ucFirst().value()} + + ); + }, + }, + { + accessorKey: "season", + header: ({ column }: { column: { id: string } }) => ( + + ), + cell: ({ row }: { row: Row> }) => { + const val = row.getValue("season") as any; + const label = typeof val === 'object' && val !== null ? val.label : val; + return ( + + {str(String(label)).ucFirst().value()} + + ); + }, + }, + { + accessorKey: "updated_at", + header: ({ column }: { column: { id: string } }) => ( + + ), + cell: ({ row }: { row: Row> }) => ( + + {GetFormatedDate(row.getValue("updated_at"))} + + ), + }, + ]; +}; + +export default function Index({ + paginatedModels, + searchFields, +}: Readonly<{ + paginatedModels: Resource; + searchFields: string; +}>) { + const { auth } = usePage().props as unknown as { + auth: AuthProp; + }; + const { create } = auth.policies.recipes || { create: true }; + const actionModels = [ + Views.VISUALIZATION, + Views.EDITION, + Views.DUPLICATION, + Views.DELETION, + ]; + + const columns = getColumns(); + const transCrudAddEntry = useTrans("uwil-backend::back.crud_add_entry"); + const afterNode = () => { + return ( + create && ( + } + tooltipContent={

{transCrudAddEntry}

} + /> + ) + ); + }; + + return ( + + + + + ); +} diff --git a/resources/views/back/app.blade.php b/resources/views/back/app.blade.php index cfc4faa0..0b6fa379 100644 --- a/resources/views/back/app.blade.php +++ b/resources/views/back/app.blade.php @@ -13,12 +13,12 @@ - + {{-- Scripts --}} @routes(nonce: $nonce ?? '') @if (app()->hasDebugModeEnabled()) @viteReactRefresh @endif - @vite(["resources/ts/{$page['component']}.tsx", 'resources/sass/back/app.scss']) + @vite(['resources/ts/app.tsx', "resources/ts/{$page['component']}.tsx", 'resources/sass/back/app.scss']) @inertiaHead diff --git a/resources/views/front/app.blade.php b/resources/views/front/app.blade.php index b97e641e..a2c1be83 100644 --- a/resources/views/front/app.blade.php +++ b/resources/views/front/app.blade.php @@ -12,12 +12,12 @@ - + {{-- Scripts --}} @routes(nonce: $nonce ?? '') @if (app()->hasDebugModeEnabled()) @viteReactRefresh @endif - @vite(["resources/ts/{$page['component']}.tsx", 'resources/sass/front/app.scss']) + @vite(['resources/ts/app.tsx', "resources/ts/{$page['component']}.tsx", 'resources/sass/front/app.scss']) @inertiaHead diff --git a/routes/back/web-back.php b/routes/back/web-back.php index 28bec970..23ae104c 100644 --- a/routes/back/web-back.php +++ b/routes/back/web-back.php @@ -1,4 +1,10 @@