nuqisukuk-widgets
v1.0.0
Published
Embeddable React / Next.js widgets for Nuqi Sukuk — browse Shariah-compliant sukuk and view instrument detail
Maintainers
Readme
nuqisukuk-widgets
Embeddable React / Next.js widgets for Nuqi Sukuk — drop a live catalog of Shariah-compliant sukuk and a full instrument detail panel into any host app in minutes.
| Widget | Feature key | API endpoint |
|---|---|---|
| <SukukList> | sukuk-list | GET /sdk/sukuk |
| <SukukDetails> | sukuk-details | GET /sdk/sukuk/:id |
Requirements
- React 18 or 19 (peer dependency — the host app provides it)
- Node ≥ 18
- An
apiKey+clientIdfrom the Nuqi Sukuk platform
Install
npm install nuqisukuk-widgetsQuick start
import {
NuqiSukukProvider,
SukukList,
SukukDetails,
} from 'nuqisukuk-widgets';
import 'nuqisukuk-widgets/styles.css'; // import once per app
function App() {
const [selectedId, setSelectedId] = useState('');
return (
<NuqiSukukProvider
apiKey="nsk_live_..."
clientId="client_..."
baseUrl="https://uat-api.nuqisukuk.com"
theme="emerald"
>
<SukukList onSelect={(s) => setSelectedId(s.id)} />
{selectedId && <SukukDetails sukukId={selectedId} />}
</NuqiSukukProvider>
);
}The provider calls GET /sdk/config once on mount. The response lists which feature keys are unlocked for the client. A widget whose key is missing renders a locked placeholder and never makes a data request.
API reference
<NuqiSukukProvider>
Wrap your widget tree with this once. All children share the same auth context.
| Prop | Type | Default | Description |
|---|---|---|---|
| apiKey | string | required | Publishable key — sent as x-api-key on every request |
| clientId | string | required | Client identifier — sent as x-client-id; selects the feature plan |
| userToken | string | — | Optional JWT for per-user feature gating — sent as Authorization: Bearer |
| baseUrl | string | https://uat-api.nuqisukuk.com | API origin |
| theme | ThemeProp | — | Preset name or ThemeConfig object (see Theming) |
| refreshInterval | number (ms) | 60000 | Auto-poll interval for data widgets; 0 disables polling |
| locale | string | en-US | BCP-47 locale for number and date formatting |
| onError | (err: NuqiSukukError) => void | — | Called on any network or config error from a child widget |
| className | string | — | Extra class on the provider wrapper <div> |
| style | React.CSSProperties | — | Extra inline styles on the provider wrapper |
<SukukList>
Catalog of available sukuk. Cards switch from a single-column to two-column grid based on the container width (≥ 560 px triggers two columns), so it re-flows inside any sidebar or column without breakpoint overrides.
| Prop | Type | Default | Description |
|---|---|---|---|
| title | string \| null | "Available Sukuk" | Widget heading; pass null to hide |
| limit | number | 20 | Maximum number of sukuk to request (capped server-side) |
| sector | string | — | Filter by sector |
| search | string | — | Free-text search across name and issuer |
| showComingSoon | boolean | true | Include sukuk flagged as coming soon |
| onSelect | (sukuk: SukukSummary) => void | — | Fired when a card is clicked; pass this to make cards interactive |
| theme | ThemeProp | — | Per-widget theme override (wins over the provider theme) |
| className | string | — | Extra class on the widget card |
| style | React.CSSProperties | — | Extra inline styles on the widget card |
Wiring list → detail:
const [id, setId] = useState('');
<SukukList onSelect={(s) => setId(s.id)} />
{id && <SukukDetails sukukId={id} />}<SukukDetails>
Full instrument detail for a single sukuk. Fetches /sdk/sukuk/:id and renders issuer info, profit & yield terms, pricing, and listing data. The four column groups collapse to 3 → 2 → 1 columns as the container narrows (breakpoints at 900 / 640 / 420 px).
| Prop | Type | Default | Description |
|---|---|---|---|
| sukukId | string | required | The id field from a SukukSummary |
| title | string \| null | "Sukuk Details" | Heading shown while data loads; replaced by the instrument name once loaded |
| theme | ThemeProp | — | Per-widget theme override |
| className | string | — | Extra class on the widget card |
| style | React.CSSProperties | — | Extra inline styles on the widget card |
Theming
theme accepts a preset name or a full ThemeConfig object. Both can be set on the provider (cascades to all widgets) or on an individual widget (overrides the provider theme for that widget only).
Presets
| Name | Look |
|---|---|
| emerald | Deep near-black surface with Shariah-green accent (default) |
| sand | Charcoal surface with warm gold accent |
| dark | Neutral dark, no green tint |
| light | White card, light-mode |
| midnight | Deep navy surface with mint accent |
<NuqiSukukProvider theme="midnight" ...>Custom ThemeConfig
Pass a partial object — every field is optional and falls back to the CSS defaults.
<NuqiSukukProvider
theme={{
accent: '#7c5cff',
accentText: '#ffffff',
profit: '#a78bfa',
up: '#4ade80',
down: '#f87171',
cardBg: 'rgba(17,14,33,0.94)',
cardBorder: 'rgba(124,92,255,0.18)',
cardRadius: '20px',
cardShadow: '0 12px 36px rgba(0,0,0,0.6)',
surface: 'rgba(255,255,255,0.045)',
textPrimary: '#f3f0ff',
textSecondary: '#c4b8ff',
textMuted: '#7c6fa8',
badgeBg: 'rgba(124,92,255,0.14)',
badgeText: '#b8a6ff',
}}
...
>ThemeConfig fields
| Field | CSS variable | Purpose |
|---|---|---|
| accent | --ns-accent | Active toggles, focus rings, links |
| accentText | --ns-accent-text | Text drawn on top of an accent fill |
| profit | --ns-profit | Profit-rate / yield highlight |
| up | --ns-up | Positive change colour |
| down | --ns-down | Negative change colour |
| cardBg | --ns-card-bg | Card background |
| cardBorder | --ns-card-border | Card border |
| cardRadius | --ns-card-radius | Card corner radius |
| cardShadow | --ns-card-shadow | Card box-shadow |
| surface | --ns-surface | Inner surface (skeleton blocks, list rows) |
| textPrimary | --ns-text-primary | Primary text |
| textSecondary | --ns-text-secondary | Secondary / supporting text |
| textMuted | --ns-text-muted | Muted / label text |
| badgeBg | --ns-badge-bg | Rating / sector badge background |
| badgeText | --ns-badge-text | Rating / sector badge text |
You can also set these CSS variables directly on any ancestor element if you prefer a pure-CSS theming approach.
Error handling
<NuqiSukukProvider
onError={(err) => {
console.error(err.code, err.message, err.feature);
// err.code: 'CONFIG_FETCH_FAILED' | 'NETWORK_ERROR' | 'FETCH_ERROR'
// err.feature: 'sukuk-list' | 'sukuk-details' | undefined
}}
...
>Widgets also show an inline Retry button when a data fetch fails so users can recover without reloading the page.
Feature gating
Features are unlocked per client on the backend. When GET /sdk/config is called, the response includes the features array. Any widget whose key is not in that list renders a locked placeholder instead of fetching data — no extra code needed on the host.
To gate other UI in the host app on the same feature set:
import { useFeature } from 'nuqisukuk-widgets';
function InvestButton() {
const canSeeSukuk = useFeature('sukuk-list');
if (!canSeeSukuk) return null;
return <button>Invest now</button>;
}To read the full context:
import { useNuqiSukuk } from 'nuqisukuk-widgets';
const { features, isReady, isLoading, locale } = useNuqiSukuk();Building a custom widget with useSdkFetch
import { useSdkFetch } from 'nuqisukuk-widgets';
function MySukukPriceBar({ sukukId }: { sukukId: string }) {
const { data, loading, error, refetch } = useSdkFetch<{ askPrice: number }>(
`/sdk/sukuk/${sukukId}/price`,
{ feature: 'sukuk-details', poll: true },
);
if (loading) return <Spinner />;
if (error) return <button onClick={refetch}>Retry</button>;
return <span>{data?.askPrice}</span>;
}useSdkFetch automatically:
- Attaches
x-api-key,x-client-id, andAuthorizationheaders from the provider context - Polls on the provider
refreshInterval - Aborts the in-flight request on unmount
- Surfaces errors through
onErrorand the returnederrorstring
| Option | Type | Default | Description |
|---|---|---|---|
| params | Record<string, string> | — | Query string params |
| feature | FeatureKey | — | Reported in onError.feature |
| skip | boolean | false | Skip the request (e.g. widget locked) |
| poll | boolean | true | Enable auto-polling on refreshInterval |
Next.js usage
The package is an ESM + CJS dual build with no "use client" directive — add it yourself in the file that imports the widgets:
// app/sukuk/page.tsx (App Router)
'use client';
import {
NuqiSukukProvider,
SukukList,
SukukDetails,
} from 'nuqisukuk-widgets';
import 'nuqisukuk-widgets/styles.css';For Pages Router nothing special is needed.
TypeScript types exported
// Provider / context
NuqiSukukProviderProps
NuqiSukukConfig
NuqiSukukContextValue
NuqiSukukError
SdkConfigResponse
FeatureKey // 'sukuk-list' | 'sukuk-details'
// Widgets
SukukListProps
SukukDetailsProps
// Data shapes
SukukSummary // lightweight — used in SukukList cards
SukukDetail // full detail — used in SukukDetails
// Theme
ThemeConfig
ThemeProp // string preset name | ThemeConfig
PresetThemeName // 'emerald' | 'sand' | 'dark' | 'light' | 'midnight'Preview app
frontend/ is a Vite dev app that renders both widgets against a local or UAT backend:
npm install && npm run build # build the package first
cd frontend && npm install && npm run devSet API_KEY, CLIENT_ID, and BASE_URL at the top of frontend/src/App.tsx.
