@mundogamernetwork/shared-ui 1.0.2 → 1.1.0
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/components/errors/401.vue +9 -0
- package/components/errors/403.vue +9 -0
- package/components/errors/404.vue +13 -0
- package/components/errors/500.vue +9 -0
- package/components/errors/503.vue +9 -0
- package/components/errors/504.vue +9 -0
- package/components/errors/MgErrorBase.vue +109 -0
- package/components/errors/MgErrorNewsCta.vue +230 -0
- package/components/ui/MgLoginModal.vue +19 -6
- package/components/ui/MgTestimonials.vue +62 -0
- package/components/ui/MgWalletPlanBadge.vue +95 -0
- package/composables/useConfirmation.ts +117 -0
- package/composables/useForceLogout.ts +73 -0
- package/composables/usePaymentListener.ts +81 -0
- package/error.vue +59 -112
- package/package.json +41 -41
- package/services/httpService.ts +46 -4
- package/services/mediaKitService.ts +6 -6
- package/services/testimoniesService.ts +40 -0
- package/stores/chat.ts +22 -0
- package/stores/notifications.ts +39 -1
- package/stores/testimonies.ts +40 -0
- package/utils/authRedirect.ts +18 -0
- package/utils/clearSession.ts +103 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import httpService from "./httpService";
|
|
2
|
+
|
|
3
|
+
export interface TestimonyParams {
|
|
4
|
+
page?: number;
|
|
5
|
+
per_page?: number;
|
|
6
|
+
[key: string]: any;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const buildQuery = (params: TestimonyParams): string => {
|
|
10
|
+
const search = new URLSearchParams();
|
|
11
|
+
for (const [key, value] of Object.entries(params)) {
|
|
12
|
+
if (value !== undefined && value !== null && value !== "") {
|
|
13
|
+
search.append(key, String(value));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const qs = search.toString();
|
|
17
|
+
return qs ? `?${qs}` : "";
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const getUserTestimonials = (params: TestimonyParams = {}) => {
|
|
21
|
+
return httpService({
|
|
22
|
+
url: `/public/user-testimonials${buildQuery(params)}`,
|
|
23
|
+
baseURL: import.meta.env.VITE_API_BASE_URL,
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const submitUserTestimonial = (data: {
|
|
28
|
+
body: string;
|
|
29
|
+
name: string;
|
|
30
|
+
role?: string;
|
|
31
|
+
company?: string;
|
|
32
|
+
[key: string]: any;
|
|
33
|
+
}) => {
|
|
34
|
+
return httpService({
|
|
35
|
+
method: "POST",
|
|
36
|
+
url: "/public/user-testimonials",
|
|
37
|
+
data,
|
|
38
|
+
baseURL: import.meta.env.VITE_API_BASE_URL,
|
|
39
|
+
});
|
|
40
|
+
};
|
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
|
|
package/stores/notifications.ts
CHANGED
|
@@ -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
|
|
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
|
+
}
|