diff --git a/src/App.vue b/src/App.vue
index 4387665..fc46055 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -17,6 +17,7 @@ export default {
currentTheme: "auto",
darkModeQuery: null,
themeChangeHandler: null,
+ isLoading: false,
};
},
@@ -176,13 +177,21 @@ export default {
},
fetchDeals() {
- axios
- .get("api/v1/topics")
- .then((response) => {
+ this.isLoading = true;
+ const minLoadingTime = new Promise(resolve => setTimeout(resolve, 500));
+
+ Promise.all([
+ axios.get("api/v1/topics"),
+ minLoadingTime
+ ])
+ .then(([response]) => {
this.topics = response.data;
})
.catch((err) => {
console.error("Failed to fetch deals:", err.response || err);
+ })
+ .finally(() => {
+ this.isLoading = false;
});
},
@@ -216,6 +225,9 @@ export default {
@keyup.enter="createFilterRoute(filter.toString())"
@keyup.esc="$refs.filter.blur()"
/>
+
@@ -225,7 +237,16 @@ export default {
-
+
+
refresh
+
Loading deals...
+
+
+
+
+
diff --git a/src/theme.css b/src/theme.css
index 32fb2fd..0ad2530 100644
--- a/src/theme.css
+++ b/src/theme.css
@@ -437,4 +437,49 @@ mark {
.search-input {
max-width: 100%;
}
+}
+
+/* ============================================
+ Loading & Spinner
+ ============================================ */
+
+@keyframes spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+.spinning {
+ animation: spin 1s linear infinite;
+}
+
+.loading-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 60px 20px;
+ color: var(--text-secondary);
+}
+
+.loading-container p {
+ margin-top: 16px;
+ font-size: 14px;
+}
+
+.loading-spinner {
+ font-size: 48px;
+}
+
+.icon-button:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
+}
+
+.icon-button:disabled:hover {
+ background-color: var(--bg-input);
+ border-color: var(--border-color-light);
}
\ No newline at end of file