@bzbs/react-providers
v3.2.8
Published
A collection of React Context Provider for Buzzebees apps
Readme
@bzbs/react-providers
Version: 3.2.4 | License: ISC | Author: Buzzebees Co., Ltd. Repository: Azure DevOps
A collection of Zustand stores and service utilities for Buzzebees loyalty/reward apps. This package is the implementation layer on top of @bzbs/react-api-client, wrapping API calls with React-friendly state, actions, and side-effects (analytics, token management, event broadcasting).
ℹ️ Breaking change in 3.0.0: The Context API layer (
/providers,./contextexport,DefaultMainAppProvider) has been removed. Only the Zustand stores remain. If you are upgrading from2.x, migrate to the Zustand stores documented below.
Table of Contents
- Installation
- Architecture Overview
- Quick Start
- Package Exports
- Zustand Stores Reference
- useBuzzebeesAppStore
- useAuthStore
- useUserStore
- useCartStore
- useLocaleStore
- useAnalyticsStore
- useAlertStore
- useConfirmStore
- usePopupStore
- useLoadingIndicatorStore
- useNotificationStore
- useMaintenanceStore
- useConsentStore
- useRegistrationStore
- useAddressStore
- useZipCodeStore
- createCampaignsStore
- createCampaignDetailStore
- createCategoriesStore
- createDashboardStore
- createPointLogStore
- createPurchaseStore
- useCouponStore
- createFavoriteCampaignsStore
- TokenFunctions Interface
- Service Utilities
- Analytics Utilities
- Locale Utilities
- Campaign Utilities
- Constants
- Types Reference
- Relationship to @bzbs/react-api-client
- Migration from 2.x
- Development Commands
Installation
npm install @bzbs/react-providersPeer dependencies:
npm install react react-dom axiosAlso required (provides BzbsService):
npm install @bzbs/react-api-clientArchitecture Overview
@bzbs/react-providers
├── Zustand Stores ← import from '@bzbs/react-providers/zustand'
├── Service Utilities ← HTTP interceptors, URL helpers, event emitter
├── Analytics Utils ← Matomo tracking wrappers
├── Locale Utils ← Locale → LCID mapping
├── Campaign Utils ← isPointEnough, isCodeAutoUse
└── Constants ← Campaign types, point typesDependencies flow:
@bzbs/react-api-client (BzbsService)
↓
useBuzzebeesAppStore ← Central config store
↓
useAuthStore, useUserStore, useCartStore, ... ← Feature storesAll feature stores read bzbsService from useBuzzebeesAppStore to make API calls. You must call useBuzzebeesAppStore.getState().configure(...) (or use individual setters) before using any other store.
Quick Start
import axios from 'axios';
import { BzbsService } from '@bzbs/react-api-client';
import {
useBuzzebeesAppStore,
addDefaultHeaderInterceptor,
useAuthStore,
} from '@bzbs/react-providers/zustand';
// 1. Create an Axios instance
const axiosClient = axios.create({
headers: {
'Content-Type': 'application/json',
'App-Id': 'YOUR_APP_ID',
'Subscription-Key': 'YOUR_SUBSCRIPTION_KEY',
},
});
// 2. Create the BzbsService instance
const bzbsService = new BzbsService(axiosClient, 'https://api.buzzebees.com');
// 3. Define token persistence functions
const tokenFunctions = {
getToken: async () => localStorage.getItem('bzbs_token'),
setToken: (token: string) => localStorage.setItem('bzbs_token', token),
removeToken: () => localStorage.removeItem('bzbs_token'),
};
// 4. Attach auth/header interceptors
addDefaultHeaderInterceptor(
axiosClient,
'YOUR_APP_ID',
'YOUR_SUBSCRIPTION_KEY',
tokenFunctions,
'1.0'
);
// 5. Configure the central app store (call this once at app startup)
useBuzzebeesAppStore.getState().configure({
appId: 'YOUR_APP_ID',
appName: 'your-app',
bzbsService,
tokenFunctions,
tokenType: 'jwt',
urls: {
webCallback: 'https://yourapp.com/callback',
cart: 'https://cart.buzzebees.com',
},
config: {
defaultDashboardConfig: 'main',
defaultDashboardMode: 'main',
defaultMenuConfig: 'default',
defaultCampaignConfig: 'default',
supportPointUnits: [], // Configure point service units, e.g. ['POINT', 'COIN']
},
});
// 6. Use stores in components
function LoginScreen() {
const { isLoading, isLoggedIn, actions } = useAuthStore();
const handleLogin = async () => {
const result = await actions.loginWithUsernamePassword('[email protected]', 'password');
if (result.type === 'success') {
console.log('Logged in!');
}
};
return <button onClick={handleLogin} disabled={isLoading}>Login</button>;
}Package Exports
Only the /zustand entry point is published.
| Import Path | Contents |
|---|---|
| @bzbs/react-providers/zustand | All Zustand stores, hooks, service utilities, analytics utilities, constants, and types |
| @bzbs/react-providers | Alias of /zustand (main/module point to the same build) |
Zustand Stores Reference
All stores are available from @bzbs/react-providers/zustand.
useBuzzebeesAppStore
Central configuration store. Must be configured before any other store is used.
import { useBuzzebeesAppStore } from '@bzbs/react-providers/zustand';State:
type BuzzebeesAppState = {
appId: string;
appName: string;
bzbsService: BzbsService | null;
uuid: string;
macAddress: string;
clientVersion: string;
os: string;
platform: string;
fcmToken: string;
deviceNotificationEnabled: boolean;
urls: { webCallback: string; cart: string };
tokenFunctions: TokenFunctions | null;
tokenType: AuthTokenType; // 'jwt' | 'auth_token'
config: {
defaultDashboardConfig: string;
defaultDashboardMode: 'main' | 'sub';
defaultMenuConfig: string;
defaultCampaignConfig: string;
supportPointUnits: string[]; // Point-service units supported by the app
};
};Actions:
type BuzzebeesAppActions = {
// Bulk configure — call this once at app startup
configure(config: {
appId: string;
appName: string;
bzbsService: BzbsService;
tokenFunctions: TokenFunctions;
tokenType?: AuthTokenType;
uuid?: string;
macAddress?: string;
clientVersion?: string;
os?: string;
platform?: string;
fcmToken?: string;
deviceNotificationEnabled?: boolean;
urls: { webCallback: string; cart: string };
config?: {
defaultDashboardConfig?: string;
defaultDashboardMode?: 'main' | 'sub';
defaultMenuConfig?: string;
defaultCampaignConfig?: string;
supportPointUnits?: string[];
};
}): void;
// Individual setters
setAppId(appId: string): void;
setAppName(appName: string): void;
setBzbsService(service: BzbsService): void;
setUuid(uuid: string): void;
setMacAddress(macAddress: string): void;
setClientVersion(clientVersion: string): void;
setOs(os: string): void;
setPlatform(platform: string): void;
setFcmToken(fcmToken: string): void;
setDeviceNotificationEnabled(enabled: boolean): void;
setUrls(urls: { webCallback: string; cart: string }): void;
setTokenType(tokenType: AuthTokenType): void;
setTokenFunctions(tokenFunctions: TokenFunctions): void;
};
supportPointUnits— When non-empty, the campaign detail store routes redemption point checks through the point-service balances on the user (pointServiceBalance) rather than the regular point balance. Leave as[]if your app does not use the point-service feature.
Usage:
// Access state in a component
const { appId, bzbsService } = useBuzzebeesAppStore();
// Access state outside React (e.g., service files)
const state = useBuzzebeesAppStore.getState();useAuthStore
Manages the full authentication lifecycle: login, logout, OTP, token validation, and third-party providers (Google, Facebook, Apple, Line).
import { useAuthStore } from '@bzbs/react-providers/zustand';State:
type AuthState = {
isInitialized: boolean;
versionData: Version | null;
rawVersionData: unknown | null;
data: LoginResponse | ResumeResponse | null;
thirdPartyLoginData?: ThirdPartyLoginData; // Set when the most recent login was third-party
isLoading: boolean;
isLoadingToken: boolean;
error: ErrorResponse | null;
token: string | null;
isLoggedIn: boolean;
};
type ThirdPartyLoginData =
| { type: 'google'; idToken: string }
| { type: 'facebook'; accessToken: string }
| { type: 'apple'; idToken: string; refreshToken: string }
| { type: 'line'; idToken: string; lineAccessToken: string; authorizationCode: string };Actions:
type AuthActions = {
// State setters
setIsLoading(value: boolean): void;
setIsLoadingToken(value: boolean): void;
setError(value: unknown): void;
setThirdPartyLoginData(data: ThirdPartyLoginData): void;
// Check persisted token and restore session
checkLoggedIn(): Promise<void>;
// Login methods — all return ServiceResponse<LoginResponse>
loginWithUsernamePassword(username: string, password: string): Promise<ServiceResponse<LoginResponse>>;
loginWithGoogle(token: string): Promise<ServiceResponse<LoginResponse>>;
loginWithFacebook(token: string): Promise<ServiceResponse<LoginResponse>>;
loginWithApple(token: string, refreshToken: string): Promise<ServiceResponse<LoginResponse>>;
loginWithLine(idToken: string, lineAccessToken: string, authorizationCode: string): Promise<ServiceResponse<LoginResponse>>;
loginWithUUID(): Promise<ServiceResponse<LoginResponse>>;
loginWithOtp(otp: string, refCode: string, contact: string): Promise<ServiceResponse<LoginResponse>>;
// Account connection — link an already-authenticated account to a third-party identity
connectThirdParty(data: ThirdPartyLoginData): Promise<ServiceResponse<LoginResponse>>;
// Password management
forgetPassword(contact: string, type: 'email' | 'contact_number'): Promise<ServiceResponse<ForgetPasswordResponse>>;
resetPassword(contact: string, otp: string, refCode: string, password: string): Promise<ServiceResponse<StatusResponse>>;
// OTP
sendOtp(contact: string, channel: string): Promise<ServiceResponse<OtpResponse>>;
validateOtp(otp: string, refCode: string, contact: string, channel: string, type: 'email' | 'contact_number'): Promise<ServiceResponse<ValidateOtpResponse>>;
confirmOtp(otp: string, refCode: string, contact: string): Promise<ServiceResponse<ConfirmOtpResponse>>;
// Apple Sign-In token exchange
appleToken(authorizationCode: string, idToken: string): Promise<ServiceResponse<AppleToken>>;
// Session management
logout(): Promise<ServiceResponse<unknown>>;
resume(clientVersion: string): Promise<ServiceResponse<ResumeResponse>>;
version(clientVersion: string): Promise<ServiceResponse<Version>>;
clear(): void;
};Example:
const { isLoggedIn, isLoading, token, actions } = useAuthStore();
// Login
const res = await actions.loginWithUsernamePassword('[email protected]', 'secret');
if (res.type === 'success') { /* navigate to home */ }
// Third-party login
await actions.loginWithGoogle(googleIdToken);
await actions.loginWithLine(lineIdToken, lineAccessToken, authorizationCode);
// If login failed because the third-party identity is not yet linked,
// authenticate the user some other way and then connect the identity:
await actions.connectThirdParty({ type: 'google', idToken: googleIdToken });
// Check token on app start
useEffect(() => { actions.checkLoggedIn(); }, []);
// Logout
await actions.logout();Behavior: Each
loginWith*call records its identity inthirdPartyLoginDataimmediately (before the API call), so the UI can read which identity is being attempted. On a successfulconnectThirdParty, the field is cleared.After a successful login,
useAuthStoreautomatically callsuseUserStore.getState().fetchUser()and fires analytics events.
useUserStore
Manages the authenticated user's profile, points, point-service balances, expiring points, badges, cart count, and traces.
import { useUserStore } from '@bzbs/react-providers/zustand';State:
type UserState = {
user: ProfileResponse | null;
points: number; // Regular point balance
pointServiceBalance: { [unit: string]: number }; // Per-unit balances from point-service
expiringPointService: { // Per-unit expiring schedule
[unit: string]: { date: string; points: number }[];
};
recentPoints: number;
expiringPoints: UpdatedPoints[];
recentBadge: Badge | null;
recentTrace: Trace | null;
cartCount: number;
isLoading: boolean;
error: ErrorResponse | null;
imageError: ErrorResponse | null; // Errors from profile-image updates
badgeList: Badge[];
traceList: Trace[];
};Actions:
type UserActions = {
fetchUser(): Promise<ServiceResponse<ProfileResponse>>;
// Fetches both regular points AND point-service balances in parallel
fetchPoint(): Promise<{
pointResponse: ServiceResponse<UpdatedPoints>;
customPointsResponse: ServiceResponse<PointBalance[]>;
}>;
fetchExpiringPoints(): Promise<ServiceResponse<ExpiringPoints>>;
fetchCartCount(): Promise<ServiceResponse<CartCountResponse>>;
editUser(params: UpdateProfileParams): Promise<ServiceResponse<ProfileResponse>>;
changePassword(oldPassword: string, newPassword: string): Promise<ServiceResponse<unknown>>;
changeContactNumber(contactNumber: string, otp: string, refCode: string): Promise<ServiceResponse<unknown>>;
changeAvatar(file: File | Blob): Promise<ServiceResponse<unknown>>;
deleteUser(): Promise<ServiceResponse<unknown>>;
consumeRecentPoints(): void;
consumeRecentBadge(): void;
consumeRecentTrace(): void;
setupEventListeners(): void;
cleanupEventListeners(): void;
setError(value: unknown): void;
clear(): void;
};Profile image:
changeAvatarandeditUser(when called withprofileImage) now use the dedicatedprofileApi.updateProfileImageendpoint. Image-upload failures are reported onimageErrorrather thanerror, so the profile-edit success state is not blocked by an avatar failure.
useCartStore
Fetches and tracks the number of items in the user's cart.
import { useCartStore } from '@bzbs/react-providers/zustand';State:
type CartState = {
count: number;
isLoading: boolean;
error: ErrorResponse | null;
};Actions:
type CartActions = {
fetchCount(): Promise<ServiceResponse<CartCountResponse>>;
clear(): void;
};useLocaleStore
Manages the app locale used for API requests.
import { useLocaleStore, EN, TH } from '@bzbs/react-providers/zustand';State:
type LocaleState = {
appLocale: AppLocale;
locale: string; // e.g. 'en', 'th'
localeId: number; // LCID e.g. 1033, 1054
};
type AppLocale = {
locale: string;
localeId: number;
};Constants:
const EN: AppLocale = { locale: 'en', localeId: 1033 };
const TH: AppLocale = { locale: 'th', localeId: 1054 };Actions:
type LocaleActions = {
// Accepts an AppLocale object or a locale string like 'en', 'th-TH', 'en-US'
setAppLocale(locale: AppLocale | string): void;
};Example:
const { locale, localeId } = useLocaleStore();
// Set by locale string (resolves via findBestLocaleFromString)
useLocaleStore.getState().setAppLocale('th');
// Set by full AppLocale object
useLocaleStore.getState().setAppLocale(TH);useAnalyticsStore
Manages Matomo analytics tracking.
import { useAnalyticsStore } from '@bzbs/react-providers/zustand';State:
type AnalyticsState = {
isInitialized: boolean;
isEnabled: boolean;
siteId: number;
urlBase: string;
trackerUrl: string | undefined;
userId: string | undefined;
disabled: boolean;
log: boolean;
matomoInstance: MatomoTracker | null;
presetUserInfo: UserInfo | undefined;
};Actions:
type AnalyticsActions = {
initialize(config: {
siteId: number;
urlBase: string;
trackerUrl?: string;
userId?: string;
disabled?: boolean;
log?: boolean;
}): void;
setEnabled(enabled: boolean): void;
setPresetUserInfo(userInfo: UserInfo): void;
clearPresetUserInfo(): void;
trackAppStart(userInfo?: UserInfo): Promise<void>;
trackEvent(params: {
action: string;
name?: string;
category?: string;
value?: number;
campaign?: string;
userInfo?: UserInfo;
}): Promise<void>;
trackScreenView(params: { name: string; userInfo?: UserInfo }): Promise<void>;
trackAction(params: { name: string; userInfo?: UserInfo }): Promise<void>;
trackSiteSearch(params: {
keyword: string;
category?: string;
count?: number;
userInfo?: UserInfo;
}): Promise<void>;
trackLink(params: { link: string; userInfo?: UserInfo }): Promise<void>;
trackDownload(params: { download: string; userInfo?: UserInfo }): Promise<void>;
setUserId(userId: string | null): void;
setCustomDimension(id: number, value: string): void;
reset(): void;
};useAlertStore
Manages alert/dialog display state.
import { useAlertStore } from '@bzbs/react-providers/zustand';State:
type AlertState = {
isOpen: boolean;
request: AlertRequest | null;
};
type AlertRequest = {
title?: string;
message: string;
confirmLabel?: string;
onConfirm?: () => void;
};Actions:
type AlertActions = {
show(request: AlertRequest): void;
hide(): void;
};useConfirmStore
Manages confirmation dialog state.
import { useConfirmStore } from '@bzbs/react-providers/zustand';State:
type ConfirmState = {
isOpen: boolean;
request: ConfirmRequest | null;
};
type ConfirmRequest = {
title?: string;
message: string;
confirmLabel?: string;
cancelLabel?: string;
onConfirm?: () => void;
onCancel?: () => void;
};Actions:
type ConfirmActions = {
show(request: ConfirmRequest): void;
hide(): void;
};usePopupStore
Manages popup/modal display state.
import { usePopupStore } from '@bzbs/react-providers/zustand';State:
type PopupState = {
isOpen: boolean;
request: PopupRequest | null;
};
type PopupRequest = {
content: React.ReactNode;
onClose?: () => void;
};Actions:
type PopupActions = {
show(request: PopupRequest): void;
hide(): void;
};useLoadingIndicatorStore
Manages a global loading indicator with named loading keys.
import { useLoadingIndicatorStore, useLoading } from '@bzbs/react-providers/zustand';State:
type LoadingIndicatorState = {
loadingKeys: string[];
isLoading: boolean;
};Actions:
type LoadingIndicatorActions = {
show(key?: string): void;
hide(key?: string): void;
clear(): void;
};useLoading hook: A convenience hook for reading the current loading state.
useNotificationStore
Manages user notification list and unread count.
import { useNotificationStore } from '@bzbs/react-providers/zustand';Wraps notificationApi from @bzbs/react-api-client. Actions include fetching notifications, marking as read, and clearing state.
useMaintenanceStore
Manages app maintenance mode state.
import { useMaintenanceStore } from '@bzbs/react-providers/zustand';Fetches maintenance configuration from the API and exposes isMaintenance: boolean and maintenanceData.
useConsentStore
Manages user consent preferences (marketing, data sharing, etc.).
import { useConsentStore } from '@bzbs/react-providers/zustand';Wraps consentApi from @bzbs/react-api-client. Exposes consent state and update actions.
useRegistrationStore
Manages user registration flow state.
import { useRegistrationStore } from '@bzbs/react-providers/zustand';Wraps registrationApi. Handles multi-step registration: form data, OTP verification, and account creation.
useAddressStore
Manages user address book.
import { useAddressStore } from '@bzbs/react-providers/zustand';Wraps addressApi. CRUD operations for user addresses.
useZipCodeStore
Fetches postal/zip code data for address forms.
import { useZipCodeStore } from '@bzbs/react-providers/zustand';createCampaignsStore
Factory that creates a scoped campaigns list store. Use when you need multiple independent campaign list instances.
import { createCampaignsStore, type CampaignsOptions } from '@bzbs/react-providers/zustand';
const useMyCampaignsStore = createCampaignsStore({ defaultCampaignConfig: 'home' });Actions:
type CampaignsActions = {
fetchData(categoryId?: string, options?: CampaignsOptions): Promise<ServiceResponse<Campaign[]>>;
loadMore(): Promise<ServiceResponse<Campaign[]>>;
clear(): void;
updateConfig(config: Partial<CampaignsConfig>): void;
};CampaignsOptions — Typed filter/sort/search options accepted by fetchData:
type CampaignsOptions = {
keyword?: string;
startDate?: string;
sponsorId?: string;
maxPoints?: string;
minPoints?: string;
minPrice?: string;
maxPrice?: string;
sortBy?: string;
center?: string;
hashTags?: string;
locationAgencyId?: string;
campaignservice?: boolean;
mode?: 'hot' | 'all' | 'bzbs' | 'sponsor' | 'draw' | 'free' | 'deal' | 'buy' | 'ads' | 'full' | 'cart' | string;
[key: string]: unknown;
};createCampaignDetailStore
Factory that creates a scoped campaign detail store.
import { createCampaignDetailStore } from '@bzbs/react-providers/zustand';
const useCampaignDetailStore = createCampaignDetailStore();Wraps campaignApi.getCampaignDetail() and campaignApi.redeem(). Computes a buttonState from the campaign type, point requirements, sold-out status, time window, and (when supportPointUnits is configured) per-unit point-service balances.
Redeem signature:
redeem(
addressOverride?: Address,
pointUnit?: string, // Which point-service unit to spend (when supportPointUnits is set)
options?: { [key: string]: unknown }
): Promise<ServiceResponse<RedeemResponse>>;When pointUnit is provided, the redemption uses data.PointServices[pointUnit].AmountPerUnit as the cost rather than data.PointPerUnit.
Condition checks: Point-sufficiency is not evaluated for
buy,event,media, ornewscampaigns. For all other types,isPointEnoughis consulted againstpoints+pointServiceBalance.
createCategoriesStore
Factory that creates a scoped categories/menu store.
import { createCategoriesStore } from '@bzbs/react-providers/zustand';
const useCategoriesStore = createCategoriesStore();createDashboardStore
Factory that creates a scoped dashboard configuration store.
import { createDashboardStore } from '@bzbs/react-providers/zustand';
const useDashboardStore = createDashboardStore();createPointLogStore
Factory that creates a scoped point history store.
import { createPointLogStore } from '@bzbs/react-providers/zustand';
const usePointLogStore = createPointLogStore();Wraps pointLogApi. Supports pagination.
createPurchaseStore
Factory that creates a scoped purchase history store.
import { createPurchaseStore } from '@bzbs/react-providers/zustand';
const usePurchaseStore = createPurchaseStore();useCouponStore
Manages user coupon list and coupon operations.
import { useCouponStore } from '@bzbs/react-providers/zustand';createFavoriteCampaignsStore
Factory that creates a scoped favorites store.
import { createFavoriteCampaignsStore } from '@bzbs/react-providers/zustand';
const useFavoriteCampaignsStore = createFavoriteCampaignsStore();TokenFunctions Interface
A required interface for token persistence. You provide your own implementation based on your storage mechanism.
type TokenFunctions = {
getToken: () => Promise<string | null>;
setToken: (token: string) => void;
removeToken: () => void;
};Example implementations:
// Web (localStorage)
const tokenFunctions: TokenFunctions = {
getToken: async () => localStorage.getItem('auth_token'),
setToken: (token) => localStorage.setItem('auth_token', token),
removeToken: () => localStorage.removeItem('auth_token'),
};
// React Native (AsyncStorage)
import AsyncStorage from '@react-native-async-storage/async-storage';
const tokenFunctions: TokenFunctions = {
getToken: () => AsyncStorage.getItem('auth_token'),
setToken: (token) => AsyncStorage.setItem('auth_token', token),
removeToken: () => AsyncStorage.removeItem('auth_token'),
};
// In-memory (testing / SSR)
let _token: string | null = null;
const tokenFunctions: TokenFunctions = {
getToken: async () => _token,
setToken: (token) => { _token = token; },
removeToken: () => { _token = null; },
};Service Utilities
Exported from @bzbs/react-providers/zustand.
addDefaultHeaderInterceptor
Attaches request and response interceptors to an Axios instance. Call this once after creating your Axios instance and before passing it to BzbsService.
import { addDefaultHeaderInterceptor } from '@bzbs/react-providers/zustand';
addDefaultHeaderInterceptor(
axiosInstance: AxiosInstance,
appId: string,
subscriptionKey: string,
tokenFunctions: TokenFunctions,
apiVersion: string,
corelationIdGenerator?: () => string // optional, defaults to uuidv7()
): voidWhat it does:
| Direction | Action |
|---|---|
| Request | Adds App-Id, Subscription-Key, Api-Version, X-Corelation-Id headers |
| Request | Adds Authorization: Bearer <token> (JWT) or Authorization: token <token> |
| Response | Normalises both new-style ({ Success, Data }) and old-style ({ error, ... }) responses |
| Response | Emits get_points event if response contains buzzebees.points |
| Response | Emits get_badges event if response contains buzzebees.badges |
| Response | Emits session_expired event on error code 1905 or 2076 |
Header rename (2.8.0):
Ocp-Apim-Subscription-Key→Subscription-Key, andapi-version→Api-Version.
addBlake2OtpSignatureInterceptor
Adds BLAKE2b OTP signature headers to requests matching specified URL paths.
import { addBlake2OtpSignatureInterceptor } from '@bzbs/react-providers/zustand';
addBlake2OtpSignatureInterceptor(
axiosInstance: AxiosInstance,
paths: string[], // URL path substrings to match (e.g. ['/otp/send'])
encoder: TextEncoder,
getCurrentDateTime?: () => Date // optional, defaults to () => new Date()
): voidWhen a matching request includes app_id, contact_number, and channel params, the interceptor adds:
OTP-Signature: BLAKE2b hex ofappId|contactNumber|channel|datetimeTimestamp: Unix timestamp (seconds)
Utility Functions
import {
avatarUrl,
largeImage,
fetchImage,
interfaceWebsite,
createInterfaceToken,
createCartUrl,
generateBlake2bSignatureHex,
generateShortLivedId,
getOtpSignatureHeaders,
getProfileImageSource,
} from '@bzbs/react-providers/zustand';| Function | Signature | Description |
|---|---|---|
| avatarUrl | (baseUrl, appId, userId, token) => string | Builds a profile picture URL with cache-busting timestamp |
| largeImage | (url) => string | Transforms a standard image URL to its large variant |
| fetchImage | (axiosClient, imageUrl) => Promise<Blob \| undefined> | Downloads an image as a Blob |
| interfaceWebsite | (url, token?, returnUrl?, params?) => string | Builds an interface website URL with obfuscated token |
| createInterfaceToken | (token) => string | Obfuscates a token by rearranging first/last characters |
| createCartUrl | (cartUrl, appName, accessKey, params?) => string | Builds a cart landing URL with access key |
| generateBlake2bSignatureHex | (data, key, encoder) => string | Generates a BLAKE2b hex signature |
| generateShortLivedId | () => string | Generates a random short-lived ID string |
| getOtpSignatureHeaders | ({ appId, contactNumber, channel, encoder, now? }) => object | Returns { 'OTP-Signature': string, Timestamp: number } |
| getProfileImageSource | (baseUrl, appId, subscriptionKey, apiVersion, token, corelationIdGenerator?, timestamp?, type?) => ProfileImageSource | Builds a { uri, headers } source for <Image> components fetching the authenticated profile picture |
getProfileImageSource is intended for React Native <Image source={...} /> where the URL needs to be fetched with full Buzzebees auth headers:
const source = getProfileImageSource(
'https://api.buzzebees.com',
'YOUR_APP_ID',
'YOUR_SUBSCRIPTION_KEY',
'1.0',
authToken
);
// source = { uri: 'https://.../profile/picture?timestamp=...&type=large', headers: { ... } }
<Image source={source} />eventEmitter
A global Emittery instance for cross-component communication. The addDefaultHeaderInterceptor automatically fires events on it.
import { eventEmitter } from '@bzbs/react-providers/zustand';
// Listen for points update (response contains buzzebees.points)
eventEmitter.on('get_points', (data) => {
console.log('New points:', data.points);
});
// Listen for badges update
eventEmitter.on('get_badges', (badges) => {
console.log('New badges:', badges);
});
// Listen for session expiry (error code 1905 or 2076)
eventEmitter.on('session_expired', (error) => {
// Redirect to login, clear token, etc.
useAuthStore.getState().clear();
});
useUserStoreinternally callssetupEventListeners()to subscribe toget_pointsandget_badgesand update its state. CallcleanupEventListeners()on unmount.
Analytics Utilities
import {
analytics,
trackCommonEvents,
useAnalytics,
usePageTracking,
useAnalyticsState,
initializeAnalytics,
withPageTracking,
} from '@bzbs/react-providers/zustand';trackCommonEvents
Pre-built Matomo event calls for common Buzzebees actions. All functions are async and no-op if analytics is not initialized.
await trackCommonEvents.loginSuccess(userInfo?)
await trackCommonEvents.registerSuccess(userInfo?)
await trackCommonEvents.viewCampaign(userInfo?)
await trackCommonEvents.redeemSuccess(campaignId: string, userInfo?)
await trackCommonEvents.unconsentSuccess(userInfo?)
await trackCommonEvents.buttonClick(buttonName: string, category?: string, userInfo?)
await trackCommonEvents.formSubmit(formName: string, success?: boolean, userInfo?)
await trackCommonEvents.search(keyword: string, resultCount?: number, category?: string, userInfo?)
await trackCommonEvents.purchase(amount: number, currency?: string, productName?: string, userInfo?)
await trackCommonEvents.userRegistration(method?: string, userInfo?)
await trackCommonEvents.userLogin(method?: string, userInfo?)
await trackCommonEvents.error(errorType: string, errorMessage?: string, userInfo?)
await trackCommonEvents.custom(name: string, category: string, label?: string, value?: number, userInfo?)analytics (global object)
For use outside React components (service files, event handlers, etc.).
analytics.init({ siteId: 1, urlBase: 'https://matomo.example.com' })
analytics.trackEvent({ action, name?, category?, value?, campaign?, userInfo? })
analytics.trackScreenView(name: string, userInfo?)
analytics.trackAppStart(userInfo?)
analytics.trackSiteSearch(keyword: string, category?, count?, userInfo?)
analytics.trackLink(link: string, userInfo?)
analytics.trackDownload(download: string, userInfo?)
analytics.setUserId(userId: string | null)
analytics.setCustomDimension(id: number, value: string)
analytics.setPresetUserInfo(userInfo: UserInfo)
analytics.clearPresetUserInfo()
analytics.setEnabled(enabled: boolean)
analytics.reset()useAnalytics Hook
React hook that returns analytics functions from the current context.
const {
isInitialized,
isEnabled,
trackEvent,
trackScreenView,
trackAction,
trackAppStart,
trackSiteSearch,
trackLink,
trackDownload,
setUserId,
setCustomDimension,
setEnabled,
setPresetUserInfo,
clearPresetUserInfo,
} = useAnalytics();usePageTracking hook — automatically tracks screen view on mount:
usePageTracking(
pageName?: string,
dependencies?: unknown[],
customUserInfo?: UserInfo
): voidwithPageTracking HOC:
const TrackedComponent = withPageTracking(MyComponent, 'Home Screen');Locale Utilities
import { findBestLocaleFromString, localeToLCIDMap } from '@bzbs/react-providers/zustand';findBestLocaleFromString(value: string): { locale: string; localeId: number }
Resolves a locale string to an AppLocale object with LCID. Supports:
- Exact match:
'th-TH'→{ locale: 'th-TH', localeId: 1054 } - Partial match:
'th'→ finds first key starting with'th' - Throws
Errorif no match found
localeToLCIDMap — Object mapping 200+ locale strings (BCP 47) to Windows LCID numbers.
Common mappings:
| Locale | LCID |
|---|---|
| en-US | 1033 |
| th-TH | 1054 |
| zh-CN | 2052 |
| zh-TW | 1028 |
| ja-JP | 1041 |
| ko-KR | 1042 |
| ms-MY | 1086 |
| id-ID | 1057 |
| vi-VN | 1066 |
Campaign Utilities
import { isCodeAutoUse, isPointEnough } from '@bzbs/react-providers/zustand';isCodeAutoUse(campaign: CampaignDetail, redeemData: RedeemResponse): boolean
Determines whether a redeemed campaign code was auto-consumed. Returns campaign.IsRequireUniqueSerial === true when IsNotAutoUse is set, otherwise redeemData.IsUsed === true.
isPointEnough(campaign, userPoints, pointServiceBalance, supportPointUnits, quantity?): boolean
Computes whether the user can afford a campaign redemption, taking the point-service feature into account:
| Condition | Result |
|---|---|
| campaign.PointType === 'get' | true (campaign awards points; never blocked) |
| supportPointUnits is empty | Compares userPoints against campaign.PointPerUnit × quantity |
| campaign.PointServices is missing | Falls back to regular userPoints comparison |
| Otherwise | true if any unit in supportPointUnits has pointServiceBalance[unit] >= PointServices[unit].AmountPerUnit × quantity |
This is the same helper consulted internally by createCampaignDetailStore's updateButtonState and checkCondition.
Constants
import { campaignType, campaignPointType, campaignInterfaceType } from '@bzbs/react-providers/zustand';campaignType
Maps campaign type names to numeric IDs used by the Buzzebees API.
const campaignType = {
draw: 0,
free: 1,
deal: 2,
buy: 3,
bid: 4,
ads: 5,
install: 6,
booking: 7,
interface: 8,
event: 9,
media: 10,
ewalletTopUp: 11,
ewalletRedeem: 12,
ewalletTransfer: 13,
ewalletBanking: 14,
autofeed: 15,
news: 16,
reservation: 17,
ewalletBuy: 18,
pointRedemption: 19,
donate: 20,
pointFree: 21,
voucher: 22,
encrypt: 23,
encryptRedeem: 24,
giftCard: 25,
verifyCode: 26,
subscription: 27,
fillCode: 28,
subscriptionFood: 29,
buyEVoucher: 30,
uploadReceipt: 31,
payWithPoints: 32,
marketPlacePrivilege: 33,
topup2C2P: 34,
directTopup2C2P: 35,
};Usage:
if (campaign.campaign_type === campaignType.voucher) {
// Handle voucher campaign
}campaignPointType
const campaignPointType = {
use: 'use', // campaign costs points
get: 'get', // campaign rewards points
};campaignInterfaceType
const campaignInterfaceType = {
web: 'web',
survey: 'survey',
surveyApprove: 'surveyapprove',
};Types Reference
import type {
TokenFunctions,
AuthTokenType,
AppLocale,
MatomoConfig,
LoginResponseHandler,
ErrorResponseHandler,
ProfileImageSource,
// Store types
BuzzebeesAppState,
BuzzebeesAppActions,
BuzzebeesAppStore,
AuthState,
AuthActions,
AuthStore,
ThirdPartyLoginData,
GoogleLoginData,
FacebookLoginData,
AppleLoginData,
LineLoginData,
UserState,
UserActions,
UserStore,
CartState,
CartActions,
CartStore,
LocaleState,
LocaleActions,
LocaleStore,
AlertState,
AlertActions,
AlertStore,
AlertRequest,
ConfirmState,
ConfirmActions,
ConfirmStore,
ConfirmRequest,
PopupState,
PopupActions,
PopupStore,
PopupRequest,
LoadingIndicatorState,
LoadingIndicatorActions,
LoadingIndicatorStore,
UpdateProfileParams,
CampaignsOptions,
CampaignsConfig,
} from '@bzbs/react-providers/zustand';| Type | Description |
|---|---|
| TokenFunctions | Token persistence interface (getToken/setToken/removeToken) |
| AuthTokenType | 'jwt' \| 'auth_token' — token format stored after login |
| AppLocale | { locale: string; localeId: number } |
| MatomoConfig | Matomo tracker configuration (siteId, urlBase, trackerUrl?, userId?, disabled?, log?) |
| LoginResponseHandler | (response: LoginResponse) => void |
| ErrorResponseHandler | (error: ErrorResponse) => void |
| ProfileImageSource | { uri: string; headers: { [key: string]: unknown } } — return shape of getProfileImageSource |
| ThirdPartyLoginData | Discriminated union of GoogleLoginData \| FacebookLoginData \| AppleLoginData \| LineLoginData |
| UpdateProfileParams | Parameters for useUserStore().editUser() |
| CampaignsOptions | Filter/sort/search options passed to createCampaignsStore fetchData |
All API response types (LoginResponse, ProfileResponse, ErrorResponse, PointBalance, CampaignDetail, etc.) are re-exported from @bzbs/react-api-client. See react-api-client.md for the full models reference.
Relationship to @bzbs/react-api-client
@bzbs/react-providers is the stateful wrapper around @bzbs/react-api-client. The relationship:
@bzbs/react-api-client @bzbs/react-providers
──────────────────────── ──────────────────────────────
BzbsService → Passed into useBuzzebeesAppStore
authApi → useAuthStore
profileApi → useUserStore
pointApi → useUserStore (pointServiceBalance, expiringPointService)
cartApi → useCartStore
campaignApi → createCampaignsStore / createCampaignDetailStore
categoryApi → createCategoriesStore
couponApi → useCouponStore
notificationApi → useNotificationStore
historyApi → createPurchaseStore
registrationApi → useRegistrationStore
addressApi → useAddressStore
badgeApi → useUserStore (badges)
consentApi → useConsentStore
dashboardApi → createDashboardStore
pointLogApi → createPointLogStoreTo create a BzbsService instance, refer to react-api-client.md.
Migration from 2.x
The 3.0.0 release removed the Context API entirely. If you are upgrading from a 2.x version:
| Removed (2.x) | Replacement (3.x) |
|---|---|
| DefaultMainAppProvider (wrapping the whole app) | Call useBuzzebeesAppStore.getState().configure({ ... }) once at app startup |
| import { ... } from '@bzbs/react-providers/context' | import { ... } from '@bzbs/react-providers/zustand' |
| import { ... } from '@bzbs/react-providers' | Now equivalent to /zustand — only that entry point is built |
| useBuzzebeesServiceContext() | useBuzzebeesAppStore() |
| useAuthContext() | useAuthStore() |
| useUserContext() | useUserStore() |
| useCartContext() | useCartStore() |
| useCampaignsContext() | createCampaignsStore() |
| useCampaignDetailContext() | createCampaignDetailStore() |
| useCategoriesContext() | createCategoriesStore() |
| useCouponContext() | useCouponStore() |
| useAlertContext() | useAlertStore() |
| useConfirmContext() | useConfirmStore() |
| usePopupContext() | usePopupStore() |
| useLoadingContext() / useLoading() | useLoadingIndicatorStore() / useLoading() |
| useNotificationContext() | useNotificationStore() |
| useAnalyticsContext() | useAnalyticsStore() |
| useAppLocaleContext() | useLocaleStore() |
| useConsentContext() | useConsentStore() |
| useMaintenanceContext() | useMaintenanceStore() |
| useRegistrationContext() | useRegistrationStore() |
| useAddressContext() | useAddressStore() |
| useZipCodeContext() | useZipCodeStore() |
| useDashboardContext() | createDashboardStore() |
| usePointLogContext() | createPointLogStore() |
| usePurchaseContext() | createPurchaseStore() |
Other notable breaking changes:
- 2.8.0 — Default headers renamed:
Ocp-Apim-Subscription-Key→Subscription-Key,api-version→Api-Version. If you set these headers manually anywhere, update them. - 2.8.0 —
useUserStore.changeAvatarandeditUser({ profileImage })now callprofileApi.updateProfileImageand report failures on the newimageErrorstate field. - 3.0.0 —
useBuzzebeesAppStore.configrequires a new fieldsupportPointUnits: string[](use[]if unused). - 3.0.0 —
useUserStore.fetchPoint()now returns{ pointResponse, customPointsResponse }instead ofServiceResponse<UpdatedPoints>. - 3.0.0 —
createCampaignDetailStoreredeem(addressOverride?, options?)is nowredeem(addressOverride?, pointUnit?, options?). PassundefinedforpointUnitif you don't use point-service. - 3.0.4 —
useUserStore.customPointswas renamed touseUserStore.pointServiceBalance; expiring data is exposed viaexpiringPointService.
Development Commands
# Build the /zustand entry point (dist/)
npm run build
# Run tests with coverage
npm test
# Format source files
npm run format
# Upgrade @bzbs/react-api-client to latest
npm run update:client
# Publish — patch version bump
npm run patch
# Publish — minor version bump
npm run minor
# Publish — major version bump
npm run majorBuild output (single entry point, CJS + ESM + types):
| Entry point | CJS | ESM | Types |
|---|---|---|---|
| zustand.ts | dist/zustand.js | dist/zustand.mjs | dist/zustand.d.ts |
