diff --git a/app/Containers/Dashboard/Actions/Pages/ReorderPagesAction.php b/app/Containers/Dashboard/Actions/Pages/ReorderPagesAction.php
new file mode 100644
index 0000000..74dba4b
--- /dev/null
+++ b/app/Containers/Dashboard/Actions/Pages/ReorderPagesAction.php
@@ -0,0 +1,41 @@
+ $pageId) {
+ Page::where('id', $pageId)
+ ->where('sub_section_id', $subSectionId)
+ ->update(['sort' => $index + 1]);
+ }
+
+ // Clear navigation cache since sort order changed
+ Cache::forget('navigation');
+
+ // Clear page data cache for all pages in this subsection
+ // so that section.pages relation is re-fetched with correct order
+ $pages = Page::where('sub_section_id', $subSectionId)->get();
+ foreach ($pages as $page) {
+ $cacheKey = 'page_data_' . md5($page->path);
+ Cache::forget($cacheKey);
+ }
+
+ return true;
+ });
+ }
+}
diff --git a/app/Observers/PageObserver.php b/app/Observers/PageObserver.php
index 37f966b..c9a49b2 100755
--- a/app/Observers/PageObserver.php
+++ b/app/Observers/PageObserver.php
@@ -29,6 +29,11 @@ class PageObserver
public function updated(Page $page): void
{
$this->pageCacheService->clearAllCacheByModel();
+
+ // Clear navigation cache if sort order changed
+ if ($page->isDirty('sort')) {
+ Cache::forget('navigation');
+ }
}
/**
diff --git a/database/migrations/2026_04_10_081834_add_sort_column_to_pages_table.php b/database/migrations/2026_04_10_081834_add_sort_column_to_pages_table.php
new file mode 100644
index 0000000..d1d0838
--- /dev/null
+++ b/database/migrations/2026_04_10_081834_add_sort_column_to_pages_table.php
@@ -0,0 +1,28 @@
+integer('sort')->default(0)->after('sub_section_id');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('pages', function (Blueprint $table) {
+ $table->dropColumn('sort');
+ });
+ }
+};
diff --git a/resources/js/Pages/Dashboard/Components/ContentBuilder/blocks/ImageBlock.vue b/resources/js/Pages/Dashboard/Components/ContentBuilder/blocks/ImageBlock.vue
index 34f0458..ca8fb15 100644
--- a/resources/js/Pages/Dashboard/Components/ContentBuilder/blocks/ImageBlock.vue
+++ b/resources/js/Pages/Dashboard/Components/ContentBuilder/blocks/ImageBlock.vue
@@ -6,7 +6,7 @@
-
![Preview]()
+
@@ -56,22 +58,50 @@ export default {
required: true
}
},
- emits: ['update:modelValue'],
+ emits: ['update:modelValue', 'update'],
+ data() {
+ return {
+ uploading: false,
+ };
+ },
methods: {
update(field, value) {
this.$emit('update:modelValue', {
...this.modelValue,
[field]: value
});
+ this.$emit('update');
},
- handleFileUpload(event) {
+ async handleFileUpload(event) {
const file = event.target.files[0];
- if (file) {
- const reader = new FileReader();
- reader.onload = (e) => {
- this.update('url', e.target.result);
- };
- reader.readAsDataURL(file);
+ if (!file) return;
+
+ this.uploading = true;
+
+ try {
+ const formData = new FormData();
+ formData.append('images[]', file);
+
+ const response = await fetch(route('dashboard.posts.upload-images'), {
+ method: 'POST',
+ headers: {
+ 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
+ 'Accept': 'application/json',
+ },
+ body: formData,
+ });
+
+ const result = await response.json();
+
+ if (result.success && result.paths && result.paths.length > 0) {
+ this.update('url', result.paths[0]);
+ }
+ } catch (error) {
+ console.error('Image upload error:', error);
+ } finally {
+ this.uploading = false;
+ // Reset input so the same file can be selected again
+ event.target.value = '';
}
}
}
diff --git a/resources/js/Pages/Dashboard/Components/ContentBuilder/blocks/ImagesBlock.vue b/resources/js/Pages/Dashboard/Components/ContentBuilder/blocks/ImagesBlock.vue
index cff9b62..c2eceaa 100644
--- a/resources/js/Pages/Dashboard/Components/ContentBuilder/blocks/ImagesBlock.vue
+++ b/resources/js/Pages/Dashboard/Components/ContentBuilder/blocks/ImagesBlock.vue
@@ -10,7 +10,7 @@
:key="index"
class="relative group"
>
-
![Preview]()
+
@@ -60,29 +62,58 @@ export default {
required: true
}
},
- emits: ['update:modelValue'],
+ emits: ['update:modelValue', 'update'],
+ data() {
+ return {
+ uploading: false,
+ };
+ },
methods: {
update(field, value) {
this.$emit('update:modelValue', {
...this.modelValue,
[field]: value
});
+ this.$emit('update');
},
- handleFilesUpload(event) {
+ async handleFilesUpload(event) {
const files = Array.from(event.target.files);
+ if (files.length === 0) return;
+
const remaining = 5 - this.modelValue.url.length;
const toProcess = files.slice(0, remaining);
+ if (toProcess.length === 0) return;
- let processed = 0;
- toProcess.forEach((file) => {
- const reader = new FileReader();
- reader.onload = (e) => {
- const newUrls = [...this.modelValue.url, e.target.result];
+ this.uploading = true;
+
+ try {
+ const formData = new FormData();
+ toProcess.forEach(file => {
+ formData.append('images[]', file);
+ });
+
+ const response = await fetch(route('dashboard.posts.upload-images'), {
+ method: 'POST',
+ headers: {
+ 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
+ 'Accept': 'application/json',
+ },
+ body: formData,
+ });
+
+ const result = await response.json();
+
+ if (result.success && result.paths) {
+ const newUrls = [...this.modelValue.url, ...result.paths];
this.update('url', newUrls);
- processed++;
- };
- reader.readAsDataURL(file);
- });
+ }
+ } catch (error) {
+ console.error('Images upload error:', error);
+ } finally {
+ this.uploading = false;
+ // Reset input so the same files can be selected again
+ event.target.value = '';
+ }
},
removeImage(index) {
const newUrls = this.modelValue.url.filter((_, i) => i !== index);
diff --git a/resources/js/componentss/shared/builder/pageBuilder/blocks/BasicImageSlider.vue b/resources/js/componentss/shared/builder/pageBuilder/blocks/BasicImageSlider.vue
index 027cfef..8e981d2 100755
--- a/resources/js/componentss/shared/builder/pageBuilder/blocks/BasicImageSlider.vue
+++ b/resources/js/componentss/shared/builder/pageBuilder/blocks/BasicImageSlider.vue
@@ -1,5 +1,5 @@
-
+
-
+
{{ block.data.alt }}
-
+