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...

+
+ +
+
+ refresh +
+
+
+ + 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