@habeetat/sdk-react 0.1.0-dev.20260323155801.0ae6298
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 +73 -0
- package/dist/index.d.mts +217 -0
- package/dist/index.d.ts +217 -0
- package/dist/index.js +269 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +260 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# @habeetat/sdk-react
|
|
2
|
+
|
|
3
|
+
Habeetat Platform SDK for React applications.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @habeetat/sdk-react @logto/react
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { LogtoProvider } from '@logto/react';
|
|
15
|
+
import { HabeetatProvider, useHabeetat, usePermissions, useFeatures } from '@habeetat/sdk-react';
|
|
16
|
+
|
|
17
|
+
// Wrap your app with providers
|
|
18
|
+
function App() {
|
|
19
|
+
return (
|
|
20
|
+
<LogtoProvider config={logtoConfig}>
|
|
21
|
+
<HabeetatProvider
|
|
22
|
+
platformUrl="https://api.habeetat.io"
|
|
23
|
+
tenantSlug="acme-corp"
|
|
24
|
+
>
|
|
25
|
+
<MyApp />
|
|
26
|
+
</HabeetatProvider>
|
|
27
|
+
</LogtoProvider>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Use hooks in components
|
|
32
|
+
function Dashboard() {
|
|
33
|
+
const { context, isLoading } = useHabeetat();
|
|
34
|
+
const { hasPermission } = usePermissions();
|
|
35
|
+
const { isEnabled } = useFeatures();
|
|
36
|
+
|
|
37
|
+
if (isLoading) return <Spinner />;
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div>
|
|
41
|
+
<h1>Welcome, {context?.user.name}</h1>
|
|
42
|
+
{hasPermission('contacts:read') && <ContactsList />}
|
|
43
|
+
{isEnabled('crm.deals.enabled') && <DealsWidget />}
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Hooks
|
|
50
|
+
|
|
51
|
+
### useHabeetat
|
|
52
|
+
Main hook for accessing SDK context.
|
|
53
|
+
|
|
54
|
+
### usePermissions
|
|
55
|
+
Hook for permission checks.
|
|
56
|
+
|
|
57
|
+
### useFeatures
|
|
58
|
+
Hook for feature flag checks.
|
|
59
|
+
|
|
60
|
+
### useSubscription
|
|
61
|
+
Hook for subscription and plan info.
|
|
62
|
+
|
|
63
|
+
## Components
|
|
64
|
+
|
|
65
|
+
### RequirePermission
|
|
66
|
+
Renders children only if user has permission.
|
|
67
|
+
|
|
68
|
+
### RequireFeature
|
|
69
|
+
Renders children only if feature is enabled.
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import { ReactNode } from 'react';
|
|
4
|
+
import * as _habeetat_sdk_core from '@habeetat/sdk-core';
|
|
5
|
+
import { SdkContext, SdkFeaturesState, SdkSubscription } from '@habeetat/sdk-core';
|
|
6
|
+
export { SdkContext, SdkFeaturesState, SdkPlan, SdkSubscription, SdkTenantContext, SdkUserContext } from '@habeetat/sdk-core';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Props for HabeetatProvider
|
|
10
|
+
*/
|
|
11
|
+
interface HabeetatProviderProps {
|
|
12
|
+
/** Platform API base URL (SDK endpoints) */
|
|
13
|
+
platformUrl: string;
|
|
14
|
+
/** Logto API resource identifier (for token requests) */
|
|
15
|
+
logtoResource?: string;
|
|
16
|
+
/** App ID for this application */
|
|
17
|
+
appId?: string;
|
|
18
|
+
/** Tenant slug (if known) */
|
|
19
|
+
tenantSlug?: string;
|
|
20
|
+
/** Whether to auto-fetch context on mount */
|
|
21
|
+
autoFetch?: boolean;
|
|
22
|
+
/** Children components */
|
|
23
|
+
children: ReactNode;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* HabeetatProvider - Main provider for Habeetat SDK in React apps
|
|
27
|
+
*
|
|
28
|
+
* Must be used inside LogtoProvider from @logto/react
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```tsx
|
|
32
|
+
* import { LogtoProvider } from '@logto/react';
|
|
33
|
+
* import { HabeetatProvider } from '@habeetat/sdk-react';
|
|
34
|
+
*
|
|
35
|
+
* function App() {
|
|
36
|
+
* return (
|
|
37
|
+
* <LogtoProvider config={logtoConfig}>
|
|
38
|
+
* <HabeetatProvider
|
|
39
|
+
* platformUrl="https://api.habeetat.io"
|
|
40
|
+
* tenantSlug="acme-corp"
|
|
41
|
+
* >
|
|
42
|
+
* <MyApp />
|
|
43
|
+
* </HabeetatProvider>
|
|
44
|
+
* </LogtoProvider>
|
|
45
|
+
* );
|
|
46
|
+
* }
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
declare function HabeetatProvider({ platformUrl, logtoResource, appId, tenantSlug, autoFetch, children, }: HabeetatProviderProps): react_jsx_runtime.JSX.Element;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Habeetat SDK state
|
|
53
|
+
*/
|
|
54
|
+
interface HabeetatState {
|
|
55
|
+
/** Whether the SDK is loading initial data */
|
|
56
|
+
isLoading: boolean;
|
|
57
|
+
/** Error if any occurred */
|
|
58
|
+
error: Error | null;
|
|
59
|
+
/** SDK context (user, tenant, permissions) */
|
|
60
|
+
context: SdkContext | null;
|
|
61
|
+
/** Feature flags */
|
|
62
|
+
features: SdkFeaturesState | null;
|
|
63
|
+
/** Subscription info */
|
|
64
|
+
subscription: SdkSubscription | null;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Habeetat SDK context value
|
|
68
|
+
*/
|
|
69
|
+
interface HabeetatContextValue extends HabeetatState {
|
|
70
|
+
/** Refresh context from server */
|
|
71
|
+
refreshContext: () => Promise<void>;
|
|
72
|
+
/** Refresh features from server */
|
|
73
|
+
refreshFeatures: () => Promise<void>;
|
|
74
|
+
/** Refresh subscription from server */
|
|
75
|
+
refreshSubscription: () => Promise<void>;
|
|
76
|
+
/** Check if user has permission */
|
|
77
|
+
hasPermission: (permission: string) => boolean;
|
|
78
|
+
/** Check if user has any of the permissions */
|
|
79
|
+
hasAnyPermission: (permissions: string[]) => boolean;
|
|
80
|
+
/** Check if user has all permissions */
|
|
81
|
+
hasAllPermissions: (permissions: string[]) => boolean;
|
|
82
|
+
/** Check if feature is enabled */
|
|
83
|
+
isFeatureEnabled: (key: string) => boolean;
|
|
84
|
+
/** Get access token for API calls */
|
|
85
|
+
getAccessToken: () => Promise<string | null>;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Habeetat React Context
|
|
89
|
+
*/
|
|
90
|
+
declare const HabeetatContext: react.Context<HabeetatContextValue>;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Hook to access Habeetat SDK context
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```tsx
|
|
97
|
+
* function MyComponent() {
|
|
98
|
+
* const { context, isLoading, error } = useHabeetat();
|
|
99
|
+
*
|
|
100
|
+
* if (isLoading) return <Spinner />;
|
|
101
|
+
* if (error) return <Error message={error.message} />;
|
|
102
|
+
*
|
|
103
|
+
* return <div>Hello, {context?.user.name}</div>;
|
|
104
|
+
* }
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
declare function useHabeetat(): HabeetatContextValue;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Hook for permission checks
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```tsx
|
|
114
|
+
* function ContactsPage() {
|
|
115
|
+
* const { hasPermission, hasAnyPermission } = usePermissions();
|
|
116
|
+
*
|
|
117
|
+
* const canRead = hasPermission('contacts:read');
|
|
118
|
+
* const canWrite = hasPermission('contacts:write');
|
|
119
|
+
* const canManage = hasAnyPermission(['contacts:delete', 'contacts:admin']);
|
|
120
|
+
*
|
|
121
|
+
* return (
|
|
122
|
+
* <div>
|
|
123
|
+
* {canRead && <ContactsList />}
|
|
124
|
+
* {canWrite && <AddContactButton />}
|
|
125
|
+
* {canManage && <ManageContactsButton />}
|
|
126
|
+
* </div>
|
|
127
|
+
* );
|
|
128
|
+
* }
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
declare function usePermissions(): {
|
|
132
|
+
/** All user permissions */
|
|
133
|
+
permissions: string[];
|
|
134
|
+
/** All user roles */
|
|
135
|
+
roles: string[];
|
|
136
|
+
/** Check if user has a specific permission */
|
|
137
|
+
hasPermission: (permission: string) => boolean;
|
|
138
|
+
/** Check if user has any of the specified permissions */
|
|
139
|
+
hasAnyPermission: (permissions: string[]) => boolean;
|
|
140
|
+
/** Check if user has all of the specified permissions */
|
|
141
|
+
hasAllPermissions: (permissions: string[]) => boolean;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Hook for feature flag checks
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```tsx
|
|
149
|
+
* function DealsPage() {
|
|
150
|
+
* const { isEnabled, features } = useFeatures();
|
|
151
|
+
*
|
|
152
|
+
* if (!isEnabled('crm.deals.enabled')) {
|
|
153
|
+
* return <UpgradePrompt feature="Deals" />;
|
|
154
|
+
* }
|
|
155
|
+
*
|
|
156
|
+
* return <DealsList />;
|
|
157
|
+
* }
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
declare function useFeatures(): {
|
|
161
|
+
/** All feature flags */
|
|
162
|
+
features: Record<string, boolean>;
|
|
163
|
+
/** Feature source (plan, tenant, etc.) */
|
|
164
|
+
source: _habeetat_sdk_core.FeatureFlagSource | undefined;
|
|
165
|
+
/** Plan code if source is plan */
|
|
166
|
+
planCode: string | undefined;
|
|
167
|
+
/** Check if a feature is enabled */
|
|
168
|
+
isEnabled: (key: string) => boolean;
|
|
169
|
+
/** Refresh features from server */
|
|
170
|
+
refresh: () => Promise<void>;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Hook for subscription and plan info
|
|
175
|
+
*/
|
|
176
|
+
declare function useSubscription(): {
|
|
177
|
+
/** Current subscription */
|
|
178
|
+
subscription: _habeetat_sdk_core.SdkSubscription | null;
|
|
179
|
+
/** Current plan */
|
|
180
|
+
plan: _habeetat_sdk_core.SdkPlan | undefined;
|
|
181
|
+
/** Plan limits */
|
|
182
|
+
limits: _habeetat_sdk_core.SdkPlanLimits;
|
|
183
|
+
/** Current usage */
|
|
184
|
+
usage: _habeetat_sdk_core.SdkPlanUsage;
|
|
185
|
+
/** Subscription status */
|
|
186
|
+
status: _habeetat_sdk_core.SubscriptionStatus | undefined;
|
|
187
|
+
/** Check if subscription is active */
|
|
188
|
+
isActive: boolean;
|
|
189
|
+
/** Check if in trial */
|
|
190
|
+
isTrialing: boolean;
|
|
191
|
+
/** Check limit */
|
|
192
|
+
checkLimit: (key: string, increment?: number) => boolean;
|
|
193
|
+
/** Refresh subscription */
|
|
194
|
+
refresh: () => Promise<void>;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
interface RequirePermissionProps {
|
|
198
|
+
permission: string;
|
|
199
|
+
children: ReactNode;
|
|
200
|
+
fallback?: ReactNode;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Component that renders children only if user has the required permission
|
|
204
|
+
*/
|
|
205
|
+
declare function RequirePermission({ permission, children, fallback }: RequirePermissionProps): react_jsx_runtime.JSX.Element;
|
|
206
|
+
|
|
207
|
+
interface RequireFeatureProps {
|
|
208
|
+
flag: string;
|
|
209
|
+
children: ReactNode;
|
|
210
|
+
fallback?: ReactNode;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Component that renders children only if the feature flag is enabled
|
|
214
|
+
*/
|
|
215
|
+
declare function RequireFeature({ flag, children, fallback }: RequireFeatureProps): react_jsx_runtime.JSX.Element;
|
|
216
|
+
|
|
217
|
+
export { HabeetatContext, type HabeetatContextValue, HabeetatProvider, type HabeetatProviderProps, type HabeetatState, RequireFeature, RequirePermission, useFeatures, useHabeetat, usePermissions, useSubscription };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import { ReactNode } from 'react';
|
|
4
|
+
import * as _habeetat_sdk_core from '@habeetat/sdk-core';
|
|
5
|
+
import { SdkContext, SdkFeaturesState, SdkSubscription } from '@habeetat/sdk-core';
|
|
6
|
+
export { SdkContext, SdkFeaturesState, SdkPlan, SdkSubscription, SdkTenantContext, SdkUserContext } from '@habeetat/sdk-core';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Props for HabeetatProvider
|
|
10
|
+
*/
|
|
11
|
+
interface HabeetatProviderProps {
|
|
12
|
+
/** Platform API base URL (SDK endpoints) */
|
|
13
|
+
platformUrl: string;
|
|
14
|
+
/** Logto API resource identifier (for token requests) */
|
|
15
|
+
logtoResource?: string;
|
|
16
|
+
/** App ID for this application */
|
|
17
|
+
appId?: string;
|
|
18
|
+
/** Tenant slug (if known) */
|
|
19
|
+
tenantSlug?: string;
|
|
20
|
+
/** Whether to auto-fetch context on mount */
|
|
21
|
+
autoFetch?: boolean;
|
|
22
|
+
/** Children components */
|
|
23
|
+
children: ReactNode;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* HabeetatProvider - Main provider for Habeetat SDK in React apps
|
|
27
|
+
*
|
|
28
|
+
* Must be used inside LogtoProvider from @logto/react
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```tsx
|
|
32
|
+
* import { LogtoProvider } from '@logto/react';
|
|
33
|
+
* import { HabeetatProvider } from '@habeetat/sdk-react';
|
|
34
|
+
*
|
|
35
|
+
* function App() {
|
|
36
|
+
* return (
|
|
37
|
+
* <LogtoProvider config={logtoConfig}>
|
|
38
|
+
* <HabeetatProvider
|
|
39
|
+
* platformUrl="https://api.habeetat.io"
|
|
40
|
+
* tenantSlug="acme-corp"
|
|
41
|
+
* >
|
|
42
|
+
* <MyApp />
|
|
43
|
+
* </HabeetatProvider>
|
|
44
|
+
* </LogtoProvider>
|
|
45
|
+
* );
|
|
46
|
+
* }
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
declare function HabeetatProvider({ platformUrl, logtoResource, appId, tenantSlug, autoFetch, children, }: HabeetatProviderProps): react_jsx_runtime.JSX.Element;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Habeetat SDK state
|
|
53
|
+
*/
|
|
54
|
+
interface HabeetatState {
|
|
55
|
+
/** Whether the SDK is loading initial data */
|
|
56
|
+
isLoading: boolean;
|
|
57
|
+
/** Error if any occurred */
|
|
58
|
+
error: Error | null;
|
|
59
|
+
/** SDK context (user, tenant, permissions) */
|
|
60
|
+
context: SdkContext | null;
|
|
61
|
+
/** Feature flags */
|
|
62
|
+
features: SdkFeaturesState | null;
|
|
63
|
+
/** Subscription info */
|
|
64
|
+
subscription: SdkSubscription | null;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Habeetat SDK context value
|
|
68
|
+
*/
|
|
69
|
+
interface HabeetatContextValue extends HabeetatState {
|
|
70
|
+
/** Refresh context from server */
|
|
71
|
+
refreshContext: () => Promise<void>;
|
|
72
|
+
/** Refresh features from server */
|
|
73
|
+
refreshFeatures: () => Promise<void>;
|
|
74
|
+
/** Refresh subscription from server */
|
|
75
|
+
refreshSubscription: () => Promise<void>;
|
|
76
|
+
/** Check if user has permission */
|
|
77
|
+
hasPermission: (permission: string) => boolean;
|
|
78
|
+
/** Check if user has any of the permissions */
|
|
79
|
+
hasAnyPermission: (permissions: string[]) => boolean;
|
|
80
|
+
/** Check if user has all permissions */
|
|
81
|
+
hasAllPermissions: (permissions: string[]) => boolean;
|
|
82
|
+
/** Check if feature is enabled */
|
|
83
|
+
isFeatureEnabled: (key: string) => boolean;
|
|
84
|
+
/** Get access token for API calls */
|
|
85
|
+
getAccessToken: () => Promise<string | null>;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Habeetat React Context
|
|
89
|
+
*/
|
|
90
|
+
declare const HabeetatContext: react.Context<HabeetatContextValue>;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Hook to access Habeetat SDK context
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```tsx
|
|
97
|
+
* function MyComponent() {
|
|
98
|
+
* const { context, isLoading, error } = useHabeetat();
|
|
99
|
+
*
|
|
100
|
+
* if (isLoading) return <Spinner />;
|
|
101
|
+
* if (error) return <Error message={error.message} />;
|
|
102
|
+
*
|
|
103
|
+
* return <div>Hello, {context?.user.name}</div>;
|
|
104
|
+
* }
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
declare function useHabeetat(): HabeetatContextValue;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Hook for permission checks
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```tsx
|
|
114
|
+
* function ContactsPage() {
|
|
115
|
+
* const { hasPermission, hasAnyPermission } = usePermissions();
|
|
116
|
+
*
|
|
117
|
+
* const canRead = hasPermission('contacts:read');
|
|
118
|
+
* const canWrite = hasPermission('contacts:write');
|
|
119
|
+
* const canManage = hasAnyPermission(['contacts:delete', 'contacts:admin']);
|
|
120
|
+
*
|
|
121
|
+
* return (
|
|
122
|
+
* <div>
|
|
123
|
+
* {canRead && <ContactsList />}
|
|
124
|
+
* {canWrite && <AddContactButton />}
|
|
125
|
+
* {canManage && <ManageContactsButton />}
|
|
126
|
+
* </div>
|
|
127
|
+
* );
|
|
128
|
+
* }
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
declare function usePermissions(): {
|
|
132
|
+
/** All user permissions */
|
|
133
|
+
permissions: string[];
|
|
134
|
+
/** All user roles */
|
|
135
|
+
roles: string[];
|
|
136
|
+
/** Check if user has a specific permission */
|
|
137
|
+
hasPermission: (permission: string) => boolean;
|
|
138
|
+
/** Check if user has any of the specified permissions */
|
|
139
|
+
hasAnyPermission: (permissions: string[]) => boolean;
|
|
140
|
+
/** Check if user has all of the specified permissions */
|
|
141
|
+
hasAllPermissions: (permissions: string[]) => boolean;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Hook for feature flag checks
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```tsx
|
|
149
|
+
* function DealsPage() {
|
|
150
|
+
* const { isEnabled, features } = useFeatures();
|
|
151
|
+
*
|
|
152
|
+
* if (!isEnabled('crm.deals.enabled')) {
|
|
153
|
+
* return <UpgradePrompt feature="Deals" />;
|
|
154
|
+
* }
|
|
155
|
+
*
|
|
156
|
+
* return <DealsList />;
|
|
157
|
+
* }
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
declare function useFeatures(): {
|
|
161
|
+
/** All feature flags */
|
|
162
|
+
features: Record<string, boolean>;
|
|
163
|
+
/** Feature source (plan, tenant, etc.) */
|
|
164
|
+
source: _habeetat_sdk_core.FeatureFlagSource | undefined;
|
|
165
|
+
/** Plan code if source is plan */
|
|
166
|
+
planCode: string | undefined;
|
|
167
|
+
/** Check if a feature is enabled */
|
|
168
|
+
isEnabled: (key: string) => boolean;
|
|
169
|
+
/** Refresh features from server */
|
|
170
|
+
refresh: () => Promise<void>;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Hook for subscription and plan info
|
|
175
|
+
*/
|
|
176
|
+
declare function useSubscription(): {
|
|
177
|
+
/** Current subscription */
|
|
178
|
+
subscription: _habeetat_sdk_core.SdkSubscription | null;
|
|
179
|
+
/** Current plan */
|
|
180
|
+
plan: _habeetat_sdk_core.SdkPlan | undefined;
|
|
181
|
+
/** Plan limits */
|
|
182
|
+
limits: _habeetat_sdk_core.SdkPlanLimits;
|
|
183
|
+
/** Current usage */
|
|
184
|
+
usage: _habeetat_sdk_core.SdkPlanUsage;
|
|
185
|
+
/** Subscription status */
|
|
186
|
+
status: _habeetat_sdk_core.SubscriptionStatus | undefined;
|
|
187
|
+
/** Check if subscription is active */
|
|
188
|
+
isActive: boolean;
|
|
189
|
+
/** Check if in trial */
|
|
190
|
+
isTrialing: boolean;
|
|
191
|
+
/** Check limit */
|
|
192
|
+
checkLimit: (key: string, increment?: number) => boolean;
|
|
193
|
+
/** Refresh subscription */
|
|
194
|
+
refresh: () => Promise<void>;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
interface RequirePermissionProps {
|
|
198
|
+
permission: string;
|
|
199
|
+
children: ReactNode;
|
|
200
|
+
fallback?: ReactNode;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Component that renders children only if user has the required permission
|
|
204
|
+
*/
|
|
205
|
+
declare function RequirePermission({ permission, children, fallback }: RequirePermissionProps): react_jsx_runtime.JSX.Element;
|
|
206
|
+
|
|
207
|
+
interface RequireFeatureProps {
|
|
208
|
+
flag: string;
|
|
209
|
+
children: ReactNode;
|
|
210
|
+
fallback?: ReactNode;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Component that renders children only if the feature flag is enabled
|
|
214
|
+
*/
|
|
215
|
+
declare function RequireFeature({ flag, children, fallback }: RequireFeatureProps): react_jsx_runtime.JSX.Element;
|
|
216
|
+
|
|
217
|
+
export { HabeetatContext, type HabeetatContextValue, HabeetatProvider, type HabeetatProviderProps, type HabeetatState, RequireFeature, RequirePermission, useFeatures, useHabeetat, usePermissions, useSubscription };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var react$1 = require('@logto/react');
|
|
5
|
+
var sdkCore = require('@habeetat/sdk-core');
|
|
6
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
7
|
+
|
|
8
|
+
// src/provider/HabeetatProvider.tsx
|
|
9
|
+
var defaultContextValue = {
|
|
10
|
+
isLoading: true,
|
|
11
|
+
error: null,
|
|
12
|
+
context: null,
|
|
13
|
+
features: null,
|
|
14
|
+
subscription: null,
|
|
15
|
+
refreshContext: async () => {
|
|
16
|
+
},
|
|
17
|
+
refreshFeatures: async () => {
|
|
18
|
+
},
|
|
19
|
+
refreshSubscription: async () => {
|
|
20
|
+
},
|
|
21
|
+
hasPermission: () => false,
|
|
22
|
+
hasAnyPermission: () => false,
|
|
23
|
+
hasAllPermissions: () => false,
|
|
24
|
+
isFeatureEnabled: () => false,
|
|
25
|
+
getAccessToken: async () => null
|
|
26
|
+
};
|
|
27
|
+
var HabeetatContext = react.createContext(defaultContextValue);
|
|
28
|
+
function HabeetatProvider({
|
|
29
|
+
platformUrl,
|
|
30
|
+
logtoResource,
|
|
31
|
+
appId,
|
|
32
|
+
tenantSlug,
|
|
33
|
+
autoFetch = true,
|
|
34
|
+
children
|
|
35
|
+
}) {
|
|
36
|
+
const { isAuthenticated, getAccessToken } = react$1.useLogto();
|
|
37
|
+
const tokenResource = logtoResource || platformUrl;
|
|
38
|
+
const [state, setState] = react.useState({
|
|
39
|
+
isLoading: true,
|
|
40
|
+
error: null,
|
|
41
|
+
context: null,
|
|
42
|
+
features: null,
|
|
43
|
+
subscription: null
|
|
44
|
+
});
|
|
45
|
+
const apiUrl = react.useMemo(() => platformUrl.replace(/\/$/, ""), [platformUrl]);
|
|
46
|
+
const fetchApi = react.useCallback(async (endpoint) => {
|
|
47
|
+
let token;
|
|
48
|
+
try {
|
|
49
|
+
token = await getAccessToken(tokenResource);
|
|
50
|
+
console.log("[HabeetatSDK] Got token for resource:", tokenResource, token ? "OK" : "EMPTY");
|
|
51
|
+
} catch (err) {
|
|
52
|
+
console.error("[HabeetatSDK] Failed to get token for resource:", tokenResource, err);
|
|
53
|
+
throw new Error(`Failed to get access token: ${err}`);
|
|
54
|
+
}
|
|
55
|
+
if (!token) {
|
|
56
|
+
console.error("[HabeetatSDK] No token returned for resource:", tokenResource);
|
|
57
|
+
throw new Error("No access token available");
|
|
58
|
+
}
|
|
59
|
+
const url = new URL(endpoint, apiUrl);
|
|
60
|
+
if (tenantSlug) {
|
|
61
|
+
url.searchParams.set("tenantSlug", tenantSlug);
|
|
62
|
+
}
|
|
63
|
+
if (appId) {
|
|
64
|
+
url.searchParams.set("appId", appId);
|
|
65
|
+
}
|
|
66
|
+
const response = await fetch(url.toString(), {
|
|
67
|
+
headers: {
|
|
68
|
+
"Authorization": `Bearer ${token}`,
|
|
69
|
+
"Content-Type": "application/json"
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
throw new Error(`API error: ${response.status}`);
|
|
74
|
+
}
|
|
75
|
+
return response.json();
|
|
76
|
+
}, [apiUrl, tokenResource, tenantSlug, appId, getAccessToken]);
|
|
77
|
+
const refreshContext = react.useCallback(async () => {
|
|
78
|
+
try {
|
|
79
|
+
const context = await fetchApi(sdkCore.SDK_ENDPOINTS.CONTEXT);
|
|
80
|
+
setState((prev) => ({ ...prev, context, error: null }));
|
|
81
|
+
} catch (error) {
|
|
82
|
+
setState((prev) => ({ ...prev, error }));
|
|
83
|
+
}
|
|
84
|
+
}, [fetchApi]);
|
|
85
|
+
const refreshFeatures = react.useCallback(async () => {
|
|
86
|
+
try {
|
|
87
|
+
const features = await fetchApi(sdkCore.SDK_ENDPOINTS.FEATURES);
|
|
88
|
+
setState((prev) => ({ ...prev, features, error: null }));
|
|
89
|
+
} catch (error) {
|
|
90
|
+
setState((prev) => ({ ...prev, error }));
|
|
91
|
+
}
|
|
92
|
+
}, [fetchApi]);
|
|
93
|
+
const refreshSubscription = react.useCallback(async () => {
|
|
94
|
+
try {
|
|
95
|
+
const subscription = await fetchApi(sdkCore.SDK_ENDPOINTS.SUBSCRIPTION);
|
|
96
|
+
setState((prev) => ({ ...prev, subscription, error: null }));
|
|
97
|
+
} catch (error) {
|
|
98
|
+
setState((prev) => ({ ...prev, error }));
|
|
99
|
+
}
|
|
100
|
+
}, [fetchApi]);
|
|
101
|
+
const hasPermission = react.useCallback((permission) => {
|
|
102
|
+
return state.context?.permissions?.includes(permission) ?? false;
|
|
103
|
+
}, [state.context?.permissions]);
|
|
104
|
+
const hasAnyPermission = react.useCallback((permissions) => {
|
|
105
|
+
return permissions.some((p) => state.context?.permissions?.includes(p));
|
|
106
|
+
}, [state.context?.permissions]);
|
|
107
|
+
const hasAllPermissions = react.useCallback((permissions) => {
|
|
108
|
+
return permissions.every((p) => state.context?.permissions?.includes(p));
|
|
109
|
+
}, [state.context?.permissions]);
|
|
110
|
+
const isFeatureEnabled = react.useCallback((key) => {
|
|
111
|
+
return state.features?.features?.[key] ?? false;
|
|
112
|
+
}, [state.features?.features]);
|
|
113
|
+
const getToken = react.useCallback(async () => {
|
|
114
|
+
try {
|
|
115
|
+
const token = await getAccessToken(tokenResource);
|
|
116
|
+
return token ?? null;
|
|
117
|
+
} catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}, [getAccessToken, tokenResource]);
|
|
121
|
+
react.useEffect(() => {
|
|
122
|
+
if (!isAuthenticated || !autoFetch) {
|
|
123
|
+
setState((prev) => ({ ...prev, isLoading: false }));
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const fetchAll = async () => {
|
|
127
|
+
setState((prev) => ({ ...prev, isLoading: true }));
|
|
128
|
+
try {
|
|
129
|
+
const [context, features, subscription] = await Promise.all([
|
|
130
|
+
fetchApi(sdkCore.SDK_ENDPOINTS.CONTEXT).catch(() => null),
|
|
131
|
+
fetchApi(sdkCore.SDK_ENDPOINTS.FEATURES).catch(() => null),
|
|
132
|
+
fetchApi(sdkCore.SDK_ENDPOINTS.SUBSCRIPTION).catch(() => null)
|
|
133
|
+
]);
|
|
134
|
+
setState({
|
|
135
|
+
isLoading: false,
|
|
136
|
+
error: null,
|
|
137
|
+
context,
|
|
138
|
+
features,
|
|
139
|
+
subscription
|
|
140
|
+
});
|
|
141
|
+
} catch (error) {
|
|
142
|
+
setState((prev) => ({
|
|
143
|
+
...prev,
|
|
144
|
+
isLoading: false,
|
|
145
|
+
error
|
|
146
|
+
}));
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
fetchAll();
|
|
150
|
+
}, [isAuthenticated, autoFetch, fetchApi]);
|
|
151
|
+
const contextValue = react.useMemo(() => ({
|
|
152
|
+
...state,
|
|
153
|
+
refreshContext,
|
|
154
|
+
refreshFeatures,
|
|
155
|
+
refreshSubscription,
|
|
156
|
+
hasPermission,
|
|
157
|
+
hasAnyPermission,
|
|
158
|
+
hasAllPermissions,
|
|
159
|
+
isFeatureEnabled,
|
|
160
|
+
getAccessToken: getToken
|
|
161
|
+
}), [
|
|
162
|
+
state,
|
|
163
|
+
refreshContext,
|
|
164
|
+
refreshFeatures,
|
|
165
|
+
refreshSubscription,
|
|
166
|
+
hasPermission,
|
|
167
|
+
hasAnyPermission,
|
|
168
|
+
hasAllPermissions,
|
|
169
|
+
isFeatureEnabled,
|
|
170
|
+
getToken
|
|
171
|
+
]);
|
|
172
|
+
return /* @__PURE__ */ jsxRuntime.jsx(HabeetatContext.Provider, { value: contextValue, children });
|
|
173
|
+
}
|
|
174
|
+
function useHabeetat() {
|
|
175
|
+
const context = react.useContext(HabeetatContext);
|
|
176
|
+
if (!context) {
|
|
177
|
+
throw new Error("useHabeetat must be used within a HabeetatProvider");
|
|
178
|
+
}
|
|
179
|
+
return context;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/hooks/usePermissions.ts
|
|
183
|
+
function usePermissions() {
|
|
184
|
+
const { context, hasPermission, hasAnyPermission, hasAllPermissions } = useHabeetat();
|
|
185
|
+
return {
|
|
186
|
+
/** All user permissions */
|
|
187
|
+
permissions: context?.permissions ?? [],
|
|
188
|
+
/** All user roles */
|
|
189
|
+
roles: context?.roles ?? [],
|
|
190
|
+
/** Check if user has a specific permission */
|
|
191
|
+
hasPermission,
|
|
192
|
+
/** Check if user has any of the specified permissions */
|
|
193
|
+
hasAnyPermission,
|
|
194
|
+
/** Check if user has all of the specified permissions */
|
|
195
|
+
hasAllPermissions
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// src/hooks/useFeatures.ts
|
|
200
|
+
function useFeatures() {
|
|
201
|
+
const { features, isFeatureEnabled, refreshFeatures } = useHabeetat();
|
|
202
|
+
return {
|
|
203
|
+
/** All feature flags */
|
|
204
|
+
features: features?.features ?? {},
|
|
205
|
+
/** Feature source (plan, tenant, etc.) */
|
|
206
|
+
source: features?.source,
|
|
207
|
+
/** Plan code if source is plan */
|
|
208
|
+
planCode: features?.planCode,
|
|
209
|
+
/** Check if a feature is enabled */
|
|
210
|
+
isEnabled: isFeatureEnabled,
|
|
211
|
+
/** Refresh features from server */
|
|
212
|
+
refresh: refreshFeatures
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// src/hooks/useSubscription.ts
|
|
217
|
+
function useSubscription() {
|
|
218
|
+
const { subscription, refreshSubscription } = useHabeetat();
|
|
219
|
+
return {
|
|
220
|
+
/** Current subscription */
|
|
221
|
+
subscription,
|
|
222
|
+
/** Current plan */
|
|
223
|
+
plan: subscription?.plan,
|
|
224
|
+
/** Plan limits */
|
|
225
|
+
limits: subscription?.limits ?? {},
|
|
226
|
+
/** Current usage */
|
|
227
|
+
usage: subscription?.usage ?? {},
|
|
228
|
+
/** Subscription status */
|
|
229
|
+
status: subscription?.status,
|
|
230
|
+
/** Check if subscription is active */
|
|
231
|
+
isActive: subscription?.status === "active" || subscription?.status === "trialing",
|
|
232
|
+
/** Check if in trial */
|
|
233
|
+
isTrialing: subscription?.status === "trialing",
|
|
234
|
+
/** Check limit */
|
|
235
|
+
checkLimit: (key, increment = 0) => {
|
|
236
|
+
const limit = subscription?.limits?.[key];
|
|
237
|
+
const current = subscription?.usage?.[key] ?? 0;
|
|
238
|
+
if (limit === void 0) return true;
|
|
239
|
+
return current + increment <= limit;
|
|
240
|
+
},
|
|
241
|
+
/** Refresh subscription */
|
|
242
|
+
refresh: refreshSubscription
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function RequirePermission({ permission, children, fallback = null }) {
|
|
246
|
+
const { hasPermission } = usePermissions();
|
|
247
|
+
if (!hasPermission(permission)) {
|
|
248
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallback });
|
|
249
|
+
}
|
|
250
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
|
|
251
|
+
}
|
|
252
|
+
function RequireFeature({ flag, children, fallback = null }) {
|
|
253
|
+
const { isEnabled } = useFeatures();
|
|
254
|
+
if (!isEnabled(flag)) {
|
|
255
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallback });
|
|
256
|
+
}
|
|
257
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
exports.HabeetatContext = HabeetatContext;
|
|
261
|
+
exports.HabeetatProvider = HabeetatProvider;
|
|
262
|
+
exports.RequireFeature = RequireFeature;
|
|
263
|
+
exports.RequirePermission = RequirePermission;
|
|
264
|
+
exports.useFeatures = useFeatures;
|
|
265
|
+
exports.useHabeetat = useHabeetat;
|
|
266
|
+
exports.usePermissions = usePermissions;
|
|
267
|
+
exports.useSubscription = useSubscription;
|
|
268
|
+
//# sourceMappingURL=index.js.map
|
|
269
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/context/HabeetatContext.ts","../src/provider/HabeetatProvider.tsx","../src/hooks/useHabeetat.ts","../src/hooks/usePermissions.ts","../src/hooks/useFeatures.ts","../src/hooks/useSubscription.ts","../src/components/RequirePermission.tsx","../src/components/RequireFeature.tsx"],"names":["createContext","useLogto","useState","useMemo","useCallback","SDK_ENDPOINTS","useEffect","useContext","jsx","Fragment"],"mappings":";;;;;;;;AA4CA,IAAM,mBAAA,GAA4C;AAAA,EAChD,SAAA,EAAW,IAAA;AAAA,EACX,KAAA,EAAO,IAAA;AAAA,EACP,OAAA,EAAS,IAAA;AAAA,EACT,QAAA,EAAU,IAAA;AAAA,EACV,YAAA,EAAc,IAAA;AAAA,EACd,gBAAgB,YAAY;AAAA,EAAC,CAAA;AAAA,EAC7B,iBAAiB,YAAY;AAAA,EAAC,CAAA;AAAA,EAC9B,qBAAqB,YAAY;AAAA,EAAC,CAAA;AAAA,EAClC,eAAe,MAAM,KAAA;AAAA,EACrB,kBAAkB,MAAM,KAAA;AAAA,EACxB,mBAAmB,MAAM,KAAA;AAAA,EACzB,kBAAkB,MAAM,KAAA;AAAA,EACxB,gBAAgB,YAAY;AAC9B,CAAA;AAKO,IAAM,eAAA,GAAkBA,oBAAoC,mBAAmB;ACf/E,SAAS,gBAAA,CAAiB;AAAA,EAC/B,WAAA;AAAA,EACA,aAAA;AAAA,EACA,KAAA;AAAA,EACA,UAAA;AAAA,EACA,SAAA,GAAY,IAAA;AAAA,EACZ;AACF,CAAA,EAA0B;AACxB,EAAA,MAAM,EAAE,eAAA,EAAiB,cAAA,EAAe,GAAIC,gBAAA,EAAS;AAGrD,EAAA,MAAM,gBAAgB,aAAA,IAAiB,WAAA;AAEvC,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIC,cAAA,CAAwB;AAAA,IAChD,SAAA,EAAW,IAAA;AAAA,IACX,KAAA,EAAO,IAAA;AAAA,IACP,OAAA,EAAS,IAAA;AAAA,IACT,QAAA,EAAU,IAAA;AAAA,IACV,YAAA,EAAc;AAAA,GACf,CAAA;AAGD,EAAA,MAAM,MAAA,GAASC,aAAA,CAAQ,MAAM,WAAA,CAAY,OAAA,CAAQ,OAAO,EAAE,CAAA,EAAG,CAAC,WAAW,CAAC,CAAA;AAG1E,EAAA,MAAM,QAAA,GAAWC,iBAAA,CAAY,OAAW,QAAA,KAAiC;AAEvE,IAAA,IAAI,KAAA;AACJ,IAAA,IAAI;AACF,MAAA,KAAA,GAAQ,MAAM,eAAe,aAAa,CAAA;AAC1C,MAAA,OAAA,CAAQ,GAAA,CAAI,uCAAA,EAAyC,aAAA,EAAe,KAAA,GAAQ,OAAO,OAAO,CAAA;AAAA,IAC5F,SAAS,GAAA,EAAK;AACZ,MAAA,OAAA,CAAQ,KAAA,CAAM,iDAAA,EAAmD,aAAA,EAAe,GAAG,CAAA;AACnF,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,4BAAA,EAA+B,GAAG,CAAA,CAAE,CAAA;AAAA,IACtD;AAEA,IAAA,IAAI,CAAC,KAAA,EAAO;AACV,MAAA,OAAA,CAAQ,KAAA,CAAM,iDAAiD,aAAa,CAAA;AAC5E,MAAA,MAAM,IAAI,MAAM,2BAA2B,CAAA;AAAA,IAC7C;AAEA,IAAA,MAAM,GAAA,GAAM,IAAI,GAAA,CAAI,QAAA,EAAU,MAAM,CAAA;AACpC,IAAA,IAAI,UAAA,EAAY;AACd,MAAA,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,YAAA,EAAc,UAAU,CAAA;AAAA,IAC/C;AACA,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,OAAA,EAAS,KAAK,CAAA;AAAA,IACrC;AAEA,IAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,CAAI,UAAS,EAAG;AAAA,MAC3C,OAAA,EAAS;AAAA,QACP,eAAA,EAAiB,UAAU,KAAK,CAAA,CAAA;AAAA,QAChC,cAAA,EAAgB;AAAA;AAClB,KACD,CAAA;AAED,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,WAAA,EAAc,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AAAA,IACjD;AAEA,IAAA,OAAO,SAAS,IAAA,EAAK;AAAA,EACvB,GAAG,CAAC,MAAA,EAAQ,eAAe,UAAA,EAAY,KAAA,EAAO,cAAc,CAAC,CAAA;AAG7D,EAAA,MAAM,cAAA,GAAiBA,kBAAY,YAAY;AAC7C,IAAA,IAAI;AACF,MAAA,MAAM,OAAA,GAAU,MAAM,QAAA,CAAqBC,qBAAA,CAAc,OAAO,CAAA;AAChE,MAAA,QAAA,CAAS,WAAS,EAAE,GAAG,MAAM,OAAA,EAAS,KAAA,EAAO,MAAK,CAAE,CAAA;AAAA,IACtD,SAAS,KAAA,EAAO;AACd,MAAA,QAAA,CAAS,CAAA,IAAA,MAAS,EAAE,GAAG,IAAA,EAAM,OAAsB,CAAE,CAAA;AAAA,IACvD;AAAA,EACF,CAAA,EAAG,CAAC,QAAQ,CAAC,CAAA;AAGb,EAAA,MAAM,eAAA,GAAkBD,kBAAY,YAAY;AAC9C,IAAA,IAAI;AACF,MAAA,MAAM,QAAA,GAAW,MAAM,QAAA,CAA2BC,qBAAA,CAAc,QAAQ,CAAA;AACxE,MAAA,QAAA,CAAS,WAAS,EAAE,GAAG,MAAM,QAAA,EAAU,KAAA,EAAO,MAAK,CAAE,CAAA;AAAA,IACvD,SAAS,KAAA,EAAO;AACd,MAAA,QAAA,CAAS,CAAA,IAAA,MAAS,EAAE,GAAG,IAAA,EAAM,OAAsB,CAAE,CAAA;AAAA,IACvD;AAAA,EACF,CAAA,EAAG,CAAC,QAAQ,CAAC,CAAA;AAGb,EAAA,MAAM,mBAAA,GAAsBD,kBAAY,YAAY;AAClD,IAAA,IAAI;AACF,MAAA,MAAM,YAAA,GAAe,MAAM,QAAA,CAA0BC,qBAAA,CAAc,YAAY,CAAA;AAC/E,MAAA,QAAA,CAAS,WAAS,EAAE,GAAG,MAAM,YAAA,EAAc,KAAA,EAAO,MAAK,CAAE,CAAA;AAAA,IAC3D,SAAS,KAAA,EAAO;AACd,MAAA,QAAA,CAAS,CAAA,IAAA,MAAS,EAAE,GAAG,IAAA,EAAM,OAAsB,CAAE,CAAA;AAAA,IACvD;AAAA,EACF,CAAA,EAAG,CAAC,QAAQ,CAAC,CAAA;AAGb,EAAA,MAAM,aAAA,GAAgBD,iBAAA,CAAY,CAAC,UAAA,KAAgC;AACjE,IAAA,OAAO,KAAA,CAAM,OAAA,EAAS,WAAA,EAAa,QAAA,CAAS,UAAU,CAAA,IAAK,KAAA;AAAA,EAC7D,CAAA,EAAG,CAAC,KAAA,CAAM,OAAA,EAAS,WAAW,CAAC,CAAA;AAE/B,EAAA,MAAM,gBAAA,GAAmBA,iBAAA,CAAY,CAAC,WAAA,KAAmC;AACvE,IAAA,OAAO,WAAA,CAAY,KAAK,CAAA,CAAA,KAAK,KAAA,CAAM,SAAS,WAAA,EAAa,QAAA,CAAS,CAAC,CAAC,CAAA;AAAA,EACtE,CAAA,EAAG,CAAC,KAAA,CAAM,OAAA,EAAS,WAAW,CAAC,CAAA;AAE/B,EAAA,MAAM,iBAAA,GAAoBA,iBAAA,CAAY,CAAC,WAAA,KAAmC;AACxE,IAAA,OAAO,WAAA,CAAY,MAAM,CAAA,CAAA,KAAK,KAAA,CAAM,SAAS,WAAA,EAAa,QAAA,CAAS,CAAC,CAAC,CAAA;AAAA,EACvE,CAAA,EAAG,CAAC,KAAA,CAAM,OAAA,EAAS,WAAW,CAAC,CAAA;AAG/B,EAAA,MAAM,gBAAA,GAAmBA,iBAAA,CAAY,CAAC,GAAA,KAAyB;AAC7D,IAAA,OAAO,KAAA,CAAM,QAAA,EAAU,QAAA,GAAW,GAAG,CAAA,IAAK,KAAA;AAAA,EAC5C,CAAA,EAAG,CAAC,KAAA,CAAM,QAAA,EAAU,QAAQ,CAAC,CAAA;AAG7B,EAAA,MAAM,QAAA,GAAWA,kBAAY,YAAoC;AAC/D,IAAA,IAAI;AACF,MAAA,MAAM,KAAA,GAAQ,MAAM,cAAA,CAAe,aAAa,CAAA;AAChD,MAAA,OAAO,KAAA,IAAS,IAAA;AAAA,IAClB,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,EACF,CAAA,EAAG,CAAC,cAAA,EAAgB,aAAa,CAAC,CAAA;AAGlC,EAAAE,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,eAAA,IAAmB,CAAC,SAAA,EAAW;AAClC,MAAA,QAAA,CAAS,WAAS,EAAE,GAAG,IAAA,EAAM,SAAA,EAAW,OAAM,CAAE,CAAA;AAChD,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,WAAW,YAAY;AAC3B,MAAA,QAAA,CAAS,WAAS,EAAE,GAAG,IAAA,EAAM,SAAA,EAAW,MAAK,CAAE,CAAA;AAE/C,MAAA,IAAI;AACF,QAAA,MAAM,CAAC,OAAA,EAAS,QAAA,EAAU,YAAY,CAAA,GAAI,MAAM,QAAQ,GAAA,CAAI;AAAA,UAC1D,SAAqBD,qBAAA,CAAc,OAAO,CAAA,CAAE,KAAA,CAAM,MAAM,IAAI,CAAA;AAAA,UAC5D,SAA2BA,qBAAA,CAAc,QAAQ,CAAA,CAAE,KAAA,CAAM,MAAM,IAAI,CAAA;AAAA,UACnE,SAA0BA,qBAAA,CAAc,YAAY,CAAA,CAAE,KAAA,CAAM,MAAM,IAAI;AAAA,SACvE,CAAA;AAED,QAAA,QAAA,CAAS;AAAA,UACP,SAAA,EAAW,KAAA;AAAA,UACX,KAAA,EAAO,IAAA;AAAA,UACP,OAAA;AAAA,UACA,QAAA;AAAA,UACA;AAAA,SACD,CAAA;AAAA,MACH,SAAS,KAAA,EAAO;AACd,QAAA,QAAA,CAAS,CAAA,IAAA,MAAS;AAAA,UAChB,GAAG,IAAA;AAAA,UACH,SAAA,EAAW,KAAA;AAAA,UACX;AAAA,SACF,CAAE,CAAA;AAAA,MACJ;AAAA,IACF,CAAA;AAEA,IAAA,QAAA,EAAS;AAAA,EACX,CAAA,EAAG,CAAC,eAAA,EAAiB,SAAA,EAAW,QAAQ,CAAC,CAAA;AAGzC,EAAA,MAAM,YAAA,GAAqCF,cAAQ,OAAO;AAAA,IACxD,GAAG,KAAA;AAAA,IACH,cAAA;AAAA,IACA,eAAA;AAAA,IACA,mBAAA;AAAA,IACA,aAAA;AAAA,IACA,gBAAA;AAAA,IACA,iBAAA;AAAA,IACA,gBAAA;AAAA,IACA,cAAA,EAAgB;AAAA,GAClB,CAAA,EAAI;AAAA,IACF,KAAA;AAAA,IACA,cAAA;AAAA,IACA,eAAA;AAAA,IACA,mBAAA;AAAA,IACA,aAAA;AAAA,IACA,gBAAA;AAAA,IACA,iBAAA;AAAA,IACA,gBAAA;AAAA,IACA;AAAA,GACD,CAAA;AAED,EAAA,sCACG,eAAA,CAAgB,QAAA,EAAhB,EAAyB,KAAA,EAAO,cAC9B,QAAA,EACH,CAAA;AAEJ;ACvNO,SAAS,WAAA,GAAoC;AAClD,EAAA,MAAM,OAAA,GAAUI,iBAAW,eAAe,CAAA;AAE1C,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,MAAM,IAAI,MAAM,oDAAoD,CAAA;AAAA,EACtE;AAEA,EAAA,OAAO,OAAA;AACT;;;ACFO,SAAS,cAAA,GAAiB;AAC/B,EAAA,MAAM,EAAE,OAAA,EAAS,aAAA,EAAe,gBAAA,EAAkB,iBAAA,KAAsB,WAAA,EAAY;AAEpF,EAAA,OAAO;AAAA;AAAA,IAEL,WAAA,EAAa,OAAA,EAAS,WAAA,IAAe,EAAC;AAAA;AAAA,IAEtC,KAAA,EAAO,OAAA,EAAS,KAAA,IAAS,EAAC;AAAA;AAAA,IAE1B,aAAA;AAAA;AAAA,IAEA,gBAAA;AAAA;AAAA,IAEA;AAAA,GACF;AACF;;;ACrBO,SAAS,WAAA,GAAc;AAC5B,EAAA,MAAM,EAAE,QAAA,EAAU,gBAAA,EAAkB,eAAA,KAAoB,WAAA,EAAY;AAEpE,EAAA,OAAO;AAAA;AAAA,IAEL,QAAA,EAAU,QAAA,EAAU,QAAA,IAAY,EAAC;AAAA;AAAA,IAEjC,QAAQ,QAAA,EAAU,MAAA;AAAA;AAAA,IAElB,UAAU,QAAA,EAAU,QAAA;AAAA;AAAA,IAEpB,SAAA,EAAW,gBAAA;AAAA;AAAA,IAEX,OAAA,EAAS;AAAA,GACX;AACF;;;AC5BO,SAAS,eAAA,GAAkB;AAChC,EAAA,MAAM,EAAE,YAAA,EAAc,mBAAA,EAAoB,GAAI,WAAA,EAAY;AAE1D,EAAA,OAAO;AAAA;AAAA,IAEL,YAAA;AAAA;AAAA,IAEA,MAAM,YAAA,EAAc,IAAA;AAAA;AAAA,IAEpB,MAAA,EAAQ,YAAA,EAAc,MAAA,IAAU,EAAC;AAAA;AAAA,IAEjC,KAAA,EAAO,YAAA,EAAc,KAAA,IAAS,EAAC;AAAA;AAAA,IAE/B,QAAQ,YAAA,EAAc,MAAA;AAAA;AAAA,IAEtB,QAAA,EAAU,YAAA,EAAc,MAAA,KAAW,QAAA,IAAY,cAAc,MAAA,KAAW,UAAA;AAAA;AAAA,IAExE,UAAA,EAAY,cAAc,MAAA,KAAW,UAAA;AAAA;AAAA,IAErC,UAAA,EAAY,CAAC,GAAA,EAAa,SAAA,GAAY,CAAA,KAAe;AACnD,MAAA,MAAM,KAAA,GAAQ,YAAA,EAAc,MAAA,GAAS,GAAG,CAAA;AACxC,MAAA,MAAM,OAAA,GAAU,YAAA,EAAc,KAAA,GAAQ,GAAG,CAAA,IAAK,CAAA;AAC9C,MAAA,IAAI,KAAA,KAAU,QAAW,OAAO,IAAA;AAChC,MAAA,OAAO,UAAU,SAAA,IAAa,KAAA;AAAA,IAChC,CAAA;AAAA;AAAA,IAEA,OAAA,EAAS;AAAA,GACX;AACF;ACrBO,SAAS,kBAAkB,EAAE,UAAA,EAAY,QAAA,EAAU,QAAA,GAAW,MAAK,EAA2B;AACnG,EAAA,MAAM,EAAE,aAAA,EAAc,GAAI,cAAA,EAAe;AAEzC,EAAA,IAAI,CAAC,aAAA,CAAc,UAAU,CAAA,EAAG;AAC9B,IAAA,uBAAOC,cAAAA,CAAAC,mBAAA,EAAA,EAAG,QAAA,EAAA,QAAA,EAAS,CAAA;AAAA,EACrB;AAEA,EAAA,uBAAOD,cAAAA,CAAAC,mBAAA,EAAA,EAAG,QAAA,EAAS,CAAA;AACrB;ACRO,SAAS,eAAe,EAAE,IAAA,EAAM,QAAA,EAAU,QAAA,GAAW,MAAK,EAAwB;AACvF,EAAA,MAAM,EAAE,SAAA,EAAU,GAAI,WAAA,EAAY;AAElC,EAAA,IAAI,CAAC,SAAA,CAAU,IAAI,CAAA,EAAG;AACpB,IAAA,uBAAOD,cAAAA,CAAAC,mBAAAA,EAAA,EAAG,QAAA,EAAA,QAAA,EAAS,CAAA;AAAA,EACrB;AAEA,EAAA,uBAAOD,cAAAA,CAAAC,mBAAAA,EAAA,EAAG,QAAA,EAAS,CAAA;AACrB","file":"index.js","sourcesContent":["import { createContext } from 'react';\nimport type { SdkContext, SdkFeaturesState, SdkSubscription } from '@habeetat/sdk-core';\n\n/**\n * Habeetat SDK state\n */\nexport interface HabeetatState {\n /** Whether the SDK is loading initial data */\n isLoading: boolean;\n /** Error if any occurred */\n error: Error | null;\n /** SDK context (user, tenant, permissions) */\n context: SdkContext | null;\n /** Feature flags */\n features: SdkFeaturesState | null;\n /** Subscription info */\n subscription: SdkSubscription | null;\n}\n\n/**\n * Habeetat SDK context value\n */\nexport interface HabeetatContextValue extends HabeetatState {\n /** Refresh context from server */\n refreshContext: () => Promise<void>;\n /** Refresh features from server */\n refreshFeatures: () => Promise<void>;\n /** Refresh subscription from server */\n refreshSubscription: () => Promise<void>;\n /** Check if user has permission */\n hasPermission: (permission: string) => boolean;\n /** Check if user has any of the permissions */\n hasAnyPermission: (permissions: string[]) => boolean;\n /** Check if user has all permissions */\n hasAllPermissions: (permissions: string[]) => boolean;\n /** Check if feature is enabled */\n isFeatureEnabled: (key: string) => boolean;\n /** Get access token for API calls */\n getAccessToken: () => Promise<string | null>;\n}\n\n/**\n * Default context value\n */\nconst defaultContextValue: HabeetatContextValue = {\n isLoading: true,\n error: null,\n context: null,\n features: null,\n subscription: null,\n refreshContext: async () => {},\n refreshFeatures: async () => {},\n refreshSubscription: async () => {},\n hasPermission: () => false,\n hasAnyPermission: () => false,\n hasAllPermissions: () => false,\n isFeatureEnabled: () => false,\n getAccessToken: async () => null,\n};\n\n/**\n * Habeetat React Context\n */\nexport const HabeetatContext = createContext<HabeetatContextValue>(defaultContextValue);\n","import { useState, useEffect, useCallback, useMemo, type ReactNode } from 'react';\nimport { useLogto } from '@logto/react';\nimport { SDK_ENDPOINTS } from '@habeetat/sdk-core';\nimport type { SdkContext, SdkFeaturesState, SdkSubscription } from '@habeetat/sdk-core';\nimport { HabeetatContext, type HabeetatContextValue, type HabeetatState } from '../context/HabeetatContext';\n\n/**\n * Props for HabeetatProvider\n */\nexport interface HabeetatProviderProps {\n /** Platform API base URL (SDK endpoints) */\n platformUrl: string;\n /** Logto API resource identifier (for token requests) */\n logtoResource?: string;\n /** App ID for this application */\n appId?: string;\n /** Tenant slug (if known) */\n tenantSlug?: string;\n /** Whether to auto-fetch context on mount */\n autoFetch?: boolean;\n /** Children components */\n children: ReactNode;\n}\n\n/**\n * HabeetatProvider - Main provider for Habeetat SDK in React apps\n * \n * Must be used inside LogtoProvider from @logto/react\n * \n * @example\n * ```tsx\n * import { LogtoProvider } from '@logto/react';\n * import { HabeetatProvider } from '@habeetat/sdk-react';\n * \n * function App() {\n * return (\n * <LogtoProvider config={logtoConfig}>\n * <HabeetatProvider \n * platformUrl=\"https://api.habeetat.io\"\n * tenantSlug=\"acme-corp\"\n * >\n * <MyApp />\n * </HabeetatProvider>\n * </LogtoProvider>\n * );\n * }\n * ```\n */\nexport function HabeetatProvider({\n platformUrl,\n logtoResource,\n appId,\n tenantSlug,\n autoFetch = true,\n children,\n}: HabeetatProviderProps) {\n const { isAuthenticated, getAccessToken } = useLogto();\n \n // Use logtoResource if provided, otherwise fall back to platformUrl\n const tokenResource = logtoResource || platformUrl;\n \n const [state, setState] = useState<HabeetatState>({\n isLoading: true,\n error: null,\n context: null,\n features: null,\n subscription: null,\n });\n\n // Build API URL\n const apiUrl = useMemo(() => platformUrl.replace(/\\/$/, ''), [platformUrl]);\n\n // Fetch helper\n const fetchApi = useCallback(async <T,>(endpoint: string): Promise<T> => {\n // Use the Logto resource for token - this must match what's configured in Logto\n let token: string | undefined;\n try {\n token = await getAccessToken(tokenResource);\n console.log('[HabeetatSDK] Got token for resource:', tokenResource, token ? 'OK' : 'EMPTY');\n } catch (err) {\n console.error('[HabeetatSDK] Failed to get token for resource:', tokenResource, err);\n throw new Error(`Failed to get access token: ${err}`);\n }\n \n if (!token) {\n console.error('[HabeetatSDK] No token returned for resource:', tokenResource);\n throw new Error('No access token available');\n }\n\n const url = new URL(endpoint, apiUrl);\n if (tenantSlug) {\n url.searchParams.set('tenantSlug', tenantSlug);\n }\n if (appId) {\n url.searchParams.set('appId', appId);\n }\n\n const response = await fetch(url.toString(), {\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n throw new Error(`API error: ${response.status}`);\n }\n\n return response.json() as Promise<T>;\n }, [apiUrl, tokenResource, tenantSlug, appId, getAccessToken]);\n\n // Refresh context\n const refreshContext = useCallback(async () => {\n try {\n const context = await fetchApi<SdkContext>(SDK_ENDPOINTS.CONTEXT);\n setState(prev => ({ ...prev, context, error: null }));\n } catch (error) {\n setState(prev => ({ ...prev, error: error as Error }));\n }\n }, [fetchApi]);\n\n // Refresh features\n const refreshFeatures = useCallback(async () => {\n try {\n const features = await fetchApi<SdkFeaturesState>(SDK_ENDPOINTS.FEATURES);\n setState(prev => ({ ...prev, features, error: null }));\n } catch (error) {\n setState(prev => ({ ...prev, error: error as Error }));\n }\n }, [fetchApi]);\n\n // Refresh subscription\n const refreshSubscription = useCallback(async () => {\n try {\n const subscription = await fetchApi<SdkSubscription>(SDK_ENDPOINTS.SUBSCRIPTION);\n setState(prev => ({ ...prev, subscription, error: null }));\n } catch (error) {\n setState(prev => ({ ...prev, error: error as Error }));\n }\n }, [fetchApi]);\n\n // Permission helpers\n const hasPermission = useCallback((permission: string): boolean => {\n return state.context?.permissions?.includes(permission) ?? false;\n }, [state.context?.permissions]);\n\n const hasAnyPermission = useCallback((permissions: string[]): boolean => {\n return permissions.some(p => state.context?.permissions?.includes(p));\n }, [state.context?.permissions]);\n\n const hasAllPermissions = useCallback((permissions: string[]): boolean => {\n return permissions.every(p => state.context?.permissions?.includes(p));\n }, [state.context?.permissions]);\n\n // Feature helper\n const isFeatureEnabled = useCallback((key: string): boolean => {\n return state.features?.features?.[key] ?? false;\n }, [state.features?.features]);\n\n // Get access token helper\n const getToken = useCallback(async (): Promise<string | null> => {\n try {\n const token = await getAccessToken(tokenResource);\n return token ?? null;\n } catch {\n return null;\n }\n }, [getAccessToken, tokenResource]);\n\n // Auto-fetch on mount when authenticated\n useEffect(() => {\n if (!isAuthenticated || !autoFetch) {\n setState(prev => ({ ...prev, isLoading: false }));\n return;\n }\n\n const fetchAll = async () => {\n setState(prev => ({ ...prev, isLoading: true }));\n \n try {\n const [context, features, subscription] = await Promise.all([\n fetchApi<SdkContext>(SDK_ENDPOINTS.CONTEXT).catch(() => null),\n fetchApi<SdkFeaturesState>(SDK_ENDPOINTS.FEATURES).catch(() => null),\n fetchApi<SdkSubscription>(SDK_ENDPOINTS.SUBSCRIPTION).catch(() => null),\n ]);\n\n setState({\n isLoading: false,\n error: null,\n context,\n features,\n subscription,\n });\n } catch (error) {\n setState(prev => ({\n ...prev,\n isLoading: false,\n error: error as Error,\n }));\n }\n };\n\n fetchAll();\n }, [isAuthenticated, autoFetch, fetchApi]);\n\n // Build context value\n const contextValue: HabeetatContextValue = useMemo(() => ({\n ...state,\n refreshContext,\n refreshFeatures,\n refreshSubscription,\n hasPermission,\n hasAnyPermission,\n hasAllPermissions,\n isFeatureEnabled,\n getAccessToken: getToken,\n }), [\n state,\n refreshContext,\n refreshFeatures,\n refreshSubscription,\n hasPermission,\n hasAnyPermission,\n hasAllPermissions,\n isFeatureEnabled,\n getToken,\n ]);\n\n return (\n <HabeetatContext.Provider value={contextValue}>\n {children}\n </HabeetatContext.Provider>\n );\n}\n","import { useContext } from 'react';\nimport { HabeetatContext, type HabeetatContextValue } from '../context/HabeetatContext';\n\n/**\n * Hook to access Habeetat SDK context\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { context, isLoading, error } = useHabeetat();\n * \n * if (isLoading) return <Spinner />;\n * if (error) return <Error message={error.message} />;\n * \n * return <div>Hello, {context?.user.name}</div>;\n * }\n * ```\n */\nexport function useHabeetat(): HabeetatContextValue {\n const context = useContext(HabeetatContext);\n \n if (!context) {\n throw new Error('useHabeetat must be used within a HabeetatProvider');\n }\n \n return context;\n}\n","import { useHabeetat } from './useHabeetat';\n\n/**\n * Hook for permission checks\n * \n * @example\n * ```tsx\n * function ContactsPage() {\n * const { hasPermission, hasAnyPermission } = usePermissions();\n * \n * const canRead = hasPermission('contacts:read');\n * const canWrite = hasPermission('contacts:write');\n * const canManage = hasAnyPermission(['contacts:delete', 'contacts:admin']);\n * \n * return (\n * <div>\n * {canRead && <ContactsList />}\n * {canWrite && <AddContactButton />}\n * {canManage && <ManageContactsButton />}\n * </div>\n * );\n * }\n * ```\n */\nexport function usePermissions() {\n const { context, hasPermission, hasAnyPermission, hasAllPermissions } = useHabeetat();\n \n return {\n /** All user permissions */\n permissions: context?.permissions ?? [],\n /** All user roles */\n roles: context?.roles ?? [],\n /** Check if user has a specific permission */\n hasPermission,\n /** Check if user has any of the specified permissions */\n hasAnyPermission,\n /** Check if user has all of the specified permissions */\n hasAllPermissions,\n };\n}\n","import { useHabeetat } from './useHabeetat';\n\n/**\n * Hook for feature flag checks\n * \n * @example\n * ```tsx\n * function DealsPage() {\n * const { isEnabled, features } = useFeatures();\n * \n * if (!isEnabled('crm.deals.enabled')) {\n * return <UpgradePrompt feature=\"Deals\" />;\n * }\n * \n * return <DealsList />;\n * }\n * ```\n */\nexport function useFeatures() {\n const { features, isFeatureEnabled, refreshFeatures } = useHabeetat();\n \n return {\n /** All feature flags */\n features: features?.features ?? {},\n /** Feature source (plan, tenant, etc.) */\n source: features?.source,\n /** Plan code if source is plan */\n planCode: features?.planCode,\n /** Check if a feature is enabled */\n isEnabled: isFeatureEnabled,\n /** Refresh features from server */\n refresh: refreshFeatures,\n };\n}\n","import { useHabeetat } from './useHabeetat';\n\n/**\n * Hook for subscription and plan info\n */\nexport function useSubscription() {\n const { subscription, refreshSubscription } = useHabeetat();\n \n return {\n /** Current subscription */\n subscription,\n /** Current plan */\n plan: subscription?.plan,\n /** Plan limits */\n limits: subscription?.limits ?? {},\n /** Current usage */\n usage: subscription?.usage ?? {},\n /** Subscription status */\n status: subscription?.status,\n /** Check if subscription is active */\n isActive: subscription?.status === 'active' || subscription?.status === 'trialing',\n /** Check if in trial */\n isTrialing: subscription?.status === 'trialing',\n /** Check limit */\n checkLimit: (key: string, increment = 0): boolean => {\n const limit = subscription?.limits?.[key];\n const current = subscription?.usage?.[key] ?? 0;\n if (limit === undefined) return true;\n return current + increment <= limit;\n },\n /** Refresh subscription */\n refresh: refreshSubscription,\n };\n}\n","import { type ReactNode } from 'react';\nimport { usePermissions } from '../hooks/usePermissions';\n\ninterface RequirePermissionProps {\n permission: string;\n children: ReactNode;\n fallback?: ReactNode;\n}\n\n/**\n * Component that renders children only if user has the required permission\n */\nexport function RequirePermission({ permission, children, fallback = null }: RequirePermissionProps) {\n const { hasPermission } = usePermissions();\n \n if (!hasPermission(permission)) {\n return <>{fallback}</>;\n }\n \n return <>{children}</>;\n}\n","import { type ReactNode } from 'react';\nimport { useFeatures } from '../hooks/useFeatures';\n\ninterface RequireFeatureProps {\n flag: string;\n children: ReactNode;\n fallback?: ReactNode;\n}\n\n/**\n * Component that renders children only if the feature flag is enabled\n */\nexport function RequireFeature({ flag, children, fallback = null }: RequireFeatureProps) {\n const { isEnabled } = useFeatures();\n \n if (!isEnabled(flag)) {\n return <>{fallback}</>;\n }\n \n return <>{children}</>;\n}\n"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { createContext, useState, useMemo, useCallback, useEffect, useContext } from 'react';
|
|
2
|
+
import { useLogto } from '@logto/react';
|
|
3
|
+
import { SDK_ENDPOINTS } from '@habeetat/sdk-core';
|
|
4
|
+
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
// src/provider/HabeetatProvider.tsx
|
|
7
|
+
var defaultContextValue = {
|
|
8
|
+
isLoading: true,
|
|
9
|
+
error: null,
|
|
10
|
+
context: null,
|
|
11
|
+
features: null,
|
|
12
|
+
subscription: null,
|
|
13
|
+
refreshContext: async () => {
|
|
14
|
+
},
|
|
15
|
+
refreshFeatures: async () => {
|
|
16
|
+
},
|
|
17
|
+
refreshSubscription: async () => {
|
|
18
|
+
},
|
|
19
|
+
hasPermission: () => false,
|
|
20
|
+
hasAnyPermission: () => false,
|
|
21
|
+
hasAllPermissions: () => false,
|
|
22
|
+
isFeatureEnabled: () => false,
|
|
23
|
+
getAccessToken: async () => null
|
|
24
|
+
};
|
|
25
|
+
var HabeetatContext = createContext(defaultContextValue);
|
|
26
|
+
function HabeetatProvider({
|
|
27
|
+
platformUrl,
|
|
28
|
+
logtoResource,
|
|
29
|
+
appId,
|
|
30
|
+
tenantSlug,
|
|
31
|
+
autoFetch = true,
|
|
32
|
+
children
|
|
33
|
+
}) {
|
|
34
|
+
const { isAuthenticated, getAccessToken } = useLogto();
|
|
35
|
+
const tokenResource = logtoResource || platformUrl;
|
|
36
|
+
const [state, setState] = useState({
|
|
37
|
+
isLoading: true,
|
|
38
|
+
error: null,
|
|
39
|
+
context: null,
|
|
40
|
+
features: null,
|
|
41
|
+
subscription: null
|
|
42
|
+
});
|
|
43
|
+
const apiUrl = useMemo(() => platformUrl.replace(/\/$/, ""), [platformUrl]);
|
|
44
|
+
const fetchApi = useCallback(async (endpoint) => {
|
|
45
|
+
let token;
|
|
46
|
+
try {
|
|
47
|
+
token = await getAccessToken(tokenResource);
|
|
48
|
+
console.log("[HabeetatSDK] Got token for resource:", tokenResource, token ? "OK" : "EMPTY");
|
|
49
|
+
} catch (err) {
|
|
50
|
+
console.error("[HabeetatSDK] Failed to get token for resource:", tokenResource, err);
|
|
51
|
+
throw new Error(`Failed to get access token: ${err}`);
|
|
52
|
+
}
|
|
53
|
+
if (!token) {
|
|
54
|
+
console.error("[HabeetatSDK] No token returned for resource:", tokenResource);
|
|
55
|
+
throw new Error("No access token available");
|
|
56
|
+
}
|
|
57
|
+
const url = new URL(endpoint, apiUrl);
|
|
58
|
+
if (tenantSlug) {
|
|
59
|
+
url.searchParams.set("tenantSlug", tenantSlug);
|
|
60
|
+
}
|
|
61
|
+
if (appId) {
|
|
62
|
+
url.searchParams.set("appId", appId);
|
|
63
|
+
}
|
|
64
|
+
const response = await fetch(url.toString(), {
|
|
65
|
+
headers: {
|
|
66
|
+
"Authorization": `Bearer ${token}`,
|
|
67
|
+
"Content-Type": "application/json"
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
if (!response.ok) {
|
|
71
|
+
throw new Error(`API error: ${response.status}`);
|
|
72
|
+
}
|
|
73
|
+
return response.json();
|
|
74
|
+
}, [apiUrl, tokenResource, tenantSlug, appId, getAccessToken]);
|
|
75
|
+
const refreshContext = useCallback(async () => {
|
|
76
|
+
try {
|
|
77
|
+
const context = await fetchApi(SDK_ENDPOINTS.CONTEXT);
|
|
78
|
+
setState((prev) => ({ ...prev, context, error: null }));
|
|
79
|
+
} catch (error) {
|
|
80
|
+
setState((prev) => ({ ...prev, error }));
|
|
81
|
+
}
|
|
82
|
+
}, [fetchApi]);
|
|
83
|
+
const refreshFeatures = useCallback(async () => {
|
|
84
|
+
try {
|
|
85
|
+
const features = await fetchApi(SDK_ENDPOINTS.FEATURES);
|
|
86
|
+
setState((prev) => ({ ...prev, features, error: null }));
|
|
87
|
+
} catch (error) {
|
|
88
|
+
setState((prev) => ({ ...prev, error }));
|
|
89
|
+
}
|
|
90
|
+
}, [fetchApi]);
|
|
91
|
+
const refreshSubscription = useCallback(async () => {
|
|
92
|
+
try {
|
|
93
|
+
const subscription = await fetchApi(SDK_ENDPOINTS.SUBSCRIPTION);
|
|
94
|
+
setState((prev) => ({ ...prev, subscription, error: null }));
|
|
95
|
+
} catch (error) {
|
|
96
|
+
setState((prev) => ({ ...prev, error }));
|
|
97
|
+
}
|
|
98
|
+
}, [fetchApi]);
|
|
99
|
+
const hasPermission = useCallback((permission) => {
|
|
100
|
+
return state.context?.permissions?.includes(permission) ?? false;
|
|
101
|
+
}, [state.context?.permissions]);
|
|
102
|
+
const hasAnyPermission = useCallback((permissions) => {
|
|
103
|
+
return permissions.some((p) => state.context?.permissions?.includes(p));
|
|
104
|
+
}, [state.context?.permissions]);
|
|
105
|
+
const hasAllPermissions = useCallback((permissions) => {
|
|
106
|
+
return permissions.every((p) => state.context?.permissions?.includes(p));
|
|
107
|
+
}, [state.context?.permissions]);
|
|
108
|
+
const isFeatureEnabled = useCallback((key) => {
|
|
109
|
+
return state.features?.features?.[key] ?? false;
|
|
110
|
+
}, [state.features?.features]);
|
|
111
|
+
const getToken = useCallback(async () => {
|
|
112
|
+
try {
|
|
113
|
+
const token = await getAccessToken(tokenResource);
|
|
114
|
+
return token ?? null;
|
|
115
|
+
} catch {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
}, [getAccessToken, tokenResource]);
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
if (!isAuthenticated || !autoFetch) {
|
|
121
|
+
setState((prev) => ({ ...prev, isLoading: false }));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const fetchAll = async () => {
|
|
125
|
+
setState((prev) => ({ ...prev, isLoading: true }));
|
|
126
|
+
try {
|
|
127
|
+
const [context, features, subscription] = await Promise.all([
|
|
128
|
+
fetchApi(SDK_ENDPOINTS.CONTEXT).catch(() => null),
|
|
129
|
+
fetchApi(SDK_ENDPOINTS.FEATURES).catch(() => null),
|
|
130
|
+
fetchApi(SDK_ENDPOINTS.SUBSCRIPTION).catch(() => null)
|
|
131
|
+
]);
|
|
132
|
+
setState({
|
|
133
|
+
isLoading: false,
|
|
134
|
+
error: null,
|
|
135
|
+
context,
|
|
136
|
+
features,
|
|
137
|
+
subscription
|
|
138
|
+
});
|
|
139
|
+
} catch (error) {
|
|
140
|
+
setState((prev) => ({
|
|
141
|
+
...prev,
|
|
142
|
+
isLoading: false,
|
|
143
|
+
error
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
fetchAll();
|
|
148
|
+
}, [isAuthenticated, autoFetch, fetchApi]);
|
|
149
|
+
const contextValue = useMemo(() => ({
|
|
150
|
+
...state,
|
|
151
|
+
refreshContext,
|
|
152
|
+
refreshFeatures,
|
|
153
|
+
refreshSubscription,
|
|
154
|
+
hasPermission,
|
|
155
|
+
hasAnyPermission,
|
|
156
|
+
hasAllPermissions,
|
|
157
|
+
isFeatureEnabled,
|
|
158
|
+
getAccessToken: getToken
|
|
159
|
+
}), [
|
|
160
|
+
state,
|
|
161
|
+
refreshContext,
|
|
162
|
+
refreshFeatures,
|
|
163
|
+
refreshSubscription,
|
|
164
|
+
hasPermission,
|
|
165
|
+
hasAnyPermission,
|
|
166
|
+
hasAllPermissions,
|
|
167
|
+
isFeatureEnabled,
|
|
168
|
+
getToken
|
|
169
|
+
]);
|
|
170
|
+
return /* @__PURE__ */ jsx(HabeetatContext.Provider, { value: contextValue, children });
|
|
171
|
+
}
|
|
172
|
+
function useHabeetat() {
|
|
173
|
+
const context = useContext(HabeetatContext);
|
|
174
|
+
if (!context) {
|
|
175
|
+
throw new Error("useHabeetat must be used within a HabeetatProvider");
|
|
176
|
+
}
|
|
177
|
+
return context;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// src/hooks/usePermissions.ts
|
|
181
|
+
function usePermissions() {
|
|
182
|
+
const { context, hasPermission, hasAnyPermission, hasAllPermissions } = useHabeetat();
|
|
183
|
+
return {
|
|
184
|
+
/** All user permissions */
|
|
185
|
+
permissions: context?.permissions ?? [],
|
|
186
|
+
/** All user roles */
|
|
187
|
+
roles: context?.roles ?? [],
|
|
188
|
+
/** Check if user has a specific permission */
|
|
189
|
+
hasPermission,
|
|
190
|
+
/** Check if user has any of the specified permissions */
|
|
191
|
+
hasAnyPermission,
|
|
192
|
+
/** Check if user has all of the specified permissions */
|
|
193
|
+
hasAllPermissions
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/hooks/useFeatures.ts
|
|
198
|
+
function useFeatures() {
|
|
199
|
+
const { features, isFeatureEnabled, refreshFeatures } = useHabeetat();
|
|
200
|
+
return {
|
|
201
|
+
/** All feature flags */
|
|
202
|
+
features: features?.features ?? {},
|
|
203
|
+
/** Feature source (plan, tenant, etc.) */
|
|
204
|
+
source: features?.source,
|
|
205
|
+
/** Plan code if source is plan */
|
|
206
|
+
planCode: features?.planCode,
|
|
207
|
+
/** Check if a feature is enabled */
|
|
208
|
+
isEnabled: isFeatureEnabled,
|
|
209
|
+
/** Refresh features from server */
|
|
210
|
+
refresh: refreshFeatures
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// src/hooks/useSubscription.ts
|
|
215
|
+
function useSubscription() {
|
|
216
|
+
const { subscription, refreshSubscription } = useHabeetat();
|
|
217
|
+
return {
|
|
218
|
+
/** Current subscription */
|
|
219
|
+
subscription,
|
|
220
|
+
/** Current plan */
|
|
221
|
+
plan: subscription?.plan,
|
|
222
|
+
/** Plan limits */
|
|
223
|
+
limits: subscription?.limits ?? {},
|
|
224
|
+
/** Current usage */
|
|
225
|
+
usage: subscription?.usage ?? {},
|
|
226
|
+
/** Subscription status */
|
|
227
|
+
status: subscription?.status,
|
|
228
|
+
/** Check if subscription is active */
|
|
229
|
+
isActive: subscription?.status === "active" || subscription?.status === "trialing",
|
|
230
|
+
/** Check if in trial */
|
|
231
|
+
isTrialing: subscription?.status === "trialing",
|
|
232
|
+
/** Check limit */
|
|
233
|
+
checkLimit: (key, increment = 0) => {
|
|
234
|
+
const limit = subscription?.limits?.[key];
|
|
235
|
+
const current = subscription?.usage?.[key] ?? 0;
|
|
236
|
+
if (limit === void 0) return true;
|
|
237
|
+
return current + increment <= limit;
|
|
238
|
+
},
|
|
239
|
+
/** Refresh subscription */
|
|
240
|
+
refresh: refreshSubscription
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
function RequirePermission({ permission, children, fallback = null }) {
|
|
244
|
+
const { hasPermission } = usePermissions();
|
|
245
|
+
if (!hasPermission(permission)) {
|
|
246
|
+
return /* @__PURE__ */ jsx(Fragment, { children: fallback });
|
|
247
|
+
}
|
|
248
|
+
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
249
|
+
}
|
|
250
|
+
function RequireFeature({ flag, children, fallback = null }) {
|
|
251
|
+
const { isEnabled } = useFeatures();
|
|
252
|
+
if (!isEnabled(flag)) {
|
|
253
|
+
return /* @__PURE__ */ jsx(Fragment, { children: fallback });
|
|
254
|
+
}
|
|
255
|
+
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export { HabeetatContext, HabeetatProvider, RequireFeature, RequirePermission, useFeatures, useHabeetat, usePermissions, useSubscription };
|
|
259
|
+
//# sourceMappingURL=index.mjs.map
|
|
260
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/context/HabeetatContext.ts","../src/provider/HabeetatProvider.tsx","../src/hooks/useHabeetat.ts","../src/hooks/usePermissions.ts","../src/hooks/useFeatures.ts","../src/hooks/useSubscription.ts","../src/components/RequirePermission.tsx","../src/components/RequireFeature.tsx"],"names":["jsx","Fragment"],"mappings":"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","file":"index.mjs","sourcesContent":["import { createContext } from 'react';\nimport type { SdkContext, SdkFeaturesState, SdkSubscription } from '@habeetat/sdk-core';\n\n/**\n * Habeetat SDK state\n */\nexport interface HabeetatState {\n /** Whether the SDK is loading initial data */\n isLoading: boolean;\n /** Error if any occurred */\n error: Error | null;\n /** SDK context (user, tenant, permissions) */\n context: SdkContext | null;\n /** Feature flags */\n features: SdkFeaturesState | null;\n /** Subscription info */\n subscription: SdkSubscription | null;\n}\n\n/**\n * Habeetat SDK context value\n */\nexport interface HabeetatContextValue extends HabeetatState {\n /** Refresh context from server */\n refreshContext: () => Promise<void>;\n /** Refresh features from server */\n refreshFeatures: () => Promise<void>;\n /** Refresh subscription from server */\n refreshSubscription: () => Promise<void>;\n /** Check if user has permission */\n hasPermission: (permission: string) => boolean;\n /** Check if user has any of the permissions */\n hasAnyPermission: (permissions: string[]) => boolean;\n /** Check if user has all permissions */\n hasAllPermissions: (permissions: string[]) => boolean;\n /** Check if feature is enabled */\n isFeatureEnabled: (key: string) => boolean;\n /** Get access token for API calls */\n getAccessToken: () => Promise<string | null>;\n}\n\n/**\n * Default context value\n */\nconst defaultContextValue: HabeetatContextValue = {\n isLoading: true,\n error: null,\n context: null,\n features: null,\n subscription: null,\n refreshContext: async () => {},\n refreshFeatures: async () => {},\n refreshSubscription: async () => {},\n hasPermission: () => false,\n hasAnyPermission: () => false,\n hasAllPermissions: () => false,\n isFeatureEnabled: () => false,\n getAccessToken: async () => null,\n};\n\n/**\n * Habeetat React Context\n */\nexport const HabeetatContext = createContext<HabeetatContextValue>(defaultContextValue);\n","import { useState, useEffect, useCallback, useMemo, type ReactNode } from 'react';\nimport { useLogto } from '@logto/react';\nimport { SDK_ENDPOINTS } from '@habeetat/sdk-core';\nimport type { SdkContext, SdkFeaturesState, SdkSubscription } from '@habeetat/sdk-core';\nimport { HabeetatContext, type HabeetatContextValue, type HabeetatState } from '../context/HabeetatContext';\n\n/**\n * Props for HabeetatProvider\n */\nexport interface HabeetatProviderProps {\n /** Platform API base URL (SDK endpoints) */\n platformUrl: string;\n /** Logto API resource identifier (for token requests) */\n logtoResource?: string;\n /** App ID for this application */\n appId?: string;\n /** Tenant slug (if known) */\n tenantSlug?: string;\n /** Whether to auto-fetch context on mount */\n autoFetch?: boolean;\n /** Children components */\n children: ReactNode;\n}\n\n/**\n * HabeetatProvider - Main provider for Habeetat SDK in React apps\n * \n * Must be used inside LogtoProvider from @logto/react\n * \n * @example\n * ```tsx\n * import { LogtoProvider } from '@logto/react';\n * import { HabeetatProvider } from '@habeetat/sdk-react';\n * \n * function App() {\n * return (\n * <LogtoProvider config={logtoConfig}>\n * <HabeetatProvider \n * platformUrl=\"https://api.habeetat.io\"\n * tenantSlug=\"acme-corp\"\n * >\n * <MyApp />\n * </HabeetatProvider>\n * </LogtoProvider>\n * );\n * }\n * ```\n */\nexport function HabeetatProvider({\n platformUrl,\n logtoResource,\n appId,\n tenantSlug,\n autoFetch = true,\n children,\n}: HabeetatProviderProps) {\n const { isAuthenticated, getAccessToken } = useLogto();\n \n // Use logtoResource if provided, otherwise fall back to platformUrl\n const tokenResource = logtoResource || platformUrl;\n \n const [state, setState] = useState<HabeetatState>({\n isLoading: true,\n error: null,\n context: null,\n features: null,\n subscription: null,\n });\n\n // Build API URL\n const apiUrl = useMemo(() => platformUrl.replace(/\\/$/, ''), [platformUrl]);\n\n // Fetch helper\n const fetchApi = useCallback(async <T,>(endpoint: string): Promise<T> => {\n // Use the Logto resource for token - this must match what's configured in Logto\n let token: string | undefined;\n try {\n token = await getAccessToken(tokenResource);\n console.log('[HabeetatSDK] Got token for resource:', tokenResource, token ? 'OK' : 'EMPTY');\n } catch (err) {\n console.error('[HabeetatSDK] Failed to get token for resource:', tokenResource, err);\n throw new Error(`Failed to get access token: ${err}`);\n }\n \n if (!token) {\n console.error('[HabeetatSDK] No token returned for resource:', tokenResource);\n throw new Error('No access token available');\n }\n\n const url = new URL(endpoint, apiUrl);\n if (tenantSlug) {\n url.searchParams.set('tenantSlug', tenantSlug);\n }\n if (appId) {\n url.searchParams.set('appId', appId);\n }\n\n const response = await fetch(url.toString(), {\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n throw new Error(`API error: ${response.status}`);\n }\n\n return response.json() as Promise<T>;\n }, [apiUrl, tokenResource, tenantSlug, appId, getAccessToken]);\n\n // Refresh context\n const refreshContext = useCallback(async () => {\n try {\n const context = await fetchApi<SdkContext>(SDK_ENDPOINTS.CONTEXT);\n setState(prev => ({ ...prev, context, error: null }));\n } catch (error) {\n setState(prev => ({ ...prev, error: error as Error }));\n }\n }, [fetchApi]);\n\n // Refresh features\n const refreshFeatures = useCallback(async () => {\n try {\n const features = await fetchApi<SdkFeaturesState>(SDK_ENDPOINTS.FEATURES);\n setState(prev => ({ ...prev, features, error: null }));\n } catch (error) {\n setState(prev => ({ ...prev, error: error as Error }));\n }\n }, [fetchApi]);\n\n // Refresh subscription\n const refreshSubscription = useCallback(async () => {\n try {\n const subscription = await fetchApi<SdkSubscription>(SDK_ENDPOINTS.SUBSCRIPTION);\n setState(prev => ({ ...prev, subscription, error: null }));\n } catch (error) {\n setState(prev => ({ ...prev, error: error as Error }));\n }\n }, [fetchApi]);\n\n // Permission helpers\n const hasPermission = useCallback((permission: string): boolean => {\n return state.context?.permissions?.includes(permission) ?? false;\n }, [state.context?.permissions]);\n\n const hasAnyPermission = useCallback((permissions: string[]): boolean => {\n return permissions.some(p => state.context?.permissions?.includes(p));\n }, [state.context?.permissions]);\n\n const hasAllPermissions = useCallback((permissions: string[]): boolean => {\n return permissions.every(p => state.context?.permissions?.includes(p));\n }, [state.context?.permissions]);\n\n // Feature helper\n const isFeatureEnabled = useCallback((key: string): boolean => {\n return state.features?.features?.[key] ?? false;\n }, [state.features?.features]);\n\n // Get access token helper\n const getToken = useCallback(async (): Promise<string | null> => {\n try {\n const token = await getAccessToken(tokenResource);\n return token ?? null;\n } catch {\n return null;\n }\n }, [getAccessToken, tokenResource]);\n\n // Auto-fetch on mount when authenticated\n useEffect(() => {\n if (!isAuthenticated || !autoFetch) {\n setState(prev => ({ ...prev, isLoading: false }));\n return;\n }\n\n const fetchAll = async () => {\n setState(prev => ({ ...prev, isLoading: true }));\n \n try {\n const [context, features, subscription] = await Promise.all([\n fetchApi<SdkContext>(SDK_ENDPOINTS.CONTEXT).catch(() => null),\n fetchApi<SdkFeaturesState>(SDK_ENDPOINTS.FEATURES).catch(() => null),\n fetchApi<SdkSubscription>(SDK_ENDPOINTS.SUBSCRIPTION).catch(() => null),\n ]);\n\n setState({\n isLoading: false,\n error: null,\n context,\n features,\n subscription,\n });\n } catch (error) {\n setState(prev => ({\n ...prev,\n isLoading: false,\n error: error as Error,\n }));\n }\n };\n\n fetchAll();\n }, [isAuthenticated, autoFetch, fetchApi]);\n\n // Build context value\n const contextValue: HabeetatContextValue = useMemo(() => ({\n ...state,\n refreshContext,\n refreshFeatures,\n refreshSubscription,\n hasPermission,\n hasAnyPermission,\n hasAllPermissions,\n isFeatureEnabled,\n getAccessToken: getToken,\n }), [\n state,\n refreshContext,\n refreshFeatures,\n refreshSubscription,\n hasPermission,\n hasAnyPermission,\n hasAllPermissions,\n isFeatureEnabled,\n getToken,\n ]);\n\n return (\n <HabeetatContext.Provider value={contextValue}>\n {children}\n </HabeetatContext.Provider>\n );\n}\n","import { useContext } from 'react';\nimport { HabeetatContext, type HabeetatContextValue } from '../context/HabeetatContext';\n\n/**\n * Hook to access Habeetat SDK context\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { context, isLoading, error } = useHabeetat();\n * \n * if (isLoading) return <Spinner />;\n * if (error) return <Error message={error.message} />;\n * \n * return <div>Hello, {context?.user.name}</div>;\n * }\n * ```\n */\nexport function useHabeetat(): HabeetatContextValue {\n const context = useContext(HabeetatContext);\n \n if (!context) {\n throw new Error('useHabeetat must be used within a HabeetatProvider');\n }\n \n return context;\n}\n","import { useHabeetat } from './useHabeetat';\n\n/**\n * Hook for permission checks\n * \n * @example\n * ```tsx\n * function ContactsPage() {\n * const { hasPermission, hasAnyPermission } = usePermissions();\n * \n * const canRead = hasPermission('contacts:read');\n * const canWrite = hasPermission('contacts:write');\n * const canManage = hasAnyPermission(['contacts:delete', 'contacts:admin']);\n * \n * return (\n * <div>\n * {canRead && <ContactsList />}\n * {canWrite && <AddContactButton />}\n * {canManage && <ManageContactsButton />}\n * </div>\n * );\n * }\n * ```\n */\nexport function usePermissions() {\n const { context, hasPermission, hasAnyPermission, hasAllPermissions } = useHabeetat();\n \n return {\n /** All user permissions */\n permissions: context?.permissions ?? [],\n /** All user roles */\n roles: context?.roles ?? [],\n /** Check if user has a specific permission */\n hasPermission,\n /** Check if user has any of the specified permissions */\n hasAnyPermission,\n /** Check if user has all of the specified permissions */\n hasAllPermissions,\n };\n}\n","import { useHabeetat } from './useHabeetat';\n\n/**\n * Hook for feature flag checks\n * \n * @example\n * ```tsx\n * function DealsPage() {\n * const { isEnabled, features } = useFeatures();\n * \n * if (!isEnabled('crm.deals.enabled')) {\n * return <UpgradePrompt feature=\"Deals\" />;\n * }\n * \n * return <DealsList />;\n * }\n * ```\n */\nexport function useFeatures() {\n const { features, isFeatureEnabled, refreshFeatures } = useHabeetat();\n \n return {\n /** All feature flags */\n features: features?.features ?? {},\n /** Feature source (plan, tenant, etc.) */\n source: features?.source,\n /** Plan code if source is plan */\n planCode: features?.planCode,\n /** Check if a feature is enabled */\n isEnabled: isFeatureEnabled,\n /** Refresh features from server */\n refresh: refreshFeatures,\n };\n}\n","import { useHabeetat } from './useHabeetat';\n\n/**\n * Hook for subscription and plan info\n */\nexport function useSubscription() {\n const { subscription, refreshSubscription } = useHabeetat();\n \n return {\n /** Current subscription */\n subscription,\n /** Current plan */\n plan: subscription?.plan,\n /** Plan limits */\n limits: subscription?.limits ?? {},\n /** Current usage */\n usage: subscription?.usage ?? {},\n /** Subscription status */\n status: subscription?.status,\n /** Check if subscription is active */\n isActive: subscription?.status === 'active' || subscription?.status === 'trialing',\n /** Check if in trial */\n isTrialing: subscription?.status === 'trialing',\n /** Check limit */\n checkLimit: (key: string, increment = 0): boolean => {\n const limit = subscription?.limits?.[key];\n const current = subscription?.usage?.[key] ?? 0;\n if (limit === undefined) return true;\n return current + increment <= limit;\n },\n /** Refresh subscription */\n refresh: refreshSubscription,\n };\n}\n","import { type ReactNode } from 'react';\nimport { usePermissions } from '../hooks/usePermissions';\n\ninterface RequirePermissionProps {\n permission: string;\n children: ReactNode;\n fallback?: ReactNode;\n}\n\n/**\n * Component that renders children only if user has the required permission\n */\nexport function RequirePermission({ permission, children, fallback = null }: RequirePermissionProps) {\n const { hasPermission } = usePermissions();\n \n if (!hasPermission(permission)) {\n return <>{fallback}</>;\n }\n \n return <>{children}</>;\n}\n","import { type ReactNode } from 'react';\nimport { useFeatures } from '../hooks/useFeatures';\n\ninterface RequireFeatureProps {\n flag: string;\n children: ReactNode;\n fallback?: ReactNode;\n}\n\n/**\n * Component that renders children only if the feature flag is enabled\n */\nexport function RequireFeature({ flag, children, fallback = null }: RequireFeatureProps) {\n const { isEnabled } = useFeatures();\n \n if (!isEnabled(flag)) {\n return <>{fallback}</>;\n }\n \n return <>{children}</>;\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@habeetat/sdk-react",
|
|
3
|
+
"version": "0.1.0-dev.20260323155801.0ae6298",
|
|
4
|
+
"description": "Habeetat Platform SDK for React applications",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup",
|
|
20
|
+
"dev": "tsup --watch",
|
|
21
|
+
"typecheck": "tsc --noEmit",
|
|
22
|
+
"clean": "rm -rf dist"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@habeetat/sdk-core": "0.1.0-dev.20260323155801.0ae6298"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"@logto/react": ">=3.0.0",
|
|
29
|
+
"react": ">=18.0.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/react": "^18.2.0",
|
|
33
|
+
"react": "^18.2.0",
|
|
34
|
+
"tsup": "^8.0.0",
|
|
35
|
+
"typescript": "^5.3.0"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "https://bitbucket.org/habeetat/nhp.git",
|
|
43
|
+
"directory": "packages/sdk-frontend"
|
|
44
|
+
},
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"keywords": [
|
|
47
|
+
"habeetat",
|
|
48
|
+
"sdk",
|
|
49
|
+
"react",
|
|
50
|
+
"frontend",
|
|
51
|
+
"platform"
|
|
52
|
+
]
|
|
53
|
+
}
|