From 9f97c763bc5a2d3337aa1659e0bf1e37843c4cd4 Mon Sep 17 00:00:00 2001 From: Allan Kong Date: Sat, 13 Sep 2025 20:21:51 -0600 Subject: [PATCH 01/12] Home Page --- app/Http/Controllers/HomeController.php | 23 ++ app/Services/HomeStatisticsService.php | 60 +++++ ...er_science_resources_difficulty_to_set.php | 7 +- .../js/Components/ApplicationHeaderLogo.vue | 4 +- resources/js/Components/CoverflowGallery.vue | 217 +++++++++++++++ resources/js/Components/Navigation/Navbar.vue | 2 +- resources/js/Pages/Home.vue | 252 ++++++++++++++++++ routes/web.php | 5 +- 8 files changed, 564 insertions(+), 6 deletions(-) create mode 100644 app/Http/Controllers/HomeController.php create mode 100644 app/Services/HomeStatisticsService.php create mode 100644 resources/js/Components/CoverflowGallery.vue create mode 100644 resources/js/Pages/Home.vue diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php new file mode 100644 index 00000000..ff43bb59 --- /dev/null +++ b/app/Http/Controllers/HomeController.php @@ -0,0 +1,23 @@ +homeStatistics->getStatistics(); + + return Inertia::render('Home', + $stats + ); + } +} diff --git a/app/Services/HomeStatisticsService.php b/app/Services/HomeStatisticsService.php new file mode 100644 index 00000000..467343d3 --- /dev/null +++ b/app/Services/HomeStatisticsService.php @@ -0,0 +1,60 @@ +url($path) : null; + } + + private function resourceTop(): Collection + { + return DB::table('computer_science_resources') + ->whereNotNull('image_path') + ->limit(10) + ->get() + ->map(fn($res) => [ + 'id' => $res->id, + 'image_url' => $this->getPublicUrl($res->image_path), + ]); + } + + private function resourcesCount(): int + { + return DB::table('computer_science_resources')->count(); + } + + private function topTopics(): Collection + { + return DB::table('tag_frequencies')->where('type','topics_tags') + ->orderByDesc('count')->limit(10)->get(); + } + + private function topicsCount(): int + { + return DB::table('tag_frequencies')->where('type','topics_tags')->count(); + } + + + public function getStatistics() + { + return array( + "resources_top" => $this->resourceTop(), + "resources_count" => $this->resourcesCount(), + "topics_count" => $this->topicsCount(), + "topics_top" => $this->topTopics() + ); + } + +} diff --git a/database/migrations/2025_09_06_223541_change_computer_science_resources_difficulty_to_set.php b/database/migrations/2025_09_06_223541_change_computer_science_resources_difficulty_to_set.php index 4092301a..b22dcf7a 100644 --- a/database/migrations/2025_09_06_223541_change_computer_science_resources_difficulty_to_set.php +++ b/database/migrations/2025_09_06_223541_change_computer_science_resources_difficulty_to_set.php @@ -22,7 +22,12 @@ public function up(): void public function down(): void { - // Rollback: rename back and revert type + // Keep only the first value from the SET + DB::statement(" + UPDATE computer_science_resources + SET difficulties = SUBSTRING_INDEX(difficulties, ',', 1) + "); + DB::statement(" ALTER TABLE computer_science_resources CHANGE difficulties difficulty ENUM( diff --git a/resources/js/Components/ApplicationHeaderLogo.vue b/resources/js/Components/ApplicationHeaderLogo.vue index 12705d91..e2719d60 100644 --- a/resources/js/Components/ApplicationHeaderLogo.vue +++ b/resources/js/Components/ApplicationHeaderLogo.vue @@ -1,4 +1,4 @@ diff --git a/resources/js/Components/CoverflowGallery.vue b/resources/js/Components/CoverflowGallery.vue new file mode 100644 index 00000000..524e8118 --- /dev/null +++ b/resources/js/Components/CoverflowGallery.vue @@ -0,0 +1,217 @@ + + + + + + diff --git a/resources/js/Components/Navigation/Navbar.vue b/resources/js/Components/Navigation/Navbar.vue index 7dd947ea..b46081e2 100644 --- a/resources/js/Components/Navigation/Navbar.vue +++ b/resources/js/Components/Navigation/Navbar.vue @@ -26,7 +26,7 @@ const { isDark, toggleDark } = useDarkMode();
- +
diff --git a/resources/js/Pages/Home.vue b/resources/js/Pages/Home.vue new file mode 100644 index 00000000..ad897cca --- /dev/null +++ b/resources/js/Pages/Home.vue @@ -0,0 +1,252 @@ + + + diff --git a/routes/web.php b/routes/web.php index 5cb65b28..e6ec46fb 100644 --- a/routes/web.php +++ b/routes/web.php @@ -2,6 +2,7 @@ use App\Http\Controllers\CommentController; use App\Http\Controllers\ComputerScienceResourceController; +use App\Http\Controllers\HomeController; use App\Http\Controllers\ResourceEditsController; use App\Http\Controllers\ResourceReviewController; use App\Http\Controllers\TagFrequencyController; @@ -51,8 +52,8 @@ // Public // ----------------------- Route::middleware('guest.or.verified')->group(function () { - Route::get('/', function () { - return redirect('/resources'); + Route::controller(HomeController::class)->group(function () { + Route::get('/', 'show')->name('home.show'); }); Route::get('/about', function () { From 7a5af245a8128868397fe841197862054aeeb8b5 Mon Sep 17 00:00:00 2001 From: AllanKoder <74692833+AllanKoder@users.noreply.github.com> Date: Sun, 14 Sep 2025 02:22:17 +0000 Subject: [PATCH 02/12] Apply automatic changes --- app/Http/Controllers/HomeController.php | 3 +-- app/Services/HomeStatisticsService.php | 29 ++++++++++--------------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index ff43bb59..722d51c7 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -3,14 +3,13 @@ namespace App\Http\Controllers; use App\Services\HomeStatisticsService; -use Illuminate\Http\Request; use Inertia\Inertia; class HomeController extends Controller { public function __construct( protected HomeStatisticsService $homeStatistics - ){} + ) {} public function show() { diff --git a/app/Services/HomeStatisticsService.php b/app/Services/HomeStatisticsService.php index 467343d3..c03de640 100644 --- a/app/Services/HomeStatisticsService.php +++ b/app/Services/HomeStatisticsService.php @@ -2,19 +2,16 @@ namespace App\Services; -use App\Models\ComputerScienceResource; use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Storage; class HomeStatisticsService { - public function __construct() - { - - } + public function __construct() {} - function getPublicUrl(?string $path): ?string { + public function getPublicUrl(?string $path): ?string + { return $path ? Storage::disk('public')->url($path) : null; } @@ -24,7 +21,7 @@ private function resourceTop(): Collection ->whereNotNull('image_path') ->limit(10) ->get() - ->map(fn($res) => [ + ->map(fn ($res) => [ 'id' => $res->id, 'image_url' => $this->getPublicUrl($res->image_path), ]); @@ -37,24 +34,22 @@ private function resourcesCount(): int private function topTopics(): Collection { - return DB::table('tag_frequencies')->where('type','topics_tags') + return DB::table('tag_frequencies')->where('type', 'topics_tags') ->orderByDesc('count')->limit(10)->get(); } private function topicsCount(): int { - return DB::table('tag_frequencies')->where('type','topics_tags')->count(); + return DB::table('tag_frequencies')->where('type', 'topics_tags')->count(); } - public function getStatistics() { - return array( - "resources_top" => $this->resourceTop(), - "resources_count" => $this->resourcesCount(), - "topics_count" => $this->topicsCount(), - "topics_top" => $this->topTopics() - ); + return [ + 'resources_top' => $this->resourceTop(), + 'resources_count' => $this->resourcesCount(), + 'topics_count' => $this->topicsCount(), + 'topics_top' => $this->topTopics(), + ]; } - } From 09959e385cec15f60f9a68e550083abd92b2bd80 Mon Sep 17 00:00:00 2001 From: Allan Kong <74692833+AllanKoder@users.noreply.github.com> Date: Sat, 13 Sep 2025 20:24:49 -0600 Subject: [PATCH 03/12] Update database/migrations/2025_09_06_223541_change_computer_science_resources_difficulty_to_set.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- ...23541_change_computer_science_resources_difficulty_to_set.php | 1 + 1 file changed, 1 insertion(+) diff --git a/database/migrations/2025_09_06_223541_change_computer_science_resources_difficulty_to_set.php b/database/migrations/2025_09_06_223541_change_computer_science_resources_difficulty_to_set.php index b22dcf7a..6be509b3 100644 --- a/database/migrations/2025_09_06_223541_change_computer_science_resources_difficulty_to_set.php +++ b/database/migrations/2025_09_06_223541_change_computer_science_resources_difficulty_to_set.php @@ -26,6 +26,7 @@ public function down(): void DB::statement(" UPDATE computer_science_resources SET difficulties = SUBSTRING_INDEX(difficulties, ',', 1) + WHERE difficulties IS NOT NULL AND difficulties != '' "); DB::statement(" From a2492d439db126341a68691ef073ab9ad8e7a207 Mon Sep 17 00:00:00 2001 From: Allan Kong Date: Sat, 13 Sep 2025 20:26:46 -0600 Subject: [PATCH 04/12] nits --- resources/js/Components/CoverflowGallery.vue | 12 ++++++------ resources/js/Pages/Home.vue | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/resources/js/Components/CoverflowGallery.vue b/resources/js/Components/CoverflowGallery.vue index 524e8118..01fd7650 100644 --- a/resources/js/Components/CoverflowGallery.vue +++ b/resources/js/Components/CoverflowGallery.vue @@ -18,6 +18,7 @@ const root = ref(null) const containerWidth = ref(0) let rafId = null let lastTs = 0 +let roRef = null // compute slide width so exactly three slides can fit comfortably const slideGap = 14 // px gap between slides (slightly smaller gap) @@ -123,18 +124,17 @@ onMounted(() => { measure() window.addEventListener('resize', measure) // observe the root for layout changes (images/font load) - const ro = new ResizeObserver(measure) - if (root.value) ro.observe(root.value) - ;(root.value || {}).__ro = ro + roRef = new ResizeObserver(measure) + if (root.value) roRef.observe(root.value) play() }) onBeforeUnmount(() => { pause() window.removeEventListener('resize', measure) - if (root.value && root.value.__ro) { - try { root.value.__ro.disconnect() } catch (e) {} - delete root.value.__ro + if (roRef) { + try { roRef.disconnect() } catch (e) {} + roRef = null } }) diff --git a/resources/js/Pages/Home.vue b/resources/js/Pages/Home.vue index ad897cca..43d886a4 100644 --- a/resources/js/Pages/Home.vue +++ b/resources/js/Pages/Home.vue @@ -5,8 +5,8 @@ import AppLayout from "@/Layouts/AppLayout.vue"; import { Head } from "@inertiajs/vue3"; import CoverflowGallery from "@/Components/CoverflowGallery.vue"; import { Link } from "@inertiajs/vue3"; -import SecondaryButton from "@/Components/SecondaryButton.vue"; import ApplicationLogo from "@/Components/ApplicationLogo.vue"; +import PrimaryButton from "@/Components/PrimaryButton.vue"; const props = defineProps({ resources_top: Object, @@ -95,9 +95,9 @@ const fmt = (n) => new Intl.NumberFormat().format(Number(n || 0));
- + Explore Resources - +
From 33075dcf56c0b156ff59caa8c83e75b2110e2119 Mon Sep 17 00:00:00 2001 From: Allan Kong Date: Sun, 21 Sep 2025 19:03:34 -0600 Subject: [PATCH 05/12] Sitemap --- app/Console/Commands/GenerateSitemap.php | 49 +++ .../ComputerScienceResourceController.php | 29 +- .../ComputerScienceResourceService.php | 28 ++ composer.json | 1 + composer.lock | 396 +++++++++++++++++- 5 files changed, 490 insertions(+), 13 deletions(-) create mode 100644 app/Console/Commands/GenerateSitemap.php diff --git a/app/Console/Commands/GenerateSitemap.php b/app/Console/Commands/GenerateSitemap.php new file mode 100644 index 00000000..4fcb6228 --- /dev/null +++ b/app/Console/Commands/GenerateSitemap.php @@ -0,0 +1,49 @@ +add(Url::create('/')) // homepage + ->add(Url::create('/about')) // about page example + ->add(Url::create('/login')) + ->add(Url::create('/register')); + + // Add each resource page to the sitemap with last modification date if available + // Limit to a maximum of 30,000 resources + $limit = 30000; + $count = 0; + + $query = ComputerScienceResource::query()->orderBy('id')->limit($limit); + foreach ($query->cursor() as $resource) { + if ($count >= $limit) { + break; + } + + $lastMod = $resource->updated_at ?? $resource->created_at; + + $sitemap->add( + Url::create(route('resources.show', ['slug' => $resource->slug])) + ->setLastModificationDate($lastMod) + ); + + $count++; + } + + // Write to public/ so it's directly accessible + $sitemap->writeToFile(public_path('sitemap.xml')); + + $this->info('Sitemap generated successfully!'); + } +} diff --git a/app/Http/Controllers/ComputerScienceResourceController.php b/app/Http/Controllers/ComputerScienceResourceController.php index f68085d2..9752c727 100644 --- a/app/Http/Controllers/ComputerScienceResourceController.php +++ b/app/Http/Controllers/ComputerScienceResourceController.php @@ -27,21 +27,26 @@ public function __construct( */ public function index(Request $request) { - $query = ComputerScienceResource::query(); - - // Apply all filters and sorting - $filters = $request->query(); - $query = $this->filterService->applyFilters($query, $filters); + try { + $data = $this->resourceService->getIndexData($request); - // Paginate with appended query params - $resources = $query->paginate(20)->appends($request->query()); + return Inertia::render('Resources/Index', $data); + } catch (Throwable $e) { + Log::error('Error loading resources index', [ + 'user_id' => Auth::id(), + 'query' => $request->query(), + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString(), + ]); - $news = NewsPost::limit(10)->get(); + // Return an empty page with an error flash so the UI can show a message + session()->flash('error', 'Unable to load resources right now.'); - return Inertia::render('Resources/Index', [ - 'resources' => $resources, - 'news_posts' => $news, - ]); + return Inertia::render('Resources/Index', [ + 'resources' => ComputerScienceResource::query()->paginate(1), + 'news_posts' => collect(), + ]); + } } /** diff --git a/app/Services/ComputerScienceResourceService.php b/app/Services/ComputerScienceResourceService.php index 3b62b7cf..dfc10e1b 100644 --- a/app/Services/ComputerScienceResourceService.php +++ b/app/Services/ComputerScienceResourceService.php @@ -5,6 +5,7 @@ use App\Exceptions\Resources\ResourceAlreadyCreatedException; use App\Exceptions\Resources\ResourceInvalidTabException; use App\Models\ComputerScienceResource; +use App\Models\NewsPost; use App\Models\ResourceEdits; use App\Models\ResourceReview; use App\Services\SortingManagers\ResourceSortingManager; @@ -24,8 +25,35 @@ public function __construct( protected UpvoteService $upvoteService, protected ResourceReviewService $reviewService, protected ResourceSortingManager $resourceSortingManager, + protected ComputerScienceResourceFilter $filterService, ) {} + /** + * Get data for the resources index page (filters, pagination, news) + * + * @return array{ + * resources: \Illuminate\Contracts\Pagination\LengthAwarePaginator, + * news_posts: \Illuminate\Database\Eloquent\Collection + * } + */ + public function getIndexData(Request $request): array + { + $query = ComputerScienceResource::query(); + + // Apply filters and sorting through the dedicated filter service + $filters = $request->query(); + $query = $this->filterService->applyFilters($query, $filters); + + $resources = $query->paginate(20)->appends($request->query()); + + $news = NewsPost::limit(10)->get(); + + return [ + 'resources' => $resources, + 'news_posts' => $news, + ]; + } + /** * Create a new ComputerScienceResource * diff --git a/composer.json b/composer.json index 0fd4f140..cb5bcdc8 100644 --- a/composer.json +++ b/composer.json @@ -23,6 +23,7 @@ "shiftonelabs/laravel-cascade-deletes": "^2.0", "spatie/laravel-activitylog": "^4.10", "spatie/laravel-backup": "^9.3", + "spatie/laravel-sitemap": "^7.3", "spatie/laravel-tags": "^4.9", "symfony/http-client": "^7.3", "symfony/mailgun-mailer": "^7.3", diff --git a/composer.lock b/composer.lock index 605faf11..ef0b30a2 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e4dbfba9cfedc7e4d7d4745986019a8b", + "content-hash": "d5c060280727af15b29e77969312c209", "packages": [ { "name": "anourvalar/eloquent-serialize", @@ -5116,6 +5116,60 @@ }, "time": "2025-08-06T21:43:34+00:00" }, + { + "name": "nicmart/tree", + "version": "0.9.0", + "source": { + "type": "git", + "url": "https://github.com/nicmart/Tree.git", + "reference": "f5e17bf18d78cfb0666ebb9f956c3acd8d14229d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nicmart/Tree/zipball/f5e17bf18d78cfb0666ebb9f956c3acd8d14229d", + "reference": "f5e17bf18d78cfb0666ebb9f956c3acd8d14229d", + "shasum": "" + }, + "require": { + "php": "~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.44.0", + "ergebnis/license": "^2.6.0", + "ergebnis/php-cs-fixer-config": "^6.28.1", + "fakerphp/faker": "^1.24.1", + "infection/infection": "~0.26.19", + "phpunit/phpunit": "^9.6.19", + "psalm/plugin-phpunit": "~0.19.0", + "vimeo/psalm": "^5.26.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Tree\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolò Martini", + "email": "nicmartnic@gmail.com" + }, + { + "name": "Andreas Möller", + "email": "am@localheinz.com" + } + ], + "description": "A basic but flexible php tree data structure and a fluent tree builder implementation.", + "support": { + "issues": "https://github.com/nicmart/Tree/issues", + "source": "https://github.com/nicmart/Tree/tree/0.9.0" + }, + "time": "2024-11-22T15:36:01+00:00" + }, { "name": "nikic/php-parser", "version": "v5.6.1", @@ -6580,6 +6634,74 @@ }, "time": "2024-12-14T18:36:46+00:00" }, + { + "name": "spatie/browsershot", + "version": "5.0.10", + "source": { + "type": "git", + "url": "https://github.com/spatie/browsershot.git", + "reference": "9e5ae15487b3cdc3eb03318c1c8ac38971f60e58" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/browsershot/zipball/9e5ae15487b3cdc3eb03318c1c8ac38971f60e58", + "reference": "9e5ae15487b3cdc3eb03318c1c8ac38971f60e58", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "ext-json": "*", + "php": "^8.2", + "spatie/temporary-directory": "^2.0", + "symfony/process": "^6.0|^7.0" + }, + "require-dev": { + "pestphp/pest": "^3.0", + "spatie/image": "^3.6", + "spatie/pdf-to-text": "^1.52", + "spatie/phpunit-snapshot-assertions": "^4.2.3|^5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\Browsershot\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://github.com/freekmurze", + "role": "Developer" + } + ], + "description": "Convert a webpage to an image or pdf using headless Chrome", + "homepage": "https://github.com/spatie/browsershot", + "keywords": [ + "chrome", + "convert", + "headless", + "image", + "pdf", + "puppeteer", + "screenshot", + "webpage" + ], + "support": { + "source": "https://github.com/spatie/browsershot/tree/5.0.10" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2025-05-15T07:10:57+00:00" + }, { "name": "spatie/color", "version": "1.8.0", @@ -6639,6 +6761,74 @@ ], "time": "2025-02-10T09:22:41+00:00" }, + { + "name": "spatie/crawler", + "version": "8.4.3", + "source": { + "type": "git", + "url": "https://github.com/spatie/crawler.git", + "reference": "4f4c3ead439e7e57085c0b802bc4e5b44fb7d751" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/crawler/zipball/4f4c3ead439e7e57085c0b802bc4e5b44fb7d751", + "reference": "4f4c3ead439e7e57085c0b802bc4e5b44fb7d751", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^7.3", + "guzzlehttp/psr7": "^2.0", + "illuminate/collections": "^10.0|^11.0|^12.0", + "nicmart/tree": "^0.9", + "php": "^8.2", + "spatie/browsershot": "^5.0.5", + "spatie/robots-txt": "^2.0", + "symfony/dom-crawler": "^6.0|^7.0" + }, + "require-dev": { + "pestphp/pest": "^2.0|^3.0", + "spatie/ray": "^1.37" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\Crawler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be" + } + ], + "description": "Crawl all internal links found on a website", + "homepage": "https://github.com/spatie/crawler", + "keywords": [ + "crawler", + "link", + "spatie", + "website" + ], + "support": { + "issues": "https://github.com/spatie/crawler/issues", + "source": "https://github.com/spatie/crawler/tree/8.4.3" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2025-05-20T09:00:51+00:00" + }, { "name": "spatie/db-dumper", "version": "3.8.0", @@ -7162,6 +7352,79 @@ ], "time": "2025-02-14T09:55:51+00:00" }, + { + "name": "spatie/laravel-sitemap", + "version": "7.3.7", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-sitemap.git", + "reference": "077b36c64bc4f373f4d95a1ac6ee1c0624acfdd3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-sitemap/zipball/077b36c64bc4f373f4d95a1ac6ee1c0624acfdd3", + "reference": "077b36c64bc4f373f4d95a1ac6ee1c0624acfdd3", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^7.8", + "illuminate/support": "^11.0|^12.0", + "nesbot/carbon": "^2.71|^3.0", + "php": "^8.2||^8.3||^8.4", + "spatie/crawler": "^8.0.1", + "spatie/laravel-package-tools": "^1.16.1", + "symfony/dom-crawler": "^6.3.4|^7.0" + }, + "require-dev": { + "mockery/mockery": "^1.6.6", + "orchestra/testbench": "^9.0|^10.0", + "pestphp/pest": "^3.7.4", + "spatie/pest-plugin-snapshots": "^2.1", + "spatie/phpunit-snapshot-assertions": "^5.1.2", + "spatie/temporary-directory": "^2.2" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\Sitemap\\SitemapServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Spatie\\Sitemap\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Create and generate sitemaps with ease", + "homepage": "https://github.com/spatie/laravel-sitemap", + "keywords": [ + "laravel-sitemap", + "spatie" + ], + "support": { + "source": "https://github.com/spatie/laravel-sitemap/tree/7.3.7" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + } + ], + "time": "2025-08-25T08:07:09+00:00" + }, { "name": "spatie/laravel-tags", "version": "4.10.0", @@ -7315,6 +7578,66 @@ ], "time": "2025-02-20T15:51:22+00:00" }, + { + "name": "spatie/robots-txt", + "version": "2.5.2", + "source": { + "type": "git", + "url": "https://github.com/spatie/robots-txt.git", + "reference": "1b59dde3fd4e1b71967b40841369c6e9779282f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/robots-txt/zipball/1b59dde3fd4e1b71967b40841369c6e9779282f3", + "reference": "1b59dde3fd4e1b71967b40841369c6e9779282f3", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "phpunit/phpunit": "^11.5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\Robots\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brent Roose", + "email": "brent@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Determine if a page may be crawled from robots.txt and robots meta tags", + "homepage": "https://github.com/spatie/robots-txt", + "keywords": [ + "robots-txt", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/robots-txt/issues", + "source": "https://github.com/spatie/robots-txt/tree/2.5.2" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2025-09-19T10:37:01+00:00" + }, { "name": "spatie/temporary-directory", "version": "2.3.0", @@ -7680,6 +8003,77 @@ ], "time": "2024-09-25T14:21:43+00:00" }, + { + "name": "symfony/dom-crawler", + "version": "v7.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "efa076ea0eeff504383ff0dcf827ea5ce15690ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/efa076ea0eeff504383ff0dcf827ea5ce15690ba", + "reference": "efa076ea0eeff504383ff0dcf827ea5ce15690ba", + "shasum": "" + }, + "require": { + "masterminds/html5": "^2.6", + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "symfony/css-selector": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DomCrawler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases DOM navigation for HTML and XML documents", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dom-crawler/tree/v7.3.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-08-06T20:13:54+00:00" + }, { "name": "symfony/error-handler", "version": "v7.3.2", From 1556c3cf130fb4f181fb45fe0866d9c094d3e0c4 Mon Sep 17 00:00:00 2001 From: Allan Kong Date: Sun, 21 Sep 2025 19:32:54 -0600 Subject: [PATCH 06/12] Image improvement --- resources/js/Pages/Resources/Create.vue | 15 ++ .../Pages/Resources/Form/MandatoryFields.vue | 196 +++++++++++--- .../js/Pages/Resources/MandatoryFields.vue | 255 ------------------ 3 files changed, 178 insertions(+), 288 deletions(-) delete mode 100644 resources/js/Pages/Resources/MandatoryFields.vue diff --git a/resources/js/Pages/Resources/Create.vue b/resources/js/Pages/Resources/Create.vue index edaa6f4f..9ca0b469 100644 --- a/resources/js/Pages/Resources/Create.vue +++ b/resources/js/Pages/Resources/Create.vue @@ -64,8 +64,23 @@ const navigateToStep = (step) => { scrollToForm(); }; +const resetFormData = () => { + formData.name = ""; + formData.platforms = []; + formData.page_url = ""; + formData.image_file = null; + formData.pricing = ""; + formData.difficulties = []; + formData.description = ""; + formData.topic_tags = []; + formData.programming_language_tags = []; + formData.general_tags = []; +}; + +// Update resetForm to also clear the reactive formData const resetForm = () => { clearLocalStorage(); + resetFormData(); showReset.value = false; stepperValue.value = "1"; }; diff --git a/resources/js/Pages/Resources/Form/MandatoryFields.vue b/resources/js/Pages/Resources/Form/MandatoryFields.vue index 5e758007..9c315e1a 100644 --- a/resources/js/Pages/Resources/Form/MandatoryFields.vue +++ b/resources/js/Pages/Resources/Form/MandatoryFields.vue @@ -5,8 +5,9 @@ import MultiSelect from "primevue/multiselect"; import PrimeVueFormError from "@/Components/Form/PrimeVueFormError.vue"; import PictureInput from "vue-picture-input"; import Select from "primevue/select"; -import { defineEmits, ref, watch } from "vue"; -import ConfirmationModal from '@/Components/ConfirmationModal.vue'; +import { defineEmits, nextTick, ref, watch } from "vue"; +import ConfirmationModal from "@/Components/ConfirmationModal.vue"; +import DialogModal from "@/Components/DialogModal.vue"; import { platformsObject, pricingsObject, @@ -27,16 +28,48 @@ const emit = defineEmits(["change", "next"]); const errors = ref({}); const showDescriptionHelp = ref(false); +const showImageError = ref(false); +const imageErrorMessage = ref(""); -function onImageChange(event) { - const file = event.target.files[0]; +// ref to access the PictureInput component instance +const pictureInput = ref(null); + + +function onImageChange(_event) { + const file = pictureInput.value?.file ?? null; props.formData.image_file = file; } -function onImageRemove(event) { +function onImageRemove() { + // clear reactive state props.formData.image_file = null; + + // also clear the PictureInput internals so the preview actually disappears + try { + if (pictureInput.value) { + if (typeof pictureInput.value.remove === "function") { + pictureInput.value.remove(); + } else { + pictureInput.value.image = null; + pictureInput.value.file = null; + } + } + } catch (e) { + console.warn("Failed to clear pictureInput internals:", e); + } } +function onImageError(err) { + console.error("PictureInput error:", err); + onImageRemove(); + + // Open dialog showing the error message (prefer err.message) + imageErrorMessage.value = + err?.message || + JSON.stringify(err) || + "An unknown image error occurred."; + showImageError.value = true; +} const validateAndNext = async () => { try { @@ -68,7 +101,8 @@ watch(
- @@ -78,15 +112,13 @@ watch( placeholder="Enter the Name" class="mt-1 w-full border border-gray-300 dark:border-gray-800 rounded-md shadow-sm focus:border-blue-500 focus:ring focus:ring-blue-200 dark:bg-gray-900 dark:text-gray-100" /> - +
- @@ -103,9 +135,11 @@ watch(
- + {{ props.formData.image_file }} + + + + + - @@ -150,17 +206,29 @@ watch(
-