@mundogamernetwork/shared-ui 1.0.3 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/stores/chat.ts CHANGED
@@ -125,6 +125,26 @@ export const useChatStore = defineStore("chat-store", () => {
125
125
  handleChatMessages(chatId);
126
126
  };
127
127
 
128
+ // WS channels: receiver = user.{platform}.chats.{uuid}
129
+ // sender = user.{platform}.chats.sendMessage.{uuid}
130
+ const subscribeToChatChannel = (userUuid: string, platformSlug: string, asSender = false) => {
131
+ if (typeof window === "undefined" || !window.Echo) return;
132
+ const channelName = asSender
133
+ ? `user.${platformSlug}.chats.sendMessage.${userUuid}`
134
+ : `user.${platformSlug}.chats.${userUuid}`;
135
+ window.Echo.private(channelName).listen(".NewChatMessage", (data: any) => {
136
+ callNewMessage(data);
137
+ });
138
+ };
139
+
140
+ const unsubscribeFromChatChannel = (userUuid: string, platformSlug: string, asSender = false) => {
141
+ if (typeof window === "undefined" || !window.Echo) return;
142
+ const channelName = asSender
143
+ ? `user.${platformSlug}.chats.sendMessage.${userUuid}`
144
+ : `user.${platformSlug}.chats.${userUuid}`;
145
+ window.Echo.leaveChannel(`private-${channelName}`);
146
+ };
147
+
128
148
  return {
129
149
  hasNewMessage,
130
150
  currentMessageData,
@@ -142,6 +162,8 @@ export const useChatStore = defineStore("chat-store", () => {
142
162
  substituteUserChat,
143
163
  startPollingForMessages,
144
164
  stopAllPolling,
165
+ subscribeToChatChannel,
166
+ unsubscribeFromChatChannel,
145
167
  };
146
168
  });
147
169
 
@@ -10,7 +10,9 @@ import type { MgNotification } from "../types";
10
10
 
11
11
  export const useNotificationsStore = defineStore("notifications-store", () => {
12
12
  const notifications = ref<MgNotification[]>([]);
13
- const notificationCount = ref(0);
13
+ const unreadCount = ref(0);
14
+ // Alias kept for backward compat
15
+ const notificationCount = unreadCount;
14
16
  const totalNotifications = ref(0);
15
17
  const currentPage = ref(1);
16
18
  const loading = ref(false);
@@ -108,12 +110,46 @@ export const useNotificationsStore = defineStore("notifications-store", () => {
108
110
  };
109
111
 
110
112
  const addNotification = (notification: MgNotification) => {
113
+ const key = (notification as any).notification_id ?? notification.id;
114
+ const exists = key && notifications.value.some((n: any) => ((n.notification_id ?? n.id) === key));
115
+ if (exists) return;
111
116
  notifications.value.unshift(notification);
112
117
  notificationCount.value++;
113
118
  };
114
119
 
120
+ const mapIncomingNotification = (data: any): MgNotification => ({
121
+ type: data.mg_network_system_id ?? data.type ?? null,
122
+ object: data.object ?? data.data?.object ?? null,
123
+ id: data.notification_id ?? data.id ?? null,
124
+ slug: data.slug ?? data.data?.slug ?? null,
125
+ url: data.url ?? data.data?.url ?? null,
126
+ title: data.title ?? data.data?.title ?? "",
127
+ message: data.message ?? data.data?.message ?? "",
128
+ image: data.image ?? data.data?.image ?? "",
129
+ read_at: null,
130
+ created_at: data.created_at_diff ?? data.created_at ?? "",
131
+ });
132
+
133
+ const subscribeToUserChannel = (userId: number | string) => {
134
+ if (typeof window === "undefined" || !window.Echo) return;
135
+ window.Echo.private(`App.Models.User.${userId}`).notification((data: any) => {
136
+ addNotification(mapIncomingNotification(data));
137
+ if (typeof window !== "undefined" && window.Audio) {
138
+ try {
139
+ new Audio("/sounds/notification.mp3").play().catch(() => {});
140
+ } catch {}
141
+ }
142
+ });
143
+ };
144
+
145
+ const unsubscribeFromUserChannel = (userId: number | string) => {
146
+ if (typeof window === "undefined" || !window.Echo) return;
147
+ window.Echo.leaveChannel(`private-App.Models.User.${userId}`);
148
+ };
149
+
115
150
  return {
116
151
  notifications,
152
+ unreadCount,
117
153
  notificationCount,
118
154
  totalNotifications,
119
155
  currentPage,
@@ -125,6 +161,8 @@ export const useNotificationsStore = defineStore("notifications-store", () => {
125
161
  markAllAsRead,
126
162
  removeNotification,
127
163
  addNotification,
164
+ subscribeToUserChannel,
165
+ unsubscribeFromUserChannel,
128
166
  };
129
167
  });
130
168
 
@@ -0,0 +1,40 @@
1
+ import { defineStore } from "pinia";
2
+ import { ref } from "vue";
3
+ import { getUserTestimonials } from "../services/testimoniesService";
4
+
5
+ export const useTestimoniesStore = defineStore("testimonies-store", () => {
6
+ const testimonies = ref<any[]>([]);
7
+ const loading = ref(false);
8
+ const error = ref<string | null>(null);
9
+ const total = ref(0);
10
+ const currentPage = ref(1);
11
+
12
+ const fetchTestimonies = async (params: Record<string, any> = {}) => {
13
+ loading.value = true;
14
+ error.value = null;
15
+ try {
16
+ const response = await getUserTestimonials(params);
17
+ const data = response.data;
18
+ testimonies.value = data.data ?? [];
19
+ total.value = data.meta?.pagination?.total ?? data.meta?.total ?? testimonies.value.length;
20
+ currentPage.value = data.meta?.pagination?.current_page ?? 1;
21
+ } catch {
22
+ error.value = "Failed to fetch testimonials";
23
+ } finally {
24
+ loading.value = false;
25
+ }
26
+ };
27
+
28
+ return {
29
+ testimonies,
30
+ loading,
31
+ error,
32
+ total,
33
+ currentPage,
34
+ fetchTestimonies,
35
+ };
36
+ });
37
+
38
+ if (import.meta.hot) {
39
+ import.meta.hot.accept(acceptHMRUpdate(useTestimoniesStore, import.meta.hot));
40
+ }
@@ -0,0 +1,18 @@
1
+ export function buildLoginUrl(baseUrl: string, currentPath?: string): string {
2
+ const redirectTo = currentPath ?? (typeof window !== "undefined" ? window.location.href : "");
3
+ const params = new URLSearchParams({ redirect_to: redirectTo });
4
+ return `${baseUrl}/login?${params}`;
5
+ }
6
+
7
+ export function buildRegisterUrl(accountsUrl: string, currentPath?: string, systemId?: string): string {
8
+ const redirectTo = currentPath ?? (typeof window !== "undefined" ? window.location.href : "");
9
+ const params = new URLSearchParams({ redirect_to: redirectTo });
10
+ if (systemId) params.set("system_id", systemId);
11
+ return `${accountsUrl}/register?${params}`;
12
+ }
13
+
14
+ export function buildLogoutUrl(apiBaseUrl: string, redirectAfter?: string): string {
15
+ if (!redirectAfter) return `${apiBaseUrl}/logout`;
16
+ const params = new URLSearchParams({ redirect_to: redirectAfter });
17
+ return `${apiBaseUrl}/logout?${params}`;
18
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Centralized session-recovery utilities.
3
+ *
4
+ * The core problem these solve: a stale/invalid `oauth_token` cookie that the
5
+ * browser keeps sending makes every request fail (OAuth rejects it), and the
6
+ * server's Set-Cookie to clear it may not reach the browser (cross-domain /
7
+ * CDN stripping). The user gets stuck — unable to load the site OR log in.
8
+ *
9
+ * The fix: clear the auth credentials CLIENT-SIDE so the next request is a clean
10
+ * guest request and the user can log in again.
11
+ */
12
+
13
+ // Auth-related cookies set across the MGN ecosystem.
14
+ const AUTH_COOKIES = [
15
+ "oauth_token",
16
+ "oauth_state",
17
+ "browser_id",
18
+ "oauth_version",
19
+ "mundo_gamer_network_session",
20
+ "XSRF-TOKEN",
21
+ ];
22
+
23
+ /**
24
+ * Compute every plausible (domain, path) combination a cookie might have been
25
+ * set with, so we can reliably expire it regardless of how the backend scoped it.
26
+ *
27
+ * e.g. host = api.mundogamer.community →
28
+ * domains: ["", "api.mundogamer.community", ".api.mundogamer.community",
29
+ * "mundogamer.community", ".mundogamer.community"]
30
+ */
31
+ function candidateDomains(): string[] {
32
+ if (typeof location === "undefined") return [""];
33
+ const host = location.hostname;
34
+ const domains = new Set<string>([""]); // "" = default (current host, no domain attr)
35
+ domains.add(host);
36
+ domains.add(`.${host}`);
37
+
38
+ const parts = host.split(".");
39
+ // Walk up to the registrable domain: a.b.c.com → b.c.com → c.com
40
+ for (let i = 1; i < parts.length - 1; i++) {
41
+ const parent = parts.slice(i).join(".");
42
+ domains.add(parent);
43
+ domains.add(`.${parent}`);
44
+ }
45
+ return Array.from(domains);
46
+ }
47
+
48
+ /** Expire a single cookie across every plausible domain/path scope. */
49
+ function expireCookie(name: string) {
50
+ if (typeof document === "undefined") return;
51
+ const expires = "expires=Thu, 01 Jan 1970 00:00:00 GMT";
52
+ const paths = ["/", location?.pathname || "/"];
53
+ for (const domain of candidateDomains()) {
54
+ for (const path of paths) {
55
+ const domainAttr = domain ? `;domain=${domain}` : "";
56
+ document.cookie = `${name}=;${expires};path=${path}${domainAttr};SameSite=Lax`;
57
+ // Secure variant for cross-site contexts
58
+ document.cookie = `${name}=;${expires};path=${path}${domainAttr};SameSite=None;Secure`;
59
+ }
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Clear ONLY the auth credentials (cookies + auth store), leaving the user's
65
+ * other client state (cart, preferences, theme) intact. Use this on detected
66
+ * session loss so the user recovers without losing unrelated data.
67
+ */
68
+ export function clearAuthCredentials() {
69
+ if (typeof window === "undefined") return;
70
+ AUTH_COOKIES.forEach(expireCookie);
71
+ try {
72
+ const authStore = useAuthStore();
73
+ authStore.clearUser?.();
74
+ } catch (_) {
75
+ // store not available in this context
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Full client reset — auth cookies + localStorage + sessionStorage + auth store.
81
+ * Use on explicit logout or as the "Reset & Reload" recovery action so a stuck
82
+ * browser is fully cleaned and the user can start fresh.
83
+ */
84
+ export function clearClientSession() {
85
+ if (typeof window === "undefined") return;
86
+ AUTH_COOKIES.forEach(expireCookie);
87
+
88
+ // Also expire ANY cookie currently visible to JS (defensive — catches
89
+ // renamed/legacy auth cookies we don't know about).
90
+ try {
91
+ document.cookie.split(";").forEach((c) => {
92
+ const name = c.split("=")[0].trim();
93
+ if (name) expireCookie(name);
94
+ });
95
+ } catch (_) {}
96
+
97
+ try { localStorage.clear(); } catch (_) {}
98
+ try { sessionStorage.clear(); } catch (_) {}
99
+ try {
100
+ const authStore = useAuthStore();
101
+ authStore.clearUser?.();
102
+ } catch (_) {}
103
+ }