@qumra/jisr 1.0.0 → 1.0.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.
Files changed (2) hide show
  1. package/README.md +205 -271
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,8 +1,21 @@
1
1
  # @qumra/jisr
2
2
 
3
- React hooks and utilities for communicating with the Qumra Admin from embedded apps running in an iframe.
3
+ [![npm version](https://img.shields.io/npm/v/@qumra/jisr.svg)](https://www.npmjs.com/package/@qumra/jisr)
4
+ [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)
4
5
 
5
- This package mirrors the functionality of Shopify's `@shopify/app-bridge-react` and provides a seamless integration layer for apps running within the Qumra Admin environment.
6
+ > App Bridge for Qumra embedded apps React hooks for communicating with the Qumra Admin.
7
+ >
8
+ > **جسر** (Jisr) = Bridge in Arabic
9
+
10
+ `@qumra/jisr` provides a set of React hooks and utilities for seamless communication between embedded applications (running in iframes) and the Qumra Admin interface via `postMessage`. Perfect for building extensible admin apps that need to interact with the parent Qumra environment.
11
+
12
+ ## Features
13
+
14
+ - 🌉 **iframe ↔ Admin Communication** — Transparent two-way messaging
15
+ - 🎣 **Custom React Hooks** — `useNavigate`, `useToast`, `useModal`, `useSaveBar`, and more
16
+ - 📘 **TypeScript Support** — Fully typed exports for better DX
17
+ - 🔐 **Authenticated Requests** — Built-in hook for fetch with auth headers
18
+ - ⚡ **Simple Setup** — Wrap your app with `QumraAppBridgeProvider`
6
19
 
7
20
  ## Installation
8
21
 
@@ -10,359 +23,249 @@ This package mirrors the functionality of Shopify's `@shopify/app-bridge-react`
10
23
  npm install @qumra/jisr react react-dom
11
24
  ```
12
25
 
13
- ## Quick Start
26
+ ### Peer Dependencies
14
27
 
15
- ### Setup the Provider
28
+ - `react` >= 18
29
+ - `react-dom` >= 18
16
30
 
17
- Wrap your application with `QumraAppBridgeProvider` at the root level:
31
+ ## Quick Start
32
+
33
+ ### 1. Wrap Your App with the Provider
18
34
 
19
- ```tsx
20
- import React from 'react';
35
+ ```jsx
21
36
  import { QumraAppBridgeProvider } from '@qumra/jisr';
22
- import { App } from './App';
23
37
 
24
- export default function Root() {
38
+ function App() {
25
39
  return (
26
- <QumraAppBridgeProvider config={{ apiKey: 'your-app-api-key' }}>
27
- <App />
40
+ <QumraAppBridgeProvider>
41
+ <YourAppComponent />
28
42
  </QumraAppBridgeProvider>
29
43
  );
30
44
  }
31
- ```
32
45
 
33
- ### Using Hooks in Your Components
46
+ export default App;
47
+ ```
34
48
 
35
- Once the provider is set up, you can use any of the available hooks in your components:
49
+ ### 2. Use Hooks in Your Components
36
50
 
37
- ```tsx
38
- import React from 'react';
39
- import {
40
- useNavigate,
41
- useToast,
42
- useSaveBar,
43
- } from '@qumra/jisr';
51
+ ```jsx
52
+ import { useNavigate, useToast } from '@qumra/jisr';
44
53
 
45
- export function ProductSettings() {
46
- const navigate = useNavigate();
47
- const toast = useToast();
48
- const saveBar = useSaveBar();
49
- const [hasChanges, setHasChanges] = React.useState(false);
50
-
51
- const handleChange = () => {
52
- setHasChanges(true);
53
- saveBar.show({
54
- saveLabel: 'Save Changes',
55
- discardLabel: 'Discard',
56
- });
57
- };
54
+ function MyComponent() {
55
+ const { navigate } = useNavigate();
56
+ const { showToast } = useToast();
58
57
 
59
- const handleSave = async () => {
60
- try {
61
- toast.show({ message: 'Saving...' });
62
- // Save your data here
63
- await new Promise(resolve => setTimeout(resolve, 1000));
64
- toast.show({ message: 'Saved successfully!' });
65
- saveBar.hide();
66
- setHasChanges(false);
67
- } catch (error) {
68
- toast.show({
69
- message: 'Error saving changes',
70
- isError: true,
71
- });
72
- }
58
+ const handleClick = () => {
59
+ showToast('Hello from embedded app!', 'success');
60
+ navigate('/dashboard');
73
61
  };
74
62
 
75
- return (
76
- <div>
77
- <input
78
- type="text"
79
- placeholder="Product name"
80
- onChange={handleChange}
81
- />
82
- <button onClick={handleSave}>Save</button>
83
- <button onClick={() => navigate('/products')}>
84
- Back to Products
85
- </button>
86
- </div>
87
- );
63
+ return <button onClick={handleClick}>Navigate & Toast</button>;
88
64
  }
89
65
  ```
90
66
 
91
- ## Available Hooks
67
+ ## Hooks Reference
92
68
 
93
- ### useAppBridge()
69
+ ### `useAppBridge`
94
70
 
95
- Get direct access to the AppBridge instance for advanced use cases:
71
+ Access the core `AppBridge` instance directly for advanced use cases.
96
72
 
97
- ```tsx
73
+ ```jsx
98
74
  import { useAppBridge } from '@qumra/jisr';
99
75
 
100
- export function MyComponent() {
76
+ function AdvancedComponent() {
101
77
  const bridge = useAppBridge();
102
78
 
103
79
  const handleCustomAction = () => {
104
- bridge.dispatch({
105
- type: 'APP::CUSTOM_ACTION',
106
- payload: { /* your data */ },
107
- });
108
-
109
- // Subscribe to events
110
- const unsubscribe = bridge.subscribe('HOST::RESPONSE', (data) => {
111
- console.log('Received:', data);
112
- });
113
-
114
- return () => unsubscribe();
80
+ bridge.dispatch({ type: 'CUSTOM_ACTION', payload: { /* ... */ } });
115
81
  };
116
82
 
117
- return <button onClick={handleCustomAction}>Custom Action</button>;
83
+ return <button onClick={handleCustomAction}>Send Custom Action</button>;
118
84
  }
119
85
  ```
120
86
 
121
- ### useNavigate()
87
+ ### `useNavigate`
122
88
 
123
- Navigate within the Qumra Admin:
89
+ Navigate within the Qumra Admin interface.
124
90
 
125
- ```tsx
91
+ ```jsx
126
92
  import { useNavigate } from '@qumra/jisr';
127
93
 
128
- export function NavigationComponent() {
129
- const navigate = useNavigate();
94
+ function NavigationComponent() {
95
+ const { navigate } = useNavigate();
130
96
 
131
97
  return (
132
- <div>
133
- <button onClick={() => navigate('/products')}>
134
- View Products
135
- </button>
136
- <button
137
- onClick={() => navigate('https://example.com', {
138
- external: true,
139
- })}
140
- >
141
- Open External Site
142
- </button>
143
- </div>
98
+ <>
99
+ <button onClick={() => navigate('/dashboard')}>Dashboard</button>
100
+ <button onClick={() => navigate('/settings')}>Settings</button>
101
+ </>
144
102
  );
145
103
  }
146
104
  ```
147
105
 
148
- ### useToast()
106
+ ### `useToast`
149
107
 
150
- Show and hide toast notifications:
108
+ Display toast notifications in the Qumra Admin UI.
151
109
 
152
- ```tsx
110
+ ```jsx
153
111
  import { useToast } from '@qumra/jisr';
154
112
 
155
- export function ToastExample() {
156
- const toast = useToast();
113
+ function ToastComponent() {
114
+ const { showToast, hideToast } = useToast();
115
+
116
+ const handleSuccess = () => {
117
+ showToast('Operation successful!', 'success');
118
+ };
119
+
120
+ const handleError = () => {
121
+ showToast('Something went wrong.', 'error');
122
+ };
157
123
 
158
124
  return (
159
- <div>
160
- <button
161
- onClick={() =>
162
- toast.show({
163
- message: 'Operation completed successfully!',
164
- duration: 3000,
165
- })
166
- }
167
- >
168
- Show Success Toast
169
- </button>
170
- <button
171
- onClick={() =>
172
- toast.show({
173
- message: 'An error occurred',
174
- isError: true,
175
- duration: 5000,
176
- })
177
- }
178
- >
179
- Show Error Toast
180
- </button>
181
- <button onClick={() => toast.hide()}>
182
- Hide Toast
183
- </button>
184
- </div>
125
+ <>
126
+ <button onClick={handleSuccess}>Show Success</button>
127
+ <button onClick={handleError}>Show Error</button>
128
+ </>
185
129
  );
186
130
  }
187
131
  ```
188
132
 
189
- ### useModal()
133
+ ### `useModal`
190
134
 
191
- Open and close modal dialogs:
135
+ Open and manage modal dialogs.
192
136
 
193
- ```tsx
137
+ ```jsx
194
138
  import { useModal } from '@qumra/jisr';
195
139
 
196
- export function ModalExample() {
197
- const modal = useModal();
198
-
199
- const handleOpenSettings = () => {
200
- modal.open({
201
- title: 'App Settings',
202
- url: '/settings',
203
- size: 'large',
204
- primaryAction: {
205
- content: 'Save',
206
- onAction: () => {
207
- console.log('Save clicked');
208
- modal.close();
209
- },
210
- },
211
- secondaryActions: [
212
- {
213
- content: 'Cancel',
214
- onAction: () => {
215
- modal.close();
216
- },
217
- },
218
- ],
140
+ function ModalComponent() {
141
+ const { openModal, closeModal } = useModal();
142
+
143
+ const handleOpenModal = () => {
144
+ openModal({
145
+ title: 'Confirm Action',
146
+ content: 'Are you sure you want to proceed?',
219
147
  });
220
148
  };
221
149
 
222
- return <button onClick={handleOpenSettings}>Open Settings</button>;
150
+ return (
151
+ <>
152
+ <button onClick={handleOpenModal}>Open Modal</button>
153
+ <button onClick={closeModal}>Close Modal</button>
154
+ </>
155
+ );
223
156
  }
224
157
  ```
225
158
 
226
- ### useSaveBar()
159
+ ### `useSaveBar`
227
160
 
228
- Show a save bar for unsaved changes:
161
+ Show/hide the save bar for form changes.
229
162
 
230
- ```tsx
163
+ ```jsx
231
164
  import { useSaveBar } from '@qumra/jisr';
165
+ import { useState } from 'react';
232
166
 
233
- export function FormWithSaveBar() {
234
- const saveBar = useSaveBar();
235
- const [isDirty, setIsDirty] = React.useState(false);
167
+ function FormComponent() {
168
+ const { showSaveBar, hideSaveBar } = useSaveBar();
169
+ const [isDirty, setIsDirty] = useState(false);
236
170
 
237
- const handleChange = () => {
238
- if (!isDirty) {
239
- setIsDirty(true);
240
- saveBar.show();
241
- }
171
+ const handleFieldChange = (e) => {
172
+ setIsDirty(true);
173
+ showSaveBar();
242
174
  };
243
175
 
244
176
  const handleSave = async () => {
245
- await saveData();
177
+ // Save logic
178
+ hideSaveBar();
246
179
  setIsDirty(false);
247
- saveBar.hide();
248
180
  };
249
181
 
250
182
  return (
251
183
  <form>
252
- <input onChange={handleChange} />
253
- <button onClick={handleSave}>Save</button>
184
+ <input onChange={handleFieldChange} />
185
+ {isDirty && <button onClick={handleSave}>Save</button>}
254
186
  </form>
255
187
  );
256
188
  }
257
189
  ```
258
190
 
259
- ### useTitleBar()
191
+ ### `useTitleBar`
260
192
 
261
- Update the title bar:
193
+ Update the page title in the Qumra Admin.
262
194
 
263
- ```tsx
195
+ ```jsx
264
196
  import { useTitleBar } from '@qumra/jisr';
197
+ import { useEffect } from 'react';
265
198
 
266
- export function PageWithTitle() {
267
- const titleBar = useTitleBar();
268
-
269
- React.useEffect(() => {
270
- titleBar.update({
271
- title: 'Products',
272
- subtitle: 'Manage your store products',
273
- breadcrumbs: [
274
- { content: 'Admin', url: '/' },
275
- { content: 'Products' },
276
- ],
277
- });
278
- }, [titleBar]);
199
+ function PageComponent() {
200
+ const { updateTitleBar } = useTitleBar();
279
201
 
280
- return <div>Products page content</div>;
202
+ useEffect(() => {
203
+ updateTitleBar('My Page Title');
204
+ }, [updateTitleBar]);
205
+
206
+ return <div>Page content</div>;
281
207
  }
282
208
  ```
283
209
 
284
- ### useFullscreen()
210
+ ### `useFullscreen`
285
211
 
286
- Enter and exit fullscreen mode:
212
+ Enter and exit fullscreen mode.
287
213
 
288
- ```tsx
214
+ ```jsx
289
215
  import { useFullscreen } from '@qumra/jisr';
290
216
 
291
- export function FullscreenComponent() {
292
- const fullscreen = useFullscreen();
217
+ function FullscreenComponent() {
218
+ const { enterFullscreen, exitFullscreen } = useFullscreen();
293
219
 
294
220
  return (
295
- <div>
296
- <button onClick={() => fullscreen.enter()}>
297
- Enter Fullscreen
298
- </button>
299
- <button onClick={() => fullscreen.exit()}>
300
- Exit Fullscreen
301
- </button>
302
- </div>
221
+ <>
222
+ <button onClick={enterFullscreen}>Go Fullscreen</button>
223
+ <button onClick={exitFullscreen}>Exit Fullscreen</button>
224
+ </>
303
225
  );
304
226
  }
305
227
  ```
306
228
 
307
- ### useAuthenticatedFetch()
229
+ ### `useAuthenticatedFetch`
308
230
 
309
- Make authenticated API requests with automatic session token injection:
231
+ Make authenticated requests to the Qumra API with automatic auth headers.
310
232
 
311
- ```tsx
233
+ ```jsx
312
234
  import { useAuthenticatedFetch } from '@qumra/jisr';
235
+ import { useEffect, useState } from 'react';
236
+
237
+ function DataComponent() {
238
+ const { fetch: authenticatedFetch } = useAuthenticatedFetch();
239
+ const [data, setData] = useState(null);
240
+ const [loading, setLoading] = useState(false);
241
+
242
+ useEffect(() => {
243
+ const loadData = async () => {
244
+ setLoading(true);
245
+ try {
246
+ const response = await authenticatedFetch('/api/data');
247
+ const json = await response.json();
248
+ setData(json);
249
+ } catch (error) {
250
+ console.error('Fetch failed:', error);
251
+ } finally {
252
+ setLoading(false);
253
+ }
254
+ };
313
255
 
314
- export function DataFetcher() {
315
- const authenticatedFetch = useAuthenticatedFetch();
316
- const [data, setData] = React.useState(null);
317
-
318
- const loadData = async () => {
319
- try {
320
- const response = await authenticatedFetch('/api/products');
321
- const result = await response.json();
322
- setData(result);
323
- } catch (error) {
324
- console.error('Error fetching data:', error);
325
- }
326
- };
327
-
328
- React.useEffect(() => {
329
256
  loadData();
330
- }, []);
257
+ }, [authenticatedFetch]);
331
258
 
332
- return <div>{data ? JSON.stringify(data) : 'Loading...'}</div>;
259
+ if (loading) return <div>Loading...</div>;
260
+ return <pre>{JSON.stringify(data, null, 2)}</pre>;
333
261
  }
334
262
  ```
335
263
 
336
- ## API Reference
337
-
338
- ### ActionType
339
-
340
- Constants for all available action types:
341
-
342
- ```typescript
343
- import { ActionType } from '@qumra/jisr';
344
-
345
- const actions = {
346
- NAVIGATE: ActionType.NAVIGATE,
347
- TOAST_SHOW: ActionType.TOAST_SHOW,
348
- TOAST_HIDE: ActionType.TOAST_HIDE,
349
- MODAL_OPEN: ActionType.MODAL_OPEN,
350
- MODAL_CLOSE: ActionType.MODAL_CLOSE,
351
- SAVE_BAR_SHOW: ActionType.SAVE_BAR_SHOW,
352
- SAVE_BAR_HIDE: ActionType.SAVE_BAR_HIDE,
353
- TITLE_BAR_UPDATE: ActionType.TITLE_BAR_UPDATE,
354
- LOADING_START: ActionType.LOADING_START,
355
- LOADING_STOP: ActionType.LOADING_STOP,
356
- FULLSCREEN_ENTER: ActionType.FULLSCREEN_ENTER,
357
- FULLSCREEN_EXIT: ActionType.FULLSCREEN_EXIT,
358
- };
359
- ```
360
-
361
- ### Action Creator Functions
264
+ ## Action Creators
362
265
 
363
- Helper functions to create properly formatted actions:
266
+ For advanced usage, dispatch actions directly via the `AppBridge`:
364
267
 
365
- ```typescript
268
+ ```jsx
366
269
  import {
367
270
  navigate,
368
271
  showToast,
@@ -378,53 +281,84 @@ import {
378
281
  exitFullscreen,
379
282
  } from '@qumra/jisr';
380
283
 
381
- // Use with useAppBridge()
382
284
  const bridge = useAppBridge();
383
- bridge.dispatch(navigate('/products'));
384
- bridge.dispatch(showToast('Success!'));
285
+
286
+ // Navigate
287
+ bridge.dispatch(navigate('/path'));
288
+
289
+ // Toast notifications
290
+ bridge.dispatch(showToast('Message', 'success'));
291
+ bridge.dispatch(hideToast());
292
+
293
+ // Modals
294
+ bridge.dispatch(openModal({ title: 'Title', content: 'Content' }));
295
+ bridge.dispatch(closeModal());
296
+
297
+ // Save bar
298
+ bridge.dispatch(showSaveBar());
299
+ bridge.dispatch(hideSaveBar());
300
+
301
+ // Title bar
302
+ bridge.dispatch(updateTitleBar('New Title'));
303
+
304
+ // Loading state
305
+ bridge.dispatch(startLoading());
306
+ bridge.dispatch(stopLoading());
307
+
308
+ // Fullscreen
309
+ bridge.dispatch(enterFullscreen());
310
+ bridge.dispatch(exitFullscreen());
385
311
  ```
386
312
 
387
- ## Type Definitions
313
+ ## TypeScript
388
314
 
389
- All types are exported for TypeScript projects:
315
+ All types are exported from the main package:
390
316
 
391
317
  ```typescript
392
- import type {
318
+ import {
319
+ AppBridge,
393
320
  AppBridgeConfig,
394
321
  AppBridgeAction,
395
322
  AppBridgeState,
396
323
  AppContext,
397
- NavigateOptions,
398
- ToastOptions,
399
- ModalOptions,
400
- SaveBarOptions,
401
- TitleBarOptions,
324
+ ActionType,
402
325
  UseToastResult,
403
326
  UseModalResult,
327
+ UseNavigateResult,
404
328
  UseSaveBarResult,
405
- UseFullscreenResult,
406
329
  UseTitleBarResult,
330
+ UseFullscreenResult,
331
+ UseAuthenticatedFetchResult,
407
332
  } from '@qumra/jisr';
408
333
  ```
409
334
 
410
- ## Error Handling
335
+ ### Example Type Usage
411
336
 
412
- All hooks require the `QumraAppBridgeProvider` to be set up. Using them outside the provider will throw an error:
337
+ ```typescript
338
+ import { AppBridgeAction, ActionType } from '@qumra/jisr';
413
339
 
414
- ```
415
- Error: useAppBridge must be used within a QumraAppBridgeProvider
340
+ const myAction: AppBridgeAction = {
341
+ type: ActionType.NAVIGATE,
342
+ payload: { path: '/dashboard' },
343
+ };
416
344
  ```
417
345
 
418
- Make sure your component is wrapped with the provider or is a child of a component that is wrapped.
346
+ ## Qumra Ecosystem
419
347
 
420
- ## Best Practices
421
-
422
- 1. **Always set up the provider at the root level** of your application
423
- 2. **Use the specific hooks** instead of `useAppBridge()` for most use cases
424
- 3. **Handle errors gracefully** when making API calls
425
- 4. **Clean up subscriptions** if you use direct subscriptions via `useAppBridge()`
426
- 5. **Use TypeScript** for better type safety and IDE autocomplete
348
+ | Package | Description |
349
+ |---------|-------------|
350
+ | [@qumra/jisr](https://www.npmjs.com/package/@qumra/jisr) | React hooks for app bridge communication |
351
+ | [@qumra/config](https://www.npmjs.com/package/@qumra/config) | Centralized config management for Qumra apps |
352
+ | [@qumra/logger](https://www.npmjs.com/package/@qumra/logger) | Structured logging utility |
353
+ | [@qumra/ui](https://www.npmjs.com/package/@qumra/ui) | Reusable React component library |
354
+ | [@qumra/api-client](https://www.npmjs.com/package/@qumra/api-client) | API client for Qumra services |
355
+ | [@qumra/types](https://www.npmjs.com/package/@qumra/types) | Shared TypeScript type definitions |
356
+ | [@qumra/utils](https://www.npmjs.com/package/@qumra/utils) | Common utility functions |
427
357
 
428
358
  ## License
429
359
 
430
- ISC
360
+ ISC © [Nicetag](https://github.com/nicetag)
361
+
362
+ ## Repository
363
+
364
+ [github.com/nicetag/qumra-apps-sdk](https://github.com/nicetag/qumra-apps-sdk)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qumra/jisr",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "React hooks and utilities for communicating with the Qumra Admin from embedded apps — navigation, toasts, modals, and more",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",