diff --git a/frontend/src/layouts/MainLayout.vue b/frontend/src/layouts/MainLayout.vue
index 35e4cbe..845fb5d 100644
--- a/frontend/src/layouts/MainLayout.vue
+++ b/frontend/src/layouts/MainLayout.vue
@@ -5,16 +5,29 @@
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
@@ -29,9 +42,9 @@
GitRec
-
Explore
-
Trending
-
Favorites
+
Explore
+
Trending
+
Favorites
@@ -41,18 +54,10 @@
-
-
@@ -117,47 +122,54 @@
-
diff --git a/frontend/src/mixins/authMixin.js b/frontend/src/mixins/authMixin.js
new file mode 100644
index 0000000..706f57d
--- /dev/null
+++ b/frontend/src/mixins/authMixin.js
@@ -0,0 +1,79 @@
+import axios from 'axios';
+
+const AUTH_CACHE_KEY = 'gitrec_auth_state';
+const AUTH_CACHE_EXPIRY_MS = 5 * 60 * 1000; // 5 minutes
+
+export default {
+ data() {
+ return {
+ isAuthenticated: false,
+ authLogin: null,
+ };
+ },
+ methods: {
+ async checkAuth() {
+ const cached = localStorage.getItem(AUTH_CACHE_KEY);
+ if (cached) {
+ try {
+ const data = JSON.parse(cached);
+ if (Date.now() - data.timestamp < AUTH_CACHE_EXPIRY_MS) {
+ this.isAuthenticated = data.is_authenticated;
+ this.authLogin = data.login;
+ return;
+ }
+ } catch {
+ localStorage.removeItem(AUTH_CACHE_KEY);
+ }
+ }
+
+ try {
+ const response = await axios.get('/api/me', { withCredentials: true });
+ this.isAuthenticated = response.data.is_authenticated;
+ this.authLogin = response.data.login;
+ if (this.isAuthenticated) {
+ localStorage.setItem(AUTH_CACHE_KEY, JSON.stringify({
+ is_authenticated: true,
+ login: response.data.login,
+ timestamp: Date.now()
+ }));
+ this.dispatchAuthChange(true);
+ } else {
+ localStorage.removeItem(AUTH_CACHE_KEY);
+ }
+ } catch {
+ this.isAuthenticated = false;
+ this.authLogin = null;
+ localStorage.removeItem(AUTH_CACHE_KEY);
+ }
+ },
+ async logout() {
+ try {
+ await axios.get('/api/logout', { withCredentials: true });
+ } catch (error) {
+ console.error('Logout failed:', error);
+ }
+ localStorage.removeItem(AUTH_CACHE_KEY);
+ this.isAuthenticated = false;
+ this.authLogin = null;
+ this.dispatchAuthChange(false);
+ },
+ dispatchAuthChange(authenticated) {
+ window.dispatchEvent(new CustomEvent('gitrec-auth-change', {
+ detail: { authenticated }
+ }));
+ },
+ handleAuthChange(event) {
+ this.isAuthenticated = event.detail.authenticated;
+ if (!event.detail.authenticated) {
+ this.authLogin = null;
+ }
+ },
+ },
+ mounted() {
+ this.checkAuth();
+ window.addEventListener('gitrec-auth-change', this.handleAuthChange);
+ },
+ beforeUnmount() {
+ window.removeEventListener('gitrec-auth-change', this.handleAuthChange);
+ },
+};
diff --git a/frontend/src/views/Favorites.vue b/frontend/src/views/Favorites.vue
index 9839bb1..ebf468c 100644
--- a/frontend/src/views/Favorites.vue
+++ b/frontend/src/views/Favorites.vue
@@ -1,33 +1,46 @@
-
-
-
-
-
-
- {{ feedbackIcon(feedback.FeedbackType) }}
-
+
+
+
+ {{ error }}
+
+
+
+
+
+
+
+
+
+ {{ feedbackIcon(feedback.FeedbackType) }}
+
- {{ feedback.ItemId }}
+ {{ feedback.ItemId }}
-
- {{ formatTime(feedback.Timestamp) }}
-
-
-
+
+ {{ formatTime(feedback.Timestamp) }}
+
+
+
-
-
+
-
-
+
+
diff --git a/frontend/src/views/Home.vue b/frontend/src/views/Home.vue
index d9ddf4d..d1f602b 100644
--- a/frontend/src/views/Home.vue
+++ b/frontend/src/views/Home.vue
@@ -2,7 +2,7 @@
-
+
+
+ {{ error }}
+
+
+
+
-
-
+
+
{{ like_pressed ? "mdi-heart" : "mdi-heart-outline" }}
-
+
mdi-play
@@ -66,100 +80,80 @@
-