@tenxyte/react 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,192 @@
1
+ # @tenxyte/react
2
+
3
+ React bindings for the [Tenxyte SDK](https://www.npmjs.com/package/@tenxyte/core). Provides reactive hooks that automatically re-render your components when authentication state changes.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @tenxyte/core @tenxyte/react
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ### 1. Create the client and wrap your app
14
+
15
+ ```tsx
16
+ import { TenxyteClient } from '@tenxyte/core';
17
+ import { TenxyteProvider } from '@tenxyte/react';
18
+ import App from './App';
19
+
20
+ const tx = new TenxyteClient({
21
+ baseUrl: 'https://api.example.com',
22
+ applicationId: 'your-app-id',
23
+ apiKey: 'your-api-key',
24
+ });
25
+
26
+ function Root() {
27
+ return (
28
+ <TenxyteProvider client={tx}>
29
+ <App />
30
+ </TenxyteProvider>
31
+ );
32
+ }
33
+ ```
34
+
35
+ ### 2. Use hooks in any component
36
+
37
+ ```tsx
38
+ import { useAuth, useUser, useRbac, useOrganization } from '@tenxyte/react';
39
+
40
+ function Dashboard() {
41
+ const { isAuthenticated, loading, logout } = useAuth();
42
+ const { user } = useUser();
43
+ const { hasRole } = useRbac();
44
+
45
+ if (loading) return <p>Loading...</p>;
46
+ if (!isAuthenticated) return <LoginPage />;
47
+
48
+ return (
49
+ <div>
50
+ <p>Welcome, {user?.email}</p>
51
+ {hasRole('admin') && <AdminPanel />}
52
+ <button onClick={logout}>Logout</button>
53
+ </div>
54
+ );
55
+ }
56
+ ```
57
+
58
+ ## Hooks
59
+
60
+ ### `useAuth()`
61
+
62
+ Reactive authentication state and actions.
63
+
64
+ ```tsx
65
+ const {
66
+ isAuthenticated, // boolean — true if access token is valid and not expired
67
+ loading, // boolean — true while initial state loads from storage
68
+ accessToken, // string | null — raw JWT access token
69
+ loginWithEmail, // (data: { email, password, device_info?, totp_code? }) => Promise<void>
70
+ loginWithPhone, // (data: { phone_country_code, phone_number, password, device_info? }) => Promise<void>
71
+ logout, // () => Promise<void>
72
+ register, // (data) => Promise<void>
73
+ } = useAuth();
74
+ ```
75
+
76
+ **Example — Login form:**
77
+
78
+ ```tsx
79
+ function LoginPage() {
80
+ const { loginWithEmail } = useAuth();
81
+ const [email, setEmail] = useState('');
82
+ const [password, setPassword] = useState('');
83
+
84
+ const handleSubmit = async (e: FormEvent) => {
85
+ e.preventDefault();
86
+ await loginWithEmail({ email, password });
87
+ };
88
+
89
+ return (
90
+ <form onSubmit={handleSubmit}>
91
+ <input value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email" />
92
+ <input value={password} onChange={(e) => setPassword(e.target.value)} type="password" />
93
+ <button type="submit">Sign In</button>
94
+ </form>
95
+ );
96
+ }
97
+ ```
98
+
99
+ ### `useUser()`
100
+
101
+ Decoded JWT user and profile management.
102
+
103
+ ```tsx
104
+ const {
105
+ user, // DecodedTenxyteToken | null — decoded JWT payload
106
+ loading, // boolean
107
+ getProfile, // () => Promise<UserProfile> — fetch full profile from API
108
+ updateProfile, // (data) => Promise<unknown>
109
+ } = useUser();
110
+ ```
111
+
112
+ **Example:**
113
+
114
+ ```tsx
115
+ function UserBadge() {
116
+ const { user, loading } = useUser();
117
+ if (loading || !user) return null;
118
+ return <span>{user.email}</span>;
119
+ }
120
+ ```
121
+
122
+ ### `useOrganization()`
123
+
124
+ Multi-tenant organization context (B2B).
125
+
126
+ ```tsx
127
+ const {
128
+ activeOrg, // string | null — current org slug
129
+ switchOrganization, // (slug: string) => void
130
+ clearOrganization, // () => void
131
+ } = useOrganization();
132
+ ```
133
+
134
+ **Example:**
135
+
136
+ ```tsx
137
+ function OrgSwitcher({ orgs }: { orgs: { slug: string; name: string }[] }) {
138
+ const { activeOrg, switchOrganization, clearOrganization } = useOrganization();
139
+
140
+ return (
141
+ <select
142
+ value={activeOrg ?? ''}
143
+ onChange={(e) =>
144
+ e.target.value ? switchOrganization(e.target.value) : clearOrganization()
145
+ }
146
+ >
147
+ <option value="">No organization</option>
148
+ {orgs.map((o) => (
149
+ <option key={o.slug} value={o.slug}>{o.name}</option>
150
+ ))}
151
+ </select>
152
+ );
153
+ }
154
+ ```
155
+
156
+ ### `useRbac()`
157
+
158
+ Synchronous role and permission checks from the current JWT.
159
+
160
+ ```tsx
161
+ const {
162
+ hasRole, // (role: string) => boolean
163
+ hasPermission, // (permission: string) => boolean
164
+ hasAnyRole, // (roles: string[]) => boolean
165
+ hasAllRoles, // (roles: string[]) => boolean
166
+ } = useRbac();
167
+ ```
168
+
169
+ **Example:**
170
+
171
+ ```tsx
172
+ function AdminPanel() {
173
+ const { hasRole } = useRbac();
174
+ if (!hasRole('admin')) return <p>Access denied</p>;
175
+ return <AdminDashboard />;
176
+ }
177
+ ```
178
+
179
+ ## How It Works
180
+
181
+ `TenxyteProvider` places the `TenxyteClient` instance into React context. Each hook subscribes to SDK events (`token:stored`, `token:refreshed`, `session:expired`) and triggers a re-render when the auth state changes. All state updates are automatic — no manual invalidation needed.
182
+
183
+ ## Peer Dependencies
184
+
185
+ | Package | Version |
186
+ |---|---|
187
+ | `@tenxyte/core` | `^0.9.2` |
188
+ | `react` | `^18.0.0 \|\| ^19.0.0` |
189
+
190
+ ## License
191
+
192
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,188 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ TenxyteContext: () => TenxyteContext,
34
+ TenxyteProvider: () => TenxyteProvider,
35
+ useAuth: () => useAuth,
36
+ useOrganization: () => useOrganization,
37
+ useRbac: () => useRbac,
38
+ useTenxyteClient: () => useTenxyteClient,
39
+ useTenxyteState: () => useTenxyteState,
40
+ useUser: () => useUser
41
+ });
42
+ module.exports = __toCommonJS(index_exports);
43
+
44
+ // src/context.tsx
45
+ var import_react = require("react");
46
+ var TenxyteContext = (0, import_react.createContext)(null);
47
+ function useTenxyteClient() {
48
+ const client = (0, import_react.useContext)(TenxyteContext);
49
+ if (!client) {
50
+ throw new Error(
51
+ "[@tenxyte/react] useTenxyteClient must be used within a <TenxyteProvider>. Wrap your app with <TenxyteProvider client={tx}>."
52
+ );
53
+ }
54
+ return client;
55
+ }
56
+
57
+ // src/provider.tsx
58
+ var import_react2 = __toESM(require("react"), 1);
59
+ var import_jsx_runtime = require("react/jsx-runtime");
60
+ function TenxyteProvider({ client, children }) {
61
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TenxyteContext.Provider, { value: client, children });
62
+ }
63
+ function useTenxyteState() {
64
+ const client = import_react2.default.useContext(TenxyteContext);
65
+ if (!client) {
66
+ throw new Error("[@tenxyte/react] useTenxyteState must be used within a <TenxyteProvider>.");
67
+ }
68
+ const [state, setState] = (0, import_react2.useState)({
69
+ isAuthenticated: false,
70
+ user: null,
71
+ accessToken: null,
72
+ activeOrg: null,
73
+ isAgentMode: false
74
+ });
75
+ const [loading, setLoading] = (0, import_react2.useState)(true);
76
+ const refresh = (0, import_react2.useCallback)(async () => {
77
+ const snapshot = await client.getState();
78
+ setState(snapshot);
79
+ setLoading(false);
80
+ }, [client]);
81
+ (0, import_react2.useEffect)(() => {
82
+ refresh();
83
+ const unsubs = [
84
+ client.on("token:stored", () => {
85
+ refresh();
86
+ }),
87
+ client.on("token:refreshed", () => {
88
+ refresh();
89
+ }),
90
+ client.on("session:expired", () => {
91
+ refresh();
92
+ })
93
+ ];
94
+ return () => {
95
+ unsubs.forEach((unsub) => unsub());
96
+ };
97
+ }, [client, refresh]);
98
+ return (0, import_react2.useMemo)(() => ({ ...state, loading }), [state, loading]);
99
+ }
100
+
101
+ // src/hooks.ts
102
+ var import_react3 = require("react");
103
+ function useAuth() {
104
+ const client = useTenxyteClient();
105
+ const { isAuthenticated, loading, accessToken } = useTenxyteState();
106
+ const loginWithEmail = (0, import_react3.useCallback)(
107
+ async (data) => {
108
+ await client.auth.loginWithEmail({ ...data, device_info: data.device_info ?? "" });
109
+ },
110
+ [client]
111
+ );
112
+ const loginWithPhone = (0, import_react3.useCallback)(
113
+ async (data) => {
114
+ await client.auth.loginWithPhone({ ...data, device_info: data.device_info ?? "" });
115
+ },
116
+ [client]
117
+ );
118
+ const logout = (0, import_react3.useCallback)(async () => {
119
+ await client.auth.logoutAll();
120
+ }, [client]);
121
+ const register = (0, import_react3.useCallback)(
122
+ async (data) => {
123
+ await client.auth.register(data);
124
+ },
125
+ [client]
126
+ );
127
+ return { isAuthenticated, loading, accessToken, loginWithEmail, loginWithPhone, logout, register };
128
+ }
129
+ function useUser() {
130
+ const client = useTenxyteClient();
131
+ const { user, loading } = useTenxyteState();
132
+ const getProfile = (0, import_react3.useCallback)(() => client.user.getProfile(), [client]);
133
+ const updateProfile = (0, import_react3.useCallback)(
134
+ (data) => client.user.updateProfile(data),
135
+ [client]
136
+ );
137
+ return { user, loading, getProfile, updateProfile };
138
+ }
139
+ function useOrganization() {
140
+ const client = useTenxyteClient();
141
+ const { activeOrg } = useTenxyteState();
142
+ const switchOrganization = (0, import_react3.useCallback)(
143
+ (slug) => {
144
+ client.b2b.switchOrganization(slug);
145
+ },
146
+ [client]
147
+ );
148
+ const clearOrganization = (0, import_react3.useCallback)(
149
+ () => {
150
+ client.b2b.clearOrganization();
151
+ },
152
+ [client]
153
+ );
154
+ return { activeOrg, switchOrganization, clearOrganization };
155
+ }
156
+ function useRbac() {
157
+ const client = useTenxyteClient();
158
+ const { accessToken } = useTenxyteState();
159
+ const hasRole = (0, import_react3.useCallback)(
160
+ (role) => client.rbac.hasRole(role, accessToken ?? void 0),
161
+ [client, accessToken]
162
+ );
163
+ const hasPermission = (0, import_react3.useCallback)(
164
+ (permission) => client.rbac.hasPermission(permission, accessToken ?? void 0),
165
+ [client, accessToken]
166
+ );
167
+ const hasAnyRole = (0, import_react3.useCallback)(
168
+ (roles) => client.rbac.hasAnyRole(roles, accessToken ?? void 0),
169
+ [client, accessToken]
170
+ );
171
+ const hasAllRoles = (0, import_react3.useCallback)(
172
+ (roles) => client.rbac.hasAllRoles(roles, accessToken ?? void 0),
173
+ [client, accessToken]
174
+ );
175
+ return { hasRole, hasPermission, hasAnyRole, hasAllRoles };
176
+ }
177
+ // Annotate the CommonJS export names for ESM import in node:
178
+ 0 && (module.exports = {
179
+ TenxyteContext,
180
+ TenxyteProvider,
181
+ useAuth,
182
+ useOrganization,
183
+ useRbac,
184
+ useTenxyteClient,
185
+ useTenxyteState,
186
+ useUser
187
+ });
188
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/context.tsx","../src/provider.tsx","../src/hooks.ts"],"sourcesContent":["export { TenxyteContext, useTenxyteClient } from './context';\nexport { TenxyteProvider, useTenxyteState } from './provider';\nexport type { TenxyteProviderProps } from './provider';\nexport { useAuth, useUser, useOrganization, useRbac } from './hooks';\nexport type { UseAuthReturn, UseUserReturn, UseOrganizationReturn, UseRbacReturn } from './hooks';\n","import { createContext, useContext } from 'react';\nimport type { TenxyteClient } from '@tenxyte/core';\n\n/**\n * React context holding the TenxyteClient instance.\n * Must be provided via <TenxyteProvider>.\n */\nexport const TenxyteContext = createContext<TenxyteClient | null>(null);\n\n/**\n * Internal hook to access the TenxyteClient instance.\n * Throws if used outside of a <TenxyteProvider>.\n */\nexport function useTenxyteClient(): TenxyteClient {\n const client = useContext(TenxyteContext);\n if (!client) {\n throw new Error(\n '[@tenxyte/react] useTenxyteClient must be used within a <TenxyteProvider>. ' +\n 'Wrap your app with <TenxyteProvider client={tx}>.',\n );\n }\n return client;\n}\n","import React, { useCallback, useEffect, useMemo, useState } from 'react';\nimport type { TenxyteClient, TenxyteClientState } from '@tenxyte/core';\nimport { TenxyteContext } from './context';\n\nexport interface TenxyteProviderProps {\n /** The initialized TenxyteClient instance. */\n client: TenxyteClient;\n children: React.ReactNode;\n}\n\n/**\n * Provides the TenxyteClient to all descendant components and manages\n * reactive state synchronization via SDK events.\n *\n * @example\n * ```tsx\n * import { TenxyteClient } from '@tenxyte/core';\n * import { TenxyteProvider } from '@tenxyte/react';\n *\n * const tx = new TenxyteClient({ baseUrl: '...' });\n *\n * function App() {\n * return (\n * <TenxyteProvider client={tx}>\n * <MyApp />\n * </TenxyteProvider>\n * );\n * }\n * ```\n */\nexport function TenxyteProvider({ client, children }: TenxyteProviderProps): React.JSX.Element {\n return (\n <TenxyteContext.Provider value={client}>\n {children}\n </TenxyteContext.Provider>\n );\n}\n\n/**\n * Internal hook that subscribes to SDK events and returns a reactive state snapshot.\n * Re-renders consuming components whenever auth state changes.\n */\nexport function useTenxyteState(): TenxyteClientState & { loading: boolean } {\n const client = React.useContext(TenxyteContext);\n if (!client) {\n throw new Error('[@tenxyte/react] useTenxyteState must be used within a <TenxyteProvider>.');\n }\n\n const [state, setState] = useState<TenxyteClientState>({\n isAuthenticated: false,\n user: null,\n accessToken: null,\n activeOrg: null,\n isAgentMode: false,\n });\n const [loading, setLoading] = useState(true);\n\n const refresh = useCallback(async () => {\n const snapshot = await client.getState();\n setState(snapshot);\n setLoading(false);\n }, [client]);\n\n useEffect(() => {\n // Initial state load\n refresh();\n\n // Subscribe to all state-changing events\n const unsubs = [\n client.on('token:stored', () => { refresh(); }),\n client.on('token:refreshed', () => { refresh(); }),\n client.on('session:expired', () => { refresh(); }),\n ];\n\n return () => {\n unsubs.forEach((unsub) => unsub());\n };\n }, [client, refresh]);\n\n return useMemo(() => ({ ...state, loading }), [state, loading]);\n}\n","import { useCallback } from 'react';\nimport type { DecodedTenxyteToken } from '@tenxyte/core';\nimport { useTenxyteClient } from './context';\nimport { useTenxyteState } from './provider';\n\n// ─── useAuth ───\n\nexport interface UseAuthReturn {\n /** Whether the user has a valid, non-expired access token. */\n isAuthenticated: boolean;\n /** Whether the initial state is still loading from storage. */\n loading: boolean;\n /** Raw access token string, or null. */\n accessToken: string | null;\n /** Login with email/password. Triggers re-render on success. */\n loginWithEmail: (data: { email: string; password: string; device_info?: string; totp_code?: string }) => Promise<void>;\n /** Login with phone/password. */\n loginWithPhone: (data: { phone_country_code: string; phone_number: string; password: string; device_info?: string }) => Promise<void>;\n /** Logout from all sessions. */\n logout: () => Promise<void>;\n /** Register a new account. */\n register: (data: Record<string, unknown>) => Promise<void>;\n}\n\n/**\n * Reactive authentication hook.\n * Automatically re-renders when the auth state changes (login, logout, refresh, expiry).\n *\n * @example\n * ```tsx\n * function LoginPage() {\n * const { isAuthenticated, loginWithEmail, logout, loading } = useAuth();\n *\n * if (loading) return <p>Loading...</p>;\n * if (isAuthenticated) return <button onClick={logout}>Logout</button>;\n *\n * return <button onClick={() => loginWithEmail({ email: '...', password: '...' })}>Login</button>;\n * }\n * ```\n */\nexport function useAuth(): UseAuthReturn {\n const client = useTenxyteClient();\n const { isAuthenticated, loading, accessToken } = useTenxyteState();\n\n const loginWithEmail = useCallback(\n async (data: { email: string; password: string; device_info?: string; totp_code?: string }) => {\n await client.auth.loginWithEmail({ ...data, device_info: data.device_info ?? '' });\n },\n [client],\n );\n\n const loginWithPhone = useCallback(\n async (data: { phone_country_code: string; phone_number: string; password: string; device_info?: string }) => {\n await client.auth.loginWithPhone({ ...data, device_info: data.device_info ?? '' });\n },\n [client],\n );\n\n const logout = useCallback(async () => {\n await client.auth.logoutAll();\n }, [client]);\n\n const register = useCallback(\n async (data: Record<string, unknown>) => {\n await client.auth.register(data as any);\n },\n [client],\n );\n\n return { isAuthenticated, loading, accessToken, loginWithEmail, loginWithPhone, logout, register };\n}\n\n// ─── useUser ───\n\nexport interface UseUserReturn {\n /** Decoded JWT payload of the current access token, or null. */\n user: DecodedTenxyteToken | null;\n /** Whether the initial state is still loading. */\n loading: boolean;\n /** Fetch the full user profile from the backend. */\n getProfile: () => ReturnType<typeof import('@tenxyte/core').TenxyteClient.prototype.user.getProfile>;\n /** Update the current user's profile. */\n updateProfile: (data: Record<string, unknown>) => Promise<unknown>;\n}\n\n/**\n * Reactive user hook.\n * Returns the decoded JWT user and convenience methods for profile management.\n *\n * @example\n * ```tsx\n * function UserBadge() {\n * const { user, loading } = useUser();\n * if (loading || !user) return null;\n * return <span>{user.email}</span>;\n * }\n * ```\n */\nexport function useUser(): UseUserReturn {\n const client = useTenxyteClient();\n const { user, loading } = useTenxyteState();\n\n const getProfile = useCallback(() => client.user.getProfile(), [client]);\n\n const updateProfile = useCallback(\n (data: Record<string, unknown>) => client.user.updateProfile(data),\n [client],\n );\n\n return { user, loading, getProfile, updateProfile };\n}\n\n// ─── useOrganization ───\n\nexport interface UseOrganizationReturn {\n /** Currently active organization slug, or null. */\n activeOrg: string | null;\n /** Switch the SDK to operate within an organization context. */\n switchOrganization: (slug: string) => void;\n /** Clear the organization context. */\n clearOrganization: () => void;\n}\n\n/**\n * Reactive organization context hook.\n * Returns the current org slug and methods to switch/clear context.\n *\n * @example\n * ```tsx\n * function OrgSwitcher({ orgs }) {\n * const { activeOrg, switchOrganization, clearOrganization } = useOrganization();\n * return (\n * <select value={activeOrg ?? ''} onChange={(e) =>\n * e.target.value ? switchOrganization(e.target.value) : clearOrganization()\n * }>\n * <option value=\"\">No org</option>\n * {orgs.map(o => <option key={o.slug} value={o.slug}>{o.name}</option>)}\n * </select>\n * );\n * }\n * ```\n */\nexport function useOrganization(): UseOrganizationReturn {\n const client = useTenxyteClient();\n const { activeOrg } = useTenxyteState();\n\n const switchOrganization = useCallback(\n (slug: string) => { client.b2b.switchOrganization(slug); },\n [client],\n );\n\n const clearOrganization = useCallback(\n () => { client.b2b.clearOrganization(); },\n [client],\n );\n\n return { activeOrg, switchOrganization, clearOrganization };\n}\n\n// ─── useRbac ───\n\nexport interface UseRbacReturn {\n /** Check if the current user has a specific role (synchronous JWT check). */\n hasRole: (role: string) => boolean;\n /** Check if the current user has a specific permission (synchronous JWT check). */\n hasPermission: (permission: string) => boolean;\n /** Check if the current user has any of the given roles. */\n hasAnyRole: (roles: string[]) => boolean;\n /** Check if the current user has all of the given roles. */\n hasAllRoles: (roles: string[]) => boolean;\n}\n\n/**\n * Reactive RBAC hook.\n * Provides synchronous role/permission checks based on the current JWT.\n *\n * @example\n * ```tsx\n * function AdminPanel() {\n * const { hasRole } = useRbac();\n * if (!hasRole('admin')) return <p>Access denied</p>;\n * return <AdminDashboard />;\n * }\n * ```\n */\nexport function useRbac(): UseRbacReturn {\n const client = useTenxyteClient();\n const { accessToken } = useTenxyteState();\n\n const hasRole = useCallback(\n (role: string) => client.rbac.hasRole(role, accessToken ?? undefined),\n [client, accessToken],\n );\n\n const hasPermission = useCallback(\n (permission: string) => client.rbac.hasPermission(permission, accessToken ?? undefined),\n [client, accessToken],\n );\n\n const hasAnyRole = useCallback(\n (roles: string[]) => client.rbac.hasAnyRole(roles, accessToken ?? undefined),\n [client, accessToken],\n );\n\n const hasAllRoles = useCallback(\n (roles: string[]) => client.rbac.hasAllRoles(roles, accessToken ?? undefined),\n [client, accessToken],\n );\n\n return { hasRole, hasPermission, hasAnyRole, hasAllRoles };\n}\n"],"mappings":"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","names":["import_react","React","import_react"]}
@@ -0,0 +1,171 @@
1
+ import * as React from 'react';
2
+ import React__default from 'react';
3
+ import * as _tenxyte_core from '@tenxyte/core';
4
+ import { TenxyteClient, TenxyteClientState, DecodedTenxyteToken } from '@tenxyte/core';
5
+
6
+ /**
7
+ * React context holding the TenxyteClient instance.
8
+ * Must be provided via <TenxyteProvider>.
9
+ */
10
+ declare const TenxyteContext: React.Context<TenxyteClient | null>;
11
+ /**
12
+ * Internal hook to access the TenxyteClient instance.
13
+ * Throws if used outside of a <TenxyteProvider>.
14
+ */
15
+ declare function useTenxyteClient(): TenxyteClient;
16
+
17
+ interface TenxyteProviderProps {
18
+ /** The initialized TenxyteClient instance. */
19
+ client: TenxyteClient;
20
+ children: React__default.ReactNode;
21
+ }
22
+ /**
23
+ * Provides the TenxyteClient to all descendant components and manages
24
+ * reactive state synchronization via SDK events.
25
+ *
26
+ * @example
27
+ * ```tsx
28
+ * import { TenxyteClient } from '@tenxyte/core';
29
+ * import { TenxyteProvider } from '@tenxyte/react';
30
+ *
31
+ * const tx = new TenxyteClient({ baseUrl: '...' });
32
+ *
33
+ * function App() {
34
+ * return (
35
+ * <TenxyteProvider client={tx}>
36
+ * <MyApp />
37
+ * </TenxyteProvider>
38
+ * );
39
+ * }
40
+ * ```
41
+ */
42
+ declare function TenxyteProvider({ client, children }: TenxyteProviderProps): React__default.JSX.Element;
43
+ /**
44
+ * Internal hook that subscribes to SDK events and returns a reactive state snapshot.
45
+ * Re-renders consuming components whenever auth state changes.
46
+ */
47
+ declare function useTenxyteState(): TenxyteClientState & {
48
+ loading: boolean;
49
+ };
50
+
51
+ interface UseAuthReturn {
52
+ /** Whether the user has a valid, non-expired access token. */
53
+ isAuthenticated: boolean;
54
+ /** Whether the initial state is still loading from storage. */
55
+ loading: boolean;
56
+ /** Raw access token string, or null. */
57
+ accessToken: string | null;
58
+ /** Login with email/password. Triggers re-render on success. */
59
+ loginWithEmail: (data: {
60
+ email: string;
61
+ password: string;
62
+ device_info?: string;
63
+ totp_code?: string;
64
+ }) => Promise<void>;
65
+ /** Login with phone/password. */
66
+ loginWithPhone: (data: {
67
+ phone_country_code: string;
68
+ phone_number: string;
69
+ password: string;
70
+ device_info?: string;
71
+ }) => Promise<void>;
72
+ /** Logout from all sessions. */
73
+ logout: () => Promise<void>;
74
+ /** Register a new account. */
75
+ register: (data: Record<string, unknown>) => Promise<void>;
76
+ }
77
+ /**
78
+ * Reactive authentication hook.
79
+ * Automatically re-renders when the auth state changes (login, logout, refresh, expiry).
80
+ *
81
+ * @example
82
+ * ```tsx
83
+ * function LoginPage() {
84
+ * const { isAuthenticated, loginWithEmail, logout, loading } = useAuth();
85
+ *
86
+ * if (loading) return <p>Loading...</p>;
87
+ * if (isAuthenticated) return <button onClick={logout}>Logout</button>;
88
+ *
89
+ * return <button onClick={() => loginWithEmail({ email: '...', password: '...' })}>Login</button>;
90
+ * }
91
+ * ```
92
+ */
93
+ declare function useAuth(): UseAuthReturn;
94
+ interface UseUserReturn {
95
+ /** Decoded JWT payload of the current access token, or null. */
96
+ user: DecodedTenxyteToken | null;
97
+ /** Whether the initial state is still loading. */
98
+ loading: boolean;
99
+ /** Fetch the full user profile from the backend. */
100
+ getProfile: () => ReturnType<typeof _tenxyte_core.TenxyteClient.prototype.user.getProfile>;
101
+ /** Update the current user's profile. */
102
+ updateProfile: (data: Record<string, unknown>) => Promise<unknown>;
103
+ }
104
+ /**
105
+ * Reactive user hook.
106
+ * Returns the decoded JWT user and convenience methods for profile management.
107
+ *
108
+ * @example
109
+ * ```tsx
110
+ * function UserBadge() {
111
+ * const { user, loading } = useUser();
112
+ * if (loading || !user) return null;
113
+ * return <span>{user.email}</span>;
114
+ * }
115
+ * ```
116
+ */
117
+ declare function useUser(): UseUserReturn;
118
+ interface UseOrganizationReturn {
119
+ /** Currently active organization slug, or null. */
120
+ activeOrg: string | null;
121
+ /** Switch the SDK to operate within an organization context. */
122
+ switchOrganization: (slug: string) => void;
123
+ /** Clear the organization context. */
124
+ clearOrganization: () => void;
125
+ }
126
+ /**
127
+ * Reactive organization context hook.
128
+ * Returns the current org slug and methods to switch/clear context.
129
+ *
130
+ * @example
131
+ * ```tsx
132
+ * function OrgSwitcher({ orgs }) {
133
+ * const { activeOrg, switchOrganization, clearOrganization } = useOrganization();
134
+ * return (
135
+ * <select value={activeOrg ?? ''} onChange={(e) =>
136
+ * e.target.value ? switchOrganization(e.target.value) : clearOrganization()
137
+ * }>
138
+ * <option value="">No org</option>
139
+ * {orgs.map(o => <option key={o.slug} value={o.slug}>{o.name}</option>)}
140
+ * </select>
141
+ * );
142
+ * }
143
+ * ```
144
+ */
145
+ declare function useOrganization(): UseOrganizationReturn;
146
+ interface UseRbacReturn {
147
+ /** Check if the current user has a specific role (synchronous JWT check). */
148
+ hasRole: (role: string) => boolean;
149
+ /** Check if the current user has a specific permission (synchronous JWT check). */
150
+ hasPermission: (permission: string) => boolean;
151
+ /** Check if the current user has any of the given roles. */
152
+ hasAnyRole: (roles: string[]) => boolean;
153
+ /** Check if the current user has all of the given roles. */
154
+ hasAllRoles: (roles: string[]) => boolean;
155
+ }
156
+ /**
157
+ * Reactive RBAC hook.
158
+ * Provides synchronous role/permission checks based on the current JWT.
159
+ *
160
+ * @example
161
+ * ```tsx
162
+ * function AdminPanel() {
163
+ * const { hasRole } = useRbac();
164
+ * if (!hasRole('admin')) return <p>Access denied</p>;
165
+ * return <AdminDashboard />;
166
+ * }
167
+ * ```
168
+ */
169
+ declare function useRbac(): UseRbacReturn;
170
+
171
+ export { TenxyteContext, TenxyteProvider, type TenxyteProviderProps, type UseAuthReturn, type UseOrganizationReturn, type UseRbacReturn, type UseUserReturn, useAuth, useOrganization, useRbac, useTenxyteClient, useTenxyteState, useUser };
@@ -0,0 +1,171 @@
1
+ import * as React from 'react';
2
+ import React__default from 'react';
3
+ import * as _tenxyte_core from '@tenxyte/core';
4
+ import { TenxyteClient, TenxyteClientState, DecodedTenxyteToken } from '@tenxyte/core';
5
+
6
+ /**
7
+ * React context holding the TenxyteClient instance.
8
+ * Must be provided via <TenxyteProvider>.
9
+ */
10
+ declare const TenxyteContext: React.Context<TenxyteClient | null>;
11
+ /**
12
+ * Internal hook to access the TenxyteClient instance.
13
+ * Throws if used outside of a <TenxyteProvider>.
14
+ */
15
+ declare function useTenxyteClient(): TenxyteClient;
16
+
17
+ interface TenxyteProviderProps {
18
+ /** The initialized TenxyteClient instance. */
19
+ client: TenxyteClient;
20
+ children: React__default.ReactNode;
21
+ }
22
+ /**
23
+ * Provides the TenxyteClient to all descendant components and manages
24
+ * reactive state synchronization via SDK events.
25
+ *
26
+ * @example
27
+ * ```tsx
28
+ * import { TenxyteClient } from '@tenxyte/core';
29
+ * import { TenxyteProvider } from '@tenxyte/react';
30
+ *
31
+ * const tx = new TenxyteClient({ baseUrl: '...' });
32
+ *
33
+ * function App() {
34
+ * return (
35
+ * <TenxyteProvider client={tx}>
36
+ * <MyApp />
37
+ * </TenxyteProvider>
38
+ * );
39
+ * }
40
+ * ```
41
+ */
42
+ declare function TenxyteProvider({ client, children }: TenxyteProviderProps): React__default.JSX.Element;
43
+ /**
44
+ * Internal hook that subscribes to SDK events and returns a reactive state snapshot.
45
+ * Re-renders consuming components whenever auth state changes.
46
+ */
47
+ declare function useTenxyteState(): TenxyteClientState & {
48
+ loading: boolean;
49
+ };
50
+
51
+ interface UseAuthReturn {
52
+ /** Whether the user has a valid, non-expired access token. */
53
+ isAuthenticated: boolean;
54
+ /** Whether the initial state is still loading from storage. */
55
+ loading: boolean;
56
+ /** Raw access token string, or null. */
57
+ accessToken: string | null;
58
+ /** Login with email/password. Triggers re-render on success. */
59
+ loginWithEmail: (data: {
60
+ email: string;
61
+ password: string;
62
+ device_info?: string;
63
+ totp_code?: string;
64
+ }) => Promise<void>;
65
+ /** Login with phone/password. */
66
+ loginWithPhone: (data: {
67
+ phone_country_code: string;
68
+ phone_number: string;
69
+ password: string;
70
+ device_info?: string;
71
+ }) => Promise<void>;
72
+ /** Logout from all sessions. */
73
+ logout: () => Promise<void>;
74
+ /** Register a new account. */
75
+ register: (data: Record<string, unknown>) => Promise<void>;
76
+ }
77
+ /**
78
+ * Reactive authentication hook.
79
+ * Automatically re-renders when the auth state changes (login, logout, refresh, expiry).
80
+ *
81
+ * @example
82
+ * ```tsx
83
+ * function LoginPage() {
84
+ * const { isAuthenticated, loginWithEmail, logout, loading } = useAuth();
85
+ *
86
+ * if (loading) return <p>Loading...</p>;
87
+ * if (isAuthenticated) return <button onClick={logout}>Logout</button>;
88
+ *
89
+ * return <button onClick={() => loginWithEmail({ email: '...', password: '...' })}>Login</button>;
90
+ * }
91
+ * ```
92
+ */
93
+ declare function useAuth(): UseAuthReturn;
94
+ interface UseUserReturn {
95
+ /** Decoded JWT payload of the current access token, or null. */
96
+ user: DecodedTenxyteToken | null;
97
+ /** Whether the initial state is still loading. */
98
+ loading: boolean;
99
+ /** Fetch the full user profile from the backend. */
100
+ getProfile: () => ReturnType<typeof _tenxyte_core.TenxyteClient.prototype.user.getProfile>;
101
+ /** Update the current user's profile. */
102
+ updateProfile: (data: Record<string, unknown>) => Promise<unknown>;
103
+ }
104
+ /**
105
+ * Reactive user hook.
106
+ * Returns the decoded JWT user and convenience methods for profile management.
107
+ *
108
+ * @example
109
+ * ```tsx
110
+ * function UserBadge() {
111
+ * const { user, loading } = useUser();
112
+ * if (loading || !user) return null;
113
+ * return <span>{user.email}</span>;
114
+ * }
115
+ * ```
116
+ */
117
+ declare function useUser(): UseUserReturn;
118
+ interface UseOrganizationReturn {
119
+ /** Currently active organization slug, or null. */
120
+ activeOrg: string | null;
121
+ /** Switch the SDK to operate within an organization context. */
122
+ switchOrganization: (slug: string) => void;
123
+ /** Clear the organization context. */
124
+ clearOrganization: () => void;
125
+ }
126
+ /**
127
+ * Reactive organization context hook.
128
+ * Returns the current org slug and methods to switch/clear context.
129
+ *
130
+ * @example
131
+ * ```tsx
132
+ * function OrgSwitcher({ orgs }) {
133
+ * const { activeOrg, switchOrganization, clearOrganization } = useOrganization();
134
+ * return (
135
+ * <select value={activeOrg ?? ''} onChange={(e) =>
136
+ * e.target.value ? switchOrganization(e.target.value) : clearOrganization()
137
+ * }>
138
+ * <option value="">No org</option>
139
+ * {orgs.map(o => <option key={o.slug} value={o.slug}>{o.name}</option>)}
140
+ * </select>
141
+ * );
142
+ * }
143
+ * ```
144
+ */
145
+ declare function useOrganization(): UseOrganizationReturn;
146
+ interface UseRbacReturn {
147
+ /** Check if the current user has a specific role (synchronous JWT check). */
148
+ hasRole: (role: string) => boolean;
149
+ /** Check if the current user has a specific permission (synchronous JWT check). */
150
+ hasPermission: (permission: string) => boolean;
151
+ /** Check if the current user has any of the given roles. */
152
+ hasAnyRole: (roles: string[]) => boolean;
153
+ /** Check if the current user has all of the given roles. */
154
+ hasAllRoles: (roles: string[]) => boolean;
155
+ }
156
+ /**
157
+ * Reactive RBAC hook.
158
+ * Provides synchronous role/permission checks based on the current JWT.
159
+ *
160
+ * @example
161
+ * ```tsx
162
+ * function AdminPanel() {
163
+ * const { hasRole } = useRbac();
164
+ * if (!hasRole('admin')) return <p>Access denied</p>;
165
+ * return <AdminDashboard />;
166
+ * }
167
+ * ```
168
+ */
169
+ declare function useRbac(): UseRbacReturn;
170
+
171
+ export { TenxyteContext, TenxyteProvider, type TenxyteProviderProps, type UseAuthReturn, type UseOrganizationReturn, type UseRbacReturn, type UseUserReturn, useAuth, useOrganization, useRbac, useTenxyteClient, useTenxyteState, useUser };
package/dist/index.js ADDED
@@ -0,0 +1,144 @@
1
+ // src/context.tsx
2
+ import { createContext, useContext } from "react";
3
+ var TenxyteContext = createContext(null);
4
+ function useTenxyteClient() {
5
+ const client = useContext(TenxyteContext);
6
+ if (!client) {
7
+ throw new Error(
8
+ "[@tenxyte/react] useTenxyteClient must be used within a <TenxyteProvider>. Wrap your app with <TenxyteProvider client={tx}>."
9
+ );
10
+ }
11
+ return client;
12
+ }
13
+
14
+ // src/provider.tsx
15
+ import React, { useCallback, useEffect, useMemo, useState } from "react";
16
+ import { jsx } from "react/jsx-runtime";
17
+ function TenxyteProvider({ client, children }) {
18
+ return /* @__PURE__ */ jsx(TenxyteContext.Provider, { value: client, children });
19
+ }
20
+ function useTenxyteState() {
21
+ const client = React.useContext(TenxyteContext);
22
+ if (!client) {
23
+ throw new Error("[@tenxyte/react] useTenxyteState must be used within a <TenxyteProvider>.");
24
+ }
25
+ const [state, setState] = useState({
26
+ isAuthenticated: false,
27
+ user: null,
28
+ accessToken: null,
29
+ activeOrg: null,
30
+ isAgentMode: false
31
+ });
32
+ const [loading, setLoading] = useState(true);
33
+ const refresh = useCallback(async () => {
34
+ const snapshot = await client.getState();
35
+ setState(snapshot);
36
+ setLoading(false);
37
+ }, [client]);
38
+ useEffect(() => {
39
+ refresh();
40
+ const unsubs = [
41
+ client.on("token:stored", () => {
42
+ refresh();
43
+ }),
44
+ client.on("token:refreshed", () => {
45
+ refresh();
46
+ }),
47
+ client.on("session:expired", () => {
48
+ refresh();
49
+ })
50
+ ];
51
+ return () => {
52
+ unsubs.forEach((unsub) => unsub());
53
+ };
54
+ }, [client, refresh]);
55
+ return useMemo(() => ({ ...state, loading }), [state, loading]);
56
+ }
57
+
58
+ // src/hooks.ts
59
+ import { useCallback as useCallback2 } from "react";
60
+ function useAuth() {
61
+ const client = useTenxyteClient();
62
+ const { isAuthenticated, loading, accessToken } = useTenxyteState();
63
+ const loginWithEmail = useCallback2(
64
+ async (data) => {
65
+ await client.auth.loginWithEmail({ ...data, device_info: data.device_info ?? "" });
66
+ },
67
+ [client]
68
+ );
69
+ const loginWithPhone = useCallback2(
70
+ async (data) => {
71
+ await client.auth.loginWithPhone({ ...data, device_info: data.device_info ?? "" });
72
+ },
73
+ [client]
74
+ );
75
+ const logout = useCallback2(async () => {
76
+ await client.auth.logoutAll();
77
+ }, [client]);
78
+ const register = useCallback2(
79
+ async (data) => {
80
+ await client.auth.register(data);
81
+ },
82
+ [client]
83
+ );
84
+ return { isAuthenticated, loading, accessToken, loginWithEmail, loginWithPhone, logout, register };
85
+ }
86
+ function useUser() {
87
+ const client = useTenxyteClient();
88
+ const { user, loading } = useTenxyteState();
89
+ const getProfile = useCallback2(() => client.user.getProfile(), [client]);
90
+ const updateProfile = useCallback2(
91
+ (data) => client.user.updateProfile(data),
92
+ [client]
93
+ );
94
+ return { user, loading, getProfile, updateProfile };
95
+ }
96
+ function useOrganization() {
97
+ const client = useTenxyteClient();
98
+ const { activeOrg } = useTenxyteState();
99
+ const switchOrganization = useCallback2(
100
+ (slug) => {
101
+ client.b2b.switchOrganization(slug);
102
+ },
103
+ [client]
104
+ );
105
+ const clearOrganization = useCallback2(
106
+ () => {
107
+ client.b2b.clearOrganization();
108
+ },
109
+ [client]
110
+ );
111
+ return { activeOrg, switchOrganization, clearOrganization };
112
+ }
113
+ function useRbac() {
114
+ const client = useTenxyteClient();
115
+ const { accessToken } = useTenxyteState();
116
+ const hasRole = useCallback2(
117
+ (role) => client.rbac.hasRole(role, accessToken ?? void 0),
118
+ [client, accessToken]
119
+ );
120
+ const hasPermission = useCallback2(
121
+ (permission) => client.rbac.hasPermission(permission, accessToken ?? void 0),
122
+ [client, accessToken]
123
+ );
124
+ const hasAnyRole = useCallback2(
125
+ (roles) => client.rbac.hasAnyRole(roles, accessToken ?? void 0),
126
+ [client, accessToken]
127
+ );
128
+ const hasAllRoles = useCallback2(
129
+ (roles) => client.rbac.hasAllRoles(roles, accessToken ?? void 0),
130
+ [client, accessToken]
131
+ );
132
+ return { hasRole, hasPermission, hasAnyRole, hasAllRoles };
133
+ }
134
+ export {
135
+ TenxyteContext,
136
+ TenxyteProvider,
137
+ useAuth,
138
+ useOrganization,
139
+ useRbac,
140
+ useTenxyteClient,
141
+ useTenxyteState,
142
+ useUser
143
+ };
144
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/context.tsx","../src/provider.tsx","../src/hooks.ts"],"sourcesContent":["import { createContext, useContext } from 'react';\nimport type { TenxyteClient } from '@tenxyte/core';\n\n/**\n * React context holding the TenxyteClient instance.\n * Must be provided via <TenxyteProvider>.\n */\nexport const TenxyteContext = createContext<TenxyteClient | null>(null);\n\n/**\n * Internal hook to access the TenxyteClient instance.\n * Throws if used outside of a <TenxyteProvider>.\n */\nexport function useTenxyteClient(): TenxyteClient {\n const client = useContext(TenxyteContext);\n if (!client) {\n throw new Error(\n '[@tenxyte/react] useTenxyteClient must be used within a <TenxyteProvider>. ' +\n 'Wrap your app with <TenxyteProvider client={tx}>.',\n );\n }\n return client;\n}\n","import React, { useCallback, useEffect, useMemo, useState } from 'react';\nimport type { TenxyteClient, TenxyteClientState } from '@tenxyte/core';\nimport { TenxyteContext } from './context';\n\nexport interface TenxyteProviderProps {\n /** The initialized TenxyteClient instance. */\n client: TenxyteClient;\n children: React.ReactNode;\n}\n\n/**\n * Provides the TenxyteClient to all descendant components and manages\n * reactive state synchronization via SDK events.\n *\n * @example\n * ```tsx\n * import { TenxyteClient } from '@tenxyte/core';\n * import { TenxyteProvider } from '@tenxyte/react';\n *\n * const tx = new TenxyteClient({ baseUrl: '...' });\n *\n * function App() {\n * return (\n * <TenxyteProvider client={tx}>\n * <MyApp />\n * </TenxyteProvider>\n * );\n * }\n * ```\n */\nexport function TenxyteProvider({ client, children }: TenxyteProviderProps): React.JSX.Element {\n return (\n <TenxyteContext.Provider value={client}>\n {children}\n </TenxyteContext.Provider>\n );\n}\n\n/**\n * Internal hook that subscribes to SDK events and returns a reactive state snapshot.\n * Re-renders consuming components whenever auth state changes.\n */\nexport function useTenxyteState(): TenxyteClientState & { loading: boolean } {\n const client = React.useContext(TenxyteContext);\n if (!client) {\n throw new Error('[@tenxyte/react] useTenxyteState must be used within a <TenxyteProvider>.');\n }\n\n const [state, setState] = useState<TenxyteClientState>({\n isAuthenticated: false,\n user: null,\n accessToken: null,\n activeOrg: null,\n isAgentMode: false,\n });\n const [loading, setLoading] = useState(true);\n\n const refresh = useCallback(async () => {\n const snapshot = await client.getState();\n setState(snapshot);\n setLoading(false);\n }, [client]);\n\n useEffect(() => {\n // Initial state load\n refresh();\n\n // Subscribe to all state-changing events\n const unsubs = [\n client.on('token:stored', () => { refresh(); }),\n client.on('token:refreshed', () => { refresh(); }),\n client.on('session:expired', () => { refresh(); }),\n ];\n\n return () => {\n unsubs.forEach((unsub) => unsub());\n };\n }, [client, refresh]);\n\n return useMemo(() => ({ ...state, loading }), [state, loading]);\n}\n","import { useCallback } from 'react';\nimport type { DecodedTenxyteToken } from '@tenxyte/core';\nimport { useTenxyteClient } from './context';\nimport { useTenxyteState } from './provider';\n\n// ─── useAuth ───\n\nexport interface UseAuthReturn {\n /** Whether the user has a valid, non-expired access token. */\n isAuthenticated: boolean;\n /** Whether the initial state is still loading from storage. */\n loading: boolean;\n /** Raw access token string, or null. */\n accessToken: string | null;\n /** Login with email/password. Triggers re-render on success. */\n loginWithEmail: (data: { email: string; password: string; device_info?: string; totp_code?: string }) => Promise<void>;\n /** Login with phone/password. */\n loginWithPhone: (data: { phone_country_code: string; phone_number: string; password: string; device_info?: string }) => Promise<void>;\n /** Logout from all sessions. */\n logout: () => Promise<void>;\n /** Register a new account. */\n register: (data: Record<string, unknown>) => Promise<void>;\n}\n\n/**\n * Reactive authentication hook.\n * Automatically re-renders when the auth state changes (login, logout, refresh, expiry).\n *\n * @example\n * ```tsx\n * function LoginPage() {\n * const { isAuthenticated, loginWithEmail, logout, loading } = useAuth();\n *\n * if (loading) return <p>Loading...</p>;\n * if (isAuthenticated) return <button onClick={logout}>Logout</button>;\n *\n * return <button onClick={() => loginWithEmail({ email: '...', password: '...' })}>Login</button>;\n * }\n * ```\n */\nexport function useAuth(): UseAuthReturn {\n const client = useTenxyteClient();\n const { isAuthenticated, loading, accessToken } = useTenxyteState();\n\n const loginWithEmail = useCallback(\n async (data: { email: string; password: string; device_info?: string; totp_code?: string }) => {\n await client.auth.loginWithEmail({ ...data, device_info: data.device_info ?? '' });\n },\n [client],\n );\n\n const loginWithPhone = useCallback(\n async (data: { phone_country_code: string; phone_number: string; password: string; device_info?: string }) => {\n await client.auth.loginWithPhone({ ...data, device_info: data.device_info ?? '' });\n },\n [client],\n );\n\n const logout = useCallback(async () => {\n await client.auth.logoutAll();\n }, [client]);\n\n const register = useCallback(\n async (data: Record<string, unknown>) => {\n await client.auth.register(data as any);\n },\n [client],\n );\n\n return { isAuthenticated, loading, accessToken, loginWithEmail, loginWithPhone, logout, register };\n}\n\n// ─── useUser ───\n\nexport interface UseUserReturn {\n /** Decoded JWT payload of the current access token, or null. */\n user: DecodedTenxyteToken | null;\n /** Whether the initial state is still loading. */\n loading: boolean;\n /** Fetch the full user profile from the backend. */\n getProfile: () => ReturnType<typeof import('@tenxyte/core').TenxyteClient.prototype.user.getProfile>;\n /** Update the current user's profile. */\n updateProfile: (data: Record<string, unknown>) => Promise<unknown>;\n}\n\n/**\n * Reactive user hook.\n * Returns the decoded JWT user and convenience methods for profile management.\n *\n * @example\n * ```tsx\n * function UserBadge() {\n * const { user, loading } = useUser();\n * if (loading || !user) return null;\n * return <span>{user.email}</span>;\n * }\n * ```\n */\nexport function useUser(): UseUserReturn {\n const client = useTenxyteClient();\n const { user, loading } = useTenxyteState();\n\n const getProfile = useCallback(() => client.user.getProfile(), [client]);\n\n const updateProfile = useCallback(\n (data: Record<string, unknown>) => client.user.updateProfile(data),\n [client],\n );\n\n return { user, loading, getProfile, updateProfile };\n}\n\n// ─── useOrganization ───\n\nexport interface UseOrganizationReturn {\n /** Currently active organization slug, or null. */\n activeOrg: string | null;\n /** Switch the SDK to operate within an organization context. */\n switchOrganization: (slug: string) => void;\n /** Clear the organization context. */\n clearOrganization: () => void;\n}\n\n/**\n * Reactive organization context hook.\n * Returns the current org slug and methods to switch/clear context.\n *\n * @example\n * ```tsx\n * function OrgSwitcher({ orgs }) {\n * const { activeOrg, switchOrganization, clearOrganization } = useOrganization();\n * return (\n * <select value={activeOrg ?? ''} onChange={(e) =>\n * e.target.value ? switchOrganization(e.target.value) : clearOrganization()\n * }>\n * <option value=\"\">No org</option>\n * {orgs.map(o => <option key={o.slug} value={o.slug}>{o.name}</option>)}\n * </select>\n * );\n * }\n * ```\n */\nexport function useOrganization(): UseOrganizationReturn {\n const client = useTenxyteClient();\n const { activeOrg } = useTenxyteState();\n\n const switchOrganization = useCallback(\n (slug: string) => { client.b2b.switchOrganization(slug); },\n [client],\n );\n\n const clearOrganization = useCallback(\n () => { client.b2b.clearOrganization(); },\n [client],\n );\n\n return { activeOrg, switchOrganization, clearOrganization };\n}\n\n// ─── useRbac ───\n\nexport interface UseRbacReturn {\n /** Check if the current user has a specific role (synchronous JWT check). */\n hasRole: (role: string) => boolean;\n /** Check if the current user has a specific permission (synchronous JWT check). */\n hasPermission: (permission: string) => boolean;\n /** Check if the current user has any of the given roles. */\n hasAnyRole: (roles: string[]) => boolean;\n /** Check if the current user has all of the given roles. */\n hasAllRoles: (roles: string[]) => boolean;\n}\n\n/**\n * Reactive RBAC hook.\n * Provides synchronous role/permission checks based on the current JWT.\n *\n * @example\n * ```tsx\n * function AdminPanel() {\n * const { hasRole } = useRbac();\n * if (!hasRole('admin')) return <p>Access denied</p>;\n * return <AdminDashboard />;\n * }\n * ```\n */\nexport function useRbac(): UseRbacReturn {\n const client = useTenxyteClient();\n const { accessToken } = useTenxyteState();\n\n const hasRole = useCallback(\n (role: string) => client.rbac.hasRole(role, accessToken ?? undefined),\n [client, accessToken],\n );\n\n const hasPermission = useCallback(\n (permission: string) => client.rbac.hasPermission(permission, accessToken ?? undefined),\n [client, accessToken],\n );\n\n const hasAnyRole = useCallback(\n (roles: string[]) => client.rbac.hasAnyRole(roles, accessToken ?? undefined),\n [client, accessToken],\n );\n\n const hasAllRoles = useCallback(\n (roles: string[]) => client.rbac.hasAllRoles(roles, accessToken ?? undefined),\n [client, accessToken],\n );\n\n return { hasRole, hasPermission, hasAnyRole, hasAllRoles };\n}\n"],"mappings":"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","names":["useCallback","useCallback"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenxyte/react",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "React bindings for the Tenxyte SDK",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",
@@ -22,7 +22,8 @@
22
22
  ],
23
23
  "sideEffects": false,
24
24
  "publishConfig": {
25
- "access": "public"
25
+ "access": "public",
26
+ "provenance": true
26
27
  },
27
28
  "scripts": {
28
29
  "build": "tsup",