Package Exports
- @igamingcareer/igaming-components
Readme
iGaming Components
A reusable React component library for iGamingCareer that can be consumed from React apps or embedded in plain HTML via data attributes. The project is built with Vite and ships Tailwind-powered styles.
Local development
Install dependencies (Node 16+ is recommended):
npm install
Start the Vite dev server:
npm run devBuild the distributable assets:
npm run build # or generate the library bundle and type declarations npm run build:lib
Storybook
Storybook is configured with the Vite builder to explore components in isolation.
Start Storybook locally:
npm run storybookGenerate the static Storybook site:
npm run build-storybook
Using the library in React
Install the package from npm (or link the local build), then import the components and shared styles:
npm install @igamingcareer/igaming-componentsimport {
Button,
JobListings,
ConsentBanner,
HomePage,
} from "@igamingcareer/igaming-components";
import "@igamingcareer/igaming-components/styles/globals.css";
export function Example() {
return (
<div>
<Button dataName="Apply now" />
<JobListings />
<ConsentBanner />
<HomePage />
</div>
);
}Sign-in prompt modal for gated actions
Use the SignInPromptModal to nudge unauthenticated users to log in or create an account before performing actions such as
saving a job or opening a full job description. The parent app controls when the modal opens and owns the navigation/flows via
the emitted callbacks.
import { useState } from "react";
import { SignInPromptModal } from "@igamingcareer/igaming-components";
export function GatedSaveButton() {
const [open, setOpen] = useState(false);
const handleLogin = () => {
setOpen(false);
// trigger your app's login flow
};
const handleRegister = () => {
setOpen(false);
// trigger your app's register flow
};
return (
<>
<button onClick={() => setOpen(true)}>Save job</button>
<SignInPromptModal
open={open}
onClose={() => setOpen(false)}
onLogin={handleLogin}
onRegister={handleRegister}
appName="iGamingCareer.com"
attemptedAction="save this job"
helperContent={<p className="text-sm text-muted-foreground">Create an account to sync saves across devices.</p>}
/>
</>
);
}Embedding components in plain HTML
The library can hydrate elements that carry specific classes or data attributes. Build the project, host the dist/ output (or publish it to a CDN), and reference the emitted CSS/JS assets from your page.
Run
npm run buildand copy thedist/assets to your static host.Add the generated stylesheet and script tags from
dist/index.html(the file names include hashes). Example:<link rel="stylesheet" href="/assets/index-XXXX.css" /> <script type="module" src="/assets/index-XXXX.js"></script>
Mark up the page with the hooks expected by the components (see
src/main.tsxfor the full list). For example:<div id="app"></div> <div class="button-component" data-name="Join now"></div> <div class="modal-component" id="offer-modal"> <div class="title">Limited offer</div> <div class="description">Save 20% when you enroll today.</div> <div class="footer">Terms and conditions apply.</div> </div>
When the bundle runs it will automatically hydrate these elements:
#apprenders the root<App />component..button-componentrenders the<Button />component using thedata-nameattribute as the label..modal-componentrenders a<CustomModal />instance, reading nested.title,.description, and.footercontent.
Use additional class hooks such as
.group-prices-component,.hero-component,.courses-component,.chatbot-component,.faq-component,.videos-component,.testimonials-component, and.sliding-summary-componentto hydrate the corresponding widgets. Pass data throughdata-*attributes as illustrated insrc/main.tsx.
This approach allows teams that are not using React to embed the library’s UI on any HTML page while still benefiting from the React components under the hood.
Interaction events (Listing, Courses, News)
The Listing, IGamingCoursePage, and NewsPage components can now emit structured interaction events for analytics or logging. Supply an onEmitEvent callback and optional eventContext when you render the component (React or DOM embedding):
import { Listing } from "@igamingcareer/igaming-components"
import type { EmittedEvent } from "@igamingcareer/igaming-components"
const handleEvent = (event: EmittedEvent) => {
console.log("component event", event)
// Forward to your analytics/datastore
}
<Listing
apiUrl="/api/jobs"
filterKeys={["company", "city"]}
cardType="job"
onEmitEvent={handleEvent}
eventContext={{ route: "/jobs", source: "listing" }}
/>All emitted events share the base shape { domain, name, payload, timestamp, context } (see src/types/events.ts). Domain-specific unions live in src/types/events.jobs.ts, src/types/events.courses.ts, and src/types/events.news.ts.
Key events include:
- Jobs Listing: search submissions/removals, filter select/clear/all-clear, date changes, pagination, view mode, items per page, job open/save/apply, alerts/subscription steps.
- Courses Listing: search submissions, filter changes/clear, category/subcategory selection, view mode, pagination, items per page, favorites, enroll/open clicks.
- News Listing: search submissions/removals, filter select/clear/all-clear, date changes, category selection, pagination, view mode, items per page, article open/bookmark/share.
When embedding via renderMultiple (see src/main.tsx), a global window.IGC_EMIT_EVENT handler will be passed automatically if present, along with a default context using the current route and optional data-source attribute.
Job Listing pagination-friendly API responses
The <Listing /> component now understands paginated API payloads. Set apiProvidesPagination (or the data-api-pagination="true" attribute when embedding) to tell the component your endpoint returns a { jobs: [], pagination: { page, limit, total, total_pages } } envelope.
- The component requests page
1withlimitset to the initialitemsPerPagevalue, reads thepagination.total/total_pagesmetadata, and then sequentially loads every remaining page to build the fulljobs[]in memory. - While those pages are fetched, the UI stays in the loading state (using the first batch size for the skeleton), and once all pages are collected it serves the same client-side pagination experience as array-based responses.
- If the flag is omitted, the component assumes a plain array response and paginates client-side as before.
Example response shape:
{
"jobs": [/* job objects for the current page */],
"pagination": {
"page": 1,
"limit": 5,
"total": 2284,
"total_pages": 457
},
"metadata": {
"cache_hit": false
}
}If your API still returns a plain array, leave apiProvidesPagination unset and the component will paginate client-side as before.