diff --git a/package.json b/package.json
index c44da49..8fbf222 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,7 @@
"@types/rox-browser": "^5.0.7",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+ "react-router-dom": "^7.6.2",
"rox-browser": "^5.4.12"
},
"devDependencies": {
diff --git a/src/About.tsx b/src/About.tsx
new file mode 100644
index 0000000..b150cc4
--- /dev/null
+++ b/src/About.tsx
@@ -0,0 +1,13 @@
+export const About = () => {
+ return (
+
+
About
+
This is the about page of our application.
+
+ Here you can find information about the application, its features, and
+ how to use it.
+
+
Feel free to explore and learn more!
+
+ )
+}
diff --git a/src/App.tsx b/src/App.tsx
index 463b135..a2c4639 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,42 +1,63 @@
-import cbLogo from './assets/CB-stacked-logo-full-color.svg'
import './App.css'
-import {useFeatureFlags} from "./feature-management";
-import {LoadingIndicator} from "./LoadingIndicator.tsx";
+import { Outlet, Route, Routes } from 'react-router-dom'
+import { Home } from './Home.tsx'
+import { About } from './About.tsx'
+import { Nav } from './Nav.tsx'
+import { useFeatureFlag } from './useFeatureFlag.ts'
+import { namespaceFlags } from './feature-management/flags.ts'
-function App() {
-
- const featureFlags = useFeatureFlags()
+export const customRoutes = [
+ {
+ path: '/',
+ label: 'Home',
+ element: ,
+ index: true,
+ featureFlag: { namespace: 'routes', flag: 'home' },
+ },
+ {
+ path: 'about',
+ label: 'About',
+ element: ,
+ // featureFlag: { namespace: 'routes', flag: 'about' },
+ },
+]
- if (featureFlags.loading) {
- return (
-
-
-
- );
- }
+const Layout = () => (
+
+
+
+
+)
+function App() {
return (
<>
- CloudBees feature management React sample application
-
- {featureFlags.showMessage.isEnabled() && (
-
- {featureFlags.message.getValue()}
-
- )}
-
-
-
-
- Sign in to the CloudBees platform below to modify flag values and see the changes reflected automatically in this application.
-
-
-
-
-
-
+
+ }>
+ {customRoutes.map((route, index) => {
+ const routeFlag = route.featureFlag
+ ? route.featureFlag.namespace === 'routes' && route.featureFlag.flag === 'about'
+ ? true
+ : useFeatureFlag(
+ namespaceFlags[route.featureFlag.namespace][
+ route.featureFlag.flag
+ ]
+ )
+ : true
+ return routeFlag ? (
+
+ ) : null
+ })}
+ 404 Not Found} />
+
+
>
)
}
-export default App
+export default App
\ No newline at end of file
diff --git a/src/Home.tsx b/src/Home.tsx
new file mode 100644
index 0000000..ac03c1a
--- /dev/null
+++ b/src/Home.tsx
@@ -0,0 +1,46 @@
+import { useFeatureFlags } from './feature-management'
+import { namespaceFlags } from './feature-management/flags'
+import { LoadingIndicator } from './LoadingIndicator'
+import { useFeatureFlag } from './useFeatureFlag'
+import cbLogo from './assets/CB-stacked-logo-full-color.svg'
+
+export const Home = () => {
+ const featureFlags = useFeatureFlags()
+
+ const showMessage = useFeatureFlag(namespaceFlags.default.showMessage)
+
+ if (featureFlags.loading) {
+ return (
+
+
+
+ )
+ }
+ return (
+ <>
+ CloudBees feature management React sample application
+
+ {showMessage && (
+
+ {featureFlags.default.message.getValue()}
+
+ )}
+
+
+
+
+ Sign in to the CloudBees platform below to modify flag values and see
+ the changes reflected automatically in this application.
+
+
+
+
+
+ >
+ )
+}
diff --git a/src/LoadingIndicator.tsx b/src/LoadingIndicator.tsx
index bf664ff..eff0556 100644
--- a/src/LoadingIndicator.tsx
+++ b/src/LoadingIndicator.tsx
@@ -1,9 +1,11 @@
export const LoadingIndicator = () => {
- return (
+ const showMessage = true // This can be set based on your application's state
+
+ return showMessage ? (
- )
-}
\ No newline at end of file
+ ) : null
+}
diff --git a/src/Nav.tsx b/src/Nav.tsx
new file mode 100644
index 0000000..c165ee4
--- /dev/null
+++ b/src/Nav.tsx
@@ -0,0 +1,29 @@
+// components/NavBar.js
+import { Link } from 'react-router-dom'
+import { useFeatureFlag } from './useFeatureFlag'
+import { customRoutes } from './App'
+import { namespaceFlags } from './feature-management/flags'
+
+export const Nav = () => {
+ const enabledRoutes = customRoutes.filter((route) => {
+ const routeFlag = route.featureFlag
+ ? route.featureFlag.namespace === 'routes' && route.featureFlag.flag === 'about'
+ ? true
+ : useFeatureFlag(
+ namespaceFlags[route.featureFlag.namespace][route.featureFlag.flag]
+ )
+ : true
+ return routeFlag
+ })
+
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/src/feature-management/FeatureFlagsProvider.tsx b/src/feature-management/FeatureFlagsProvider.tsx
index 621d713..fc5e530 100644
--- a/src/feature-management/FeatureFlagsProvider.tsx
+++ b/src/feature-management/FeatureFlagsProvider.tsx
@@ -1,17 +1,16 @@
-import React, {useEffect, useState} from "react";
-import Rox from "rox-browser";
-import {flags} from "./flags.ts";
-import {FeatureFlagsContext, initialFlagState} from "./index.ts";
+import React, { useEffect, useState } from 'react'
+import Rox from 'rox-browser'
+import { namespaceFlags } from './flags.ts'
+import { FeatureFlagsContext, initialFlagState } from './index.ts'
// TODO: insert your SDK key from https://cloudbees.io/ below.
const sdkKey = ''
type Props = {
children?: React.ReactNode
-};
-
-export const FeatureFlagsProvider = ({children} : Props): React.ReactNode => {
+}
+export const FeatureFlagsProvider = ({ children }: Props): React.ReactNode => {
const [flagState, setFlagState] = useState(initialFlagState)
const [error, setError] = useState(undefined)
@@ -24,44 +23,45 @@ export const FeatureFlagsProvider = ({children} : Props): React.ReactNode => {
}
initialised.current = true
- setFlagState({...flagState, loading: true})
-
- Rox.register('', flags)
+ setFlagState({ ...flagState, loading: true })
- const initFeatureFlags = async() => {
+ // Register the flags
+ Object.keys(namespaceFlags).forEach((namespace) => {
+ const flagsUnderNamespace = (namespaceFlags as any)[namespace]
+ Rox.register(namespace, flagsUnderNamespace)
+ })
+ const initFeatureFlags = async () => {
// Easy to forget to insert your SDK key where shown above, so let's check & remind you!
// @ts-ignore
if (sdkKey === '') {
- throw new Error("You haven't yet inserted your SDK key into FeatureFlagsProvider.tsx - the application below will not update until you do so. Please check the README.adoc for instructions.")
+ throw new Error()
+ // "You haven't yet inserted your SDK key into FeatureFlagsProvider.tsx - the application below will not update until you do so. Please check the README.adoc for instructions."
}
await Rox.setup(sdkKey, {
configurationFetchedHandler(fetcherResult: Rox.RoxFetcherResult) {
- if (fetcherResult.fetcherStatus === "APPLIED_FROM_NETWORK") {
+ if (fetcherResult.fetcherStatus === 'APPLIED_FROM_NETWORK') {
setFlagState({
...flagState,
})
}
- }
+ },
})
- setFlagState({...flagState, loading: false})
+ setFlagState({ ...flagState, loading: false })
}
initFeatureFlags().catch((e) => {
console.error(e.message)
setError(e.message)
- setFlagState({...flagState, loading: false})
+ setFlagState({ ...flagState, loading: false })
})
-
}, [flagState])
return (
- {error && (
- {error}
- )}
+ {error && {error}
}
{children}
diff --git a/src/feature-management/flags.ts b/src/feature-management/flags.ts
index f93673f..0f7492d 100644
--- a/src/feature-management/flags.ts
+++ b/src/feature-management/flags.ts
@@ -1,18 +1,28 @@
-import {Flag, RoxString, RoxNumber} from "rox-browser";
+import { Flag, RoxString, RoxNumber } from 'rox-browser'
-type IFeatureFlags = typeof flags
+type IFeatureFlags = typeof namespaceFlags
export interface IFeatureFlagsState extends IFeatureFlags {
- loading: boolean;
+ loading: boolean
}
-export const flags = {
- // Boolean - should the message be shown?
- showMessage: new Flag(),
- // String - the message to show.
- message: new RoxString('This is the default message; try changing some flag values!'),
- // String (with options) - the color of the message text.
- fontColor: new RoxString('Black', ['Red', 'Green', 'Blue', 'Black']),
- // Number (with options) - the size of the message text.
- fontSize: new RoxNumber(12, [12, 16, 24]),
-}
+export const namespaceFlags = {
+ namespace: {
+ namespacedFlag: new Flag(),
+ },
+ routes: {
+ home: new Flag(true),
+ },
+ default: {
+ // Boolean - should the message be shown?
+ showMessage: new Flag(),
+ // String - the message to show.
+ message: new RoxString(
+ 'This is the default message; try changing some flag values!'
+ ),
+ // String (with options) - the color of the message text.
+ fontColor: new RoxString('White', ['Red', 'Green', 'Blue', 'Black']),
+ // Number (with options) - the size of the message text.
+ fontSize: new RoxNumber(24, [12, 16, 24]),
+ },
+}
\ No newline at end of file
diff --git a/src/feature-management/index.ts b/src/feature-management/index.ts
index 76e54e1..a390912 100644
--- a/src/feature-management/index.ts
+++ b/src/feature-management/index.ts
@@ -1,11 +1,12 @@
-import {createContext, useContext} from "react";
-import {flags, IFeatureFlagsState} from "./flags.ts";
+import { createContext, useContext } from 'react'
+import { namespaceFlags, IFeatureFlagsState } from './flags.ts'
export const initialFlagState: IFeatureFlagsState = {
- ...flags,
- loading: false
+ ...namespaceFlags,
+ loading: false,
}
-export const FeatureFlagsContext = createContext(initialFlagState);
+export const FeatureFlagsContext = createContext(initialFlagState)
-export const useFeatureFlags: () => IFeatureFlagsState = () => useContext(FeatureFlagsContext);
\ No newline at end of file
+export const useFeatureFlags: () => IFeatureFlagsState = () =>
+ useContext(FeatureFlagsContext)
diff --git a/src/main.tsx b/src/main.tsx
index f5fec51..cb42f1c 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -2,12 +2,15 @@ import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
-import {FeatureFlagsProvider} from "./feature-management/FeatureFlagsProvider.tsx";
+import { FeatureFlagsProvider } from './feature-management/FeatureFlagsProvider.tsx'
+import { BrowserRouter } from 'react-router-dom'
ReactDOM.createRoot(document.getElementById('root')!).render(
-
+
+
+
- ,
+
)
diff --git a/src/useFeatureFlag.ts b/src/useFeatureFlag.ts
new file mode 100644
index 0000000..b291666
--- /dev/null
+++ b/src/useFeatureFlag.ts
@@ -0,0 +1,21 @@
+import { useMemo } from 'react'
+import { type RoxNumber, type RoxString, Flag } from 'rox-browser'
+
+type FlagValue = T extends Flag
+ ? boolean
+ : T extends RoxString
+ ? string
+ : T extends RoxNumber
+ ? number
+ : never
+
+export const useFeatureFlag = (
+ flag: T
+) =>
+ useMemo>(() => {
+ if (flag instanceof Flag) {
+ return flag.isEnabled() as FlagValue
+ } else {
+ return flag.getValue() as FlagValue
+ }
+ }, [flag])
diff --git a/yarn.lock b/yarn.lock
index 7b7cc97..cdfdb01 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -836,6 +836,11 @@ convert-source-map@^2.0.0:
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
+cookie@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/cookie/-/cookie-1.0.2.tgz#27360701532116bd3f1f9416929d176afe1e4610"
+ integrity sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==
+
cross-spawn@^7.0.2:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
@@ -1500,6 +1505,21 @@ react-refresh@^0.14.0:
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.2.tgz#3833da01ce32da470f1f936b9d477da5c7028bf9"
integrity sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==
+react-router-dom@^7.6.2:
+ version "7.6.2"
+ resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-7.6.2.tgz#e97e386ab390b6503a2a7968124b7a3237fb10c7"
+ integrity sha512-Q8zb6VlTbdYKK5JJBLQEN06oTUa/RAbG/oQS1auK1I0TbJOXktqm+QENEVJU6QvWynlXPRBXI3fiOQcSEA78rA==
+ dependencies:
+ react-router "7.6.2"
+
+react-router@7.6.2:
+ version "7.6.2"
+ resolved "https://registry.yarnpkg.com/react-router/-/react-router-7.6.2.tgz#9f48b343bead7d0a94e28342fc4f9ae29131520e"
+ integrity sha512-U7Nv3y+bMimgWjhlT5CRdzHPu2/KVmqPwKUCChW8en5P3znxUqwlYFlbmyj8Rgp1SF6zs5X4+77kBVknkg6a0w==
+ dependencies:
+ cookie "^1.0.1"
+ set-cookie-parser "^2.6.0"
+
react@^18.2.0:
version "18.3.1"
resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891"
@@ -1580,6 +1600,11 @@ semver@^7.6.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13"
integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==
+set-cookie-parser@^2.6.0:
+ version "2.7.1"
+ resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz#3016f150072202dfbe90fadee053573cc89d2943"
+ integrity sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==
+
shebang-command@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"