@ozdao/martyrs 0.2.581 → 0.2.583
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/dist/martyrs/src/components/Feed/Feed.vue.js +1 -1
- package/dist/martyrs/src/modules/auth/views/components/pages/Profile.vue.js +6 -12
- package/dist/martyrs/src/modules/auth/views/components/pages/Profile.vue.js.map +1 -1
- package/dist/martyrs/src/modules/auth/views/components/pages/ProfileEdit.vue.js +1 -1
- package/dist/martyrs/src/modules/auth/views/components/pages/ProfileEdit.vue.js.map +1 -1
- package/dist/martyrs/src/modules/auth/views/components/pages/UserDashboard.vue.js +182 -89
- package/dist/martyrs/src/modules/auth/views/components/pages/UserDashboard.vue.js.map +1 -1
- package/dist/martyrs/src/modules/auth/views/configs/navigation.user.config.js +11 -5
- package/dist/martyrs/src/modules/auth/views/configs/navigation.user.config.js.map +1 -1
- package/dist/martyrs/src/modules/auth/views/router/users.router.js +1 -2
- package/dist/martyrs/src/modules/auth/views/router/users.router.js.map +1 -1
- package/dist/martyrs/src/modules/backoffice/components/partials/Sidebar.vue.js +1 -1
- package/dist/martyrs/src/modules/core/views/classes/ws.manager.js +16 -1
- package/dist/martyrs/src/modules/core/views/classes/ws.manager.js.map +1 -1
- package/dist/martyrs/src/modules/core/views/components/sections/{Filters.vue.js → Filters.vue2.js} +2 -2
- package/dist/martyrs/src/modules/core/views/components/sections/Filters.vue2.js.map +1 -0
- package/dist/martyrs/src/modules/core/views/utils/vue-app-renderer.js +7 -0
- package/dist/martyrs/src/modules/core/views/utils/vue-app-renderer.js.map +1 -1
- package/dist/martyrs/src/modules/events/components/pages/EditEvent.vue.js +1 -1
- package/dist/martyrs/src/modules/events/components/pages/Event.vue.js +1 -1
- package/dist/martyrs/src/modules/gallery/components/sections/BackofficeGallery.vue.js +1 -1
- package/dist/martyrs/src/modules/marketplace/views/components/pages/Marketplace.vue.js +1 -1
- package/dist/martyrs/src/modules/notifications/components/elements/NotificationBadge.vue.js +3 -3
- package/dist/martyrs/src/modules/notifications/components/elements/NotificationBadge.vue.js.map +1 -1
- package/dist/martyrs/src/modules/notifications/components/layouts/NotificationsLayout.vue.js +11 -40
- package/dist/martyrs/src/modules/notifications/components/layouts/NotificationsLayout.vue.js.map +1 -1
- package/dist/martyrs/src/modules/notifications/components/pages/Notifications.vue.js +26 -21
- package/dist/martyrs/src/modules/notifications/components/pages/Notifications.vue.js.map +1 -1
- package/dist/martyrs/src/modules/notifications/components/sections/NotificationPreferences.vue.js +54 -48
- package/dist/martyrs/src/modules/notifications/components/sections/NotificationPreferences.vue.js.map +1 -1
- package/dist/martyrs/src/modules/notifications/components/sections/NotificationsList.vue.js +37 -117
- package/dist/martyrs/src/modules/notifications/components/sections/NotificationsList.vue.js.map +1 -1
- package/dist/martyrs/src/modules/notifications/notifications.client.js +18 -15
- package/dist/martyrs/src/modules/notifications/notifications.client.js.map +1 -1
- package/dist/martyrs/src/modules/orders/components/blocks/CardOrderUser.vue.js +90 -175
- package/dist/martyrs/src/modules/orders/components/blocks/CardOrderUser.vue.js.map +1 -1
- package/dist/martyrs/src/modules/orders/components/pages/OrderBackoffice.vue.js +6 -6
- package/dist/martyrs/src/modules/orders/components/pages/OrderBackoffice.vue.js.map +1 -1
- package/dist/martyrs/src/modules/orders/components/pages/Orders.vue.js +41 -28
- package/dist/martyrs/src/modules/orders/components/pages/Orders.vue.js.map +1 -1
- package/dist/martyrs/src/modules/orders/components/sections/FormDelivery.vue.js +1 -1
- package/dist/martyrs/src/modules/orders/orders.client.js +14 -14
- package/dist/martyrs/src/modules/orders/orders.client.js.map +1 -1
- package/dist/martyrs/src/modules/organizations/components/pages/OrganizationBackoffice.vue.js +1 -1
- package/dist/martyrs/src/modules/organizations/components/pages/OrganizationEdit.vue.js +88 -39
- package/dist/martyrs/src/modules/organizations/components/pages/OrganizationEdit.vue.js.map +1 -1
- package/dist/martyrs/src/modules/products/components/pages/CategoryEdit.vue.js +1 -1
- package/dist/martyrs/src/modules/products/components/pages/ProductEdit.vue.js +1 -1
- package/dist/martyrs/src/modules/products/components/pages/Products.vue.js +1 -1
- package/dist/style.css +73 -265
- package/package.json +1 -1
- package/src/modules/TASKS.MD +26 -1
- package/src/modules/auth/views/components/pages/Profile.vue +9 -15
- package/src/modules/auth/views/components/pages/ProfileEdit.vue +1 -1
- package/src/modules/auth/views/components/pages/UserDashboard.vue +214 -125
- package/src/modules/auth/views/configs/navigation.user.config.js +17 -11
- package/src/modules/auth/views/router/users.router.js +0 -1
- package/src/modules/core/views/classes/ws.manager.js +20 -1
- package/src/modules/core/views/utils/vue-app-renderer.js +9 -3
- package/src/modules/notifications/components/elements/NotificationBadge.vue +1 -1
- package/src/modules/notifications/components/layouts/NotificationsLayout.vue +9 -53
- package/src/modules/notifications/components/pages/Notifications.vue +21 -22
- package/src/modules/notifications/components/sections/NotificationPreferences.vue +41 -180
- package/src/modules/notifications/components/sections/NotificationsList.vue +39 -219
- package/src/modules/notifications/notifications.client.js +17 -16
- package/src/modules/orders/components/blocks/CardOrderUser.vue +88 -190
- package/src/modules/orders/components/pages/OrderBackoffice.vue +5 -5
- package/src/modules/orders/components/pages/Orders.vue +56 -50
- package/src/modules/organizations/components/pages/OrganizationEdit.vue +42 -11
- package/dist/martyrs/src/components/SelectMulti/SelectMulti.vue.js +0 -625
- package/dist/martyrs/src/components/SelectMulti/SelectMulti.vue.js.map +0 -1
- package/dist/martyrs/src/modules/auth/views/components/blocks/ProfileCard.vue.js +0 -44
- package/dist/martyrs/src/modules/auth/views/components/blocks/ProfileCard.vue.js.map +0 -1
- package/dist/martyrs/src/modules/core/views/components/sections/Filters.vue.js.map +0 -1
- package/dist/martyrs/src/modules/orders/components/icons/IconStatusCanceled.vue.js +0 -32
- package/dist/martyrs/src/modules/orders/components/icons/IconStatusCanceled.vue.js.map +0 -1
- package/dist/martyrs/src/modules/orders/components/icons/IconStatusConfirmed.vue.js +0 -32
- package/dist/martyrs/src/modules/orders/components/icons/IconStatusConfirmed.vue.js.map +0 -1
- package/dist/martyrs/src/modules/orders/components/icons/IconStatusCreated.vue.js +0 -32
- package/dist/martyrs/src/modules/orders/components/icons/IconStatusCreated.vue.js.map +0 -1
- package/dist/martyrs/src/modules/orders/components/icons/IconStatusDelay.vue.js +0 -32
- package/dist/martyrs/src/modules/orders/components/icons/IconStatusDelay.vue.js.map +0 -1
- package/dist/martyrs/src/modules/orders/components/icons/IconStatusFinished.vue.js +0 -32
- package/dist/martyrs/src/modules/orders/components/icons/IconStatusFinished.vue.js.map +0 -1
- package/dist/martyrs/src/modules/orders/components/icons/IconStatusInUse.vue.js +0 -32
- package/dist/martyrs/src/modules/orders/components/icons/IconStatusInUse.vue.js.map +0 -1
|
@@ -1,51 +1,49 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
<div class="loading-spinner">🔄</div>
|
|
8
|
-
<p>Loading preferences...</p>
|
|
2
|
+
<div>
|
|
3
|
+
<p class="t-transp mn-b-small">Choose how you want to receive notifications</p>
|
|
4
|
+
|
|
5
|
+
<div v-if="loading" class="pd-large t-center t-transp">
|
|
6
|
+
<Loader />
|
|
9
7
|
</div>
|
|
10
|
-
|
|
11
|
-
<div v-else class="
|
|
12
|
-
<div
|
|
13
|
-
v-for="(enabled, channelType) in preferences"
|
|
14
|
-
:key="channelType"
|
|
15
|
-
class="
|
|
8
|
+
|
|
9
|
+
<div v-else class="gap-thin flex-column flex">
|
|
10
|
+
<div
|
|
11
|
+
v-for="(enabled, channelType) in preferences"
|
|
12
|
+
:key="channelType"
|
|
13
|
+
class="flex flex-v-center flex-between bg-light radius-small pd-small"
|
|
16
14
|
>
|
|
17
|
-
<div class="
|
|
18
|
-
<div class="
|
|
19
|
-
<div class="
|
|
20
|
-
<
|
|
21
|
-
<p>{{ getChannelDescription(channelType) }}</p>
|
|
15
|
+
<div class="flex w-100 flex-v-center gap-small mn-r-small">
|
|
16
|
+
<div class="h4">{{ getChannelIcon(channelType) }}</div>
|
|
17
|
+
<div class="flex-column flex gap-micro">
|
|
18
|
+
<p class="fw-medium lh-1">{{ getChannelName(channelType) }}</p>
|
|
19
|
+
<p class="p-small t-transp lh-1">{{ getChannelDescription(channelType) }}</p>
|
|
22
20
|
</div>
|
|
23
21
|
</div>
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
<span class="toggle-slider"></span>
|
|
31
|
-
</label>
|
|
22
|
+
<Checkbox
|
|
23
|
+
mode="switch"
|
|
24
|
+
:radio="enabled"
|
|
25
|
+
@update:radio="updatePreference(channelType, $event)"
|
|
26
|
+
class="flex-shrink-0"
|
|
27
|
+
/>
|
|
32
28
|
</div>
|
|
33
|
-
|
|
34
|
-
<div class="
|
|
35
|
-
<
|
|
36
|
-
|
|
37
|
-
:
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
</button>
|
|
42
|
-
<button
|
|
43
|
-
v-if="hasChanges"
|
|
44
|
-
class="cancel-btn"
|
|
45
|
-
@click="resetChanges"
|
|
29
|
+
|
|
30
|
+
<div class="flex flex-end gap-small mn-t-thin">
|
|
31
|
+
<Button
|
|
32
|
+
v-if="hasChanges"
|
|
33
|
+
:submit="resetChanges"
|
|
34
|
+
:showLoader="false"
|
|
35
|
+
:showSucces="false"
|
|
36
|
+
class="bg-white t-dark"
|
|
46
37
|
>
|
|
47
38
|
Cancel
|
|
48
|
-
</
|
|
39
|
+
</Button>
|
|
40
|
+
<Button
|
|
41
|
+
:submit="savePreferences"
|
|
42
|
+
:validation="!hasChanges || saving"
|
|
43
|
+
class="bg-main t-white"
|
|
44
|
+
>
|
|
45
|
+
{{ saving ? 'Saving...' : 'Save Changes' }}
|
|
46
|
+
</Button>
|
|
49
47
|
</div>
|
|
50
48
|
</div>
|
|
51
49
|
</div>
|
|
@@ -53,6 +51,9 @@
|
|
|
53
51
|
|
|
54
52
|
<script setup>
|
|
55
53
|
import { ref, reactive, computed, onMounted, inject } from 'vue';
|
|
54
|
+
import Checkbox from '@martyrs/src/components/Checkbox/Checkbox.vue';
|
|
55
|
+
import Button from '@martyrs/src/components/Button/Button.vue';
|
|
56
|
+
import Loader from '@martyrs/src/components/Loader/Loader.vue';
|
|
56
57
|
|
|
57
58
|
// Get notification preferences functionality
|
|
58
59
|
const {
|
|
@@ -154,143 +155,3 @@ onMounted(async () => {
|
|
|
154
155
|
});
|
|
155
156
|
</script>
|
|
156
157
|
|
|
157
|
-
<style scoped>
|
|
158
|
-
|
|
159
|
-
.description {
|
|
160
|
-
color: #666;
|
|
161
|
-
margin-bottom: 24px;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
.preferences-loading {
|
|
165
|
-
display: flex;
|
|
166
|
-
flex-direction: column;
|
|
167
|
-
align-items: center;
|
|
168
|
-
justify-content: center;
|
|
169
|
-
padding: 48px 0;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
.loading-spinner {
|
|
173
|
-
font-size: 2rem;
|
|
174
|
-
margin-bottom: 16px;
|
|
175
|
-
animation: spin 2s linear infinite;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
@keyframes spin {
|
|
179
|
-
0% { transform: rotate(0deg); }
|
|
180
|
-
100% { transform: rotate(360deg); }
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
.preference-item {
|
|
184
|
-
display: flex;
|
|
185
|
-
align-items: center;
|
|
186
|
-
justify-content: space-between;
|
|
187
|
-
padding: 16px;
|
|
188
|
-
border-bottom: 1px solid #eee;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
.preference-item:last-child {
|
|
192
|
-
border-bottom: none;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
.preference-info {
|
|
196
|
-
display: flex;
|
|
197
|
-
align-items: center;
|
|
198
|
-
flex: 1;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
.preference-icon {
|
|
202
|
-
font-size: 1.5rem;
|
|
203
|
-
margin-right: 16px;
|
|
204
|
-
width: 40px;
|
|
205
|
-
height: 40px;
|
|
206
|
-
display: flex;
|
|
207
|
-
align-items: center;
|
|
208
|
-
justify-content: center;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
.preference-details h3 {
|
|
212
|
-
margin: 0 0 4px 0;
|
|
213
|
-
font-size: 1rem;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
.preference-details p {
|
|
217
|
-
margin: 0;
|
|
218
|
-
color: #666;
|
|
219
|
-
font-size: 0.875rem;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/* Toggle switch styles */
|
|
223
|
-
.toggle-switch {
|
|
224
|
-
position: relative;
|
|
225
|
-
display: inline-block;
|
|
226
|
-
width: 50px;
|
|
227
|
-
height: 24px;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
.toggle-switch input {
|
|
231
|
-
opacity: 0;
|
|
232
|
-
width: 0;
|
|
233
|
-
height: 0;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
.toggle-slider {
|
|
237
|
-
position: absolute;
|
|
238
|
-
cursor: pointer;
|
|
239
|
-
top: 0;
|
|
240
|
-
left: 0;
|
|
241
|
-
right: 0;
|
|
242
|
-
bottom: 0;
|
|
243
|
-
background-color: #ccc;
|
|
244
|
-
transition: .4s;
|
|
245
|
-
border-radius: 24px;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
.toggle-slider:before {
|
|
249
|
-
position: absolute;
|
|
250
|
-
content: "";
|
|
251
|
-
height: 16px;
|
|
252
|
-
width: 16px;
|
|
253
|
-
left: 4px;
|
|
254
|
-
bottom: 4px;
|
|
255
|
-
background-color: white;
|
|
256
|
-
transition: .4s;
|
|
257
|
-
border-radius: 50%;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
input:checked + .toggle-slider {
|
|
261
|
-
background-color: #2196F3;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
input:checked + .toggle-slider:before {
|
|
265
|
-
transform: translateX(26px);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
.form-actions {
|
|
269
|
-
margin-top: 24px;
|
|
270
|
-
display: flex;
|
|
271
|
-
justify-content: flex-end;
|
|
272
|
-
gap: 16px;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
.save-btn, .cancel-btn {
|
|
276
|
-
padding: 8px 16px;
|
|
277
|
-
border-radius: 4px;
|
|
278
|
-
cursor: pointer;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
.save-btn {
|
|
282
|
-
background-color: #2196F3;
|
|
283
|
-
border: none;
|
|
284
|
-
color: white;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
.save-btn:disabled {
|
|
288
|
-
background-color: #ccc;
|
|
289
|
-
cursor: not-allowed;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
.cancel-btn {
|
|
293
|
-
background-color: white;
|
|
294
|
-
border: 1px solid #ccc;
|
|
295
|
-
}
|
|
296
|
-
</style>
|
|
@@ -1,111 +1,52 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
</div>
|
|
32
|
-
|
|
33
|
-
<div v-else-if="filteredNotifications.length === 0" class="notifications-empty">
|
|
34
|
-
<p>{{ emptyMessage }}</p>
|
|
35
|
-
</div>
|
|
36
|
-
|
|
37
|
-
<div v-else class="notifications-items">
|
|
38
|
-
<notification-item
|
|
39
|
-
v-for="notification in filteredNotifications"
|
|
40
|
-
:key="notification._id"
|
|
41
|
-
:notification="notification"
|
|
42
|
-
@click="handleNotificationClick(notification)"
|
|
43
|
-
/>
|
|
44
|
-
</div>
|
|
45
|
-
|
|
46
|
-
<div v-if="!loading && notifications.length > 0" class="notifications-footer">
|
|
47
|
-
<button v-if="lastSync" class="refresh-btn" @click="refreshNotifications">
|
|
48
|
-
🔄 Last updated: {{ formatTime(lastSync) }}
|
|
49
|
-
</button>
|
|
50
|
-
</div>
|
|
51
|
-
</div>
|
|
2
|
+
<Feed
|
|
3
|
+
:states="{
|
|
4
|
+
empty: {
|
|
5
|
+
title: 'No Notifications',
|
|
6
|
+
description: 'You have no notifications yet'
|
|
7
|
+
}
|
|
8
|
+
}"
|
|
9
|
+
:store="{
|
|
10
|
+
read: fetchNotifications
|
|
11
|
+
}"
|
|
12
|
+
:options="{
|
|
13
|
+
limit: 20
|
|
14
|
+
}"
|
|
15
|
+
:skeleton="{
|
|
16
|
+
structure: [
|
|
17
|
+
{ block: 'text', size: 'small' },
|
|
18
|
+
{ block: 'text', size: 'large' }
|
|
19
|
+
]
|
|
20
|
+
}"
|
|
21
|
+
v-slot="{ items }"
|
|
22
|
+
class="gap-thin cols-1"
|
|
23
|
+
>
|
|
24
|
+
<NotificationItem
|
|
25
|
+
v-for="notification in items"
|
|
26
|
+
:key="notification._id"
|
|
27
|
+
:notification="notification"
|
|
28
|
+
@click="handleNotificationClick(notification)"
|
|
29
|
+
/>
|
|
30
|
+
</Feed>
|
|
52
31
|
</template>
|
|
53
32
|
|
|
54
33
|
<script setup>
|
|
55
|
-
import {
|
|
56
|
-
import
|
|
34
|
+
import { inject } from 'vue';
|
|
35
|
+
import Feed from '@martyrs/src/components/Feed/Feed.vue';
|
|
57
36
|
import NotificationItem from '../blocks/NotificationItem.vue';
|
|
58
37
|
import * as auth from '@martyrs/src/modules/auth/views/store/auth.js';
|
|
38
|
+
import { actions } from '@martyrs/src/modules/notifications/store/notifications.store.js';
|
|
59
39
|
|
|
60
|
-
|
|
61
|
-
const router = useRouter();
|
|
62
|
-
const {
|
|
63
|
-
notifications,
|
|
64
|
-
unreadCount,
|
|
65
|
-
loading,
|
|
66
|
-
lastSync,
|
|
67
|
-
markAllAsRead,
|
|
68
|
-
getNotifications,
|
|
69
|
-
handleNotificationAction
|
|
70
|
-
} = inject('useNotifications')();
|
|
40
|
+
const { handleNotificationAction } = inject('useNotifications')();
|
|
71
41
|
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
// Computed properties
|
|
76
|
-
const filteredNotifications = computed(() => {
|
|
77
|
-
let filtered = [...notifications.value];
|
|
78
|
-
|
|
79
|
-
// Apply filters
|
|
80
|
-
if (filterType.value === 'unread') {
|
|
81
|
-
filtered = filtered.filter(n => n.status !== 'read');
|
|
82
|
-
} else if (filterType.value === 'read') {
|
|
83
|
-
filtered = filtered.filter(n => n.status === 'read');
|
|
84
|
-
} else if (filterType.value !== 'all') {
|
|
85
|
-
// Filter by notification type
|
|
86
|
-
filtered = filtered.filter(n => n.type === filterType.value);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Sort by creation date (newest first)
|
|
90
|
-
return filtered.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
|
|
91
|
-
});
|
|
42
|
+
const fetchNotifications = async (options) => {
|
|
43
|
+
const userId = auth.state.user._id;
|
|
44
|
+
if (!userId) return [];
|
|
92
45
|
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
switch (filterType.value) {
|
|
99
|
-
case 'unread':
|
|
100
|
-
return 'You have no unread notifications';
|
|
101
|
-
case 'read':
|
|
102
|
-
return 'You have no read notifications';
|
|
103
|
-
default:
|
|
104
|
-
return `You have no ${filterType.value} notifications`;
|
|
105
|
-
}
|
|
106
|
-
});
|
|
46
|
+
const data = await actions.getNotifications(userId);
|
|
47
|
+
return data || [];
|
|
48
|
+
};
|
|
107
49
|
|
|
108
|
-
// Methods
|
|
109
50
|
const handleNotificationClick = (notification) => {
|
|
110
51
|
if (notification._id) {
|
|
111
52
|
handleNotificationAction({
|
|
@@ -114,125 +55,4 @@ const handleNotificationClick = (notification) => {
|
|
|
114
55
|
});
|
|
115
56
|
}
|
|
116
57
|
};
|
|
117
|
-
|
|
118
|
-
const refreshNotifications = () => {
|
|
119
|
-
const userId = auth.state.user._id;
|
|
120
|
-
if (userId) {
|
|
121
|
-
getNotifications(userId);
|
|
122
|
-
}
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
// Format relative time without external libraries
|
|
126
|
-
const formatTime = (timestamp) => {
|
|
127
|
-
if (!timestamp) return '';
|
|
128
|
-
|
|
129
|
-
const now = new Date();
|
|
130
|
-
const date = new Date(timestamp);
|
|
131
|
-
const diffSeconds = Math.floor((now - date) / 1000);
|
|
132
|
-
|
|
133
|
-
// Format based on how long ago
|
|
134
|
-
if (diffSeconds < 60) {
|
|
135
|
-
return 'Just now';
|
|
136
|
-
} else if (diffSeconds < 3600) {
|
|
137
|
-
const minutes = Math.floor(diffSeconds / 60);
|
|
138
|
-
return `${minutes} ${minutes === 1 ? 'minute' : 'minutes'} ago`;
|
|
139
|
-
} else if (diffSeconds < 86400) {
|
|
140
|
-
const hours = Math.floor(diffSeconds / 3600);
|
|
141
|
-
return `${hours} ${hours === 1 ? 'hour' : 'hours'} ago`;
|
|
142
|
-
} else {
|
|
143
|
-
// Format date string
|
|
144
|
-
return date.toLocaleDateString(undefined, {
|
|
145
|
-
year: 'numeric',
|
|
146
|
-
month: 'short',
|
|
147
|
-
day: 'numeric',
|
|
148
|
-
hour: '2-digit',
|
|
149
|
-
minute: '2-digit'
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
// Lifecycle
|
|
155
|
-
onMounted(() => {
|
|
156
|
-
const userId = auth.state.user._id;
|
|
157
|
-
if (userId && notifications.value.length === 0 && !loading.value) {
|
|
158
|
-
getNotifications(userId);
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
58
|
</script>
|
|
162
|
-
|
|
163
|
-
<style scoped>
|
|
164
|
-
.notifications-list-container {
|
|
165
|
-
width: 100%;
|
|
166
|
-
max-width: 800px;
|
|
167
|
-
margin: 0 auto;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
.notifications-header {
|
|
171
|
-
display: flex;
|
|
172
|
-
justify-content: space-between;
|
|
173
|
-
align-items: center;
|
|
174
|
-
margin-bottom: 20px;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
.notifications-controls {
|
|
178
|
-
display: flex;
|
|
179
|
-
align-items: center;
|
|
180
|
-
gap: 16px;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
.notifications-filter select {
|
|
184
|
-
padding: 6px 12px;
|
|
185
|
-
border-radius: 4px;
|
|
186
|
-
border: 1px solid #ddd;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
.mark-all-read-btn {
|
|
190
|
-
background-color: #2196f3;
|
|
191
|
-
color: white;
|
|
192
|
-
border: none;
|
|
193
|
-
padding: 6px 12px;
|
|
194
|
-
border-radius: 4px;
|
|
195
|
-
cursor: pointer;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
.notifications-loading,
|
|
199
|
-
.notifications-empty {
|
|
200
|
-
padding: 40px 0;
|
|
201
|
-
text-align: center;
|
|
202
|
-
color: #666;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
.loading-spinner {
|
|
206
|
-
font-size: 2rem;
|
|
207
|
-
margin-bottom: 10px;
|
|
208
|
-
animation: spin 1s linear infinite;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
@keyframes spin {
|
|
212
|
-
from { transform: rotate(0deg); }
|
|
213
|
-
to { transform: rotate(360deg); }
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
.notifications-items {
|
|
217
|
-
display: flex;
|
|
218
|
-
flex-direction: column;
|
|
219
|
-
gap: 10px;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
.notifications-footer {
|
|
223
|
-
margin-top: 20px;
|
|
224
|
-
text-align: center;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
.refresh-btn {
|
|
228
|
-
background: none;
|
|
229
|
-
border: none;
|
|
230
|
-
color: #666;
|
|
231
|
-
cursor: pointer;
|
|
232
|
-
padding: 6px 12px;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
.refresh-btn:hover {
|
|
236
|
-
color: #2196f3;
|
|
237
|
-
}
|
|
238
|
-
</style>
|
|
@@ -439,29 +439,30 @@ function initializeNotifications(app, store, router, options = {}) {
|
|
|
439
439
|
const autoInit = !isServer && options.autoInit !== false;
|
|
440
440
|
|
|
441
441
|
if (autoInit) {
|
|
442
|
-
//
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
// Watch for user login/logout using store.core.state.session
|
|
442
|
+
// Watch for user login/logout using store.core.state.session.userId
|
|
443
|
+
// immediate: true — сработает сразу после применения initialState (гидратация)
|
|
446
444
|
watch(
|
|
447
|
-
() => store.core.state.session.
|
|
448
|
-
async (
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
// Re-register device for authenticated user
|
|
454
|
-
console.log('[Notifications] User logged in, re-registering device...');
|
|
445
|
+
() => store.core.state.session.userId,
|
|
446
|
+
async (userId, oldUserId) => {
|
|
447
|
+
console.log('[Notifications] Watch triggered, userId:', userId, 'oldUserId:', oldUserId);
|
|
448
|
+
if (userId && !oldUserId) {
|
|
449
|
+
// Пользователь залогинен (либо при гидратации, либо при логине)
|
|
450
|
+
console.log('[Notifications] User authenticated, initializing WebSocket...', userId);
|
|
455
451
|
await store.notifications.actions.reregisterDeviceAfterLogin();
|
|
456
|
-
// Reinitialize notifications for authenticated user
|
|
457
452
|
notificationManager.disconnect();
|
|
458
453
|
await notificationManager.initialize();
|
|
459
|
-
} else if (!
|
|
460
|
-
//
|
|
454
|
+
} else if (!userId && oldUserId) {
|
|
455
|
+
// Пользователь разлогинился
|
|
461
456
|
console.log('[Notifications] User logged out, resetting notifications...');
|
|
457
|
+
notificationManager.disconnect();
|
|
462
458
|
store.notifications.mutations.resetNotifications();
|
|
459
|
+
} else if (!userId && !oldUserId) {
|
|
460
|
+
// Анонимный пользователь — инициализируем без WebSocket
|
|
461
|
+
console.log('[Notifications] Anonymous user, initializing without WebSocket...');
|
|
462
|
+
await notificationManager.initialize();
|
|
463
463
|
}
|
|
464
|
-
}
|
|
464
|
+
},
|
|
465
|
+
{ immediate: true }
|
|
465
466
|
);
|
|
466
467
|
}
|
|
467
468
|
|