From fceb906b642cc8b78d9c1d38ee217d8e7b891dc3 Mon Sep 17 00:00:00 2001 From: Tajim Date: Thu, 27 Aug 2026 12:50:05 +0600 Subject: [PATCH 1/4] refactor(home): relocate FAQ to guide with pagination and make blog cards compact on mobile --- resources/js/components/BlogCard.vue | 115 ++++++----- resources/js/components/FAQSection.vue | 255 ------------------------ resources/js/pages/ContributorGuide.vue | 204 ++++++++++++++++++- 3 files changed, 264 insertions(+), 310 deletions(-) delete mode 100644 resources/js/components/FAQSection.vue diff --git a/resources/js/components/BlogCard.vue b/resources/js/components/BlogCard.vue index 50e58c0..7f7bd34 100644 --- a/resources/js/components/BlogCard.vue +++ b/resources/js/components/BlogCard.vue @@ -28,12 +28,12 @@ const formattedDate = computed(() => { diff --git a/tests/Feature/SubjectManagementTest.php b/tests/Feature/SubjectManagementTest.php index a76ed3d..4390ec8 100644 --- a/tests/Feature/SubjectManagementTest.php +++ b/tests/Feature/SubjectManagementTest.php @@ -7,6 +7,7 @@ $response = $this->actingAs($admin)->post('/admin/subjects', [ 'name' => 'Platform Testing', + 'english_name' => 'Testing Platform Subject', 'course' => 'hsc', 'tailwind_format' => 'bg-slate-500', 'icon' => 'book-open', @@ -17,6 +18,7 @@ $this->assertDatabaseHas('subjects', [ 'name' => 'Platform Testing', + 'english_name' => 'Testing Platform Subject', 'course' => 'hsc', 'tailwind_format' => 'bg-slate-500', 'icon' => 'book-open', @@ -29,6 +31,7 @@ $subject = Subject::create([ 'name' => 'Platform Testing', + 'english_name' => 'Testing Platform Subject', 'slug' => 'platform-testing', 'course' => 'hsc', 'tailwind_format' => 'bg-slate-500', @@ -38,6 +41,7 @@ $response = $this->actingAs($admin)->patch("/admin/subjects/edit/{$subject->id}", [ 'name' => 'Platform Testing Updated', + 'english_name' => 'Testing Platform Updated', 'course' => 'ssc', 'tailwind_format' => 'bg-slate-600', 'icon' => 'book-open', @@ -49,6 +53,7 @@ $this->assertDatabaseHas('subjects', [ 'id' => $subject->id, 'name' => 'Platform Testing Updated', + 'english_name' => 'Testing Platform Updated', 'course' => 'ssc', 'sort_order' => 2, ]); @@ -75,6 +80,51 @@ ]); }); +test('admin can create a subject with custom slug', function () { + $admin = adminUserWithPermissions(['view admin', 'create subjects']); + + $response = $this->actingAs($admin)->post('/admin/subjects', [ + 'name' => 'Custom Slug Subject', + 'english_name' => 'Custom Slug Eng', + 'slug' => 'my-special-custom-slug', + 'course' => 'hsc', + 'tailwind_format' => 'bg-slate-500', + 'icon' => 'book-open', + 'sort_order' => 1, + ]); + + $response->assertRedirect(route('admin.subjects.index')); + + $this->assertDatabaseHas('subjects', [ + 'name' => 'Custom Slug Subject', + 'slug' => 'my-special-custom-slug', + ]); +}); + +test('admin can update a subject with custom slug', function () { + $admin = adminUserWithPermissions(['view admin', 'edit subjects']); + + $subject = Subject::create([ + 'name' => 'Existing Subject', + 'slug' => 'existing-subject-slug', + 'course' => 'hsc', + 'tailwind_format' => 'bg-slate-500', + 'icon' => 'book-open', + 'sort_order' => 1, + ]); + + $response = $this->actingAs($admin)->patch("/admin/subjects/edit/{$subject->id}", [ + 'slug' => 'updated-custom-slug', + ]); + + $response->assertRedirect(route('admin.subjects.index')); + + $this->assertDatabaseHas('subjects', [ + 'id' => $subject->id, + 'slug' => 'updated-custom-slug', + ]); +}); + test('invalid subject creation is rejected by validation', function () { $admin = adminUserWithPermissions(['view admin', 'create subjects']); From 6444b1fab0404fdfb0dbe1587006224675989b0b Mon Sep 17 00:00:00 2001 From: Tajim Date: Thu, 27 Aug 2026 12:50:23 +0600 Subject: [PATCH 3/4] fix(admin): unify deterministic hierarchical navigation, replace full page reloads with Link, and fix redirect loops --- app/Http/Controllers/Admin/NodeController.php | 27 ++- .../Controllers/Admin/ResourceController.php | 50 ++++- resources/js/pages/admin/Node.vue | 38 ++-- resources/js/pages/admin/NodeCreateOrEdit.vue | 188 +++++++++-------- resources/js/pages/admin/NoticeEdit.vue | 13 +- .../js/pages/admin/ResourceCreateOrEdit.vue | 6 + .../js/pages/admin/SubjectCreateOrEdit.vue | 194 ++++++++++++++---- .../pages/admin/resources/BulkImageCreate.vue | 6 + .../pages/admin/resources/BulkVideoCreate.vue | 6 + .../js/pages/admin/users/CreateOrEdit.vue | 19 +- 10 files changed, 367 insertions(+), 180 deletions(-) diff --git a/app/Http/Controllers/Admin/NodeController.php b/app/Http/Controllers/Admin/NodeController.php index 685fef1..21beb8c 100644 --- a/app/Http/Controllers/Admin/NodeController.php +++ b/app/Http/Controllers/Admin/NodeController.php @@ -62,21 +62,30 @@ public function create(Subject $subject, Request $request) ->firstOrFail(); } + $prev = url()->previous(); + $redirect = (! empty($prev) && ! str_contains($prev, '/create') && ! str_contains($prev, '/edit')) + ? $prev + : route('admin.nodes.index', ['subject' => $subject->slug]); + return Inertia::render('admin/NodeCreateOrEdit', [ 'subject' => $subject, 'parent' => $parent, - 'redirect' => url()->previous(), + 'redirect' => $redirect, ]); } public function edit(Node $node) { + $prev = url()->previous(); + $redirect = (! empty($prev) && ! str_contains($prev, '/create') && ! str_contains($prev, '/edit')) + ? $prev + : route('admin.nodes.index', ['subject' => $node->subject->slug]); return Inertia::render('admin/NodeCreateOrEdit', [ 'subject' => $node->subject, 'node' => $node, 'parent' => $node->parent, - 'redirect' => url()->previous(), + 'redirect' => $redirect, ]); } @@ -118,7 +127,9 @@ public function store(StoreNodeRequest $request, Subject $subject) 'sort_order' => $validated['sort_order'] ?? 0, ]); - $redirect = $validated['redirect'] ? $validated['redirect'] : explode('/create', url()->previous())[0]; + $redirect = (! empty($validated['redirect']) && ! str_contains($validated['redirect'], '/create') && ! str_contains($validated['redirect'], '/edit')) + ? $validated['redirect'] + : route('admin.nodes.index', ['subject' => $subject->slug]); return redirect($redirect)->with('success', 'Node created successfully.'); } @@ -151,12 +162,8 @@ public function update(UpdateNodeRequest $request, Subject $subject, Node $node) $slug = Str::slug($rawSlug); - $parentId = array_key_exists('parent_id', $validated) - ? $validated['parent_id'] - : $node->parent_id; - $exists = Node::where('subject_id', $subject->id) - ->where('parent_id', $parentId) + ->where('parent_id', $node->parent_id) ->where('slug', $slug) ->where('id', '!=', $node->id) ->exists(); @@ -176,7 +183,9 @@ public function update(UpdateNodeRequest $request, Subject $subject, Node $node) $node->save(); - $redirect = $validated['redirect'] ?? explode('/edit', url()->previous())[0]; + $redirect = (! empty($validated['redirect']) && ! str_contains($validated['redirect'], '/create') && ! str_contains($validated['redirect'], '/edit')) + ? $validated['redirect'] + : route('admin.nodes.index', ['subject' => $subject->slug]); return redirect($redirect)->with('success', 'Node updated successfully.'); } diff --git a/app/Http/Controllers/Admin/ResourceController.php b/app/Http/Controllers/Admin/ResourceController.php index 6ad53f1..08429b0 100644 --- a/app/Http/Controllers/Admin/ResourceController.php +++ b/app/Http/Controllers/Admin/ResourceController.php @@ -22,8 +22,13 @@ public function create(Request $request) { $node = Node::findOrFail($request->node_id); + $prev = url()->previous(); + $redirect = (! empty($prev) && ! str_contains($prev, '/create') && ! str_contains($prev, '/edit') && ! str_contains($prev, '/resources/')) + ? $prev + : '/admin/subjects'; + return Inertia::render('admin/ResourceCreateOrEdit', [ - 'redirect' => url()->previous(), + 'redirect' => $redirect, 'node' => $node, ]); } @@ -32,8 +37,13 @@ public function edit(Resource $resource) { $node = $resource->node; + $prev = url()->previous(); + $redirect = (! empty($prev) && ! str_contains($prev, '/create') && ! str_contains($prev, '/edit') && ! str_contains($prev, '/resources/')) + ? $prev + : '/admin/subjects'; + return Inertia::render('admin/ResourceCreateOrEdit', [ - 'redirect' => url()->previous(), + 'redirect' => $redirect, 'node' => $node, 'resource' => $resource, ]); @@ -51,7 +61,9 @@ public function store(StoreResourceRequest $request) Resource::create($validated); - $redirect = $validated['redirect'] ?? explode('/resources', url()->previous())[0]; + $redirect = (! empty($validated['redirect']) && ! str_contains($validated['redirect'], '/create') && ! str_contains($validated['redirect'], '/edit')) + ? $validated['redirect'] + : '/admin/subjects'; return redirect($redirect)->with('success', 'Resource created successfully.'); } @@ -74,7 +86,9 @@ public function update(UpdateResourceRequest $request, Resource $resource) $resource->update($validated); - $redirect = $validated['redirect'] ?? '/admin/subjects'; + $redirect = (! empty($validated['redirect']) && ! str_contains($validated['redirect'], '/create') && ! str_contains($validated['redirect'], '/edit')) + ? $validated['redirect'] + : '/admin/subjects'; return redirect($redirect) ->with('success', 'Resource updated successfully.'); @@ -93,7 +107,13 @@ public function destroy(Resource $resource) public function createBulkImages(Request $request) { - $redirect = $request->input('redirect', url()->previous()); + $redirect = $request->input('redirect'); + if (empty($redirect) || str_contains($redirect, '/create') || str_contains($redirect, '/edit')) { + $prev = url()->previous(); + $redirect = (! empty($prev) && ! str_contains($prev, '/create') && ! str_contains($prev, '/edit')) + ? $prev + : '/admin/subjects'; + } $node = Node::findOrFail($request->node_id); return Inertia::render('admin/resources/BulkImageCreate', [ @@ -118,12 +138,22 @@ public function storeBulkImages(BulkImageStoreRequest $request) } }); - return redirect($validated['redirect'])->with('success', 'Bulk images uploaded successfully.'); + $redirect = (! empty($validated['redirect']) && ! str_contains($validated['redirect'], '/create') && ! str_contains($validated['redirect'], '/edit')) + ? $validated['redirect'] + : '/admin/subjects'; + + return redirect($redirect)->with('success', 'Bulk images uploaded successfully.'); } public function createBulkVideos(Request $request) { - $redirect = $request->input('redirect', url()->previous()); + $redirect = $request->input('redirect'); + if (empty($redirect) || str_contains($redirect, '/create') || str_contains($redirect, '/edit')) { + $prev = url()->previous(); + $redirect = (! empty($prev) && ! str_contains($prev, '/create') && ! str_contains($prev, '/edit')) + ? $prev + : '/admin/subjects'; + } $node = Node::findOrFail($request->node_id); return Inertia::render('admin/resources/BulkVideoCreate', [ @@ -220,6 +250,10 @@ public function storeBulkVideos(BulkVideoStoreRequest $request) } }); - return redirect($validated['redirect'])->with('success', 'Bulk Videos uploaded successfully.'); + $redirect = (! empty($validated['redirect']) && ! str_contains($validated['redirect'], '/create') && ! str_contains($validated['redirect'], '/edit')) + ? $validated['redirect'] + : '/admin/subjects'; + + return redirect($redirect)->with('success', 'Bulk Videos uploaded successfully.'); } } diff --git a/resources/js/pages/admin/Node.vue b/resources/js/pages/admin/Node.vue index f5924bf..8d9e937 100644 --- a/resources/js/pages/admin/Node.vue +++ b/resources/js/pages/admin/Node.vue @@ -1,5 +1,5 @@