@igamingcareer/igaming-components
v1.0.125
Published
Reusable React component library for the iGamingCareer platform
Maintainers
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 installStart 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
Component preview hub in index.html
To avoid commenting/uncommenting mount nodes, index.html now includes a preview selector (#component-selector) that shows one preview component at a time.
How it works
- The selector writes the selected component into the URL query param:
?component=<mount-id>. index.htmlkeeps all preview mount nodes in the DOM and toggles visibility so only the selected one is visible.src/main.tsxreads the same query param and only mounts the selected preview component withshouldRenderPreviewComponent(...).
Add a new preview component
When you create a new preview (example: dashboard-component), you only need to add the mount node.
main.tsx now auto-registers previews from data-preview-item.
Add the mount node in
index.html<div id="dashboard-component" data-preview-item="true" data-preview-label="Dashboard component"></div>(Optional) Provide a custom label
- If
data-preview-labelis present, that text is used in the selector and generated nav link. - If omitted, the label is generated from the id (
dashboard-component→Dashboard Component).
- If
Done
- The selector options are generated automatically.
- Preview links are auto-generated into
.navbar-component[data-preview-links="true"]with:class="navbar-link"data-text="<label>"data-url="/<component-id>"
No manual option editing is required anymore.
Useful URLs
http://localhost:5173/?component=dashboard-componenthttp://localhost:5173/?component=onboarding-demo
This lets you deep-link directly to a specific preview while keeping all mount nodes available in one page.
In-page preview logs (no DevTools required)
If the viewport shrinks below desktop, the logger is hidden automatically. The preview page includes a Show logs button (bottom-right).
Click Show logs to open an in-page console panel.
The panel mirrors
console.log,console.info,console.warn, andconsole.errorevents.Click Hide logs to collapse it, or Clear to reset entries.
This is useful for QA/demo sessions where you want to monitor runtime events without opening browser DevTools.
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>
);
}Login (remember me identifier)
The Login and LoginForm components support a real “Remember me” experience by persisting the identifier (email/username)
separately from the checkbox state. When the user opts in, the identifier is stored in localStorage, and on the next visit the
identifier field is prefilled and focus moves to the password input. Passwords are never persisted.
import { Login } from "@igamingcareer/igaming-components";
import "@igamingcareer/igaming-components/styles/globals.css";
export function LoginScreen() {
return (
<Login
onSubmit={(identifier, password, rememberMe) => {
// send credentials to your API
}}
rememberIdentifierPersistKey="igaming-auth-identifier"
defaultPrefilledIdentifier="[email protected]"
/>
);
}Behavior details
- The checkbox state is persisted via
rememberMeStorageKey(default:"igaming-auth-remember-me"). - When
rememberMeis true on submit, the identifier is stored atrememberIdentifierPersistKey. IfrememberMeis false, the identifier is removed. - On mount, if
rememberMeis true and a stored identifier exists, the identifier is prefilled and the password input is focused.
Notifications (global notification system)
The notification system provides a provider, hook, and global helpers to trigger messages from anywhere in your app.
Setup
Wrap your application with the provider once (typically in your app/layout/root component):
import {
NotificationProvider,
} from "@igamingcareer/igaming-components";
import "@igamingcareer/igaming-components/styles/globals.css";
export function AppRoot({ children }: { children: React.ReactNode }) {
return (
<NotificationProvider placement="top-right">
{children}
</NotificationProvider>
);
}Trigger notifications with the hook
import { useNotification } from "@igamingcareer/igaming-components";
export function ProfileSaveButton() {
const { notify } = useNotification();
return (
<button
onClick={() =>
notify({
type: "success",
title: "Profile updated",
message: "Your changes were saved successfully.",
})
}
>
Save
</button>
);
}Trigger notifications globally
import { notify } from "@igamingcareer/igaming-components";
export function handleLoginError() {
notify({
type: "error",
title: "Login failed",
message: "We couldn't sign you in. Please try again.",
actions: [
{
label: "Retry",
variant: "primary",
onClick: () => console.log("retry login"),
},
],
});
}Use the NotificationTester in a consumer app
The NotificationTester is a ready-made panel for manually firing notifications. Mount it anywhere in your app (or inside a dedicated demo page) and wrap it with NotificationProvider.
import React from "react";
import ReactDOM from "react-dom/client";
import {
NotificationProvider,
NotificationTester,
} from "@igamingcareer/igaming-components";
import "@igamingcareer/igaming-components/styles/globals.css";
const notificationTesterElement = document.getElementById("notification-tester");
if (notificationTesterElement) {
ReactDOM.createRoot(notificationTesterElement).render(
<NotificationProvider placement="top-right">
<NotificationTester />
</NotificationProvider>
);
}If you are still using the legacy React 17 API, the equivalent is:
import React from "react";
import ReactDOM from "react-dom";
import {
NotificationProvider,
NotificationTester,
} from "@igamingcareer/igaming-components";
import "@igamingcareer/igaming-components/styles/globals.css";
const notificationTesterElement = document.getElementById("notification-tester");
if (notificationTesterElement) {
ReactDOM.render(
<NotificationProvider placement="top-right">
<NotificationTester />
</NotificationProvider>,
notificationTesterElement
);
}Configuration options
| Option | Type | Description |
| --- | --- | --- |
| placement | NotificationPlacement | Controls where the stack renders (top-right, bottom-left, center, etc.). |
| duration | number | Auto-dismiss time in ms (0 or Infinity disables). |
| persistent | boolean | Keeps the message open until dismissed. |
| actions | NotificationAction[] | Buttons/links with callbacks (e.g., Retry). |
| content | ReactNode | Custom body content for advanced layouts. |
| containerClassName | string | Customize the stack container styling. |
Recommended follow-up tasks
Create sub-issues for the notification epic so teams can track separate scopes:
- Design exploration (visual styles and variants).
- Implementation checklists (API ergonomics, accessibility, animation polish).
- Documentation expansion (Storybook examples + README).
- Next.js usage examples (app router and pages router snippets).
Follow company callbacks (CompanyCard + CompanyDetail)
The company list/detail components are stateless and emit follow actions so your app can handle API calls. Use the follow props to control state and provide callbacks for follow/unfollow actions.
import { useState } from "react";
import {
CompanyCard,
CompanyDetail,
type CompanyFollowActionPayload,
} from "@igamingcareer/igaming-components";
export function FollowCompaniesExample({ company }: { company: Company }) {
const [isFollowing, setIsFollowing] = useState(false);
const [followLoading, setFollowLoading] = useState(false);
const handleToggleFollow = async (companyId: string, nextIsFollowing: boolean) => {
setFollowLoading(true);
try {
// call your API here
// await api.followCompany({ companyId, follow: nextIsFollowing });
setIsFollowing(nextIsFollowing);
} finally {
setFollowLoading(false);
}
};
const handleFollowAction = (payload: CompanyFollowActionPayload) => {
// optional: analytics or logging
console.log("follow action", payload);
};
return (
<>
<CompanyCard
company={{ ...company, isFollowing }}
isFollowing={isFollowing}
followLoading={followLoading}
onToggleFollow={handleToggleFollow}
onFollowAction={handleFollowAction}
/>
<CompanyDetail
company={{ ...company, isFollowing }}
isFollowing={isFollowing}
followLoading={followLoading}
onToggleFollow={handleToggleFollow}
onFollowAction={handleFollowAction}
/>
</>
);
}Company admin shell layout
The CompanyAdminShell component now renders the admin tabs internally. It handles tab routing, renders the built-in admin views, and surfaces update events so the parent can persist changes.
Permission system
Permissions decide whether a tab is visible and whether fields render as editable inputs or read-only text. Use a boolean per section:
export type CompanyAdminPermissions = {
canViewOverview?: boolean;
canManageProducts?: boolean;
canManagePeople?: boolean;
canManageJobs?: boolean;
canManageEvents?: boolean;
canManageNews?: boolean;
canManageSettings?: boolean;
};Update events
Each admin section emits update events so the parent can save to the backend and update local state.
import type { CompanyAdminUpdateEvent } from "@igamingcareer/igaming-components";
const handleUpdate = async (event: CompanyAdminUpdateEvent) => {
console.log("update event", event);
// call backend API
// update local state based on event.type
};Example usage
import { useState } from "react";
import {
CompanyAdminShell,
type AdminTab,
type CompanyAdminPermissions,
type CompanyAdminUpdateEvent,
} from "@igamingcareer/igaming-components";
export function CompanyAdminExample({ company }: { company: Company }) {
const [activeTab, setActiveTab] = useState<AdminTab>("overview");
const [isSaving, setIsSaving] = useState(false);
const permissions: CompanyAdminPermissions = {
canViewOverview: true,
canManageProducts: true,
canManagePeople: false,
canManageJobs: true,
canManageEvents: true,
canManageNews: false,
canManageSettings: true,
};
const handleUpdate = async (event: CompanyAdminUpdateEvent) => {
setIsSaving(true);
try {
// call backend API
console.log("update", event);
} finally {
setIsSaving(false);
}
};
return (
<CompanyAdminShell
company={company}
role="admin"
permissions={permissions}
activeTab={activeTab}
onTabChange={setActiveTab}
onUpdate={handleUpdate}
isSaving={isSaving}
/>
);
}CompanyAdminShell props
| Prop | Type | Description |
| --- | --- | --- |
| company | Company | Full company object used for header and admin sections. |
| companyName | string | Optional override for the display name shown in the header. |
| companySlug | string | Optional override for the slug used in the header. |
| activeTab | AdminTab | Current active tab key (controlled). |
| visibleTabs | AdminTab[] | Tabs that should appear in the admin UI (optional override). |
| onTabChange | (tab: AdminTab) => void | Called when a user switches tabs. |
| role | CompanyAdminRole | Role label shown in the header (defaults to admin). |
| permissions | CompanyAdminPermissions | Permission map for tabs and editability. |
| companyMeta | CompanyAdminCompanyMeta | Optional metadata for the header summary. |
| summaryStats | CompanyAdminSummaryStat[] | Optional summary stats for the header (derived from company when omitted). |
| onUpdate | (event: CompanyAdminUpdateEvent) => void \| Promise<void> | Optional update callback for child sections. |
| isSaving | boolean | Optional saving state for buttons. |
Backend integration guide
- Listen for
CompanyAdminUpdateEventfrom the active admin tab. - Call your backend endpoint for that section.
- Merge the returned data into your local
companystate. - Provide the updated
companyback to the admin components.
Component patterns
- Each admin section owns local
formStateand emits a full payload viaonUpdate. - When
canEditisfalse, fields render as read-only text and edit controls are hidden. - Use
isSavingto lock save buttons and show spinners.
Best practices
- Use
onUpdateto connect each tab to your backend and keepcompanystate in sync. - Use separate state slices for admin-only data (e.g., settings) if it is not part of the core company API.
- Guard any optional permissions with defaults so read-only views remain safe.
Troubleshooting
- Nothing renders: ensure the active tab is included in
visibleTabsand has permission enabled. - Save button disabled: verify
isSavingisfalseandcanEditistrue. - Updates not persisting: confirm your parent handler merges the payload back into company state.
Dashboard tab routing events
The Dashboard component can emit a routing-friendly event whenever a tab is selected. Use it to keep your app router in sync (for example, pushing routes in Next.js). You can also pass an activeTab prop to control the active tab from outside, along with a tabPaths map so your app remains the source of truth for URL formats.
import { useState } from "react";
import {
Dashboard,
type DashboardTab,
type DashboardTabChangeEvent,
} from "@igamingcareer/igaming-components";
export function DashboardWithRouter({ data }: { data: DashboardData }) {
const [activeTab, setActiveTab] = useState<DashboardTab>("overview");
const handleTabChange = (event: DashboardTabChangeEvent) => {
setActiveTab(event.tab);
// Example: Next.js router
// router.push(event.path ?? "/dashboard");
};
return (
<Dashboard
user={data.user}
profileData={data.profile}
alerts={data.alerts}
snapshot={data.snapshot}
feedItems={data.feedItems}
onUpdateProfile={data.onUpdateProfile}
activeTab={activeTab}
tabPaths={{
overview: "/dashboard",
profile: "/dashboard/profile",
jobs: "/dashboard/jobs",
courses: "/dashboard/courses",
news: "/dashboard/news",
settings: "/dashboard/settings",
}}
onTabChange={handleTabChange}
/>
);
}Company detail tab routing events
The CompanyDetail component can emit a routing-friendly event whenever a tab is selected, so your Next.js app remains the source of truth for URLs. Provide activeTab, tabPaths, and onTabChange to keep routing and UI in sync.
import { useState } from "react";
import {
CompanyDetail,
type CompanyDetailTab,
type CompanyDetailTabChangeEvent,
} from "@igamingcareer/igaming-components";
export function CompanyDetailWithRouter({ data }: { data: CompanyDetailData }) {
const [activeTab, setActiveTab] = useState<CompanyDetailTab>("overview");
const handleTabChange = (event: CompanyDetailTabChangeEvent) => {
setActiveTab(event.tab);
// Example: Next.js router
// router.push(event.path ?? `/companies/${data.company.slug}`);
};
return (
<CompanyDetail
company={data.company}
jobs={data.jobs}
news={data.news}
events={data.events}
activeTab={activeTab}
tabPaths={{
overview: `/companies/${data.company.slug}`,
products: `/companies/${data.company.slug}/products`,
trust: `/companies/${data.company.slug}/trust`,
people: `/companies/${data.company.slug}/people`,
jobs: `/companies/${data.company.slug}/jobs`,
news: `/companies/${data.company.slug}/news`,
events: `/companies/${data.company.slug}/events`,
insights: `/companies/${data.company.slug}/insights`,
}}
onTabChange={handleTabChange}
/>
);
}Onboarding flow (Dashboard + OnboardingPopup)
The new Onboarding folder includes components and helpers for managing the full onboarding lifecycle:
- Dashboard onboarding preview (inside
DashboardOverview)- Uses
DashboardOnboardingSectionto show a compact, actionable checklist. - Data is driven by
OnboardingStep[],emailVerified,profileComplete, andonboardingProgress.
- Uses
- Full onboarding modal
OnboardingPopuprendersOnboardingModal+OnboardingDashboardand manages state viaOnboardingProvider.- Use it for a blocking or reminder-style onboarding flow.
Key backend flags
Fetch the flags from your backend (for example /api/auth/me) and map them into steps using mapBackendFlagsToSteps:
export interface OnboardingBackendFlags {
email_verified: boolean;
email_verified_at?: string | null;
profile_complete: boolean;
onboarding_steps: OnboardingStepId[];
}These flags define the onboarding state:
- Registration started →
email_verified = false,profile_complete = false. - Email verified →
email_verified = true,profile_complete = false(profile step becomespending). - Onboarding completed →
email_verified = true,profile_complete = true(all stepscomplete).
Dashboard integration (Next.js)
Pass onboarding data into Dashboard so the overview can render the compact onboarding section. Use the helper
functions to keep the UI in sync with your backend.
import {
Dashboard,
type OnboardingBackendFlags,
mapBackendFlagsToSteps,
calculateOnboardingProgress,
} from "@igamingcareer/igaming-components";
export function DashboardPage({ data }: { data: DashboardData }) {
const flags: OnboardingBackendFlags = data.onboardingFlags;
const steps = mapBackendFlagsToSteps(flags);
const progress = calculateOnboardingProgress(steps);
const handleOnboardingAction = (actionId: string, stepId: string) => {
// actionId values: "resend_verification" | "edit_profile" | custom
// Your routing or API calls here (Next.js router, toast, etc.)
console.log("onboarding action", actionId, stepId);
};
return (
<Dashboard
user={data.user}
profileData={data.profile}
alerts={data.alerts}
snapshot={data.snapshot}
feedItems={data.feedItems}
onUpdateProfile={data.onUpdateProfile}
onboardingSteps={steps}
emailVerified={flags.email_verified}
profileComplete={flags.profile_complete}
onboardingProgress={progress}
onOnboardingAction={handleOnboardingAction}
onOpenOnboarding={() => console.log("open full onboarding modal")}
/>
);
}Onboarding popup (full flow)
Wrap your app (or the page that needs onboarding) with OnboardingProvider. Trigger the modal via the context
API (openModal) when registration starts or when the user returns and still has pending steps.
import { useEffect } from "react";
import {
OnboardingProvider,
OnboardingPopup,
useOnboardingContext,
type OnboardingBackendFlags,
} from "@igamingcareer/igaming-components";
function OnboardingGate({ flags }: { flags: OnboardingBackendFlags }) {
const {
openModal,
updateFromBackend,
isComplete,
isDismissed,
} = useOnboardingContext();
useEffect(() => {
updateFromBackend(flags);
// Registration started or incomplete onboarding: prompt the user.
if (!isComplete && !isDismissed) {
openModal();
}
}, [flags, isComplete, isDismissed, openModal, updateFromBackend]);
return (
<OnboardingPopup
onComplete={() => console.log("onboarding finished")}
onClose={() => console.log("onboarding dismissed/closed")}
/>
);
}
export function AppRoot({ flags }: { flags: OnboardingBackendFlags }) {
return (
<OnboardingProvider>
<OnboardingGate flags={flags} />
{/* rest of your app */}
</OnboardingProvider>
);
}Action handling checklist
Use these hooks to connect the onboarding UI to your Next.js app:
- Registration started
- Load backend flags and call
updateFromBackend(flags). - Call
openModal()to showOnboardingPopup.
- Load backend flags and call
- Email verified
- Update backend flags (or re-fetch
/api/auth/me) and callupdateFromBackend(flags). - The profile step changes from
blockedtopending.
- Update backend flags (or re-fetch
- Onboarding completed
- When the profile is complete, update flags so all steps are
complete. OnboardingPopupwill show the completion state and invokeonCompletewhen closed.
- When the profile is complete, update flags so all steps are
Component options
DashboardOnboardingSectionsteps,progress,emailVerified,profileComplete,onActionClick,onOpenFullOnboarding
OnboardingDashboardsteps,progress,variant("compact"or"full"),onActionClick
OnboardingCheckliststeps,variant("compact"or"full"),onActionClick
OnboardingModalopen,onOpenChange,steps,emailVerified,profileComplete,onActionClick,onResendVerification,onChangeEmail,onDismiss,dismissLabel,title,description
OnboardingPopuponComplete,onClose(state is sourced fromOnboardingProvider)
Dashboard tab routing events
The Dashboard component can emit a routing-friendly event whenever a tab is selected. Use it to keep your app router in sync (for example, pushing routes in Next.js). You can also pass an activeTab prop to control the active tab from outside, along with a tabPaths map so your app remains the source of truth for URL formats.
import { useState } from "react";
import {
Dashboard,
type DashboardTab,
type DashboardTabChangeEvent,
} from "@igamingcareer/igaming-components";
export function DashboardWithRouter({ data }: { data: DashboardData }) {
const [activeTab, setActiveTab] = useState<DashboardTab>("overview");
const handleTabChange = (event: DashboardTabChangeEvent) => {
setActiveTab(event.tab);
// Example: Next.js router
// router.push(event.path ?? "/dashboard");
};
return (
<Dashboard
user={data.user}
profileData={data.profile}
alerts={data.alerts}
snapshot={data.snapshot}
feedItems={data.feedItems}
onUpdateProfile={data.onUpdateProfile}
activeTab={activeTab}
tabPaths={{
overview: "/dashboard",
profile: "/dashboard/profile",
jobs: "/dashboard/jobs",
courses: "/dashboard/courses",
news: "/dashboard/news",
settings: "/dashboard/settings",
}}
onTabChange={handleTabChange}
/>
);
}Company detail tab routing events
The CompanyDetail component can emit a routing-friendly event whenever a tab is selected, so your Next.js app remains the source of truth for URLs. Provide activeTab, tabPaths, and onTabChange to keep routing and UI in sync.
import { useState } from "react";
import {
CompanyDetail,
type CompanyDetailTab,
type CompanyDetailTabChangeEvent,
} from "@igamingcareer/igaming-components";
export function CompanyDetailWithRouter({ data }: { data: CompanyDetailData }) {
const [activeTab, setActiveTab] = useState<CompanyDetailTab>("overview");
const handleTabChange = (event: CompanyDetailTabChangeEvent) => {
setActiveTab(event.tab);
// Example: Next.js router
// router.push(event.path ?? `/companies/${data.company.slug}`);
};
return (
<CompanyDetail
company={data.company}
jobs={data.jobs}
news={data.news}
events={data.events}
activeTab={activeTab}
tabPaths={{
overview: `/companies/${data.company.slug}`,
products: `/companies/${data.company.slug}/products`,
trust: `/companies/${data.company.slug}/trust`,
people: `/companies/${data.company.slug}/people`,
jobs: `/companies/${data.company.slug}/jobs`,
news: `/companies/${data.company.slug}/news`,
events: `/companies/${data.company.slug}/events`,
insights: `/companies/${data.company.slug}/insights`,
}}
onTabChange={handleTabChange}
/>
);
}Dashboard tab routing events
The Dashboard component can emit a routing-friendly event whenever a tab is selected. Use it to keep your app router in sync (for example, pushing routes in Next.js). You can also pass an activeTab prop to control the active tab from outside, along with a tabPaths map so your app remains the source of truth for URL formats.
import { useState } from "react";
import {
Dashboard,
type DashboardTab,
type DashboardTabChangeEvent,
} from "@igamingcareer/igaming-components";
export function DashboardWithRouter({ data }: { data: DashboardData }) {
const [activeTab, setActiveTab] = useState<DashboardTab>("overview");
const handleTabChange = (event: DashboardTabChangeEvent) => {
setActiveTab(event.tab);
// Example: Next.js router
// router.push(event.path ?? "/dashboard");
};
return (
<Dashboard
user={data.user}
profileData={data.profile}
alerts={data.alerts}
snapshot={data.snapshot}
feedItems={data.feedItems}
onUpdateProfile={data.onUpdateProfile}
activeTab={activeTab}
tabPaths={{
overview: "/dashboard",
profile: "/dashboard/profile",
jobs: "/dashboard/jobs",
courses: "/dashboard/courses",
news: "/dashboard/news",
settings: "/dashboard/settings",
}}
onTabChange={handleTabChange}
/>
);
}Save company callbacks (CompanyCard + CompanyDetail)
The company components also emit save events so host apps can manage bookmarking without API logic inside the library. Use the save props to track state and respond to user interactions.
import { useState } from "react";
import {
CompanyCard,
CompanyDetail,
type SaveCompanyPayload,
} from "@igamingcareer/igaming-components";
export function SaveCompaniesExample({ company }: { company: Company }) {
const [isSaved, setIsSaved] = useState(false);
const [saveLoading, setSaveLoading] = useState(false);
const handleToggleSave = async (companyId: string, nextIsSaved: boolean) => {
setSaveLoading(true);
try {
// call your API here
// await api.saveCompany({ companyId, save: nextIsSaved });
setIsSaved(nextIsSaved);
} finally {
setSaveLoading(false);
}
};
const handleSaveAction = (payload: SaveCompanyPayload) => {
// optional: analytics or logging
console.log("save action", payload);
};
return (
<>
<CompanyCard
company={{ ...company, isSaved }}
isSaved={isSaved}
saveLoading={saveLoading}
onToggleSave={handleToggleSave}
onSaveAction={handleSaveAction}
/>
<CompanyDetail
company={{ ...company, isSaved }}
isSaved={isSaved}
saveLoading={saveLoading}
onToggleSave={handleToggleSave}
onSaveAction={handleSaveAction}
/>
</>
);
}Use saveDisabled to disable the action for unauthenticated users, and keep isSaved in sync with your data source.
Claim company CTA (CompanyClaim)
Use the CompanyClaim component to render a minimal “Claim this company” CTA. It is fully controlled by props and emits a click event upward without any auth or API logic.
import { CompanyClaim } from "@igamingcareer/igaming-components";
export function CompanyClaimExample({ company, canClaim }: { company: Company; canClaim: boolean }) {
const handleClaimClick = () => {
// optional: analytics or open your claim flow
console.log("claim company", { companyId: company.id });
};
return (
<CompanyClaim
isClaimed={company.claimedStatus === "claimed"}
canClaim={canClaim}
onClaimClick={handleClaimClick}
/>
);
}Claim profile dialog submissions (CompanyDetail)
When the claim dialog is submitted from CompanyDetail, the component emits a structured payload so your app can handle the request outside the library.
import { CompanyDetail } from "@igamingcareer/igaming-components";
import type { CompanyClaimRequestPayload } from "@igamingcareer/igaming-components";
export function CompanyDetailWithClaim({ company }: { company: Company }) {
const handleClaimSubmit = (payload: CompanyClaimRequestPayload) => {
// send to your API or analytics
console.log("claim submit", payload);
};
return <CompanyDetail company={company} onClaimSubmit={handleClaimSubmit} />;
}Claim company CTA (CompanyClaim)
Use the CompanyClaim component to render a minimal “Claim this company” CTA. It is fully controlled by props and emits a click event upward without any auth or API logic.
import { CompanyClaim } from "@igamingcareer/igaming-components";
export function CompanyClaimExample({ company, canClaim }: { company: Company; canClaim: boolean }) {
const handleClaimClick = () => {
// optional: analytics or open your claim flow
console.log("claim company", { companyId: company.id });
};
return (
<CompanyClaim
isClaimed={company.claimedStatus === "claimed"}
canClaim={canClaim}
onClaimClick={handleClaimClick}
/>
);
}CV upload from the dashboard
The dashboard emits CV upload events so the host application can upload, store, and extract text. The component library does not parse PDFs directly — pass the extracted preview text back into the dashboard to display it.
import { useCallback, useState } from "react";
import { Dashboard } from "@igamingcareer/igaming-components";
export function DashboardWithCvUpload() {
const [cvPreviewText, setCvPreviewText] = useState("");
const [cvPreviewStatus, setCvPreviewStatus] = useState<
"idle" | "parsing" | "error"
>("idle");
const [cvPreviewErrorMessage, setCvPreviewErrorMessage] = useState<
string | undefined
>(undefined);
const handleUploadCV = useCallback(async (file: File | null) => {
if (!file) {
setCvPreviewText("");
setCvPreviewStatus("idle");
setCvPreviewErrorMessage(undefined);
return;
}
try {
setCvPreviewStatus("parsing");
setCvPreviewErrorMessage(undefined);
// Upload the file to your backend and parse the PDF there.
// const response = await uploadResume(file);
// setCvPreviewText(response.text);
// Example placeholder:
setCvPreviewText("Preview text returned by your backend.");
setCvPreviewStatus("idle");
} catch (error) {
setCvPreviewStatus("error");
setCvPreviewErrorMessage(
"We couldn't extract text from this CV. The file is still saved."
);
}
}, []);
return (
<Dashboard
user={{ name: "Jane Doe", email: "[email protected]" }}
profileData={/* your profile data */}
alerts={[]}
snapshot={[]}
feedItems={[]}
onUpdateProfile={async () => {}}
onUploadCV={handleUploadCV}
cvPreviewText={cvPreviewText}
cvPreviewStatus={cvPreviewStatus}
cvPreviewErrorMessage={cvPreviewErrorMessage}
/>
);
}Use the file argument to upload the PDF to your backend. When the upload is
deleted from the dialog, the handler receives null.
Tag badge and tag picker
Use the TagBadge component to display tags (for job cards, filters, etc.) and the TagPicker component to select or create tags.
Example 1: Basic tag badge
import { TagBadge } from "@igamingcareer/igaming-components";
export function TagBadgeExample() {
return <TagBadge name="Remote" color="#10B981" size="sm" />;
}Example 2: Removable and clickable tag badge
import { TagBadge } from "@igamingcareer/igaming-components";
export function InteractiveTagBadge() {
return (
<TagBadge
name="Interview"
color="#F59E0B"
size="md"
removable
onRemove={() => console.log("remove tag")}
onClick={() => console.log("badge clicked")}
/>
);
}Example 3: Tag picker with inline creation
import { useState } from "react";
import { TagPicker } from "@igamingcareer/igaming-components";
const initialTags = [
{ id: "1", name: "Remote", color: "#10B981" },
{ id: "2", name: "Urgent", color: "#EF4444" },
{ id: "3", name: "Interview", color: "#F59E0B", category: "Stage" },
];
export function TagPickerExample() {
const [tags, setTags] = useState(initialTags);
const [selectedTagIds, setSelectedTagIds] = useState<string[]>(["1"]);
const handleCreateTag = async ({ name, color }: { name: string; color: string }) => {
const nextTag = { id: String(Date.now()), name, color };
setTags((prev) => [...prev, nextTag]);
};
return (
<TagPicker
availableTags={tags}
selectedTagIds={selectedTagIds}
onTagSelect={(tagId) => setSelectedTagIds((prev) => [...prev, tagId])}
onTagDeselect={(tagId) =>
setSelectedTagIds((prev) => prev.filter((id) => id !== tagId))
}
onCreateTag={handleCreateTag}
maxTags={8}
placeholder="Add tags to this job..."
/>
);
}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.
