From f00612339caf1dcb8e21d32e08dddebf5f5e521f Mon Sep 17 00:00:00 2001 From: marszayn Date: Wed, 12 Feb 2025 15:55:37 +0700 Subject: [PATCH] Initial Rapih --- app/Http/Controllers/Admin/RoleController.php | 21 + .../Auth/AuthenticatedSessionController.php | 3 + app/Http/Controllers/KategoriController.php | 65 ++ app/Http/Controllers/PostController.php | 103 ++ .../Controllers/SubKategoriController.php | 60 ++ app/Http/Requests/Auth/LoginRequest.php | 12 +- app/Http/Requests/KategoriRequest.php | 34 + app/Http/Requests/PostRequest.php | 36 + app/Http/Requests/RoleRequest.php | 30 + app/Http/Requests/SubKategoriRequest.php | 31 + app/Models/Kategori.php | 20 + app/Models/Post.php | 48 + app/Models/SubKategori.php | 23 + app/Models/User.php | 4 +- app/Providers/AppServiceProvider.php | 3 +- app/Providers/RepositoryServiceProvider.php | 25 + bootstrap/app.php | 8 +- bootstrap/providers.php | 1 + composer.json | 1 + composer.lock | 86 +- config/permission.php | 186 ++++ ...025_02_11_164124_create_kategori_table.php | 29 + ..._02_11_174528_create_permission_tables.php | 138 +++ ..._11_180031_add_username_to_users_table.php | 28 + ..._02_12_000414_create_subkategori_table.php | 36 + .../2025_02_12_003736_create_posts_table.php | 37 + ...2_081712_add_is_publish_to_posts_table.php | 29 + database/seeders/DatabaseSeeder.php | 8 +- database/seeders/PermissionsTableSeeder.php | 31 + database/seeders/PostSeeder.php | 46 + database/seeders/RolesTableSeeder.php | 27 + database/seeders/UserSeeder.php | 20 +- package-lock.json | 974 +++++++++++++++++- package.json | 6 +- resources/js/app.tsx | 3 +- .../js/components/Card/CardPengumuman.tsx | 193 ++-- resources/js/components/Partials/Navbar.tsx | 16 +- .../js/components/Partials/PopupModal.tsx | 40 +- .../js/components/Partials/SearchDialog.tsx | 72 ++ resources/js/components/app-sidebar.tsx | 21 +- resources/js/components/ui/dialog.tsx | 120 +++ resources/js/components/ui/select.tsx | 157 +++ resources/js/components/ui/textarea.tsx | 22 + resources/js/components/ui/toast.tsx | 127 +++ resources/js/components/ui/toaster.tsx | 41 + resources/js/hooks/use-toast.ts | 194 ++++ resources/js/pages/Pengumuman.tsx | 2 +- .../{peraturan/Index.tsx => Peraturan.tsx} | 0 .../pages/admin/kategori/index_kategori.tsx | 461 +++++++++ resources/js/pages/admin/post/add_post.tsx | 273 +++++ resources/js/pages/admin/post/edit_post.tsx | 5 + resources/js/pages/admin/post/index_post.tsx | 247 +++++ .../admin/subkategori/index_subkategori.tsx | 552 ++++++++++ resources/js/pages/auth/login.tsx | 12 +- resources/js/pages/menu/index_menu.tsx | 100 ++ routes/web.php | 36 + 56 files changed, 4744 insertions(+), 159 deletions(-) create mode 100644 app/Http/Controllers/Admin/RoleController.php create mode 100644 app/Http/Controllers/KategoriController.php create mode 100644 app/Http/Controllers/PostController.php create mode 100644 app/Http/Controllers/SubKategoriController.php create mode 100644 app/Http/Requests/KategoriRequest.php create mode 100644 app/Http/Requests/PostRequest.php create mode 100644 app/Http/Requests/RoleRequest.php create mode 100644 app/Http/Requests/SubKategoriRequest.php create mode 100644 app/Models/Kategori.php create mode 100644 app/Models/Post.php create mode 100644 app/Models/SubKategori.php create mode 100644 app/Providers/RepositoryServiceProvider.php create mode 100644 config/permission.php create mode 100644 database/migrations/2025_02_11_164124_create_kategori_table.php create mode 100644 database/migrations/2025_02_11_174528_create_permission_tables.php create mode 100644 database/migrations/2025_02_11_180031_add_username_to_users_table.php create mode 100644 database/migrations/2025_02_12_000414_create_subkategori_table.php create mode 100644 database/migrations/2025_02_12_003736_create_posts_table.php create mode 100644 database/migrations/2025_02_12_081712_add_is_publish_to_posts_table.php create mode 100644 database/seeders/PermissionsTableSeeder.php create mode 100644 database/seeders/PostSeeder.php create mode 100644 database/seeders/RolesTableSeeder.php create mode 100644 resources/js/components/Partials/SearchDialog.tsx create mode 100644 resources/js/components/ui/dialog.tsx create mode 100644 resources/js/components/ui/select.tsx create mode 100644 resources/js/components/ui/textarea.tsx create mode 100644 resources/js/components/ui/toast.tsx create mode 100644 resources/js/components/ui/toaster.tsx create mode 100644 resources/js/hooks/use-toast.ts rename resources/js/pages/{peraturan/Index.tsx => Peraturan.tsx} (100%) create mode 100644 resources/js/pages/admin/kategori/index_kategori.tsx create mode 100644 resources/js/pages/admin/post/add_post.tsx create mode 100644 resources/js/pages/admin/post/edit_post.tsx create mode 100644 resources/js/pages/admin/post/index_post.tsx create mode 100644 resources/js/pages/admin/subkategori/index_subkategori.tsx create mode 100644 resources/js/pages/menu/index_menu.tsx diff --git a/app/Http/Controllers/Admin/RoleController.php b/app/Http/Controllers/Admin/RoleController.php new file mode 100644 index 0000000..de3dd5b --- /dev/null +++ b/app/Http/Controllers/Admin/RoleController.php @@ -0,0 +1,21 @@ +latest() + ->paginate(10); + + // Kembalikan data ke komponen Inertia 'Admin/Roles/Index' + return inertia('Admin/Roles/Index', compact('roles')); + } +} diff --git a/app/Http/Controllers/Auth/AuthenticatedSessionController.php b/app/Http/Controllers/Auth/AuthenticatedSessionController.php index 480c7f4..71c7dbf 100644 --- a/app/Http/Controllers/Auth/AuthenticatedSessionController.php +++ b/app/Http/Controllers/Auth/AuthenticatedSessionController.php @@ -33,6 +33,9 @@ class AuthenticatedSessionController extends Controller $request->session()->regenerate(); + // Redirect berdasarkan role user + $user = Auth::user(); + return redirect()->intended(route('dashboard', absolute: false)); } diff --git a/app/Http/Controllers/KategoriController.php b/app/Http/Controllers/KategoriController.php new file mode 100644 index 0000000..e9e6daf --- /dev/null +++ b/app/Http/Controllers/KategoriController.php @@ -0,0 +1,65 @@ +get(); + return Inertia::render('admin/kategori/index_kategori', ['kategori' => $kategori]); + } catch (\Exception $e) { + Log::error('Error fetching Kategori: ' . $e->getMessage()); + return back()->with('error', 'Something went wrong.'); + } + } + + public function store(KategoriRequest $request) + { + try { + $kategori = Kategori::withTrashed() + ->where('NamaKategori', $request->NamaKategori) + ->first(); + + if ($kategori) { + $kategori->restore(); + return redirect()->route('admin.kategori.index')->with('success', 'Kategori berhasil dikembalikan.'); + } + + Kategori::create($request->validated()); + + return redirect()->route('admin.kategori.index')->with('success', 'Kategori berhasil dibuat.'); + } catch (\Exception $e) { + Log::error('Error creating Kategori: ' . $e->getMessage()); + return back()->with('error', 'Something went wrong.'); + } + } + public function update(KategoriRequest $request, Kategori $kategori) + { + try { + $kategori->update($request->validated()); + return redirect()->route('admin.kategori.index')->with('success', 'Kategori berhasil diperbarui.'); + } catch (\Exception $e) { + Log::error('Error updating Kategori: ' . $e->getMessage()); + return back()->with('error', 'Something went wrong.'); + } + } + + public function destroy(Kategori $kategori) + { + try { + $kategori->delete(); + return redirect()->route('admin.kategori.index')->with('success', 'Kategori berhasil dihapus.'); + } catch (\Exception $e) { + Log::error('Error deleting Kategori: ' . $e->getMessage()); + return back()->with('error', 'Something went wrong.'); + } + } +} diff --git a/app/Http/Controllers/PostController.php b/app/Http/Controllers/PostController.php new file mode 100644 index 0000000..ef511e9 --- /dev/null +++ b/app/Http/Controllers/PostController.php @@ -0,0 +1,103 @@ +latest()->get(); + $kategori = Kategori::all(); + $subkategori = SubKategori::all(); + + return Inertia::render('admin/post/index_post', [ + 'posts' => $posts, + 'kategori' => $kategori, + 'subkategori' => $subkategori + ]); + } + + public function create() + { + $kategori = Kategori::all(); + $subkategori = SubKategori::all(); + + return Inertia::render('admin/post/add_post', [ + 'kategori' => $kategori, + 'subkategori' => $subkategori + ]); + } + + public function store(PostRequest $request) + { + + try { + $data = $request->validated(); + $data['IsPublish'] = $request->has('IsPublish'); + + if ($request->hasFile('ImagePost')) { + $data['ImagePost'] = $request->file('ImagePost')->store('images/posts'); + } + + Post::create($data); + + return redirect()->route('admin.post.index')->with('success', 'Post berhasil ditambahkan'); + } catch (\Exception $e) { + return redirect()->route('admin.post.index')->with('error', 'Post gagal ditambahkan'); + } + + } + + public function edit(Post $post) + { + + return Inertia::render('admin/post/edit_post', [ + 'post' => $post, + 'kategori' => Kategori::all(), + 'subkategori' => SubKategori::where('KategoriId', $post->KategoriId)->get(), + ]); + } + + public function update(PostRequest $request, Post $post) + { + try { + $data = $request->validated(); + + if ($request->hasFile('ImagePost')) { + Storage::disk('public')->delete($post->ImagePost); + $data['ImagePost'] = $request->file('ImagePost')->store('images/posts', 'public'); + } + + $post->update($data); + + return redirect()->route('admin.post.index')->with('success', 'Post berhasil diperbarui.'); + } catch (\Exception $e) { + Log::error('Error updating Post: ' . $e->getMessage()); + return back()->with('error', 'Something went wrong.'); + } + } + + public function destroy(Post $post) + { + try { + Storage::disk('public')->delete($post->ImagePost); + $post->delete(); + + return redirect()->route('admin.post.index')->with('success', 'Post berhasil dihapus.'); + } catch (\Exception $e) { + Log::error('Error deleting Post: ' . $e->getMessage()); + return back()->with('error', 'Something went wrong.'); + } + } + + +} diff --git a/app/Http/Controllers/SubKategoriController.php b/app/Http/Controllers/SubKategoriController.php new file mode 100644 index 0000000..08e0fe1 --- /dev/null +++ b/app/Http/Controllers/SubKategoriController.php @@ -0,0 +1,60 @@ +latest()->get(); + $kategori = Kategori::all(); + + return Inertia::render('admin/subkategori/index_subkategori', [ + 'subkategori' => $subkategori, + 'kategori' => $kategori + ]); + } + + public function store(SubKategoriRequest $request) + { + try { + SubKategori::create($request->validated()); + return redirect()->route('admin.subkategori.index')->with('success', 'SubKategori berhasil dibuat.'); + } catch (\Exception $e) { + Log::error('Error creating SubKategori: ' . $e->getMessage()); + return back()->with('error', 'Something went wrong.'); + } + } + + public function update(SubKategoriRequest $request, SubKategori $subkategori) + { + try { + $subkategori->update($request->validated()); + return redirect()->route('admin.subkategori.index')->with('success', 'SubKategori berhasil diperbarui.'); + } catch (\Exception $e) { + Log::error('Error updating SubKategori: ' . $e->getMessage()); + return back()->with('error', 'Something went wrong.'); + } + } + + public function destroy(SubKategori $subkategori) + { + try { + $subkategori->delete(); + return redirect()->route('admin.subkategori.index')->with('success', 'SubKategori berhasil dihapus.'); + } catch (\Exception $e) { + Log::error('Error deleting SubKategori: ' . $e->getMessage()); + return back()->with('error', 'Something went wrong.'); + } + } +} diff --git a/app/Http/Requests/Auth/LoginRequest.php b/app/Http/Requests/Auth/LoginRequest.php index 2b92f65..e422499 100644 --- a/app/Http/Requests/Auth/LoginRequest.php +++ b/app/Http/Requests/Auth/LoginRequest.php @@ -27,7 +27,7 @@ class LoginRequest extends FormRequest public function rules(): array { return [ - 'email' => ['required', 'string', 'email'], + 'login' => ['required', 'string'], 'password' => ['required', 'string'], ]; } @@ -41,11 +41,13 @@ class LoginRequest extends FormRequest { $this->ensureIsNotRateLimited(); - if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) { + $loginField = filter_var($this->input('login'), FILTER_VALIDATE_EMAIL) ? 'email' : 'username'; + + if (! Auth::attempt([$loginField => $this->input('login'), 'password' => $this->input('password')], $this->boolean('remember'))) { RateLimiter::hit($this->throttleKey()); throw ValidationException::withMessages([ - 'email' => trans('auth.failed'), + 'login' => trans('auth.failed'), ]); } @@ -68,7 +70,7 @@ class LoginRequest extends FormRequest $seconds = RateLimiter::availableIn($this->throttleKey()); throw ValidationException::withMessages([ - 'email' => trans('auth.throttle', [ + 'login' => trans('auth.throttle', [ 'seconds' => $seconds, 'minutes' => ceil($seconds / 60), ]), @@ -80,6 +82,6 @@ class LoginRequest extends FormRequest */ public function throttleKey(): string { - return Str::transliterate(Str::lower($this->string('email')).'|'.$this->ip()); + return Str::transliterate(Str::lower($this->string('login')).'|'.$this->ip()); } } diff --git a/app/Http/Requests/KategoriRequest.php b/app/Http/Requests/KategoriRequest.php new file mode 100644 index 0000000..fb36690 --- /dev/null +++ b/app/Http/Requests/KategoriRequest.php @@ -0,0 +1,34 @@ +|string> + */ + public function rules(): array + { + return [ + 'NamaKategori' => [ + 'required', + 'string', + 'max:255', + Rule::unique('Kategori', 'NamaKategori')->whereNull('deleted_at') // Abaikan data yang dihapus (soft delete) + ], + ]; + } +} diff --git a/app/Http/Requests/PostRequest.php b/app/Http/Requests/PostRequest.php new file mode 100644 index 0000000..ef6a44e --- /dev/null +++ b/app/Http/Requests/PostRequest.php @@ -0,0 +1,36 @@ +|string> + */ + public function rules(): array + { + return [ + 'KategoriId' => 'required|exists:Kategori,KategoriId', + 'SubKategoriId' => [ + 'required', + Rule::exists('SubKategori', 'SubKategoriId')->where('KategoriId', $this->KategoriId), + ], + 'JudulPost' => ['required', 'string', 'max:255'], + 'DescPost' => ['required', 'string'], + 'ImagePost' => ['nullable', 'image', 'mimes:jpg,png,webp', 'max:2048'], + ]; + } +} diff --git a/app/Http/Requests/RoleRequest.php b/app/Http/Requests/RoleRequest.php new file mode 100644 index 0000000..5619589 --- /dev/null +++ b/app/Http/Requests/RoleRequest.php @@ -0,0 +1,30 @@ +|string> + */ + public function rules(): array + { + return [ + 'name' => 'required|string|max:255', + 'permissions' => 'required|array', + 'permissions.*' => 'exists:permissions,id' + ]; + } +} diff --git a/app/Http/Requests/SubKategoriRequest.php b/app/Http/Requests/SubKategoriRequest.php new file mode 100644 index 0000000..bdc3dcb --- /dev/null +++ b/app/Http/Requests/SubKategoriRequest.php @@ -0,0 +1,31 @@ +|string> + */ + public function rules(): array + { + return [ + 'KategoriId' => ['required', 'integer', 'exists:Kategori,KategoriId'], + 'NamaSubKategori' => ['required', 'string', 'max:255'], + Rule::unique('SubKategori', 'NamaSubKategori')->where('KategoriId', $this->KategoriId)->whereNull('deleted_at') + ]; + } +} diff --git a/app/Models/Kategori.php b/app/Models/Kategori.php new file mode 100644 index 0000000..1228289 --- /dev/null +++ b/app/Models/Kategori.php @@ -0,0 +1,20 @@ +belongsTo(Kategori::class, 'KategoriId', 'KategoriId'); + } + + public function subkategori() + { + return $this->belongsTo(SubKategori::class, 'SubKategoriId', 'SubKategoriId'); + } + + public function getFormattedPublishDateAttribute() + { + return \Carbon\Carbon::parse($this->created_at)->translatedFormat('d F Y'); + } + + public static function boot() + { + parent::boot(); + + static::creating(function ($post) { + $post->Slug = Str::slug($post->JudulPost); + }); + } +} diff --git a/app/Models/SubKategori.php b/app/Models/SubKategori.php new file mode 100644 index 0000000..fc953f6 --- /dev/null +++ b/app/Models/SubKategori.php @@ -0,0 +1,23 @@ +belongsTo(Kategori::class, 'KategoriId', 'KategoriId'); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 20dd00d..b99dbcf 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -6,10 +6,11 @@ namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; +use Spatie\Permission\Traits\HasRoles; class User extends Authenticatable /*implements MustVerifyEmail*/ { - use HasFactory, Notifiable; + use HasFactory, Notifiable, HasRoles; /** * The attributes that are mass assignable. @@ -18,6 +19,7 @@ class User extends Authenticatable /*implements MustVerifyEmail*/ */ protected $fillable = [ 'name', + 'username', 'email', 'password', ]; diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 452e6b6..2194dbd 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -4,6 +4,7 @@ namespace App\Providers; use Illuminate\Support\ServiceProvider; + class AppServiceProvider extends ServiceProvider { /** @@ -11,7 +12,7 @@ class AppServiceProvider extends ServiceProvider */ public function register(): void { - // +// } /** diff --git a/app/Providers/RepositoryServiceProvider.php b/app/Providers/RepositoryServiceProvider.php new file mode 100644 index 0000000..d0a4d2c --- /dev/null +++ b/app/Providers/RepositoryServiceProvider.php @@ -0,0 +1,25 @@ +web(append: [ \App\Http\Middleware\HandleInertiaRequests::class, \Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets::class, + HandleInertiaRequests::class, ]); - // + $middleware->alias([ + 'role' => \Spatie\Permission\Middleware\RoleMiddleware::class, + 'permission' => \Spatie\Permission\Middleware\PermissionMiddleware::class, + 'role_or_permission' => \Spatie\Permission\Middleware\RoleOrPermissionMiddleware::class, + ]); }) ->withExceptions(function (Exceptions $exceptions) { // diff --git a/bootstrap/providers.php b/bootstrap/providers.php index 38b258d..3734166 100644 --- a/bootstrap/providers.php +++ b/bootstrap/providers.php @@ -2,4 +2,5 @@ return [ App\Providers\AppServiceProvider::class, + App\Providers\RepositoryServiceProvider::class, ]; diff --git a/composer.json b/composer.json index e0bd5b1..23e9cb1 100644 --- a/composer.json +++ b/composer.json @@ -10,6 +10,7 @@ "laravel/framework": "^11.0", "laravel/sanctum": "^4.0", "laravel/tinker": "^2.9", + "spatie/laravel-permission": "6.4.0", "tightenco/ziggy": "^2.0" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 9f56ba8..2b584e4 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "86d4a008177912b9459f27068d5f4e1d", + "content-hash": "953cea45b540b8469800ed81d3f1fba6", "packages": [ { "name": "brick/math", @@ -3254,6 +3254,88 @@ ], "time": "2024-04-27T21:32:50+00:00" }, + { + "name": "spatie/laravel-permission", + "version": "6.4.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-permission.git", + "reference": "05cce017fe3ac78f60a3fce78c07fe6e8e6e6e52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-permission/zipball/05cce017fe3ac78f60a3fce78c07fe6e8e6e6e52", + "reference": "05cce017fe3ac78f60a3fce78c07fe6e8e6e6e52", + "shasum": "" + }, + "require": { + "illuminate/auth": "^8.12|^9.0|^10.0|^11.0", + "illuminate/container": "^8.12|^9.0|^10.0|^11.0", + "illuminate/contracts": "^8.12|^9.0|^10.0|^11.0", + "illuminate/database": "^8.12|^9.0|^10.0|^11.0", + "php": "^8.0" + }, + "require-dev": { + "laravel/passport": "^11.0|^12.0", + "orchestra/testbench": "^6.23|^7.0|^8.0|^9.0", + "phpunit/phpunit": "^9.4|^10.1" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\Permission\\PermissionServiceProvider" + ] + }, + "branch-alias": { + "dev-main": "6.x-dev", + "dev-master": "6.x-dev" + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Spatie\\Permission\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Permission handling for Laravel 8.0 and up", + "homepage": "https://github.com/spatie/laravel-permission", + "keywords": [ + "acl", + "laravel", + "permission", + "permissions", + "rbac", + "roles", + "security", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-permission/issues", + "source": "https://github.com/spatie/laravel-permission/tree/6.4.0" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2024-02-28T08:11:20+00:00" + }, { "name": "symfony/clock", "version": "v7.1.1", @@ -8410,5 +8492,5 @@ "php": "^8.2" }, "platform-dev": [], - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.3.0" } diff --git a/config/permission.php b/config/permission.php new file mode 100644 index 0000000..2a520f3 --- /dev/null +++ b/config/permission.php @@ -0,0 +1,186 @@ + [ + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * Eloquent model should be used to retrieve your permissions. Of course, it + * is often just the "Permission" model but you may use whatever you like. + * + * The model you want to use as a Permission model needs to implement the + * `Spatie\Permission\Contracts\Permission` contract. + */ + + 'permission' => Spatie\Permission\Models\Permission::class, + + /* + * When using the "HasRoles" trait from this package, we need to know which + * Eloquent model should be used to retrieve your roles. Of course, it + * is often just the "Role" model but you may use whatever you like. + * + * The model you want to use as a Role model needs to implement the + * `Spatie\Permission\Contracts\Role` contract. + */ + + 'role' => Spatie\Permission\Models\Role::class, + + ], + + 'table_names' => [ + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your roles. We have chosen a basic + * default value but you may easily change it to any table you like. + */ + + 'roles' => 'roles', + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * table should be used to retrieve your permissions. We have chosen a basic + * default value but you may easily change it to any table you like. + */ + + 'permissions' => 'permissions', + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * table should be used to retrieve your models permissions. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'model_has_permissions' => 'model_has_permissions', + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your models roles. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'model_has_roles' => 'model_has_roles', + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your roles permissions. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'role_has_permissions' => 'role_has_permissions', + ], + + 'column_names' => [ + /* + * Change this if you want to name the related pivots other than defaults + */ + 'role_pivot_key' => null, //default 'role_id', + 'permission_pivot_key' => null, //default 'permission_id', + + /* + * Change this if you want to name the related model primary key other than + * `model_id`. + * + * For example, this would be nice if your primary keys are all UUIDs. In + * that case, name this `model_uuid`. + */ + + 'model_morph_key' => 'model_id', + + /* + * Change this if you want to use the teams feature and your related model's + * foreign key is other than `team_id`. + */ + + 'team_foreign_key' => 'team_id', + ], + + /* + * When set to true, the method for checking permissions will be registered on the gate. + * Set this to false if you want to implement custom logic for checking permissions. + */ + + 'register_permission_check_method' => true, + + /* + * When set to true, Laravel\Octane\Events\OperationTerminated event listener will be registered + * this will refresh permissions on every TickTerminated, TaskTerminated and RequestTerminated + * NOTE: This should not be needed in most cases, but an Octane/Vapor combination benefited from it. + */ + 'register_octane_reset_listener' => false, + + /* + * Teams Feature. + * When set to true the package implements teams using the 'team_foreign_key'. + * If you want the migrations to register the 'team_foreign_key', you must + * set this to true before doing the migration. + * If you already did the migration then you must make a new migration to also + * add 'team_foreign_key' to 'roles', 'model_has_roles', and 'model_has_permissions' + * (view the latest version of this package's migration file) + */ + + 'teams' => false, + + /* + * Passport Client Credentials Grant + * When set to true the package will use Passports Client to check permissions + */ + + 'use_passport_client_credentials' => false, + + /* + * When set to true, the required permission names are added to exception messages. + * This could be considered an information leak in some contexts, so the default + * setting is false here for optimum safety. + */ + + 'display_permission_in_exception' => false, + + /* + * When set to true, the required role names are added to exception messages. + * This could be considered an information leak in some contexts, so the default + * setting is false here for optimum safety. + */ + + 'display_role_in_exception' => false, + + /* + * By default wildcard permission lookups are disabled. + * See documentation to understand supported syntax. + */ + + 'enable_wildcard_permission' => false, + + /* + * The class to use for interpreting wildcard permissions. + * If you need to modify delimiters, override the class and specify its name here. + */ + // 'permission.wildcard_permission' => Spatie\Permission\WildcardPermission::class, + + /* Cache-specific settings */ + + 'cache' => [ + + /* + * By default all permissions are cached for 24 hours to speed up performance. + * When permissions or roles are updated the cache is flushed automatically. + */ + + 'expiration_time' => \DateInterval::createFromDateString('24 hours'), + + /* + * The cache key used to store all permissions. + */ + + 'key' => 'spatie.permission.cache', + + /* + * You may optionally indicate a specific cache driver to use for permission and + * role caching using any of the `store` drivers listed in the cache.php config + * file. Using 'default' here means to use the `default` set in cache.php. + */ + + 'store' => 'default', + ], +]; diff --git a/database/migrations/2025_02_11_164124_create_kategori_table.php b/database/migrations/2025_02_11_164124_create_kategori_table.php new file mode 100644 index 0000000..b95f5a9 --- /dev/null +++ b/database/migrations/2025_02_11_164124_create_kategori_table.php @@ -0,0 +1,29 @@ +id('KategoriId'); + $table->string('NamaKategori')->unique(); + $table->softDeletes(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('Kategori'); + } +}; diff --git a/database/migrations/2025_02_11_174528_create_permission_tables.php b/database/migrations/2025_02_11_174528_create_permission_tables.php new file mode 100644 index 0000000..b865d48 --- /dev/null +++ b/database/migrations/2025_02_11_174528_create_permission_tables.php @@ -0,0 +1,138 @@ +bigIncrements('id'); // permission id + $table->string('name'); // For MySQL 8.0 use string('name', 125); + $table->string('guard_name'); // For MySQL 8.0 use string('guard_name', 125); + $table->timestamps(); + + $table->unique(['name', 'guard_name']); + }); + + Schema::create($tableNames['roles'], function (Blueprint $table) use ($teams, $columnNames) { + $table->bigIncrements('id'); // role id + if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing + $table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable(); + $table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index'); + } + $table->string('name'); // For MySQL 8.0 use string('name', 125); + $table->string('guard_name'); // For MySQL 8.0 use string('guard_name', 125); + $table->timestamps(); + if ($teams || config('permission.testing')) { + $table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']); + } else { + $table->unique(['name', 'guard_name']); + } + }); + + Schema::create($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) { + $table->unsignedBigInteger($pivotPermission); + + $table->string('model_type'); + $table->unsignedBigInteger($columnNames['model_morph_key']); + $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index'); + + $table->foreign($pivotPermission) + ->references('id') // permission id + ->on($tableNames['permissions']) + ->onDelete('cascade'); + if ($teams) { + $table->unsignedBigInteger($columnNames['team_foreign_key']); + $table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index'); + + $table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'], + 'model_has_permissions_permission_model_type_primary'); + } else { + $table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'], + 'model_has_permissions_permission_model_type_primary'); + } + + }); + + Schema::create($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) { + $table->unsignedBigInteger($pivotRole); + + $table->string('model_type'); + $table->unsignedBigInteger($columnNames['model_morph_key']); + $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index'); + + $table->foreign($pivotRole) + ->references('id') // role id + ->on($tableNames['roles']) + ->onDelete('cascade'); + if ($teams) { + $table->unsignedBigInteger($columnNames['team_foreign_key']); + $table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index'); + + $table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'], + 'model_has_roles_role_model_type_primary'); + } else { + $table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'], + 'model_has_roles_role_model_type_primary'); + } + }); + + Schema::create($tableNames['role_has_permissions'], function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) { + $table->unsignedBigInteger($pivotPermission); + $table->unsignedBigInteger($pivotRole); + + $table->foreign($pivotPermission) + ->references('id') // permission id + ->on($tableNames['permissions']) + ->onDelete('cascade'); + + $table->foreign($pivotRole) + ->references('id') // role id + ->on($tableNames['roles']) + ->onDelete('cascade'); + + $table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary'); + }); + + app('cache') + ->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null) + ->forget(config('permission.cache.key')); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + $tableNames = config('permission.table_names'); + + if (empty($tableNames)) { + throw new \Exception('Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.'); + } + + Schema::drop($tableNames['role_has_permissions']); + Schema::drop($tableNames['model_has_roles']); + Schema::drop($tableNames['model_has_permissions']); + Schema::drop($tableNames['roles']); + Schema::drop($tableNames['permissions']); + } +}; diff --git a/database/migrations/2025_02_11_180031_add_username_to_users_table.php b/database/migrations/2025_02_11_180031_add_username_to_users_table.php new file mode 100644 index 0000000..c17dac0 --- /dev/null +++ b/database/migrations/2025_02_11_180031_add_username_to_users_table.php @@ -0,0 +1,28 @@ +string('username')->unique()->after('name'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('username'); + }); + } +}; diff --git a/database/migrations/2025_02_12_000414_create_subkategori_table.php b/database/migrations/2025_02_12_000414_create_subkategori_table.php new file mode 100644 index 0000000..7b4f591 --- /dev/null +++ b/database/migrations/2025_02_12_000414_create_subkategori_table.php @@ -0,0 +1,36 @@ +id('SubKategoriId'); + $table->unsignedBigInteger('KategoriId'); + $table->string('NamaSubKategori'); + $table->softDeletes(); + $table->timestamps(); + + // Foreign key ke tabel Kategori + $table->foreign('KategoriId')->references('KategoriId')->on('Kategori')->onDelete('cascade'); + + // UNIQUE constraint untuk memastikan kategori & subkategori tidak duplikat + $table->unique(['KategoriId', 'NamaSubKategori']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('SubKategori'); + } +}; diff --git a/database/migrations/2025_02_12_003736_create_posts_table.php b/database/migrations/2025_02_12_003736_create_posts_table.php new file mode 100644 index 0000000..457a1ff --- /dev/null +++ b/database/migrations/2025_02_12_003736_create_posts_table.php @@ -0,0 +1,37 @@ +id('PostId'); + $table->unsignedBigInteger('KategoriId'); + $table->unsignedBigInteger('SubKategoriId'); + $table->string('JudulPost'); + $table->string('SlugPost'); + $table->text('DescPost'); + $table->string('ImagePost')->nullable(); + $table->timestamps(); + + + $table->foreign('KategoriId')->references('KategoriId')->on('Kategori')->onDelete('cascade'); + $table->foreign('SubKategoriId')->references('SubKategoriId')->on('SubKategori')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('Post'); + } +}; diff --git a/database/migrations/2025_02_12_081712_add_is_publish_to_posts_table.php b/database/migrations/2025_02_12_081712_add_is_publish_to_posts_table.php new file mode 100644 index 0000000..2928c49 --- /dev/null +++ b/database/migrations/2025_02_12_081712_add_is_publish_to_posts_table.php @@ -0,0 +1,29 @@ +boolean('IsPublish')->default(0); + + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('Post', function (Blueprint $table) { + $table->dropColumn('IsPublish'); + }); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index d01a0ef..7653ddb 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -13,11 +13,9 @@ class DatabaseSeeder extends Seeder */ public function run(): void { - // User::factory(10)->create(); - - User::factory()->create([ - 'name' => 'Test User', - 'email' => 'test@example.com', + $this->call([ + RolesTableSeeder::class, + UserSeeder::class, ]); } } diff --git a/database/seeders/PermissionsTableSeeder.php b/database/seeders/PermissionsTableSeeder.php new file mode 100644 index 0000000..440b008 --- /dev/null +++ b/database/seeders/PermissionsTableSeeder.php @@ -0,0 +1,31 @@ + ['index', 'create', 'edit', 'delete'], + 'roles' => ['index', 'create', 'edit', 'delete'], + 'kategori' => ['index', 'create', 'edit', 'delete'], + 'subkategori' => ['index', 'create', 'edit', 'delete'], + ]; + foreach ($resources as $resource => $actions) { + foreach ($actions as $action) { + $permissionName = "{$resource}.{$action}"; + + Permission::firstOrCreate(['name' => $permissionName, 'guard_name' => 'web']); + } + } + } +} diff --git a/database/seeders/PostSeeder.php b/database/seeders/PostSeeder.php new file mode 100644 index 0000000..c4d9033 --- /dev/null +++ b/database/seeders/PostSeeder.php @@ -0,0 +1,46 @@ +pluck('KategoriId')->toArray(); + $subkategori = DB::table('SubKategori')->pluck('SubKategoriId')->toArray(); + + if (empty($kategori) || empty($subkategori)) { + $this->command->info('Tidak ada data kategori atau subkategori. Harap jalankan seeder kategori dan subkategori terlebih dahulu.'); + return; + } + + // Buat 10 post dummy + for ($i = 1; $i <= 10; $i++) { + $judul = "Judul Post " . $i; + $slug = Str::slug($judul); + + DB::table('Post')->insert([ + 'KategoriId' => $kategori[array_rand($kategori)], + 'SubKategoriId' => $subkategori[array_rand($subkategori)], + 'JudulPost' => $judul, + 'SlugPost' => $slug, + 'DescPost' => "

Deskripsi konten untuk $judul

", + 'ImagePost' => 'images/posts/default.png', // Pastikan ada file default.png di folder public/images/posts/ + 'IsPublish' => rand(0, 1), + 'created_at' => Carbon::now()->subDays(rand(1, 30))->format('Y-m-d H:i:s'), + 'updated_at' => Carbon::now()->format('Y-m-d H:i:s'), + ]); + } + + $this->command->info('Seeder post berhasil dijalankan.'); + } +} diff --git a/database/seeders/RolesTableSeeder.php b/database/seeders/RolesTableSeeder.php new file mode 100644 index 0000000..1990be4 --- /dev/null +++ b/database/seeders/RolesTableSeeder.php @@ -0,0 +1,27 @@ + 'admin', 'guard_name' => 'web']); + $user = Role::firstOrCreate(['name' => 'user', 'guard_name' => 'web']); + + // Assign permissions ke role + $admin->syncPermissions(Permission::all()); // Admin mendapatkan semua akses + + // Assign role `admin` ke user + $user->assignRole($admin); + + } +} diff --git a/database/seeders/UserSeeder.php b/database/seeders/UserSeeder.php index b35225f..4a99117 100644 --- a/database/seeders/UserSeeder.php +++ b/database/seeders/UserSeeder.php @@ -14,10 +14,22 @@ class UserSeeder extends Seeder */ public function run(): void { - User::create([ - 'name' => 'Admin User', - 'email' => 'admin@gmail.com', - 'password' => Hash::make('password') + $admin = User::create([ + 'name' => 'Admin', + 'username' => 'admindlh25', + 'email' => 'dlh@gmail.com', + 'password' => Hash::make('admindlh25'), ]); + + $admin->assignRole('admin'); + + $user = User::create([ + 'name' => 'Uji Coba', + 'username' => 'ujicoba25', + 'email' => 'ujicoba@gmail.com', + 'password' => Hash::make('ujicoba25'), + ]); + + $user->assignRole('user'); } } diff --git a/package-lock.json b/package-lock.json index 1961a5b..da35c5a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,19 +9,22 @@ "@radix-ui/react-alert-dialog": "^1.0.5", "@radix-ui/react-avatar": "^1.1.1", "@radix-ui/react-collapsible": "^1.1.1", - "@radix-ui/react-dialog": "^1.1.2", + "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-navigation-menu": "^1.2.4", "@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-scroll-area": "^1.0.5", + "@radix-ui/react-select": "^2.1.6", "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slot": "^1.1.1", "@radix-ui/react-switch": "^1.1.2", + "@radix-ui/react-toast": "^1.2.6", "@radix-ui/react-tooltip": "^1.1.3", "@reduxjs/toolkit": "^2.5.1", "@types/react-redux": "^7.1.34", "apexcharts": "^4.4.0", + "ckeditor4-react": "^4.1.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "date-fns": "^3.6.0", @@ -36,6 +39,7 @@ "react-i18next": "^15.4.0", "react-perfect-scrollbar": "^1.5.8", "react-popper": "^2.3.0", + "react-quill": "^2.0.0", "react-redux": "^9.2.0", "react-resizable-panels": "^2.0.19", "react-router-dom": "^7.1.4", @@ -1033,6 +1037,42 @@ } } }, + "node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-dialog": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.5.tgz", + "integrity": "sha512-LaO3e5h/NOEL4OfXjxD43k9Dx+vn+8n+PCFt6uhX/BADFflllyv3WJG6rgvvSVBxpTch938Qq/LGc2MMxipXPw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.4", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.1", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-portal": "1.1.3", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-slot": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.1.0", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.2" + }, + "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-arrow": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.1.tgz", @@ -1169,25 +1209,25 @@ } }, "node_modules/@radix-ui/react-dialog": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.5.tgz", - "integrity": "sha512-LaO3e5h/NOEL4OfXjxD43k9Dx+vn+8n+PCFt6uhX/BADFflllyv3WJG6rgvvSVBxpTch938Qq/LGc2MMxipXPw==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.6.tgz", + "integrity": "sha512-/IVhJV5AceX620DUJ4uYVMymzsipdKBzo3edo+omeskCKGm9FRHM0ebIdbPnlQVJqyuHbuBltQUOG2mOTq2IYw==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.5", "@radix-ui/react-focus-guards": "1.1.1", - "@radix-ui/react-focus-scope": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.2", "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-portal": "1.1.3", + "@radix-ui/react-portal": "1.1.4", "@radix-ui/react-presence": "1.1.2", - "@radix-ui/react-primitive": "2.0.1", - "@radix-ui/react-slot": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-slot": "1.1.2", "@radix-ui/react-use-controllable-state": "1.1.0", "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.2" + "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", @@ -1204,6 +1244,123 @@ } } }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.5.tgz", + "integrity": "sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" + }, + "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-dialog/node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.2.tgz", + "integrity": "sha512-zxwE80FCU7lcXUGWkdt6XpTTCKPitG1XKOwViTxHVKIJhZl9MvIl2dVHeZENCWD9+EdWv05wlaEkRXUykU27RA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "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-dialog/node_modules/@radix-ui/react-portal": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.4.tgz", + "integrity": "sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "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-dialog/node_modules/@radix-ui/react-primitive": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz", + "integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.1.2" + }, + "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-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", + "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-direction": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", @@ -1634,6 +1791,270 @@ } } }, + "node_modules/@radix-ui/react-select": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.6.tgz", + "integrity": "sha512-T6ajELxRvTuAMWH0YmRJ1qez+x4/7Nq7QIx7zJ0VK3qaEWdnWpNbEDnmWldG1zBDwqrLy5aLMUWcoGirVj5kMg==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.0", + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-collection": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.5", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.2", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.2", + "@radix-ui/react-portal": "1.1.4", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-slot": "1.1.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.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-select/node_modules/@radix-ui/react-arrow": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.2.tgz", + "integrity": "sha512-G+KcpzXHq24iH0uGG/pF8LyzpFJYGD4RfLjCIBfGdSLXvjLHST31RUiRVrupIBMvIppMgSzQ6l66iAxl03tdlg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.2" + }, + "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-select/node_modules/@radix-ui/react-collection": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.2.tgz", + "integrity": "sha512-9z54IEKRxIa9VityapoEYMuByaG42iSy1ZXlY2KcuLSEtq8x4987/N6m15ppoMffgZX72gER2uHe1D9Y6Unlcw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-slot": "1.1.2" + }, + "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-select/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.5.tgz", + "integrity": "sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" + }, + "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-select/node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.2.tgz", + "integrity": "sha512-zxwE80FCU7lcXUGWkdt6XpTTCKPitG1XKOwViTxHVKIJhZl9MvIl2dVHeZENCWD9+EdWv05wlaEkRXUykU27RA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "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-select/node_modules/@radix-ui/react-popper": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.2.tgz", + "integrity": "sha512-Rvqc3nOpwseCyj/rgjlJDYAgyfw7OC1tTkKn2ivhaMGcYt8FSBlahHOZak2i3QwkRXUXgGgzeEe2RuqeEHuHgA==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-rect": "1.1.0", + "@radix-ui/react-use-size": "1.1.0", + "@radix-ui/rect": "1.1.0" + }, + "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-select/node_modules/@radix-ui/react-portal": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.4.tgz", + "integrity": "sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "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-select/node_modules/@radix-ui/react-primitive": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz", + "integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.1.2" + }, + "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-select/node_modules/@radix-ui/react-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", + "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-visually-hidden": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.2.tgz", + "integrity": "sha512-1SzA4ns2M1aRlvxErqhLHsBHoS5eI5UUcI2awAMgGUp4LoaoWOKYmvqDY2s/tltuPkh3Yk77YF/r3IRj+Amx4Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.2" + }, + "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-separator": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.1.tgz", @@ -1704,6 +2125,181 @@ } } }, + "node_modules/@radix-ui/react-toast": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.6.tgz", + "integrity": "sha512-gN4dpuIVKEgpLn1z5FhzT9mYRUitbfZq9XqN/7kkBMUgFTzTG8x/KszWJugJXHcwxckY8xcKDZPz7kG3o6DsUA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-collection": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.5", + "@radix-ui/react-portal": "1.1.4", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.2" + }, + "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-toast/node_modules/@radix-ui/react-collection": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.2.tgz", + "integrity": "sha512-9z54IEKRxIa9VityapoEYMuByaG42iSy1ZXlY2KcuLSEtq8x4987/N6m15ppoMffgZX72gER2uHe1D9Y6Unlcw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-slot": "1.1.2" + }, + "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-toast/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.5.tgz", + "integrity": "sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" + }, + "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-toast/node_modules/@radix-ui/react-portal": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.4.tgz", + "integrity": "sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "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-toast/node_modules/@radix-ui/react-primitive": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz", + "integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.1.2" + }, + "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-toast/node_modules/@radix-ui/react-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", + "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-visually-hidden": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.2.tgz", + "integrity": "sha512-1SzA4ns2M1aRlvxErqhLHsBHoS5eI5UUcI2awAMgGUp4LoaoWOKYmvqDY2s/tltuPkh3Yk77YF/r3IRj+Amx4Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.2" + }, + "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-tooltip": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.7.tgz", @@ -2432,6 +3028,15 @@ "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==", "license": "MIT" }, + "node_modules/@types/quill": { + "version": "1.3.10", + "resolved": "https://registry.npmjs.org/@types/quill/-/quill-1.3.10.tgz", + "integrity": "sha512-IhW3fPW+bkt9MLNlycw8u8fWb7oO7W5URC9MfZYHBlA24rex9rs23D5DETChu1zvgVdc5ka64ICjJOgQMr6Shw==", + "license": "MIT", + "dependencies": { + "parchment": "^1.1.2" + } + }, "node_modules/@types/react": { "version": "18.3.18", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", @@ -2709,11 +3314,28 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -2727,7 +3349,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -2806,6 +3427,33 @@ "node": ">= 6" } }, + "node_modules/ckeditor4": { + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/ckeditor4/-/ckeditor4-4.25.1.tgz", + "integrity": "sha512-azUTe02NzSDfMX1UGWDdkhEdTdB/Ivn9uPyIRpygkCc4jmP/l8KOiK1nCfhSAb3KofD7gl+K+oee1WG0MZnwuQ==", + "license": "SEE LICENSE IN LICENSE.md", + "peer": true + }, + "node_modules/ckeditor4-integrations-common": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ckeditor4-integrations-common/-/ckeditor4-integrations-common-1.0.0.tgz", + "integrity": "sha512-OAoQT/gYrHkg0qgzf6MS/rndYhq3SScLVQ3rtXQeuCE8ju7nFHg3qZ7WGA2XpFxcZzsMP6hhugXqdel5vbcC3g==", + "license": "(GPL-2.0-or-later OR LGPL-2.1-or-later OR MPL-1.1-or-later)" + }, + "node_modules/ckeditor4-react": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/ckeditor4-react/-/ckeditor4-react-4.1.2.tgz", + "integrity": "sha512-kaKXiJ2KIR8abAWYNy9MXo7iqo2dvMNKGM3wBbqJW69qi2f9EvhpOJSQselvaVmoiXXzJhwrlA8j+2yh9qbNQw==", + "license": "(GPL-2.0-or-later OR LGPL-2.1-or-later OR MPL-1.1-or-later)", + "dependencies": { + "ckeditor4-integrations-common": "^1.0.0", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "ckeditor4": "^4.20.2", + "react": "^18" + } + }, "node_modules/class-variance-authority": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", @@ -2825,6 +3473,15 @@ "dev": true, "license": "MIT" }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -3086,6 +3743,26 @@ "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", "license": "MIT" }, + "node_modules/deep-equal": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.2.tgz", + "integrity": "sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==", + "license": "MIT", + "dependencies": { + "is-arguments": "^1.1.1", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.5.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -3096,6 +3773,40 @@ "node": ">=0.10.0" } }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -3138,7 +3849,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -3172,7 +3882,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3182,7 +3891,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3192,7 +3900,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -3256,6 +3963,18 @@ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "license": "MIT" }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.1.2.tgz", + "integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==", + "license": "Apache-2.0" + }, "node_modules/fast-equals": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz", @@ -3403,6 +4122,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -3417,7 +4145,6 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -3451,7 +4178,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -3507,7 +4233,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3516,11 +4241,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3529,6 +4265,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -3637,6 +4388,22 @@ "node": ">=12" } }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -3664,6 +4431,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3703,6 +4486,24 @@ "node": ">=0.12.0" } }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -3852,7 +4653,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4067,12 +4867,43 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "license": "BlueOak-1.0.0" }, + "node_modules/parchment": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/parchment/-/parchment-1.1.4.tgz", + "integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==", + "license": "BSD-3-Clause" + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -4349,6 +5180,40 @@ ], "license": "MIT" }, + "node_modules/quill": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/quill/-/quill-1.3.7.tgz", + "integrity": "sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==", + "license": "BSD-3-Clause", + "dependencies": { + "clone": "^2.1.1", + "deep-equal": "^1.0.1", + "eventemitter3": "^2.0.3", + "extend": "^3.0.2", + "parchment": "^1.1.4", + "quill-delta": "^3.6.2" + } + }, + "node_modules/quill-delta": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-3.6.3.tgz", + "integrity": "sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==", + "license": "MIT", + "dependencies": { + "deep-equal": "^1.0.1", + "extend": "^3.0.2", + "fast-diff": "1.1.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/quill/node_modules/eventemitter3": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz", + "integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==", + "license": "MIT" + }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -4477,6 +5342,21 @@ "react-dom": "^16.8.0 || ^17 || ^18" } }, + "node_modules/react-quill": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/react-quill/-/react-quill-2.0.0.tgz", + "integrity": "sha512-4qQtv1FtCfLgoD3PXAur5RyxuUbPXQGOHgTlFie3jtxp43mXDtzCKaOgQ3mLyZfi1PUlyjycfivKelFhy13QUg==", + "license": "MIT", + "dependencies": { + "@types/quill": "^1.3.10", + "lodash": "^4.17.4", + "quill": "^1.3.7" + }, + "peerDependencies": { + "react": "^16 || ^17 || ^18", + "react-dom": "^16 || ^17 || ^18" + } + }, "node_modules/react-redux": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", @@ -4751,6 +5631,26 @@ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", "license": "MIT" }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/reselect": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", @@ -4874,6 +5774,38 @@ "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", "license": "MIT" }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", diff --git a/package.json b/package.json index ae34b8b..f36ec22 100644 --- a/package.json +++ b/package.json @@ -29,19 +29,22 @@ "@radix-ui/react-alert-dialog": "^1.0.5", "@radix-ui/react-avatar": "^1.1.1", "@radix-ui/react-collapsible": "^1.1.1", - "@radix-ui/react-dialog": "^1.1.2", + "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-navigation-menu": "^1.2.4", "@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-scroll-area": "^1.0.5", + "@radix-ui/react-select": "^2.1.6", "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slot": "^1.1.1", "@radix-ui/react-switch": "^1.1.2", + "@radix-ui/react-toast": "^1.2.6", "@radix-ui/react-tooltip": "^1.1.3", "@reduxjs/toolkit": "^2.5.1", "@types/react-redux": "^7.1.34", "apexcharts": "^4.4.0", + "ckeditor4-react": "^4.1.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "date-fns": "^3.6.0", @@ -56,6 +59,7 @@ "react-i18next": "^15.4.0", "react-perfect-scrollbar": "^1.5.8", "react-popper": "^2.3.0", + "react-quill": "^2.0.0", "react-redux": "^9.2.0", "react-resizable-panels": "^2.0.19", "react-router-dom": "^7.1.4", diff --git a/resources/js/app.tsx b/resources/js/app.tsx index 2c25c85..ee4574f 100644 --- a/resources/js/app.tsx +++ b/resources/js/app.tsx @@ -1,4 +1,5 @@ import "./bootstrap"; +import "../css/app.css"; import { createRoot } from "react-dom/client"; import { createInertiaApp } from "@inertiajs/react"; @@ -6,7 +7,6 @@ import { resolvePageComponent } from "laravel-vite-plugin/inertia-helpers"; import { ThemeProvider } from "@/components/theme-provider"; const appName = import.meta.env.VITE_APP_NAME || "Laravel"; - createInertiaApp({ title: (title) => `${title} - ${appName}`, resolve: (name) => @@ -14,6 +14,7 @@ createInertiaApp({ `./pages/${name}.tsx`, import.meta.glob("./pages/**/*.tsx") ), + setup({ el, App, props }) { const root = createRoot(el); diff --git a/resources/js/components/Card/CardPengumuman.tsx b/resources/js/components/Card/CardPengumuman.tsx index da3dd52..439a95d 100644 --- a/resources/js/components/Card/CardPengumuman.tsx +++ b/resources/js/components/Card/CardPengumuman.tsx @@ -3,102 +3,139 @@ import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent } from "@/components/ui/card"; import { ArrowRight } from "lucide-react"; +import { Link } from "@inertiajs/react"; -const pengumumans = [ - { - id: 1, - title_page: "Pengumuman", - alt_image: "Pengumuman", - date: "16 Januari 2025", - title: "Pelatihan & Sertifikasi Online Bidang Pengendalian Pencemaran Air Dan Udara", - description: - "Kegiatan Pelatihan Dan Uji Sertifikasi Tersebut Akan Diselenggarakan Sebagaimana Jadwal Terlampir, Kegiatan Tersebut Bekerjasama Dengan Lembaga P...", - image: "/assets/img1.jpg", - }, - { - id: 2, - title_page: "Pengumuman", - alt_image: "Pengumuman", - date: "12 Desember 2024", - title: "Pembinaan Pelaporan secara Online Pengelolaan Lingkungan melalui situs Status Ketaatan Lingkungan (SKL)", - description: - "Sukolompok Pengawasan Lingkungan Bidang Pengawasan dan Penataan Hukum Dinas Lingkungan Hidup Prov. DKI Jakarta...", - image: "/assets/img1.jpg", - }, - { - id: 3, - title_page: "Pengumuman", - alt_image: "Pengumuman", - date: "12 Desember 2024", - title: "Pembinaan Pelaporan secara Online Pengelolaan Lingkungan melalui situs Status Ketaatan Lingkungan (SKL)", - description: - "Sukolompok Pengawasan Lingkungan Bidang Pengawasan dan Penataan Hukum Dinas Lingkungan Hidup Prov. DKI Jakarta...", - image: "/assets/img1.jpg", - }, - { - id: 4, - title_page: "Pengumuman", - alt_image: "Pengumuman", - date: "12 Desember 2024", - title: "Pembinaan Pelaporan secara Online Pengelolaan Lingkungan melalui situs Status Ketaatan Lingkungan (SKL)", - description: - "Sukolompok Pengawasan Lingkungan Bidang Pengawasan dan Penataan Hukum Dinas Lingkungan Hidup Prov. DKI Jakarta...", - image: "/assets/img1.jpg", - }, - { - id: 5, - title_page: "Pengumuman", - alt_image: "Pengumuman", - date: "12 Desember 2024", - title: "Pembinaan Pelaporan secara Online Pengelolaan Lingkungan melalui situs Status Ketaatan Lingkungan (SKL)", - description: - "Sukolompok Pengawasan Lingkungan Bidang Pengawasan dan Penataan Hukum Dinas Lingkungan Hidup Prov. DKI Jakarta...", - image: "/assets/img1.jpg", - }, - { - id: 6, - title_page: "Pengumuman", - alt_image: "Pengumuman", - date: "12 Desember 2024", - title: "Pembinaan Pelaporan secara Online Pengelolaan Lingkungan melalui situs Status Ketaatan Lingkungan (SKL)", - description: - "Sukolompok Pengawasan Lingkungan Bidang Pengawasan dan Penataan Hukum Dinas Lingkungan Hidup Prov. DKI Jakarta...", - image: "/assets/img1.jpg", - }, -]; +// const pengumumans = [ +// { +// id: 1, +// title_page: "Pengumuman", +// alt_image: "Pengumuman", +// date: "16 Januari 2025", +// title: "Pelatihan & Sertifikasi Online Bidang Pengendalian Pencemaran Air Dan Udara", +// description: +// "Kegiatan Pelatihan Dan Uji Sertifikasi Tersebut Akan Diselenggarakan Sebagaimana Jadwal Terlampir, Kegiatan Tersebut Bekerjasama Dengan Lembaga P...", +// image: "/assets/img1.jpg", +// }, +// { +// id: 2, +// title_page: "Pengumuman", +// alt_image: "Pengumuman", +// date: "12 Desember 2024", +// title: "Pembinaan Pelaporan secara Online Pengelolaan Lingkungan melalui situs Status Ketaatan Lingkungan (SKL)", +// description: +// "Sukolompok Pengawasan Lingkungan Bidang Pengawasan dan Penataan Hukum Dinas Lingkungan Hidup Prov. DKI Jakarta...", +// image: "/assets/img1.jpg", +// }, +// { +// id: 3, +// title_page: "Pengumuman", +// alt_image: "Pengumuman", +// date: "12 Desember 2024", +// title: "Pembinaan Pelaporan secara Online Pengelolaan Lingkungan melalui situs Status Ketaatan Lingkungan (SKL)", +// description: +// "Sukolompok Pengawasan Lingkungan Bidang Pengawasan dan Penataan Hukum Dinas Lingkungan Hidup Prov. DKI Jakarta...", +// image: "/assets/img1.jpg", +// }, +// { +// id: 4, +// title_page: "Pengumuman", +// alt_image: "Pengumuman", +// date: "12 Desember 2024", +// title: "Pembinaan Pelaporan secara Online Pengelolaan Lingkungan melalui situs Status Ketaatan Lingkungan (SKL)", +// description: +// "Sukolompok Pengawasan Lingkungan Bidang Pengawasan dan Penataan Hukum Dinas Lingkungan Hidup Prov. DKI Jakarta...", +// image: "/assets/img1.jpg", +// }, +// { +// id: 5, +// title_page: "Pengumuman", +// alt_image: "Pengumuman", +// date: "12 Desember 2024", +// title: "Pembinaan Pelaporan secara Online Pengelolaan Lingkungan melalui situs Status Ketaatan Lingkungan (SKL)", +// description: +// "Sukolompok Pengawasan Lingkungan Bidang Pengawasan dan Penataan Hukum Dinas Lingkungan Hidup Prov. DKI Jakarta...", +// image: "/assets/img1.jpg", +// }, +// { +// id: 6, +// title_page: "Pengumuman", +// alt_image: "Pengumuman", +// date: "12 Desember 2024", +// title: "Pembinaan Pelaporan secara Online Pengelolaan Lingkungan melalui situs Status Ketaatan Lingkungan (SKL)", +// description: +// "Sukolompok Pengawasan Lingkungan Bidang Pengawasan dan Penataan Hukum Dinas Lingkungan Hidup Prov. DKI Jakarta...", +// image: "/assets/img1.jpg", +// }, +// ]; -const CardPengumuman = () => { +interface SubKategori { + SubKategoriId: number; + NamaSubKategori: string; +} + +interface Kategori { + KategoriId: number; + NamaKategori: string; +} + +interface Post { + PostId: number; + JudulPost: string; + DescPost: string; + SlugPost: string; + ImagePost: string; + IsPublish: boolean; + created_at: string; + kategori?: Kategori; + subkategori?: SubKategori; +} + +interface CardPengumumanProps { + posts: Post[]; +} + +const CardPengumuman = ({ posts }: CardPengumumanProps) => { return (
{/* List of Announcements */}
- {pengumumans.map((item) => ( - + {posts.map((post) => ( + {item.alt_image} - {item.title_page} + {post.kategori?.NamaKategori} |{" "} + {post.subkategori?.NamaSubKategori}

- {item.date} + {new Date(post.created_at).toLocaleDateString( + "id-ID", + { + day: "numeric", + month: "long", + year: "numeric", + } + )}

- {item.title} + {post.JudulPost}

- {item.description} + {post.DescPost.replace(/<[^>]*>/g, "")}

- + + +
))} diff --git a/resources/js/components/Partials/Navbar.tsx b/resources/js/components/Partials/Navbar.tsx index f5bff82..50f1d02 100644 --- a/resources/js/components/Partials/Navbar.tsx +++ b/resources/js/components/Partials/Navbar.tsx @@ -17,6 +17,7 @@ import { } from "@/components/ui/drawer"; import RunningText from "./RunningText"; import { useTheme } from "next-themes"; +import SearchDialog from "./SearchDialog"; interface NavItemsProps { mobile?: boolean; @@ -26,7 +27,7 @@ interface NavItemsProps { const Navbar = () => { const [isScrolled, setIsScrolled] = useState(false); const [isDrawerOpen, setIsDrawerOpen] = useState(false); - const [searchQuery, setSearchQuery] = useState(""); + // const [searchQuery, setSearchQuery] = useState(""); const { theme, setTheme } = useTheme(); // Handle scroll effect @@ -44,14 +45,6 @@ const Navbar = () => { setTheme(theme === "light" ? "dark" : "light"); }; - const handleSearch = (e: React.FormEvent) => { - e.preventDefault(); - if (searchQuery) { - router.push(`/search/${searchQuery}`); - setSearchQuery(""); // Clear input setelah search - } - }; - const NavItems: React.FC = ({ mobile = false, onClose }) => ( <> @@ -82,7 +75,7 @@ const Navbar = () => { ))} - + {/*
{ +
*/} + + ); diff --git a/resources/js/components/Partials/PopupModal.tsx b/resources/js/components/Partials/PopupModal.tsx index 2a7c43d..e7f6ef0 100644 --- a/resources/js/components/Partials/PopupModal.tsx +++ b/resources/js/components/Partials/PopupModal.tsx @@ -1,4 +1,3 @@ -// PopupModal.js import React, { useState } from "react"; import { Button } from "@/components/ui/button"; import { X, ArrowLeft, ArrowRight } from "lucide-react"; @@ -15,7 +14,7 @@ const PopupModal = ({ onClose }: { onClose: () => void }) => { title: "Workshop Peningkatan Kapasitas Pengendalian Pencemaran Udara", image: "/assets/popup-2.jpeg", description: - "Workshop ini bertujuan untuk meningkatkan pengetahuan dan keterampilan di bidang lingkungan hidup.", + "Workshop ini bertujuan untuk meningkatkan pengetahuan dan keterampilan di bidang lingkungan hidup. Workshop ini bertujuan untuk meningkatkan pengetahuan dan keterampilan di bidang lingkungan hidup.", }, { title: "Sosialisasi Program Ramah Lingkungan", @@ -36,30 +35,39 @@ const PopupModal = ({ onClose }: { onClose: () => void }) => { }; return ( -
-
+
+
-

+

{slides[currentSlide].title}

- Popup Slide Image -

- {slides[currentSlide].description} -

-
- -
+ {/*

+ {slides[currentSlide].title} +

*/} +

+ {slides[currentSlide].description} +

+ + + + + Search + + +
+ setSearchQuery(e.target.value)} + className="bg-gray-100 px-3 py-2 rounded-lg focus:outline-none w-full" + /> + +
+ + + +
+ + ); +}; + +export default SearchDialog; diff --git a/resources/js/components/app-sidebar.tsx b/resources/js/components/app-sidebar.tsx index 4c81388..054962e 100644 --- a/resources/js/components/app-sidebar.tsx +++ b/resources/js/components/app-sidebar.tsx @@ -3,6 +3,7 @@ import * as React from "react"; import { Command, + FolderArchive, Frame, Home, LifeBuoy, @@ -38,20 +39,28 @@ const data = { icon: Home, }, { - title: "Projects", + title: "Data Master", url: "#", - icon: SquareTerminal, + icon: FolderArchive, isActive: true, items: [ { - title: "History", - url: "#", + title: "Kategori Post", + url: "/admin/kategori", }, + { + title: "Sub Kategori Post", + url: "/admin/subkategori", + }, + // { + // title: "Menu", + // url: "/menus", + // }, ], }, { - title: "Design Engineering", - url: "#", + title: "Post", + url: "/admin/post", icon: Frame, }, ], diff --git a/resources/js/components/ui/dialog.tsx b/resources/js/components/ui/dialog.tsx new file mode 100644 index 0000000..9dbeaa0 --- /dev/null +++ b/resources/js/components/ui/dialog.tsx @@ -0,0 +1,120 @@ +import * as React from "react" +import * as DialogPrimitive from "@radix-ui/react-dialog" +import { X } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Dialog = DialogPrimitive.Root + +const DialogTrigger = DialogPrimitive.Trigger + +const DialogPortal = DialogPrimitive.Portal + +const DialogClose = DialogPrimitive.Close + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)) +DialogContent.displayName = DialogPrimitive.Content.displayName + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogHeader.displayName = "DialogHeader" + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogFooter.displayName = "DialogFooter" + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogTitle.displayName = DialogPrimitive.Title.displayName + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogDescription.displayName = DialogPrimitive.Description.displayName + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogTrigger, + DialogClose, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +} diff --git a/resources/js/components/ui/select.tsx b/resources/js/components/ui/select.tsx new file mode 100644 index 0000000..a84242c --- /dev/null +++ b/resources/js/components/ui/select.tsx @@ -0,0 +1,157 @@ +import * as React from "react" +import * as SelectPrimitive from "@radix-ui/react-select" +import { Check, ChevronDown, ChevronUp } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Select = SelectPrimitive.Root + +const SelectGroup = SelectPrimitive.Group + +const SelectValue = SelectPrimitive.Value + +const SelectTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + span]:line-clamp-1", + className + )} + {...props} + > + {children} + + + + +)) +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName + +const SelectScrollUpButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName + +const SelectScrollDownButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +SelectScrollDownButton.displayName = + SelectPrimitive.ScrollDownButton.displayName + +const SelectContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, position = "popper", ...props }, ref) => ( + + + + + {children} + + + + +)) +SelectContent.displayName = SelectPrimitive.Content.displayName + +const SelectLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SelectLabel.displayName = SelectPrimitive.Label.displayName + +const SelectItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)) +SelectItem.displayName = SelectPrimitive.Item.displayName + +const SelectSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SelectSeparator.displayName = SelectPrimitive.Separator.displayName + +export { + Select, + SelectGroup, + SelectValue, + SelectTrigger, + SelectContent, + SelectLabel, + SelectItem, + SelectSeparator, + SelectScrollUpButton, + SelectScrollDownButton, +} diff --git a/resources/js/components/ui/textarea.tsx b/resources/js/components/ui/textarea.tsx new file mode 100644 index 0000000..e56b0af --- /dev/null +++ b/resources/js/components/ui/textarea.tsx @@ -0,0 +1,22 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Textarea = React.forwardRef< + HTMLTextAreaElement, + React.ComponentProps<"textarea"> +>(({ className, ...props }, ref) => { + return ( +