@serhii.olishevskyi/docs 1.0.6 → 1.0.8

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-UYCEKHKQ.mjs";
4
+ } from "./chunk-ABYNAO4A.mjs";
5
5
  export {
6
6
  UserPopoverComponent,
7
7
  UserPopoverComponent_default as default
@@ -189,7 +189,54 @@ var config_default = i18n;
189
189
  // src/context/AuthProvider.tsx
190
190
  import { createContext as createContext3, useContext as useContext3, useEffect, useState, useCallback, useRef } from "react";
191
191
 
192
+ // src/utils/deviceId.ts
193
+ var getDeviceId = () => {
194
+ let deviceId = localStorage.getItem("app_device_id");
195
+ if (!deviceId) {
196
+ deviceId = crypto.randomUUID();
197
+ localStorage.setItem("app_device_id", deviceId);
198
+ }
199
+ return deviceId;
200
+ };
201
+
192
202
  // src/services/session.service.ts
203
+ var trackUserSession = async (supabase, userId) => {
204
+ const deviceId = getDeviceId();
205
+ let ip = "";
206
+ try {
207
+ const ipRes = await fetch("https://api.ipify.org?format=json");
208
+ const ipData = await ipRes.json();
209
+ ip = ipData.ip;
210
+ } catch (e) {
211
+ console.warn("Failed to fetch IP address", e);
212
+ }
213
+ await supabase.from("user_sessions").upsert(
214
+ {
215
+ user_id: userId,
216
+ device_id: deviceId,
217
+ user_agent: navigator.userAgent,
218
+ ip_address: ip,
219
+ url: window.location.href,
220
+ last_seen_at: (/* @__PURE__ */ new Date()).toISOString()
221
+ },
222
+ { onConflict: "user_id, device_id" }
223
+ );
224
+ };
225
+ var isCurrentSessionValid = async (supabase, userId) => {
226
+ try {
227
+ const currentDeviceId = getDeviceId();
228
+ const { data, error } = await supabase.from("user_sessions").select("id").eq("user_id", userId).eq("device_id", currentDeviceId).maybeSingle();
229
+ if (error) {
230
+ console.warn("\u041F\u043E\u043C\u0438\u043B\u043A\u0430 \u043F\u0435\u0440\u0435\u0432\u0456\u0440\u043A\u0438 \u0441\u0435\u0441\u0456\u0457:", error.message);
231
+ return true;
232
+ }
233
+ if (!data) return false;
234
+ return true;
235
+ } catch (err) {
236
+ console.error("\u041D\u0435\u0441\u043F\u043E\u0434\u0456\u0432\u0430\u043D\u0430 \u043F\u043E\u043C\u0438\u043B\u043A\u0430 \u043F\u0440\u0438 \u043F\u0435\u0440\u0435\u0432\u0456\u0440\u0446\u0456 \u0441\u0435\u0441\u0456\u0457:", err);
237
+ return true;
238
+ }
239
+ };
193
240
  var getUserLastSeenAt = async (supabase, userId) => {
194
241
  const { data, error } = await supabase.from("user_sessions").select("last_seen_at").eq("user_id", userId).order("last_seen_at", { ascending: false }).limit(1).maybeSingle();
195
242
  if (error || !data) return null;
@@ -199,6 +246,179 @@ var getUserLastSeenAt = async (supabase, userId) => {
199
246
  // src/context/AuthProvider.tsx
200
247
  import { jsx as jsx3 } from "react/jsx-runtime";
201
248
  var AuthContext = createContext3(void 0);
249
+ var AuthProvider = ({ children }) => {
250
+ const supabase = useSupabase();
251
+ const [user, setUser] = useState(null);
252
+ const [profile, setProfile] = useState(null);
253
+ const [membership, setMembership] = useState(null);
254
+ const [session, setSession] = useState(null);
255
+ const [loading, setLoading] = useState(true);
256
+ const currentUserIdRef = useRef(null);
257
+ const fetchProfileAndMembership = useCallback(async (userId) => {
258
+ try {
259
+ const { data, error } = await supabase.rpc("get_user_avatar_and_profile", {
260
+ p_user_id: userId
261
+ });
262
+ if (error || !data) {
263
+ console.error("Error fetching user context via RPC:", error?.message);
264
+ setProfile(null);
265
+ setMembership(null);
266
+ return;
267
+ }
268
+ const response = data;
269
+ if (response?.profile) {
270
+ const profileData = response.profile;
271
+ if (profileData.avatar_path && !profileData.avatar_path.startsWith("http")) {
272
+ const cleanPath = profileData.avatar_path.replace(/^user-media\//, "");
273
+ const { data: avatarSignedData } = await supabase.storage.from("user-media").createSignedUrl(cleanPath, 3600);
274
+ if (avatarSignedData?.signedUrl) {
275
+ profileData.avatar_url = avatarSignedData.signedUrl;
276
+ }
277
+ }
278
+ if (profileData.cover_image_path && !profileData.cover_image_path.startsWith("http")) {
279
+ const cleanPath = profileData.cover_image_path.replace(/^user-media\//, "");
280
+ const { data: coverSignedData } = await supabase.storage.from("user-media").createSignedUrl(cleanPath, 3600);
281
+ if (coverSignedData?.signedUrl) {
282
+ profileData.cover_image_url = coverSignedData.signedUrl;
283
+ }
284
+ }
285
+ let updatedMembership = response.membership;
286
+ if (updatedMembership) {
287
+ const now = (/* @__PURE__ */ new Date()).toISOString();
288
+ const isFirstLogin = !updatedMembership.first_login_at;
289
+ const updateData = {
290
+ last_login_at: now,
291
+ ...isFirstLogin && { first_login_at: now }
292
+ };
293
+ const { error: updateError } = await supabase.from("memberships").update(updateData).eq("id", updatedMembership.id);
294
+ if (updateError) {
295
+ console.error("Error updating membership login timestamps:", updateError.message);
296
+ } else {
297
+ updatedMembership = {
298
+ ...updatedMembership,
299
+ ...updateData
300
+ };
301
+ }
302
+ }
303
+ setProfile(profileData);
304
+ setMembership(updatedMembership);
305
+ }
306
+ } catch (err) {
307
+ console.error("Unexpected error fetching user context:", err);
308
+ setProfile(null);
309
+ setMembership(null);
310
+ }
311
+ }, []);
312
+ const refreshProfile = async () => {
313
+ if (currentUserIdRef.current) {
314
+ await fetchProfileAndMembership(currentUserIdRef.current);
315
+ }
316
+ };
317
+ const signOut = useCallback(async () => {
318
+ await supabase.auth.signOut();
319
+ currentUserIdRef.current = null;
320
+ setUser(null);
321
+ setProfile(null);
322
+ setMembership(null);
323
+ setSession(null);
324
+ }, []);
325
+ const setActiveCompany = async (companyId) => {
326
+ if (!user) {
327
+ throw new Error("\u041F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044C \u043D\u0435 \u0430\u0432\u0442\u043E\u0440\u0438\u0437\u043E\u0432\u0430\u043D");
328
+ }
329
+ const { error: profileError } = await supabase.from("profiles").update({ active_company_id: companyId }).eq("id", user.id);
330
+ if (profileError) {
331
+ console.error("Error updating active company:", profileError);
332
+ throw new Error(profileError.message);
333
+ }
334
+ await refreshProfile();
335
+ };
336
+ const updateProfile = (newProfileData) => {
337
+ setProfile((prev) => prev ? { ...prev, ...newProfileData } : null);
338
+ };
339
+ useEffect(() => {
340
+ const { data: { subscription } } = supabase.auth.onAuthStateChange(async (event, currentSession) => {
341
+ if (event === "TOKEN_REFRESHED" && currentUserIdRef.current === currentSession?.user?.id) {
342
+ setSession(currentSession);
343
+ return;
344
+ }
345
+ const currentUser = currentSession?.user ?? null;
346
+ const newUserId = currentUser?.id ?? null;
347
+ if (newUserId !== currentUserIdRef.current) {
348
+ currentUserIdRef.current = newUserId;
349
+ setUser(currentUser);
350
+ if (newUserId) {
351
+ await trackUserSession(supabase, newUserId).catch(console.error);
352
+ const isAuthLinkEvent = event === "SIGNED_IN" || event === "INITIAL_SESSION" || event === "PASSWORD_RECOVERY";
353
+ if (!isAuthLinkEvent) {
354
+ const isValid = await isCurrentSessionValid(supabase, newUserId);
355
+ if (!isValid) {
356
+ await supabase.auth.signOut();
357
+ currentUserIdRef.current = null;
358
+ setUser(null);
359
+ setProfile(null);
360
+ setMembership(null);
361
+ setLoading(false);
362
+ return;
363
+ }
364
+ }
365
+ await fetchProfileAndMembership(newUserId);
366
+ setSession(currentSession);
367
+ } else {
368
+ setProfile(null);
369
+ setMembership(null);
370
+ }
371
+ }
372
+ setLoading(false);
373
+ });
374
+ return () => {
375
+ subscription.unsubscribe();
376
+ };
377
+ }, [fetchProfileAndMembership]);
378
+ useEffect(() => {
379
+ if (!user) return;
380
+ const checkSession = async () => {
381
+ try {
382
+ const valid = await isCurrentSessionValid(supabase, user.id);
383
+ if (!valid) {
384
+ console.warn("\u0421\u0435\u0441\u0456\u044E \u0432\u0456\u0434\u043A\u043B\u0438\u043A\u0430\u043D\u043E \u0437 \u0456\u043D\u0448\u043E\u0433\u043E \u043F\u0440\u0438\u0441\u0442\u0440\u043E\u044E.");
385
+ await signOut();
386
+ }
387
+ } catch (err) {
388
+ console.error("\u041F\u043E\u043C\u0438\u043B\u043A\u0430 \u043F\u0440\u0438 \u043F\u0435\u0440\u0435\u0432\u0456\u0440\u0446\u0456 \u0441\u0435\u0441\u0456\u0457:", err);
389
+ }
390
+ };
391
+ const interval = setInterval(checkSession, 6e4);
392
+ const handleFocus = () => checkSession();
393
+ window.addEventListener("focus", handleFocus);
394
+ return () => {
395
+ clearInterval(interval);
396
+ window.removeEventListener("focus", handleFocus);
397
+ };
398
+ }, [user, signOut]);
399
+ const activeCompanyId = profile?.active_company_id ?? null;
400
+ const userRole = membership?.role ?? null;
401
+ return /* @__PURE__ */ jsx3(
402
+ AuthContext.Provider,
403
+ {
404
+ value: {
405
+ user,
406
+ profile,
407
+ membership,
408
+ userRole,
409
+ activeCompanyId,
410
+ session,
411
+ isAuthenticated: !!user,
412
+ loading,
413
+ signOut,
414
+ updateProfile,
415
+ refreshProfile,
416
+ setActiveCompany
417
+ },
418
+ children
419
+ }
420
+ );
421
+ };
202
422
  var useAuth = () => {
203
423
  const context = useContext3(AuthContext);
204
424
  if (!context) {
@@ -1456,6 +1676,7 @@ var useAppSettings = () => {
1456
1676
  var Providers = {
1457
1677
  Supabase: SupabaseProvider,
1458
1678
  AppSettings: AppSettingsProvider,
1679
+ Auth: AuthProvider,
1459
1680
  Translate: TranslateProvider,
1460
1681
  TranslationEdit: TranslationEditProvider,
1461
1682
  Localization: LocalizationProvider,
@@ -2015,6 +2236,8 @@ var UserPopoverComponent_default = UserPopoverComponent;
2015
2236
  export {
2016
2237
  SupabaseProvider,
2017
2238
  useSupabase,
2239
+ AuthProvider,
2240
+ useAuth,
2018
2241
  TranslationEditProvider,
2019
2242
  useTranslationEdit,
2020
2243
  translationService,
package/dist/index.d.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as React$1 from 'react';
2
2
  import React__default, { ReactNode } from 'react';
3
3
  import { TextFieldProps, IconButtonProps, TooltipProps, SxProps, Theme } from '@mui/material';
4
- import { SupabaseClient } from '@supabase/supabase-js';
4
+ import { SupabaseClient, User, Session } from '@supabase/supabase-js';
5
5
 
6
6
  interface UrlValue {
7
7
  url: string;
@@ -112,7 +112,50 @@ interface UserPopoverComponentProps {
112
112
  onClose: () => void;
113
113
  }
114
114
 
115
+ interface UserProfile {
116
+ id: string;
117
+ email: string;
118
+ system_role: 'superadmin' | 'admin' | 'support' | null;
119
+ updated_at: string;
120
+ avatar_url: string | null;
121
+ avatar_path?: string | null;
122
+ first_name: string | null;
123
+ middle_name: string | null;
124
+ last_name: string | null;
125
+ display_name: string | null;
126
+ locked: boolean;
127
+ archived: boolean;
128
+ birthday: string | null;
129
+ phone: string | null;
130
+ description: string | null;
131
+ created_at: string;
132
+ cover_image_url: string | null;
133
+ cover_image_path?: string | null;
134
+ active_company_id: string | null;
135
+ name_order: 'first_last' | 'last_first' | null;
136
+ include_middle_name: boolean | null;
137
+ name_genitive: string | null;
138
+ name_dative: string | null;
139
+ name_instrumental: string | null;
140
+ name_vocative: string | null;
141
+ }
142
+
115
143
  type MembershipRole = 'superadmin' | 'admin' | 'user';
144
+ interface Membership {
145
+ id: string;
146
+ user_id: string;
147
+ company_id: string;
148
+ role: 'superadmin' | 'admin' | 'user';
149
+ created_at: string;
150
+ last_updated_at: string | null;
151
+ first_login_at?: string | null;
152
+ last_login_at?: string | null;
153
+ employee_post: string | null;
154
+ work_schedule_id: string | null;
155
+ locked: boolean;
156
+ locked_reason: string | null;
157
+ excluded: boolean;
158
+ }
116
159
 
117
160
  interface UserRoleChipComponentProps {
118
161
  overrideNamespace?: string;
@@ -233,6 +276,25 @@ declare const AppSettingsProvider: React__default.FC<{
233
276
  }>;
234
277
  declare const useAppSettings: () => AppSettingsContextType;
235
278
 
279
+ interface AuthContextType {
280
+ user: User | null;
281
+ profile: UserProfile | null;
282
+ membership: Membership | null;
283
+ userRole: MembershipRole | null;
284
+ activeCompanyId: string | null;
285
+ session: Session | null;
286
+ isAuthenticated: boolean;
287
+ loading: boolean;
288
+ signOut: () => Promise<void>;
289
+ updateProfile: (newProfileData: Partial<UserProfile>) => void;
290
+ refreshProfile: () => Promise<void>;
291
+ setActiveCompany: (companyId: string) => Promise<void>;
292
+ }
293
+ declare const AuthProvider: React__default.FC<{
294
+ children: React__default.ReactNode;
295
+ }>;
296
+ declare const useAuth: () => AuthContextType;
297
+
236
298
  interface TranslateTextProps {
237
299
  overrideNamespace?: string;
238
300
  children: string;
@@ -356,6 +418,9 @@ declare const Providers: {
356
418
  readonly AppSettings: React$1.FC<{
357
419
  children: React.ReactNode;
358
420
  }>;
421
+ readonly Auth: React$1.FC<{
422
+ children: React.ReactNode;
423
+ }>;
359
424
  readonly Translate: React$1.FC<TranslateProviderProps>;
360
425
  readonly TranslationEdit: React$1.FC<{
361
426
  children: React.ReactNode;
@@ -376,4 +441,4 @@ declare const Providers: {
376
441
  readonly AI: React$1.FC<AIProviderProps>;
377
442
  };
378
443
 
379
- export { type AIConfig, type AIGenerateOptions, AIProvider, type AIProviderProps, type AIProviderType, AppSettingsProvider, BreadcrumbProvider, type ConfirmOptions, type ConfirmResult, ConfirmationProvider, type ConfirmationProviderProps, type CopyClipboardButtonComponentProps as CopyClipboardButtonProps, type DialogSeverity, type DisplayState, type EmailInputComponentProps as EmailInputProps, type Language, LocalizationProvider, type NumberInputComponentProps as NumberInputProps, Providers, type SupabaseConfig, SupabaseProvider, type SupabaseProviderProps, type TextFieldComponentProps as TextFieldProps, type ThemeColors, type ThemeConfig, type ThemeMode, ThemeProvider, ToastProvider, type ToastType, type TranslatableTooltipComponentProps as TranslatableTooltipProps, TranslateProvider, TranslateText, TranslationEditProvider, type TypographyConfig, UI, type UrlComponentProps as UrlProps, type UserAvatarComponentProps as UserAvatarProps, type UserPopoverComponentProps as UserPopoverProps, type UserRoleChipComponentProps as UserRoleChipProps, type UserStatusChipComponentProps as UserStatusChipProps, useAI, useAppSettings, useAppTheme, useBreadcrumb, useConfirmation, useLocalization, useSupabase, useToast, useTranslationEdit, useTranslationNamespace };
444
+ export { type AIConfig, type AIGenerateOptions, AIProvider, type AIProviderProps, type AIProviderType, AppSettingsProvider, AuthProvider, BreadcrumbProvider, type ConfirmOptions, type ConfirmResult, ConfirmationProvider, type ConfirmationProviderProps, type CopyClipboardButtonComponentProps as CopyClipboardButtonProps, type DialogSeverity, type DisplayState, type EmailInputComponentProps as EmailInputProps, type Language, LocalizationProvider, type NumberInputComponentProps as NumberInputProps, Providers, type SupabaseConfig, SupabaseProvider, type SupabaseProviderProps, type TextFieldComponentProps as TextFieldProps, type ThemeColors, type ThemeConfig, type ThemeMode, ThemeProvider, ToastProvider, type ToastType, type TranslatableTooltipComponentProps as TranslatableTooltipProps, TranslateProvider, TranslateText, TranslationEditProvider, type TypographyConfig, UI, type UrlComponentProps as UrlProps, type UserAvatarComponentProps as UserAvatarProps, type UserPopoverComponentProps as UserPopoverProps, type UserRoleChipComponentProps as UserRoleChipProps, type UserStatusChipComponentProps as UserStatusChipProps, useAI, useAppSettings, useAppTheme, useAuth, useBreadcrumb, useConfirmation, useLocalization, useSupabase, useToast, useTranslationEdit, useTranslationNamespace };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as React$1 from 'react';
2
2
  import React__default, { ReactNode } from 'react';
3
3
  import { TextFieldProps, IconButtonProps, TooltipProps, SxProps, Theme } from '@mui/material';
4
- import { SupabaseClient } from '@supabase/supabase-js';
4
+ import { SupabaseClient, User, Session } from '@supabase/supabase-js';
5
5
 
6
6
  interface UrlValue {
7
7
  url: string;
@@ -112,7 +112,50 @@ interface UserPopoverComponentProps {
112
112
  onClose: () => void;
113
113
  }
114
114
 
115
+ interface UserProfile {
116
+ id: string;
117
+ email: string;
118
+ system_role: 'superadmin' | 'admin' | 'support' | null;
119
+ updated_at: string;
120
+ avatar_url: string | null;
121
+ avatar_path?: string | null;
122
+ first_name: string | null;
123
+ middle_name: string | null;
124
+ last_name: string | null;
125
+ display_name: string | null;
126
+ locked: boolean;
127
+ archived: boolean;
128
+ birthday: string | null;
129
+ phone: string | null;
130
+ description: string | null;
131
+ created_at: string;
132
+ cover_image_url: string | null;
133
+ cover_image_path?: string | null;
134
+ active_company_id: string | null;
135
+ name_order: 'first_last' | 'last_first' | null;
136
+ include_middle_name: boolean | null;
137
+ name_genitive: string | null;
138
+ name_dative: string | null;
139
+ name_instrumental: string | null;
140
+ name_vocative: string | null;
141
+ }
142
+
115
143
  type MembershipRole = 'superadmin' | 'admin' | 'user';
144
+ interface Membership {
145
+ id: string;
146
+ user_id: string;
147
+ company_id: string;
148
+ role: 'superadmin' | 'admin' | 'user';
149
+ created_at: string;
150
+ last_updated_at: string | null;
151
+ first_login_at?: string | null;
152
+ last_login_at?: string | null;
153
+ employee_post: string | null;
154
+ work_schedule_id: string | null;
155
+ locked: boolean;
156
+ locked_reason: string | null;
157
+ excluded: boolean;
158
+ }
116
159
 
117
160
  interface UserRoleChipComponentProps {
118
161
  overrideNamespace?: string;
@@ -233,6 +276,25 @@ declare const AppSettingsProvider: React__default.FC<{
233
276
  }>;
234
277
  declare const useAppSettings: () => AppSettingsContextType;
235
278
 
279
+ interface AuthContextType {
280
+ user: User | null;
281
+ profile: UserProfile | null;
282
+ membership: Membership | null;
283
+ userRole: MembershipRole | null;
284
+ activeCompanyId: string | null;
285
+ session: Session | null;
286
+ isAuthenticated: boolean;
287
+ loading: boolean;
288
+ signOut: () => Promise<void>;
289
+ updateProfile: (newProfileData: Partial<UserProfile>) => void;
290
+ refreshProfile: () => Promise<void>;
291
+ setActiveCompany: (companyId: string) => Promise<void>;
292
+ }
293
+ declare const AuthProvider: React__default.FC<{
294
+ children: React__default.ReactNode;
295
+ }>;
296
+ declare const useAuth: () => AuthContextType;
297
+
236
298
  interface TranslateTextProps {
237
299
  overrideNamespace?: string;
238
300
  children: string;
@@ -356,6 +418,9 @@ declare const Providers: {
356
418
  readonly AppSettings: React$1.FC<{
357
419
  children: React.ReactNode;
358
420
  }>;
421
+ readonly Auth: React$1.FC<{
422
+ children: React.ReactNode;
423
+ }>;
359
424
  readonly Translate: React$1.FC<TranslateProviderProps>;
360
425
  readonly TranslationEdit: React$1.FC<{
361
426
  children: React.ReactNode;
@@ -376,4 +441,4 @@ declare const Providers: {
376
441
  readonly AI: React$1.FC<AIProviderProps>;
377
442
  };
378
443
 
379
- export { type AIConfig, type AIGenerateOptions, AIProvider, type AIProviderProps, type AIProviderType, AppSettingsProvider, BreadcrumbProvider, type ConfirmOptions, type ConfirmResult, ConfirmationProvider, type ConfirmationProviderProps, type CopyClipboardButtonComponentProps as CopyClipboardButtonProps, type DialogSeverity, type DisplayState, type EmailInputComponentProps as EmailInputProps, type Language, LocalizationProvider, type NumberInputComponentProps as NumberInputProps, Providers, type SupabaseConfig, SupabaseProvider, type SupabaseProviderProps, type TextFieldComponentProps as TextFieldProps, type ThemeColors, type ThemeConfig, type ThemeMode, ThemeProvider, ToastProvider, type ToastType, type TranslatableTooltipComponentProps as TranslatableTooltipProps, TranslateProvider, TranslateText, TranslationEditProvider, type TypographyConfig, UI, type UrlComponentProps as UrlProps, type UserAvatarComponentProps as UserAvatarProps, type UserPopoverComponentProps as UserPopoverProps, type UserRoleChipComponentProps as UserRoleChipProps, type UserStatusChipComponentProps as UserStatusChipProps, useAI, useAppSettings, useAppTheme, useBreadcrumb, useConfirmation, useLocalization, useSupabase, useToast, useTranslationEdit, useTranslationNamespace };
444
+ export { type AIConfig, type AIGenerateOptions, AIProvider, type AIProviderProps, type AIProviderType, AppSettingsProvider, AuthProvider, BreadcrumbProvider, type ConfirmOptions, type ConfirmResult, ConfirmationProvider, type ConfirmationProviderProps, type CopyClipboardButtonComponentProps as CopyClipboardButtonProps, type DialogSeverity, type DisplayState, type EmailInputComponentProps as EmailInputProps, type Language, LocalizationProvider, type NumberInputComponentProps as NumberInputProps, Providers, type SupabaseConfig, SupabaseProvider, type SupabaseProviderProps, type TextFieldComponentProps as TextFieldProps, type ThemeColors, type ThemeConfig, type ThemeMode, ThemeProvider, ToastProvider, type ToastType, type TranslatableTooltipComponentProps as TranslatableTooltipProps, TranslateProvider, TranslateText, TranslationEditProvider, type TypographyConfig, UI, type UrlComponentProps as UrlProps, type UserAvatarComponentProps as UserAvatarProps, type UserPopoverComponentProps as UserPopoverProps, type UserRoleChipComponentProps as UserRoleChipProps, type UserStatusChipComponentProps as UserStatusChipProps, useAI, useAppSettings, useAppTheme, useAuth, useBreadcrumb, useConfirmation, useLocalization, useSupabase, useToast, useTranslationEdit, useTranslationNamespace };
package/dist/index.js CHANGED
@@ -153,18 +153,64 @@ var init_config = __esm({
153
153
  });
154
154
 
155
155
  // src/utils/deviceId.ts
156
+ var getDeviceId;
156
157
  var init_deviceId = __esm({
157
158
  "src/utils/deviceId.ts"() {
158
159
  "use strict";
160
+ getDeviceId = () => {
161
+ let deviceId = localStorage.getItem("app_device_id");
162
+ if (!deviceId) {
163
+ deviceId = crypto.randomUUID();
164
+ localStorage.setItem("app_device_id", deviceId);
165
+ }
166
+ return deviceId;
167
+ };
159
168
  }
160
169
  });
161
170
 
162
171
  // src/services/session.service.ts
163
- var getUserLastSeenAt;
172
+ var trackUserSession, isCurrentSessionValid, getUserLastSeenAt;
164
173
  var init_session_service = __esm({
165
174
  "src/services/session.service.ts"() {
166
175
  "use strict";
167
176
  init_deviceId();
177
+ trackUserSession = async (supabase, userId) => {
178
+ const deviceId = getDeviceId();
179
+ let ip = "";
180
+ try {
181
+ const ipRes = await fetch("https://api.ipify.org?format=json");
182
+ const ipData = await ipRes.json();
183
+ ip = ipData.ip;
184
+ } catch (e) {
185
+ console.warn("Failed to fetch IP address", e);
186
+ }
187
+ await supabase.from("user_sessions").upsert(
188
+ {
189
+ user_id: userId,
190
+ device_id: deviceId,
191
+ user_agent: navigator.userAgent,
192
+ ip_address: ip,
193
+ url: window.location.href,
194
+ last_seen_at: (/* @__PURE__ */ new Date()).toISOString()
195
+ },
196
+ { onConflict: "user_id, device_id" }
197
+ );
198
+ };
199
+ isCurrentSessionValid = async (supabase, userId) => {
200
+ try {
201
+ const currentDeviceId = getDeviceId();
202
+ const { data, error } = await supabase.from("user_sessions").select("id").eq("user_id", userId).eq("device_id", currentDeviceId).maybeSingle();
203
+ if (error) {
204
+ console.warn("\u041F\u043E\u043C\u0438\u043B\u043A\u0430 \u043F\u0435\u0440\u0435\u0432\u0456\u0440\u043A\u0438 \u0441\u0435\u0441\u0456\u0457:", error.message);
205
+ return true;
206
+ }
207
+ if (!data) return false;
208
+ return true;
209
+ } catch (err) {
210
+ console.error("\u041D\u0435\u0441\u043F\u043E\u0434\u0456\u0432\u0430\u043D\u0430 \u043F\u043E\u043C\u0438\u043B\u043A\u0430 \u043F\u0440\u0438 \u043F\u0435\u0440\u0435\u0432\u0456\u0440\u0446\u0456 \u0441\u0435\u0441\u0456\u0457:", err);
211
+ return true;
212
+ }
213
+ };
168
214
  getUserLastSeenAt = async (supabase, userId) => {
169
215
  const { data, error } = await supabase.from("user_sessions").select("last_seen_at").eq("user_id", userId).order("last_seen_at", { ascending: false }).limit(1).maybeSingle();
170
216
  if (error || !data) return null;
@@ -209,7 +255,7 @@ var init_SupabaseProvider = __esm({
209
255
  });
210
256
 
211
257
  // src/context/AuthProvider.tsx
212
- var import_react2, import_jsx_runtime2, AuthContext, useAuth;
258
+ var import_react2, import_jsx_runtime2, AuthContext, AuthProvider, useAuth;
213
259
  var init_AuthProvider = __esm({
214
260
  "src/context/AuthProvider.tsx"() {
215
261
  "use strict";
@@ -218,6 +264,179 @@ var init_AuthProvider = __esm({
218
264
  init_SupabaseProvider();
219
265
  import_jsx_runtime2 = require("react/jsx-runtime");
220
266
  AuthContext = (0, import_react2.createContext)(void 0);
267
+ AuthProvider = ({ children }) => {
268
+ const supabase = useSupabase();
269
+ const [user, setUser] = (0, import_react2.useState)(null);
270
+ const [profile, setProfile] = (0, import_react2.useState)(null);
271
+ const [membership, setMembership] = (0, import_react2.useState)(null);
272
+ const [session, setSession] = (0, import_react2.useState)(null);
273
+ const [loading, setLoading] = (0, import_react2.useState)(true);
274
+ const currentUserIdRef = (0, import_react2.useRef)(null);
275
+ const fetchProfileAndMembership = (0, import_react2.useCallback)(async (userId) => {
276
+ try {
277
+ const { data, error } = await supabase.rpc("get_user_avatar_and_profile", {
278
+ p_user_id: userId
279
+ });
280
+ if (error || !data) {
281
+ console.error("Error fetching user context via RPC:", error?.message);
282
+ setProfile(null);
283
+ setMembership(null);
284
+ return;
285
+ }
286
+ const response = data;
287
+ if (response?.profile) {
288
+ const profileData = response.profile;
289
+ if (profileData.avatar_path && !profileData.avatar_path.startsWith("http")) {
290
+ const cleanPath = profileData.avatar_path.replace(/^user-media\//, "");
291
+ const { data: avatarSignedData } = await supabase.storage.from("user-media").createSignedUrl(cleanPath, 3600);
292
+ if (avatarSignedData?.signedUrl) {
293
+ profileData.avatar_url = avatarSignedData.signedUrl;
294
+ }
295
+ }
296
+ if (profileData.cover_image_path && !profileData.cover_image_path.startsWith("http")) {
297
+ const cleanPath = profileData.cover_image_path.replace(/^user-media\//, "");
298
+ const { data: coverSignedData } = await supabase.storage.from("user-media").createSignedUrl(cleanPath, 3600);
299
+ if (coverSignedData?.signedUrl) {
300
+ profileData.cover_image_url = coverSignedData.signedUrl;
301
+ }
302
+ }
303
+ let updatedMembership = response.membership;
304
+ if (updatedMembership) {
305
+ const now = (/* @__PURE__ */ new Date()).toISOString();
306
+ const isFirstLogin = !updatedMembership.first_login_at;
307
+ const updateData = {
308
+ last_login_at: now,
309
+ ...isFirstLogin && { first_login_at: now }
310
+ };
311
+ const { error: updateError } = await supabase.from("memberships").update(updateData).eq("id", updatedMembership.id);
312
+ if (updateError) {
313
+ console.error("Error updating membership login timestamps:", updateError.message);
314
+ } else {
315
+ updatedMembership = {
316
+ ...updatedMembership,
317
+ ...updateData
318
+ };
319
+ }
320
+ }
321
+ setProfile(profileData);
322
+ setMembership(updatedMembership);
323
+ }
324
+ } catch (err) {
325
+ console.error("Unexpected error fetching user context:", err);
326
+ setProfile(null);
327
+ setMembership(null);
328
+ }
329
+ }, []);
330
+ const refreshProfile = async () => {
331
+ if (currentUserIdRef.current) {
332
+ await fetchProfileAndMembership(currentUserIdRef.current);
333
+ }
334
+ };
335
+ const signOut = (0, import_react2.useCallback)(async () => {
336
+ await supabase.auth.signOut();
337
+ currentUserIdRef.current = null;
338
+ setUser(null);
339
+ setProfile(null);
340
+ setMembership(null);
341
+ setSession(null);
342
+ }, []);
343
+ const setActiveCompany = async (companyId) => {
344
+ if (!user) {
345
+ throw new Error("\u041F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044C \u043D\u0435 \u0430\u0432\u0442\u043E\u0440\u0438\u0437\u043E\u0432\u0430\u043D");
346
+ }
347
+ const { error: profileError } = await supabase.from("profiles").update({ active_company_id: companyId }).eq("id", user.id);
348
+ if (profileError) {
349
+ console.error("Error updating active company:", profileError);
350
+ throw new Error(profileError.message);
351
+ }
352
+ await refreshProfile();
353
+ };
354
+ const updateProfile = (newProfileData) => {
355
+ setProfile((prev) => prev ? { ...prev, ...newProfileData } : null);
356
+ };
357
+ (0, import_react2.useEffect)(() => {
358
+ const { data: { subscription } } = supabase.auth.onAuthStateChange(async (event, currentSession) => {
359
+ if (event === "TOKEN_REFRESHED" && currentUserIdRef.current === currentSession?.user?.id) {
360
+ setSession(currentSession);
361
+ return;
362
+ }
363
+ const currentUser = currentSession?.user ?? null;
364
+ const newUserId = currentUser?.id ?? null;
365
+ if (newUserId !== currentUserIdRef.current) {
366
+ currentUserIdRef.current = newUserId;
367
+ setUser(currentUser);
368
+ if (newUserId) {
369
+ await trackUserSession(supabase, newUserId).catch(console.error);
370
+ const isAuthLinkEvent = event === "SIGNED_IN" || event === "INITIAL_SESSION" || event === "PASSWORD_RECOVERY";
371
+ if (!isAuthLinkEvent) {
372
+ const isValid = await isCurrentSessionValid(supabase, newUserId);
373
+ if (!isValid) {
374
+ await supabase.auth.signOut();
375
+ currentUserIdRef.current = null;
376
+ setUser(null);
377
+ setProfile(null);
378
+ setMembership(null);
379
+ setLoading(false);
380
+ return;
381
+ }
382
+ }
383
+ await fetchProfileAndMembership(newUserId);
384
+ setSession(currentSession);
385
+ } else {
386
+ setProfile(null);
387
+ setMembership(null);
388
+ }
389
+ }
390
+ setLoading(false);
391
+ });
392
+ return () => {
393
+ subscription.unsubscribe();
394
+ };
395
+ }, [fetchProfileAndMembership]);
396
+ (0, import_react2.useEffect)(() => {
397
+ if (!user) return;
398
+ const checkSession = async () => {
399
+ try {
400
+ const valid = await isCurrentSessionValid(supabase, user.id);
401
+ if (!valid) {
402
+ console.warn("\u0421\u0435\u0441\u0456\u044E \u0432\u0456\u0434\u043A\u043B\u0438\u043A\u0430\u043D\u043E \u0437 \u0456\u043D\u0448\u043E\u0433\u043E \u043F\u0440\u0438\u0441\u0442\u0440\u043E\u044E.");
403
+ await signOut();
404
+ }
405
+ } catch (err) {
406
+ console.error("\u041F\u043E\u043C\u0438\u043B\u043A\u0430 \u043F\u0440\u0438 \u043F\u0435\u0440\u0435\u0432\u0456\u0440\u0446\u0456 \u0441\u0435\u0441\u0456\u0457:", err);
407
+ }
408
+ };
409
+ const interval = setInterval(checkSession, 6e4);
410
+ const handleFocus = () => checkSession();
411
+ window.addEventListener("focus", handleFocus);
412
+ return () => {
413
+ clearInterval(interval);
414
+ window.removeEventListener("focus", handleFocus);
415
+ };
416
+ }, [user, signOut]);
417
+ const activeCompanyId = profile?.active_company_id ?? null;
418
+ const userRole = membership?.role ?? null;
419
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
420
+ AuthContext.Provider,
421
+ {
422
+ value: {
423
+ user,
424
+ profile,
425
+ membership,
426
+ userRole,
427
+ activeCompanyId,
428
+ session,
429
+ isAuthenticated: !!user,
430
+ loading,
431
+ signOut,
432
+ updateProfile,
433
+ refreshProfile,
434
+ setActiveCompany
435
+ },
436
+ children
437
+ }
438
+ );
439
+ };
221
440
  useAuth = () => {
222
441
  const context = (0, import_react2.useContext)(AuthContext);
223
442
  if (!context) {
@@ -1600,8 +1819,10 @@ var init_context = __esm({
1600
1819
  init_ConfirmationProvider();
1601
1820
  init_AIProvider();
1602
1821
  init_AppSettingsProvider();
1822
+ init_AuthProvider();
1603
1823
  init_SupabaseProvider();
1604
1824
  init_AppSettingsProvider();
1825
+ init_AuthProvider();
1605
1826
  init_TranslateProvider();
1606
1827
  init_TranslateText();
1607
1828
  init_TranslationEditProvider();
@@ -1614,6 +1835,7 @@ var init_context = __esm({
1614
1835
  Providers = {
1615
1836
  Supabase: SupabaseProvider,
1616
1837
  AppSettings: AppSettingsProvider,
1838
+ Auth: AuthProvider,
1617
1839
  Translate: TranslateProvider,
1618
1840
  TranslationEdit: TranslationEditProvider,
1619
1841
  Localization: LocalizationProvider,
@@ -2204,6 +2426,7 @@ var index_exports = {};
2204
2426
  __export(index_exports, {
2205
2427
  AIProvider: () => AIProvider,
2206
2428
  AppSettingsProvider: () => AppSettingsProvider,
2429
+ AuthProvider: () => AuthProvider,
2207
2430
  BreadcrumbProvider: () => BreadcrumbProvider,
2208
2431
  ConfirmationProvider: () => ConfirmationProvider,
2209
2432
  LocalizationProvider: () => LocalizationProvider,
@@ -2218,6 +2441,7 @@ __export(index_exports, {
2218
2441
  useAI: () => useAI,
2219
2442
  useAppSettings: () => useAppSettings,
2220
2443
  useAppTheme: () => useAppTheme,
2444
+ useAuth: () => useAuth,
2221
2445
  useBreadcrumb: () => useBreadcrumb,
2222
2446
  useConfirmation: () => useConfirmation,
2223
2447
  useLocalization: () => useLocalization,
@@ -3069,6 +3293,7 @@ init_context();
3069
3293
  0 && (module.exports = {
3070
3294
  AIProvider,
3071
3295
  AppSettingsProvider,
3296
+ AuthProvider,
3072
3297
  BreadcrumbProvider,
3073
3298
  ConfirmationProvider,
3074
3299
  LocalizationProvider,
@@ -3083,6 +3308,7 @@ init_context();
3083
3308
  useAI,
3084
3309
  useAppSettings,
3085
3310
  useAppTheme,
3311
+ useAuth,
3086
3312
  useBreadcrumb,
3087
3313
  useConfirmation,
3088
3314
  useLocalization,
package/dist/index.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  AIProvider,
3
3
  AppSettingsProvider,
4
+ AuthProvider,
4
5
  BreadcrumbProvider,
5
6
  ConfirmationProvider,
6
7
  CopyClipboardButtonComponent,
@@ -21,6 +22,7 @@ import {
21
22
  useAI,
22
23
  useAppSettings,
23
24
  useAppTheme,
25
+ useAuth,
24
26
  useBreadcrumb,
25
27
  useConfirmation,
26
28
  useLocalization,
@@ -28,7 +30,7 @@ import {
28
30
  useToast,
29
31
  useTranslationEdit,
30
32
  useTranslationNamespace
31
- } from "./chunk-UYCEKHKQ.mjs";
33
+ } from "./chunk-ABYNAO4A.mjs";
32
34
 
33
35
  // src/UI/components/inputs/TextFieldComponent.tsx
34
36
  import { useState } from "react";
@@ -726,7 +728,7 @@ var EmailInputComponent = ({
726
728
  import { useState as useState5, useEffect as useEffect2, lazy, Suspense } from "react";
727
729
  import { Avatar, Tooltip as Tooltip3, Typography as Typography2 } from "@mui/material";
728
730
  import { Fragment as Fragment4, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
729
- var LazyUserPopover = lazy(() => import("./UserPopoverComponent-MG2V632M.mjs"));
731
+ var LazyUserPopover = lazy(() => import("./UserPopoverComponent-YKVTSRTJ.mjs"));
730
732
  var getInitials = (name) => {
731
733
  if (!name) return "?";
732
734
  const parts = name.trim().split(" ");
@@ -865,6 +867,7 @@ var UI = {
865
867
  export {
866
868
  AIProvider,
867
869
  AppSettingsProvider,
870
+ AuthProvider,
868
871
  BreadcrumbProvider,
869
872
  ConfirmationProvider,
870
873
  LocalizationProvider,
@@ -879,6 +882,7 @@ export {
879
882
  useAI,
880
883
  useAppSettings,
881
884
  useAppTheme,
885
+ useAuth,
882
886
  useBreadcrumb,
883
887
  useConfirmation,
884
888
  useLocalization,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serhii.olishevskyi/docs",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "DOCS.UA react packages",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",