diff --git a/frontend/src/components/common/LoadMore.vue b/frontend/src/components/common/LoadMore.vue
new file mode 100644
index 0000000..1e8b25f
--- /dev/null
+++ b/frontend/src/components/common/LoadMore.vue
@@ -0,0 +1,17 @@
+
+
+
+
+
diff --git a/frontend/src/pages/relay/MyTasksPage.vue b/frontend/src/pages/relay/MyTasksPage.vue
index 4be4bb8..ef0a472 100644
--- a/frontend/src/pages/relay/MyTasksPage.vue
+++ b/frontend/src/pages/relay/MyTasksPage.vue
@@ -7,6 +7,7 @@ import { RouterLink } from 'vue-router'
import AppShell from '@/layouts/AppShell.vue'
import UserAvatar from '@/components/UserAvatar.vue'
import DropdownSelect from '@/components/DropdownSelect.vue'
+import LoadMore from '@/components/common/LoadMore.vue'
import { useMyTaskStore } from '@/stores/my-task.store'
import { useProjectStore } from '@/stores/project.store'
import { countActive, type MyTaskRow } from '@/mock/relay.mock'
@@ -412,15 +413,11 @@ function loadMore() {
-
+ :loading="loadingMore"
+ @load="loadMore"
+ />
diff --git a/frontend/src/pages/relay/ProjectActivityPage.vue b/frontend/src/pages/relay/ProjectActivityPage.vue
index 4133c9a..db98b15 100644
--- a/frontend/src/pages/relay/ProjectActivityPage.vue
+++ b/frontend/src/pages/relay/ProjectActivityPage.vue
@@ -8,6 +8,7 @@ import ProjectTabs from '@/components/ProjectTabs.vue'
import UserAvatar from '@/components/UserAvatar.vue'
import DropdownSelect from '@/components/DropdownSelect.vue'
import SegmentedTabs from '@/components/common/SegmentedTabs.vue'
+import LoadMore from '@/components/common/LoadMore.vue'
import { useProjectStore } from '@/stores/project.store'
import { useActivityStore } from '@/stores/activity.store'
import type { Project, ActivityIcon } from '@/mock/relay.mock'
@@ -228,20 +229,12 @@ const ActIcon = defineComponent({
-
-
+
-
-
+ :loading="activityStore.loadingMore"
+ @load="activityStore.loadMore()"
+ />
@@ -373,30 +366,6 @@ const ActIcon = defineComponent({
margin-left: 0.75rem;
}
-/* 더 보기 */
-.more-row {
- display: flex;
- justify-content: center;
- margin-top: 0.875rem;
-}
-.more-btn {
- border: 1px solid var(--border);
- background: var(--panel);
- border-radius: 0.5rem;
- padding: 0.5rem 1.25rem;
- font-size: 0.813rem;
- font-weight: 600;
- color: var(--text-2);
- cursor: pointer;
-}
-.more-btn:hover:not(:disabled) {
- background: #f4f5f7;
-}
-.more-btn:disabled {
- opacity: 0.6;
- cursor: default;
-}
-
/* 로딩/빈 상태 */
.feed-state {
background: var(--panel);
diff --git a/frontend/src/pages/relay/ProjectDetailPage.vue b/frontend/src/pages/relay/ProjectDetailPage.vue
index e2f1652..a0a6bbb 100644
--- a/frontend/src/pages/relay/ProjectDetailPage.vue
+++ b/frontend/src/pages/relay/ProjectDetailPage.vue
@@ -10,6 +10,7 @@ import ProjectTabs from '@/components/ProjectTabs.vue'
import UserAvatar from '@/components/UserAvatar.vue'
import DropdownSelect from '@/components/DropdownSelect.vue'
import SegmentedTabs from '@/components/common/SegmentedTabs.vue'
+import LoadMore from '@/components/common/LoadMore.vue'
import { useProjectStore } from '@/stores/project.store'
import { useTaskStore } from '@/stores/task.store'
import type { TaskListQuery, TaskSegment, TaskSort } from '@/types/task'
@@ -405,15 +406,11 @@ const STATUS_LABEL: Record = {
-
+ :loading="loadingMore"
+ @load="loadMoreTasks"
+ />
diff --git a/frontend/src/pages/relay/ProjectListPage.vue b/frontend/src/pages/relay/ProjectListPage.vue
index cfd4cbc..1089f25 100644
--- a/frontend/src/pages/relay/ProjectListPage.vue
+++ b/frontend/src/pages/relay/ProjectListPage.vue
@@ -6,6 +6,7 @@ import { RouterLink } from 'vue-router'
import AppShell from '@/layouts/AppShell.vue'
import UserAvatar from '@/components/UserAvatar.vue'
import DropdownSelect from '@/components/DropdownSelect.vue'
+import LoadMore from '@/components/common/LoadMore.vue'
import { useProjectStore } from '@/stores/project.store'
import type { ProjectListQuery, ProjectSort } from '@/types/project'
@@ -218,15 +219,11 @@ const isFiltered = computed(() => keyword.value.trim() !== '')
-
+ :loading="loadingMore"
+ @load="projectStore.loadMore()"
+ />