Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | 520x | import { showMessage } from "@common/Toast";
import { useGetCurrentMerchantId } from "@hooks/common";
import flagsmith from "flagsmith";
import { IFlags } from "flagsmith/types";
import { useEffect, useState } from "react";
interface Feature {
id: number;
enabled: boolean;
value: string | null; // Assuming value can be null or a string
}
type EvaluationEvent = unknown;
interface Trait {
[key: string]: string;
}
export interface TFlagSmithState {
environmentID: string;
evaluationEvent: EvaluationEvent;
flags: {
[key: string]: Feature;
};
identity: number;
traits: Trait;
ts: null;
}
export const useGetFlags = () => {
const { merchantId, selectedUser } = useGetCurrentMerchantId();
const [flags, setFlags] = useState<Map<string, boolean>>(new Map());
const [isForceLoading, setIsForceLoading] = useState<boolean>(false);
const isFlagsmithLoading =
!flagsmith.loadingState?.error && flagsmith.loadingState?.isLoading;
// Basically each feature flag is create globally at environment level for all identities (users) in that environment
// Each identity/user can override the status of a particular flag for testing
// That's why we first Identify the user (if the user is not present - flagsmith adds a new identity for that user AUTOMATICALLY with the all the features set as their global state)
useEffect(() => {
const identifyUser = async () => {
setIsForceLoading(true);
try {
// In this case we're passing the entire user informations as traits (2nd parameter).
// Traits are optional filters, a way to identify a subset of identities (for example all the merchants with role Provider)
await flagsmith.identify(merchantId.toString(), {
...selectedUser,
id: merchantId,
});
let allFlags: IFlags<string> = {};
try {
allFlags = flagsmith.getAllFlags();
} catch (flagError) {
console.warn("Error getting all flags:", flagError);
// Fallback: try to get flags from state directly
const state = flagsmith.getState();
allFlags = state?.flags || {};
}
setFlags(
Object.keys(allFlags).reduce((all, key) => {
all.set(key, allFlags[key].enabled);
return all;
}, new Map<string, boolean>()),
);
} catch (error) {
//show a toast for proper error handling, and we need to properly initialize flagsmith for the app to work correctly.
//its better to let the user know something is wrong
if (flags.size < 1) {
//QA noticed random errors while switching account/control mode while refethcing flags
//we should still refetch flags, because we CAN have user/account based settings
//currently we do not have such settings in production, so its safe to show this message only on the first fetch while the flags are not loaded at all in the app
//we show the messgae only if there are no flags fetched yet, in this case it is crucial for the user to refresh to have an up-to date application
showMessage(
"Warning",
"Flagsmith was not properly initialized, it may cause issues in the application. Please try to refresh.",
undefined,
"Flagsmith identification failed",
10000,
);
return;
}
console.warn("Flagsmith initialization failed", error); //if we already have flags, we just add a console warning, to avoid unhandled errors on sentry
} finally {
setIsForceLoading(false);
}
};
identifyUser();
}, [merchantId, selectedUser]);
return {
flags,
flagsmithState: flagsmith.getState() as unknown as TFlagSmithState,
isLoading: isFlagsmithLoading || (isForceLoading && flags.size === 0),
};
};
|