@serhii.olishevskyi/docs 1.1.8 → 1.2.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.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  UserPopoverComponent,
3
3
  UserPopoverComponent_default
4
- } from "./chunk-UA57YNRJ.mjs";
4
+ } from "./chunk-FYNGJLO7.mjs";
5
5
  export {
6
6
  UserPopoverComponent,
7
7
  UserPopoverComponent_default as default
@@ -2546,6 +2546,7 @@ var UserPopoverComponent = ({
2546
2546
  var UserPopoverComponent_default = UserPopoverComponent;
2547
2547
 
2548
2548
  export {
2549
+ getDeviceId,
2549
2550
  fetchCompanySessions,
2550
2551
  trackUserSession,
2551
2552
  trackUserSessionByRoute,
@@ -2580,6 +2581,7 @@ export {
2580
2581
  useConfirmation,
2581
2582
  AppSettingsProvider,
2582
2583
  useAppSettings,
2584
+ defaultUserSettings,
2583
2585
  UserSettingsProvider,
2584
2586
  useUserSettings,
2585
2587
  Providers,
package/dist/index.d.mts CHANGED
@@ -182,6 +182,7 @@ interface UserSettings {
182
182
  created_at?: string;
183
183
  updated_at?: string;
184
184
  }
185
+ declare const defaultUserSettings: Omit<UserSettings, 'id'>;
185
186
 
186
187
  type MembershipRole = 'superadmin' | 'admin' | 'user';
187
188
  interface Membership {
@@ -199,6 +200,76 @@ interface Membership {
199
200
  locked_reason: string | null;
200
201
  excluded: boolean;
201
202
  }
203
+ interface CompanyMember {
204
+ id: string;
205
+ userId: string;
206
+ companyId: string;
207
+ role: string;
208
+ employeePost: string | null;
209
+ displayName: string;
210
+ email: string;
211
+ avatarUrl: string | null;
212
+ }
213
+ interface SaveCompanyUserPayload {
214
+ company_id: string;
215
+ membership_id?: string;
216
+ user_id?: string;
217
+ email: string;
218
+ first_name?: string | null;
219
+ middle_name?: string | null;
220
+ last_name?: string | null;
221
+ display_name?: string | null;
222
+ phone?: string | null;
223
+ birthday?: string | null;
224
+ description?: string | null;
225
+ name_order?: 'first_last' | 'last_first';
226
+ include_middle_name?: boolean;
227
+ name_genitive?: string;
228
+ name_dative?: string;
229
+ name_instrumental?: string;
230
+ name_vocative?: string;
231
+ role: MembershipRole;
232
+ employee_post?: string | null;
233
+ work_schedule_id?: string | null;
234
+ locked?: boolean;
235
+ locked_reason?: string | null;
236
+ excluded?: boolean;
237
+ langCode?: string;
238
+ requestUser?: string;
239
+ }
240
+ interface FullCompanyMemberItem {
241
+ id: string;
242
+ membership_id: string;
243
+ company_id: string;
244
+ user_id: string;
245
+ role: MembershipRole;
246
+ employee_post: string | null;
247
+ work_schedule_id: string | null;
248
+ locked: boolean;
249
+ locked_reason: string | null;
250
+ excluded: boolean;
251
+ first_login_at?: string | null;
252
+ last_login_at?: string | null;
253
+ email: string;
254
+ first_name: string | null;
255
+ middle_name: string | null;
256
+ last_name: string | null;
257
+ display_name: string | null;
258
+ phone: string | null;
259
+ birthday: string | null;
260
+ avatar_url: string | null;
261
+ avatar_path: string | null;
262
+ description: string | null;
263
+ name_order?: 'first_last' | 'last_first';
264
+ include_middle_name?: boolean;
265
+ name_genitive?: string;
266
+ name_dative?: string;
267
+ name_instrumental?: string;
268
+ name_vocative?: string;
269
+ profile_archived?: boolean;
270
+ profile_locked?: boolean;
271
+ created_at: string;
272
+ }
202
273
 
203
274
  interface UserRoleChipComponentProps {
204
275
  overrideNamespace?: string;
@@ -571,10 +642,17 @@ declare const Services: {
571
642
  };
572
643
  };
573
644
 
645
+ type UserStatusType = 'online' | 'away' | 'offline';
646
+
574
647
  declare function useDebounce<T>(value: T, delay?: number): T;
575
648
 
576
649
  declare const Hooks: {
577
650
  readonly useDebounce: typeof useDebounce;
651
+ readonly useUserStatus: (userId?: string, initialLastSeenAt?: string | null, currentAuthUserId?: string) => {
652
+ status: UserStatusType;
653
+ isOnline: boolean;
654
+ lastSeen: string | null;
655
+ };
578
656
  };
579
657
 
580
- export { type AIConfig, type AIGenerateOptions, AIProvider, type AIProviderProps, type AIProviderType, AppSettingsProvider, AuthProvider, BreadcrumbProvider, type ConfirmOptions, type ConfirmResult, ConfirmationProvider, type ConfirmationProviderProps, type CopyClipboardButtonComponentProps, type DialogSeverity, type DisplayState, type EmailInputComponentProps, type ExpandableNoteComponentProps, Hooks, type Language, LocalizationProvider, type MyAvatarComponentProps, type NumberInputComponentProps, Providers, type SearchComponentProps, type SelectComponentProps, type SelectOption, Services, type SupabaseConfig, SupabaseProvider, type SupabaseProviderProps, type TextFieldComponentProps, type ThemeColors, type ThemeConfig, type ThemeMode, ThemeProvider, ToastProvider, type ToastType, type TranslatableTooltipComponentProps, TranslateProvider, TranslateText, TranslationEditProvider, type TypographyConfig, UI, type UrlComponentProps as UrlProps, type UserAvatarComponentProps, type UserGroupedSessions, type UserPopoverComponentProps, type UserRoleChipComponentProps, type UserSessionItem, UserSettingsProvider, type UserStatusChipComponentProps, useAI, useAppSettings, useAppTheme, useAuth, useBreadcrumb, useConfirmation, useLocalization, useSupabase, useToast, useTranslationEdit, useTranslationNamespace, useUserSettings };
658
+ export { type AIConfig, type AIGenerateOptions, AIProvider, type AIProviderProps, type AIProviderType, AppSettingsProvider, AuthProvider, BreadcrumbProvider, type CompanyMember, type ConfirmOptions, type ConfirmResult, ConfirmationProvider, type ConfirmationProviderProps, type CopyClipboardButtonComponentProps, type DialogSeverity, type DisplayState, type EmailInputComponentProps, type ExpandableNoteComponentProps, type FullCompanyMemberItem, Hooks, type Language, LocalizationProvider, type Membership, type MembershipRole, type MyAvatarComponentProps, type NumberInputComponentProps, Providers, type SaveCompanyUserPayload, type SearchComponentProps, type SelectComponentProps, type SelectOption, Services, type SupabaseConfig, SupabaseProvider, type SupabaseProviderProps, type TextFieldComponentProps, type ThemeColors, type ThemeConfig, type ThemeMode, ThemeProvider, ToastProvider, type ToastType, type TranslatableTooltipComponentProps, TranslateProvider, TranslateText, TranslationEditProvider, type TypographyConfig, UI, type UrlComponentProps as UrlProps, type UserAvatarComponentProps, type UserGroupedSessions, type UserPopoverComponentProps, type UserProfile, type UserRoleChipComponentProps, type UserSessionItem, type UserSettings, UserSettingsProvider, type UserStatusChipComponentProps, defaultUserSettings, useAI, useAppSettings, useAppTheme, useAuth, useBreadcrumb, useConfirmation, useLocalization, useSupabase, useToast, useTranslationEdit, useTranslationNamespace, useUserSettings };
package/dist/index.d.ts CHANGED
@@ -182,6 +182,7 @@ interface UserSettings {
182
182
  created_at?: string;
183
183
  updated_at?: string;
184
184
  }
185
+ declare const defaultUserSettings: Omit<UserSettings, 'id'>;
185
186
 
186
187
  type MembershipRole = 'superadmin' | 'admin' | 'user';
187
188
  interface Membership {
@@ -199,6 +200,76 @@ interface Membership {
199
200
  locked_reason: string | null;
200
201
  excluded: boolean;
201
202
  }
203
+ interface CompanyMember {
204
+ id: string;
205
+ userId: string;
206
+ companyId: string;
207
+ role: string;
208
+ employeePost: string | null;
209
+ displayName: string;
210
+ email: string;
211
+ avatarUrl: string | null;
212
+ }
213
+ interface SaveCompanyUserPayload {
214
+ company_id: string;
215
+ membership_id?: string;
216
+ user_id?: string;
217
+ email: string;
218
+ first_name?: string | null;
219
+ middle_name?: string | null;
220
+ last_name?: string | null;
221
+ display_name?: string | null;
222
+ phone?: string | null;
223
+ birthday?: string | null;
224
+ description?: string | null;
225
+ name_order?: 'first_last' | 'last_first';
226
+ include_middle_name?: boolean;
227
+ name_genitive?: string;
228
+ name_dative?: string;
229
+ name_instrumental?: string;
230
+ name_vocative?: string;
231
+ role: MembershipRole;
232
+ employee_post?: string | null;
233
+ work_schedule_id?: string | null;
234
+ locked?: boolean;
235
+ locked_reason?: string | null;
236
+ excluded?: boolean;
237
+ langCode?: string;
238
+ requestUser?: string;
239
+ }
240
+ interface FullCompanyMemberItem {
241
+ id: string;
242
+ membership_id: string;
243
+ company_id: string;
244
+ user_id: string;
245
+ role: MembershipRole;
246
+ employee_post: string | null;
247
+ work_schedule_id: string | null;
248
+ locked: boolean;
249
+ locked_reason: string | null;
250
+ excluded: boolean;
251
+ first_login_at?: string | null;
252
+ last_login_at?: string | null;
253
+ email: string;
254
+ first_name: string | null;
255
+ middle_name: string | null;
256
+ last_name: string | null;
257
+ display_name: string | null;
258
+ phone: string | null;
259
+ birthday: string | null;
260
+ avatar_url: string | null;
261
+ avatar_path: string | null;
262
+ description: string | null;
263
+ name_order?: 'first_last' | 'last_first';
264
+ include_middle_name?: boolean;
265
+ name_genitive?: string;
266
+ name_dative?: string;
267
+ name_instrumental?: string;
268
+ name_vocative?: string;
269
+ profile_archived?: boolean;
270
+ profile_locked?: boolean;
271
+ created_at: string;
272
+ }
202
273
 
203
274
  interface UserRoleChipComponentProps {
204
275
  overrideNamespace?: string;
@@ -571,10 +642,17 @@ declare const Services: {
571
642
  };
572
643
  };
573
644
 
645
+ type UserStatusType = 'online' | 'away' | 'offline';
646
+
574
647
  declare function useDebounce<T>(value: T, delay?: number): T;
575
648
 
576
649
  declare const Hooks: {
577
650
  readonly useDebounce: typeof useDebounce;
651
+ readonly useUserStatus: (userId?: string, initialLastSeenAt?: string | null, currentAuthUserId?: string) => {
652
+ status: UserStatusType;
653
+ isOnline: boolean;
654
+ lastSeen: string | null;
655
+ };
578
656
  };
579
657
 
580
- export { type AIConfig, type AIGenerateOptions, AIProvider, type AIProviderProps, type AIProviderType, AppSettingsProvider, AuthProvider, BreadcrumbProvider, type ConfirmOptions, type ConfirmResult, ConfirmationProvider, type ConfirmationProviderProps, type CopyClipboardButtonComponentProps, type DialogSeverity, type DisplayState, type EmailInputComponentProps, type ExpandableNoteComponentProps, Hooks, type Language, LocalizationProvider, type MyAvatarComponentProps, type NumberInputComponentProps, Providers, type SearchComponentProps, type SelectComponentProps, type SelectOption, Services, type SupabaseConfig, SupabaseProvider, type SupabaseProviderProps, type TextFieldComponentProps, type ThemeColors, type ThemeConfig, type ThemeMode, ThemeProvider, ToastProvider, type ToastType, type TranslatableTooltipComponentProps, TranslateProvider, TranslateText, TranslationEditProvider, type TypographyConfig, UI, type UrlComponentProps as UrlProps, type UserAvatarComponentProps, type UserGroupedSessions, type UserPopoverComponentProps, type UserRoleChipComponentProps, type UserSessionItem, UserSettingsProvider, type UserStatusChipComponentProps, useAI, useAppSettings, useAppTheme, useAuth, useBreadcrumb, useConfirmation, useLocalization, useSupabase, useToast, useTranslationEdit, useTranslationNamespace, useUserSettings };
658
+ export { type AIConfig, type AIGenerateOptions, AIProvider, type AIProviderProps, type AIProviderType, AppSettingsProvider, AuthProvider, BreadcrumbProvider, type CompanyMember, type ConfirmOptions, type ConfirmResult, ConfirmationProvider, type ConfirmationProviderProps, type CopyClipboardButtonComponentProps, type DialogSeverity, type DisplayState, type EmailInputComponentProps, type ExpandableNoteComponentProps, type FullCompanyMemberItem, Hooks, type Language, LocalizationProvider, type Membership, type MembershipRole, type MyAvatarComponentProps, type NumberInputComponentProps, Providers, type SaveCompanyUserPayload, type SearchComponentProps, type SelectComponentProps, type SelectOption, Services, type SupabaseConfig, SupabaseProvider, type SupabaseProviderProps, type TextFieldComponentProps, type ThemeColors, type ThemeConfig, type ThemeMode, ThemeProvider, ToastProvider, type ToastType, type TranslatableTooltipComponentProps, TranslateProvider, TranslateText, TranslationEditProvider, type TypographyConfig, UI, type UrlComponentProps as UrlProps, type UserAvatarComponentProps, type UserGroupedSessions, type UserPopoverComponentProps, type UserProfile, type UserRoleChipComponentProps, type UserSessionItem, type UserSettings, UserSettingsProvider, type UserStatusChipComponentProps, defaultUserSettings, useAI, useAppSettings, useAppTheme, useAuth, useBreadcrumb, useConfirmation, useLocalization, useSupabase, useToast, useTranslationEdit, useTranslationNamespace, useUserSettings };
package/dist/index.js CHANGED
@@ -2760,6 +2760,7 @@ __export(index_exports, {
2760
2760
  TranslationEditProvider: () => TranslationEditProvider,
2761
2761
  UI: () => UI,
2762
2762
  UserSettingsProvider: () => UserSettingsProvider,
2763
+ defaultUserSettings: () => defaultUserSettings,
2763
2764
  useAI: () => useAI,
2764
2765
  useAppSettings: () => useAppSettings,
2765
2766
  useAppTheme: () => useAppTheme,
@@ -4408,6 +4409,9 @@ var UI = {
4408
4409
  }
4409
4410
  };
4410
4411
 
4412
+ // src/types/index.ts
4413
+ init_user_types();
4414
+
4411
4415
  // src/index.ts
4412
4416
  init_context();
4413
4417
  init_context();
@@ -4427,9 +4431,101 @@ var Services = {
4427
4431
  }
4428
4432
  };
4429
4433
 
4434
+ // src/hooks/useUserStatus.ts
4435
+ var import_react28 = require("react");
4436
+ init_deviceId();
4437
+ init_context();
4438
+ var ONLINE_THRESHOLD_MINUTES = 2;
4439
+ var AWAY_THRESHOLD_MINUTES = 15;
4440
+ var useUserStatus2 = (userId, initialLastSeenAt, currentAuthUserId) => {
4441
+ const supabase = useSupabase();
4442
+ const [status, setStatus] = (0, import_react28.useState)("offline");
4443
+ const [lastSeen, setLastSeen] = (0, import_react28.useState)(initialLastSeenAt ?? null);
4444
+ const lastSeenRef = (0, import_react28.useRef)(initialLastSeenAt ?? null);
4445
+ lastSeenRef.current = lastSeen;
4446
+ const calculateStatus = (isOnline, lastSeenIso) => {
4447
+ if (isOnline) return "online";
4448
+ if (!lastSeenIso) return "offline";
4449
+ const lastSeenTime = new Date(lastSeenIso).getTime();
4450
+ if (isNaN(lastSeenTime)) return "offline";
4451
+ const diffInMinutes = (Date.now() - lastSeenTime) / (1e3 * 60);
4452
+ if (diffInMinutes >= 0 && diffInMinutes <= ONLINE_THRESHOLD_MINUTES) {
4453
+ return "online";
4454
+ }
4455
+ if (diffInMinutes > ONLINE_THRESHOLD_MINUTES && diffInMinutes <= AWAY_THRESHOLD_MINUTES) {
4456
+ return "away";
4457
+ }
4458
+ return "offline";
4459
+ };
4460
+ (0, import_react28.useEffect)(() => {
4461
+ if (!userId) return;
4462
+ let isMounted = true;
4463
+ const isSelf = currentAuthUserId && userId === currentAuthUserId;
4464
+ const fetchLastSeen = async () => {
4465
+ if (initialLastSeenAt !== void 0) return;
4466
+ let query = supabase.from("user_sessions").select("last_seen_at").eq("user_id", userId);
4467
+ if (isSelf) {
4468
+ query = query.eq("device_id", getDeviceId());
4469
+ } else {
4470
+ query = query.order("last_seen_at", { ascending: false });
4471
+ }
4472
+ const { data, error } = await query.limit(1).maybeSingle();
4473
+ if (error) {
4474
+ console.warn(`[useUserStatus] Error fetching session for ${userId}:`, error.message);
4475
+ return;
4476
+ }
4477
+ if (data && isMounted) {
4478
+ setLastSeen(data.last_seen_at);
4479
+ setStatus(calculateStatus(false, data.last_seen_at));
4480
+ }
4481
+ };
4482
+ fetchLastSeen();
4483
+ const channel = supabase.channel(`presence_user_${userId}`);
4484
+ channel.on("presence", { event: "sync" }, () => {
4485
+ const state = channel.presenceState();
4486
+ const isOnline = Object.keys(state).length > 0;
4487
+ if (isMounted) {
4488
+ setStatus(isOnline ? "online" : calculateStatus(false, lastSeenRef.current));
4489
+ }
4490
+ }).on("presence", { event: "join" }, () => {
4491
+ if (isMounted) setStatus("online");
4492
+ }).on("presence", { event: "leave" }, () => {
4493
+ const nowIso = (/* @__PURE__ */ new Date()).toISOString();
4494
+ if (isMounted) {
4495
+ setLastSeen(nowIso);
4496
+ setStatus(calculateStatus(false, nowIso));
4497
+ }
4498
+ }).on(
4499
+ "postgres_changes",
4500
+ {
4501
+ event: "*",
4502
+ schema: "public",
4503
+ table: "user_sessions",
4504
+ filter: isSelf ? `device_id=eq.${getDeviceId()}` : `user_id=eq.${userId}`
4505
+ },
4506
+ (payload) => {
4507
+ if (!isMounted) return;
4508
+ const newLastSeen = payload.new?.last_seen_at ?? (/* @__PURE__ */ new Date()).toISOString();
4509
+ setLastSeen(newLastSeen);
4510
+ setStatus(calculateStatus(false, newLastSeen));
4511
+ }
4512
+ ).subscribe();
4513
+ return () => {
4514
+ isMounted = false;
4515
+ supabase.removeChannel(channel);
4516
+ };
4517
+ }, [userId, initialLastSeenAt, currentAuthUserId]);
4518
+ return {
4519
+ status,
4520
+ isOnline: status === "online",
4521
+ lastSeen
4522
+ };
4523
+ };
4524
+
4430
4525
  // src/hooks/index.ts
4431
4526
  var Hooks = {
4432
- useDebounce
4527
+ useDebounce,
4528
+ useUserStatus: useUserStatus2
4433
4529
  };
4434
4530
  // Annotate the CommonJS export names for ESM import in node:
4435
4531
  0 && (module.exports = {
@@ -4450,6 +4546,7 @@ var Hooks = {
4450
4546
  TranslationEditProvider,
4451
4547
  UI,
4452
4548
  UserSettingsProvider,
4549
+ defaultUserSettings,
4453
4550
  useAI,
4454
4551
  useAppSettings,
4455
4552
  useAppTheme,
package/dist/index.mjs CHANGED
@@ -19,8 +19,10 @@ import {
19
19
  UserRoleChipComponent,
20
20
  UserSettingsProvider,
21
21
  UserStatusChipComponent,
22
+ defaultUserSettings,
22
23
  fetchCompanySessions,
23
24
  fetchUserSessions,
25
+ getDeviceId,
24
26
  getUserLastSeenAt,
25
27
  getUsersLastSeenMap,
26
28
  groupSessionsByUser,
@@ -40,7 +42,7 @@ import {
40
42
  useTranslationEdit,
41
43
  useTranslationNamespace,
42
44
  useUserSettings
43
- } from "./chunk-UA57YNRJ.mjs";
45
+ } from "./chunk-FYNGJLO7.mjs";
44
46
 
45
47
  // src/UI/components/inputs/TextFieldComponent.tsx
46
48
  import { useState } from "react";
@@ -738,7 +740,7 @@ var EmailInputComponent = ({
738
740
  import { useState as useState5, useEffect as useEffect2, lazy, Suspense } from "react";
739
741
  import { Avatar, Tooltip as Tooltip3, Typography as Typography2 } from "@mui/material";
740
742
  import { Fragment as Fragment4, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
741
- var LazyUserPopover = lazy(() => import("./UserPopoverComponent-5F4S3X4F.mjs"));
743
+ var LazyUserPopover = lazy(() => import("./UserPopoverComponent-FZF757NQ.mjs"));
742
744
  var getInitials = (name) => {
743
745
  if (!name) return "?";
744
746
  const parts = name.trim().split(" ");
@@ -1695,9 +1697,99 @@ var Services = {
1695
1697
  }
1696
1698
  };
1697
1699
 
1700
+ // src/hooks/useUserStatus.ts
1701
+ import { useEffect as useEffect6, useRef, useState as useState11 } from "react";
1702
+ var ONLINE_THRESHOLD_MINUTES = 2;
1703
+ var AWAY_THRESHOLD_MINUTES = 15;
1704
+ var useUserStatus = (userId, initialLastSeenAt, currentAuthUserId) => {
1705
+ const supabase = useSupabase();
1706
+ const [status, setStatus] = useState11("offline");
1707
+ const [lastSeen, setLastSeen] = useState11(initialLastSeenAt ?? null);
1708
+ const lastSeenRef = useRef(initialLastSeenAt ?? null);
1709
+ lastSeenRef.current = lastSeen;
1710
+ const calculateStatus = (isOnline, lastSeenIso) => {
1711
+ if (isOnline) return "online";
1712
+ if (!lastSeenIso) return "offline";
1713
+ const lastSeenTime = new Date(lastSeenIso).getTime();
1714
+ if (isNaN(lastSeenTime)) return "offline";
1715
+ const diffInMinutes = (Date.now() - lastSeenTime) / (1e3 * 60);
1716
+ if (diffInMinutes >= 0 && diffInMinutes <= ONLINE_THRESHOLD_MINUTES) {
1717
+ return "online";
1718
+ }
1719
+ if (diffInMinutes > ONLINE_THRESHOLD_MINUTES && diffInMinutes <= AWAY_THRESHOLD_MINUTES) {
1720
+ return "away";
1721
+ }
1722
+ return "offline";
1723
+ };
1724
+ useEffect6(() => {
1725
+ if (!userId) return;
1726
+ let isMounted = true;
1727
+ const isSelf = currentAuthUserId && userId === currentAuthUserId;
1728
+ const fetchLastSeen = async () => {
1729
+ if (initialLastSeenAt !== void 0) return;
1730
+ let query = supabase.from("user_sessions").select("last_seen_at").eq("user_id", userId);
1731
+ if (isSelf) {
1732
+ query = query.eq("device_id", getDeviceId());
1733
+ } else {
1734
+ query = query.order("last_seen_at", { ascending: false });
1735
+ }
1736
+ const { data, error } = await query.limit(1).maybeSingle();
1737
+ if (error) {
1738
+ console.warn(`[useUserStatus] Error fetching session for ${userId}:`, error.message);
1739
+ return;
1740
+ }
1741
+ if (data && isMounted) {
1742
+ setLastSeen(data.last_seen_at);
1743
+ setStatus(calculateStatus(false, data.last_seen_at));
1744
+ }
1745
+ };
1746
+ fetchLastSeen();
1747
+ const channel = supabase.channel(`presence_user_${userId}`);
1748
+ channel.on("presence", { event: "sync" }, () => {
1749
+ const state = channel.presenceState();
1750
+ const isOnline = Object.keys(state).length > 0;
1751
+ if (isMounted) {
1752
+ setStatus(isOnline ? "online" : calculateStatus(false, lastSeenRef.current));
1753
+ }
1754
+ }).on("presence", { event: "join" }, () => {
1755
+ if (isMounted) setStatus("online");
1756
+ }).on("presence", { event: "leave" }, () => {
1757
+ const nowIso = (/* @__PURE__ */ new Date()).toISOString();
1758
+ if (isMounted) {
1759
+ setLastSeen(nowIso);
1760
+ setStatus(calculateStatus(false, nowIso));
1761
+ }
1762
+ }).on(
1763
+ "postgres_changes",
1764
+ {
1765
+ event: "*",
1766
+ schema: "public",
1767
+ table: "user_sessions",
1768
+ filter: isSelf ? `device_id=eq.${getDeviceId()}` : `user_id=eq.${userId}`
1769
+ },
1770
+ (payload) => {
1771
+ if (!isMounted) return;
1772
+ const newLastSeen = payload.new?.last_seen_at ?? (/* @__PURE__ */ new Date()).toISOString();
1773
+ setLastSeen(newLastSeen);
1774
+ setStatus(calculateStatus(false, newLastSeen));
1775
+ }
1776
+ ).subscribe();
1777
+ return () => {
1778
+ isMounted = false;
1779
+ supabase.removeChannel(channel);
1780
+ };
1781
+ }, [userId, initialLastSeenAt, currentAuthUserId]);
1782
+ return {
1783
+ status,
1784
+ isOnline: status === "online",
1785
+ lastSeen
1786
+ };
1787
+ };
1788
+
1698
1789
  // src/hooks/index.ts
1699
1790
  var Hooks = {
1700
- useDebounce
1791
+ useDebounce,
1792
+ useUserStatus
1701
1793
  };
1702
1794
  export {
1703
1795
  AIProvider,
@@ -1717,6 +1809,7 @@ export {
1717
1809
  TranslationEditProvider,
1718
1810
  UI,
1719
1811
  UserSettingsProvider,
1812
+ defaultUserSettings,
1720
1813
  useAI,
1721
1814
  useAppSettings,
1722
1815
  useAppTheme,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serhii.olishevskyi/docs",
3
- "version": "1.1.8",
3
+ "version": "1.2.0",
4
4
  "description": "DOCS.UA react packages",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",