From 8fb5e4426dd99672db4ba372dc13ce929c2dfb4f Mon Sep 17 00:00:00 2001 From: Faradina <148948429+faradiiina@users.noreply.github.com> Date: Mon, 14 Jul 2025 10:24:47 +0700 Subject: [PATCH] Update --- .gitattributes | 13 +- README.md | 61 +- .../Controllers/Admin/CategoryController.php | 176 +- .../Admin/SubcategoryController.php | 244 +- app/Http/Controllers/Admin/UserController.php | 362 +- .../Auth/ForgotPasswordController.php | 80 +- app/Http/Controllers/Auth/LoginController.php | 300 +- .../Controllers/Auth/RegisterController.php | 100 +- .../Auth/ResetPasswordController.php | 114 +- app/Http/Controllers/CafeController.php | 180 +- app/Http/Controllers/ContactController.php | 140 +- app/Http/Controllers/FavoriteController.php | 220 +- app/Http/Controllers/ProfileController.php | 106 +- .../Controllers/SmartSearchController.php | 396 +- app/Http/Kernel.php | 134 +- app/Http/Middleware/AdminMiddleware.php | 50 +- .../Middleware/RedirectIfAuthenticated.php | 74 +- app/Models/CafeImage.php | 40 +- app/Models/CafeRating.php | 62 +- app/Models/Category.php | 98 +- app/Models/ContactMessage.php | 44 +- app/Models/Favorite.php | 44 +- app/Models/SearchHistory.php | 88 +- app/Models/Subcategory.php | 84 +- app/Notifications/CustomVerifyEmail.php | 230 +- app/Providers/RouteServiceProvider.php | 96 +- config/database.php | 6 +- ...1_000000_create_contact_messages_table.php | 68 +- .../2023_05_15_add_area_to_cafes_table.php | 58 +- ...0_000000_create_search_histories_table.php | 70 +- .../2024_03_21_create_categories_table.txt | 62 +- ...5_04_27_000001_create_categories_table.php | 58 +- ...4_27_000002_create_subcategories_table.php | 58 +- ...5_04_30_000001_modify_categories_table.txt | 110 +- .../2025_05_01_create_cafe_ratings_table.php | 58 +- .../2025_05_10_restructure_cafes_table.php | 112 +- .../2025_05_20_create_favorites_table.php | 62 +- .../2025_05_22_create_cafe_images_table.php | 44 +- ..._000001_add_harga_range_to_cafes_table.php | 58 +- database/seeders/CafeAndRatingsSeeder.php | 250 +- database/seeders/CafePriceSeeder.php | 122 +- .../seeders/CategoryAndSubcategorySeeder.php | 470 +- database/seeders/CategorySeeder.php | 172 +- database/seeders/SubcategorySeeder.php | 130 +- public/.htaccess | 25 + public/assets/icons/User Dark.png | Bin 0 -> 16328 bytes public/assets/icons/User Light.png | Bin 0 -> 20658 bytes public/assets/icons/User Logo.svg | 1 + public/assets/images/About Us.png | Bin 0 -> 1973397 bytes public/assets/images/Feature 1.webp | Bin 0 -> 690682 bytes public/assets/images/Feature 2.webp | Bin 0 -> 480154 bytes public/assets/images/Logo.png | Bin 0 -> 17068 bytes public/assets/images/LogoDark.png | Bin 0 -> 17068 bytes public/assets/images/LogoLight.png | Bin 0 -> 26109 bytes public/css/disable-keyboard.css | 9 + public/favicon.ico | 0 public/images/layers-2x.png | Bin 0 -> 1259 bytes public/images/layers.png | Bin 0 -> 696 bytes public/images/leaflet/layers-2x.png | Bin 0 -> 1259 bytes public/images/leaflet/layers.png | Bin 0 -> 696 bytes public/images/leaflet/marker-icon-2x.png | Bin 0 -> 2464 bytes public/images/leaflet/marker-icon.png | Bin 0 -> 1466 bytes public/images/leaflet/marker-shadow.png | Bin 0 -> 618 bytes public/images/marker-icon-2x.png | Bin 0 -> 2464 bytes public/images/marker-icon.png | Bin 0 -> 1466 bytes public/images/marker-shadow.png | Bin 0 -> 618 bytes public/index.php | 20 + public/js/cafe-search.js | 252 + public/js/disable-keyboard.js | 23 + public/js/leaflet-init.js | 80 + .../Control.Geocoder.css | 1 + .../Control.Geocoder.js | 9 + .../Control.Geocoder.js.map | 1 + .../Control.Geocoder.modern.js | 1133 ++ .../Control.Geocoder.modern.js.map | 1 + public/leaflet-control-geocoder/control.d.ts | 161 + .../geocoders/api.d.ts | 77 + .../geocoders/arcgis.d.ts | 47 + .../geocoders/azure.d.ts | 84 + .../geocoders/bing.d.ts | 22 + .../geocoders/google.d.ts | 52 + .../geocoders/here.d.ts | 119 + .../geocoders/index.d.ts | 16 + .../geocoders/latlng.d.ts | 31 + .../geocoders/mapbox.d.ts | 64 + .../geocoders/mapquest.d.ts | 20 + .../geocoders/neutrino.d.ts | 20 + .../geocoders/nominatim.d.ts | 63 + .../geocoders/open-location-code.d.ts | 37 + .../geocoders/opencage.d.ts | 16 + .../geocoders/pelias.d.ts | 83 + .../geocoders/photon.d.ts | 46 + .../geocoders/what3words.d.ts | 19 + public/leaflet-control-geocoder/index.d.ts | 5 + public/leaflet-control-geocoder/util.d.ts | 12 + public/leaflet-images.zip | Bin 0 -> 696 bytes public/leaflet-src.esm.js | 14419 +++++++++++++++ public/leaflet-src.esm.js.map | 1 + public/leaflet-src.js | 14512 ++++++++++++++++ public/leaflet-src.js.map | 1 + public/leaflet.css | 661 + public/leaflet.js | 6 + public/leaflet.js.map | 1 + public/robots.txt | 2 + resources/css/app.css | 11 + resources/js/app.js | 1 + resources/js/bootstrap.js | 4 + resources/views/admin/bencana/index.blade.php | 694 + resources/views/admin/bencana/rekap.blade.php | 79 + resources/views/admin/cafes/create.blade.php | 574 + resources/views/admin/cafes/edit.blade.php | 1085 ++ resources/views/admin/cafes/index.blade.php | 117 + resources/views/admin/cafes/show.blade.php | 100 + .../views/admin/categories/create.blade.php | 31 + .../views/admin/categories/edit.blade.php | 32 + .../views/admin/categories/index.blade.php | 64 + .../admin/contact-messages/index.blade.php | 91 + .../admin/contact-messages/show.blade.php | 108 + resources/views/admin/dashboard.blade.php | 114 + .../admin/subcategories/create.blade.php | 153 + .../views/admin/subcategories/edit.blade.php | 53 + .../views/admin/subcategories/index.blade.php | 80 + resources/views/admin/users/create.blade.php | 82 + resources/views/admin/users/edit.blade.php | 83 + resources/views/admin/users/index.blade.php | 84 + resources/views/admin/users/show.blade.php | 106 + .../views/admin/weights/create.blade.txt | 114 + resources/views/admin/weights/edit.blade.txt | 123 + resources/views/admin/weights/index.blade.txt | 88 + resources/views/auth/login.blade.php | 114 + .../views/auth/passwords/email.blade.php | 65 + .../views/auth/passwords/reset.blade.php | 68 + resources/views/auth/register.blade.php | 120 + resources/views/auth/verify.blade.php | 67 + resources/views/cafe/index.blade.php | 104 + resources/views/cafe/recommend.blade.php | 155 + resources/views/cafe/search-detail.blade.php | 166 + resources/views/cafe/search-fixed.blade.php | 1219 ++ resources/views/cafe/search-history.blade.php | 199 + resources/views/cafe/search.blade.php | 679 + resources/views/cafe/show.blade.php | 481 + resources/views/components/alert.blade.php | 57 + .../views/components/session-alerts.blade.php | 25 + resources/views/components/stepper.blade.php | 77 + resources/views/favorites/index.blade.php | 87 + resources/views/layouts/admin/admin.blade.php | 278 + resources/views/layouts/admin/app.blade.php | 235 + .../views/layouts/admin/dashboard.blade.php | 114 + .../views/layouts/admin/header.blade.php | 40 + .../views/layouts/admin/sidebar.blade.php | 145 + resources/views/layouts/app.blade.php | 34 + resources/views/layouts/header.blade.php | 67 + resources/views/profile/index.blade.php | 113 + resources/views/welcome.blade.php | 104 + routes/console.php | 8 + routes/web.php | 131 + storage/app/.gitignore | 4 + storage/app/private/.gitignore | 2 + storage/app/public/.gitignore | 2 + storage/framework/.gitignore | 9 + storage/framework/cache/.gitignore | 3 + storage/framework/cache/data/.gitignore | 2 + storage/framework/sessions/.gitignore | 2 + storage/framework/testing/.gitignore | 2 + storage/framework/views/.gitignore | 2 + storage/logs/.gitignore | 2 + tests/Feature/ExampleTest.php | 19 + tests/TestCase.php | 10 + tests/Unit/ExampleTest.php | 16 + 169 files changed, 44037 insertions(+), 2717 deletions(-) create mode 100644 public/.htaccess create mode 100644 public/assets/icons/User Dark.png create mode 100644 public/assets/icons/User Light.png create mode 100644 public/assets/icons/User Logo.svg create mode 100644 public/assets/images/About Us.png create mode 100644 public/assets/images/Feature 1.webp create mode 100644 public/assets/images/Feature 2.webp create mode 100644 public/assets/images/Logo.png create mode 100644 public/assets/images/LogoDark.png create mode 100644 public/assets/images/LogoLight.png create mode 100644 public/css/disable-keyboard.css create mode 100644 public/favicon.ico create mode 100644 public/images/layers-2x.png create mode 100644 public/images/layers.png create mode 100644 public/images/leaflet/layers-2x.png create mode 100644 public/images/leaflet/layers.png create mode 100644 public/images/leaflet/marker-icon-2x.png create mode 100644 public/images/leaflet/marker-icon.png create mode 100644 public/images/leaflet/marker-shadow.png create mode 100644 public/images/marker-icon-2x.png create mode 100644 public/images/marker-icon.png create mode 100644 public/images/marker-shadow.png create mode 100644 public/index.php create mode 100644 public/js/cafe-search.js create mode 100644 public/js/disable-keyboard.js create mode 100644 public/js/leaflet-init.js create mode 100644 public/leaflet-control-geocoder/Control.Geocoder.css create mode 100644 public/leaflet-control-geocoder/Control.Geocoder.js create mode 100644 public/leaflet-control-geocoder/Control.Geocoder.js.map create mode 100644 public/leaflet-control-geocoder/Control.Geocoder.modern.js create mode 100644 public/leaflet-control-geocoder/Control.Geocoder.modern.js.map create mode 100644 public/leaflet-control-geocoder/control.d.ts create mode 100644 public/leaflet-control-geocoder/geocoders/api.d.ts create mode 100644 public/leaflet-control-geocoder/geocoders/arcgis.d.ts create mode 100644 public/leaflet-control-geocoder/geocoders/azure.d.ts create mode 100644 public/leaflet-control-geocoder/geocoders/bing.d.ts create mode 100644 public/leaflet-control-geocoder/geocoders/google.d.ts create mode 100644 public/leaflet-control-geocoder/geocoders/here.d.ts create mode 100644 public/leaflet-control-geocoder/geocoders/index.d.ts create mode 100644 public/leaflet-control-geocoder/geocoders/latlng.d.ts create mode 100644 public/leaflet-control-geocoder/geocoders/mapbox.d.ts create mode 100644 public/leaflet-control-geocoder/geocoders/mapquest.d.ts create mode 100644 public/leaflet-control-geocoder/geocoders/neutrino.d.ts create mode 100644 public/leaflet-control-geocoder/geocoders/nominatim.d.ts create mode 100644 public/leaflet-control-geocoder/geocoders/open-location-code.d.ts create mode 100644 public/leaflet-control-geocoder/geocoders/opencage.d.ts create mode 100644 public/leaflet-control-geocoder/geocoders/pelias.d.ts create mode 100644 public/leaflet-control-geocoder/geocoders/photon.d.ts create mode 100644 public/leaflet-control-geocoder/geocoders/what3words.d.ts create mode 100644 public/leaflet-control-geocoder/index.d.ts create mode 100644 public/leaflet-control-geocoder/util.d.ts create mode 100644 public/leaflet-images.zip create mode 100644 public/leaflet-src.esm.js create mode 100644 public/leaflet-src.esm.js.map create mode 100644 public/leaflet-src.js create mode 100644 public/leaflet-src.js.map create mode 100644 public/leaflet.css create mode 100644 public/leaflet.js create mode 100644 public/leaflet.js.map create mode 100644 public/robots.txt create mode 100644 resources/css/app.css create mode 100644 resources/js/app.js create mode 100644 resources/js/bootstrap.js create mode 100644 resources/views/admin/bencana/index.blade.php create mode 100644 resources/views/admin/bencana/rekap.blade.php create mode 100644 resources/views/admin/cafes/create.blade.php create mode 100644 resources/views/admin/cafes/edit.blade.php create mode 100644 resources/views/admin/cafes/index.blade.php create mode 100644 resources/views/admin/cafes/show.blade.php create mode 100644 resources/views/admin/categories/create.blade.php create mode 100644 resources/views/admin/categories/edit.blade.php create mode 100644 resources/views/admin/categories/index.blade.php create mode 100644 resources/views/admin/contact-messages/index.blade.php create mode 100644 resources/views/admin/contact-messages/show.blade.php create mode 100644 resources/views/admin/dashboard.blade.php create mode 100644 resources/views/admin/subcategories/create.blade.php create mode 100644 resources/views/admin/subcategories/edit.blade.php create mode 100644 resources/views/admin/subcategories/index.blade.php create mode 100644 resources/views/admin/users/create.blade.php create mode 100644 resources/views/admin/users/edit.blade.php create mode 100644 resources/views/admin/users/index.blade.php create mode 100644 resources/views/admin/users/show.blade.php create mode 100644 resources/views/admin/weights/create.blade.txt create mode 100644 resources/views/admin/weights/edit.blade.txt create mode 100644 resources/views/admin/weights/index.blade.txt create mode 100644 resources/views/auth/login.blade.php create mode 100644 resources/views/auth/passwords/email.blade.php create mode 100644 resources/views/auth/passwords/reset.blade.php create mode 100644 resources/views/auth/register.blade.php create mode 100644 resources/views/auth/verify.blade.php create mode 100644 resources/views/cafe/index.blade.php create mode 100644 resources/views/cafe/recommend.blade.php create mode 100644 resources/views/cafe/search-detail.blade.php create mode 100644 resources/views/cafe/search-fixed.blade.php create mode 100644 resources/views/cafe/search-history.blade.php create mode 100644 resources/views/cafe/search.blade.php create mode 100644 resources/views/cafe/show.blade.php create mode 100644 resources/views/components/alert.blade.php create mode 100644 resources/views/components/session-alerts.blade.php create mode 100644 resources/views/components/stepper.blade.php create mode 100644 resources/views/favorites/index.blade.php create mode 100644 resources/views/layouts/admin/admin.blade.php create mode 100644 resources/views/layouts/admin/app.blade.php create mode 100644 resources/views/layouts/admin/dashboard.blade.php create mode 100644 resources/views/layouts/admin/header.blade.php create mode 100644 resources/views/layouts/admin/sidebar.blade.php create mode 100644 resources/views/layouts/app.blade.php create mode 100644 resources/views/layouts/header.blade.php create mode 100644 resources/views/profile/index.blade.php create mode 100644 resources/views/welcome.blade.php create mode 100644 routes/console.php create mode 100644 routes/web.php create mode 100644 storage/app/.gitignore create mode 100644 storage/app/private/.gitignore create mode 100644 storage/app/public/.gitignore create mode 100644 storage/framework/.gitignore create mode 100644 storage/framework/cache/.gitignore create mode 100644 storage/framework/cache/data/.gitignore create mode 100644 storage/framework/sessions/.gitignore create mode 100644 storage/framework/testing/.gitignore create mode 100644 storage/framework/views/.gitignore create mode 100644 storage/logs/.gitignore create mode 100644 tests/Feature/ExampleTest.php create mode 100644 tests/TestCase.php create mode 100644 tests/Unit/ExampleTest.php diff --git a/.gitattributes b/.gitattributes index dfe0770..fcb21d3 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,11 @@ -# Auto detect text files and perform LF normalization -* text=auto +* text=auto eol=lf + +*.blade.php diff=html +*.css diff=css +*.html diff=html +*.md diff=markdown +*.php diff=php + +/.github export-ignore +CHANGELOG.md export-ignore +.styleci.yml export-ignore diff --git a/README.md b/README.md index e431711..6851ea9 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,61 @@ -# RECAJE +
+ + +## About Laravel + +Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as: + +- [Simple, fast routing engine](https://laravel.com/docs/routing). +- [Powerful dependency injection container](https://laravel.com/docs/container). +- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage. +- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent). +- Database agnostic [schema migrations](https://laravel.com/docs/migrations). +- [Robust background job processing](https://laravel.com/docs/queues). +- [Real-time event broadcasting](https://laravel.com/docs/broadcasting). + +Laravel is accessible, powerful, and provides tools required for large, robust applications. + +## Learning Laravel + +Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework. + +You may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch. + +If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library. + +## Laravel Sponsors + +We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com). + +### Premium Partners + +- **[Vehikl](https://vehikl.com/)** +- **[Tighten Co.](https://tighten.co)** +- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)** +- **[64 Robots](https://64robots.com)** +- **[Curotec](https://www.curotec.com/services/technologies/laravel/)** +- **[DevSquad](https://devsquad.com/hire-laravel-developers)** +- **[Redberry](https://redberry.international/laravel-development/)** +- **[Active Logic](https://activelogic.com)** + +## Contributing + +Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions). + +## Code of Conduct + +In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct). + +## Security Vulnerabilities + +If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed. + +## License + +The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT). diff --git a/app/Http/Controllers/Admin/CategoryController.php b/app/Http/Controllers/Admin/CategoryController.php index 178bb93..d6121bc 100644 --- a/app/Http/Controllers/Admin/CategoryController.php +++ b/app/Http/Controllers/Admin/CategoryController.php @@ -1,89 +1,89 @@ -get(); - return view('admin.categories.index', compact('categories')); - } - - /** - * Show the form for creating a new resource. - */ - public function create() - { - return view('admin.categories.create'); - } - - /** - * Store a newly created resource in storage. - */ - public function store(Request $request) - { - $request->validate([ - 'name' => 'required|string|max:255', - ]); - - $category = Category::create([ - 'name' => $request->name, - ]); - - return redirect()->route('admin.subcategories.create', ['category_id' => $category->id]) - ->with('success', 'Kategori berhasil ditambahkan! Silakan tambahkan sub-kategori.'); - } - - /** - * Display the specified resource. - */ - public function show(Category $category) - { - return view('admin.categories.show', compact('category')); - } - - /** - * Show the form for editing the specified resource. - */ - public function edit(Category $category) - { - return view('admin.categories.edit', compact('category')); - } - - /** - * Update the specified resource in storage. - */ - public function update(Request $request, Category $category) - { - $request->validate([ - 'name' => 'required|string|max:255', - ]); - - $category->update([ - 'name' => $request->name, - ]); - - return redirect()->route('admin.categories.index') - ->with('success', 'Kategori berhasil diperbarui!'); - } - - /** - * Remove the specified resource from storage. - */ - public function destroy(Category $category) - { - $category->delete(); - - return redirect()->route('admin.categories.index') - ->with('success', 'Kategori berhasil dihapus!'); - } +get(); + return view('admin.categories.index', compact('categories')); + } + + /** + * Show the form for creating a new resource. + */ + public function create() + { + return view('admin.categories.create'); + } + + /** + * Store a newly created resource in storage. + */ + public function store(Request $request) + { + $request->validate([ + 'name' => 'required|string|max:255', + ]); + + $category = Category::create([ + 'name' => $request->name, + ]); + + return redirect()->route('admin.subcategories.create', ['category_id' => $category->id]) + ->with('success', 'Kategori berhasil ditambahkan! Silakan tambahkan sub-kategori.'); + } + + /** + * Display the specified resource. + */ + public function show(Category $category) + { + return view('admin.categories.show', compact('category')); + } + + /** + * Show the form for editing the specified resource. + */ + public function edit(Category $category) + { + return view('admin.categories.edit', compact('category')); + } + + /** + * Update the specified resource in storage. + */ + public function update(Request $request, Category $category) + { + $request->validate([ + 'name' => 'required|string|max:255', + ]); + + $category->update([ + 'name' => $request->name, + ]); + + return redirect()->route('admin.categories.index') + ->with('success', 'Kategori berhasil diperbarui!'); + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(Category $category) + { + $category->delete(); + + return redirect()->route('admin.categories.index') + ->with('success', 'Kategori berhasil dihapus!'); + } } \ No newline at end of file diff --git a/app/Http/Controllers/Admin/SubcategoryController.php b/app/Http/Controllers/Admin/SubcategoryController.php index 460b3b7..b9ec140 100644 --- a/app/Http/Controllers/Admin/SubcategoryController.php +++ b/app/Http/Controllers/Admin/SubcategoryController.php @@ -1,123 +1,123 @@ -all()); - - $request->validate([ - 'category_id' => 'required|exists:categories,id', - 'subcategories' => 'required|array|min:1', - 'subcategories.*.name' => 'required|string|max:255', - 'subcategories.*.value' => 'required|integer|min:1|max:5' - ]); - - \Illuminate\Support\Facades\Log::info('Validation passed, processing subcategories'); - - try { - DB::beginTransaction(); - - foreach ($request->subcategories as $subcategory) { - \Illuminate\Support\Facades\Log::info('Creating subcategory:', $subcategory); - - Subcategory::create([ - 'category_id' => $request->category_id, - 'name' => $subcategory['name'], - 'value' => $subcategory['value'] - ]); - } - - DB::commit(); - \Illuminate\Support\Facades\Log::info('All subcategories created successfully'); - - return redirect()->route('admin.subcategories.index') - ->with('success', 'Sub-kategori berhasil ditambahkan!'); - - } catch (\Exception $e) { - DB::rollBack(); - \Illuminate\Support\Facades\Log::error('Error creating subcategories: ' . $e->getMessage()); - - return back()->with('error', 'Terjadi kesalahan saat menambahkan sub-kategori: ' . $e->getMessage()); - } - } - - /** - * Display the specified resource. - */ - public function show(Subcategory $subcategory) - { - return view('admin.subcategories.show', compact('subcategory')); - } - - /** - * Show the form for editing the specified resource. - */ - public function edit(Subcategory $subcategory) - { - $categories = Category::all(); - return view('admin.subcategories.edit', compact('subcategory', 'categories')); - } - - /** - * Update the specified resource in storage. - */ - public function update(Request $request, Subcategory $subcategory) - { - $request->validate([ - 'category_id' => 'required|exists:categories,id', - 'name' => 'required|string|max:255', - ]); - - $subcategory->update([ - 'category_id' => $request->category_id, - 'name' => $request->name, - ]); - - return redirect()->route('admin.subcategories.index') - ->with('success', 'Sub-kategori berhasil diperbarui!'); - } - - /** - * Remove the specified resource from storage. - */ - public function destroy(Subcategory $subcategory) - { - $subcategory->delete(); - - return redirect()->route('admin.subcategories.index') - ->with('success', 'Sub-kategori berhasil dihapus!'); - } +all()); + + $request->validate([ + 'category_id' => 'required|exists:categories,id', + 'subcategories' => 'required|array|min:1', + 'subcategories.*.name' => 'required|string|max:255', + 'subcategories.*.value' => 'required|integer|min:1|max:5' + ]); + + \Illuminate\Support\Facades\Log::info('Validation passed, processing subcategories'); + + try { + DB::beginTransaction(); + + foreach ($request->subcategories as $subcategory) { + \Illuminate\Support\Facades\Log::info('Creating subcategory:', $subcategory); + + Subcategory::create([ + 'category_id' => $request->category_id, + 'name' => $subcategory['name'], + 'value' => $subcategory['value'] + ]); + } + + DB::commit(); + \Illuminate\Support\Facades\Log::info('All subcategories created successfully'); + + return redirect()->route('admin.subcategories.index') + ->with('success', 'Sub-kategori berhasil ditambahkan!'); + + } catch (\Exception $e) { + DB::rollBack(); + \Illuminate\Support\Facades\Log::error('Error creating subcategories: ' . $e->getMessage()); + + return back()->with('error', 'Terjadi kesalahan saat menambahkan sub-kategori: ' . $e->getMessage()); + } + } + + /** + * Display the specified resource. + */ + public function show(Subcategory $subcategory) + { + return view('admin.subcategories.show', compact('subcategory')); + } + + /** + * Show the form for editing the specified resource. + */ + public function edit(Subcategory $subcategory) + { + $categories = Category::all(); + return view('admin.subcategories.edit', compact('subcategory', 'categories')); + } + + /** + * Update the specified resource in storage. + */ + public function update(Request $request, Subcategory $subcategory) + { + $request->validate([ + 'category_id' => 'required|exists:categories,id', + 'name' => 'required|string|max:255', + ]); + + $subcategory->update([ + 'category_id' => $request->category_id, + 'name' => $request->name, + ]); + + return redirect()->route('admin.subcategories.index') + ->with('success', 'Sub-kategori berhasil diperbarui!'); + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(Subcategory $subcategory) + { + $subcategory->delete(); + + return redirect()->route('admin.subcategories.index') + ->with('success', 'Sub-kategori berhasil dihapus!'); + } } \ No newline at end of file diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 0b92434..a3bb99b 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -1,182 +1,182 @@ -role !== 'admin') { - return redirect('/')->with('error', 'Anda tidak memiliki akses ke halaman tersebut.'); - } - - $users = User::orderBy('id', 'asc')->paginate(10); - - // Format nama setiap user - $users->getCollection()->transform(function ($user) { - $user->name = ucwords(strtolower($user->name)); - return $user; - }); - - return view('admin.users.index', compact('users')); - } - - /** - * Menampilkan detail pengguna. - * - * @param \App\Models\User $user - * @return \Illuminate\View\View - */ - public function show(User $user) - { - // Cek apakah pengguna adalah admin - if (Auth::user()->role !== 'admin') { - return redirect('/')->with('error', 'Anda tidak memiliki akses ke halaman tersebut.'); - } - - $user->name = ucwords(strtolower($user->name)); - return view('admin.users.show', compact('user')); - } - - /** - * Menghapus pengguna. - * - * @param \App\Models\User $user - * @return \Illuminate\Http\RedirectResponse - */ - public function destroy(User $user) - { - // Cek apakah pengguna adalah admin - if (Auth::user()->role !== 'admin') { - return redirect('/')->with('error', 'Anda tidak memiliki akses ke halaman tersebut.'); - } - - // Tidak bisa menghapus diri sendiri - if (Auth::id() === $user->id) { - return back()->with('error', 'Anda tidak dapat menghapus akun Anda sendiri.'); - } - - $user->delete(); - - return redirect()->route('admin.users.index') - ->with('success', 'Pengguna berhasil dihapus!'); - } - - public function create() - { - // Cek apakah pengguna adalah admin - if (Auth::user()->role !== 'admin') { - return redirect('/')->with('error', 'Anda tidak memiliki akses ke halaman tersebut.'); - } - - return view('admin.users.create'); - } - - /** - * Menyimpan user baru. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\RedirectResponse - */ - public function store(Request $request) - { - // Cek apakah pengguna adalah admin - if (Auth::user()->role !== 'admin') { - return redirect('/')->with('error', 'Anda tidak memiliki akses ke halaman tersebut.'); - } - - $request->validate([ - 'name' => 'required|string|max:255', - 'email' => 'required|string|email|max:255|unique:users', - 'password' => 'required|string|min:8|confirmed', - 'role' => 'required|in:admin,user', - ]); - - $user = User::create([ - 'name' => ucwords(strtolower($request->name)), - 'email' => $request->email, - 'password' => Hash::make($request->password), - 'role' => $request->role, - ]); - - return redirect()->route('admin.users.index') - ->with('success', 'Pengguna berhasil ditambahkan!'); - } - - /** - * Menampilkan form edit user. - * - * @param \App\Models\User $user - * @return \Illuminate\View\View - */ - public function edit(User $user) - { - // Cek apakah pengguna adalah admin - if (Auth::user()->role !== 'admin') { - return redirect('/')->with('error', 'Anda tidak memiliki akses ke halaman tersebut.'); - } - - $user->name = ucwords(strtolower($user->name)); - return view('admin.users.edit', compact('user')); - } - - /** - * Memperbarui data user. - * - * @param \Illuminate\Http\Request $request - * @param \App\Models\User $user - * @return \Illuminate\Http\RedirectResponse - */ - public function update(Request $request, User $user) - { - // Cek apakah pengguna adalah admin - if (Auth::user()->role !== 'admin') { - return redirect('/')->with('error', 'Anda tidak memiliki akses ke halaman tersebut.'); - } - - $request->validate([ - 'name' => 'required|string|max:255', - 'email' => 'required|string|email|max:255|unique:users,email,' . $user->id, - 'password' => 'nullable|string|min:8|confirmed', - 'role' => 'required|in:admin,user', - ]); - - $data = [ - 'name' => ucwords(strtolower($request->name)), - 'email' => $request->email, - 'role' => $request->role, - ]; - - // Update password hanya jika diisi - if ($request->filled('password')) { - $data['password'] = Hash::make($request->password); - } - - $user->update($data); - - return redirect()->route('admin.users.index') - ->with('success', 'Pengguna berhasil diperbarui!'); - } +role !== 'admin') { + return redirect('/')->with('error', 'Anda tidak memiliki akses ke halaman tersebut.'); + } + + $users = User::orderBy('id', 'asc')->paginate(10); + + // Format nama setiap user + $users->getCollection()->transform(function ($user) { + $user->name = ucwords(strtolower($user->name)); + return $user; + }); + + return view('admin.users.index', compact('users')); + } + + /** + * Menampilkan detail pengguna. + * + * @param \App\Models\User $user + * @return \Illuminate\View\View + */ + public function show(User $user) + { + // Cek apakah pengguna adalah admin + if (Auth::user()->role !== 'admin') { + return redirect('/')->with('error', 'Anda tidak memiliki akses ke halaman tersebut.'); + } + + $user->name = ucwords(strtolower($user->name)); + return view('admin.users.show', compact('user')); + } + + /** + * Menghapus pengguna. + * + * @param \App\Models\User $user + * @return \Illuminate\Http\RedirectResponse + */ + public function destroy(User $user) + { + // Cek apakah pengguna adalah admin + if (Auth::user()->role !== 'admin') { + return redirect('/')->with('error', 'Anda tidak memiliki akses ke halaman tersebut.'); + } + + // Tidak bisa menghapus diri sendiri + if (Auth::id() === $user->id) { + return back()->with('error', 'Anda tidak dapat menghapus akun Anda sendiri.'); + } + + $user->delete(); + + return redirect()->route('admin.users.index') + ->with('success', 'Pengguna berhasil dihapus!'); + } + + public function create() + { + // Cek apakah pengguna adalah admin + if (Auth::user()->role !== 'admin') { + return redirect('/')->with('error', 'Anda tidak memiliki akses ke halaman tersebut.'); + } + + return view('admin.users.create'); + } + + /** + * Menyimpan user baru. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\RedirectResponse + */ + public function store(Request $request) + { + // Cek apakah pengguna adalah admin + if (Auth::user()->role !== 'admin') { + return redirect('/')->with('error', 'Anda tidak memiliki akses ke halaman tersebut.'); + } + + $request->validate([ + 'name' => 'required|string|max:255', + 'email' => 'required|string|email|max:255|unique:users', + 'password' => 'required|string|min:8|confirmed', + 'role' => 'required|in:admin,user', + ]); + + $user = User::create([ + 'name' => ucwords(strtolower($request->name)), + 'email' => $request->email, + 'password' => Hash::make($request->password), + 'role' => $request->role, + ]); + + return redirect()->route('admin.users.index') + ->with('success', 'Pengguna berhasil ditambahkan!'); + } + + /** + * Menampilkan form edit user. + * + * @param \App\Models\User $user + * @return \Illuminate\View\View + */ + public function edit(User $user) + { + // Cek apakah pengguna adalah admin + if (Auth::user()->role !== 'admin') { + return redirect('/')->with('error', 'Anda tidak memiliki akses ke halaman tersebut.'); + } + + $user->name = ucwords(strtolower($user->name)); + return view('admin.users.edit', compact('user')); + } + + /** + * Memperbarui data user. + * + * @param \Illuminate\Http\Request $request + * @param \App\Models\User $user + * @return \Illuminate\Http\RedirectResponse + */ + public function update(Request $request, User $user) + { + // Cek apakah pengguna adalah admin + if (Auth::user()->role !== 'admin') { + return redirect('/')->with('error', 'Anda tidak memiliki akses ke halaman tersebut.'); + } + + $request->validate([ + 'name' => 'required|string|max:255', + 'email' => 'required|string|email|max:255|unique:users,email,' . $user->id, + 'password' => 'nullable|string|min:8|confirmed', + 'role' => 'required|in:admin,user', + ]); + + $data = [ + 'name' => ucwords(strtolower($request->name)), + 'email' => $request->email, + 'role' => $request->role, + ]; + + // Update password hanya jika diisi + if ($request->filled('password')) { + $data['password'] = Hash::make($request->password); + } + + $user->update($data); + + return redirect()->route('admin.users.index') + ->with('success', 'Pengguna berhasil diperbarui!'); + } } \ No newline at end of file diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php index e161bdd..0f03131 100644 --- a/app/Http/Controllers/Auth/ForgotPasswordController.php +++ b/app/Http/Controllers/Auth/ForgotPasswordController.php @@ -1,41 +1,41 @@ -validate([ - 'email' => ['required', 'email'], - ]); - - $status = Password::sendResetLink( - $request->only('email') - ); - - return $status === Password::RESET_LINK_SENT - ? back()->with('status', 'Link reset password telah dikirim ke email Anda!') - : back()->withErrors(['email' => __($status)]); - } +validate([ + 'email' => ['required', 'email'], + ]); + + $status = Password::sendResetLink( + $request->only('email') + ); + + return $status === Password::RESET_LINK_SENT + ? back()->with('status', 'Link reset password telah dikirim ke email Anda!') + : back()->withErrors(['email' => __($status)]); + } } \ No newline at end of file diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 9e1247b..c08b3e7 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -1,151 +1,151 @@ -role === 'admin') { - return route('admin.dashboard'); - } - - return '/'; - } - - /** - * Memproses request login - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\RedirectResponse - */ - public function login(Request $request) - { - try { - $credentials = $request->validate([ - 'email' => ['required', 'email'], - 'password' => ['required'], - ], [ - 'email.required' => 'Email harus diisi', - 'email.email' => 'Format email tidak valid', - 'password.required' => 'Password harus diisi', - ]); - - if (Auth::attempt($credentials, $request->boolean('remember'))) { - $request->session()->regenerate(); - - if (Auth::user()->role === 'admin') { - return redirect()->route('admin.dashboard'); - } - - return redirect()->intended('/'); - } - - return back() - ->withInput($request->only('email')) - ->withErrors([ - 'email' => 'Email atau password yang diberikan tidak cocok dengan data kami.', - ]); - - } catch (\Exception $e) { - return back() - ->withInput($request->only('email')) - ->withErrors([ - 'error' => 'Terjadi kesalahan saat mencoba login. Silakan coba lagi.', - ]); - } - } - - /** - * Logout pengguna - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\RedirectResponse - */ - public function logout(Request $request) - { - Auth::logout(); - - $request->session()->invalidate(); - $request->session()->regenerateToken(); - - return redirect('/'); - } - - /** - * Redirect ke halaman login Google. - * - * @return \Illuminate\Http\Response - */ - public function redirectToGoogle() - { - return Socialite::driver('google')->redirect(); - } - - /** - * Handle callback dari Google. - * - * @return \Illuminate\Http\Response - */ - public function handleGoogleCallback() - { - try { - $googleUser = Socialite::driver('google')->user(); - - // Cari user berdasarkan google id, jika tidak ada cari berdasarkan email - $user = User::where('google_id', $googleUser->id)->first(); - - if (!$user) { - // Cek apakah email sudah terdaftar - $user = User::where('email', $googleUser->email)->first(); - - if (!$user) { - // Jika user belum terdaftar, buat user baru - $user = User::create([ - 'name' => $googleUser->name, - 'email' => $googleUser->email, - 'google_id' => $googleUser->id, - 'password' => Hash::make(rand(1,10000)), - 'email_verified_at' => now(), // Email otomatis terverifikasi - ]); - } else { - // Update google_id untuk user yang sudah ada - $user->update([ - 'google_id' => $googleUser->id, - 'email_verified_at' => now(), // Email otomatis terverifikasi - ]); - } - } - - // Login user - Auth::login($user); - - return redirect('/'); - - } catch (\Exception $e) { - return redirect('/login')->with('error', 'Terjadi kesalahan saat login dengan Google: ' . $e->getMessage()); - } - } +role === 'admin') { + return route('admin.dashboard'); + } + + return '/'; + } + + /** + * Memproses request login + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\RedirectResponse + */ + public function login(Request $request) + { + try { + $credentials = $request->validate([ + 'email' => ['required', 'email'], + 'password' => ['required'], + ], [ + 'email.required' => 'Email harus diisi', + 'email.email' => 'Format email tidak valid', + 'password.required' => 'Password harus diisi', + ]); + + if (Auth::attempt($credentials, $request->boolean('remember'))) { + $request->session()->regenerate(); + + if (Auth::user()->role === 'admin') { + return redirect()->route('admin.dashboard'); + } + + return redirect()->intended('/'); + } + + return back() + ->withInput($request->only('email')) + ->withErrors([ + 'email' => 'Email atau password yang diberikan tidak cocok dengan data kami.', + ]); + + } catch (\Exception $e) { + return back() + ->withInput($request->only('email')) + ->withErrors([ + 'error' => 'Terjadi kesalahan saat mencoba login. Silakan coba lagi.', + ]); + } + } + + /** + * Logout pengguna + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\RedirectResponse + */ + public function logout(Request $request) + { + Auth::logout(); + + $request->session()->invalidate(); + $request->session()->regenerateToken(); + + return redirect('/'); + } + + /** + * Redirect ke halaman login Google. + * + * @return \Illuminate\Http\Response + */ + public function redirectToGoogle() + { + return Socialite::driver('google')->redirect(); + } + + /** + * Handle callback dari Google. + * + * @return \Illuminate\Http\Response + */ + public function handleGoogleCallback() + { + try { + $googleUser = Socialite::driver('google')->user(); + + // Cari user berdasarkan google id, jika tidak ada cari berdasarkan email + $user = User::where('google_id', $googleUser->id)->first(); + + if (!$user) { + // Cek apakah email sudah terdaftar + $user = User::where('email', $googleUser->email)->first(); + + if (!$user) { + // Jika user belum terdaftar, buat user baru + $user = User::create([ + 'name' => $googleUser->name, + 'email' => $googleUser->email, + 'google_id' => $googleUser->id, + 'password' => Hash::make(rand(1,10000)), + 'email_verified_at' => now(), // Email otomatis terverifikasi + ]); + } else { + // Update google_id untuk user yang sudah ada + $user->update([ + 'google_id' => $googleUser->id, + 'email_verified_at' => now(), // Email otomatis terverifikasi + ]); + } + } + + // Login user + Auth::login($user); + + return redirect('/'); + + } catch (\Exception $e) { + return redirect('/login')->with('error', 'Terjadi kesalahan saat login dengan Google: ' . $e->getMessage()); + } + } } \ No newline at end of file diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php index f575e42..82907fe 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -1,51 +1,51 @@ -validate([ - 'name' => ['required', 'string', 'max:255'], - 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], - 'password' => ['required', 'string', 'min:8', 'confirmed'], - 'terms' => ['required'], - ]); - - $user = User::create([ - 'name' => $request->name, - 'email' => $request->email, - 'password' => Hash::make($request->password), - ]); - - event(new Registered($user)); - - Auth::login($user); - - return redirect()->route('verification.notice'); - } +validate([ + 'name' => ['required', 'string', 'max:255'], + 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], + 'password' => ['required', 'string', 'min:8', 'confirmed'], + 'terms' => ['required'], + ]); + + $user = User::create([ + 'name' => $request->name, + 'email' => $request->email, + 'password' => Hash::make($request->password), + ]); + + event(new Registered($user)); + + Auth::login($user); + + return redirect()->route('verification.notice'); + } } \ No newline at end of file diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php index 7a1fbf9..70f33e0 100644 --- a/app/Http/Controllers/Auth/ResetPasswordController.php +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -1,58 +1,58 @@ -with( - ['token' => $token, 'email' => $request->email] - ); - } - - /** - * Memproses reset password - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\RedirectResponse - */ - public function reset(Request $request) - { - $request->validate([ - 'token' => ['required'], - 'email' => ['required', 'email'], - 'password' => ['required', 'confirmed', 'min:8'], - ]); - - $status = Password::reset( - $request->only('email', 'password', 'password_confirmation', 'token'), - function ($user, $password) { - $user->forceFill([ - 'password' => Hash::make($password) - ])->setRememberToken(Str::random(60)); - - $user->save(); - - event(new PasswordReset($user)); - } - ); - - return $status === Password::PASSWORD_RESET - ? redirect()->route('login')->with('status', 'Password Anda telah direset!') - : back()->withErrors(['email' => [__($status)]]); - } +with( + ['token' => $token, 'email' => $request->email] + ); + } + + /** + * Memproses reset password + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\RedirectResponse + */ + public function reset(Request $request) + { + $request->validate([ + 'token' => ['required'], + 'email' => ['required', 'email'], + 'password' => ['required', 'confirmed', 'min:8'], + ]); + + $status = Password::reset( + $request->only('email', 'password', 'password_confirmation', 'token'), + function ($user, $password) { + $user->forceFill([ + 'password' => Hash::make($password) + ])->setRememberToken(Str::random(60)); + + $user->save(); + + event(new PasswordReset($user)); + } + ); + + return $status === Password::PASSWORD_RESET + ? redirect()->route('login')->with('status', 'Password Anda telah direset!') + : back()->withErrors(['email' => [__($status)]]); + } } \ No newline at end of file diff --git a/app/Http/Controllers/CafeController.php b/app/Http/Controllers/CafeController.php index 388cbb8..b76249c 100644 --- a/app/Http/Controllers/CafeController.php +++ b/app/Http/Controllers/CafeController.php @@ -1,91 +1,91 @@ -has('weight')) { - $currentStep = 3; // Hasil - } elseif ($request->has('criteria')) { - $currentStep = 2; // Kriteria - } - - return view('cafe.search-fixed', compact('currentStep')); - } - - /** - * Tampilkan halaman rekomendasi cafe - */ - public function recommend() - { - return view('cafe.recommend'); - } - - /** - * Tampilkan semua cafe dengan fitur fuzzy search toleran terhadap typo - */ - public function index(Request $request) - { - $query = Cafe::with(['ratings.subcategory', 'ratings.category']); - - // Filter berdasarkan pencarian jika ada - if ($request->has('search') && !empty($request->search)) { - $searchTerm = $request->search; - - // Memecah kata kunci pencarian menjadi beberapa kata - $keywords = explode(' ', $searchTerm); - - $query->where(function($q) use ($searchTerm, $keywords) { - // Exact match dengan prioritas tertinggi - $q->where('nama', 'LIKE', "%{$searchTerm}%"); - - // Pencarian fuzzy untuk masing-masing kata kunci - foreach ($keywords as $keyword) { - if (strlen($keyword) >= 3) { - // Membuat variasi kata kunci untuk toleransi typo - $q->orWhere('nama', 'LIKE', "%{$keyword}%"); - - // Tambahkan wildcard di tengah untuk toleransi kesalahan ketik 1 karakter - for ($i = 0; $i < strlen($keyword) - 1; $i++) { - $fuzzyWord = substr($keyword, 0, $i) . '_' . substr($keyword, $i + 1); - $q->orWhere('nama', 'LIKE', "%{$fuzzyWord}%"); - } - } - } - - // Mencoba juga pencarian dengan operator SOUNDS LIKE jika tersedia - try { - $q->orWhereRaw("SOUNDEX(nama) = SOUNDEX(?)", [$searchTerm]); - } catch (\Exception $e) { - // SOUNDEX mungkin tidak tersedia, tidak perlu error handling - } - }); - } - - $cafes = $query->latest()->paginate(12)->withQueryString(); - - return view('cafe.index', compact('cafes')); - } - - /** - * Tampilkan detail cafe - */ - public function show($id) - { - $cafe = Cafe::with(['ratings.subcategory', 'ratings.category'])->findOrFail($id); - return view('cafe.show', compact('cafe')); - } +has('weight')) { + $currentStep = 3; // Hasil + } elseif ($request->has('criteria')) { + $currentStep = 2; // Kriteria + } + + return view('cafe.search-fixed', compact('currentStep')); + } + + /** + * Tampilkan halaman rekomendasi cafe + */ + public function recommend() + { + return view('cafe.recommend'); + } + + /** + * Tampilkan semua cafe dengan fitur fuzzy search toleran terhadap typo + */ + public function index(Request $request) + { + $query = Cafe::with(['ratings.subcategory', 'ratings.category']); + + // Filter berdasarkan pencarian jika ada + if ($request->has('search') && !empty($request->search)) { + $searchTerm = $request->search; + + // Memecah kata kunci pencarian menjadi beberapa kata + $keywords = explode(' ', $searchTerm); + + $query->where(function($q) use ($searchTerm, $keywords) { + // Exact match dengan prioritas tertinggi + $q->where('nama', 'LIKE', "%{$searchTerm}%"); + + // Pencarian fuzzy untuk masing-masing kata kunci + foreach ($keywords as $keyword) { + if (strlen($keyword) >= 3) { + // Membuat variasi kata kunci untuk toleransi typo + $q->orWhere('nama', 'LIKE', "%{$keyword}%"); + + // Tambahkan wildcard di tengah untuk toleransi kesalahan ketik 1 karakter + for ($i = 0; $i < strlen($keyword) - 1; $i++) { + $fuzzyWord = substr($keyword, 0, $i) . '_' . substr($keyword, $i + 1); + $q->orWhere('nama', 'LIKE', "%{$fuzzyWord}%"); + } + } + } + + // Mencoba juga pencarian dengan operator SOUNDS LIKE jika tersedia + try { + $q->orWhereRaw("SOUNDEX(nama) = SOUNDEX(?)", [$searchTerm]); + } catch (\Exception $e) { + // SOUNDEX mungkin tidak tersedia, tidak perlu error handling + } + }); + } + + $cafes = $query->latest()->paginate(12)->withQueryString(); + + return view('cafe.index', compact('cafes')); + } + + /** + * Tampilkan detail cafe + */ + public function show($id) + { + $cafe = Cafe::with(['ratings.subcategory', 'ratings.category'])->findOrFail($id); + return view('cafe.show', compact('cafe')); + } } \ No newline at end of file diff --git a/app/Http/Controllers/ContactController.php b/app/Http/Controllers/ContactController.php index d89737b..cf04559 100644 --- a/app/Http/Controllers/ContactController.php +++ b/app/Http/Controllers/ContactController.php @@ -1,71 +1,71 @@ -validate([ - 'name' => 'required|string|max:255', - 'email' => 'required|string|email|max:255', - 'message' => 'required|string', - ]); - - ContactMessage::create($validated); - - return redirect()->back()->with('success', 'Pesan Anda telah terkirim! Terima kasih telah menghubungi kami.'); - } - - /** - * Display a listing of contact messages for admin. - * - * @return \Illuminate\Http\Response - */ - public function index() - { - $messages = ContactMessage::orderBy('created_at', 'desc')->paginate(10); - - return view('admin.contact-messages.index', compact('messages')); - } - - /** - * Display the specified contact message. - * - * @param \App\Models\ContactMessage $contactMessage - * @return \Illuminate\Http\Response - */ - public function show(ContactMessage $contactMessage) - { - // Mark as read - if (!$contactMessage->is_read) { - $contactMessage->is_read = true; - $contactMessage->save(); - } - - return view('admin.contact-messages.show', compact('contactMessage')); - } - - /** - * Remove the specified contact message from storage. - * - * @param \App\Models\ContactMessage $contactMessage - * @return \Illuminate\Http\Response - */ - public function destroy(ContactMessage $contactMessage) - { - $contactMessage->delete(); - - return redirect()->route('admin.contact-messages.index') - ->with('success', 'Pesan kontak berhasil dihapus.'); - } +validate([ + 'name' => 'required|string|max:255', + 'email' => 'required|string|email|max:255', + 'message' => 'required|string', + ]); + + ContactMessage::create($validated); + + return redirect()->back()->with('success', 'Pesan Anda telah terkirim! Terima kasih telah menghubungi kami.'); + } + + /** + * Display a listing of contact messages for admin. + * + * @return \Illuminate\Http\Response + */ + public function index() + { + $messages = ContactMessage::orderBy('created_at', 'desc')->paginate(10); + + return view('admin.contact-messages.index', compact('messages')); + } + + /** + * Display the specified contact message. + * + * @param \App\Models\ContactMessage $contactMessage + * @return \Illuminate\Http\Response + */ + public function show(ContactMessage $contactMessage) + { + // Mark as read + if (!$contactMessage->is_read) { + $contactMessage->is_read = true; + $contactMessage->save(); + } + + return view('admin.contact-messages.show', compact('contactMessage')); + } + + /** + * Remove the specified contact message from storage. + * + * @param \App\Models\ContactMessage $contactMessage + * @return \Illuminate\Http\Response + */ + public function destroy(ContactMessage $contactMessage) + { + $contactMessage->delete(); + + return redirect()->route('admin.contact-messages.index') + ->with('success', 'Pesan kontak berhasil dihapus.'); + } } \ No newline at end of file diff --git a/app/Http/Controllers/FavoriteController.php b/app/Http/Controllers/FavoriteController.php index 84f496f..76cb201 100644 --- a/app/Http/Controllers/FavoriteController.php +++ b/app/Http/Controllers/FavoriteController.php @@ -1,111 +1,111 @@ -json(['success' => false, 'message' => 'ID cafe tidak valid'], 400); - } - - // Cek apakah cafe dengan ID tersebut ada - $cafe = Cafe::find($cafeId); - if (!$cafe) { - Log::error('Cafe not found with ID: ' . $cafeId); - return response()->json(['success' => false, 'message' => 'Cafe tidak ditemukan'], 404); - } - - $user = Auth::user(); - if (!$user) { - Log::error('User not authenticated for favorite toggle'); - return response()->json(['success' => false, 'message' => 'Pengguna tidak terautentikasi'], 401); - } - - Log::info('Toggling favorite for user ID: ' . $user->id . ' and cafe ID: ' . $cafeId); - - // Pemeriksaan apakah sudah favorit secara langsung menggunakan query - $existingFavorite = DB::table('favorites') - ->where('user_id', $user->id) - ->where('cafe_id', $cafeId) - ->first(); - - if ($existingFavorite) { - // Hapus favorit - DB::table('favorites') - ->where('user_id', $user->id) - ->where('cafe_id', $cafeId) - ->delete(); - - $isFavorited = false; - Log::info('Favorite removed for user ID: ' . $user->id . ' and cafe ID: ' . $cafeId); - } else { - // Tambahkan favorit - DB::table('favorites')->insert([ - 'user_id' => $user->id, - 'cafe_id' => $cafeId, - 'created_at' => now(), - 'updated_at' => now() - ]); - - $isFavorited = true; - Log::info('Favorite added for user ID: ' . $user->id . ' and cafe ID: ' . $cafeId); - } - - if ($request->wantsJson() || $request->ajax()) { - return response()->json([ - 'success' => true, - 'isFavorited' => $isFavorited - ]); - } - - return back()->with('success', $isFavorited ? 'Cafe ditambahkan ke favorit' : 'Cafe dihapus dari favorit'); - } catch (Exception $e) { - Log::error('Error toggling favorite: ' . $e->getMessage() . "\n" . $e->getTraceAsString()); - - if ($request->wantsJson() || $request->ajax()) { - return response()->json([ - 'success' => false, - 'message' => 'Terjadi kesalahan: ' . $e->getMessage() - ], 500); - } - - return back()->with('error', 'Terjadi kesalahan saat mengubah status favorit.'); - } - } - - /** - * Menampilkan daftar cafe favorit user - * - * @return \Illuminate\Http\Response - */ - public function index() - { - $user = Auth::user(); - $favorites = $user->favorites()->with('cafe')->get(); - - return view('favorites.index', [ - 'favorites' => $favorites - ]); - } +json(['success' => false, 'message' => 'ID cafe tidak valid'], 400); + } + + // Cek apakah cafe dengan ID tersebut ada + $cafe = Cafe::find($cafeId); + if (!$cafe) { + Log::error('Cafe not found with ID: ' . $cafeId); + return response()->json(['success' => false, 'message' => 'Cafe tidak ditemukan'], 404); + } + + $user = Auth::user(); + if (!$user) { + Log::error('User not authenticated for favorite toggle'); + return response()->json(['success' => false, 'message' => 'Pengguna tidak terautentikasi'], 401); + } + + Log::info('Toggling favorite for user ID: ' . $user->id . ' and cafe ID: ' . $cafeId); + + // Pemeriksaan apakah sudah favorit secara langsung menggunakan query + $existingFavorite = DB::table('favorites') + ->where('user_id', $user->id) + ->where('cafe_id', $cafeId) + ->first(); + + if ($existingFavorite) { + // Hapus favorit + DB::table('favorites') + ->where('user_id', $user->id) + ->where('cafe_id', $cafeId) + ->delete(); + + $isFavorited = false; + Log::info('Favorite removed for user ID: ' . $user->id . ' and cafe ID: ' . $cafeId); + } else { + // Tambahkan favorit + DB::table('favorites')->insert([ + 'user_id' => $user->id, + 'cafe_id' => $cafeId, + 'created_at' => now(), + 'updated_at' => now() + ]); + + $isFavorited = true; + Log::info('Favorite added for user ID: ' . $user->id . ' and cafe ID: ' . $cafeId); + } + + if ($request->wantsJson() || $request->ajax()) { + return response()->json([ + 'success' => true, + 'isFavorited' => $isFavorited + ]); + } + + return back()->with('success', $isFavorited ? 'Cafe ditambahkan ke favorit' : 'Cafe dihapus dari favorit'); + } catch (Exception $e) { + Log::error('Error toggling favorite: ' . $e->getMessage() . "\n" . $e->getTraceAsString()); + + if ($request->wantsJson() || $request->ajax()) { + return response()->json([ + 'success' => false, + 'message' => 'Terjadi kesalahan: ' . $e->getMessage() + ], 500); + } + + return back()->with('error', 'Terjadi kesalahan saat mengubah status favorit.'); + } + } + + /** + * Menampilkan daftar cafe favorit user + * + * @return \Illuminate\Http\Response + */ + public function index() + { + $user = Auth::user(); + $favorites = $user->favorites()->with('cafe')->get(); + + return view('favorites.index', [ + 'favorites' => $favorites + ]); + } } \ No newline at end of file diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index 2878da7..3995d45 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -1,54 +1,54 @@ -validate([ - 'name' => ['required', 'string', 'max:255'], - 'email' => ['required', 'string', 'email', 'max:255', 'unique:users,email,'.$user->id], - ]); - - $user->update($validated); - - return redirect()->route('profile.index')->with('success', 'Profil berhasil diperbarui!'); - } - - /** - * Update password pengguna - */ - public function updatePassword(Request $request) - { - $validated = $request->validate([ - 'current_password' => ['required', 'current_password'], - 'password' => ['required', 'confirmed', Password::defaults()], - ]); - - $user = Auth::user(); - $user->password = Hash::make($validated['password']); - $user->save(); - - return redirect()->route('profile.index')->with('success', 'Password berhasil diperbarui!'); - } +validate([ + 'name' => ['required', 'string', 'max:255'], + 'email' => ['required', 'string', 'email', 'max:255', 'unique:users,email,'.$user->id], + ]); + + $user->update($validated); + + return redirect()->route('profile.index')->with('success', 'Profil berhasil diperbarui!'); + } + + /** + * Update password pengguna + */ + public function updatePassword(Request $request) + { + $validated = $request->validate([ + 'current_password' => ['required', 'current_password'], + 'password' => ['required', 'confirmed', Password::defaults()], + ]); + + $user = Auth::user(); + $user->password = Hash::make($validated['password']); + $user->save(); + + return redirect()->route('profile.index')->with('success', 'Password berhasil diperbarui!'); + } } \ No newline at end of file diff --git a/app/Http/Controllers/SmartSearchController.php b/app/Http/Controllers/SmartSearchController.php index ea56efa..92eab52 100644 --- a/app/Http/Controllers/SmartSearchController.php +++ b/app/Http/Controllers/SmartSearchController.php @@ -1,199 +1,199 @@ -input('weight', []); - $criterias = $request->input('criteria', []); - $lokasi = $request->input('lokasi'); - - // Get cafes with ratings - $cafes = Cafe::with(['ratings.subcategory', 'ratings.category'])->get(); - - // Filter berdasarkan lokasi jika ada - if ($lokasi) { - $cafes = $cafes->filter(function($cafe) use ($lokasi) { - return stripos($cafe->lokasi, $lokasi) !== false; - }); - } - - // Hitung total semua bobot untuk normalisasi - $totalWeight = array_sum($weights); - if ($totalWeight == 0) { - // Jika tidak ada bobot, buat bobot merata - $categories = Category::all(); - foreach ($categories as $category) { - $weights[$category->id] = 100 / $categories->count(); - } - $totalWeight = 100; - } - - // Hitung skor SMART untuk setiap cafe - $cafeScores = []; - - foreach ($cafes as $cafe) { - $score = 0; - $debugInfo = []; - - $ratings = \Illuminate\Support\Facades\DB::table('cafe_ratings') - ->join('subcategories', 'cafe_ratings.subcategory_id', '=', 'subcategories.id') - ->join('categories', 'cafe_ratings.category_id', '=', 'categories.id') - ->where('cafe_ratings.cafe_id', $cafe->id) - ->select( - 'cafe_ratings.category_id', - 'categories.name as category_name', - 'cafe_ratings.subcategory_id', - 'subcategories.name as subcategory_name', - 'subcategories.value' - ) - ->get(); - - foreach ($ratings as $rating) { - // Jika ada bobot untuk kategori ini - if (isset($weights[$rating->category_id])) { - $weight = floatval($weights[$rating->category_id]); - $normalizedWeight = $totalWeight > 0 ? $weight / $totalWeight : 0; - - // Pastikan nilai valid - $value = max(1, intval($rating->value)); - - // Bonus nilai jika kriteria dipilih - if (isset($criterias[$rating->category_id]) && - $criterias[$rating->category_id] == $rating->subcategory_id) { - $value = 5; // Nilai maksimal - } - - // Hitung utilitas - menerapkan konsep yang sama dengan view - $utilityValue = ($value - 1) / 4; // Normalisasi ke rentang 0-1 (jika nilai antara 1-5) - $ratingScore = $normalizedWeight * $utilityValue; - $score += $ratingScore; - - // Simpan untuk debug - $debugInfo[$rating->category_name] = [ - 'bobot' => $weight, - 'bobot_normal' => $normalizedWeight, - 'nilai' => $value, - 'nilai_asli' => $rating->value, - 'subcategory' => $rating->subcategory_name, - 'skor_kriteria' => $ratingScore - ]; - } - } - - $cafeScores[$cafe->id] = [ - 'score' => $score, - 'debug' => $debugInfo - ]; - } - - // Urutkan cafe berdasarkan skor tertinggi - uasort($cafeScores, function($a, $b) { - return $b['score'] <=> $a['score']; - }); - - // Ambil id cafe yang sudah diurutkan - $rankedCafeIds = array_keys($cafeScores); - - // Urutkan koleksi cafe sesuai skor - $rankedCafes = $cafes->sortBy(function($cafe) use ($rankedCafeIds) { - return array_search($cafe->id, $rankedCafeIds); - }); - - // Ambil hanya 10 cafe teratas - $rankedCafes = $rankedCafes->take(10); - - // Persiapkan data hasil untuk disimpan - $resultsData = []; - foreach ($rankedCafes as $cafe) { - if (isset($cafeScores[$cafe->id])) { - $resultsData[$cafe->id] = [ - 'name' => $cafe->nama, - 'score' => $cafeScores[$cafe->id]['score'], - 'details' => $cafeScores[$cafe->id]['debug'] - ]; - } - } - - // Simpan hasil pencarian ke database jika user sudah login - if (Auth::check()) { - SearchHistory::create([ - 'user_id' => Auth::id(), - 'weights' => $weights, - 'criteria' => $criterias, - 'location' => $lokasi, - 'results' => $resultsData - ]); - } - - return view('cafe.search-fixed', compact('currentStep', 'rankedCafes', 'cafeScores')); - } - - /** - * Display search history for the authenticated user. - * - * @return \Illuminate\View\View - */ - public function history() - { - $histories = SearchHistory::where('user_id', Auth::id()) - ->orderBy('created_at', 'desc') - ->paginate(10); - - return view('cafe.search-history', compact('histories')); - } - - /** - * Display search history detail. - * - * @param int $id - * @return \Illuminate\View\View - */ - public function detail($id) - { - $history = SearchHistory::where('user_id', Auth::id()) - ->findOrFail($id); - - // Retrieve cafes from the stored results - $cafeIds = array_keys($history->results ?? []); - $cafes = Cafe::whereIn('id', $cafeIds)->get(); - - // Map cafes to include their scores from history - $rankedCafes = $cafes->map(function($cafe) use ($history) { - $cafe->smart_score = $history->results[$cafe->id]['score'] ?? 0; - $cafe->smart_details = $history->results[$cafe->id]['details'] ?? []; - return $cafe; - })->sortByDesc('smart_score'); - - $weights = $history->weights; - - return view('cafe.search-detail', compact('history', 'rankedCafes', 'weights')); - } +input('weight', []); + $criterias = $request->input('criteria', []); + $lokasi = $request->input('lokasi'); + + // Get cafes with ratings + $cafes = Cafe::with(['ratings.subcategory', 'ratings.category'])->get(); + + // Filter berdasarkan lokasi jika ada + if ($lokasi) { + $cafes = $cafes->filter(function($cafe) use ($lokasi) { + return stripos($cafe->lokasi, $lokasi) !== false; + }); + } + + // Hitung total semua bobot untuk normalisasi + $totalWeight = array_sum($weights); + if ($totalWeight == 0) { + // Jika tidak ada bobot, buat bobot merata + $categories = Category::all(); + foreach ($categories as $category) { + $weights[$category->id] = 100 / $categories->count(); + } + $totalWeight = 100; + } + + // Hitung skor SMART untuk setiap cafe + $cafeScores = []; + + foreach ($cafes as $cafe) { + $score = 0; + $debugInfo = []; + + $ratings = \Illuminate\Support\Facades\DB::table('cafe_ratings') + ->join('subcategories', 'cafe_ratings.subcategory_id', '=', 'subcategories.id') + ->join('categories', 'cafe_ratings.category_id', '=', 'categories.id') + ->where('cafe_ratings.cafe_id', $cafe->id) + ->select( + 'cafe_ratings.category_id', + 'categories.name as category_name', + 'cafe_ratings.subcategory_id', + 'subcategories.name as subcategory_name', + 'subcategories.value' + ) + ->get(); + + foreach ($ratings as $rating) { + // Jika ada bobot untuk kategori ini + if (isset($weights[$rating->category_id])) { + $weight = floatval($weights[$rating->category_id]); + $normalizedWeight = $totalWeight > 0 ? $weight / $totalWeight : 0; + + // Pastikan nilai valid + $value = max(1, intval($rating->value)); + + // Bonus nilai jika kriteria dipilih + if (isset($criterias[$rating->category_id]) && + $criterias[$rating->category_id] == $rating->subcategory_id) { + $value = 5; // Nilai maksimal + } + + // Hitung utilitas - menerapkan konsep yang sama dengan view + $utilityValue = ($value - 1) / 4; // Normalisasi ke rentang 0-1 (jika nilai antara 1-5) + $ratingScore = $normalizedWeight * $utilityValue; + $score += $ratingScore; + + // Simpan untuk debug + $debugInfo[$rating->category_name] = [ + 'bobot' => $weight, + 'bobot_normal' => $normalizedWeight, + 'nilai' => $value, + 'nilai_asli' => $rating->value, + 'subcategory' => $rating->subcategory_name, + 'skor_kriteria' => $ratingScore + ]; + } + } + + $cafeScores[$cafe->id] = [ + 'score' => $score, + 'debug' => $debugInfo + ]; + } + + // Urutkan cafe berdasarkan skor tertinggi + uasort($cafeScores, function($a, $b) { + return $b['score'] <=> $a['score']; + }); + + // Ambil id cafe yang sudah diurutkan + $rankedCafeIds = array_keys($cafeScores); + + // Urutkan koleksi cafe sesuai skor + $rankedCafes = $cafes->sortBy(function($cafe) use ($rankedCafeIds) { + return array_search($cafe->id, $rankedCafeIds); + }); + + // Ambil hanya 10 cafe teratas + $rankedCafes = $rankedCafes->take(10); + + // Persiapkan data hasil untuk disimpan + $resultsData = []; + foreach ($rankedCafes as $cafe) { + if (isset($cafeScores[$cafe->id])) { + $resultsData[$cafe->id] = [ + 'name' => $cafe->nama, + 'score' => $cafeScores[$cafe->id]['score'], + 'details' => $cafeScores[$cafe->id]['debug'] + ]; + } + } + + // Simpan hasil pencarian ke database jika user sudah login + if (Auth::check()) { + SearchHistory::create([ + 'user_id' => Auth::id(), + 'weights' => $weights, + 'criteria' => $criterias, + 'location' => $lokasi, + 'results' => $resultsData + ]); + } + + return view('cafe.search-fixed', compact('currentStep', 'rankedCafes', 'cafeScores')); + } + + /** + * Display search history for the authenticated user. + * + * @return \Illuminate\View\View + */ + public function history() + { + $histories = SearchHistory::where('user_id', Auth::id()) + ->orderBy('created_at', 'desc') + ->paginate(10); + + return view('cafe.search-history', compact('histories')); + } + + /** + * Display search history detail. + * + * @param int $id + * @return \Illuminate\View\View + */ + public function detail($id) + { + $history = SearchHistory::where('user_id', Auth::id()) + ->findOrFail($id); + + // Retrieve cafes from the stored results + $cafeIds = array_keys($history->results ?? []); + $cafes = Cafe::whereIn('id', $cafeIds)->get(); + + // Map cafes to include their scores from history + $rankedCafes = $cafes->map(function($cafe) use ($history) { + $cafe->smart_score = $history->results[$cafe->id]['score'] ?? 0; + $cafe->smart_details = $history->results[$cafe->id]['details'] ?? []; + return $cafe; + })->sortByDesc('smart_score'); + + $weights = $history->weights; + + return view('cafe.search-detail', compact('history', 'rankedCafes', 'weights')); + } } \ No newline at end of file diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index ef30ff4..da44daa 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -1,67 +1,69 @@ - - */ - protected $middleware = [ - // - ]; - - /** - * The application's route middleware groups. - * - * @var arrayP&!z=R_Z7ZLL@Pcl>_o%5j24IDNWi`3BRK*9+~`? ztOIq=c1?H9t9kKj?e;tE8cwBo7bTB5hn%{p$rWhIk0)=Qr*3J9l={mV{F;6w)&ZOt znqWJ^o;A43FbI6;Zs^tyjjOD_|lk8S=I|g$;azScvoQvo|o@hqjr5?z(jaQbM_s; zOg}O9X`(q*pbhfHp6;&|L&|kCULk}AffTQBb&}hzjTtKzldS~f*%nDGQp%O~jh` 0;@HFjZ+9yGXw80)Y(&?SaXlx1_z4UuOX476#O;e;a z#xgD@n)v=@rvRS(k6^ND8E~Nj=g)i96VCx^{8k8e&So`mzy{i38J2x|XZd{y#fSm| zAJyj<1x`}e%Y^
_p_#_I{>xgWm8{fu8bZPGEi|+jJ$PZvgZy$65={eV zqj<1!wcaMP;z?335L*p`p)w|U$9_y4vKog*W}cl4SyWCVUZj{fU$JbuH^UI7ROO(X zWw4>We8+Q63qf O$ka_Jduaj{w*vtV zHUi^2-WzgbaF9jmXn1Ti@y&%-XrrMf@QOaq;@C7|XgUord`&updnbKgYnbbtK04UF zmbd5S=>O!yEST$>jysBdg&tsS^NB;uiba9cmluv6122ckdTQ5fl%G?%i>>7+t_CcS z6|D%fn!hN(ec=U^0iJ7CNg`kaUAHcp24{=C&9faEhWS{E9g42IM{FE3e(Y)jS y_T%{89b@9VA8CaHLqIm8ukVz>b2GPe_ZIgs^g +oD3Olw=W{^h!D9L-l+?nhAZOUCa51~yJ3TuPO-|3eA%Ob0g)pMIHu zvvh- a8%bc5SV=xulrOU z$9ThDKCkk*k)kMXTdRMTJU T}dA5I3!U8pmj*5-GYIFDZgcDg(SnIN~&W4{lwvPODRV?%=;*;7!?3%~V%7^dt< zS?^+J%Ea;Q_*zoYc;4r0^cPi)%yR{E{nO kIl zJV^fm)FFOP@t>9W&%QsQ1aW7b&)3Rb5;R{sbXI`*m&(}^nx<_(jW|FgN4DiRuXkPM zJq}=!2KX+?1W3`-S)*+c3+5d^6ZJ2(#X|-+MUoaKP(Yg$uroIFFtC9eFV2gmjA&gy z2K3+dwvBPM>uUvF{1|p8uO?L6eJJvGTy^3G#Qe|}l{y_<9?1HfvrlxYLUy4~6ten& zk0%8Djx`8sE|;3&ACsgCWz_J7UytSsy4Bjx{?&;e_79&8n^fbr)Hm)67SNjg4VCG> z7%Dk{&|g7h)F_4 @M8$Y<>93jg*qpGC}7p=*BF*ulW(+^$Q^{d3gqd8k2T4D1TZeMWQ zZie(S#NL8NL@(c^v>%79bl1Pnv?xLbAKHW5boB)>9)W(D7g{wX1_myrt&MqHY>)*v zb_ii)edZG_aQA1JLyRY&Sx|5F>9MfzvID=7C;y2`TC6k>ET?!MpI4eXg4qp*l}eX$ zERHWN k(@R4fH*Q T=sy-f?LX}O-1?4F*f SpgcpFW%YJQNeM<`j z+w>iTohrnJ%DJPSgPVt)c~0w Lh227Jz}0|?!q5W{I_BF+@Fr7a2_owqIDd@Kq3+~aq|s0*e(7~aXRvhQmD-nU zK_9m+`CBc-#61Z835nj5-E&D|_7DG=DjDMY{u3((Ts;}YcFf_o!H=?lKs9g7f#VP) zDjnc0EBv(vP49=li+QvM*Emu^BeKoB+>-`PjrOpgRDyQCO>YI b8hgG!*!#DotaUG)=j*@U6U$=uYRz_r3wlQ;$YE~eg <1zgqWFiw+ak>1FJLO7S|d0!GcJ zhW}s?KRgbO?=O#Ra8xjfXI<+$56!+X@==1O47#@S*Ke>aLNqX=T?gk8_nN>x!6sIu z=Sw^4aogKr774%f8mulCd|hE$&lP-_XNdtj{FN13L81oA2wW1DDJrG~t0~Eo;vE z@Gad%on^8@C32SiFTG-#)n7VvyII6{R|!JTTK{&DSj-fI%WC|AN+Znm`MoZH9qV5; z7;#hrim5PF0Qkc@o&I$BM2u(%KmM3|X OkuN5WG zQRb8&m+2=mBB_YeuiL)7d}781Ps(B|V$1q|;(<%{ xHt^d5LTL%T+ zhf#}f&`o=R_L2qrr4iyhV5_#EYvl$Uj+X%eUda!ZPvjwCV2J!AfWnop?*jE|KUlRS z-Y) a%~@R%6P@zR`3Jcy!JcAXS;Rs-sPFvRd9 zKSv?zNB~k*%(>3#E;Y`VqcYT#3gTJ7_fZJvuS|#*qNv5Cc#Ff_sk>f(i{sA%^;~C4|nn z!gpoep=@=jMBsRrCHC&$OXLsEE$l2axkB*6dT+pCd^A+04q)a`U6)V2W+KZ)(`fgG zU!*2psYH4$O!=!V q8~eKErU3n$EkPom}gMY7`%arum@hS zvXvWZ1WNS>bgFaVj|5$$@+VZKIN&HZ3rbY%sR$E!1pJo!z)R^r!S3u&*Ah~KJT$HU zEi=|cv_2GjtFT~tXY}}RnWW|+X0{nO{;X 8{|2&agg&IiSoAwmV94%#G{~s2QoXOHsV+B!>&+#YFY1j?2~ZS9`I)cC*@fu={pWWL z4KPe2pg7Vug;-M5+*S<_j^ Fs7%2{G;l(4vd8emZQjow!fV_~}4j z17Su|%jozD5?*|6fKKSUg3NoS_%;1GGbxA`t+D~JH8Z)7@6Rd7d{^de!@-wM{557A zyd!-UnFuAw0fx-<_G9|qzD0T+rN94Zi5tFxs0=gJ1Mge#suraC!)_oh=s=AGrqoz# z-t`|l>dOFr`f01(-+v#nDn(^~?wSr(4QhX@0|m)&sFhat6H28+z? ySk?2nb7G=WMuvcwLw~*4Io%!xsy1?QSG*%HxO4QQ_;BuBV0~ zVENeNlrc+>ArxH_qP*7;Y&b=V87Skl5 (5$Pm(HV6Q zuR`Jam1lt*54-RR2|>qX0aMSO<9&`6DhFgPs)k4fI9RojwLkiwjuxlnxf}w0N?D9k zVo))sPkwac-1KOCzJh?uVcqJroZ*iOkX=!#etx9=5p|LJR6;v;#ufxeEr-nEdb#v) zP0DIMzoHu9=`lF1&1HUyb3);pw$+E~_7k_s0i;%*31D|9p*fso4UZvJ%Chr4Z_TY@ z>FZ^Smnab#H96#Vydla0X)SGWJc#5V10C}#Q4M;WiQhvkXI#aT?~W0^pBj?3lHgcI zwsMu_i1w7-b uz3Zt`nVimI?)ZRWG&;Oq8}@g>K^@^ zhHt`EvDT54#Jn#+@S=UlV)CdR Z#mLRzGju&FoO0y-UmAHa=g870uf)T-K zBj59f`_D^$Q%LEZq<&wKq 9#@b#+Qzt{!3#p{lMPl>c0YKBt4j;X)`M z*}Tq2?^9P-UnsRwhNx8PC;u5Xc>Ym-Zf hD{P^5l7w)qK8HF(D zxD|#}HPpS8o142nobbPn>Vxd8F53-g(4p-1m$T3ppdesuYaTf6;L~;R3HN`W;u4XJ zg#RNwFAEL+|IY*en?rA2l0|i6Rp1X*eJw;zZsA)N-S1;u@V!Mrz!}ZSwh3Y_sQA$T zc%PRS{?Q`(4+KeAv9JJtxL2S2--Og0=**zaB{1yz`bRc;)&I?K{|qGF^M9W&TjsG* zIb?iG)Bxoo) &_%{;ZzN3Eso=8`upP;`ao{JPOZLAL0qclqf z*n4@>QE=&B&vK(?2Dx`tfq<;Rg<^9Cy(`SLV};VY@|3Wls8CcosYWUMNTo!jd!Q<% zs?q @M357z@^zlB!Y<613?jf zG2Yk%DGr@WC|OIDCK1}wbv~cEzZ(UMnVyF*$rc^$V+3lK6juB`@tK?>PXQ;j@2qc^ z7;0|@IO+dky%vbf3#NK`VqIk-*qP5nCR?Y$v3bAwD#zC$bWb46(Dt&uWsZ$|M6em< zW0$CLlGx=GVhf=~ejs0Up*}m|oo5ZyfTHZ?TBr@Q`I+v>=-zWNH0`(szkt &%#Yce|S25uncOb;x{e>iS4m( zA*@j1J(o HjM~(CuOmsh;&Zv>@9B_Cj^gDO#U_Ugqa%dOlBl|5xu;ijL zD^MNA*C_;3(1~k)`VdBqJB-t}@U?JkA-KC%?-D75!qsMIVPYv&b61rq0KrlEd_Mir zVx5XfE5|O;2$9dJK?sR!A%zXmm0?X#WvFhp_lrHLF=J}pV?E9VUbquP$Xhw|PXWUb z_2D1*HFo9~kl5M3FzSSQ$p23;4Obb~8vNH0JedoA2!HgDsW$)RNpHt|E A3Glo}ots+S@+hh9Vv2`@Bs678Atp%wAkzf|IMWt5f_5%}q{2b( zArJsc E-jLarj}vp>Q1Nar=MHeQBIPN8VgEY;59?jiZTRKuSfTlP@3F-Wd6} z1kXt{WHfYKa1Cmf%;5bJ>2?1NEi2C@$ OmcJD>4X*3mt%`+oA!wrG)Z9o=V`f*=Zz&2Ywa$z^?xD6YE7kY2b zAm_th$x0A*#)|$WRHpjVNgXW9E2iFw;t6++<}AG>g7xkSk2&}c8lt~dzFoDog$lnM z&8aTp(J0Yv!5|xnn2j$XVe=oN4C6tjR%vo1`v_Vt3-1ySA|nldF+M9Vt>+RO)WMyU z=3(m2&x)cyzm{dU_(j(nA%LMAyd?8PgEoeVIM13id(SuLBE9Urve?;*rOR;;r2Jj= z-teG3$QwvfgA;nyQ3f~S!^gkgcs~(P!w&xvW2=hW3O3zQVomR9y2Hh+UlgUqv>oa% zyrakMWk*^@(8OXyOCMU}wYF{ DpnIG3TTFGv??nL|&EB)*Yfmli@H*4*UJIoC(3_N_F=#jEE2$pzH! jm<$p3B$jD!s12&zf|9AfYmqT@i-3ME{jbre`GjP+gx;5F-`DI$kb) zWubp=<*s`)wro35t~iers?pH*;ts$_lH-%V#bwjis~mx+B=TtdPM!<5Wgd?nRF+ZL z+2k!=>1wKHkcCcb1o|nCcy)MTwye5!xof)YB6dcHJ`N(3LCS}MX8C7X>j){hPYJyC z7t ?n_BX`ODdE;3nDADHqoY(qB3o%xc#fhh>-Yr9_#v9Qs7hvLim_&Nh ze#RtNe^&1bVm&6&R8OY}on8;rPF75h_T%Dr{k{HAN?2vo^1FdcV!yt{N{m_|!a3;6 zvs+s>(U5xSEeRaDG9o@pO>~#?cZ4uBH@d1bd%gL2+#PQx3w4S;w1PTs>woq5{nCfx z5Polov6?koMvOH8%*-8=MR`0ZZ10+TWFtNifM5jC-|$sj`n|aZLKlwmiMD?iqY2Z| zk%aVp_()W)L`)>_!s|ouZI;N-+jLvMUigA844t6`@I!(Y?_P3`RP8byOk>yh)#L3N zYqs+iMKuGFi66h2om7T;pZg|${@DEsL}SZHiG^aY`BF8JX JO {f Es7c}#H_}$=+?O5OR7yq zGnzhiMZbo$#bV3)MrxLs7g@Vd7wMBL0th}9M !*Bb? z5W(lBQ;0f~&0Zit3$?Oe)gVXR2L6{wnp-t$w2#VR^L@kYwFmS_*1GW1(t>L@aM8vt zH$Z PMecXJd@b5a|G{c{NLvq)jC6ClqHL9>U>uniBEShz1e18jq z+XHN&E1%axe(v-I<9s4O{2E2)qc?k8Z1!h2GHu4kBVNw1qFRo}K*(GbN|zrWD*Ecj z55f2GZ5vFTVBd?y`hmE#4a;a~)|g(>l5nF)2waK6+O64xm!ACq_)_)K18x5QZM)WM z?Ydm3h=(4JHhaZM6m{SEq7G4m6?cjrELhRwTM|h{!VqSaC;U`Jard3Mz$t3i+m%C& z>1o7V(AFCK^GJYBO72(t?bU>h;}504l?WukPttsvLkRt?CFZP!jZ0MUF}wp2iQgK8 zk31a20r!m&Z(iiH{hnzeQ6Kl{MA}nfFW0uB4^WOUCKbd8C|emd9G!|+7b;5dQCM$t z!=f@3eF&OM`uFbdID_~H$F8Z1l#Y!dT)&I_4%~S%n5=K e`s&6H8JX%K5?YD}0~D2dO5$?IoX_;|{d>xD$$yrgt6Tqc z9&&=WY-nXZv`T)C%M*u_BGqt#WJS8rH9_Y#cg<<7>q`hoZbUP&ae8n(vJXV s-~kKzE3IAD+f~h1!G(Wr;qRYX;P|2 zrFD(06Iz40?R_^O0Zbcv;=|l0N9zVyx^*PruSyrdt+onQnaY#l3O(Az RI55+b1N;F K>0+q@o%Uoa8sK{$ zzWZF0@}3cI7S<1?5AY!lfQ8IlqFayY91b1)VQQ{UG0rr(Jq~7RP%-cD8IydBWjFe! zO{Xmi@Pb&D(VvrQ^0(vbe^1mS_8y9P6Hbf$CG{;0HUl#(-p5p#v3e9mZU8Lk_c52l z!S%!jY26NF=F6Yh8E^W%MN!_3K&x?opc1F!bVGOIzs);<4fh7hK=MFL*u3`(nT#ObI-x`jh#tMurX3uk_@P4X{P-4=dQNw-NwK) z!wZ!x*T|H9QctATDG?nk!E&!~jnYaS6Ns|x!skyC`4=-9=T^=sjG!QuqW$5js#?0% zCM`nRa=>6ayxn_~mQ+DS88~)!Nhr8Nz 2JT;)d}b833|~C=BnF*R?h`h>~M`@u{b~Uq0@fADzP-?Af4l- z))Z%g44CaXa~}|Z87%1#y7Od)9WrVR{SnU+<1x}y5%87(O5%;6Y?W(RfgmQ^|9>fT zp^Oum-rSu4fpOyF8LtvzgN`mScRh Dhp6FfTnN(y=mE;~u#XB)p3EebOzl z=*S(DG%po0fw(PQc^eKn%j;U-)aP0}!~zF-<0d5oZ0W+Wsp}qbg0WGidL%L5n!pbz zpvU|O^o#?drA9COE;)wfJPuf$eDrjDIxT61lXR|ZQfB0 zU2E ?Mydm?w$kj5PW39)+w0RK8ebn&X&IZ z3xON9TVB}P!w=f8ZLS|~ H#ovL-2uWO3gbR*ch|w8!bSodsrjH zu=na-R8%@%IP-c=>U%cIl56IW?hV-tsX#>a2E{VhE zSo)_zSONN%TZ^|oDmM502JW|m@~ZAW?fsjH8{_p-Gc}6>ljpWsI(YVp^MT5M=q#bF z1L0mQ{A5SjH|cWJ4nRxkoEDixE`o~+P`=ZZ5Sa?z!mq()OR4$SP3{ic2FMQ3d`SkJ z6GV|c9(8<-#`D!=jsq;kko8Jxb_l X*@Y#y*+fM# zV0XX0FbCb|qVMhH7C!6TK<~=+=w{?b5y LE>%K|rs=*|wO}&5#0T_u{Wn=I?jz*t2K{bbmNj3_G?{mfkq|342d{ays zCYbX8rZxT%$P{4sOo1WpuD)^*K-tw+9$htxK1g}_KyYrQD@1VZ^5Jn^H>qT^4v-#* z`{#hlajR*hK~Z75$5nX-KPJK-Y-cb4#O S6IZpWh( zImaN;{TK?RJ!jpm(S2hYZ;RajaCY>R6Y8~jN5SHe7Fr4tN~zLFDMXG~rF*Na%hJ~{ z5z%1cL^&t<>zqt}y#Y^+P2{*m2f*`gJ<(u?@Z>Z?DZ~7*!&P2JBe4O1&Rr@jKk+gZ zDx;!>;&V&zh1eO|;*sA&U#;+|DMSD_<7nt>jEja|J-Ygm90gd3dPwFyTDZ^sp2(g; z9(VKWvuTDkWe~V8`Up1}(Qh0C;a0&?_Vl%C<5FF#W0nBdojFq*GAu=ZhKY!X&PWWp z$U3WrsC1BxH*pnZo?!S}Sy;L%=w^<jn{6kHo8V23 e;qIc&H55KV`{Kh`b)PGy91c5xMc|_Ll c# pOW^RL1HK#Gb( zlt9yR*>QR6x5y( snHvAEZyz*(_Eq zE5t0vAL|TG929<8F^|`mnn?l3n$Jit%$&`|d4x3p2BuG(QPX35AjX0?&G3El0ePD* z+iaB{k)jLKHP}sRh6D!8AiV@g8_)np*1G|3v3&LdRDv#6xtGspST;Z;Wwvw^-HbN- z-Omo7}f7KZk`OlZ>Cr^!eC=3ae!(ku5<5D?SK%O9nWk=o=gCD=QZ5Zt^!^Jc(S> za=XQizhrq*fN7vKsaEv{EEHwPp`1dz)^kmf0Mc+Rat7a4connq-!(0=v*5gTEI2Ph z6={`>m |dTa#oZA*(XO>buek|R8rqXc^r<$vP{@t9BV9eg=md{ctxfx% zJO$kDhcOh=+UP;cJY@4-?_X0>u@5UWf1Nko35q731j6T-`{t2&sdjKpaGSe;y8wep zIy>NVGowZsROQ(I8ZM3fG@nCco{A Ouvz1h#mC-JUC06_KDkLa88nC3_1b-BaaA-xrD zv-TUou~+cFu`@qG|2gDi6JUyW1Y*oSjq2^2;Fw^aaG6?0E{A>p(H_aW+a&0-E`kur z^3-C_vc88~U(;h05t$Bufa(9TlQuYPI=An*%@>u(v4KJC;%vLFiND>z&72eBJQug^ z0BXrJdMkpeD&J2-&U#9ZH9F7GLkvbst|&uIrb7$zd;*d6&)0{7d%Yp1Zs)HrAJC2c za$x{Yp^EsDcJ;Wo4khtT2CZ7q|I(Rf%v%PdA=kV#yv}+>P3*_DXo$sHo4V(Vk+}fg z{sFmMKK(Bb4c*i+&Mye=6apcw)x|;I6aZ8o{ST3!OV*Inyf=27RX|}f!O^UNT?Q~! z=~r8?6|uV<&EndItQM3z&*FbwZQGzq!ANIdOOsAsRK(#lHcY%!!d?xFC_{15*e_-s z-2^GQ=$8OZ{~i;5)igmH`vsRApfHF4g$mn9Srj;uYU-s8I xtXS>35L3sZjF$o zOn^0A7(T|TYgq3=!LOxewg8DQl6c>_R+^h#nSiQ#P}3SgX_7e)EEWQ@dV*m?=Z3ji zRTXi@f>(++(uv=|b!-A k6gIiFQKNb+u9BD8 zroJ6b9G;c@^LZ4(v;ieO6utts;MuOWJWx&(L7FnQ 2>5U> zU|akjF&kEDB?ifQ#{LUL2oympG1J!q3XnhVyd$FrCp~#+z0vBsQUsIb?mj1hVv~ zg&Jk!8V}Rbv;e@3yFhi4h7d;hT(HzmDi9(~p#+itdbL7p6FPB=+(Ot-s0D={E 3 YEClz=qAj)mRu!i`c*mr+yg&j zfnN)$Bpo0OqU=*PqtrxxR$UhWp6~t;Fyc1r@B Hp1S^Up zIU>_J_z*Inc8)R?FV0k8WD*(8kA*<(8~~OHFtxm Hg|2uO68HKO4=(f}&Z4!0po zWdW)8@%Ns}u8b0M3dOo!_JByR<&ZDk9#r^60xb-_Y>SkI_6}eMK{-?)-~^Q89ipqc zbLKeoe}L=pDi$607F8YjL9CMsN=6M3JNqjWB1I{hZj8Ho&RRQ~j-X{iT8TR$&wGV& z0O(L{&n#R8uP%SDUj@~6Vh?NB7zQb`$yzq)J~ZD=)HKw(a5kaL9-xteq|ZWAr{5o| zj0Y3SG-i5lwicTM+>a`a2eischwvxx#iCW!Kndf;i)U8v?m1zefJQ~oSR!4{Es98B zb3XZ}z7dhgm!O2*%>gMXP;Iz=n>efWc9a6{|gx`1*f|E?Q+QJ<)K>{^>m)PbDl35^^ z=y1NV1P&Fn$Zg&4ETi>cEJu%b=Pzyc25GE^<$%w|aj>2rAd}q8H7XpdrK$sZJjQ`% zU b3R+=|tt=?OWr0@;A}LSA z uIH`a{6yL^cL7ad6-@u>Dc z3u;YW&4#?;4Z%)W9Ij*5ysgVayW75}dvdyqVAdj~a@X$z3K~|{_B_IiY>S+WJc +$6l&~k1Z 4>-aXm%J0<1csNRGHm zYO1`>uXau{>I79YpE~E9R yH{on!g~c1WgX}eI(7$ry|0RCW05JjJa+}$hzB=V~ G - zl$}IIcE;{^e7?``damb>u8}$SIrn*=_x^evVY)Xoj$n>s002icuj2FpK*Ar9fSwk9 zta|rt!H+|3S4}+tVElaW57F$JV*`JA?4i2JLjzaahhDec?*T6_FY)^>jvjY!yWJCa zb+=2JS3C{?KhVUf7<#{%8}{+DK-cZ>Q>Wjw{whms{eJy>xQgnT)91AGbXCGdEt+^Z z4Zj?z`#6SgXlULQnXTKxTkAe(qD;-!%?BxbWJ8 P~xsuk4`I#Lgmve)7=Xi#x zUaRUUh9Bp+TOZZia<^Pf{JK8()1!+XG^Fht5$(0V$P f3-7VNuS+@wF&IX`cK`YFWT|?|fT!Ue zSrXoPIZ8*cRd`PJCqg71<{_G<=T# (lwc`#d =J>p0&bh zWn%&Z@R9aj Y76LIRY=4 zQkl`uRYQ4Xt)Fiei&E#R>;|eswfN|ONX|yqB%g%brX8`C78{3W(xdM{e7w@PIk&Oc zX`X*K=OL7rs@uf3e-h02kwY`R`FDw`bF}!?m7k3xj))EN1w%7gpu6|bGrqa`kF|i? z2-D0VvK;R8Z+{~TmZI0YW&r{F&+v!iXc z6O1qwZiJoFMs9>#TkVB~S$f&!-xaoibCfs_44HDa`z+}GXy+%IQ<-}{X92&T5lu=} zUTKa)@=renq5zMPL2oPNkIlNXt{*|vKLeYDT)daKq9m;}QrR#6H0U`Bh!vKW*18PU zm>4M^=D(k;hHIC`jP;hsm0FLPqRP17mY8Kyu3MfWO@YYbNP!Ha?HX0_uB@6OT35~M z8e;$(n(OKikhRc~oi5u{9YS2T&88obQtDedL1D%)G6EWq )+fS1(1VhW9O>`I9 _J~O~&z1MY{BAOyeu|Th!205 ess(!H0k&I=7elH20V}G^GE4ybO0Q%zUKatZpvjIVsv!^pAPUtlym3+S3 zZxY%)>jvo4|J%&(g6)PpZ-tHhXrhy)Wg+{1ha~z_sAyEQ<|Su2mWco+k|vxRNB3|$ zawRXUF*%@|F@0)Mg)lC#l+|}naO)%xgFXT}d*&Z9P@L7h25$q9MS@(IAtW;rnoR4P zq-|+}M}aw!7F&@rd*J!u*L3jOm$67E6Q(8LwK|=|A%b<2@W3<8;pS$V>ZK?@T 3eQqF9$jJth`tZGZ--Y_o577T7Xd-D zx4ePWiKU05b!35uvhzs#S2%;_O{*KOXgIy$aeWLmzS?Fs^We)AG&8r0Wyz!(a~*4t z2!%^9LopAysTgUi{pjc$;AQj+r9=cPpF zOoChIM!v7pEDiIaWXaG#a<1-;W3A=m=j*=7pK9|yOi1_k*mnHlEtFaFl2q6^};nk1lg^ZRMDQtp*ca4i8{`szuNTf3S)jl^MT(WDtw&G7a^Y>uy zjcJu%YD+km);~dAPb1F05PB$5YYptn;kGesO)f@{T|9LblF4WF8t;PecNJ&X9vUrN zahuY?t=b1^Pqd^`UK`ZT`NS;B19fH{&ss8zA72puU}nTk#2EVz%8AhI>?>X-&9U-~ zAmlW@HL@MC%T0KNKg^;>FAF-0tVVTZE=#SOQ(ozRy$djzrKO4&; LO%oYE+phSO$~}94erXA zG(R~+M_|vn=IO^p(kOsm&$uDwvh%Xi9iYK`k;QH|B5ozlzQ~OdUR0ql3JRL_CH|0l z-nrU0<4K7=KZbB>ce|CUEVFGe(1GAWpQpWbJ@5ica&VL;VAik&Ia`m$*lwC;2VoGi zh)cKj8z{y590j=B%YoOcLl=H(G$O`?SzFgDk;|tkj2Y@3NOuWBP5KONw?s6Z<>+5N z=u)O95B(RFCo4s2Z-N5B8Qm65Am2DnKwcY-*QEOYtQKsM1?L1(=02LF83*ns3v>Aa zs_KK6;d@OgPR-tr?lOJ2Z1=KM>}CSDJ+ U=~Wl zZtN71b1lm%i$o<&o@%+NOt5}GA(QiZ*Px<7*^#F#*4xpMyO&+(XrmA4M0Y#9!S(z( z =I451>p{i>!YvPfBu+ep@qOD%p{KsQWiG`Wn+X0G zZwoPzHWwPV47h534SHh+N>YQ^ZM*j)?;!k#`&k6`=9}l(;J2rybP@;fUSH+E=$FwE zIy;xYn9R!qJM(Pav){$pr-Of{&!+~7?!~=;QHS&W#7V_l(HI-wETwhshxl)%p(bI= za`P|TC`HIcB!yW-ZZdD9f6lG_gl(3~0yM7oFxDqa8oaeG$j=QR1uC*DnnW+ZFLyAD zN=-bz8-^#wwJ-vFYo7+F{ahWarFDbGcw=fxB9W`sOQCzgoGuO|CmydUJ(p73wp8bU ztq3wc{%%LExH?NO J^vF6d#Dp1UYbivRh9O@a>!EOK4t(J{py)zW1Mn998ln&|7)>H*L9$#Z(b=Oy3L{P z&aJL~dAE(Ggx?X~M2*kB)-MVj`%LCxa}O`NNA~6Hn49G(y^$WGy}*<7M)S;Gjl)f8 z)hCs_SwD`xxoO(_!4bMFMoO%z$X`paq}{A+r8q4C^5DE>ibl1f3PUUE6lSw}Z-d#3 zEPk{s8_{ZL$w`LJ*Ictz!}9NRwLHPVhpN)fZ&NOGZuxkI5VDd{t%$;q$8$V=fkkcR zZb-@gxj+XB2d2PMPNVV$<9ei7D4S|7FKSFah58r P!YndtTYL=2XNgZy0$}-qu ~HL3(Ku zs_q8;?~i$FYxkgwZm4u=t$Z a=V8^`?}R#P zs$NuG)zx%D{O4r17Zx?j^k2?y>9o(+7rm3%PzinS{L?*ea$5%E6uYD^vWx}k%kuKC zqOiMv*0ud_%pVkXs_By|Q|gK1^Qq>cx=W{D{F_F&i@|JxA>^4LgN&!vKolotTO4S3 zEL)GYrQTE@ZHx dq)u 2uwAr2YM$X8$(!ybeS&r2I#UiYeUr%QEP3 z&;MsT;;>1A?o8|^$?L=k1vQ`Z_l4;Q_g$AgfPB7!@WVemYZ9-H*9S!5>Cx>em#K#% zBqUJhU7;{fI?D-B?^D8m&<|e#+Y)P{sU`Di%=){POXJ(#)xu)-k49&(Q(Ls`V=#wf zyBO3f(l0f1q15%9q4A-^KQ4eHM{+qNo+= ^%{Erwus4hBDM|C1kDG%aN+#>fleeZ5 x?5 zoN6({|A__j= |R{)+;??cp<6(KaOp z=(47=E=#PRfwm`_E!VYa2YSf@Z1S3Ro_j~hEbS~CmIom!TOp`R;FP(jFxF5!b)a M7H`DPS3(@#1$HB`BgWNLY2 znp=c%hyQU>Fx;H{1kwNT!5Ff@wL_~Mpt5@Nt|D{jO*Qq=Xx@)7OJ~GXI!PzJFT6i+ zWb7avkY8Id`vcRl-4S`43xT^fmV-mZKlJL*wMIe(L+jV8r@1NgB0OO>%|{4I->QRE zg|Z9xzs${V&wpSvy^8fhI7L+DS){JvKQYQ1^u_q->Me)0Q3 zRcPYHEh3A;+Sye}xQF0rLtY{)c?NW@8J {Vg$I?ZvRZ7&1Xf}j-Le#K2`N_a5x!TD>j+_#nZLj@+#IMP?CP@1wJ1y8RY zlyo&~o^H_unrLR-J%7{f11mBQrMbg^75}rZ@NqfYNPo;6?kBw2V5YL*q;Q#ZNBfXQ z{z(Ej48or0zC?Q$z?I{LZp F8_t@zy6jt!3KQB#Jg;Sc_Q5OF#p`*yfat+4^E|%9q|88}; z12;rUrMBZmqklJc+vI~Xa=+(pq6uf=VM(kl$+d6OygCB#+W(4BZW%vT3=T0{D4aik z-Z@#N=aTHlNdeGMHlKFB_vl3H>idF}VH;rY>|9_N!(^r?6UVr=grd-fOSA9R<53|= zSP2bt^N=Qwq{G11u3ZAX$Q3c2_2Po1LB)bRg+9E(!F1moNJ7nah z95Y2~WfagsvRfx8w3xapMTZxJ*I#nrfeFk%(z*lA{_)Vizx;SSNgN$SFZ=BKud^fO zz8#ujca-kw(GgxvSdN_zxc|@dAJ0#pR6Gp}cy?snQ>}Oq8buK~IVr&ILh1*56zS== zWW4U74$ElaFF4nd8~pOm2t}G^k|9<%I|_jM2{5$Dt-NdCFFfjW)+Dw_vn|TZ%Vn?R z&Sg_QTEamhPcn~KvM}80TiU-fjD0AKe}V#2A*!RM`S9su;7ww VoALkBcv3bTXOU z@HmaT>D{|_+Ucp;V< @#H%XNsXzB9!XtWua5ecybKY^ACtR<~ ziPq6=L6DZzN6bl61~hVvFvI3IBNErmp_^C=q_VqjqECR?*9umru)gW>Ix)ZdW53On zMDm-pY%N#$1BYRiOf`bg8M9wq=lYfzq=E3NWr&%ydhDH?hKAH^Gd+GDZ|AH6g`6+m z%k!#Y)%+-|v&ZZu;S_!1J%cBQHvXmt6^_aiw4RM^o;Y5~ZB10?v;l5@Y->+x*;vAF zQ)n{YauqYCy_o!1XdPWM^XD xwt8ibAHG zf$m3FH2;4Ca8+nR&^OrgMxTk?&+-!!l|YdKw>cW*`Pv1|o{^{pCy4Q vw=X`^w+Sj;u%)VQiz3r8Ud6d}A;iLYJ2EPV1> z;=wx_mVJV*G!+!JH@lM+!6m0zdpfr(yk^AscgXrG{j@{YZw=WVk)M9;tSA@1_YFmu z{!3{L-p_$u6y5IJu6|+mkdHU!C(6Zbo})+=uv?ig6=W~R9rxs5a_$&;=q#Q^AU-;p zl3vpiaPCwOP?mr17_L3Do0dYGk+~?PrG9e2u>bqgv+{FyV3&;Be=GY=UL_;HJO)bF zzOPrRAlUdaxOC|q86_JargNi=6}i~Uj(!~uF_Q_FS~szN8k02;9q~5;7#6KB;Y2fs zhH(g?yrhB?!uk$>&X>+IO}ys?G|O#2l;Z8<%o-(c2!Es@U4WkVf*@z%R{DD%UG0*B z;=}X&gaL=tSN2kOtnz&hfQ(_4hwq*~75iq@!3P~;OJY~mcg3n2U#Cnaiys_>i(jl5 z3wK%xLL s54PtW>8Dg{#n(8cgF&?l`-1sPvue2aS$;1w z39R+ft I5G~qk7i4@Tlc3w;k+5AVDocw^sME~|I(oTFK9C-KX|93- zr^~(dvl(&D5gc@upX_?wIr$&xc37~h7<%n5le6Z(6yjHc+Xs>SZHE$qvdYDMRI~9k z>gE~u+rt2_BRpJToqMK~xqC-|$X0&&6f=8JSCW3rS(X4JncD&)r}TXOxckrNps Ns$4H zu+rhdF_>_shF{*aSAN)sAaRcW3BZWcaheI~UJ9FbV_-wa69fA2)Qgb_=xGpHk1l+O zqh_ZG4sxK}V({pmJBCs9^H%QvRga9_2FnS!Sg%&^_ESZAD(dX6yo!9-QaoMH9h!mT zuFlUT)+Dbnf=<8r{I|P@W2Ba*w8=-<#4?tzK;?StW|QUAx}9~d&Yjc8%*um^nUpKn z{ufmmnPxA$Xg1jtAWZY7G+}4{`)J>3zUyqvg(tlaWEFg}vj%*G+t8?74fQsiI{`EN zHLJsuW7^`v+`Mg7?j679=P$tChvf-rFLG7YkT{^^n-k*&rvK>5$p4b8>XzXgMvgVD zUWbYFxE@dY_%||(;V*6pud^^oS|n~xmCURGj!|@4$?rxKF7V`5=hv^DFpU<`K3FN^ zV@Gwcy}IAIYH{m_fVokxND#+%{<&PUZw;O%F3aMWII8>Buu2(g-lMN~M$Mgo8=Gta z_A(CrZb9K!RS`}!?f{rzgOOOJ>`qW`aZ9y-qr@5LY{4xxfg~bEmD$Ty#ODf_ii=IC zqr)acVT$yTPY=p-Y<&$v5lH3_Un2CnpcWi|@RfuaI-8&)H!x|C7!>+nrf)@;iI; z|DOx+@rJb k0>ok?IEDt~UzUePo*e$8Ye=sqr; ze+Fl6-V|f)-v`di8Thk;5vw?@#@w*q%+%rUd8b~}j-nbT;cUxk`RnN=ohKVMa-h;p z_f4!avp0pxT=J|i)zJOuWIyDth*Pp3R*98>Fz$Fu(d8{JV05F_P%F BP;A>s0kF6&-052_cXyqBE*A{zgG_6t9(&m zu32eMMN=AB)$)s6YSc#&Fh$_1o-S?N!pc(>HXYBhytm2->b3#RhKAFW7vp}p`#z_- zx}@Ji{}P$}5l{a!=e^?~KkU+ `&_gaaOWG zjTruodWXsS`0lvOW$WMGN!)^WgN%QiHW;N@*y2FHu-AHw7AY!Y6et x$)l&wk$V!@6LAk~KbqFTm z;tkiv3wApNtfv5$rdFsYwwhJ(Oj>M9V1dFyF Ogh>c>56hI zFt9JEXkyjQ-PdnY$iGHJs_-6zpsB+P=jZp_r~~XQ=;#UgM5;I!-@>3{vZ`qRz<}vX zkGg&IY83=#R5Id9!<+&F$OzDX*7q5nS#OL!rC@6QIbN)JX+fg}26!`fmwDR4oiJW) z-aJ`a*$)g@#eh<0M!j3f@BHVoysr%&%~!McaWJoVGh6lX2q_oN8u)u73IVZO!P+zO zJrleq>$elCv-(cJareXi^c`rHBu!G}UN25>O0fhDbX_J+nVDr`>J#l9_K!>OaATdF z+40|UC9n^q2o4nP4D8XzHVlAf2#qb{7EJik>>e=E{V~M2^Dq$-0rFhh>cTX(j*7jM z2t5qKIeNJ=m|4a&3n`<=T(vB1hgtDUHrwhcQG@KT6R*m~A!tv_JA&pvAqG+U94(sE zC?NyoUmusgoT0qRx$iW0{mg59W%d>?& RxiW0B?zizDi zX@WBpkHGZ3@0Z#28>$|^Onr%<9M53AAQ2?G8~&5&-u8AW`9dcD$gkrGiQ!DZTZn*x z@V>t8pM=vqUDT@mRN;U&!zISV+!^glUbV*t%xrnstjyaXsB-OI)`SvRpo1Wwk7U&5 znP!#z9{DFZG*YT-@56rgb$XZV3NC(C75yN_p_65maP`I-SiT32kkxUSUpbv0jzP%( zNzM} +4%VVMOhM$u2S$N#0vnsL z& `!j8QCePK7DI4LCjAPTxJ07J{#Xx9z0d(}^gj%mRv4`1g zuVHq&6ITNMV=PpQ@tK)b+|=>iPC@ka-*TBpgSM()LKn}lMyZs=n*$;;9T`1+Xi-tj z%Z|0R&(mn<%%2IFx51Y$lOKrc9W>N)--w2LWbfvd3gncz2xh2p4 ;Md6RD9AW!#A2 Etlx5z(!9g*3@lSk%rs2Mna~M7!RlsXyX$OsGq_ePI-0(W zCaj#NBZd?6wr+IvgmVu$^s9wnW{q?gz#3+lzR^}V65WlNYj`fq5;F4q!bZ-Y?i+>| zU#HvCt~0IWAp?=f91w}D$Gi#M?-a$^?Dwa22g}em3p7uApak? 3{RLb%6EG#VS zQOF;W0Jh3-IGmxO5hJPBgGeN1;L?p4Ve9sPBd~Y4!ND)DjLVNbZVQ7ysWVRkEDpzZ zc?fy%q4Iw|3h!FN|Kgga!D9YN_(V>QBBXrba6K>opW$-V0SwsA>R>n`{OY>vLEZ)@ z5x{(t1Gf2U2hVxaT>K||-kC=gKCCYFk2oPf!3YWpitr+M-N)Pjzo1|^E1K6G2i1p2 z6xL_!jSPb7KqLzO5unR3AhZ9snQiVIn*z~;>4>;y9-{kyk1+?@K%WmEa<}=!0xLtf z@Y%d$bZwRg@2NMT@B%-kaE!wI-(qXKhngkYpPPg-v#{tb$-0Fm4>&mu$UixFZ#!NO z-#0xySuCI)!r@SkAu^Bw@CxPBh2W|z6c7~5_+&6647z^qoBzCPrgpFof#};n_B;@d zLLIz6h}-4cqB4OXf!^Z>t6}tbkkAK*2fQL5EcZaz6A?9s{GVvhkvk~(WDbT7z=?%* z_hS&Cuo-c%4NTuK8e9O2V>>5plhka!(VeGH<0A!wr-*0aclx~W9c$;&jU#VlNA^h$I{Y+eA|#FIeF^D`bO0Ard1o^8_VfJm{m6Hul!zO z_$}8n!v+l8N?_>77_RhG#-~?w5VlA%TuON3Eu)jU?TtG3DE76D`Q7WoLJ?zUK?-K< zA-MO|6t|iA@ucL?3e6ZvoPe^P3x@USi*BYdnb{fFyB^Ei|8a!)G4-h6@p8I76-b+U zTxgLhkn)D1(c?n^6`_}to3=1k#CHrZZVKwr?^@FI=gcrk2G_|TbSeCpN>uP<7voNF z_3uN;gn&S%$C=1s?bQWCoRIXDuB?+VyLH7u^cBM(brq6S-zbZu6*RIx9YQ _r3rDVSOM4S$mt+i+{KMtHdjc zj*^5(!$?D1y*$i;HBvSf&b-L28`BQVS=`I3uUD$crrv-iE?WML$DC)TGVOrKrZv(R zm;^TI8r*)1UAT;$7k4?T{w#bf0)o6Rp^$Qtpfbc9%k!BCEJg3URU+80&68M)iqZ37 z1p%V^ht<2pL*7U#^?4j~t-JQHJ92aBA$6*L!W7WvJLVW(el)z7gTUfow+GQ8vAew7 zY}Y$1W0Js(?)l=@#phnA$P1!E(`%2$DMar4dP@tKw)!W2>*9n7IyT>jyHfu{R-OvC zobmFCg1N{vVgr1 OPk!#8UOnj_x>f%eAUgzml+hr31qDY5?fogQ9H{> zIs{U;s(ehv@Y8Sd#fG`oyK2;>YF3 P5-b*~DSB#x&%I_Ji}{;3&&dQiz(5v~=2sgFB0UquuR$Ou}25L!QKxdCEN ztU>0*Ev6 Zit}dg#@3K%)z8$H ztH%99)dPKKg1e)bn61*&HZ&8UiRS`WGny#OdNxp>XguZ zKkPO{#G_sKc 4%<;?|r~BQxBdsUf8t|NH&Hr C6w z9v#q-1Gpiz$Vuy#lY3#GZa*xu`_Acen&3m>D0wUB$Op*A--hW4GG1XKF<#Fhq+o8x z+_S8Gcx|XKD9XeuI`Q$(*F$ fH;xGxT7Nu-8@Q{xdx#3T3h? z;%3-pv+Z8-OMH?GfS}QWs?blipThoT9t?6wp|qVKiG7TDo7XQkJsl6B>=laC58WB! z#PD5IT@5Vy?ANY%c~MoT-jaC?EiQ02Sax=`x^up|n#Bv;=JFzhukXFmq$;9nKlAAw z8sNBgs<#rcMFXj%;oH#MDyDYEmMh@(hgh%V@TYS9ag_w9eyqipg_K4xd&!YBMpd3w z$~CZ6_oOoU8ByZd{QP`*B!9-vvC-bgl5C^p^Jy?;kci(dZRZ7F`+Wjv&R69LV$g{7 zTb_H$kQ*kv|K{Q@AgC`K);N880jK8l)6V}~04LUZ^c-7*wGnGbaLazH4ezTDop90< zCuW8Vx^>LFbX9gaV6<-E9cSl8ft(g8(S`K$%#DvH)mq=fNpsxVYD>D1T*dQgNS1TH z`^q0aAwZm@GU~(J-=I%wkE^%`QcCCfGd>-IYywvLN@Q%7TtmK}dh=|7J!pZ9cB9p& zaZ-AV_l<2oZU?}0D`!#Ku+#4l#%l8I@0PWrHS?YMzga{FkpMfk z1AF!R9oHE;UrqBzDD0z+4AxU}*TpEqjq|KgL-b&0$%VRze#l_U%dW31Ti6NL- zz54)P7gp|wBUHViaJYg4s{^&_ZY{Z#HkpEx#Fj|RC`wglky3}UoP6&y nwHuR%MeSNuSbw>P#DcFFRcaK+#WDu0_^@%IwXo#XhNGz{!yYZ^Ioq!;ij|o zF_DDFE2$?79)W>|1rz`JU(GLbTk-b{luIM|1$|vQtB%()UNzaZ5o>;MFs5sJq5N{n zb>YGTr%}-fhrE5O5DIpFRtMj_;Ws^7uIR6g!k&3N>Ix1+n7~G*-WBHMfuE>P7LRd_ zmaZ^C9N~B+AfXnArNo27HDkWzcUih7tKb?hFfC|| AR@0$$&e3~ zImuj~`&$$6-v9t6CiDExg8WsUH0hr_@`_Ntz4|j@Gre5+6@VK# iZ*kZ6K;^wlf**SUE=Zwxt84Q O#!&MF0~CZA3{{FH6oMjY%)dTb$u{ zwv54ollJ!~#uS2s6?og4OW-g-;HR=L-Xh&HsOpgqBqFajn%B`KSNqh2JC4v24oJbk zS`m>Y*Wb`2A4CkgW;x~S{lS?}XLK;`KPU+R`uf-XQ+i5xodZG ^bnLL}hi4FFh?ug<#j_FcMxn5gofp zz_JeAOUB|Fg~AmIPS~p$XS{b5Pi5#>0D#mgXN+udJXMgq0qZS$)At^DmA3l$ zg%F6$9Yj=`CZ^>&3$r9sZ#ZNo2N#RNlIR!fAp7}YeL@ly0f3)|k~MMad*1U!Selh_ zUnr>MRLoS+_3PiiBM@&Mjkt9u$CnPIc<^#96YvO@kk5;YmfLJ?OotHyB)*cGg_^g_ z?cy!ajfpH*sfs1t)Y>j!APOtecz0I^t{aFAajXR$0SBZ$gu5#QEJgW0HFV(0kT)9e z2n6u7g``Q-oDfbY0LCJ=hA_ PVQRd3$LyucA(TZNfxnC@;iAyyRb20 Tq=ry4)GTs9JMZEYJ=D~ z;GdRFMSpb7ZE+_faBSkme}`)EYBcwB<)|Tdlx{;5zIAx`E@s#1Lh8#8j06*?lisqF zJ5uB$F>jZ1@Az-gg)0JpO*CFVW_2$wXDBP{<$LI^gcUDM_nXP!!ho-ZPJ`jxwAdCn z4-+qR%)a^D^z)0ORI)kLT%!F?TFMh)y%`OdriA-fd-lNoZ%g4yA ;Ud>Q=by?MVCE$WI z3J{#CryIbK^V&{K#xDqh;l{_+DokW>L1Udx)zq4sI HzK472}9 zBimRlfcfGv!tj})+>{Z#;+o$?5k}3VaY1dl~D|2921 zb-#&LkNgu_seT6qUjja46B@yV(OrJ;u(412sR6lRGdX^UQBjO$OtQxOXYw&DmX-hw zuMDenXIQU|&E2K8T*_q_dQqdnSoxcT0yy<`0lqgdZkTuh#4!MFDJJhA3DB=;nUr@J z)_y;5IqeTtwduPbrLWo>dwLHW7l<-qy7y=P$yvu`qdCatkdGjoQ_+;RY(@#!&x)=@ zDIh(vo4^r3f(*U|^wv>IB&(;pWpBJBY`6$}&%W-J0FbDq_o+Tro4V5@KcO24FI$p0 zsg{7ZWM0?^qs&Z~%B+_ro3|xEA+k!JE~N5Oh$qo5poQ_@j)He8;S-Bu&B09p%3(oF zB)qEd9=x#`*2^ Xuyw`i#Zf^fHi~8Vy-BcmM zWd1d+805Z0(!9^b!xEb{uSm`aS&{)?5gqfZ;L}=RKqp+tfYaQIIi}9rec2ka0Mmbu zdAnW##(QAQjP9JR?dX7;k@jcEA~vgx6+~1RIbZd6wS^6v>JXyS7nzDwK|$k_Budcv zqfO1Q 31& zuvcBml|P_Ux3yRE57-RrXJqyDOP2aRT7V!##;0%@zWqyj(~sPG1JV!QpPD=Lb8jyn z-_k>~#YG4{xI*?N29m9`Or8^WN^Toy)WD7$`;EuFOW6?%7O27 6#g+w6$V@&-#t3t0NdeWi*%@y zq@~{l0QulVlm0u>l?T6>)>_wv%-~h2y*{bz&X6`&054OVHA2pvrM8d2XIRsZ!ve;O z5wT^qi17?DoL1X2=^t8Rh2^wa4P=mAGkkCaVgT{1S$WG}FZ<3Pt6($@Bc-C<-X-Q1 z*XO-{dWryf<60(i1cp3^<9W7OU-tl`1x}z$>}J2klAO_wg6f79pZIbdq(x5`h~5-o zvqNy_X5MpLD^o5 gH!TXZqg`t0 {fvDVcx zU%$XeiT*+V&o(`=b?G5gKyHGaX(bN(lHSU)xNv;^=i|HFNFp*%Jh2)CrJ@Gxhgs3c zO{{H>OqpGFh$r+raF0pv9glY+Aw&5ZjbX&tr`feN1IQn)G;3z=Nr$*kYj_lDgZraV znXut+FWA{IXPLPBPPK+%EN$!CzoeZvLx<8vs!yx>&o|xST!1xLC2ZSQSI1BCzO|YQ z;P=kMUwdNnr8W7YlJ~xHz8YE3FdHFm @#iF|99(yl^V;LU$`_am&KS#SSS!d4C^+z_8qOXN zoe_JS-Mt6j%)1wOFCSzU`D)AQHt@o@{Xk#Oyw9T(1T#u}i{2cEv6ZsYz~f{*Mlbh& zcW)#RS4}E$k%E-XVxf7Szq6$`S`%Iz2ZY`2FZ_>=Xf1Yeuo~CM)#49oM+kiw_MI9$ ze%GytjgT-?kuI|J!ilJ}X?|6c8|9Xh`1oy|5rxu@kZZ3#opD2tt|&kM{zu3^&mWb4 zDm(|#mjL(i9W#OuJl!~~(rZns#r@$J*UYW6KQ&JoGyu00{M9FC4IA_#+q~l1_AX}J z(K^Lz9I-qgB;X!~zZRQ2v-UH6WCeJW#Wp#%dI9J7v<0Mq`BS3lj}k2|3YAv&%kXps z#}RMx$2ci7P2ID!*i7>6_G-5ZWVP5tW!9hpU_LNT*=5c}=~raG9RH&pRn&VInXa2O zGoybT5L><}tfV3r9bz}OJ?J|&1nq7d#xHzeJRAE$fp$z+a{KGk_hgwlB=k`6#G^zf z^^Ura|MwbT1|jAEKC<3%M7%*1?tDAz){3j1HYW3gu!V7&3MN*wlFaiJW^NDiXzcqq zeW)Cif70@6Pb_XjgdZ}iyH%E_u2*k6li5RVem4h yu_$2YuLk&Yy(@mc_7<#q;SXnt;~6X7MjAS6dq-)bB8W#l`w^ zbCRzOT{yB%7cvlsJm7s6y%<@D^}Sc0y36cT5-C*XwFTuObntfp(myN87F9`RJzW@u zcgGILKu48Uzquh+FE0~l+=IzTYD%C!Tll*dfv49?hrn^lJiQWn%n)7xj6Ia&VQcF> zeVHuK#F`6Z!o;^`W$nHyMr;V4;c_@~Bc{lEA2!{B;r+zT@7*u7e3X _Giayq#GchOooC;S@XOTMqY zN?vzF>EDt462`L?SGj4t-z7f2OEo0-sqqiyo7=016M9)ZMs=&|Z`TW0;ipIHd_Bl| zxOR3(H!(Ioqdb=5&qwOjt>(03xQtec)uvkA9= i-N?2;p~SB9 zXVSdT{S?D`xQx5Wp#NDfR`z*$2{ 7oHLEwsd6Y!#u37X-T|z=ZMJX%S)wPYw6}T zsn5qft=XnrN0dJJTh4HuwWKS5=qG(VB?X^VmV4h=*nW6DKkd52T+CU{(@8b)K40Rht#WeeA~q8)qUzqOhO%Lf5TXWF z-W4_n{!BOj4Y-Q9Jbf?yr7|7K^bF~YnUE|hcw79vs%2Q5F7oj3NjjUp+osaVkHjBd z73cvQTt}QO?H ?=D>aqL#W67m0^|5g=y2y>M?Nz59Z`?CaOn=I&ub{f!c6N2cME z24|wS`Rr(Y_r>6n-@yjWnwX`v%#O<{Tamux)0JrT9dGZnwPPl^*@xrP(F`0NosnHz zyTXy;?kiK+doyPe^^5Ov+Vx&mKFp(^a|tM^mt; 67BF) zcmA=_cI1Tgvk=A?b^U#PmXf9yd}s$Q4ki450RRmD^XnmB<-@SQ9ZvUL6S|`qXaMYk zhL*0saa=!GD1Ms zBZ`Fvz&@&P>G+;u*M{zpU>xja+@FZ>l~sv(&+B~zVI-4B)KQf<$+0gQwhZVB-6X*{ z@ZC&0bwlWoVxj@CACfJde+gV4x j3PzMesKCOG!T25bz?- z3!d;7BJwZlI)V?YJGlMGghj`>*%IJ5;8^sQJ^5r41{V&eQxAm>DOMT)2PoOx^+%ZB zgHA=tJ}}touog(U(-TG36FaGFx@RM$d;4LcwyFE@M3nC`qboM=G^Y#b29lxUq^OL7 zpO;DZJ`p;km}vk^fn;;na?F2 -P1uF560Y~A|$VCcY4 z5H&U~YW9l!p?TvFVms7~Lmdt`ybEk0Fe`MV6ul9Lujs`-2TlWEiqy9*JjFcvIXZ1A z(`y{q47LU62hU=j!7XF%8KNkYk3B;o%977#6N&s}zGBV@K;^p=xlemDVlsI|T|94Y z1Ync7TF*~bkf^Ayh~uQ2B{567dlE?Eo^-PYb6PRx18wL`m`tyXd34tB;JP)TLrQ@L zz!a)$TXbv`$BzRop<6^gMGkm>4Ai0PSfua`cp^S4yJh{$p+icA2EY_MXnw~b@uaQ5 zI3{$9h)8MhwQh-J*@1N%Lzk2it^O&teRJ=YM4nTCCquW0h!i`6XMOg>NjmsXy+r7M zQuOY-+b70G7C%ruccdBPxX?W!BE`t?dxq2VE*Q(M)6+Jmfd;^|*$|KI-o9e?+{UaK zzlT0<5fRy2{@zP{QhyEuap;&*rGwH8XlUs?-{D?hcIXxnkpee?aY<$%^`p=srA`B2 zhBO@9^*N9HQJ^t&i-<^p$~bmTHr=x_bVxbS0GL6wP2KGk6}czeSs1!SM5MsH9YucD z_Ws_U&>`hQcLB_xo!i#-RpcvA#&|w-i-<_Ua8^%xZmACbGfNi%&amCPx9%PrS$uDG z%}5n+Qs^ELk)Yh~%rsVBF!6S3XXuo2qmKXrO15@hh;t_}Cv=O5$P61Z=knpf)ZL*& z%9RE{VD*PAJjU|T!@v=tTSP==$UCud?v8=f3!y{GnXUl{EW2g>%Qlh!4926OTSP== zz?1Q;S;y$$AD*rOC;+3Q!{cKk+aIW|X&ePkLuY10r1Us&eI}i{WPHcg(a &sL81gD+d4l_%w52u&@CcT?7Yvh%d+XzbD>LQYUvC>2^!nAebd;;f;+2g zCUyg-pnqo(*>{t`jhUMGYh&-M?+YCxQ%`3AN?fwJYca-M;3tG`5s_kHFU65vx 8(?OsN)wgtg!SGwr 9Rd7@UpXIw`VW z44!r3n=|S4PoOvS$rRH7kZD!d+ jZum{R~Q( zGk7t^Z`J4fGSl?SSAwZ+TC^~ch%W&yLAMb~dD(&c1CY2aGq~>c&@ob;Gyo*H`Sa&j zkL2f^XKvpBPDMYLQkr6em2UiNUDfzQ8#Zhh3mqioO}}WRpfnuPKHu|+3xUgkBSQCx zOo7e7kK;Ih*N&}yZ-$N$d7lP=6sqW;=8n^1i!K8HDlji}kjOsRY533b&V4%ud!IwE z;*|oV0U!l$!GZ;|cZ|+H1M~BNbAdUbn?ydwIQY{J4_5i?hxMdBDRdeDQdH^=TbxXc zkDnVye9hdx2vme_lCm}l{FgaDjjP8W(F=K`m}me<(W*OaandHnziilYU>Q0yATwj! z%~lcniX6H7b ZGITNEkbd!`2CyVi)xIJo 2F&DD|4ZvL}rQxfRwf7=A-B47(3A|Iw>|jZ|)}pRiV3NTI38~!B~y+ zKlxd+p4suvYnjkdBB5vih +mboE^CL^m{A9yU08RiJLU+l2*$(`|@n3+gs;St$ zX7lFFqoJcj3WWxM$S1SLw$6@R>?as@g24$6i-B38!{kFI3|{j+2mFdev}R~aPjBcb zkwT^cAhOR+t!QZ3+8L8uV%SmO-C!pI2Zb&x6T5I;1z+pf8 t;>7pqJ~2>>CXL zktx#H*xBX@KPr!P8-A2W(GlkUJ3v$Dwj#CDV7 X1Ao0d<`i*ty+4>>}Oank< znl(0dw&g10c04-L%-eBmH#pLrBlW-{G6rus^x^cm@ur#gB_ tvCaQ4??sNi#LO9!V{aK zT>hP*q13L>p(5o<13;t*)HEM?@GPrpiiw+u;(B6BM&|W+RF8SRyVZkN z7b?yLs)5-b`I2+^_3e)1z`Gy$`%z#F*ljQY>~JTCu>)+>%(J* g9AzV- oZ4*pv9ZC0Xo4)Uch)jk51KlP!o@x+vEC2ui07*qoM6N<$f-4y4?EnA( literal 0 HcmV?d00001 diff --git a/public/assets/icons/User Logo.svg b/public/assets/icons/User Logo.svg new file mode 100644 index 0000000..1d4dde0 --- /dev/null +++ b/public/assets/icons/User Logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/images/About Us.png b/public/assets/images/About Us.png new file mode 100644 index 0000000000000000000000000000000000000000..e95b8bd5b18770da79c5b6d5b4560b11375ac7ee GIT binary patch literal 1973397 zcmeF4byOTnx9A56F2UX1-66PJaF?0E24@DBkU(&Ehu}^E!3hKs+#P}?1a}XCAa8Qc zx!;v{*Lt$v`!Br~bXWK8-BrK6cTaU!?U|_OTFTfM K^Fi(hHoMR&=BFP z)8G%Y00f_QFi_6;_uA3R1q9zs4PV=U`Q3p4JiybJ06-N$G{*z!uN?q@2*E#6%706_ z{AVdIsH^yINpWGIfE~yN4B`?N7U1U+1lrkhiHX?pbBPEEf&{=~Kw(}{KKQi|*8fq* z_P079?0>o_HxLH1g?d8VTw%XYc=Df40J%Z`n-jo)NppMi!%qOrg5g4i_Q1b)h7{ZV zz2*G7n%rQhEzr}}#s%hP=jj84+RED7x_W-JaRs{A+9^NR=Mr>#^ss^0Lp*`bdTw4& zu&utIyREO1gQusvI1dlln;YWp3FdZ#+Vi;D`oO?$HnuRHf8=3*$$P@(c|4&&h^wuQ zth2ov6yoXN0s(8<+CYFs|5U~G@2a@Ddc)iK-&e*_+uhbxR+U!vx#}alP1XNEVEv2Q ze+$6@2y@`J0eS*|GYI(2pw!XU7wqf>`%UsykuA&<2=yeTvvam}wfA%&6H)xD)vmua z+tIoJT_JXG5iro%-VWvf =gH-dZRYh#cq1Fn2DwgB9zGA4J@>>kWVFW&2<0{g-q6qgEz{OL3O@ zbHsDUtk1GLxE?~`qQ)&=NRumo&%7nH=(!FyU~x}~r?V{t;sUg{<#u +4z#W5s?6;YxUz?!z6d^E^(uJ5HiSULX3Gjv>QoKPzL_kC&bgg48 zKJIJXWEZ}r+Lk4%0})e5@M5E&vxlpMUt=R6U_Alws_~KYlA<6PAR}Sn8QFs5fBTUh z+=>uadwE$tY+lUY$1t&wHEdmBd{23Ceit#YQ1zi+w(bxxA0{vQ?>#64hz5GR)Fjya zLcDx@0{pxJ!h*u4@S^a)ibAHmhW|;N4;NmSzqFzJ9hv{89uo`oA3x(`Df`mo! z&jo3J*MnAGLzNcdO8bu#uL&;!3M#x&<5GwSC}@9eAR(Y4$pMfJseA|6pW_A{BM%Z@ zRhM`N#2n2OGf2&q$q9VXU_+PV<3&?16-wJp+TcE2$}X-!z&J+y5@m%o$JN12oGr3j z{Az;t)hI6(yb@Fd2ndhJh`az^n%^Y`WKv$@mxQJ?PIfb?JMUyegL1I~Ec#&T>hF0q zQP4P05S~0iML S&RzzcioYZ5K&+L7yBcHARqwz9G(S8ZG^0Uv1n^`?I>KpSD4HoWV=Gu ?I^q`Y#&$vv4B;};g(!Lj0zQlXTfL0adYdv{fb_)q2c@UC+pbbsCPWYRL zyjP&EmC8uf C@NoHm8JZyo^#8H`2u}cT z%i{ugLl7_!f4e{rG5+WB=RKLD<4!;0OV0WnZ=vati%$f8MIVNo+rHe9&CTvzw o59~kXnJ{xx@O6eI<$N)kn?YUq3#)DlD*3SvN-WH?Q`W{S_WA zj!<}923?~qTK!a^Ba|1LQ4RIvzahxUx$Q8ypItQ47_2=h^q>iIrrBAfI_+fN6ts)% z$|86dRR0y|GS=lffAMu!fJBe|5y>mPID2`Am_3P#bU?is2~U6iBCFpcCZ)?=w+(-* zKPLE`;sy~!eMjIFo~5(ONxj>j*+aFkC0nr5cKI36Ze zqH~Yl>+xUn*aT!i`%J}4%tQu5TY=Ac3#HWxTUV3()dMjr7zbxLzNgKkBaT)f&fzxo ze`>h?Bm `}-ts7S4 z*7!8)t%POiu9)W?3bz|WAJea7fohWXW!6veU|&0*#Wc|#iE&r>AZy9>ejsjc<&ve> z&&{XS=r+&B4>nkRU6d=6=@7jIdE9L1$Vq8?R8#k|fN>rx4BPbpSv(G(S2E1d=?K4y zt2Rt1^F^mvH8}Uw6SyJT;VoAs&}ge*XsEAxD4qQ}JliMIA{n*#?0wz1s+)%MkV?S8 zg--m}*oB!uz$7G-zciIEJ+p^-hYp>Je5b|KUHID^8-78_C%5Q6myHf|w#SfYUxPYe z6njU&UcvqI$9!`IQ1faC%idJs?;f8Cd*eSHzCWu!1pW~CL*NgAKLq{|_(R|ifj ;17X61pW~CL*NgAKLq{|_(R|ifj ;17X6 z1pW~CL*NgAKLq{|_(R|ifj s1lieu z0Doon@cg|G0jUoJu>n0UK7Io{RZ~(`0wBN>2H~^J0f5JD03I5`CBh{V0v-Sn4*>}e z;c*!t4?sbFg7O3z1?33}Dk=&Z##0OobaV_t99*oYB!r}-B!tAo 5s;7&k&&J}L562TA_T#Y1Ca5a;M4KRq7di+QRzJh`Cla! zqA|$T_Ymn$oiYl5pdsiO#3ZC- $7laY<=ec|~Pa!~4dj=9bpB_D`RC z`}zk4hlZzTX6NP?7QZa5f8W^L+TPjS+dn(MxV-vt{qyGbce@Y(@EQI8EPuD`zqAW( z4g^Gaq6{+X?{*;|`oRB?@Q|O-@uA?$>YxHW2 r7|AoAy`B{`VRd@_*H`e>Lpi+qDG1f+sH^;vwMyqyeW+nbDLY46{ZVC?$ ^g~PBTQn>6UG*3!`8w=E6u{)J*(rlXSeg1tyD)pB53#$ep)S1M=Y1mbi}-$C2t#7 z3#SVok?|ecFQ1&RTp23jq3asQfXb%~ls%D%?(dIzVTVGNb6s5caQX-!ANFXjn8xpl zT>zf%)XLffD9d-h!En_e;fyGMuIyhs*^DXnah_n!1oz{_Gs=c%;4GU zfyx?Euc#QD5+9-`SjnzgRui#P)^v~`xi;XXyqAi(Pf^t3vk+qd16lIWFV{sq9%YC2 zq~Bwn3^M16c*qd0$u3oWVpm&auIgQ>o3_7WTQrjRiftbBR-uKi%i)3`z-3qZB=2J$ zY}PQTk?Omn4N5;rep(hNY7XtD%VXC=274j8u9PyALj61_@)3YFS1hbF_EMIn7Ks+$ zBjjtm1H!fe%Zck9D+0B~6$4EgFU5AMHMDps(Y%dlIP(F7@WzhQhvSFK^Scj2v_nU) zk!}ss=g7V)S tH56CzV0Mmvu)(=GQ32a^SghXj
Dtc95}MF&99(NGNlhVs^f`&`-=^Q)k;t z`3W;j;!0ymOfW5pa;_HGdFdx Q{K#CV0$$nrWc^jS#hvF_qYAVXQEOS+-< z*O3Tl0FUiM`N5I-McQgyQB8cdJ)X=+EZz*!6VinYX*z@w)Ws{oX#z1#$6q59vLiw+ zXe;Bo_2X@JpwX3lO=pm2o(1wtq;V-+q8-yISC-l_zf$_nxh}%gJLOV+b{K@-UUR@W zYn~&s1ogpR
znVDG)MU|HWIYdE|z1aO@J*9W >aPiDz{<2RGN%ebVv&jzkSY_bDPCLj>dwj1tZ}rh zCYyV2DQwi+qtA_Ju$Xj-f_f!dFWPlWNo$_PLif;K0QT_x5fG-#&XJX*USYw aZmXMRz?GrT6=I@?Jlg!1*oXh4W2W>LZ|g z%pgg`23s=h#a+SmR5{SRZgf?h# (I&9b>L`Ws+g6J7U$t1^z6Z;W?wyx WNh}^0$-cuk8kX`1o8Kkky}De zn~Ki$qC0QZruo|Ny_GpQ<0SiX-j ) $>|;FE!ZN8*g{0#QZL 2M>L9Qczf=#ZMWm& zh?D7se `(UI(tx>blb*QjdNF?jm2Fc@@3mJsLF{0a9vYbtR12R z$VQ-DT?JMb_mE?*umFAM23F*&%e#tg5_bMgT TWW;gOgxfYxf$QqI z$1KgoT6S6^$nmyVS!=epTOR5?5{q <|SKHA8+_?wqm*zeCaeQpyAoR~+3oT|5fJ+?yO& z?qM8NEC&CN9H`^5DEHwxU2IWaH_#LSA?$_tx&qzmOxh$#7GX2n8wKM hGDjtZPGta?7Pl@)yM5pa81f*|7+_5Gq o(HDI3 z?jj7~;1Gf^BHH&@qYYlt?947mDUXg>*TlsfIf`z4YTY^OQ&SX%8Qr$Gerh(RF`bf$ zV93edsw=2m#N9E2o@a@Sa`JhxEC}Y1E7m*#;7v@IyjMgQpclVBnLKq$<4|lIzvdWH zQc3oWM$}7`AWvhw1gDzLk+rK}W4)%T-R^X;_)_wtKAy@#I4^Gu?evQ>>Cixv_4k<% zTKS$cvEdbLGc(BKfkV54n;vsdN252-9s%=0kVbiUtzruq@;MyFcO)wt#aKQAUk!9R z*fz&D<7`s+C#j$yg07Z#1A+@Kj;~pOc6<|Lam^Q#*MuB}QX1azW@O`YOcJs7H201R zsti(~XWL)6=`N0fzp3xR^ z^WfY4ueTI>W>JSmk#>XwrF!_(#ic4kIZptUtjSazn#^db3$z~dud%>v{XuN6q*_t4 z({(XzCvqME7FIViK?qMB*&-?KuFFv?`4^ioz7t9xCT!_mvpI|DF&wCXoZrZsa+l?G zN9j|3&wS0N<6e)c9D6&{TB9_gfi_r1_aXju=#d7l$&72*ae O zjiQ%TPUxUuck{5)%8Q^qtnq~xn%q!r7C^wcbg6}OWYirCW2aNej(9L;7c+*$Y<(Vg zDKWP^@2^3M+hDbw%C=4^9fd-k`23p+PLro7S8&% zeXz`{XeB$$Nl@!Z0dZvt9&-@wkP4}!GrjxNGOL%UFhAY+2som6g^6De_KcOly?A9J z2Hkfk9xJQ~ 9eE!W_CGs0sA)me2E ze*aos@2RNj{nyzpOa8?K+cxzR!I?8HogP*)&5r>60ySM^4HRSjR0b?^fTvl9E?6=7 zBa^Cvt*S0tWRnXCQkkO}8+Nm$-6NoG;%;vckGP`L#dzaxJJOZS*-YgOA0XOYx&JJr z;_40P^-eSE)4Bp4H9)Ll?3va&5X(fg=o@d(d`xsN^Gg2gF)>{-BCZ-Fy3dcE7RsJN z6u;2zLw9*o8}(#8u!7;IS;02NMV dXOwqBbQTZb^HB_{?u5Y^ z6j<^ZQ@ S~lY EYJPTz{aQNz6 zv93u1$REr9`tHCVQfrdB qC!*;`rnc5#-0 z#!;nrpSOWM><$fyCc$*vC&{CM94aHBthFjFJ~+KjI+{hh=|4?IIQ8@{H%0Dg0?kme z%CRCZ8zGQ|@}BMG4GFsv4g^6?L9*aejiN B-t1Vg)>C(QGu)l!9_?XP>;?5&L^eQJwhH%A*s z>h1QOl;mg-zlFv(=8!AwpuJ8RFwGmN&k&l)U_!%Orf+`Uzz6f0+{>F*G>{H(CuPW9 zSM5K%TuIH=fE6p!aa }WpB@6a TZG851+vw>dMxVT^K6%y_+QmHOEj%l(CV z7H(QRxy2gn2P~li{%kCk2z~c3+|{r6J^(7+A8$ih1KqtIsBN*#N>aWm&O+sQ>X5Pe zO((&G u2+_P%_LA8%;MKU9a+ykd9&TmnhlCq5GNaV&8k9vXh= sONXYC#c^5h gEo zV3_oZTM0d>j>dz<^@Yy8lNq#{?09J1e6}H(NrT5qEh(!-ukjqMXoO7^5R6y5PT@Gp z!m%5ri$+hu9JiGk^gR#ZvZ_s3TseyAx%Mnx578!@T2B3ms9aSW^5HH5cpJNX@y8?J zz_5NRc}5=nLiV6iU7VUZxej~v`CnuUs-C8Ln{1)>WU!{1;VwA zgv7C!AIuK9OcI_yW4K?bue$UC;H~Jo^xa@Hv6fsbsz(k#CEfI&>-!2~tZhS(z}0wL z>s$h4^=2ZW;LA@_*JF93a;8qLe=2or;1~s_>*S@2iw8;btnX>3Z?5$-4)0M4lzAsA z1%FeH{^BhV)aTNIhoZ$u*bHm~jX$W6ay&Hghy7p|XpY@=z9Q^qmI?Cf=RhTPE{Qq) z+N-@Le-j)hAHybS@+**ZYtmz%%_M#oY-!rYFrIK`?eLyzvepNI6zk)wqEYT>ajIDl z%+=9iua$_DWA=TnPPEXK7w@+$mQ$6?H_9OLN@9-D&nVk{@oL|K>599Rj$*-pgi?qB zOBH>Nlai T=P>D5F2j*d)h1_(x|AwC06=yjh|<+P%NtW 2vwmm*G0tK5jH`P7asdEgtXTuSckp% z9s=V@?}+s77JV*5Q&}>}tl?^o1^*_auhH_72Yu};*=g%hR` c`K^XLZBR zw~WLbwT=hGO`G`vkVxMW 0R_u2;aB;22f}0c{B_6VN_@)WS~wEFFm_+NRP*MZ6!Gkd2R5v5HGLG}`P{lQ zOdYzGV?$kB@>7YziF6~y@uRNT#Ksu6*)g~4z*GGXZ~FUGX?BFR*QTzMjzqfx`WE9- zLo`A;8aLmr%e}YUeP(mN`FskDZz?QGuhc_hy7VE}rW1?zH1_+wraBa4h@0FtzCWy~ zH7^uvPSN!_n=h16l)81-(XU;l%c=>HcW}`}Y0dAlN(Zt=pPHIh62%ZT%fYsWFww>8 zD&hoW;$Uh0l4f%HO!-pEk!Yi uE&io>ACDl zg84K;Eq&-*Fp^?8g9_hu1tJ+GHI(nC9-bD6Z>bWklx|2!A(3x8Gyb~g+HObQxPk0w zrT@$2zO wu$Rq}3_IT;2ZLOAbIl~j6{+om)xCd33Iq4rWsIjNt4D|o z +#!9dDt}HD-qT*Bckn>2qf2i^TYcOv Y Nl*V5eKi!1!{Fym z$t5maI91Sgv5uJO7sq^8V`@=GZ0bG(rMi*1(fk%Dl)_Gm_+rr=Equ(?Al2{Ra81Ka zxk~GQQLL!oV6vBg92IyV5}E(poPo(2n{S88K$!Dxno7`@l>T*3v6vBwy6IOr@$AGm zcv$ft=CSvk)58Lm(^oe?kH!s9RjbHX3sg!tL^uC9UZ_{Z3_O&B$L!7>w#($_%EJPj zMhOScin>ps94R3I0r!r??NRURyDdrJo$9kny60bFkA$|^R!#EnDoakN&@6`O@Udt{ z$))nHBH5d#?OsGtANbfZ((u(M)Vf)k*Rm|`e3sAp{i;C$OdZ(s>i(N3{8QA3BRb=g zH=Agn(>6=0$>JxOZ#LdiPKq3iC7=yI5fml =A0J z_(LGsaVWPpF3Y6TIQx<~@`Ac%ZW_f3wG`{=LF6X+>snM!Uhk{Hes;m;Qm;wd!C!K* z{lgo}i4ZQ7D~6D96Dx_-XW}XIImvQ5&MjaEQ1&KeVCzH+8T?Z AieX5}6DjTf;Ms;c&a ?Jc>_ zpA;8!JH08t0}5)_OC$=mIx8Ox&5IS$eFS(3{Dk{EWrg`@R9V`M=sn~M#$oK>#yj}N zlf@Z^{K8;%&XB`BNhH7bMge{R4sohtq!S*7%T*J@gbtXR?)>Ow=BeMEU!C5Y@>*`^ zGGrW-^@m_`4|os#0+r>L(iXpao$o}ax2H1KmF>NN!pDUZ-Ori{f#o~iu&_APCz(Ge z6JC5~E*+A!NG;ktOpuxGBH2rKq|oteLLV@g$e?}^x_uvvv60X3rn2iLk4bdq=tbNv zl%D-@m)QG9_o;?X7|Xsg%UPY+xZcma@m4v$p9U|><{2LWDJB&3AKp9>HO(Q+Qd9bD zpR0eK0nI(91p3<_D6^WE9e(!K7BZA;!#5wo^h=WHV->?Gsicu(|Gr}J{ZJv*u9gW# z&`+YQ*I?K|yRv*iS?F$#kDxSwe2ymN>%1dI-Aw+WibK6{3*?}b^8tt2JYI^UVv{!U z)G;xObRu1fEv8Tq4AAGFDlIn^TG=BHjjvG#ny48n)f)+4y{op}s=y$uzz`i!8>YzM ziZ&Gtc$gI3aJ#C?Ayi)L#Z&3WHPx{m6+u066!)%u7|K}UvRH`(sm$C1_l|T(EmqUS z&a3G>N646R6|zKd+>h?N18{#N?3oJNf-4JDEF^l{UD}=_4v^7$54KAG;u1Pd{<8NK zxAxjsceKDzx>itiIw